[
  {
    "path": ".gitignore",
    "content": "# Created by .ignore support plugin (hsz.mobi)\n### Example user template template\n### Example user template\n\n# IntelliJ project files\n.idea\n*.iml\nout\ngen\n/target/\n\n\n\n"
  },
  {
    "path": "ReadMe.md",
    "content": "<div align=\"center\">\n<a href=\"https://github.com/h2pl/Java-Tutorial\">\n    <img src=\"https://java-tutorial.oss-cn-shanghai.aliyuncs.com/Javatutorial-v1.png\" width=\"300\" height=\"300\"/>\n</a>\n</div>\n\n<p>\n<div align=\"center\">\n    <a href=\"https://github.com/h2pl/Java-Tutorial\"><img src=\"https://img.shields.io/github/stars/h2pl/JavaTutorial.svg\"></a>\n    <a href=\"https://github.com/h2pl/Java-Tutorial\"><img src=\"https://img.shields.io/github/forks/h2pl/JavaTutorial.svg\"></a>\n    <a href=\"https://github.com/h2pl/Java-Tutorial\"><img src=\"https://img.shields.io/github/issues/h2pl/JavaTutorial.svg\"></a>\n    <a href=\"https://github.com/h2pl/Java-Tutorial\"><img src=\"https://img.shields.io/github/license/h2pl/JavaTutorial.svg\"></a>\n    <a href=\"https://github.com/h2pl/Java-Tutorial\"><img src=\"https://img.shields.io/github/last-commit/h2pl/JavaTutorial.svg\"></a>\n    <a href=\"https://github.com/h2pl/Java-Tutorial\"><img src=\"https://img.shields.io/github/release/h2pl/JavaTutorial.svg\"></a>\n</div>\n</p>\n\n力求打造最完整最实用的Java工程师学习指南！\n\n这些文章和总结都是我近几年学习Java总结和整理出来的，非常实用，对于学习Java后端的朋友来说应该是最全面最完整的技术仓库。\n我靠着这些内容进行复习，拿到了BAT等大厂的offer，这个仓库也已经帮助了很多的Java学习者，如果对你有用，希望能给个star支持我，谢谢！\n\n为了更好地讲清楚每个知识模块，我们也参考了很多网上的优质博文，力求不漏掉每一个知识点，所有参考博文都将声明转载来源，如有侵权，请联系我。\n\n点击关注[微信公众号](#微信公众号)及时获取笔主最新更新文章，并可免费领取Java工程师必备学习资源\n\n<p align=\"center\">\n<a href=\"https://github.com/h2pl/Java-Tutorial\" target=\"_blank\">\n    <img src=\"https://java-tutorial.oss-cn-shanghai.aliyuncs.com/Javatutorial.jpeg\" width=\"900\" height=\"300\"/>\n</a>\n</p>\n\n<p>\n    \n# Java基础\n\n## 基础知识\n* [面向对象基础](docs/Java/basic/面向对象基础.md)\n* [Java基本数据类型](docs/Java/basic/Java基本数据类型.md)\n* [string和包装类](docs/Java/basic/string和包装类.md)\n* [final关键字特性](docs/Java/basic/final关键字特性.md)\n* [Java类和包](docs/Java/basic/Java类和包.md)\n* [抽象类和接口](docs/Java/basic/抽象类和接口.md)\n* [代码块和代码执行顺序](docs/Java/basic/代码块和代码执行顺序.md)\n* [Java自动拆箱装箱里隐藏的秘密](docs/Java/basic/Java自动拆箱装箱里隐藏的秘密.md)\n* [Java中的Class类和Object类](docs/Java/basic/Java中的Class类和Object类.md)\n* [Java异常](docs/Java/basic/Java异常.md)\n* [解读Java中的回调](docs/Java/basic/解读Java中的回调.md)\n* [反射](docs/Java/basic/反射.md)\n* [泛型](docs/Java/basic/泛型.md)\n* [枚举类](docs/Java/basic/枚举类.md)\n* [Java注解和最佳实践](docs/Java/basic/Java注解和最佳实践.md)\n* [JavaIO流](docs/Java/basic/JavaIO流.md)\n* [多线程](docs/Java/basic/多线程.md)\n* [深入理解内部类](docs/Java/basic/深入理解内部类.md)\n* [javac和javap](docs/Java/basic/javac和javap.md)\n* [Java8新特性终极指南](docs/Java/basic/Java8新特性终极指南.md)\n* [序列化和反序列化](docs/Java/basic/序列化和反序列化.md)\n* [继承封装多态的实现原理](docs/Java/basic/继承封装多态的实现原理.md)\n\n## 集合类\n\n* [Java集合类总结](docs/Java/collection/Java集合类总结.md)\n* [Java集合详解：一文读懂ArrayList,Vector与Stack使用方法和实现原理](docs/Java/collection/Java集合详解：一文读懂ArrayList,Vector与Stack使用方法和实现原理.md)  \n* [Java集合详解：Queue和LinkedList](docs/Java/collection/Java集合详解：Queue和LinkedList.md)\n* [Java集合详解：Iterator，fail-fast机制与比较器](docs/Java/collection/Java集合详解：Iterator，fail-fast机制与比较器.md)\n* [Java集合详解：HashMap和HashTable](docs/Java/collection/Java集合详解：HashMap和HashTable.md)\n* [Java集合详解：深入理解LinkedHashMap和LRU缓存](docs/Java/collection/Java集合详解：深入理解LinkedHashMap和LRU缓存.md)\n* [Java集合详解：TreeMap和红黑树](docs/Java/collection/Java集合详解：TreeMap和红黑树.md)\n* [Java集合详解：HashSet，TreeSet与LinkedHashSet](docs/Java/collection/Java集合详解：HashSet，TreeSet与LinkedHashSet.md)\n* [Java集合详解：Java集合类细节精讲](docs/Java/collection/Java集合详解：Java集合类细节精讲.md)\n\n# JavaWeb\n\n* [走进JavaWeb技术世界：JavaWeb的由来和基础知识](docs/JavaWeb/走进JavaWeb技术世界：JavaWeb的由来和基础知识.md)\n* [走进JavaWeb技术世界：JSP与Servlet的曾经与现在](docs/JavaWeb/走进JavaWeb技术世界：JSP与Servlet的曾经与现在.md)\n* [走进JavaWeb技术世界：JDBC的进化与连接池技术](docs/JavaWeb/走进JavaWeb技术世界：JDBC的进化与连接池技术.md)\n* [走进JavaWeb技术世界：Servlet工作原理详解](docs/JavaWeb/走进JavaWeb技术世界：Servlet工作原理详解.md)\n* [走进JavaWeb技术世界：初探Tomcat的HTTP请求过程](docs/JavaWeb/走进JavaWeb技术世界：初探Tomcat的HTTP请求过程.md)\n* [走进JavaWeb技术世界：Tomcat5总体架构剖析](docs/JavaWeb/走进JavaWeb技术世界：Tomcat5总体架构剖析.md)\n* [走进JavaWeb技术世界：Tomcat和其他WEB容器的区别](docs/JavaWeb/走进JavaWeb技术世界：Tomcat和其他WEB容器的区别.md)\n* [走进JavaWeb技术世界：浅析Tomcat9请求处理流程与启动部署过程](docs/JavaWeb/走进JavaWeb技术世界：浅析Tomcat9请求处理流程与启动部署过程.md)\n* [走进JavaWeb技术世界：Java日志系统的诞生与发展](docs/JavaWeb/走进JavaWeb技术世界：Java日志系统的诞生与发展.md)\n* [走进JavaWeb技术世界：从JavaBean讲到Spring](docs/JavaWeb/走进JavaWeb技术世界：从JavaBean讲到Spring.md)\n* [走进JavaWeb技术世界：单元测试框架Junit](docs/JavaWeb/走进JavaWeb技术世界：单元测试框架Junit.md)\n* [走进JavaWeb技术世界：从手动编译打包到项目构建工具Maven](docs/JavaWeb/走进JavaWeb技术世界：从手动编译打包到项目构建工具Maven.md)\n* [走进JavaWeb技术世界：Hibernate入门经典与注解式开发](docs/JavaWeb/走进JavaWeb技术世界：Hibernate入门经典与注解式开发.md)\n* [走进JavaWeb技术世界：Mybatis入门](docs/JavaWeb/走进JavaWeb技术世界：Mybatis入门.md)\n* [走进JavaWeb技术世界：深入浅出Mybatis基本原理](docs/JavaWeb/走进JavaWeb技术世界：深入浅出Mybatis基本原理.md)\n* [走进JavaWeb技术世界：极简配置的SpringBoot](docs/JavaWeb/走进JavaWeb技术世界：极简配置的SpringBoot.md)\n\n# Java进阶\n\n## 并发编程\n\n* [Java并发指南：并发基础与Java多线程](docs/Java/concurrency/Java并发指南：并发基础与Java多线程.md)\n* [Java并发指南：深入理解Java内存模型JMM](docs/Java/concurrency/Java并发指南：深入理解Java内存模型JMM.md)\n* [Java并发指南：并发三大问题与volatile关键字，CAS操作](docs/Java/concurrency/Java并发指南：并发三大问题与volatile关键字，CAS操作.md)\n* [Java并发指南：Java中的锁Lock和synchronized](docs/Java/concurrency/Java并发指南：Java中的锁Lock和synchronized.md)\n* [Java并发指南：JMM中的final关键字解析](docs/Java/concurrency/Java并发指南：JMM中的final关键字解析.md)\n* [Java并发指南：Java内存模型JMM总结](docs/Java/concurrency/Java并发指南：Java内存模型JMM总结.md)\n* [Java并发指南：JUC的核心类AQS详解](docs/Java/concurrency/Java并发指南：JUC的核心类AQS详解.md)\n* [Java并发指南：AQS中的公平锁与非公平锁，Condtion](docs/Java/concurrency/Java并发指南：AQS中的公平锁与非公平锁，Condtion.md)\n* [Java并发指南：AQS共享模式与并发工具类的实现](docs/Java/concurrency/Java并发指南：AQS共享模式与并发工具类的实现.md)\n* [Java并发指南：Java读写锁ReentrantReadWriteLock源码分析](docs/Java/concurrency/Java并发指南：Java读写锁ReentrantReadWriteLock源码分析.md)\n* [Java并发指南：解读Java阻塞队列BlockingQueue](docs/Java/concurrency/Java并发指南：解读Java阻塞队列BlockingQueue.md)\n* [Java并发指南：深度解读java线程池设计思想及源码实现](docs/Java/concurrency/Java并发指南：深度解读Java线程池设计思想及源码实现.md)\n* [Java并发指南：Java中的HashMap和ConcurrentHashMap全解析](docs/Java/concurrency/Java并发指南：Java中的HashMap和ConcurrentHashMap全解析.md)\n* [Java并发指南：JUC中常用的Unsafe和Locksupport](docs/Java/concurrency/Java并发指南：JUC中常用的Unsafe和Locksupport.md)\n* [Java并发指南：ForkJoin并发框架与工作窃取算法剖析](docs/Java/concurrency/Java并发指南：ForkJoin并发框架与工作窃取算法剖析.md)\n* [Java并发编程学习总结](docs/Java/concurrency/Java并发编程学习总结.md)\n\n## JVM\n\n* [JVM总结](docs/Java/JVM/JVM总结.md)\n* [深入理解JVM虚拟机：JVM内存的结构与消失的永久代](docs/Java/JVM/深入理解JVM虚拟机：JVM内存的结构与消失的永久代.md)\n* [深入理解JVM虚拟机：JVM垃圾回收基本原理和算法](docs/Java/JVM/深入理解JVM虚拟机：JVM垃圾回收基本原理和算法.md)\n* [深入理解JVM虚拟机：垃圾回收器详解](docs/Java/JVM/深入理解JVM虚拟机：垃圾回收器详解.md)\n* [深入理解JVM虚拟机：Javaclass介绍与解析实践](docs/Java/JVM/深入理解JVM虚拟机：Java字节码介绍与解析实践.md)\n* [深入理解JVM虚拟机：虚拟机字节码执行引擎](docs/Java/JVM/深入理解JVM虚拟机：虚拟机字节码执行引擎.md)\n* [深入理解JVM虚拟机：深入理解JVM类加载机制](docs/Java/JVM/深入理解JVM虚拟机：深入理解JVM类加载机制.md)\n* [深入理解JVM虚拟机：JNDI，OSGI，Tomcat类加载器实现](docs/Java/JVM/深入理解JVM虚拟机：JNDI，OSGI，Tomcat类加载器实现.md)\n* [深入了解JVM虚拟机：Java的编译期优化与运行期优化](docs/Java/JVM/深入理解JVM虚拟机：Java的编译期优化与运行期优化.md)\n* [深入理解JVM虚拟机：JVM监控工具与诊断实践](docs/Java/JVM/深入理解JVM虚拟机：JVM监控工具与诊断实践.md)\n* [深入理解JVM虚拟机：JVM常用参数以及调优实践](docs/Java/JVM/深入理解JVM虚拟机：JVM常用参数以及调优实践.md)\n* [深入理解JVM虚拟机：Java内存异常原理与实践](docs/Java/JVM/深入理解JVM虚拟机：Java内存异常原理与实践.md)\n* [深入理解JVM虚拟机：JVM性能管理神器VisualVM介绍与实战](docs/Java/JVM/深入理解JVM虚拟机：JVM性能管理神器VisualVM介绍与实战.md)\n* [深入理解JVM虚拟机：再谈四种引用及GC实践](docs/Java/JVM/深入理解JVM虚拟机：再谈四种引用及GC实践.md)\n* [深入理解JVM虚拟机：GC调优思路与常用工具](docs/Java/JVM/深入理解JVM虚拟机：GC调优思路与常用工具.md)\n\n## Java网络编程\n\n* [Java网络编程和NIO详解：JAVA 中原生的 socket 通信机制](docs/Java/network/Java网络编程与NIO详解：JAVA中原生的socket通信机制.md)\n* [Java网络编程与NIO详解：JAVA NIO 一步步构建IO多路复用的请求模型](docs/Java/network/Java网络编程与NIO详解：JavaNIO一步步构建IO多路复用的请求模型.md) \n* [Java网络编程和NIO详解：IO模型与Java网络编程模型](docs/Java/network/Java网络编程与NIO详解：IO模型与Java网络编程模型.md) \n* [Java网络编程与NIO详解：浅析NIO包中的BufferChannel和Selector](docs/Java/network/Java网络编程与NIO详解：浅析NIO包中的BufferChannel和Selector.md) \n* [Java网络编程和NIO详解：Java非阻塞IO和异步IO](docs/Java/network/Java网络编程与NIO详解：Java非阻塞IO和异步IO.md)\n* [Java网络编程与NIO详解：LinuxEpoll实现原理详解](docs/Java/network/Java网络编程与NIO详解：LinuxEpoll实现原理详解.md.md) \n* [Java网络编程与NIO详解：浅谈Linux中Selector的实现原理](docs/Java/network/Java网络编程与NIO详解：浅谈Linux中Selector的实现原理.md)\n* [Java网络编程与NIO详解：浅析mmap和DirectBuffer](docs/Java/network/Java网络编程与NIO详解：浅析mmap和DirectBuffer.md)\n* [Java网络编程与NIO详解：基于NIO的网络编程框架Netty](docs/Java/network/Java网络编程与NIO详解：基于NIO的网络编程框架Netty.md)\n* [Java网络编程与NIO详解：Java网络编程与NIO详解](docs/Java/network/Java网络编程与NIO详解：深度解读Tomcat中的NIO模型.md)\n* [Java网络编程与NIO详解：Tomcat中的Connector源码分析（NIO）](docs/Java/network/Java网络编程与NIO详解：Tomcat中的Connector源码分析（NIO）.md)\n\n# Spring全家桶\n\n## Spring\n\n* [SpringAOP的概念与作用](docs/Spring全家桶/Spring/Spring常见注解.md)\n* [SpringBean的定义与管理（核心）](docs/Spring全家桶/Spring/Spring常见注解.md)\n* [Spring中对于数据库的访问](docs/Spring全家桶/Spring/Spring常见注解.md)\n* [Spring中对于校验功能的支持](docs/Spring全家桶/Spring/Spring常见注解.md)\n* [Spring中的Environment环境变量](docs/Spring全家桶/Spring/Spring常见注解.md)\n* [Spring中的事件处理机制](docs/Spring全家桶/Spring/Spring常见注解.md)\n* [Spring中的资源管理](docs/Spring全家桶/Spring/Spring常见注解.md)\n* [Spring中的配置元数据（管理配置的基本数据）](docs/Spring全家桶/Spring/Spring常见注解.md)\n* [Spring事务基本用法](docs/Spring全家桶/Spring/Spring常见注解.md)\n* [Spring合集](docs/Spring全家桶/Spring/Spring常见注解.md)\n* [Spring容器与IOC](docs/Spring全家桶/Spring/Spring常见注解.md)\n* [Spring常见注解](docs/Spring全家桶/Spring/Spring常见注解.md)\n* [Spring概述](docs/Spring全家桶/Spring/Spring常见注解.md)\n* [第一个Spring应用](docs/Spring全家桶/Spring/Spring常见注解.md)\n\n## Spring源码分析\n\n### 综合\n* [Spring源码剖析：初探SpringIOC核心流程](docs/Spring全家桶/Spring源码分析/Spring源码剖析：初探SpringIOC核心流程.md)\n* [Spring源码剖析：SpringIOC容器的加载过程 ](docs/Spring全家桶/Spring源码分析/Spring源码剖析：SpringIOC容器的加载过程.md)\n* [Spring源码剖析：懒加载的单例Bean获取过程分析](docs/Spring全家桶/Spring源码分析/Spring源码剖析：懒加载的单例Bean获取过程分析.md)\n* [Spring源码剖析：JDK和cglib动态代理原理详解 ](docs/Spring全家桶/Spring源码分析/Spring源码剖析：JDK和cglib动态代理原理详解.md)\n* [Spring源码剖析：SpringAOP概述](docs/Spring全家桶/Spring源码分析/Spring源码剖析：SpringAOP概述.md)\n* [Spring源码剖析：AOP实现原理详解 ](docs/Spring全家桶/Spring源码分析/Spring源码剖析：AOP实现原理详解.md)\n* [Spring源码剖析：Spring事务概述](docs/Spring全家桶/Spring源码分析/Spring源码剖析：Spring事务概述.md)\n* [Spring源码剖析：Spring事务源码剖析](docs/Spring全家桶/Spring源码分析/Spring源码剖析：Spring事务源码剖析.md)\n\n### AOP\n* [AnnotationAwareAspectJAutoProxyCreator 分析（上）](docs/Spring全家桶/Spring源码分析/SpringAOP/AnnotationAwareAspectJAutoProxyCreator分析（上）.md)\n* [AnnotationAwareAspectJAutoProxyCreator 分析（下）](docs/Spring全家桶/Spring源码分析/SpringAOP/AnnotationAwareAspectJAutoProxyCreator分析（下）.md)\n* [AOP示例demo及@EnableAspectJAutoProxy](docs/Spring全家桶/Spring源码分析/SpringAOP/AOP示例demo及@EnableAspectJAutoProxy.md)\n* [SpringAop（四）：jdk 动态代理](docs/Spring全家桶/Spring源码分析/SpringAOP/SpringAop（四）：jdk动态代理.md)\n* [SpringAop（五）：cglib 代理](docs/Spring全家桶/Spring源码分析/SpringAOP/SpringAop（五）：cglib代理.md)\n* [SpringAop（六）：aop 总结](docs/Spring全家桶/Spring源码分析/SpringAOP/SpringAop（六）：aop总结.md)\n\n### 事务\n* [spring 事务（一）：认识事务组件](docs/Spring全家桶/Spring源码分析/Spring事务/Spring事务（一）：认识事务组件.md)\n* [spring 事务（二）：事务的执行流程](docs/Spring全家桶/Spring源码分析/Spring事务/Spring事务（二）：事务的执行流程.md)\n* [spring 事务（三）：事务的隔离级别与传播方式的处理](docs/Spring全家桶/Spring源码分析/Spring事务/Spring事务（三）：事务的隔离级别与传播方式的处理01.md)\n* [spring 事务（四）：事务的隔离级别与传播方式的处理](docs/Spring全家桶/Spring源码分析/Spring事务/Spring事务（四）：事务的隔离级别与传播方式的处理02.md)\n* [spring 事务（五）：事务的隔离级别与传播方式的处理](docs/Spring全家桶/Spring源码分析/Spring事务/Spring事务（五）：事务的隔离级别与传播方式的处理03.md)\n* [spring 事务（六）：事务的隔离级别与传播方式的处理](docs/Spring全家桶/Spring源码分析/Spring事务/Spring事务（六）：事务的隔离级别与传播方式的处理04.md)\n\n### 启动流程\n* [spring启动流程（一）：启动流程概览](docs/Spring全家桶/Spring源码分析/Spring启动流程/Spring启动流程（一）：启动流程概览.md)\n* [spring启动流程（二）：ApplicationContext 的创建](docs/Spring全家桶/Spring源码分析/Spring启动流程/Spring启动流程（二）：ApplicationContext的创建.md)\n* [spring启动流程（三）：包的扫描流程](docs/Spring全家桶/Spring源码分析/Spring启动流程/Spring启动流程（三）：包的扫描流程.md)\n* [spring启动流程（四）：启动前的准备工作](docs/Spring全家桶/Spring源码分析/Spring启动流程/Spring启动流程（四）：启动前的准备工作.md)\n* [spring启动流程（五）：执行 BeanFactoryPostProcessor](docs/Spring全家桶/Spring源码分析/Spring启动流程/Spring启动流程（五）：执行BeanFactoryPostProcessor.md)\n* [spring启动流程（六）：注册 BeanPostProcessor](docs/Spring全家桶/Spring源码分析/Spring启动流程/Spring启动流程（六）：注册BeanPostProcessor.md)\n* [spring启动流程（七）：国际化与事件处理](docs/Spring全家桶/Spring源码分析/Spring启动流程/Spring启动流程（七）：国际化与事件处理.md)\n* [spring启动流程（八）：完成 BeanFactory 的初始化](docs/Spring全家桶/Spring源码分析/Spring启动流程/Spring启动流程（八）：完成BeanFactory的初始化.md)\n* [spring启动流程（九）：单例 bean 的创建](docs/Spring全家桶/Spring源码分析/Spring启动流程/Spring启动流程（九）：单例bean的创建.md)\n* [spring启动流程（十）：启动完成的处理](docs/Spring全家桶/Spring源码分析/Spring启动流程/Spring启动流程（十）：启动完成的处理.md)\n* [spring启动流程（十一）：启动流程总结](docs/Spring全家桶/Spring源码分析/Spring启动流程/Spring启动流程（十一）：启动流程总结.md)\n\n### 组件分析\n* [spring 组件之 ApplicationContext](docs/Spring全家桶/Spring源码分析/Spring组件分析/Spring组件之ApplicationContext.md)\n* [spring 组件之 BeanDefinition](docs/Spring全家桶/Spring源码分析/Spring组件分析/Spring组件之BeanDefinition.md)\n* [Spring 组件之 BeanFactory](docs/Spring全家桶/Spring源码分析/Spring组件分析/Spring组件之BeanFactory.md)\n* [spring 组件之 BeanFactoryPostProcessor](docs/Spring全家桶/Spring源码分析/Spring组件分析/Spring组件之BeanFactoryPostProcessor.md)\n* [spring 组件之 BeanPostProcessor](docs/Spring全家桶/Spring源码分析/Spring组件分析/Spring组件之BeanPostProcessor.md)\n\n### 重要机制探秘\n\n* [ConfigurationClassPostProcessor（一）：处理 @ComponentScan 注解](docs/Spring全家桶/Spring源码分析/Spring重要机制探秘/ConfigurationClassPostProcessor（一）：处理@ComponentScan注解.md)\n* [ConfigurationClassPostProcessor（三）：处理 @Import 注解](docs/Spring全家桶/Spring源码分析/Spring重要机制探秘/ConfigurationClassPostProcessor（三）：处理@Import注解.md)\n* [ConfigurationClassPostProcessor（二）：处理 @Bean 注解](docs/Spring全家桶/Spring源码分析/Spring重要机制探秘/ConfigurationClassPostProcessor（二）：处理@Bean注解.md)\n* [ConfigurationClassPostProcessor（四）：处理 @Conditional 注解](docs/Spring全家桶/Spring源码分析/Spring重要机制探秘/ConfigurationClassPostProcessor（四）：处理@Conditional注解.md)\n* [Spring 探秘之 AOP 的执行顺序](docs/Spring全家桶/Spring源码分析/Spring重要机制探秘/Spring探秘之AOP的执行顺序.md)\n* [Spring 探秘之 Spring 事件机制](docs/Spring全家桶/Spring源码分析/Spring重要机制探秘/Spring探秘之Spring事件机制.md)\n* [spring 探秘之循环依赖的解决（一）：理论基石](docs/Spring全家桶/Spring源码分析/Spring重要机制探秘/Spring探秘之循环依赖的解决（一）：理论基石.md)\n* [spring 探秘之循环依赖的解决（二）：源码分析](docs/Spring全家桶/Spring源码分析/Spring重要机制探秘/Spring探秘之循环依赖的解决（二）：源码分析.md)\n* [spring 探秘之监听器注解 @EventListener](docs/Spring全家桶/Spring源码分析/Spring重要机制探秘/spring探秘之监听器注解@EventListener.md)\n* [spring 探秘之组合注解的处理](docs/Spring全家桶/Spring源码分析/Spring重要机制探秘/Spring探秘之组合注解的处理.md)\n\n## SpringMVC\n\n* [SpringMVC中的国际化功能](docs/Spring全家桶/SpringMVC/SpringMVC中的国际化功能.md)\n* [SpringMVC中的异常处理器](docs/Spring全家桶/SpringMVC/SpringMVC中的异常处理器.md)\n* [SpringMVC中的拦截器](docs/Spring全家桶/SpringMVC/SpringMVC中的拦截器.md)\n* [SpringMVC中的视图解析器](docs/Spring全家桶/SpringMVC/SpringMVC中的视图解析器.md)\n* [SpringMVC中的过滤器Filter](docs/Spring全家桶/SpringMVC/SpringMVC中的过滤器Filter.md)\n* [SpringMVC基本介绍与快速入门](docs/Spring全家桶/SpringMVC/SpringMVC基本介绍与快速入门.md)\n* [SpringMVC如何实现文件上传](docs/Spring全家桶/SpringMVC/SpringMVC如何实现文件上传.md)\n* [SpringMVC中的常用功能](docs/Spring全家桶/SpringMVC/SpringMVC中的常用功能.md)\n\n## SpringMVC源码分析\n\n* [SpringMVC源码分析：SpringMVC概述](docs/Spring全家桶/SpringMVC源码分析/SpringMVC源码分析：SpringMVC概述.md)\n* [SpringMVC源码分析：SpringMVC设计理念与DispatcherServlet](docs/Spring全家桶/SpringMVC源码分析/SpringMVC源码分析：SpringMVC设计理念与DispatcherServlet.md)\n* [SpringMVC源码分析：DispatcherServlet的初始化与请求转发 ](docs/Spring全家桶/SpringMVC源码分析/SpringMVC源码分析：DispatcherServlet的初始化与请求转发.md)\n* [SpringMVC源码分析：DispatcherServlet如何找到正确的Controller ](docs/Spring全家桶/SpringMVC源码分析/SpringMVC源码分析：DispatcherServlet如何找到正确的Controller.md)\n* [SpringMVC源码剖析：消息转换器HttpMessageConverter与@ResponseBody注解](docs/Spring全家桶/SpringMVC/SpringMVC源码剖析：消息转换器HttpMessageConverter与@ResponseBody注解.md)\n* [DispatcherServlet 初始化流程 ](docs/Spring全家桶/SpringMVC源码分析/DispatcherServlet初始化流程.md)\n* [RequestMapping 初始化流程 ](docs/Spring全家桶/SpringMVC源码分析/RequestMapping初始化流程.md)\n* [Spring 容器启动 Tomcat ](docs/Spring全家桶/SpringMVC源码分析/Spring容器启动Tomcat.md)\n* [SpringMVC demo 与@EnableWebMvc 注解 ](docs/Spring全家桶/SpringMVC源码分析/SpringMVC的Demo与@EnableWebMvc注解.md)\n* [SpringMVC 整体源码结构总结 ](docs/Spring全家桶/SpringMVC源码分析/SpringMVC整体源码结构总结.md)\n* [请求执行流程（一）之获取 Handler ](docs/Spring全家桶/SpringMVC源码分析/请求执行流程（一）之获取Handler.md)\n* [请求执行流程（二）之执行 Handler 方法 ](docs/Spring全家桶/SpringMVC源码分析/请求执行流程（二）之执行Handler方法.md)\n\n## SpringBoot\n\n* [SpringBoot系列:SpringBoot的前世今生](docs/Spring全家桶/SpringBoot/SpringBoot的前世今生.md)\n* [给你一份SpringBoot知识清单.md](docs/Spring全家桶/SpringBoot/给你一份SpringBoot知识清单.md)\n* [Spring常见注解使用指南(包含Spring+SpringMVC+SpringBoot)](docs/Spring全家桶/SpringBoot/Spring常见注解使用指南(包含Spring+SpringMVC+SpringBoot).md)\n* [SpringBoot中的日志管理](docs/Spring全家桶/SpringBoot/SpringBoot中的日志管理.md)\n* [SpringBoot常见注解](docs/Spring全家桶/SpringBoot/SpringBoot常见注解.md)\n* [SpringBoot应用也可以部署到外部Tomcat](docs/Spring全家桶/SpringBoot/SpringBoot应用也可以部署到外部Tomcat.md)\n* [SpringBoot生产环境工具Actuator](docs/Spring全家桶/SpringBoot/SpringBoot生产环境工具Actuator.md)\n* [SpringBoot的Starter机制](docs/Spring全家桶/SpringBoot/SpringBoot的Starter机制.md)\n* [SpringBoot的前世今生](docs/Spring全家桶/SpringBoot/SpringBoot的前世今生.md)\n* [SpringBoot的基本使用](docs/Spring全家桶/SpringBoot/SpringBoot的基本使用.md)\n* [SpringBoot的配置文件管理](docs/Spring全家桶/SpringBoot/SpringBoot的配置文件管理.md)\n* [SpringBoot自带的热部署工具](docs/Spring全家桶/SpringBoot/SpringBoot自带的热部署工具.md)\n* [SpringBoot中的任务调度与@Async](docs/Spring全家桶/SpringBoot/SpringBoot中的任务调度与@Async.md)\n* [基于SpringBoot中的开源监控工具SpringBootAdmin](docs/Spring全家桶/SpringBoot/基于SpringBoot中的开源监控工具SpringBootAdmin.md)\n\n## SpringBoot源码分析\n* [@SpringBootApplication 注解](docs/Spring全家桶/SpringBoot源码解析/@SpringBootApplication注解.md)\n* [springboot web应用（一）：servlet 组件的注册流程](docs/Spring全家桶/SpringBoot源码解析/SpringBootWeb应用（一）：servlet组件的注册流程.md)\n* [springboot web应用（二）：WebMvc 装配过程](docs/Spring全家桶/SpringBoot源码解析/SpringBootWeb应用（二）：WebMvc装配过程.md)\n\n* [SpringBoot 启动流程（一）：准备 SpringApplication](docs/Spring全家桶/SpringBoot源码解析/SpringBoot启动流程（一）：准备SpringApplication.md)\n* [SpringBoot 启动流程（二）：准备运行环境](docs/Spring全家桶/SpringBoot源码解析/SpringBoot启动流程（二）：准备运行环境.md)\n* [SpringBoot 启动流程（三）：准备IOC容器](docs/Spring全家桶/SpringBoot源码解析/SpringBoot启动流程（三）：准备IOC容器.md)\n* [springboot 启动流程（四）：启动IOC容器](docs/Spring全家桶/SpringBoot源码解析/SpringBoot启动流程（四）：启动IOC容器.md)\n* [springboot 启动流程（五）：完成启动](docs/Spring全家桶/SpringBoot源码解析/SpringBoot启动流程（五）：完成启动.md)\n* [springboot 启动流程（六）：启动流程总结](docs/Spring全家桶/SpringBoot源码解析/SpringBoot启动流程（六）：启动流程总结.md)\n\n* [springboot 自动装配（一）：加载自动装配类](docs/Spring全家桶/SpringBoot源码解析/SpringBoot自动装配（一）：加载自动装配类.md)\n* [springboot 自动装配（二）：条件注解](docs/Spring全家桶/SpringBoot源码解析/SpringBoot自动装配（二）：条件注解.md)\n* [springboot 自动装配（三）：自动装配顺序](docs/Spring全家桶/SpringBoot源码解析/SpringBoot自动装配（三）：自动装配顺序.md)\n\n## SpringCloud\n* [SpringCloud概述](docs/Spring全家桶/SpringCloud/SpringCloud概述.md)\n* [Spring Cloud Config](docs/Spring全家桶/SpringCloud/SpringCloudConfig.md)\n* [Spring Cloud Consul](docs/Spring全家桶/SpringCloud/SpringCloudConsul.md)\n* [Spring Cloud Eureka](docs/Spring全家桶/SpringCloud/SpringCloudEureka.md)\n* [Spring Cloud Gateway](docs/Spring全家桶/SpringCloud/SpringCloudGateway.md)\n* [Spring Cloud Hystrix](docs/Spring全家桶/SpringCloud/SpringCloudHystrix.md)\n* [Spring Cloud LoadBalancer](docs/Spring全家桶/SpringCloud/SpringCloudLoadBalancer.md)\n* [Spring Cloud OpenFeign](docs/Spring全家桶/SpringCloud/SpringCloudOpenFeign.md)\n* [Spring Cloud Ribbon](docs/Spring全家桶/SpringCloud/SpringCloudRibbon.md)\n* [Spring Cloud Sleuth](docs/Spring全家桶/SpringCloud/SpringCloudSleuth.md)\n* [Spring Cloud Zuul](docs/Spring全家桶/SpringCloud/SpringCloudZuul.md)\n\n## SpringCloud 源码分析\n* [Spring Cloud Config源码分析](docs/Spring全家桶/SpringCloud源码分析/SpringCloudConfig源码分析.md)\n* [Spring Cloud Eureka源码分析](docs/Spring全家桶/SpringCloud源码分析/SpringCloudEureka源码分析.md)\n* [Spring Cloud Gateway源码分析](docs/Spring全家桶/SpringCloud源码分析/SpringCloudGateway源码分析.md)\n* [Spring Cloud Hystrix源码分析](docs/Spring全家桶/SpringCloud源码分析/SpringCloudHystrix源码分析.md)\n* [Spring Cloud LoadBalancer源码分析](docs/Spring全家桶/SpringCloud源码分析/SpringCloudLoadBalancer源码分析.md)\n* [Spring Cloud OpenFeign源码分析](docs/Spring全家桶/SpringCloud源码分析/SpringCloudOpenFeign源码分析.md)\n* [Spring Cloud Ribbon源码分析](docs/Spring全家桶/SpringCloud源码分析/SpringCloudRibbon源码分析.md)\n\n## SpringCloud Alibaba\n* [SpringCloud Alibaba概览](docs/Spring全家桶/SpringCloudAlibaba/SpringCloudAlibaba概览.md)\n* [SpringCloud Alibaba nacos](docs/Spring全家桶/SpringCloudAlibaba/SpringCloudAlibabaNacos.md)\n* [SpringCloud Alibaba RocketMQ](docs/Spring全家桶/SpringCloudAlibaba/SpringCloudAlibabaRocketMQ.md)\n* [SpringCloud Alibaba sentinel](docs/Spring全家桶/SpringCloudAlibaba/SpringCloudAlibabaSentinel.md)\n* [SpringCloud Alibaba skywalking](docs/Spring全家桶/SpringCloudAlibaba/SpringCloudAlibabaSkywalking.md)\n* [SpringCloud Alibaba seata](docs/Spring全家桶/SpringCloudAlibaba/SpringCloudAlibabaSeata.md)\n\n## SpringCloud Alibaba源码分析\n* [Spring Cloud Seata源码分析](docs/Spring全家桶/SpringCloudAlibaba源码分析/SpringCloudSeata源码分析.md)\n* [Spring Cloud Sentinel源码分析](docs/Spring全家桶/SpringCloudAlibaba源码分析/SpringCloudSentinel源码分析.md)\n* [SpringCloudAlibaba nacos源码分析：概览](docs/Spring全家桶/SpringCloudAlibaba源码分析/SpringCloudAlibabaNacos源码分析：概览.md)\n* [SpringCloudAlibaba nacos源码分析：服务发现](docs/Spring全家桶/SpringCloudAlibaba源码分析/SpringCloudAlibabaNacos源码分析：服务发现.md)\n* [SpringCloudAlibaba nacos源码分析：服务注册](docs/Spring全家桶/SpringCloudAlibaba源码分析/SpringCloudAlibabaNacos源码分析：服务注册.md)\n* [SpringCloudAlibaba nacos源码分析：配置中心](docs/Spring全家桶/SpringCloudAlibaba源码分析/SpringCloudAlibabaNacos源码分析：配置中心.md)\n* [Spring Cloud RocketMQ源码分析](docs/Spring全家桶/SpringCloudAlibaba源码分析/SpringCloudRocketMQ源码分析.md)\n\n# 设计模式\n\n* [设计模式学习总结](docs/Java/design-parttern/设计模式学习总结.md)\n* [初探Java设计模式：创建型模式（工厂，单例等）.md](docs/Java/design-parttern/初探Java设计模式：创建型模式（工厂，单例等）.md)\n* [初探Java设计模式：结构型模式（代理模式，适配器模式等）.md](docs/Java/design-parttern/初探Java设计模式：结构型模式（代理模式，适配器模式等）.md)\n* [初探Java设计模式：行为型模式（策略，观察者等）.md](docs/Java/design-parttern/初探Java设计模式：行为型模式（策略，观察者等）.md)\n* [初探Java设计模式：JDK中的设计模式.md](docs/Java/design-parttern/初探Java设计模式：JDK中的设计模式.md)\n* [初探Java设计模式：Spring涉及到的种设计模式.md](docs/Java/design-parttern/初探Java设计模式：Spring涉及到的种设计模式.md)\n\n\n# 计算机基础\n\n## 计算机网络\ntodo\n\n\n## 操作系统\ntodo\n\n## Linux相关\ntodo\n\n\n## 数据结构与算法\ntodo\n\n## 数据结构\ntodo\n\n## 算法\ntodo\n\n# 数据库\ntodo\n\n## MySQL\n* [Mysql原理与实践总结](docs/database/Mysql原理与实践总结.md)\n* [重新学习Mysql数据库：无废话MySQL入门](docs/database/重新学习MySQL数据库：无废话MySQL入门.md)\n* [重新学习Mysql数据库：『浅入浅出』MySQL和InnoDB](docs/database/重新学习MySQL数据库：『浅入浅出』MySQL和InnoDB.md)\n* [重新学习Mysql数据库：Mysql存储引擎与数据存储原理](docs/database/重新学习MySQL数据库：Mysql存储引擎与数据存储原理.md)\n* [重新学习Mysql数据库：Mysql索引实现原理和相关数据结构算法](docs/database/重新学习MySQL数据库：Mysql索引实现原理和相关数据结构算法.md)\n* [重新学习Mysql数据库：根据MySQL索引原理进行分析与优化](docs/database/重新学习MySQL数据库：根据MySQL索引原理进行分析与优化.md)\n* [重新学习MySQL数据库：浅谈MySQL的中事务与锁](docs/database/重新学习MySQL数据库：浅谈MySQL的中事务与锁.md) \n* [重新学习Mysql数据库：详解MyIsam与InnoDB引擎的锁实现](docs/database/重新学习MySQL数据库：详解MyIsam与InnoDB引擎的锁实现.md) \n* [重新学习Mysql数据库：MySQL的事务隔离级别实战](docs/database/重新学习MySQL数据库：MySQL的事务隔离级别实战.md)\n* [重新学习MySQL数据库：Innodb中的事务隔离级别和锁的关系](docs/database/重新学习MySQL数据库：Innodb中的事务隔离级别和锁的关系.md) \n* [重新学习MySQL数据库：MySQL里的那些日志们](docs/database/重新学习MySQL数据库：MySQL里的那些日志们.md) \n* [重新学习MySQL数据库：以Java的视角来聊聊SQL注入](docs/database/重新学习MySQL数据库：以Java的视角来聊聊SQL注入.md) \n* [重新学习MySQL数据库：从实践sql语句优化开始](docs/database/重新学习MySQL数据库：从实践sql语句优化开始.md) \n* [重新学习Mysql数据库：Mysql主从复制，读写分离，分表分库策略与实践](docs/database/重新学习MySQL数据库：Mysql主从复制，读写分离，分表分库策略与实践.md)\n\n\n# 缓存\n\n## Redis\n* [Redis原理与实践总结](docs/cache/Redis原理与实践总结.md)\n* [探索Redis设计与实现开篇：什么是Redis](docs/cache/探索Redis设计与实现开篇：什么是Redis.md)\n* [探索Redis设计与实现：Redis的基础数据结构概览](docs/cache/探索Redis设计与实现：Redis的基础数据结构概览.md)\n* [探索Redis设计与实现：Redis内部数据结构详解——dict](docs/cache/探索Redis设计与实现：Redis内部数据结构详解——dict.md)\n* [探索Redis设计与实现：Redis内部数据结构详解——sds](docs/cache/探索Redis设计与实现：Redis内部数据结构详解——sds.md)\n* [探索Redis设计与实现：Redis内部数据结构详解——ziplist](docs/cache/探索Redis设计与实现：Redis内部数据结构详解——ziplist.md)\n* [探索Redis设计与实现：Redis内部数据结构详解——quicklist](docs/cache/探索Redis设计与实现：Redis内部数据结构详解——quicklist.md)\n* [探索Redis设计与实现：Redis内部数据结构详解——skiplist](docs/cache/探索Redis设计与实现：Redis内部数据结构详解——skiplist.md)\n* [探索Redis设计与实现：Redis内部数据结构详解——intset](docs/cache/探索Redis设计与实现：Redis内部数据结构详解——intset.md)\n* [探索Redis设计与实现：连接底层与表面的数据结构robj](docs/cache/探索Redis设计与实现：连接底层与表面的数据结构robj.md)\n* [探索Redis设计与实现：数据库redisDb与键过期删除策略](docs/cache/探索Redis设计与实现：数据库redisDb与键过期删除策略.md)\n* [探索Redis设计与实现：Redis的事件驱动模型与命令执行过程](docs/cache/探索Redis设计与实现：Redis的事件驱动模型与命令执行过程.md)\n* [探索Redis设计与实现：使用快照和AOF将Redis数据持久化到硬盘中](docs/cache/探索Redis设计与实现：使用快照和AOF将Redis数据持久化到硬盘中.md)\n* [探索Redis设计与实现：浅析Redis主从复制](docs/cache/探索Redis设计与实现：浅析Redis主从复制.md)\n* [探索Redis设计与实现：Redis集群机制及一个Redis架构演进实例](docs/cache/探索Redis设计与实现：Redis集群机制及一个Redis架构演进实例.md)\n* [探索Redis设计与实现：Redis事务浅析与ACID特性介绍](docs/cache/探索Redis设计与实现：Redis事务浅析与ACID特性介绍.md)\n* [探索Redis设计与实现：Redis分布式锁进化史 ](docs/cache/探索Redis设计与实现：Redis分布式锁进化史.md )\n\n# 消息队列\n\n## Kafka\n* [消息队列kafka详解：Kafka快速上手（Java版）](docs/mq/kafka/消息队列kafka详解：Kafka快速上手（Java版）.md)\n* [消息队列kafka详解：Kafka一条消息存到broker的过程](docs/mq/kafka/消息队列kafka详解：Kafka一条消息存到broker的过程.md)\n* [消息队列kafka详解：消息队列kafka详解：Kafka介绍](docs/mq/kafka/消息队列kafka详解：Kafka介绍.md)\n* [消息队列kafka详解：Kafka原理分析总结篇](docs/mq/kafka/消息队列kafka详解：Kafka原理分析总结篇.md)\n* [消息队列kafka详解：Kafka常见命令及配置总结](docs/mq/kafka/消息队列kafka详解：Kafka常见命令及配置总结.md)\n* [消息队列kafka详解：Kafka架构介绍](docs/mq/kafka/消息队列kafka详解：Kafka架构介绍.md)\n* [消息队列kafka详解：Kafka的集群工作原理](docs/mq/kafka/消息队列kafka详解：Kafka的集群工作原理.md)\n* [消息队列kafka详解：Kafka重要知识点+面试题大全](docs/mq/kafka/消息队列kafka详解：Kafka重要知识点+面试题大全.md)\n* [消息队列kafka详解：如何实现延迟队列](docs/mq/kafka/消息队列kafka详解：如何实现延迟队列.md)\n* [消息队列kafka详解：如何实现死信队列](docs/mq/kafka/消息队列kafka详解：如何实现死信队列.md)\n\n## RocketMQ\n* [RocketMQ系列：事务消息（最终一致性）](docs/mq/RocketMQ/RocketMQ系列：事务消息（最终一致性）.md)\n* [RocketMQ系列：基本概念](docs/mq/RocketMQ/RocketMQ系列：基本概念.md)\n* [RocketMQ系列：广播与延迟消息](docs/mq/RocketMQ/RocketMQ系列：广播与延迟消息.md)\n* [RocketMQ系列：批量发送与过滤](docs/mq/RocketMQ/RocketMQ系列：批量发送与过滤.md)\n* [RocketMQ系列：消息的生产与消费](docs/mq/RocketMQ/RocketMQ系列：消息的生产与消费.md)\n* [RocketMQ系列：环境搭建](docs/mq/RocketMQ/RocketMQ系列：环境搭建.md)\n* [RocketMQ系列：顺序消费](docs/mq/RocketMQ/RocketMQ系列：顺序消费.md)\n\n# 大后端\n* [后端技术杂谈开篇：云计算，大数据与AI的故事](docs/backend/后端技术杂谈开篇：云计算，大数据与AI的故事.md)\n* [后端技术杂谈：搜索引擎基础倒排索引](docs/backend/后端技术杂谈：搜索引擎基础倒排索引.md)\n* [后端技术杂谈：搜索引擎工作原理](docs/backend/后端技术杂谈：搜索引擎工作原理.md)\n* [后端技术杂谈：Lucene基础原理与实践](docs/backend/后端技术杂谈：Lucene基础原理与实践.md)\n* [后端技术杂谈：Elasticsearch与solr入门实践](docs/backend/后端技术杂谈：Elasticsearch与solr入门实践.md)\n* [后端技术杂谈：云计算的前世今生](docs/backend/后端技术杂谈：云计算的前世今生.md)\n* [后端技术杂谈：白话虚拟化技术](docs/backend/后端技术杂谈：白话虚拟化技术.md )\n* [后端技术杂谈：OpenStack的基石KVM](docs/backend/后端技术杂谈：OpenStack的基石KVM.md)\n* [后端技术杂谈：OpenStack架构设计](docs/backend/后端技术杂谈：OpenStack架构设计.md)\n* [后端技术杂谈：先搞懂Docker核心概念吧](docs/backend/后端技术杂谈：先搞懂Docker核心概念吧.md)\n* [后端技术杂谈：Docker 核心技术与实现原理](docs/backend/后端技术杂谈：Docker%核心技术与实现原理.md)\n* [后端技术杂谈：十分钟理解Kubernetes核心概念](docs/backend/后端技术杂谈：十分钟理解Kubernetes核心概念.md)\n* [后端技术杂谈：捋一捋大数据研发的基本概念](docs/backend/后端技术杂谈：捋一捋大数据研发的基本概念.md)\n\n# 分布式\n## 分布式理论\n* [分布式系统理论基础：一致性PC和PC ](docs/distributed/basic/分布式系统理论基础：一致性PC和PC.md)\n* [分布式系统理论基础：CAP ](docs/distributed/basic/分布式系统理论基础：CAP.md)\n* [分布式系统理论基础：时间时钟和事件顺序](docs/distributed/basic/分布式系统理论基础：时间时钟和事件顺序.md)\n* [分布式系统理论基础：Paxos](docs/distributed/basic/分布式系统理论基础：Paxos.md)\n* [分布式系统理论基础：选举多数派和租约](docs/distributed/basic/分布式系统理论基础：选举多数派和租约.md)\n* [分布式系统理论基础：RaftZab ](docs/distributed/basic/分布式系统理论基础：RaftZab.md)\n* [分布式系统理论进阶：Paxos变种和优化 ](docs/distributed/basic/分布式系统理论进阶：Paxos变种和优化.md)\n* [分布式系统理论基础：zookeeper分布式协调服务 ](docs/distributed/basic/分布式系统理论基础：zookeeper分布式协调服务.md)\n* [分布式理论总结](docs/distributed/分布式技术实践总结.md)\n\n## 分布式技术\n* [搞懂分布式技术：分布式系统的一些基本概念](docs/distributed/practice/搞懂分布式技术：分布式系统的一些基本概念.md )\n* [搞懂分布式技术：分布式一致性协议与Paxos，Raft算法](docs/distributed/practice/搞懂分布式技术：分布式一致性协议与Paxos，Raft算法.md)\n* [搞懂分布式技术：初探分布式协调服务zookeeper](docs/distributed/practice/搞懂分布式技术：初探分布式协调服务zookeeper.md )\n* [搞懂分布式技术：ZAB协议概述与选主流程详解](docs/distributed/practice/搞懂分布式技术：ZAB协议概述与选主流程详解.md )\n* [搞懂分布式技术：Zookeeper的配置与集群管理实战](docs/distributed/practice/搞懂分布式技术：Zookeeper的配置与集群管理实战.md)\n* [搞懂分布式技术：Zookeeper典型应用场景及实践](docs/distributed/practice/搞懂分布式技术：Zookeeper典型应用场景及实践.md )\n* [搞懂分布式技术：LVS实现负载均衡的原理与实践 ](docs/distributed/practice/搞懂分布式技术：LVS实现负载均衡的原理与实践.md )\n* [搞懂分布式技术：分布式session解决方案与一致性hash](docs/distributed/practice/搞懂分布式技术：分布式session解决方案与一致性hash.md)\n* [搞懂分布式技术：分布式ID生成方案 ](docs/distributed/practice/搞懂分布式技术：分布式ID生成方案.md )\n* [搞懂分布式技术：缓存的那些事](docs/distributed/practice/搞懂分布式技术：缓存的那些事.md)\n* [搞懂分布式技术：SpringBoot使用注解集成Redis缓存](docs/distributed/practice/搞懂分布式技术：SpringBoot使用注解集成Redis缓存.md)\n* [搞懂分布式技术：缓存更新的套路 ](docs/distributed/practice/搞懂分布式技术：缓存更新的套路.md )\n* [搞懂分布式技术：浅谈分布式锁的几种方案 ](docs/distributed/practice/搞懂分布式技术：浅谈分布式锁的几种方案.md )\n* [搞懂分布式技术：浅析分布式事务](docs/distributed/practice/搞懂分布式技术：浅析分布式事务.md )\n* [搞懂分布式技术：分布式事务常用解决方案 ](docs/distributed/practice/搞懂分布式技术：分布式事务常用解决方案.md )\n* [搞懂分布式技术：使用RocketMQ事务消息解决分布式事务 ](docs/distributed/practice/搞懂分布式技术：使用RocketMQ事务消息解决分布式事务.md )\n* [搞懂分布式技术：消息队列因何而生](docs/distributed/practice/搞懂分布式技术：消息队列因何而生.md)\n* [搞懂分布式技术：浅谈分布式消息技术Kafka](docs/distributed/practice/搞懂分布式技术：浅谈分布式消息技术Kafka.md )\n* [分布式技术实践总结](docs/distributed/分布式理论总结.md)\n\n# 面试指南\n\ntodo\n## 校招指南\ntodo\n\n## 面经\ntodo\n\n# 工具\ntodo\n\n# 资料\ntodo\n\n## 书单\ntodo\n\n# 待办\nspringboot和springcloud\n\n# 微信公众号\n\n## Java技术江湖\n如果大家想要实时关注我更新的文章以及分享的干货的话，可以关注我的公众号【Java技术江湖】\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/Javatutorial.jpeg)\n"
  },
  {
    "path": "docs/Java/JVM/JVM总结.md",
    "content": "# 目录\n\n  * [JVM介绍和源码](#jvm介绍和源码)\n  * [JVM内存模型](#jvm内存模型)\n  * [JVM OOM和内存泄漏](#jvm-oom和内存泄漏)\n  * [常见调试工具](#常见调试工具)\n  * [class文件结构](#class文件结构)\n  * [JVM的类加载机制](#jvm的类加载机制)\n  * [defineclass findclass和loadclass](#defineclass-findclass和loadclass)\n  * [JVM虚拟机字节码执行引擎](#jvm虚拟机字节码执行引擎)\n  * [编译期优化和运行期优化](#编译期优化和运行期优化)\n  * [JVM的垃圾回收](#jvm的垃圾回收)\n  * [JVM的锁优化](#jvm的锁优化)\n\n\n\n---\ntitle: JVM原理学习总结\ndate: 2018-07-08 22:09:47\ntags:\n\t- JVM\ncategories:\n\t- 后端\n\t- 技术总结\n---\n\n这篇总结主要是基于我之前JVM系列文章而形成的的。主要是把重要的知识点用自己的话说了一遍，可能会有一些错误，还望见谅和指点。谢谢\n\n更多详细内容可以查看我的专栏文章：深入理解JVM虚拟机\n\nhttps://blog.csdn.net/column/details/21960.html\n<!-- more -->\n\n## JVM介绍和源码\n\n首先JVM是一个虚拟机，当你安装了jre，它就包含了jvm环境。JVM有自己的内存结构，字节码执行引擎，因此class字节码才能在jvm上运行，除了Java以外，Scala，groovy等语言也可以编译成字节码而后在jvm中运行。JVM是用c开发的。\n\n## JVM内存模型\n\n内存模型老生常谈了，主要就是线程共享的堆区，方法区，本地方法栈。还有线程私有的虚拟机栈和程序计数器。\n\n堆区存放所有对象，每个对象有一个地址，Java类jvm初始化时加载到方法区，而后会在堆区中生成一个Class对象，来负责这个类所有实例的实例化。\n\n\n\n栈区存放的是栈帧结构，栈帧是一段内存空间，包括参数列表，返回地址，局部变量表等，局部变量表由一堆slot组成，slot的大小固定，根据变量的数据类型决定需要用到几个slot。\n\n方法区存放类的元数据，将原来的字面量转换成引用，当然，方法区也提供常量池，常量池存放-128到127的数字类型的包装类。\n字符串常量池则会存放使用intern的字符串变量。\n## JVM OOM和内存泄漏\n\n这里指的是oom和内存泄漏这类错误。\n\noom一般分为三种，堆区内存溢出，栈区内存溢出以及方法区内存溢出。\n\n堆内存溢出主要原因是创建了太多对象，比如一个集合类死循环添加一个数，此时设置jvm参数使堆内存最大值为10m，一会就会报oom异常。\n\n栈内存溢出主要与栈空间和线程有关，因为栈是线程私有的，如果创建太多线程，内存值超过栈空间上限，也会报oom。\n\n方法区内存溢出主要是由于动态加载类的数量太多，或者是不断创建一个动态代理，用不了多久方法区内存也会溢出，会报oom，这里在1.7之前会报permgem oom，1.8则会报meta space oom，这是因为1.8中删除了堆中的永久代，转而使用元数据区。\n\n内存泄漏一般是因为对象被引用无法回收，比如一个集合中存着很多对象，可能你在外部代码把对象的引用置空了，但是由于对象还被集合给引用着，所以无法被回收，导致内存泄漏。测试也很简单，就在集合里添加对象，添加完以后把引用置空，循环操作，一会就会出现oom异常，原因是内存泄漏太多了，导致没有空间分配新的对象。\n\n## 常见调试工具\n\n命令行工具有jstack jstat jmap 等，jstack可以跟踪线程的调用堆栈，以便追踪错误原因。\n\njstat可以检查jvm的内存使用情况，gc情况以及线程状态等。\n\njmap用于把堆栈快照转储到文件系统，然后可以用其他工具去排查。\n\nvisualvm是一款很不错的gui调试工具，可以远程登录主机以便访问其jvm的状态并进行监控。\n\n## class文件结构\n\nclass文件结构比较复杂，首先jvm定义了一个class文件的规则，并且让jvm按照这个规则去验证与读取。\n\n开头是一串魔数，然后接下来会有各种不同长度的数据，通过class的规则去读取这些数据，jvm就可以识别其内容，最后将其加载到方法区。\n\n## JVM的类加载机制\n\njvm的类加载顺序是bootstrap类加载器，extclassloader加载器，最后是appclassloader用户加载器，分别加载的是jdk/bin ，jdk/ext以及用户定义的类目录下的类（一般通过ide指定），一般核心类都由bootstrap和ext加载器来加载，appclassloader用于加载自己写的类。\n\n双亲委派模型，加载一个类时，首先获取当前类加载器，先找到最高层的类加载器bootstrap让他尝试加载，他如果加载不了再让ext加载器去加载，如果他也加载不了再让appclassloader去加载。这样的话，确保一个类型只会被加载一次，并且以高层类加载器为准，防止某些类与核心类重复，产生错误。\n\n## defineclass findclass和loadclass\n\n类加载classloader中有两个方法loadclass和findclass，loadclass遵从双亲委派模型，先调用父类加载的loadclass，如果父类和自己都无法加载该类，则会去调用findclass方法，而findclass默认实现为空，如果要自定义类加载方式，则可以重写findclass方法。\n\n常见使用defineclass的情况是从网络或者文件读取字节码，然后通过defineclass将其定义成一个类，并且返回一个Class对象，说明此时类已经加载到方法区了。当然1.8以前实现方法区的是永久代，1.8以后则是元空间了。\n\n## JVM虚拟机字节码执行引擎\n\njvm通过字节码执行引擎来执行class代码，他是一个栈式执行引擎。这部分内容比较高深，在这里就不献丑了。\n\n## 编译期优化和运行期优化\n\n编译期优化主要有几种\n\n1 泛型的擦除，使得泛型在编译时变成了实际类型，也叫伪泛型。\n\n2 自动拆箱装箱，foreach循环自动变成迭代器实现的for循环。\n\n3 条件编译，比如if(true)直接可得。\n\n运行期优化主要有几种\n\n1 JIT即时编译\n\nJava既是编译语言也是解释语言，因为需要编译代码生成字节码，而后通过解释器解释执行。\n\n但是，有些代码由于经常被使用而成为热点代码，每次都编译太过费时费力，干脆直接把他编译成本地代码，这种方式叫做JIT即时编译处理，所以这部分代码可以直接在本地运行而不需要通过jvm的执行引擎。\n\n2 公共表达式擦除，就是一个式子在后面如果没有被修改，在后面调用时就会被直接替换成数值。\n\n3 数组边界擦除，方法内联，比较偏，意义不大。\n\n4 逃逸分析，用于分析一个对象的作用范围，如果只局限在方法中被访问，则说明不会逃逸出方法，这样的话他就是线程安全的，不需要进行并发加锁。\n\n1\n\n## JVM的垃圾回收\n\n1 GC算法：停止复制，存活对象少时适用，缺点是需要两倍空间。标记清除，存活对象多时适用，但是容易产生随便。标记整理，存活对象少时适用，需要移动对象较多。\n\n2 GC分区，一般GC发生在堆区，堆区可分为年轻代，老年代，以前有永久代，现在没有了。\n\n年轻代分为eden和survior，新对象分配在eden，当年轻代满时触发minor gc，存活对象移至survivor区，然后两个区互换，等待下一场gc，\n当对象存活的阈值达到设定值时进入老年代，大对象也会直接进入老年代。\n\n老年代空间较大，当老年代空间不足以存放年轻代过来的对象时，开始进行full gc。同时整理年轻代和老年代。\n一般年轻代使用停止复制，老年代使用标记清除。\n\n3 垃圾收集器\n\nserial串行\n\nparallel并行\n\n它们都有年轻代与老年代的不同实现。\n\n然后是scanvage收集器，注重吞吐量，可以自己设置，不过不注重延迟。\n\ncms垃圾收集器，注重延迟的缩短和控制，并且收集线程和系统线程可以并发。\n\ncms收集步骤主要是，初次标记gc root，然后停顿进行并发标记，而后处理改变后的标记，最后停顿进行并发清除。\n\ng1收集器和cms的收集方式类似，但是g1将堆内存划分成了大小相同的小块区域，并且将垃圾集中到一个区域，存活对象集中到另一个区域，然后进行收集，防止产生碎片，同时使分配方式更灵活，它还支持根据对象变化预测停顿时间，从而更好地帮用户解决延迟等问题。\n\n## JVM的锁优化\n\n在Java并发中讲述了synchronized重量级锁以及锁优化的方法，包括轻量级锁，偏向锁，自旋锁等。详细内容可以参考我的专栏：Java并发技术指南\n\n\n"
  },
  {
    "path": "docs/Java/JVM/深入理解JVM虚拟机：GC调优思路与常用工具.md",
    "content": "# 目录\n\n* [核心概念(Core Concepts)](#核心概念core-concepts)\n  * [Latency(延迟)](#latency延迟)\n  * [Throughput(吞吐量)](#throughput吞吐量)\n  * [Capacity(系统容量)](#capacity系统容量)\n* [相关示例](#相关示例)\n  * [Tuning for Latency(调优延迟指标)](#tuning-for-latency调优延迟指标)\n  * [Tuning for Throughput(吞吐量调优)](#tuning-for-throughput吞吐量调优)\n  * [Tuning for Capacity(调优系统容量)](#tuning-for-capacity调优系统容量)\n  * [6\\. GC 调优(工具篇) - GC参考手册](#6-gc-调优工具篇---gc参考手册)\n  * [JMX API](#jmx-api)\n  * [JVisualVM](#jvisualvm)\n  * [jstat](#jstat)\n  * [GC日志(GC logs)](#gc日志gc-logs)\n  * [GCViewer](#gcviewer)\n  * [分析器(Profilers)](#分析器profilers)\n    * [hprof](#hprof)\n    * [Java VisualVM](#java-visualvm)\n    * [AProf](#aprof)\n  * [参考文章](#参考文章)\n\n\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章首发于我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《深入理解JVM虚拟机》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n该系列博文会告诉你如何从入门到进阶，一步步地学习JVM基础知识，并上手进行JVM调优实战，JVM是每一个Java工程师必须要学习和理解的知识点，你必须要掌握其实现原理，才能更完整地了解整个Java技术体系，形成自己的知识框架。为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n> **说明**:\n>\n> **Capacity**: 性能,能力,系统容量; 文中翻译为”**系统容量**“; 意为硬件配置。\n\n您应该已经阅读了前面的章节:\n\n1.  垃圾收集简介 - GC参考手册\n2.  Java中的垃圾收集 - GC参考手册\n3.  GC 算法(基础篇) - GC参考手册\n4.  GC 算法(实现篇) - GC参考手册\n\nGC调优(Tuning Garbage Collection)和其他性能调优是同样的原理。初学者可能会被 200 多个 GC参数弄得一头雾水, 然后随便调整几个来试试结果,又或者修改几行代码来测试。其实只要参照下面的步骤，就能保证你的调优方向正确:\n\n1.  列出性能调优指标(State your performance goals)\n2.  执行测试(Run tests)\n3.  检查结果(Measure the results)\n4.  与目标进行对比(Compare the results with the goals)\n5.  如果达不到指标, 修改配置参数, 然后继续测试(go back to running tests)\n\n第一步, 我们需要做的事情就是: 制定明确的GC性能指标。对所有性能监控和管理来说, 有三个维度是通用的:\n\n*   Latency(延迟)\n*   Throughput(吞吐量)\n*   Capacity(系统容量)\n\n我们先讲解基本概念,然后再演示如何使用这些指标。如果您对 延迟、吞吐量和系统容量等概念很熟悉, 可以跳过这一小节。\n\n### 核心概念(Core Concepts)\n\n我们先来看一家工厂的装配流水线。工人在流水线将现成的组件按顺序拼接,组装成自行车。通过实地观测, 我们发现从组件进入生产线，到另一端组装成自行车需要4小时。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404224347.png)\n继续观察,我们还发现,此后每分钟就有1辆自行车完成组装, 每天24小时,一直如此。将这个模型简化, 并忽略维护窗口期后得出结论：**这条流水线每小时可以组装60辆自行车**。\n\n> **说明**: 时间窗口/窗口期，请类比车站卖票的窗口，是一段规定/限定做某件事的时间段。\n\n通过这两种测量方法, 就知道了生产线的相关性能信息：**延迟**与**吞吐量**:\n\n*   生产线的延迟:**4小时**\n*   生产线的吞吐量:**60辆/小时**\n\n请注意, 衡量延迟的时间单位根据具体需要而确定 —— 从纳秒(nanosecond)到几千年(millennia)都有可能。系统的吞吐量是每个单位时间内完成的操作。操作(Operations)一般是特定系统相关的东西。在本例中,选择的时间单位是小时, 操作就是对自行车的组装。\n\n掌握了延迟和吞吐量两个概念之后, 让我们对这个工厂来进行实际的调优。自行车的需求在一段时间内都很稳定, 生产线组装自行车有四个小时延迟, 而吞吐量在几个月以来都很稳定: 60辆/小时。假设某个销售团队突然业绩暴涨, 对自行车的需求增加了1倍。客户每天需要的自行车不再是 60 * 24 = 1440辆, 而是 2*1440 = 2880辆/天。老板对工厂的产能不满意，想要做些调整以提升产能。\n\n看起来总经理很容易得出正确的判断, 系统的延迟没法子进行处理 —— 他关注的是每天的自行车生产总量。得出这个结论以后, 假若工厂资金充足, 那么应该立即采取措施, 改善吞吐量以增加产能。\n\n我们很快会看到, 这家工厂有两条相同的生产线。每条生产线一分钟可以组装一辆成品自行车。 可以想象，每天生产的自行车数量会增加一倍。达到 2880辆/天。要注意的是, 不需要减少自行车的装配时间 —— 从开始到结束依然需要 4 小时。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404224359.png)\n\n巧合的是，这样进行的性能优化,同时增加了吞吐量和产能。一般来说，我们会先测量当前的系统性能, 再设定新目标, 只优化系统的某个方面来满足性能指标。\n\n在这里做了一个很重要的决定 —— 要增加吞吐量,而不是减小延迟。在增加吞吐量的同时, 也需要增加系统容量。比起原来的情况, 现在需要两条流水线来生产出所需的自行车。在这种情况下, 增加系统的吞吐量并不是免费的, 需要水平扩展, 以满足增加的吞吐量需求。\n\n在处理性能问题时, 应该考虑到还有另一种看似不相关的解决办法。假如生产线的延迟从1分钟降低为30秒,那么吞吐量同样可以增长 1 倍。\n\n或者是降低延迟, 或者是客户非常有钱。软件工程里有一种相似的说法 —— 每个性能问题背后,总有两种不同的解决办法。 可以用更多的机器, 或者是花精力来改善性能低下的代码。\n\n#### Latency(延迟)\n\nGC的延迟指标由一般的延迟需求决定。延迟指标通常如下所述:\n\n*   所有交易必须在10秒内得到响应\n*   90%的订单付款操作必须在3秒以内处理完成\n*   推荐商品必须在 100 ms 内展示到用户面前\n\n面对这类性能指标时, 需要确保在交易过程中, GC暂停不能占用太多时间，否则就满足不了指标。“不能占用太多” 的意思需要视具体情况而定, 还要考虑到其他因素, 比如外部数据源的交互时间(round-trips), 锁竞争(lock contention), 以及其他的安全点等等。\n\n假设性能需求为:`90%`的交易要在`1000ms`以内完成, 每次交易最长不能超过`10秒`。 根据经验, 假设GC暂停时间比例不能超过10%。 也就是说, 90%的GC暂停必须在`100ms`内结束, 也不能有超过`1000ms`的GC暂停。为简单起见, 我们忽略在同一次交易过程中发生多次GC停顿的可能性。\n\n有了正式的需求,下一步就是检查暂停时间。有许多工具可以使用, 在接下来的6\\. GC 调优(工具篇)\n\n\n\n```\n2015-06-04T13:34:16.974-0200: 2.578: [Full GC (Ergonomics)\n        [PSYoungGen: 93677K->70109K(254976K)] \n        [ParOldGen: 499597K->511230K(761856K)] \n        593275K->581339K(1016832K),\n        [Metaspace: 2936K->2936K(1056768K)]\n    , 0.0713174 secs]\n    [Times: user=0.21 sys=0.02, real=0.07 secs\n```\n\n\n\n这表示一次GC暂停, 在`2015-06-04T13:34:16`这个时刻触发. 对应于JVM启动之后的`2,578 ms`。\n\n此事件将应用线程暂停了`0.0713174`秒。虽然花费的总时间为 210 ms, 但因为是多核CPU机器, 所以最重要的数字是应用线程被暂停的总时间, 这里使用的是并行GC, 所以暂停时间大约为`70ms`。 这次GC的暂停时间小于`100ms`的阈值，满足需求。\n\n继续分析, 从所有GC日志中提取出暂停相关的数据, 汇总之后就可以得知是否满足需求。\n\n#### Throughput(吞吐量)\n\n吞吐量和延迟指标有很大区别。当然两者都是根据一般吞吐量需求而得出的。一般吞吐量需求(Generic requirements for throughput) 类似这样:\n\n*   解决方案每天必须处理 100万个订单\n*   解决方案必须支持1000个登录用户,同时在5-10秒内执行某个操作: A、B或C\n*   每周对所有客户进行统计, 时间不能超过6小时，时间窗口为每周日晚12点到次日6点之间。\n\n可以看出,吞吐量需求不是针对单个操作的, 而是在给定的时间内, 系统必须完成多少个操作。和延迟需求类似, GC调优也需要确定GC行为所消耗的总时间。每个系统能接受的时间不同, 一般来说, GC占用的总时间比不能超过`10%`。\n\n现在假设需求为: 每分钟处理 1000 笔交易。同时, 每分钟GC暂停的总时间不能超过6秒(即10%)。\n\n有了正式的需求, 下一步就是获取相关的信息。依然是从GC日志中提取数据, 可以看到类似这样的信息:\n\n\n\n```\n2015-06-04T13:34:16.974-0200: 2.578: [Full GC (Ergonomics)\n        [PSYoungGen: 93677K->70109K(254976K)] \n        [ParOldGen: 499597K->511230K(761856K)] \n        593275K->581339K(1016832K), \n        [Metaspace: 2936K->2936K(1056768K)], \n     0.0713174 secs] \n     [Times: user=0.21 sys=0.02, real=0.07 secs\n```\n\n\n\n此时我们对 用户耗时(user)和系统耗时(sys)感兴趣, 而不关心实际耗时(real)。在这里, 我们关心的时间为`0.23s`(user + sys = 0.21 + 0.02 s), 这段时间内, GC暂停占用了 cpu 资源。 重要的是, 系统运行在多核机器上, 转换为实际的停顿时间(stop-the-world)为`0.0713174秒`, 下面的计算会用到这个数字。\n\n提取出有用的信息后, 剩下要做的就是统计每分钟内GC暂停的总时间。看看是否满足需求: 每分钟内总的暂停时间不得超过6000毫秒(6秒)。\n\n#### Capacity(系统容量)\n\n系统容量(Capacity)需求,是在达成吞吐量和延迟指标的情况下,对硬件环境的额外约束。这类需求大多是来源于计算资源或者预算方面的原因。例如:\n\n*   系统必须能部署到小于512 MB内存的Android设备上\n*   系统必须部署在Amazon**EC2**实例上, 配置不得超过**c3.xlarge(4核8GB)**。\n*   每月的 Amazon EC2 账单不得超过`$12,000`\n\n因此, 在满足延迟和吞吐量需求的基础上必须考虑系统容量。可以说, 假若有无限的计算资源可供挥霍, 那么任何 延迟和吞吐量指标 都不成问题, 但现实情况是, 预算(budget)和其他约束限制了可用的资源。\n\n### 相关示例\n\n介绍完性能调优的三个维度后, 我们来进行实际的操作以达成GC性能指标。\n\n请看下面的代码:\n\n\n\n```\n//imports skipped for brevity\npublic class Producer implements Runnable {\n\n  private static ScheduledExecutorService executorService\n         = Executors.newScheduledThreadPool(2);\n\n  private Deque<byte[]> deque;\n  private int objectSize;\n  private int queueSize;\n\n  public Producer(int objectSize, int ttl) {\n    this.deque = new ArrayDeque<byte[]>();\n    this.objectSize = objectSize;\n    this.queueSize = ttl * 1000;\n  }\n\n  @Override\n  public void run() {\n    for (int i = 0; i < 100; i++) { \n        deque.add(new byte[objectSize]); \n        if (deque.size() > queueSize) {\n            deque.poll();\n        }\n    }\n  }\n\n  public static void main(String[] args) \n        throws InterruptedException {\n    executorService.scheduleAtFixedRate(\n        new Producer(200 * 1024 * 1024 / 1000, 5), \n        0, 100, TimeUnit.MILLISECONDS\n    );\n    executorService.scheduleAtFixedRate(\n        new Producer(50 * 1024 * 1024 / 1000, 120), \n        0, 100, TimeUnit.MILLISECONDS);\n    TimeUnit.MINUTES.sleep(10);\n    executorService.shutdownNow();\n  }\n}\n```\n\n\n\n这段程序代码, 每 100毫秒 提交两个作业(job)来。每个作业都模拟特定的生命周期: 创建对象, 然后在预定的时间释放, 接着就不管了, 由GC来自动回收占用的内存。\n\n在运行这个示例程序时，通过以下JVM参数打开GC日志记录:\n\n\n\n```\n-XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps\n```\n\n\n\n还应该加上JVM参数`-Xloggc`以指定GC日志的存储位置,类似这样:\n\n\n\n```\n-Xloggc:C:\\\\Producer_gc.log\n```\n\n\n\n*   1\n*   2\n\n在日志文件中可以看到GC的行为, 类似下面这样:\n\n\n\n```\n2015-06-04T13:34:16.119-0200: 1.723: [GC (Allocation Failure) \n        [PSYoungGen: 114016K->73191K(234496K)] \n    421540K->421269K(745984K), \n    0.0858176 secs] \n    [Times: user=0.04 sys=0.06, real=0.09 secs] \n\n2015-06-04T13:34:16.738-0200: 2.342: [GC (Allocation Failure) \n        [PSYoungGen: 234462K->93677K(254976K)] \n    582540K->593275K(766464K), \n    0.2357086 secs] \n    [Times: user=0.11 sys=0.14, real=0.24 secs] \n\n2015-06-04T13:34:16.974-0200: 2.578: [Full GC (Ergonomics) \n        [PSYoungGen: 93677K->70109K(254976K)] \n        [ParOldGen: 499597K->511230K(761856K)] \n    593275K->581339K(1016832K), \n        [Metaspace: 2936K->2936K(1056768K)], \n    0.0713174 secs] \n    [Times: user=0.21 sys=0.02, real=0.07 secs]\n```\n\n\n\n基于日志中的信息, 可以通过三个优化目标来提升性能:\n\n1.  确保最坏情况下,GC暂停时间不超过预定阀值\n2.  确保线程暂停的总时间不超过预定阀值\n3.  在确保达到延迟和吞吐量指标的情况下, 降低硬件配置以及成本。\n\n为此, 用三种不同的配置, 将代码运行10分钟, 得到了三种不同的结果, 汇总如下:\n\n<colgroup data-id=\"c7104f7d-gSmaHJLh\"><col data-id=\"cdf32f61-8J2cfg0C\" span=\"1\" width=\"239\"><col data-id=\"cdf32f61-AWERG8eA\" span=\"1\" width=\"239\"><col data-id=\"cdf32f61-NaGTMlPG\" span=\"1\" width=\"239\"><col data-id=\"cdf32f61-dRpWPkiD\" span=\"1\" width=\"239\"></colgroup>\n| **堆内存大小(Heap)** | **GC算法(GC Algorithm)** | **有效时间比(Useful work)** | **最长停顿时间(Longest pause)** |\n| -Xmx12g | -XX:+UseConcMarkSweepGC | 89.8% | **560 ms** |\n| -Xmx12g | -XX:+UseParallelGC | 91.5% | 1,104 ms |\n| -Xmx8g | -XX:+UseConcMarkSweepGC | 66.3% | 1,610 ms |\n\n使用不同的GC算法,和不同的内存配置,运行相同的代码, 以测量GC暂停时间与 延迟、吞吐量的关系。实验的细节和结果在后面章节详细介绍。\n\n注意, 为了尽量简单, 示例中只改变了很少的输入参数, 此实验也没有在不同CPU数量或者不同的堆布局下进行测试。\n\n#### Tuning for Latency(调优延迟指标)\n\n假设有一个需求,**每次作业必须在 1000ms 内处理完成**。我们知道, 实际的作业处理只需要100 ms，简化后， 两者相减就可以算出对 GC暂停的延迟要求。现在需求变成:**GC暂停不能超过900ms**。这个问题很容易找到答案, 只需要解析GC日志文件, 并找出GC暂停中最大的那个暂停时间即可。\n\n再来看测试所用的三个配置:\n\n<colgroup data-id=\"c7104f7d-odLOoE9j\"><col data-id=\"cdf32f61-NXanPMa5\" span=\"1\" width=\"239\"><col data-id=\"cdf32f61-agXUo5B7\" span=\"1\" width=\"239\"><col data-id=\"cdf32f61-1a1qqOLf\" span=\"1\" width=\"239\"><col data-id=\"cdf32f61-k2joL2XZ\" span=\"1\" width=\"239\"></colgroup>\n| **堆内存大小(Heap)** | **GC算法(GC Algorithm)** | **有效时间比(Useful work)** | **最长停顿时间(Longest pause)** |\n| -Xmx12g | -XX:+UseConcMarkSweepGC | 89.8% | **560 ms** |\n| -Xmx12g | -XX:+UseParallelGC | 91.5% | 1,104 ms |\n| -Xmx8g | -XX:+UseConcMarkSweepGC | 66.3% | 1,610 ms |\n\n可以看到,其中有一个配置达到了要求。运行的参数为:\n\n\n\n```\njava -Xmx12g -XX:+UseConcMarkSweepGC Producer\n```\n\n\n\n对应的GC日志中,暂停时间最大为`560 ms`, 这达到了延迟指标`900 ms`的要求。如果还满足吞吐量和系统容量需求的话,就可以说成功达成了GC调优目标, 调优结束。\n\n#### Tuning for Throughput(吞吐量调优)\n\n假定吞吐量指标为:**每小时完成 1300万次操作处理**。同样是上面的配置, 其中有一种配置满足了需求:\n\n<colgroup data-id=\"c7104f7d-XWFk01FQ\"><col data-id=\"cdf32f61-bRSQkdW2\" span=\"1\" width=\"239\"><col data-id=\"cdf32f61-AX9oFR9r\" span=\"1\" width=\"239\"><col data-id=\"cdf32f61-6dlMCOOR\" span=\"1\" width=\"239\"><col data-id=\"cdf32f61-rnF2Ml61\" span=\"1\" width=\"239\"></colgroup>\n| **堆内存大小(Heap)** | **GC算法(GC Algorithm)** | **有效时间比(Useful work)** | **最长停顿时间(Longest pause)** |\n| -Xmx12g | -XX:+UseConcMarkSweepGC | 89.8% | 560 ms |\n| -Xmx12g | -XX:+UseParallelGC | **91.5%** | 1,104 ms |\n| -Xmx8g | -XX:+UseConcMarkSweepGC | 66.3% | 1,610 ms |\n\n此配置对应的命令行参数为:\n\n\n\n```\njava -Xmx12g -XX:+UseParallelGC Producer\n```\n\n\n\n*   可以看到,GC占用了 8.5%的CPU时间,剩下的`91.5%`是有效的计算时间。为简单起见, 忽略示例中的其他安全点。现在需要考虑:\n\n1.  每个CPU核心处理一次作业需要耗时`100ms`\n2.  因此, 一分钟内每个核心可以执行 60,000 次操作(**每个job完成100次操作**)\n3.  一小时内, 一个核心可以执行 360万次操作\n4.  有四个CPU内核, 则每小时可以执行: 4 x 3.6M = 1440万次操作\n\n理论上，通过简单的计算就可以得出结论, 每小时可以执行的操作数为:`14.4 M * 91.5% = 13,176,000`次, 满足需求。\n\n值得一提的是, 假若还要满足延迟指标, 那就有问题了, 最坏情况下, GC暂停时间为`1,104 ms`, 最大延迟时间是前一种配置的两倍。\n\n#### Tuning for Capacity(调优系统容量)\n\n假设需要将软件部署到服务器上(commodity-class hardware), 配置为`4核10G`。这样的话, 系统容量的要求就变成: 最大的堆内存空间不能超过`8GB`。有了这个需求, 我们需要调整为第三套配置进行测试:\n\n<colgroup data-id=\"c7104f7d-CM3OYb8V\"><col data-id=\"cdf32f61-2q4eRIn9\" span=\"1\" width=\"239\"><col data-id=\"cdf32f61-e9evDkSA\" span=\"1\" width=\"239\"><col data-id=\"cdf32f61-32S42uAd\" span=\"1\" width=\"239\"><col data-id=\"cdf32f61-VjMFsW5B\" span=\"1\" width=\"239\"></colgroup>\n| **堆内存大小(Heap)** | **GC算法(GC Algorithm)** | **有效时间比(Useful work)** | **最长停顿时间(Longest pause)** |\n| -Xmx12g | -XX:+UseConcMarkSweepGC | 89.8% | 560 ms |\n| -Xmx12g | -XX:+UseParallelGC | 91.5% | 1,104 ms |\n| **-Xmx8g** | -XX:+UseConcMarkSweepGC | 66.3% | 1,610 ms |\n\n程序可以通过如下参数执行:\n\n\n\n```\njava -Xmx8g -XX:+UseConcMarkSweepGC Producer\n```\n\n\n\n*   测试结果是延迟大幅增长, 吞吐量同样大幅降低:\n*   现在,GC占用了更多的CPU资源, 这个配置只有`66.3%`的有效CPU时间。因此,这个配置让吞吐量从最好的情况**13,176,000 操作/小时**下降到**不足 9,547,200次操作/小时**.\n*   最坏情况下的延迟变成了**1,610 ms**, 而不再是**560ms**。\n\n通过对这三个维度的介绍, 你应该了解, 不是简单的进行“性能(performance)”优化, 而是需要从三种不同的维度来进行考虑, 测量, 并调优延迟和吞吐量, 此外还需要考虑系统容量的约束。\n\n请继续阅读下一章:6\\. GC 调优(工具篇) - GC参考手册\n\n原文链接:[GC Tuning: Basics](https://plumbr.eu/handbook/gc-tuning)\n\n翻译时间: 2016年02月06日\n\n## 6\\. GC 调优(工具篇) - GC参考手册\n\n2017年02月23日 18:56:02\n\n阅读数：6469\n\n进行GC性能调优时, 需要明确了解, 当前的GC行为对系统和用户有多大的影响。有多种监控GC的工具和方法, 本章将逐一介绍常用的工具。\n\n您应该已经阅读了前面的章节:\n\n1.  垃圾收集简介 - GC参考手册\n2.  Java中的垃圾收集 - GC参考手册\n3.  GC 算法(基础篇) - GC参考手册\n4.  GC 算法(实现篇) - GC参考手册\n5.  GC 调优(基础篇) - GC参考手册\n\nJVM 在程序执行的过程中, 提供了GC行为的原生数据。那么, 我们就可以利用这些原生数据来生成各种报告。原生数据(_raw data_) 包括:\n\n*   各个内存池的当前使用情况,\n*   各个内存池的总容量,\n*   每次GC暂停的持续时间,\n*   GC暂停在各个阶段的持续时间。\n\n可以通过这些数据算出各种指标, 例如: 程序的内存分配率, 提升率等等。本章主要介绍如何获取原生数据。 后续的章节将对重要的派生指标(derived metrics)展开讨论, 并引入GC性能相关的话题。\n\n## JMX API\n\n从 JVM 运行时获取GC行为数据, 最简单的办法是使用标准[JMX API 接口](https://docs.oracle.com/javase/tutorial/jmx/index.html). JMX是获取 JVM内部运行时状态信息 的标准API. 可以编写程序代码, 通过 JMX API 来访问本程序所在的JVM，也可以通过JMX客户端执行(远程)访问。\n\n最常见的 JMX客户端是[JConsole](http://docs.oracle.com/javase/7/docs/technotes/guides/management/jconsole.html)和[JVisualVM](http://docs.oracle.com/javase/7/docs/technotes/tools/share/jvisualvm.html)(可以安装各种插件,十分强大)。两个工具都是标准JDK的一部分, 而且很容易使用. 如果使用的是 JDK 7u40 及更高版本, 还可以使用另一个工具:[Java Mission Control](http://www.oracle.com/technetwork/java/javaseproducts/mission-control/java-mission-control-1998576.html)( 大致翻译为 Java控制中心,`jmc.exe`)。\n\n> JVisualVM安装MBeans插件的步骤: 通过 工具(T) – 插件(G) – 可用插件 – 勾选VisualVM-MBeans – 安装 – 下一步 – 等待安装完成…… 其他插件的安装过程基本一致。\n\n所有 JMX客户端都是独立的程序,可以连接到目标JVM上。目标JVM可以在本机, 也可能是远端JVM. 如果要连接远端JVM, 则目标JVM启动时必须指定特定的环境变量,以开启远程JMX连接/以及端口号。 示例如下:\n\n\n\n```\njava -Dcom.sun.management.jmxremote.port=5432 com.yourcompany.YourApp\n```\n\n\n\n在此处, JVM 打开端口`5432`以支持JMX连接。\n\n通过 JVisualVM 连接到某个JVM以后, 切换到 MBeans 标签, 展开 “java.lang/GarbageCollector” . 就可以看到GC行为信息, 下图是 JVisualVM 中的截图:\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404224430.png)\n\n下图是Java Mission Control 中的截图:\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404224439.png)\n\n从以上截图中可以看到两款垃圾收集器。其中一款负责清理年轻代(**PS Scavenge**)，另一款负责清理老年代(**PS MarkSweep**); 列表中显示的就是垃圾收集器的名称。可以看到 , jmc 的功能和展示数据的方式更强大。\n\n对所有的垃圾收集器, 通过 JMX API 获取的信息包括:\n\n*   **CollectionCount**: 垃圾收集器执行的GC总次数,\n*   **CollectionTime**: 收集器运行时间的累计。这个值等于所有GC事件持续时间的总和,\n*   **LastGcInfo**: 最近一次GC事件的详细信息。包括 GC事件的持续时间(duration), 开始时间(startTime) 和 结束时间(endTime), 以及各个内存池在最近一次GC之前和之后的使用情况,\n*   **MemoryPoolNames**: 各个内存池的名称,\n*   **Name**: 垃圾收集器的名称\n*   **ObjectName**: 由JMX规范定义的 MBean的名字,,\n*   **Valid**: 此收集器是否有效。本人只见过 “`true`“的情况 (^_^)\n\n根据经验, 这些信息对GC的性能来说,不能得出什么结论. 只有编写程序, 获取GC相关的 JMX 信息来进行统计和分析。 在下文可以看到, 一般也不怎么关注 MBean , 但 MBean 对于理解GC的原理倒是挺有用的。\n\n## JVisualVM\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404224451.png)\n\nVisual GC 插件常用来监控本机运行的Java程序, 比如开发者和性能调优专家经常会使用此插件, 以快速获取程序运行时的GC信息。\n\n![06_03_jvmsualvm-garbage-collection-monitoring.png](https://s4.51cto.com/images/blog/202106/25/eabd68ba262d004c4919475f00d8ec9c.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk= \"06_03_jvmsualvm-garbage-collection-monitoring.png\")\n\n左侧的图表展示了各个内存池的使用情况: Metaspace/永久代, 老年代, Eden区以及两个存活区。\n\n在右边, 顶部的两个图表与 GC无关, 显示的是 JIT编译时间 和 类加载时间。下面的6个图显示的是内存池的历史记录, 每个内存池的GC次数,GC总时间, 以及最大值，峰值, 当前使用情况。\n\n再下面是 HistoGram, 显示了年轻代对象的年龄分布。至于对象的年龄监控(objects tenuring monitoring), 本章不进行讲解。\n\n与纯粹的JMX工具相比, VisualGC 插件提供了更友好的界面, 如果没有其他趁手的工具, 请选择VisualGC. 本章接下来会介绍其他工具, 这些工具可以提供更多的信息, 以及更好的视角. 当然, 在“Profilers(分析器)”一节中，也会介绍 JVisualVM 的适用场景 —— 如: 分配分析(allocation profiling), 所以我们绝不会贬低哪一款工具, 关键还得看实际情况。\n\n## jstat\n\n[jstat](https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jstat.html)也是标准JDK提供的一款监控工具(Java Virtual Machine statistics monitoring tool),可以统计各种指标。既可以连接到本地JVM,也可以连到远程JVM. 查看支持的指标和对应选项可以执行 “`jstat -options`” 。例如:\n\n\n\n```\n+-----------------+---------------------------------------------------------------+\n|     Option      |                          Displays...                          |\n+-----------------+---------------------------------------------------------------+\n|class            | Statistics on the behavior of the class loader                |\n|compiler         | Statistics  on  the behavior of the HotSpot Just-In-Time com- |\n|                 | piler                                                         |\n|gc               | Statistics on the behavior of the garbage collected heap      |\n|gccapacity       | Statistics of the capacities of  the  generations  and  their |\n|                 | corresponding spaces.                                         |\n|gccause          | Summary  of  garbage collection statistics (same as -gcutil), |\n|                 | with the cause  of  the  last  and  current  (if  applicable) |\n|                 | garbage collection events.                                    |\n|gcnew            | Statistics of the behavior of the new generation.             |\n|gcnewcapacity    | Statistics of the sizes of the new generations and its corre- |\n|                 | sponding spaces.                                              |\n|gcold            | Statistics of the behavior of the old and  permanent  genera- |\n|                 | tions.                                                        |\n|gcoldcapacity    | Statistics of the sizes of the old generation.                |\n|gcpermcapacity   | Statistics of the sizes of the permanent generation.          |\n|gcutil           | Summary of garbage collection statistics.                     |\n|printcompilation | Summary of garbage collection statistics.                     |\n+-----------------+---------------------------------------------------------------+\n```\n\n\n\n*   jstat 对于快速确定GC行为是否健康非常有用。启动方式为: “`jstat -gc -t PID 1s`” , 其中,PID 就是要监视的Java进程ID。可以通过`jps`命令查看正在运行的Java进程列表。\n\n\n\n```\njps\n\njstat -gc -t 2428 1s\n```\n\n\n\n以上命令的结果, 是 jstat 每秒向标准输出输出一行新内容, 比如:\n\n\n\n```\nTimestamp  S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT   \n200.0    8448.0 8448.0 8448.0  0.0   67712.0  67712.0   169344.0   169344.0  21248.0 20534.3 3072.0 2807.7     34    0.720  658   133.684  134.404\n201.0    8448.0 8448.0 8448.0  0.0   67712.0  67712.0   169344.0   169343.2  21248.0 20534.3 3072.0 2807.7     34    0.720  662   134.712  135.432\n202.0    8448.0 8448.0 8102.5  0.0   67712.0  67598.5   169344.0   169343.6  21248.0 20534.3 3072.0 2807.7     34    0.720  667   135.840  136.559\n203.0    8448.0 8448.0 8126.3  0.0   67712.0  67702.2   169344.0   169343.6  21248.0 20547.2 3072.0 2807.7     34    0.720  669   136.178  136.898\n204.0    8448.0 8448.0 8126.3  0.0   67712.0  67702.2   169344.0   169343.6  21248.0 20547.2 3072.0 2807.7     34    0.720  669   136.178  136.898\n205.0    8448.0 8448.0 8134.6  0.0   67712.0  67712.0   169344.0   169343.5  21248.0 20547.2 3072.0 2807.7     34    0.720  671   136.234  136.954\n206.0    8448.0 8448.0 8134.6  0.0   67712.0  67712.0   169344.0   169343.5  21248.0 20547.2 3072.0 2807.7     34    0.720  671   136.234  136.954\n207.0    8448.0 8448.0 8154.8  0.0   67712.0  67712.0   169344.0   169343.5  21248.0 20547.2 3072.0 2807.7     34    0.720  673   136.289  137.009\n208.0    8448.0 8448.0 8154.8  0.0   67712.0  67712.0   169344.0   169343.5  21248.0 20547.2 3072.0 2807.7     34    0.720  673   136.289  137.009\n```\n\n\n\n稍微解释一下上面的内容。参考[jstat manpage](http://www.manpagez.com/man/1/jstat/), 我们可以知道:\n\n*   jstat 连接到 JVM 的时间, 是JVM启动后的 200秒。此信息从第一行的 “**Timestamp**” 列得知。继续看下一行, jstat 每秒钟从JVM 接收一次信息, 也就是命令行参数中 “`1s`” 的含义。\n*   从第一行的 “**YGC**” 列得知年轻代共执行了34次GC, 由 “**FGC**” 列得知整个堆内存已经执行了 658次 full GC。\n*   年轻代的GC耗时总共为`0.720 秒`, 显示在“**YGCT**” 这一列。\n*   Full GC 的总计耗时为`133.684 秒`, 由“**FGCT**”列得知。 这立马就吸引了我们的目光, 总的JVM 运行时间只有 200 秒,**但其中有 66% 的部分被 Full GC 消耗了**。\n\n再看下一行, 问题就更明显了。\n\n*   在接下来的一秒内共执行了 4 次 Full GC。参见 “**FGC**” 列.\n*   这4次 Full GC 暂停占用了差不多 1秒的时间(根据**FGCT**列的差得知)。与第一行相比, Full GC 耗费了`928 毫秒`, 即`92.8%`的时间。\n*   根据 “**OC**和 “**OU**” 列得知,**整个老年代的空间**为`169,344.0 KB`(“OC“), 在 4 次 Full GC 后依然占用了`169,344.2 KB`(“OU“)。用了`928ms`的时间却只释放了 800 字节的内存, 怎么看都觉得很不正常。\n\n只看这两行的内容, 就知道程序出了很严重的问题。继续分析下一行, 可以确定问题依然存在,而且变得更糟。\n\nJVM几乎完全卡住了(stalled), 因为GC占用了90%以上的计算资源。GC之后, 所有的老代空间仍然还在占用。事实上, 程序在一分钟以后就挂了, 抛出了 “[java.lang.OutOfMemoryError: GC overhead limit exceeded](https://plumbr.eu/outofmemoryerror/gc-overhead-limit-exceeded)” 错误。\n\n可以看到, 通过 jstat 能很快发现对JVM健康极为不利的GC行为。一般来说, 只看 jstat 的输出就能快速发现以下问题:\n\n*   最后一列 “**GCT**”, 与JVM的总运行时间 “**Timestamp**” 的比值, 就是GC 的开销。如果每一秒内, “**GCT**” 的值都会明显增大, 与总运行时间相比, 就暴露出GC开销过大的问题. 不同系统对GC开销有不同的容忍度, 由性能需求决定, 一般来讲, 超过`10%`的GC开销都是有问题的。\n*   “**YGC**” 和 “**FGC**” 列的快速变化往往也是有问题的征兆。频繁的GC暂停会累积,并导致更多的线程停顿(stop-the-world pauses), 进而影响吞吐量。\n*   如果看到 “**OU**” 列中,老年代的使用量约等于老年代的最大容量(**OC**), 并且不降低的话, 就表示虽然执行了老年代GC, 但基本上属于无效GC。\n\n## GC日志(GC logs)\n\n通过日志内容也可以得到GC相关的信息。因为GC日志模块内置于JVM中, 所以日志中包含了对GC活动最全面的描述。 这就是事实上的标准, 可作为GC性能评估和优化的最真实数据来源。\n\nGC日志一般输出到文件之中, 是纯 text 格式的, 当然也可以打印到控制台。有多个可以控制GC日志的JVM参数。例如,可以打印每次GC的持续时间, 以及程序暂停时间(`-XX:+PrintGCApplicationStoppedTime`), 还有GC清理了多少引用类型(`-XX:+PrintReferenceGC`)。\n\n要打印GC日志, 需要在启动脚本中指定以下参数:\n\n\n\n```\n-XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintGCDetails -Xloggc:<filename>\n```\n\n\n\n以上参数指示JVM: 将所有GC事件打印到日志文件中, 输出每次GC的日期和时间戳。不同GC算法输出的内容略有不同. ParallelGC 输出的日志类似这样:\n\n\n\n```\n199.879: [Full GC (Ergonomics) [PSYoungGen: 64000K->63998K(74240K)] [ParOldGen: 169318K->169318K(169472K)] 233318K->233317K(243712K), [Metaspace: 20427K->20427K(1067008K)], 0.1473386 secs] [Times: user=0.43 sys=0.01, real=0.15 secs]\n200.027: [Full GC (Ergonomics) [PSYoungGen: 64000K->63998K(74240K)] [ParOldGen: 169318K->169318K(169472K)] 233318K->233317K(243712K), [Metaspace: 20427K->20427K(1067008K)], 0.1567794 secs] [Times: user=0.41 sys=0.00, real=0.16 secs]\n200.184: [Full GC (Ergonomics) [PSYoungGen: 64000K->63998K(74240K)] [ParOldGen: 169318K->169318K(169472K)] 233318K->233317K(243712K), [Metaspace: 20427K->20427K(1067008K)], 0.1621946 secs] [Times: user=0.43 sys=0.00, real=0.16 secs]\n200.346: [Full GC (Ergonomics) [PSYoungGen: 64000K->63998K(74240K)] [ParOldGen: 169318K->169318K(169472K)] 233318K->233317K(243712K), [Metaspace: 20427K->20427K(1067008K)], 0.1547695 secs] [Times: user=0.41 sys=0.00, real=0.15 secs]\n200.502: [Full GC (Ergonomics) [PSYoungGen: 64000K->63999K(74240K)] [ParOldGen: 169318K->169318K(169472K)] 233318K->233317K(243712K), [Metaspace: 20427K->20427K(1067008K)], 0.1563071 secs] [Times: user=0.42 sys=0.01, real=0.16 secs]\n200.659: [Full GC (Ergonomics) [PSYoungGen: 64000K->63999K(74240K)] [ParOldGen: 169318K->169318K(169472K)] 233318K->233317K(243712K), [Metaspace: 20427K->20427K(1067008K)], 0.1538778 secs] [Times: user=0.42 sys=0.00, real=0.16 secs]\n```\n\n\n\n在 “04\\. GC算法:实现篇” 中详细介绍了这些格式, 如果对此不了解, 可以先阅读该章节。\n\n分析以上日志内容, 可以得知:\n\n*   这部分日志截取自JVM启动后200秒左右。\n*   日志片段中显示, 在`780毫秒`以内, 因为垃圾回收 导致了5次 Full GC 暂停(去掉第六次暂停,这样更精确一些)。\n*   这些暂停事件的总持续时间是`777毫秒`, 占总运行时间的**99.6%**。\n*   在GC完成之后, 几乎所有的老年代空间(`169,472 KB`)依然被占用(`169,318 KB`)。\n\n通过日志信息可以确定, 该应用的GC情况非常糟糕。JVM几乎完全停滞, 因为GC占用了超过`99%`的CPU时间。 而GC的结果是, 老年代空间仍然被占满, 这进一步肯定了我们的结论。 示例程序和jstat 小节中的是同一个, 几分钟之后系统就挂了, 抛出 “[java.lang.OutOfMemoryError: GC overhead limit exceeded](https://plumbr.eu/outofmemoryerror/gc-overhead-limit-exceeded)” 错误, 不用说, 问题是很严重的.\n\n从此示例可以看出, GC日志对监控GC行为和JVM是否处于健康状态非常有用。一般情况下, 查看 GC 日志就可以快速确定以下症状:\n\n*   GC开销太大。如果GC暂停的总时间很长, 就会损害系统的吞吐量。不同的系统允许不同比例的GC开销, 但一般认为, 正常范围在`10%`以内。\n*   极个别的GC事件暂停时间过长。当某次GC暂停时间太长, 就会影响系统的延迟指标. 如果延迟指标规定交易必须在`1,000 ms`内完成, 那就不能容忍任何超过`1000毫秒`的GC暂停。\n*   老年代的使用量超过限制。如果老年代空间在 Full GC 之后仍然接近全满, 那么GC就成为了性能瓶颈, 可能是内存太小, 也可能是存在内存泄漏。这种症状会让GC的开销暴增。\n\n可以看到,GC日志中的信息非常详细。但除了这些简单的小程序, 生产系统一般都会生成大量的GC日志, 纯靠人工是很难阅读和进行解析的。\n\n## GCViewer\n\n我们可以自己编写解析器, 来将庞大的GC日志解析为直观易读的图形信息。 但很多时候自己写程序也不是个好办法, 因为各种GC算法的复杂性, 导致日志信息格式互相之间不太兼容。那么神器来了:[GCViewer](https://github.com/chewiebug/GCViewer)。\n\n[GCViewer](https://github.com/chewiebug/GCViewer)是一款开源的GC日志分析工具。项目的 GitHub 主页对各项指标进行了完整的描述. 下面我们介绍最常用的一些指标。\n\n第一步是获取GC日志文件。这些日志文件要能够反映系统在性能调优时的具体场景. 假若运营部门(operational department)反馈: 每周五下午,系统就运行缓慢, 不管GC是不是主要原因, 分析周一早晨的日志是没有多少意义的。\n\n获取到日志文件之后, 就可以用 GCViewer 进行分析, 大致会看到类似下面的图形界面:\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404224526.png)\n\n使用的命令行大致如下:\n\n\n\n```\njava -jar gcviewer_1.3.4.jar gc.log\n```\n\n\n\n当然, 如果不想打开程序界面,也可以在后面加上其他参数,直接将分析结果输出到文件。\n\n命令大致如下:\n\n\n\n```\njava -jar gcviewer_1.3.4.jar gc.log summary.csv chart.png\n```\n\n\n\n以上命令将信息汇总到当前目录下的 Excel 文件`summary.csv`之中, 将图形信息保存为`chart.png`文件。\n\n点击下载:gcviewer的jar包及使用示例\n\n上图中, Chart 区域是对GC事件的图形化展示。包括各个内存池的大小和GC事件。上图中, 只有两个可视化指标: 蓝色线条表示堆内存的使用情况, 黑色的Bar则表示每次GC暂停时间的长短。\n\n从图中可以看到, 内存使用量增长很快。一分钟左右就达到了堆内存的最大值. 堆内存几乎全部被消耗, 不能顺利分配新对象, 并引发频繁的 Full GC 事件. 这说明程序可能存在内存泄露, 或者启动时指定的内存空间不足。\n\n从图中还可以看到 GC暂停的频率和持续时间。`30秒`之后, GC几乎不间断地运行,最长的暂停时间超过`1.4秒`。\n\n在右边有三个选项卡。“`**Summary**`(摘要)” 中比较有用的是 “`Throughput`”(吞吐量百分比) 和 “`Number of GC pauses`”(GC暂停的次数), 以及“`Number of full GC pauses`”(Full GC 暂停的次数). 吞吐量显示了有效工作的时间比例, 剩下的部分就是GC的消耗。\n\n以上示例中的吞吐量为`**6.28%**`。这意味着有`**93.72%**`\n\n下一个有意思的地方是“**Pause**”(暂停)选项卡:\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404224540.png)\n\n“`Pause`” 展示了GC暂停的总时间,平均值,最小值和最大值, 并且将 total 与minor/major 暂停分开统计。如果要优化程序的延迟指标, 这些统计可以很快判断出暂停时间是否过长。另外, 我们可以得出明确的信息: 累计暂停时间为`634.59 秒`, GC暂停的总次数为`3,938 次`, 这在`11分钟/660秒`的总运行时间里那不是一般的高。\n\n更详细的GC暂停汇总信息, 请查看主界面中的 “**Event details**” 标签:\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404224553.png)\n\n从“**Event details**” 标签中, 可以看到日志中所有重要的GC事件汇总:`普通GC停顿`和`Full GC 停顿次数`, 以及`并发执行数`,`非 stop-the-world 事件`等。此示例中, 可以看到一个明显的地方, Full GC 暂停严重影响了吞吐量和延迟, 依据是:`3,928 次 Full GC`, 暂停了`634秒`。\n\n可以看到, GCViewer 能用图形界面快速展现异常的GC行为。一般来说, 图像化信息能迅速揭示以下症状:\n\n*   低吞吐量。当应用的吞吐量下降到不能容忍的地步时, 有用工作的总时间就大量减少. 具体有多大的 “容忍度”(tolerable) 取决于具体场景。按照经验, 低于 90% 的有效时间就值得警惕了, 可能需要好好优化下GC。\n*   单次GC的暂停时间过长。只要有一次GC停顿时间过长,就会影响程序的延迟指标. 例如, 延迟需求规定必须在 1000 ms以内完成交易, 那就不能容忍任何一次GC暂停超过1000毫秒。\n*   堆内存使用率过高。如果老年代空间在 Full GC 之后仍然接近全满, 程序性能就会大幅降低, 可能是资源不足或者内存泄漏。这种症状会对吞吐量产生严重影响。\n\n业界良心 —— 图形化展示的GC日志信息绝对是我们重磅推荐的。不用去阅读冗长而又复杂的GC日志,通过容易理解的图形, 也可以得到同样的信息。\n\n## 分析器(Profilers)\n\n下面介绍分析器([profilers](http://zeroturnaround.com/rebellabs/developer-productivity-report-2015-java-performance-survey-results/3/), Oracle官方翻译是:`抽样器`)。相对于前面的工具, 分析器只关心GC中的一部分领域. 本节我们也只关注分析器相关的GC功能。\n\n首先警告 —— 不要认为分析器适用于所有的场景。分析器有时确实作用很大, 比如检测代码中的CPU热点时。但某些情况使用分析器不一定是个好方案。\n\n对GC调优来说也是一样的。要检测是否因为GC而引起延迟或吞吐量问题时, 不需要使用分析器. 前面提到的工具(`jstat`或 原生/可视化GC日志)就能更好更快地检测出是否存在GC问题. 特别是从生产环境中收集性能数据时, 最好不要使用分析器, 因为性能开销非常大。\n\n如果确实需要对GC进行优化, 那么分析器就可以派上用场了, 可以对 Object 的创建信息一目了然. 换个角度看, 如果GC暂停的原因不在某个内存池中, 那就只会是因为创建对象太多了。 所有分析器都能够跟踪对象分配(via allocation profiling), 根据内存分配的轨迹, 让你知道**实际驻留在内存中的是哪些对象**。\n\n分配分析能定位到在哪个地方创建了大量的对象. 使用分析器辅助进行GC调优的好处是, 能确定哪种类型的对象最占用内存, 以及哪些线程创建了最多的对象。\n\n下面我们通过实例介绍3种分配分析器:`**hprof**`,`**JVisualV**`**M**和`**AProf**`。实际上还有很多分析器可供选择, 有商业产品,也有免费工具, 但其功能和应用基本上都是类似的。\n\n### hprof\n\n[hprof 分析器](http://docs.oracle.com/javase/8/docs/technotes/samples/hprof.html)内置于JDK之中。 在各种环境下都可以使用, 一般优先使用这款工具。\n\n要让`hprof`和程序一起运行, 需要修改启动脚本, 类似这样:\n\n\n\n```\njava -agentlib:hprof=heap=sites com.yourcompany.YourApplication\n```\n\n\n\n在程序退出时,会将分配信息dump(转储)到工作目录下的`java.hprof.txt`文件中。使用文本编辑器打开, 并搜索 “**SITES BEGIN**” 关键字, 可以看到:\n\n\n\n```\nSITES BEGIN (ordered by live bytes) Tue Dec  8 11:16:15 2015\n          percent          live          alloc'ed  stack class\n rank   self  accum     bytes objs     bytes  objs trace name\n    1  64.43% 4.43%   8370336 20121  27513408 66138 302116 int[]\n    2  3.26% 88.49%    482976 20124   1587696 66154 302104 java.util.ArrayList\n    3  1.76% 88.74%    241704 20121   1587312 66138 302115 eu.plumbr.demo.largeheap.ClonableClass0006\n    ... 部分省略 ...\n\nSITES END\n```\n\n\n\n从以上片段可以看到, allocations 是根据每次创建的对象数量来排序的。第一行显示所有对象中有`**64.43%**`的对象是整型数组(`int[]`), 在标识为`302116`的位置创建。搜索 “**TRACE 302116**” 可以看到:\n\n\n\n```\nTRACE 302116:   \n    eu.plumbr.demo.largeheap.ClonableClass0006.<init>(GeneratorClass.java:11)\n    sun.reflect.GeneratedConstructorAccessor7.newInstance(<Unknown Source>:Unknown line)\n    sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)\n    java.lang.reflect.Constructor.newInstance(Constructor.java:422)\n```\n\n\n\n现在, 知道有`64.43%`的对象是整数数组, 在`ClonableClass0006`类的构造函数中, 第11行的位置, 接下来就可以优化代码, 以减少GC的压力。\n\n### Java VisualVM\n\n本章前面的第一部分, 在监控 JVM 的GC行为工具时介绍了 JVisualVM , 本节介绍其在分配分析上的应用。\n\nJVisualVM 通过GUI的方式连接到正在运行的JVM。 连接上目标JVM之后 :\n\n1.  打开 “工具” –> “选项” 菜单, 点击**性能分析(Profiler)**标签, 新增配置, 选择 Profiler 内存, 确保勾选了 “Record allocations stack traces”(记录分配栈跟踪)。\n2.  勾选 “Settings”(设置) 复选框, 在内存设置标签下,修改预设配置。\n3.  点击 “Memory”(内存) 按钮开始进行内存分析。\n4.  让程序运行一段时间,以收集关于对象分配的足够信息。\n5.  单击下方的 “Snapshot”(快照) 按钮。可以获取收集到的快照信息。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404224729.png)\n\n完成上面的步骤后, 可以得到类似这样的信息:\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404224743.png)\n\n上图按照每个类被创建的对象数量多少来排序。看第一行可以知道, 创建的最多的对象是`int[]`数组. 鼠标右键单击这行, 就可以看到这些对象都在哪些地方创建的:\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404224753.png)\n\n与`hprof`相比, JVisualVM 更加容易使用 —— 比如上面的截图中, 在一个地方就可以看到所有`int[]`的分配信息, 所以多次在同一处代码进行分配的情况就很容易发现。\n\n### AProf\n\n最重要的一款分析器,是由 Devexperts 开发的[**AProf**](https://code.devexperts.com/display/AProf/About+Aprof)。 内存分配分析器 AProf 也被打包为 Java agent 的形式。\n\n用 AProf 分析应用程序, 需要修改 JVM 启动脚本,类似这样:\n\n\n\n```\njava -javaagent:/path-to/aprof.jar com.yourcompany.YourApplication\n```\n\n\n\n重启应用之后, 工作目录下会生成一个`aprof.txt`文件。此文件每分钟更新一次, 包含这样的信息:\n\n\n\n```\n========================================================================================================================\nTOTAL allocation dump for 91,289 ms (0h01m31s)\nAllocated 1,769,670,584 bytes in 24,868,088 objects of 425 classes in 2,127 locations\n========================================================================================================================\n\nTop allocation-inducing locations with the data types allocated from them\n------------------------------------------------------------------------------------------------------------------------\neu.plumbr.demo.largeheap.ManyTargetsGarbageProducer.newRandomClassObject: 1,423,675,776 (80.44%) bytes in 17,113,721 (68.81%) objects (avg size 83 bytes)\n    int[]: 711,322,976 (40.19%) bytes in 1,709,911 (6.87%) objects (avg size 416 bytes)\n    char[]: 369,550,816 (20.88%) bytes in 5,132,759 (20.63%) objects (avg size 72 bytes)\n    java.lang.reflect.Constructor: 136,800,000 (7.73%) bytes in 1,710,000 (6.87%) objects (avg size 80 bytes)\n    java.lang.Object[]: 41,079,872 (2.32%) bytes in 1,710,712 (6.87%) objects (avg size 24 bytes)\n    java.lang.String: 41,063,496 (2.32%) bytes in 1,710,979 (6.88%) objects (avg size 24 bytes)\n    java.util.ArrayList: 41,050,680 (2.31%) bytes in 1,710,445 (6.87%) objects (avg size 24 bytes)\n          ... cut for brevity ...\n```\n\n\n\n上面的输出是按照`size`进行排序的。可以看出,`80.44%`的 bytes 和`68.81%`的 objects 是在`ManyTargetsGarbageProducer.newRandomClassObject()`方法中分配的。 其中,**int[]**数组占用了`40.19%`的内存, 是最大的一个。\n\n继续往下看, 会发现`allocation traces`(分配痕迹)相关的内容, 也是以 allocation size 排序的:\n\n\n\n```\nTop allocated data types with reverse location traces\n------------------------------------------------------------------------------------------------------------------------\nint[]: 725,306,304 (40.98%) bytes in 1,954,234 (7.85%) objects (avg size 371 bytes)\n    eu.plumbr.demo.largeheap.ClonableClass0006.: 38,357,696 (2.16%) bytes in 92,206 (0.37%) objects (avg size 416 bytes)\n        java.lang.reflect.Constructor.newInstance: 38,357,696 (2.16%) bytes in 92,206 (0.37%) objects (avg size 416 bytes)\n            eu.plumbr.demo.largeheap.ManyTargetsGarbageProducer.newRandomClassObject: 38,357,280 (2.16%) bytes in 92,205 (0.37%) objects (avg size 416 bytes)\n            java.lang.reflect.Constructor.newInstance: 416 (0.00%) bytes in 1 (0.00%) objects (avg size 416 bytes)\n... cut for brevity ...\n```\n\n\n\n可以看到,`int[]`数组的分配, 在`ClonableClass0006`构造函数中继续增大。\n\n和其他工具一样,`AProf`揭露了 分配的大小以及位置信息(`allocation size and locations`), 从而能够快速找到最耗内存的部分。在我们看来,**AProf**是最有用的分配分析器, 因为它只专注于内存分配, 所以做得最好。 当然, 这款工具是开源免费的, 资源开销也最小。\n\n请继续阅读下一章:7\\. GC 调优(实战篇) - GC参考手册\n\n原文链接:[GC Tuning: Tooling](https://plumbr.eu/handbook/gc-tuning-measuring)\n\n翻译时间: 2016年02月06日\n\n## 参考文章\n\n<https://segmentfault.com/a/1190000009707894>\n\n<https://www.cnblogs.com/hysum/p/7100874.html>\n\n<http://c.biancheng.net/view/939.html>\n\n<https://www.runoob.com/>\n\nhttps://blog.csdn.net/android_hl/article/details/53228348\n\n\n"
  },
  {
    "path": "docs/Java/JVM/深入理解JVM虚拟机：JNDI，OSGI，Tomcat类加载器实现.md",
    "content": "# 目录\n  * [打破双亲委派模型](#打破双亲委派模型)\n    * [JNDI](#jndi)\n    * [JNDI 的理解](#[jndi-的理解])\n  * [OSGI](#osgi)\n    * [1.如何正确的理解和认识OSGI技术？](#1如何正确的理解和认识osgi技术？)\n  * [Tomcat类加载器以及应用间class隔离与共享](#tomcat类加载器以及应用间class隔离与共享)\n    * [类加载器](#类加载器)\n  * [参考文章](#参考文章)\n\n\n\n本文转自互联网，侵删\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章将同步到我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《深入理解JVM虚拟机》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，一步步地学习JVM基础知识，并上手进行JVM调优实战，JVM是每一个Java工程师必须要学习和理解的知识点，你必须要掌握其实现原理，才能更完整地了解整个Java技术体系，形成自己的知识框架。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n## 打破双亲委派模型\n\n### JNDI\n\n### JNDI 的理解\n\n\nJNDI是 Java 命名与文件夹接口（Java Naming and Directory Interface），在J2EE规范中是重要的规范之中的一个，不少专家觉得，没有透彻理解JNDI的意义和作用，就没有真正掌握J2EE特别是EJB的知识。\n\n那么，JNDI究竟起什么作用？//带着问题看文章是最有效的\n\n要了解JNDI的作用，我们能够从“假设不用JNDI我们如何做？用了JNDI后我们又将如何做？”这个问题来探讨。\n\n没有JNDI的做法：\n\n程序猿开发时，知道要开发訪问MySQL数据库的应用，于是将一个对 MySQL JDBC 驱动程序类的引用进行了编码，并通过使用适当的 JDBC URL 连接到数据库。\n就像以下代码这样：\n\n```` \n    1.  Connectionconn=null;\n    2.  try{\n    3.  Class.forName(\"com.mysql.jdbc.Driver\",\n    4.  true,Thread.currentThread().getContextClassLoader());\n    5.  conn=DriverManager.\n    6.  getConnection(\"jdbc:mysql://MyDBServer?user=qingfeng&password=mingyue\");\n    7.  ......\n    8.  conn.close();\n    9.  }catch(Exceptione){\n    10.  e.printStackTrace();\n    11.  }finally{\n    12.  if(conn!=null){\n    13.  try{\n    14.  conn.close();\n    15.  }catch(SQLExceptione){}\n    16.  }\n    17.  }\n````\n\n\n这是传统的做法，也是曾经非Java程序猿（如Delphi、VB等）常见的做法。\n\n这种做法一般在小规模的开发过程中不会产生问题，仅仅要程序猿熟悉Java语言、了解JDBC技术和MySQL，能够非常快开发出对应的应用程序。\n\n没有JNDI的做法存在的问题：\n\n    1、数据库server名称MyDBServer 、username和口令都可能须要改变，由此引发JDBC URL须要改动；\n    2、数据库可能改用别的产品，如改用DB2或者Oracle，引发JDBC驱动程序包和类名须要改动；\n    3、随着实际使用终端的添加，原配置的连接池參数可能须要调整；\n    4、......\n\n解决的方法：\n\n程序猿应该不须要关心“详细的数据库后台是什么？JDBC驱动程序是什么？JDBC URL格式是什么？訪问数据库的username和口令是什么？”等等这些问题。\n\n程序猿编写的程序应该没有对 JDBC驱动程序的引用，没有server名称，没实username称或口令 —— 甚至没有数据库池或连接管理。\n\n而是把这些问题交给J2EE容器（比方weblogic）来配置和管理，程序猿仅仅须要对这些配置和管理进行引用就可以。\n\n由此，就有了JNDI。\n\n//看的出来。是为了一个最最核心的问题：是为了解耦，是为了开发出更加可维护、可扩展//的系统\n\n用了JNDI之后的做法：\n首先。在在J2EE容器中配置JNDI參数，定义一个数据源。也就是JDBC引用參数，给这个数据源设置一个名称；然后，在程序中，通过数据源名称引用数据源从而訪问后台数据库。\n\n//红色的字能够看出。JNDI是由j2ee容器提供的功能\n\n详细操作例如以下（以JBoss为例）：\n1、配置数据源\n在JBoss 的 D:\\jboss420GA\\docs\\examples\\jca 文件夹以下。有非常多不同数据库引用的数据源定义模板。\n\n将当中的 mysql-ds.xml 文件Copy到你使用的server下，如 D:\\jboss420GA\\server\\default\\deploy。\n改动 mysql-ds.xml 文件的内容，使之能通过JDBC正确訪问你的MySQL数据库。例如以下：\n\n\n    \n````    \n<?xmlversion=\"1.0\"encoding=\"UTF-8\"?>\n<datasources>\n<local-tx-datasource>\n<jndi-name>MySqlDS</jndi-name>\n<connection-url>jdbc:mysql://localhost:3306/lw</connection-url>\n<driver-class>com.mysql.jdbc.Driver</driver-class>\n<user-name>root</user-name>\n<password>rootpassword</password>\n<exception-sorter-class-name>\norg.jboss.resource.adapter.jdbc.vendor.MySQLExceptionSorter\n</exception-sorter-class-name>\n<metadata>\n<type-mapping>mySQL</type-mapping>\n</metadata>\n</local-tx-datasource>\n</datasources>\n````\n\n\n这里，定义了一个名为MySqlDS的数据源。其參数包含JDBC的URL。驱动类名，username及密码等。\n\n2、在程序中引用数据源：\n\n````    \n1.  Connectionconn=null;\n2.  try{\n3.  Contextctx=newInitialContext();\n4.  ObjectdatasourceRef=ctx.lookup(\"java:MySqlDS\");//引用数据源\n5.  DataSourceds=(Datasource)datasourceRef;\n6.  conn=ds.getConnection();\n7.  ......\n8.  c.close();\n9.  }catch(Exceptione){\n10.  e.printStackTrace();\n11.  }finally{\n12.  if(conn!=null){\n13.  try{\n14.  conn.close();\n15.  }catch(SQLExceptione){}\n16.  }\n17.  }\n````    \n\n\n直接使用JDBC或者通过JNDI引用数据源的编程代码量相差无几，可是如今的程序能够不用关心详细JDBC參数了。\n\n//解藕了。可扩展了\n在系统部署后。假设数据库的相关參数变更。仅仅须要又一次配置 mysql-ds.xml 改动当中的JDBC參数，仅仅要保证数据源的名称不变，那么程序源码就无需改动。\n\n由此可见。JNDI避免了程序与数据库之间的紧耦合，使应用更加易于配置、易于部署。\n\nJNDI的扩展：\nJNDI在满足了数据源配置的要求的基础上。还进一步扩充了作用：全部与系统外部的资源的引用，都能够通过JNDI定义和引用。\n\n//注意什么叫资源\n\n所以，在J2EE规范中，J2EE 中的资源并不局限于 JDBC 数据源。\n\n引用的类型有非常多，当中包含资源引用（已经讨论过）、环境实体和 EJB 引用。\n\n特别是 EJB 引用，它暴露了 JNDI 在 J2EE 中的另外一项关键角色：查找其它应用程序组件。\n\nEJB 的 JNDI 引用非常相似于 JDBC 资源的引用。在服务趋于转换的环境中，这是一种非常有效的方法。能够对应用程序架构中所得到的全部组件进行这类配置管理，从 EJB 组件到 JMS 队列和主题。再到简单配置字符串或其它对象。这能够降低随时间的推移服务变更所产生的维护成本，同一时候还能够简化部署，降低集成工作。外部资源”。\n\n总结：\n\nJ2EE 规范要求全部 J2EE 容器都要提供 JNDI 规范的实现。//sun 果然喜欢制定规范JNDI 在 J2EE 中的角色就是“交换机” —— J2EE 组件在执行时间接地查找其它组件、资源或服务的通用机制。在多数情况下，提供 JNDI 供应者的容器能够充当有限的数据存储。这样管理员就能够设置应用程序的执行属性，并让其它应用程序引用这些属性（Java 管理扩展（Java Management Extensions，JMX）也能够用作这个目的）。JNDI 在 J2EE 应用程序中的主要角色就是提供间接层，这样组件就能够发现所须要的资源，而不用了解这些间接性。\n\n在 J2EE 中，JNDI 是把 J2EE 应用程序合在一起的粘合剂。JNDI 提供的间接寻址同意跨企业交付可伸缩的、功能强大且非常灵活的应用程序。\n\n这是 J2EE 的承诺，并且经过一些计划和预先考虑。这个承诺是全然能够实现的。\n\n 从上面的文章中能够看出：\n1、JNDI 提出的目的是为了解藕，是为了开发更加easy维护，easy扩展。easy部署的应用。\n2、JNDI 是一个sun提出的一个规范(相似于jdbc),详细的实现是各个j2ee容器提供商。sun 仅仅是要求，j2ee容器必须有JNDI这种功能。\n\n3、JNDI 在j2ee系统中的角色是“交换机”，是J2EE组件在执行时间接地查找其它组件、资源或服务的通用机制。\n4、JNDI 是通过资源的名字来查找的，资源的名字在整个j2ee应用中(j2ee容器中)是唯一的。\n\n\n 上文提到过双亲委派模型并不是一个强制性的约束模型，而是Java设计者推荐给开发者的类加载器实现方式。在Java的世界中大部分的类加载器都遵循这个模型，但也有例外。\n\n 双亲委派模型的一次“被破坏”是由这个模型自身的缺陷所导致的，双亲委派很好地解决了各个类加载器的基础类的统一问题（越基础的类由越上层的加载器进行加载），基础类之所以称为“基础”，是因为它们总是作为被用户代码调用的API，但世事往往没有绝对的完美，如果基础类又要调用回用户的代码，那该怎么办？\n\n这并非是不可能的事情，一个典型的例子便是JNDI服务，JNDI现在已经是Java的标准服务，它的代码由启动类加载器去加载（在JDK 1.3时放进去的rt.jar），但JNDI的目的就是对资源进行集中管理和查找，它需要调用由独立厂商实现并部署在应用程序的ClassPath下的JNDI接口提供者（SPI，Service Provider Interface）的代码，但启动类加载器不可能“认识”这些代码，因为启动类加载器的搜索范围中找不到用户应用程序类，那该怎么办？\n\n\n为了解决这个问题，Java设计团队只好引入了一个不太优雅的设计：线程上下文类加载器（Thread Context ClassLoader）。这个类加载器可以通过java.lang.Thread类的setContextClassLoader()方法进行设置，如果创建线程时还未设置，它将会从父线程中继承一个，如果在应用程序的全局范围内都没有设置过的话，那这个类加载器默认就是应用程序类加载器（Application ClassLoader）。\n\n 有了线程上下文类加载器，就可以做一些“舞弊”的事情了，JNDI服务使用这个线程上下文类加载器去加载所需要的SPI代码，也就是父类加载器请求子类加载器去完成类加载的动作，这种行为实际上就是打通了双亲委派模型的层次结构来逆向使用类加载器，实际上已经违背了双亲委派模型的一般性原则，但这也是无可奈何的事情。Java中所有涉及SPI的加载动作基本上都采用这种方式，例如JNDI、JDBC、JCE、JAXB和JBI等。\n\n  \n\n## OSGI\n\n  \n\n目前，业内关于OSGI技术的学习资源或者技术文档还是很少的。我在某宝网搜索了一下“OSGI”的书籍，结果倒是有，但是种类少的可怜，而且几乎没有人购买。\n因为工作的原因我需要学习OSGI，所以我不得不想尽办法来主动学习OSGI。我将用文字记录学习OSGI的整个过程，通过整理书籍和视频教程，来让我更加了解这门技术，同时也让需要学习这门技术的同志们有一个清晰的学习路线。\n\n我们需要解决一下几问题:\n\n### 1.如何正确的理解和认识OSGI技术？\n\n我们从外文资料上或者从翻译过来的资料上看到OSGi解释和定义，都是直译过来的，但是OSGI的真实意义未必是中文直译过来的意思。OSGI的解释就是Open Service Gateway Initiative，直译过来就是“开放的服务入口(网关)的初始化”，听起来非常费解，什么是服务入口初始化？\n\n所以我们不去直译这个OSGI，我们换一种说法来描述OSGI技术。\n\n我们来回到我们以前的某些开发场景中去，假设我们使用SSH(struts+spring+hibernate)框架来开发我们的Web项目，我们做产品设计和开发的时候都是分模块的，我们分模块的目的就是实现模块之间的“解耦”，更进一步的目的是方便对一个项目的控制和管理。\n我们对一个项目进行模块化分解之后，我们就可以把不同模块交给不同的开发人员来完成开发，然后项目经理把大家完成的模块集中在一起，然后拼装成一个最终的产品。一般我们开发都是这样的基本情况。\n\n那么我们开发的时候预计的是系统的功能，根据系统的功能来进行模块的划分，也就是说，这个产品的功能或客户的需求是划分的重要依据。\n\n但是我们在开发过程中，我们模块之间还要彼此保持联系，比如A模块要从B模块拿到一些数据，而B模块可能要调用C模块中的一些方法(除了公共底层的工具类之外)。所以这些模块只是一种逻辑意义上的划分。\n\n最重要的一点是，我们把最终的项目要去部署到tomcat或者jBoss的服务器中去部署。那么我们启动服务器的时候，能不能关闭项目的某个模块或功能呢？很明显是做不到的，一旦服务器启动，所有模块就要一起启动，都要占用服务器资源，所以关闭不了模块，假设能强制拿掉，就会影响其它的功能。\n\n以上就是我们传统模块式开发的一些局限性。\n\n我们做软件开发一直在追求一个境界，就是模块之间的真正“解耦”、“分离”，这样我们在软件的管理和开发上面就会更加的灵活，甚至包括给客户部署项目的时候都可以做到更加的灵活可控。但是我们以前使用SSH框架等架构模式进行产品开发的时候我们是达不到这种要求的。\n\n所以我们“架构师”或顶尖的技术高手都在为模块化开发努力的摸索和尝试，然后我们的OSGI的技术规范就应运而生。\n\n现在我们的OSGI技术就可以满足我们之前所说的境界:在不同的模块中做到彻底的分离，而不是逻辑意义上的分离，是物理上的分离，也就是说在运行部署之后都可以在不停止服务器的时候直接把某些模块拿下来，其他模块的功能也不受影响。\n\n由此，OSGI技术将来会变得非常的重要，因为它在实现模块化解耦的路上，走得比现在大家经常所用的SSH框架走的更远。这个技术在未来大规模、高访问、高并发的Java模块化开发领域，或者是项目规范化管理中，会大大超过SSH等框架的地位。\n\n现在主流的一些应用服务器，Oracle的weblogic服务器，IBM的WebSphere，JBoss，还有Sun公司的glassfish服务器，都对OSGI提供了强大的支持，都是在OSGI的技术基础上实现的。有那么多的大型厂商支持OSGI这门技术，我们既可以看到OSGI技术的重要性。所以将来OSGI是将来非常重要的技术。 \n\n但是OSGI仍然脱离不了框架的支持，因为OSGI本身也使用了很多spring等框架的基本控件(因为要实现AOP依赖注入等功能)，但是哪个项目又不去依赖第三方jar呢？\n\n  \n 双亲委派模型的另一次“被破坏”是由于用户对程序动态性的追求而导致的，这里所说的“动态性”指的是当前一些非常“热门”的名词：代码热替换（HotSwap）、模块热部署（HotDeployment）等，说白了就是希望应用程序能像我们的计算机外设那样，接上鼠标、U盘，不用重启机器就能立即使用，鼠标有问题或要升级就换个鼠标，不用停机也不用重启。\n\n 对于个人计算机来说，重启一次其实没有什么大不了的，但对于一些生产系统来说，关机重启一次可能就要被列为生产事故，这种情况下热部署就对软件开发者，尤其是企业级软件开发者具有很大的吸引力。Sun公司所提出的JSR-294、JSR-277规范在与JCP组织的模块化规范之争中落败给JSR-291（即OSGi R4.2），虽然Sun不甘失去Java模块化的主导权，独立在发展Jigsaw项目，但目前OSGi已经成为了业界“事实上”的Java模块化标准，而OSGi实现模块化热部署的关键则是它自定义的类加载器机制的实现。\n\n 每一个程序模块（OSGi中称为Bundle）都有一个自己的类加载器，当需要更换一个Bundle时，就把Bundle连同类加载器一起换掉以实现代码的热替换。\n\n 在OSGi环境下，类加载器不再是双亲委派模型中的树状结构，而是进一步发展为更加复杂的网状结构，当收到类加载请求时，OSGi将按照下面的顺序进行类搜索：\n\n    1）将以java.*开头的类委派给父类加载器加载。\n    \n    2）否则，将委派列表名单内的类委派给父类加载器加载。\n    \n    3）否则，将Import列表中的类委派给Export这个类的Bundle的类加载器加载。\n    \n    4）否则，查找当前Bundle的ClassPath，使用自己的类加载器加载。\n    \n    5）否则，查找类是否在自己的Fragment Bundle中，如果在，则委派给Fragment Bundle的类加载器加载。\n    \n    6）否则，查找Dynamic Import列表的Bundle，委派给对应Bundle的类加载器加载。\n    \n    7）否则，类查找失败。\n\n 上面的查找顺序中只有开头两点仍然符合双亲委派规则，其余的类查找都是在平级的类加载器中进行的。\n\n 只要有足够意义和理由，突破已有的原则就可认为是一种创新。正如OSGi中的类加载器并不符合传统的双亲委派的类加载器，并且业界对其为了实现热部署而带来的额外的高复杂度还存在不少争议，但在Java程序员中基本有一个共识：OSGi中对类加载器的使用是很值得学习的，弄懂了OSGi的实现，就可以算是掌握了类加载器的精髓。\n\n\n## Tomcat类加载器以及应用间class隔离与共享\n\n\n\nTomcat的用户一定都使用过其应用部署功能，无论是直接拷贝文件到webapps目录，还是修改server.xml以目录的形式部署，或者是增加虚拟主机，指定新的appBase等等。\n\n但部署应用时，不知道你是否曾注意过这几点：\n\n1.  如果在一个Tomcat内部署多个应用，甚至多个应用内使用了某个类似的几个不同版本，但它们之间却互不影响。这是如何做到的。\n\n2.  如果多个应用都用到了某类似的相同版本，是否可以统一提供，不在各个应用内分别提供，占用内存呢。\n\n3.  还有时候，在开发Web应用时，在pom.xml中添加了servlet-api的依赖，那实际应用的class加载时，会加载你的servlet-api 这个jar吗\n\n以上提到的这几点，在Tomcat以及各类的应用服务器中，都是通过类加载器（ClasssLoader）来实现的。通过本文，你可以了解到Tomcat内部提供的各种类加载器，Web应用的class和资源等加载的方式，以及其内部的实现原理。在遇到类似问题时，更胸有成竹。\n\n### 类加载器\n\nJava语言本身，以及现在其它的一些基于JVM之上的语言(Groovy，Jython， Scala...)，都是在将代码编译生成class文件，以实现跨多平台，write once, run anywhere。最终的这些class文件，在应用中，又被加载到JVM虚拟机中，开始工作。而把class文件加载到JVM的组件，就是我们所说的类加载器。而对于类加载器的抽象，能面对更多的class数据提供形式，例如网络、文件系统等。\n\nJava中常见的那个ClassNotFoundException和NoClassDefFoundError就是类加载器告诉我们的。\n\nServlet规范指出，容器用于加载Web应用内Servlet的class loader, 允许加载位于Web应用内的资源。但不允许重写java.*, javax.*以及容器实现的类。同时\n\n每个应用内使用Thread.currentThread.getContextClassLoader()获得的类加载器，都是该应用区别于其它应用的类加载器等等。\n\n根据Servlet规范，各个应用服务器厂商自行实现。所以像其他的一些应用服务器一样， Tomcat也提供了多种的类加载器，以便应用服务器内的class以及部署的Web应用类文件运行在容器中时，可以使用不同的class repositories。\n\n在Java中，类加载器是以一种父子关系树来组织的。除Bootstrap外，都会包含一个parent 类加载器。(这里写parent 类加载器，而不是父类加载器，不是为了装X，是为了避免和Java里的父类混淆)一般以类加载器需要加载一个class或者资源文件的时候，他会先委托给他的parent类加载器，让parent类加载器先来加载，如果没有，才再在自己的路径上加载。这就是人们常说的双亲委托，即把类加载的请求委托给parent。\n\n但是...，这里需要注意一下\n\n> 对于Web应用的类加载，和上面的双亲委托是有区别的。\n\n 主流的Java Web服务器（也就是Web容器），如Tomcat、Jetty、WebLogic、WebSphere或其他笔者没有列举的服务器，都实现了自己定义的类加载器（一般都不止一个）。因为一个功能健全的Web容器，要解决如下几个问题：\n\n 1）部署在同一个Web容器上的两个Web应用程序所使用的Java类库可以实现相互隔离。这是最基本的需求，两个不同的应用程序可能会依赖同一个第三方类库的不同版本，不能要求一个类库在一个服务器中只有一份，服务器应当保证两个应用程序的类库可以互相独立使用。\n\n 2）部署在同一个Web容器上的两个Web应用程序所使用的Java类库可以互相共享。这个需求也很常见，例如，用户可能有10个使用[spring](https://yq.aliyun.com/go/articleRenderRedirect?url=https%3A%2F%2Flink.juejin.im%2F%3Ftarget%3Dhttp%253A%252F%252Flib.csdn.net%252Fbase%252Fjavaee \"Java EE知识库\")组织的应用程序部署在同一台服务器上，如果把10份Spring分别存放在各个应用程序的隔离目录中，将会是很大的资源浪费——这主要倒不是浪费磁盘空间的问题，而是指类库在使用时都要被加载到Web容器的内存，如果类库不能共享，虚拟机的方法区就会很容易出现过度膨胀的风险。\n\n 3）Web容器需要尽可能地保证自身的安全不受部署的Web应用程序影响。目前，有许多主流的Java Web容器自身也是使用Java语言来实现的。因此，Web容器本身也有类库依赖的问题，一般来说，基于安全考虑，容器所使用的类库应该与应用程序的类库互相独立。\n\n 4）支持JSP应用的Web容器，大多数都需要支持HotSwap功能。我们知道，JSP文件最终要编译成Java Class才能由虚拟机执行，但JSP文件由于其纯文本存储的特性，运行时修改的概率远远大于第三方类库或程序自身的Class文件。而且ASP、[PHP](https://yq.aliyun.com/go/articleRenderRedirect?url=https%3A%2F%2Flink.juejin.im%2F%3Ftarget%3Dhttp%253A%252F%252Flib.csdn.net%252Fbase%252Fphp \"PHP知识库\")和JSP这些网页应用也把修改后无须重启作为一个很大的“优势”来看待，因此“主流”的Web容器都会支持JSP生成类的热替换，当然也有“非主流”的，如运行在生产模式（Production Mode）下的WebLogic服务器默认就不会处理JSP文件的变化。\n\n 由于存在上述问题，在部署Web应用时，单独的一个Class Path就无法满足需求了，所以各种Web容都“不约而同”地提供了好几个Class Path路径供用户存放第三方类库，这些路径一般都以“lib”或“classes”命名。被放置到不同路径中的类库，具备不同的访问范围和服务对象，通常，每一个目录都会有一个相应的自定义类加载器去加载放置在里面的Java类库。现在，就以Tomcat容器为例，看一看Tomcat具体是如何规划用户类库结构和类加载器的。\n\n 在Tomcat目录结构中，有3组目录（“/common/*”、“/server/*”和“/shared/*”）可以存放Java类库，另外还可以加上Web应用程序自身的目录“/WEB-INF/*”，一共4组，把Java类库放置在这些目录中的含义分别如下：\n\n ①放置在/common目录中：类库可被Tomcat和所有的Web应用程序共同使用。\n\n ②放置在/server目录中：类库可被Tomcat使用，对所有的Web应用程序都不可见。\n\n ③放置在/shared目录中：类库可被所有的Web应用程序共同使用，但对Tomcat自己不可见。\n\n ④放置在/WebApp/WEB-INF目录中：类库仅仅可以被此Web应用程序使用，对Tomcat和其他Web应用程序都不可见。\n\n 为了支持这套目录结构，并对目录里面的类库进行加载和隔离，Tomcat自定义了多个类加载器，这些类加载器按照经典的双亲委派模型来实现，其关系如下图所示。\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404222020.png)\n  \n\n  \n\n\n\n 上图中灰色背景的3个类加载器是JDK默认提供的类加载器，这3个加载器的作用已经介绍过了。而CommonClassLoader、CatalinaClassLoader、SharedClassLoader和WebappClassLoader则是Tomcat自己定义的类加载器，它们分别加载/common/*、/server/*、/shared/*和/WebApp/WEB-INF/*中的Java类库。其中WebApp类加载器和Jsp类加载器通常会存在多个实例，每一个Web应用程序对应一个WebApp类加载器，每一个JSP文件对应一个Jsp类加载器。\n\n 从图中的委派关系中可以看出，CommonClassLoader能加载的类都可以被CatalinaClassLoader和SharedClassLoader使用，而CatalinaClassLoader和SharedClassLoader自己能加载的类则与对方相互隔离。WebAppClassLoader可以使用SharedClassLoader加载到的类，但各个WebAppClassLoader实例之间相互隔离。\n\n 而JasperLoader的加载范围仅仅是这个JSP文件所编译出来的那一个.Class文件，它出现的目的就是为了被丢弃：当Web容器检测到JSP文件被修改时，会替换掉目前的JasperLoader的实例，并通过再建立一个新的Jsp类加载器来实现JSP文件的HotSwap功能。\n\n对于Tomcat的6.x版本，只有指定了tomcat/conf/catalina.properties配置文件的server.loader和share.loader项后才会真正建立CatalinaClassLoader和Shared ClassLoader的实例，否则在用到这两个类加载器的地方都会用Common ClassLoader的实例代替，而默认的配置文件中没有设置这两个loader项，所以Tomcat 6.x顺理成章地把/common、/server和/shared三个目录默认合并到一起变成一个/lib目录，这个目录里的类库相当于以前/common目录中类库的作用。\n\n这是Tomcat设计团队为了简化大多数的部署场景所做的一项改进，如果默认设置不能满足需要，用户可以通过修改配置文件指定server.loader和share.loader的方式重新启用Tomcat 5.x的加载器[架构](https://yq.aliyun.com/go/articleRenderRedirect?url=https%3A%2F%2Flink.juejin.im%2F%3Ftarget%3Dhttp%253A%252F%252Flib.csdn.net%252Fbase%252Farchitecture \"大型网站架构知识库\")。\n\n  Tomcat加载器的实现清晰易懂，并且采用了官方推荐的“正统”的使用类加载器的方式。如果读者阅读完上面的案例后，能完全理解Tomcat设计团队这样布置加载器架构的用意，那说明已经大致掌握了类加载器“主流”的使用方式，那么笔者不妨再提一个问题让读者思考一下：前面曾经提到过一个场景，如果有10个Web应用程序都是用Spring来进行组织和管理的话，可以把Spring放到Common或Shared目录下让这些程序共享。\n\n  Spring要对用户程序的类进行管理，自然要能访问到用户程序的类，而用户的程序显然是放在/WebApp/WEB-INF目录中的，那么被CommonClassLoader或SharedClassLoader加载的Spring如何访问并不在其加载范围内的用户程序呢？如果研究过虚拟机类加载器机制中的双亲委派模型，相信读者可以很容易地回答这个问题。\n\n分析：如果按主流的双亲委派机制，显然无法做到让父类加载器加载的类去访问子类加载器加载的类，上面在类加载器一节中提到过通过线程上下文方式传播类加载器。\n\n 答案是使用线程上下文类加载器来实现的，使用线程上下文加载器，可以让父类加载器请求子类加载器去完成类加载的动作。\n\n 看spring源码发现，spring加载类所用的Classloader是通过Thread.currentThread().getContextClassLoader()来获取的，而当线程创建时会默认setContextClassLoader(AppClassLoader)，即线程上下文类加载器被设置为AppClassLoader，spring中始终可以获取到这个AppClassLoader(在Tomcat里就是WebAppClassLoader)子类加载器来加载bean，以后任何一个线程都可以通过getContextClassLoader()获取到WebAppClassLoader来getbean了。\n\n  \n\n本篇博文内容取材自《深入理解Java虚拟机：JVM高级特性与最佳实践》\n\n## 参考文章\n\n<https://segmentfault.com/a/1190000009707894>\n\n<https://www.cnblogs.com/hysum/p/7100874.html>\n\n<http://c.biancheng.net/view/939.html>\n\n<https://www.runoob.com/>\n\nhttps://blog.csdn.net/android_hl/article/details/53228348\n\n\n"
  },
  {
    "path": "docs/Java/JVM/深入理解JVM虚拟机：JVM内存的结构与消失的永久代.md",
    "content": "# 目录\n  * [前言](#前言)\n  * [Java堆（Heap）](#java堆（heap）)\n  * [方法区（Method Area）](#方法区（method-area）)\n  * [程序计数器（Program Counter Register）](#程序计数器（program-counter-register）)\n  * [JVM栈（JVM Stacks）](#jvm栈（jvm-stacks）)\n  * [本地方法栈（Native Method Stacks）](#本地方法栈（native-method-stacks）)\n  * [哪儿的OutOfMemoryError](#哪儿的outofmemoryerror)\n  * [一、背景](#一、背景)\n    * [1.1 永久代（PermGen）在哪里？](#11-永久代（permgen）在哪里？)\n    * [1.2 JDK8永久代的废弃](#12-jdk8永久代的废弃)\n  * [二、为什么废弃永久代（PermGen）](#二、为什么废弃永久代（permgen）)\n    * [2.1 官方说明](#21-官方说明)\n  * [Motivation](#motivation)\n    * [2.2 现实使用中易出问题](#22-现实使用中易出问题)\n  * [三、深入理解元空间（Metaspace）](#三、深入理解元空间（metaspace）)\n    * [3.1元空间的内存大小](#31元空间的内存大小)\n    * [3.2常用配置参数](#32常用配置参数)\n    * [3.3测试并追踪元空间大小](#33测试并追踪元空间大小)\n      * [3.3.1.测试字符串常量](#331测试字符串常量)\n      * [3.3.2.测试元空间溢出](#332测试元空间溢出)\n  * [四、总结](#四、总结)\n  * [参考文章](#参考文章)\n\n\n\n本文转自互联网，侵删\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章将同步到我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《深入理解JVM虚拟机》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，一步步地学习JVM基础知识，并上手进行JVM调优实战，JVM是每一个Java工程师必须要学习和理解的知识点，你必须要掌握其实现原理，才能更完整地了解整个Java技术体系，形成自己的知识框架。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n## 前言\n\n所有的Java开发人员可能会遇到这样的困惑？我该为堆内存设置多大空间呢？OutOfMemoryError的异常到底涉及到运行时数据的哪块区域？该怎么解决呢？\n\n其实如果你经常解决服务器性能问题，那么这些问题就会变的非常常见，了解JVM内存也是为了服务器出现性能问题的时候可以快速的了解那块的内存区域出现问题，以便于快速的解决生产故障。\n\n先看一张图，这张图能很清晰的说明JVM内存结构布局。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404214718.png)\n\nJVM内存结构主要有三大块：堆内存、方法区和栈。堆内存是JVM中最大的一块由年轻代和老年代组成，而年轻代内存又被分成三部分，Eden空间、From Survivor空间、To Survivor空间,默认情况下年轻代按照8:1:1的比例来分配；\n\n方法区存储类信息、常量、静态变量等数据，是线程共享的区域，为与Java堆区分，方法区还有一个别名Non-Heap(非堆)；栈又分为java虚拟机栈和本地方法栈主要用于方法的执行。\n\n在通过一张图来了解如何通过参数来控制各区域的内存大小\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404214735.png)\n控制参数\n\n*   -Xms设置堆的最小空间大小。\n*   -Xmx设置堆的最大空间大小。\n*   -XX:NewSize设置新生代最小空间大小。\n*   -XX:MaxNewSize设置新生代最大空间大小。\n*   -XX:PermSize设置永久代最小空间大小。\n*   -XX:MaxPermSize设置永久代最大空间大小。\n*   -Xss设置每个线程的堆栈大小。\n\n没有直接设置老年代的参数，但是可以设置堆空间大小和新生代空间大小两个参数来间接控制。\n\n> 老年代空间大小=堆空间大小-年轻代大空间大小\n\n从更高的一个维度再次来看JVM和系统调用之间的关系\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404214754.png)\n\n方法区和对是所有线程共享的内存区域；而java栈、本地方法栈和程序员计数器是运行是线程私有的内存区域。\n\n下面我们详细介绍每个区域的作用\n\n## Java堆（Heap）\n\n对于大多数应用来说，Java堆（Java Heap）是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域，在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例，几乎所有的对象实例都在这里分配内存。\n\nJava堆是垃圾收集器管理的主要区域，因此很多时候也被称做“GC堆”。如果从内存回收的角度看，由于现在收集器基本都是采用的分代收集算法，所以Java堆中还可以细分为：新生代和老年代；再细致一点的有Eden空间、From Survivor空间、To Survivor空间等。\n\n根据Java虚拟机规范的规定，Java堆可以处于物理上不连续的内存空间中，只要逻辑上是连续的即可，就像我们的磁盘空间一样。在实现时，既可以实现成固定大小的，也可以是可扩展的，不过当前主流的虚拟机都是按照可扩展来实现的（通过-Xmx和-Xms控制）。\n\n如果在堆中没有内存完成实例分配，并且堆也无法再扩展时，将会抛出OutOfMemoryError异常。\n\n## 方法区（Method Area）\n\n方法区（Method Area）与Java堆一样，是各个线程共享的内存区域，它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分，但是它却有一个别名叫做Non-Heap（非堆），目的应该是与Java堆区分开来。\n\n对于习惯在HotSpot虚拟机上开发和部署程序的开发者来说，很多人愿意把方法区称为“永久代”（Permanent Generation），本质上两者并不等价，仅仅是因为HotSpot虚拟机的设计团队选择把GC分代收集扩展至方法区，或者说使用永久代来实现方法区而已。\n\nJava虚拟机规范对这个区域的限制非常宽松，除了和Java堆一样不需要连续的内存和可以选择固定大小或者可扩展外，还可以选择不实现垃圾收集。相对而言，垃圾收集行为在这个区域是比较少出现的，但并非数据进入了方法区就如永久代的名字一样“永久”存在了。这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载，一般来说这个区域的回收“成绩”比较难以令人满意，尤其是类型的卸载，条件相当苛刻，但是这部分区域的回收确实是有必要的。\n\n根据Java虚拟机规范的规定，当方法区无法满足内存分配需求时，将抛出OutOfMemoryError异常。\n\n方法区有时被称为持久代（PermGen）。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404214839.png)\n\n所有的对象在实例化后的整个运行周期内，都被存放在堆内存中。堆内存又被划分成不同的部分：伊甸区(Eden)，幸存者区域(Survivor Sapce)，老年代（Old Generation Space）。\n\n方法的执行都是伴随着线程的。原始类型的本地变量以及引用都存放在线程栈中。而引用关联的对象比如String，都存在在堆中。为了更好的理解上面这段话，我们可以看一个例子：\n\n\n\n\n````\nimport java.text.SimpleDateFormat;import java.util.Date;import org.apache.log4j.Logger;\n public class HelloWorld {\n  private static Logger LOGGER = Logger.getLogger(HelloWorld.class.getName());\n  public void sayHello(String message) {\n    SimpleDateFormat formatter = new SimpleDateFormat(\"dd.MM.YYYY\");\n    String today = formatter.format(new Date());\n    LOGGER.info(today + \": \" + message);\n  }}\n\n````\n\n\n\n这段程序的数据在内存中的存放如下：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404214906.png)\n通过JConsole工具可以查看运行中的Java程序（比如Eclipse）的一些信息：堆内存的分配，线程的数量以及加载的类的个数；\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404214922.png)\n\n## 程序计数器（Program Counter Register）\n\n程序计数器（Program Counter Register）是一块较小的内存空间，它的作用可以看做是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里（仅是概念模型，各种虚拟机可能会通过一些更高效的方式去实现），字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令，分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。\n\n由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的，在任何一个确定的时刻，一个处理器（对于多核处理器来说是一个内核）只会执行一条线程中的指令。因此，为了线程切换后能恢复到正确的执行位置，每条线程都需要有一个独立的程序计数器，各条线程之间的计数器互不影响，独立存储，我们称这类内存区域为“线程私有”的内存。\n\n如果线程正在执行的是一个Java方法，这个计数器记录的是正在执行的虚拟机字节码指令的地址；如果正在执行的是Natvie方法，这个计数器值则为空（Undefined）。\n\n此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。\n\n## JVM栈（JVM Stacks）\n\n与程序计数器一样，Java虚拟机栈（Java Virtual Machine Stacks）也是线程私有的，它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型：每个方法被执行的时候都会同时创建一个栈帧（Stack Frame）用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程，就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。\n\n局部变量表存放了编译期可知的各种基本数据类型（boolean、byte、char、short、int、float、long、double）、对象引用（reference类型，它不等同于对象本身，根据不同的虚拟机实现，它可能是一个指向对象起始地址的引用指针，也可能指向一个代表对象的句柄或者其他与此对象相关的位置）和returnAddress类型（指向了一条字节码指令的地址）。\n\n其中64位长度的long和double类型的数据会占用2个局部变量空间（Slot），其余的数据类型只占用1个。局部变量表所需的内存空间在编译期间完成分配，当进入一个方法时，这个方法需要在帧中分配多大的局部变量空间是完全确定的，在方法运行期间不会改变局部变量表的大小。\n\n在Java虚拟机规范中，对这个区域规定了两种异常状况：如果线程请求的栈深度大于虚拟机所允许的深度，将抛出StackOverflowError异常；如果虚拟机栈可以动态扩展（当前大部分的Java虚拟机都可动态扩展，只不过Java虚拟机规范中也允许固定长度的虚拟机栈），当扩展时无法申请到足够的内存时会抛出OutOfMemoryError异常。\n\n## 本地方法栈（Native Method Stacks）\n\n本地方法栈（Native Method Stacks）与虚拟机栈所发挥的作用是非常相似的，其区别不过是虚拟机栈为虚拟机执行Java方法（也就是字节码）服务，而本地方法栈则是为虚拟机使用到的Native方法服务。虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并没有强制规定，因此具体的虚拟机可以自由实现它。甚至有的虚拟机（譬如Sun HotSpot虚拟机）直接就把本地方法栈和虚拟机栈合二为一。与虚拟机栈一样，本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常。\n\n## 哪儿的OutOfMemoryError\n\n对内存结构清晰的认识同样可以帮助理解不同OutOfMemoryErrors：\n\n\nException in thread “main”: java.lang.OutOfMemoryError: Java heap space\n\n\n原因：对象不能被分配到堆内存中\n\n\nException in thread “main”: java.lang.OutOfMemoryError: PermGen space\n\n\n原因：类或者方法不能被加载到持久代。它可能出现在一个程序加载很多类的时候，比如引用了很多第三方的库；\n\nException in thread “main”: java.lang.OutOfMemoryError: Requested array size exceeds VM limit\n\n\n原因：创建的数组大于堆内存的空间\n\n\nException in thread “main”: java.lang.OutOfMemoryError: request <size> bytes for <reason>. Out of swap space?\n\n\n原因：分配本地分配失败。JNI、本地库或者Java虚拟机都会从本地堆中分配内存空间。\n\n\nException in thread “main”: java.lang.OutOfMemoryError: <reason> <stack trace>（Native method）\n\n\n原因：同样是本地方法内存分配失败，只不过是JNI或者本地方法或者Java虚拟机发现\n\n关于永久代的废弃可以参考这篇文章\n\nJDK8-废弃永久代（PermGen）迎来元空间（Metaspace）\n(https://www.cnblogs.com/yulei126/p/6777323.html)\n\n\n1.背景\n\n2.为什么废弃永久代（PermGen）\n\n3.深入理解元空间（Metaspace）\n\n4.总结\n\n========正文分割线=====\n\n## 一、背景\n\n### 1.1 永久代（PermGen）在哪里？\n\n根据，hotspot jvm结构如下(虚拟机栈和本地方法栈合一起了)：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404215109.png)\n上图引自网络，但有个问题：方法区和heap堆都是线程共享的内存区域。\n\n关于方法区和永久代：\n\n在HotSpot JVM中，这次讨论的永久代，就是上图的方法区（JVM规范中称为方法区）。《Java虚拟机规范》只是规定了有方法区这么个概念和它的作用，并没有规定如何去实现它。在其他JVM上不存在永久代。\n\n### 1.2 JDK8永久代的废弃\n\nJDK8 永久代变化如下图：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404215123.png)\n1.新生代：Eden+From Survivor+To Survivor\n\n2.老年代：OldGen\n\n3.永久代（方法区的实现） : PermGen----->替换为Metaspace(本地内存中)\n\n## 二、为什么废弃永久代（PermGen）\n\n### 2.1 官方说明\n\n参照JEP122：http://openjdk.java.net/jeps/122，原文截取：\n\n## Motivation\n\nThis is part of the JRockit and Hotspot convergence effort. JRockit customers do not need to configure the permanent generation (since JRockit does not have a permanent generation) and are accustomed to not configuring the permanent generation.\n\n即：移除永久代是为融合HotSpot JVM与 JRockit VM而做出的努力，因为JRockit没有永久代，不需要配置永久代。\n\n### 2.2 现实使用中易出问题\n\n由于永久代内存经常不够用或发生内存泄露，爆出异常java.lang.OutOfMemoryError: PermGen\n\n其实在JDK7时就已经逐步把永久代的内容移动到其他区域了，比如移动到native区，移动到堆区等，而JDK8则是则是废除了永久代，改用元数据。\n\n## 三、深入理解元空间（Metaspace）\n\n### 3.1元空间的内存大小\n\n元空间是方法区的在HotSpot jvm 中的实现，方法区主要用于存储类的信息、常量池、方法数据、方法代码等。方法区逻辑上属于堆的一部分，但是为了与堆进行区分，通常又叫“非堆”。\n\n元空间的本质和永久代类似，都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于：元空间并不在虚拟机中，而是使用本地内存。，理论上取决于32位/64位系统可虚拟的内存大小。可见也不是无限制的，需要配置参数。\n\n### 3.2常用配置参数\n\n1.MetaspaceSize\n\n初始化的Metaspace大小，控制元空间发生GC的阈值。GC后，动态增加或降低MetaspaceSize。在默认情况下，这个值大小根据不同的平台在12M到20M浮动。使用[Java](http://lib.csdn.net/base/javase \"Java SE知识库\")-XX:+PrintFlagsInitial命令查看本机的初始化参数\n\n2.MaxMetaspaceSize\n\n限制Metaspace增长的上限，防止因为某些情况导致Metaspace无限的使用本地内存，影响到其他程序。在本机上该参数的默认值为4294967295B（大约4096MB）。\n\n3.MinMetaspaceFreeRatio\n\n当进行过Metaspace GC之后，会计算当前Metaspace的空闲空间比，如果空闲比小于这个参数（即实际非空闲占比过大，内存不够用），那么虚拟机将增长Metaspace的大小。默认值为40，也就是40%。设置该参数可以控制Metaspace的增长的速度，太小的值会导致Metaspace增长的缓慢，Metaspace的使用逐渐趋于饱和，可能会影响之后类的加载。而太大的值会导致Metaspace增长的过快，浪费内存。\n\n4.MaxMetasaceFreeRatio\n\n当进行过Metaspace GC之后， 会计算当前Metaspace的空闲空间比，如果空闲比大于这个参数，那么虚拟机会释放Metaspace的部分空间。默认值为70，也就是70%。\n\n5.MaxMetaspaceExpansion\n\nMetaspace增长时的最大幅度。在本机上该参数的默认值为5452592B（大约为5MB）。\n\n6.MinMetaspaceExpansion\n\nMetaspace增长时的最小幅度。在本机上该参数的默认值为340784B（大约330KB为）。\n\n### 3.3测试并追踪元空间大小\n\n#### 3.3.1.测试字符串常量\n\n````\npublic class StringOomMock {\n    static String  base = \"string\";\n    \n    public static void main(String[] args) {\n        List<String> list = new ArrayList<String>();\n        for (int i=0;i< Integer.MAX_VALUE;i++){\n            String str = base + base;\n            base = str;\n            list.add(str.intern());\n        }\n    }\n}\n````\n\n在eclipse中选中类--》run configuration-->java application--》new 参数如下：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404215213.png)\n\n由于设定了最大内存20M，很快就溢出，如下图：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404215254.png)\n\n可见在jdk8中：\n\n1.字符串常量由永久代转移到堆中。\n\n2.持久代已不存在，PermSize MaxPermSize参数已移除。（看图中最后两行）\n\n#### 3.3.2.测试元空间溢出\n\n根据定义，我们以加载类来测试元空间溢出，代码如下：\n````\npackage jdk8;\n\nimport java.io.File;\nimport java.lang.management.ClassLoadingMXBean;\nimport java.lang.management.ManagementFactory;\nimport java.net.URL;\nimport java.net.URLClassLoader;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * \n * @ClassName:OOMTest\n * @Description:模拟类加载溢出（元空间oom）\n * @author diandian.zhang\n * @date 2017年4月27日上午9:45:40\n */\npublic class OOMTest {  \n    public static void main(String[] args) {  \n        try {  \n            //准备url  \n            URL url = new File(\"D:/58workplace/11study/src/main/java/jdk8\").toURI().toURL();  \n            URL[] urls = {url};  \n            //获取有关类型加载的JMX接口  \n            ClassLoadingMXBean loadingBean = ManagementFactory.getClassLoadingMXBean();  \n            //用于缓存类加载器  \n            List<ClassLoader> classLoaders = new ArrayList<ClassLoader>();  \n            while (true) {  \n                //加载类型并缓存类加载器实例  \n                ClassLoader classLoader = new URLClassLoader(urls);  \n                classLoaders.add(classLoader);  \n                classLoader.loadClass(\"ClassA\");  \n                //显示数量信息（共加载过的类型数目，当前还有效的类型数目，已经被卸载的类型数目）  \n                System.out.println(\"total: \" + loadingBean.getTotalLoadedClassCount());  \n                System.out.println(\"active: \" + loadingBean.getLoadedClassCount());  \n                System.out.println(\"unloaded: \" + loadingBean.getUnloadedClassCount());  \n            }  \n        } catch (Exception e) {  \n            e.printStackTrace();  \n        }  \n    }  \n}  \n````\n\n为了快速溢出，设置参数：-XX:MetaspaceSize=8m -XX:MaxMetaspaceSize=80m，运行结果如下：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404215337.png)\n\n上图证实了，我们的JDK8中类加载（方法区的功能）已经不在永久代PerGem中了，而是Metaspace中。可以配合JVisualVM来看，更直观一些。\n\n## 四、总结\n\n本文讲解了元空间（Metaspace）的由来和本质，常用配置，以及监控测试。元空间的大小是动态变更的，但不是无限大的，最好也时常关注一下大小，以免影响服务器内存。\n\n\n## 参考文章\n\nhttps://segmentfault.com/a/1190000009707894\n\nhttps://www.cnblogs.com/hysum/p/7100874.html\n\nhttp://c.biancheng.net/view/939.html\n\nhttps://www.runoob.com\n\nhttps://blog.csdn.net/android_hl/article/details/53228348\n\n## 微信公众号\n\n### Java技术江湖\n\n如果大家想要实时关注我更新的文章以及分享的干货的话，可以关注我的公众号【Java技术江湖】一位阿里 Java 工程师的技术小站，作者黄小斜，专注 Java 相关技术：SSM、SpringBoot、MySQL、分布式、中间件、集群、Linux、网络、多线程，偶尔讲点Docker、ELK，同时也分享技术干货和学习经验，致力于Java全栈开发！\n\n**Java工程师必备学习资源:** 一些Java工程师常用学习资源，关注公众号后，后台回复关键字 **“Java”** 即可免费无套路获取。\n\n![我的公众号](https://img-blog.csdnimg.cn/20190805090108984.jpg)\n\n### 个人公众号：黄小斜\n\n作者是 985 硕士，蚂蚁金服 JAVA 工程师，专注于 JAVA 后端技术栈：SpringBoot、MySQL、分布式、中间件、微服务，同时也懂点投资理财，偶尔讲点算法和计算机理论基础，坚持学习和写作，相信终身学习的力量！\n\n**程序员3T技术学习资源：** 一些程序员学习技术的资源大礼包，关注公众号后，后台回复关键字 **“资料”** 即可免费无套路获取。 \n\n![](https://img-blog.csdnimg.cn/20190829222750556.jpg)\n"
  },
  {
    "path": "docs/Java/JVM/深入理解JVM虚拟机：JVM垃圾回收基本原理和算法.md",
    "content": "# 目录\n  * [JVM GC基本原理与GC算法](#jvm-gc基本原理与gc算法)\n  * [Java关键术语](#java关键术语)\n  * [Java HotSpot 虚拟机](#java-hotspot-虚拟机)\n  * [Java堆内存](#java堆内存)\n  * [启动Java垃圾回收](#启动java垃圾回收)\n  * [各种GC的触发时机(When)](#各种gc的触发时机when)\n    * [GC类型](#gc类型)\n    * [触发时机](#触发时机)\n    * [FULL GC触发条件详解](#full-gc触发条件详解)\n    * [总结](#总结)\n    * [什么是Stop the world](#什么是stop-the-world)\n  * [Java垃圾回收过程](#java垃圾回收过程)\n  * [垃圾回收中实例的终结](#垃圾回收中实例的终结)\n  * [对象什么时候符合垃圾回收的条件？](#对象什么时候符合垃圾回收的条件？)\n    * [GC Scope 示例程序](#gc-scope-示例程序)\n  * [JVM GC算法](#JVM GC算法)\n  * [JVM垃圾判定算法](#jvm垃圾判定算法)\n    * [引用计数算法(Reference Counting)](#引用计数算法reference-counting)\n    * [可达性分析算法（根搜索算法）](#可达性分析算法（根搜索算法）)\n  * [四种引用](#四种引用)\n  * [JVM垃圾回收算法](#jvm垃圾回收算法)\n  * [标记—清除算法（Mark-Sweep）](#标记清除算法（mark-sweep）)\n  * [复制算法（Copying）](#复制算法（copying）)\n  * [标记—整理算法（Mark-Compact）](#标记整理算法（mark-compact）)\n  * [分代收集算法(Generational Collection)](#分代收集算法generational-collection)\n  * [参考文章](#参考文章)\n\n\n\n本文转自互联网，侵删\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章将同步到我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《深入理解JVM虚拟机》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，一步步地学习JVM基础知识，并上手进行JVM调优实战，JVM是每一个Java工程师必须要学习和理解的知识点，你必须要掌握其实现原理，才能更完整地了解整个Java技术体系，形成自己的知识框架。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n## JVM GC基本原理与GC算法\n\nJava的内存分配与回收全部由JVM垃圾回收进程自动完成。与C语言不同，Java开发者不需要自己编写代码实现垃圾回收。这是Java深受大家欢迎的众多特性之一，能够帮助程序员更好地编写Java程序。\n\n下面四篇教程是了解Java 垃圾回收（GC）的基础：\n\n1.  [垃圾回收简介](http://www.importnew.com/13504.html)\n2.  [圾回收是如何工作的？](http://www.importnew.com/13493.html)\n3.  [垃圾回收的类别](http://www.importnew.com/13827.html)\n\n这篇教程是系列第一部分。首先会解释基本的术语，比如JDK、JVM、JRE和HotSpotVM。接着会介绍JVM结构和Java 堆内存结构。理解这些基础对于理解后面的垃圾回收知识很重要。\n\n## Java关键术语\n\n*   JavaAPI：一系列帮助开发者创建Java应用程序的封装好的库。\n*   Java 开发工具包 （JDK）：一系列工具帮助开发者创建Java应用程序。JDK包含工具编译、运行、打包、分发和监视Java应用程序。\n*   Java 虚拟机（JVM）：JVM是一个抽象的计算机结构。Java程序根据JVM的特性编写。JVM针对特定于操作系统并且可以将Java指令翻译成底层系统的指令并执行。JVM确保了Java的平台无关性。\n*   Java 运行环境（JRE）：JRE包含JVM实现和Java API。\n\n## Java HotSpot 虚拟机\n\n每种JVM实现可能采用不同的方法实现垃圾回收机制。在收购SUN之前，Oracle使用的是JRockit JVM，收购之后使用HotSpot JVM。目前Oracle拥有两种JVM实现并且一段时间后两个JVM实现会合二为一。\n\nHotSpot JVM是目前Oracle SE平台标准核心组件的一部分。在这篇垃圾回收教程中，我们将会了解基于HotSpot虚拟机的垃圾回收原则。\n\n## Java堆内存\n\n我们有必要了解堆内存在JVM内存模型的角色。在运行时，Java的实例被存放在堆内存区域。当一个对象不再被引用时，满足条件就会从堆内存移除。在垃圾回收进程中，这些对象将会从堆内存移除并且内存空间被回收。堆内存以下三个主要区域：\n\n1.  新生代（Young Generation）\n    *   Eden空间（Eden space，任何实例都通过Eden空间进入运行时内存区域）\n    *   S0 Survivor空间（S0 Survivor space，存在时间长的实例将会从Eden空间移动到S0 Survivor空间）\n    *   S1 Survivor空间 （存在时间更长的实例将会从S0 Survivor空间移动到S1 Survivor空间）\n2.  老年代（Old Generation）实例将从S1提升到Tenured（终身代）\n3.  永久代（Permanent Generation）包含类、方法等细节的元信息\n\n\n永久代空间[在Java SE8特性](http://javapapers.com/java/java-8-features/)中已经被移除。\n\nJava 垃圾回收是一项自动化的过程，用来管理程序所使用的运行时内存。通过这一自动化过程，JVM 解除了程序员在程序中分配和释放内存资源的开销。\n\n## 启动Java垃圾回收\n\n作为一个自动的过程，程序员不需要在代码中显示地启动垃圾回收过程。`System.gc()`和`Runtime.gc()`用来请求JVM启动垃圾回收。\n\n虽然这个请求机制提供给程序员一个启动 GC 过程的机会，但是启动由 JVM负责。JVM可以拒绝这个请求，所以并不保证这些调用都将执行垃圾回收。启动时机的选择由JVM决定，并且取决于堆内存中Eden区是否可用。JVM将这个选择留给了Java规范的实现，不同实现具体使用的算法不尽相同。\n\n毋庸置疑，我们知道垃圾回收过程是不能被强制执行的。我刚刚发现了一个调用`System.gc()`有意义的场景。通过这篇文章了解一下[适合调用System.gc()](http://javapapers.com/core-java/system-gc-invocation-a-suitable-scenario/)这种极端情况。\n\n## 各种GC的触发时机(When)\n\n### GC类型\n\n说到GC类型，就更有意思了，为什么呢，因为业界没有统一的严格意义上的界限，也没有严格意义上的GC类型，都是左边一个教授一套名字，右边一个作者一套名字。为什么会有这个情况呢，因为GC类型是和收集器有关的，不同的收集器会有自己独特的一些收集类型。所以作者在这里引用**R大**关于GC类型的介绍，作者觉得还是比较妥当准确的。如下:\n\n*   Partial GC：并不收集整个GC堆的模式\n    *   Young GC(Minor GC)：只收集young gen的GC\n    *   Old GC：只收集old gen的GC。只有CMS的concurrent collection是这个模式\n    *   Mixed GC：收集整个young gen以及部分old gen的GC。只有G1有这个模式\n*   Full GC(Major GC)：收集整个堆，包括young gen、old gen、perm gen（如果存在的话）等所有部分的模式。\n\n### 触发时机\n\n上面大家也看到了，GC类型分分类是和收集器有关的，那么当然了，对于不同的收集器，GC触发时机也是不一样的，作者就针对默认的serial GC来说:\n\n*   young GC：当young gen中的eden区分配满的时候触发。注意young GC中有部分存活对象会晋升到old gen，所以young GC后old gen的占用量通常会有所升高。\n*   full GC：当准备要触发一次young GC时，如果发现统计数据说之前young GC的平均晋升大小比目前old gen剩余的空间大，则不会触发young GC而是转为触发full GC（因为HotSpot VM的GC里，除了CMS的concurrent collection之外，其它能收集old gen的GC都会同时收集整个GC堆，包括young gen，所以不需要事先触发一次单独的young GC）；或者，如果有perm gen的话，要在perm gen分配空间但已经没有足够空间时，也要触发一次full GC；或者System.gc()、heap dump带GC，默认也是触发full GC。\n\n### FULL GC触发条件详解\n\n除直接调用System.gc外，触发Full GC执行的情况有如下四种。\n\n1.旧生代空间不足\n\n旧生代空间只有在新生代对象转入及创建为大对象、大数组时才会出现不足的现象，当执行Full GC后空间仍然不足，则抛出如下错误：\n\njava.lang.OutOfMemoryError:Javaheapspace\n\n为避免以上两种状况引起的Full GC，调优时应尽量做到让对象在Minor GC阶段被回收、让对象在新生代多存活一段时间及不要创建过大的对象及数组。\n\n2\\. Permanet Generation空间满\n\nPermanet Generation中存放的为一些class的信息等，当系统中要加载的类、反射的类和调用的方法较多时，Permanet Generation可能会被占满，在未配置为采用CMS GC的情况下会执行Full GC。如果经过Full GC仍然回收不了，那么JVM会抛出如下错误信息：\n\njava.lang.OutOfMemoryError:PermGenspace\n\n为避免Perm Gen占满造成Full GC现象，可采用的方法为增大Perm Gen空间或转为使用CMS GC。\n\n3\\. CMS GC时出现promotion failed和concurrent mode failure\n\n对于采用CMS进行旧生代GC的程序而言，尤其要注意GC日志中是否有promotion failed和concurrent mode failure两种状况，当这两种状况出现时可能会触发Full GC。\n\npromotion failed是在进行Minor GC时，survivor space放不下、对象只能放入旧生代，而此时旧生代也放不下造成的；concurrent mode failure是在执行CMS GC的过程中同时有对象要放入旧生代，而此时旧生代空间不足造成的。\n\n应对措施为：增大survivor space、旧生代空间或调低触发并发GC的比率，但在JDK 5.0+、6.0+的版本中有可能会由于JDK的bug29导致CMS在remark完毕后很久才触发sweeping动作。对于这种状况，可通过设置-XX: CMSMaxAbortablePrecleanTime=5（单位为ms）来避免。\n\n4.统计得到的Minor GC晋升到旧生代的平均大小大于旧生代的剩余空间\n\n这是一个较为复杂的触发情况，Hotspot为了避免由于新生代对象晋升到旧生代导致旧生代空间不足的现象，在进行Minor GC时，做了一个判断，如果之前统计所得到的Minor GC晋升到旧生代的平均大小大于旧生代的剩余空间，那么就直接触发Full GC。\n\n例如程序第一次触发Minor GC后，有6MB的对象晋升到旧生代，那么当下一次Minor GC发生时，首先检查旧生代的剩余空间是否大于6MB，如果小于6MB，则执行Full GC。\n\n当新生代采用PS GC时，方式稍有不同，PS GC是在Minor GC后也会检查，例如上面的例子中第一次Minor GC后，PS GC会检查此时旧生代的剩余空间是否大于6MB，如小于，则触发对旧生代的回收。\n\n除了以上4种状况外，对于使用RMI来进行RPC或管理的Sun JDK应用而言，默认情况下会一小时执行一次Full GC。可通过在启动时通过- java -Dsun.rmi.dgc.client.gcInterval=3600000来设置Full GC执行的间隔时间或通过-XX:+ DisableExplicitGC来禁止RMI调用System.gc。\n\n### 总结\n\n**Minor GC ，Full GC 触发条件**\n\nMinor GC触发条件：当Eden区满时，触发Minor GC。\n\nFull GC触发条件：\n\n（1）调用System.gc时，系统建议执行Full GC，但是不必然执行\n\n（2）老年代空间不足\n\n（3）方法去空间不足\n\n（4）通过Minor GC后进入老年代的平均大小大于老年代的可用内存\n\n（5）由Eden区、From Space区向To Space区复制时，对象大小大于To Space可用内存，则把该对象转存到老年代，且老年代的可用内存小于该对象大小\n\n### 什么是Stop the world\n\nJava中Stop-The-World机制简称STW，是在执行垃圾收集算法时，Java应用程序的其他所有线程都被挂起（除了垃圾收集帮助器之外）。Java中一种全局暂停现象，全局停顿，所有Java代码停止，native代码可以执行，但不能与JVM交互；这些现象多半是由于gc引起。\n\nGC时的Stop the World(STW)是大家最大的敌人。但可能很多人还不清楚，除了GC，JVM下还会发生停顿现象。\n\nJVM里有一条特殊的线程－－VM Threads，专门用来执行一些特殊的VM Operation，比如分派GC，thread dump等，这些任务，都需要整个Heap，以及所有线程的状态是静止的，一致的才能进行。所以JVM引入了安全点(Safe Point)的概念，想办法在需要进行VM Operation时，通知所有的线程进入一个静止的安全点。\n\n除了GC，其他触发安全点的VM Operation包括：\n\n1\\. JIT相关，比如Code deoptimization, Flushing code cache ；\n\n2\\. Class redefinition (e.g. javaagent，AOP代码植入的产生的instrumentation) ；\n\n3\\. Biased lock revocation 取消偏向锁 ；\n\n4\\. Various debug operation (e.g. thread dump or deadlock check)；\n\n## Java垃圾回收过程\n\n垃圾回收是一种回收无用内存空间并使其对未来实例可用的过程。\n\nEden 区：当一个实例被创建了，首先会被存储在堆内存年轻代的 Eden 区中。\n\n注意：如果你不能理解这些词汇，我建议你阅读这篇[垃圾回收介绍](http://javapapers.com/java/java-garbage-collection-introduction/)，这篇教程详细地介绍了内存模型、JVM 架构以及这些术语。\n\nSurvivor 区（S0 和 S1）：作为年轻代 GC（Minor GC）周期的一部分，存活的对象（仍然被引用的）从 Eden 区被移动到 Survivor 区的 S0 中。类似的，垃圾回收器会扫描 S0 然后将存活的实例移动到 S1 中。\n\n（译注：此处不应该是Eden和S0中存活的都移到S1么，为什么会先移到S0再从S0移到S1？）\n\n死亡的实例（不再被引用）被标记为垃圾回收。根据垃圾回收器（有四种常用的垃圾回收器，将在下一教程中介绍它们）选择的不同，要么被标记的实例都会不停地从内存中移除，要么回收过程会在一个单独的进程中完成。\n\n老年代：老年代（Old or tenured generation）是堆内存中的第二块逻辑区。当垃圾回收器执行 Minor GC 周期时，在 S1 Survivor 区中的存活实例将会被晋升到老年代，而未被引用的对象被标记为回收。\n\n老年代 GC（Major GC）：相对于 Java 垃圾回收过程，老年代是实例生命周期的最后阶段。Major GC 扫描老年代的垃圾回收过程。如果实例不再被引用，那么它们会被标记为回收，否则它们会继续留在老年代中。\n\n内存碎片：一旦实例从堆内存中被删除，其位置就会变空并且可用于未来实例的分配。这些空出的空间将会使整个内存区域碎片化。为了实例的快速分配，需要进行碎片整理。基于垃圾回收器的不同选择，回收的内存区域要么被不停地被整理，要么在一个单独的GC进程中完成。\n\n## 垃圾回收中实例的终结\n\n在释放一个实例和回收内存空间之前，Java 垃圾回收器会调用实例各自的`finalize()`方法，从而该实例有机会释放所持有的资源。虽然可以保证`finalize()`会在回收内存空间之前被调用，但是没有指定的顺序和时间。多个实例间的顺序是无法被预知，甚至可能会并行发生。程序不应该预先调整实例之间的顺序并使用`finalize()`方法回收资源。\n\n*   任何在 finalize过程中未被捕获的异常会自动被忽略，然后该实例的 finalize 过程被取消。\n*   JVM 规范中并没有讨论关于弱引用的垃圾回收机制，也没有很明确的要求。具体的实现都由实现方决定。\n*   垃圾回收是由一个守护线程完成的。\n\n## 对象什么时候符合垃圾回收的条件？\n\n*   所有实例都没有活动线程访问。\n*   没有被其他任何实例访问的循环引用实例。\n\n[Java 中有不同的引用类型](http://javapapers.com/core-java/java-weak-reference/)。判断实例是否符合垃圾收集的条件都依赖于它的引用类型。\n\n| 引用类型 | 垃圾收集 |\n| --- | --- |\n| 强引用（Strong Reference） | 不符合垃圾收集 |\n| 软引用（Soft Reference） | 垃圾收集可能会执行，但会作为最后的选择 |\n| 弱引用（Weak Reference） | 符合垃圾收集 |\n| 虚引用（Phantom Reference） | 符合垃圾收集 |\n\n在编译过程中作为一种优化技术，Java 编译器能选择给实例赋`null`值，从而标记实例为可回收。\n````\n    class Animal {\n    \n        public static void main(String[] args) {\n    \n            Animal lion = new Animal();\n    \n            System.out.println(\"Main is completed.\");\n    \n        }\n    \n     \n    \n        protected void finalize() {\n    \n            System.out.println(\"Rest in Peace!\");\n    \n        }\n    \n    }\n````\n在上面的类中，`lion`对象在实例化行后从未被使用过。因此 Java 编译器作为一种优化措施可以直接在实例化行后赋值`lion = null`。因此，即使在 SOP 输出之前， finalize 函数也能够打印出`'Rest in Peace!'`。我们不能证明这确定会发生，因为它依赖JVM的实现方式和运行时使用的内存。然而，我们还能学习到一点：如果编译器看到该实例在未来再也不会被引用，能够选择并提早释放实例空间。\n\n*   关于对象什么时候符合垃圾回收有一个更好的例子。实例的所有属性能被存储在寄存器中，随后寄存器将被访问并读取内容。无一例外，这些值将被写回到实例中。虽然这些值在将来能被使用，这个实例仍然能被标记为符合垃圾回收。这是一个很经典的例子，不是吗？\n*   当被赋值为null时，这是很简单的一个符合垃圾回收的示例。当然，复杂的情况可以像上面的几点。这是由 JVM 实现者所做的选择。目的是留下尽可能小的内存占用，加快响应速度，提高吞吐量。为了实现这一目标， JVM 的实现者可以选择一个更好的方案或算法在垃圾回收过程中回收内存空间。\n*   当`finalize()`方法被调用时，JVM 会释放该线程上的所有同步锁。\n\n### GC Scope 示例程序\n````\nClass GCScope {\n\n    GCScope t;\n\n    static int i = 1;\n\n \n\n    public static void main(String args[]) {\n\n        GCScope t1 = new GCScope();\n\n        GCScope t2 = new GCScope();\n\n        GCScope t3 = new GCScope();\n\n \n\n        // No Object Is Eligible for GC\n\n \n\n        t1.t = t2; // No Object Is Eligible for GC\n\n        t2.t = t3; // No Object Is Eligible for GC\n\n        t3.t = t1; // No Object Is Eligible for GC\n\n \n\n        t1 = null;\n\n        // No Object Is Eligible for GC (t3.t still has a reference to t1)\n\n \n\n        t2 = null;\n\n        // No Object Is Eligible for GC (t3.t.t still has a reference to t2)\n\n \n\n        t3 = null;\n\n        // All the 3 Object Is Eligible for GC (None of them have a reference.\n\n        // only the variable t of the objects are referring each other in a\n\n        // rounded fashion forming the Island of objects with out any external\n\n        // reference)\n\n    }\n\n \n\n    protected void finalize() {\n\n        System.out.println(\"Garbage collected from object\" + i);\n\n        i++;\n\n    }\n\n \n\nclass GCScope {\n\n    GCScope t;\n\n    static int i = 1;\n\n \n\n    public static void main(String args[]) {\n\n        GCScope t1 = new GCScope();\n\n        GCScope t2 = new GCScope();\n\n        GCScope t3 = new GCScope();\n\n \n\n        // 没有对象符合GC\n\n        t1.t = t2; // 没有对象符合GC\n\n        t2.t = t3; // 没有对象符合GC\n\n        t3.t = t1; // 没有对象符合GC\n\n \n\n        t1 = null;\n\n        // 没有对象符合GC (t3.t 仍然有一个到 t1 的引用)\n\n \n\n        t2 = null;\n\n        // 没有对象符合GC (t3.t.t 仍然有一个到 t2 的引用)\n\n \n\n        t3 = null;\n\n        // 所有三个对象都符合GC (它们中没有一个拥有引用。\n\n        // 只有各对象的变量 t 还指向了彼此，\n\n        // 形成了一个由对象组成的环形的岛，而没有任何外部的引用。)\n\n    }\n\n \n\n    protected void finalize() {\n\n        System.out.println(\"Garbage collected from object\" + i);\n\n        i++;\n\n    }\n}\n````\n## JVM GC算法\n\n在判断哪些内存需要回收和什么时候回收用到GC 算法，本文主要对GC 算法进行讲解。\n\n## JVM垃圾判定算法\n\n常见的JVM垃圾判定算法包括：引用计数算法、可达性分析算法。\n\n### 引用计数算法(Reference Counting)\n\n引用计数算法是通过判断对象的引用数量来决定对象是否可以被回收。\n\n给对象中添加一个引用计数器，每当有一个地方引用它时，计数器值就加1；当引用失效时，计数器值就减1；任何时刻计数器为0的对象就是不可能再被使用的。\n\n优点：简单，高效，现在的objective-c用的就是这种算法。\n\n缺点：很难处理循环引用，相互引用的两个对象则无法释放。因此目前主流的Java虚拟机都摒弃掉了这种算法。\n\n举个简单的例子，对象objA和objB都有字段instance，赋值令objA.instance=objB及objB.instance=objA，除此之外，这两个对象没有任何引用，实际上这两个对象已经不可能再被访问，但是因为互相引用，导致它们的引用计数都不为0，因此引用计数算法无法通知GC收集器回收它们。\n\n```\n\npublic class ReferenceCountingGC {\n    public Object instance = null;\n\n    public static void main(String[] args) {\n        ReferenceCountingGC objA = new ReferenceCountingGC();\n        ReferenceCountingGC objB = new ReferenceCountingGC();\n        objA.instance = objB;\n        objB.instance = objA;\n\n        objA = null;\n        objB = null;\n\n        System.gc();//GC\n    }\n}\n```\n\n运行结果\n\n```\n[GC (System.gc()) [PSYoungGen: 3329K->744K(38400K)] 3329K->752K(125952K), 0.0341414 secs] [Times: user=0.00 sys=0.00, real=0.06 secs] \n[Full GC (System.gc()) [PSYoungGen: 744K->0K(38400K)] [ParOldGen: 8K->628K(87552K)] 752K->628K(125952K), [Metaspace: 3450K->3450K(1056768K)], 0.0060728 secs] [Times: user=0.05 sys=0.00, real=0.01 secs] \nHeap\n PSYoungGen      total 38400K, used 998K [0x00000000d5c00000, 0x00000000d8680000, 0x0000000100000000)\n  eden space 33280K, 3% used [0x00000000d5c00000,0x00000000d5cf9b20,0x00000000d7c80000)\n  from space 5120K, 0% used [0x00000000d7c80000,0x00000000d7c80000,0x00000000d8180000)\n  to   space 5120K, 0% used [0x00000000d8180000,0x00000000d8180000,0x00000000d8680000)\n ParOldGen       total 87552K, used 628K [0x0000000081400000, 0x0000000086980000, 0x00000000d5c00000)\n  object space 87552K, 0% used [0x0000000081400000,0x000000008149d2c8,0x0000000086980000)\n Metaspace       used 3469K, capacity 4496K, committed 4864K, reserved 1056768K\n  class space    used 381K, capacity 388K, committed 512K, reserved 1048576K\n\nProcess finished with exit code 0\n```\n\n从运行结果看，GC日志中包含“3329K->744K”,意味着虚拟机并没有因为这两个对象互相引用就不回收它们，说明虚拟机不是通过引用技术算法来判断对象是否存活的。\n\n### 可达性分析算法（根搜索算法）\n\n可达性分析算法是通过判断对象的引用链是否可达来决定对象是否可以被回收。\n\n从GC Roots（每种具体实现对GC Roots有不同的定义）作为起点，向下搜索它们引用的对象，可以生成一棵引用树，树的节点视为可达对象，反之视为不可达。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404215732.png)\n\n在Java语言中，可以作为GC Roots的对象包括下面几种：\n\n*   虚拟机栈（栈帧中的本地变量表）中的引用对象。\n*   方法区中的类静态属性引用的对象。\n*   方法区中的常量引用的对象。\n*   本地方法栈中JNI（Native方法）的引用对象\n\n真正标记以为对象为可回收状态至少要标记两次。\n\n## 四种引用\n\n强引用就是指在程序代码之中普遍存在的，类似\"Object obj = new Object()\"这类的引用，只要强引用还存在，垃圾收集器永远不会回收掉被引用的对象。\n\n```\n\nObject obj = new Object();\n```\n\n软引用是用来描述一些还有用但并非必需的对象，对于软引用关联着的对象，在系统将要发生内存溢出异常之前，将会把这些对象列进回收范围进行第二次回收。如果这次回收还没有足够的内存，才会抛出内存溢出异常。在JDK1.2之后，提供了SoftReference类来实现软引用。\n\n```\n\nObject obj = new Object();\nSoftReference<Object> sf = new SoftReference<Object>(obj);\n```\n\n弱引用也是用来描述非必需对象的，但是它的强度比软引用更弱一些，被弱引用关联的对象，只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时，无论当前内存是否足够，都会回收掉只被弱引用关联的对象。在JDK1.2之后，提供了WeakReference类来实现弱引用。\n\n```\n\nObject obj = new Object();\nWeakReference<Object> wf = new WeakReference<Object>(obj);\n```\n\n虚引用也成为幽灵引用或者幻影引用，它是最弱的一中引用关系。一个对象是否有虚引用的存在，完全不会对其生存时间构成影响，也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。在JDK1.2之后，提供给了PhantomReference类来实现虚引用。\n\n```\n\nObject obj = new Object();\nPhantomReference<Object> pf = new PhantomReference<Object>(obj);\n```\n\n## JVM垃圾回收算法\n\n常见的垃圾回收算法包括：标记-清除算法，复制算法，标记-整理算法，分代收集算法。\n\n在介绍JVM垃圾回收算法前，先介绍一个概念。\n\nStop-the-World\n\nStop-the-world意味着 JVM由于要执行GC而停止了应用程序的执行，并且这种情形会在任何一种GC算法中发生。当Stop-the-world发生时，除了GC所需的线程以外，所有线程都处于等待状态直到GC任务完成。事实上，GC优化很多时候就是指减少Stop-the-world发生的时间，从而使系统具有高吞吐 、低停顿的特点。\n\n## 标记—清除算法（Mark-Sweep）\n\n之所以说标记/清除算法是几种GC算法中最基础的算法，是因为后续的收集算法都是基于这种思路并对其不足进行改进而得到的。标记/清除算法的基本思想就跟它的名字一样，分为“标记”和“清除”两个阶段：首先标记出所有需要回收的对象，在标记完成后统一回收所有被标记的对象。\n\n标记阶段：标记的过程其实就是前面介绍的可达性分析算法的过程，遍历所有的GC Roots对象，对从GC Roots对象可达的对象都打上一个标识，一般是在对象的header中，将其记录为可达对象；\n\n清除阶段：清除的过程是对堆内存进行遍历，如果发现某个对象没有被标记为可达对象（通过读取对象header信息），则将其回收。\n\n不足：\n\n*   标记和清除过程效率都不高\n*   会产生大量碎片，内存碎片过多可能导致无法给大对象分配内存。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404215805.png)\n## 复制算法（Copying）\n\n将内存划分为大小相等的两块，每次只使用其中一块，当这一块内存用完了就将还存活的对象复制到另一块上面，然后再把使用过的内存空间进行一次清理。\n\n现在的商业虚拟机都采用这种收集算法来回收新生代，但是并不是将内存划分为大小相等的两块，而是分为一块较大的 Eden 空间和两块较小的 Survior 空间，每次使用 Eden 空间和其中一块 Survivor。在回收时，将 Eden 和 Survivor 中还存活着的对象一次性复制到另一块 Survivor 空间上，最后清理 Eden 和 使用过的那一块 Survivor。HotSpot 虚拟机的 Eden 和 Survivor 的大小比例默认为 8:1，保证了内存的利用率达到 90 %。如果每次回收有多于 10% 的对象存活，那么一块 Survivor 空间就不够用了，此时需要依赖于老年代进行分配担保，也就是借用老年代的空间。\n\n不足：\n\n*   将内存缩小为原来的一半，浪费了一半的内存空间，代价太高；如果不想浪费一半的空间，就需要有额外的空间进行分配担保，以应对被使用的内存中所有对象都100%存活的极端情况，所以在老年代一般不能直接选用这种算法。\n*   复制收集算法在对象存活率较高时就要进行较多的复制操作，效率将会变低。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404215817.png)\n\n## 标记—整理算法（Mark-Compact）\n\n标记—整理算法和标记—清除算法一样，但是标记—整理算法不是把存活对象复制到另一块内存，而是把存活对象往内存的一端移动，然后直接回收边界以外的内存，因此其不会产生内存碎片。标记—整理算法提高了内存的利用率，并且它适合在收集对象存活时间较长的老年代。\n\n不足：\n\n效率不高，不仅要标记存活对象，还要整理所有存活对象的引用地址，在效率上不如复制算法。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404215845.png)\n\n## 分代收集算法(Generational Collection)\n\n分代回收算法实际上是把复制算法和标记整理法的结合，并不是真正一个新的算法，一般分为：老年代（Old Generation）和新生代（Young Generation），老年代就是很少垃圾需要进行回收的，新生代就是有很多的内存空间需要回收，所以不同代就采用不同的回收算法，以此来达到高效的回收算法。\n\n新生代：由于新生代产生很多临时对象，大量对象需要进行回收，所以采用复制算法是最高效的。\n\n老年代：回收的对象很少，都是经过几次标记后都不是可回收的状态转移到老年代的，所以仅有少量对象需要回收，故采用标记清除或者标记整理算法。\n\n## 参考文章\n\n<https://segmentfault.com/a/1190000009707894>\n\n<https://www.cnblogs.com/hysum/p/7100874.html>\n\n<http://c.biancheng.net/view/939.html>\n\n<https://www.runoob.com/>\n\nhttps://blog.csdn.net/android_hl/article/details/53228348\n\n\n"
  },
  {
    "path": "docs/Java/JVM/深入理解JVM虚拟机：JVM常用参数以及调优实践.md",
    "content": "# 目录\n\n  * [JVM优化的必要性](#jvm优化的必要性)\n  * [JVM调优原则](#jvm调优原则)\n    * [JVM运行参数设置](#jvm运行参数设置)\n  * [JVM性能调优工具](#jvm性能调优工具)\n  * [常用调优策略](#常用调优策略)\n  * [六、JVM调优实例](#六、jvm调优实例)\n  * [七、一个具体的实战案例分析](#七、一个具体的实战案例分析)\n  * [参考资料](#参考资料)\n  * [参考文章](#参考文章)\n\n\n\n\n![](https://pic.rmb.bdstatic.com/bjh/down/97522789c423e19931adafa65bd5424d.png)\n\n## JVM优化的必要性\n\n**1.1： 项目上线后，什么原因使得我们需要进行jvm调优**\n\n1)、垃圾太多（java线程，对象占满内存），内存占满了，程序跑不动了！！\n2)、垃圾回收线程太多，频繁地回收垃圾（垃圾回收线程本身也会占用资源： 占用内存，cpu资源），导致程序性能下降\n3)、回收垃圾频繁导致STW\n\n因此基于以上的原因，程序上线后，必须进行调优，否则程序性能就无法提升；也就是程序上线后，必须设置合理的垃圾回收策略；\n\n**1.2： jvm调优的本质是什么？？**\n\n答案： 回收垃圾，及时回收没有用垃圾对象，及时释放掉内存空间\n\n**1.3： 基于服务器环境，jvm堆内存到底应用设置多少内存？**\n\n1、32位的操作系统 --- 寻址能力 2^32 = 4GB ,最大的能支持4gb; jvm可以分配 2g+\n\n2、64位的操作系统 --- 寻址能力 2^64 = 16384PB , 高性能计算机（IBM Z unix 128G 200+）\n\n![](https://pic.rmb.bdstatic.com/bjh/down/aae367a929e2d1361975942091ffadfe.png)\n\nJvm堆内存不能设置太大，否则会导致寻址垃圾的时间过长，也就是导致整个程序STW, 也不能设置太小，否则会导致回收垃圾过于频繁；\n\n**1.4：总结**\n\n如果你遇到以下情况，就需要考虑进行JVM调优了：\n\n*   Heap内存（老年代）持续上涨达到设置的最大内存值；\n\n*   Full GC 次数频繁；\n\n*   GC 停顿时间过长（超过1秒）；\n\n*   应用出现OutOfMemory 等内存异常；\n\n*   应用中有使用本地缓存且占用大量内存空间；\n\n*   系统吞吐量与响应性能不高或下降。\n\n## JVM调优原则\n\n![](https://pic.rmb.bdstatic.com/bjh/down/7cd4f5c19ab4f5f5d3087422097ee931.png)\n\nJVM调优是一个手段，但并不一定所有问题都可以通过JVM进行调优解决，因此，在进行JVM调优时，我们要遵循一些原则：\n\n*   大多数的Java应用不需要进行JVM优化；\n\n*   大多数导致GC问题的原因是代码层面的问题导致的（代码层面）；\n\n*   上线之前，应先考虑将机器的JVM参数设置到最优；\n\n*   减少创建对象的数量（代码层面）；\n\n*   减少使用全局变量和大对象（代码层面）；\n\n*   优先架构调优和代码调优，JVM优化是不得已的手段（代码、架构层面）；\n\n*   分析GC情况优化代码比优化JVM参数更好（代码层面）；\n\n通过以上原则，我们发现，其实最有效的优化手段是架构和代码层面的优化，而JVM优化则是最后不得已的手段，也可以说是对服务器配置的最后一次“压榨”。\n\n**2.1 JVM调优目标**\n\n调优的最终目的都是为了令应用程序使用最小的硬件消耗来承载更大的吞吐。jvm调优主要是针对垃圾收集器的收集性能优化，令运行在虚拟机上的应用能够使用更少的内存以及延迟获取更大的吞吐量。\n\n*   延迟：GC低停顿和GC低频率；\n\n*   低内存占用；\n\n*   高吞吐量;\n\n其中，任何一个属性性能的提高，几乎都是以牺牲其他属性性能的损为代价的，不可兼得。具体根据在业务中的重要性确定。\n\n**2.2 JVM调优量化目标**\n\n下面展示了一些JVM调优的量化目标参考实例：\n\n*   Heap 内存使用率 <= 70%;\n\n*   Old generation内存使用率<= 70%;\n\n*   avgpause <= 1秒;\n\n*   Full gc 次数0 或 avg pause interval >= 24小时 ;\n\n注意：不同应用的JVM调优量化目标是不一样的。\n\n**2.3 JVM调优的步骤**\n\n一般情况下，JVM调优可通过以下步骤进行：\n\n*   分析GC日志及dump文件，判断是否需要优化，确定瓶颈问题点；\n\n*   确定JVM调优量化目标；\n\n*   确定JVM调优参数（根据历史JVM参数来调整）；\n\n*   依次调优内存、延迟、吞吐量等指标；\n\n*   对比观察调优前后的差异；\n\n*   不断的分析和调整，直到找到合适的JVM参数配置；\n\n*   找到最合适的参数，将这些参数应用到所有服务器，并进行后续跟踪。\n\n以上操作步骤中，某些步骤是需要多次不断迭代完成的。一般是从满足程序的内存使用需求开始的，之后是时间延迟的要求，最后才是吞吐量的要求，要基于这个步骤来不断优化，每一个步骤都是进行下一步的基础，不可逆行之。\n\n![](https://pic.rmb.bdstatic.com/bjh/down/c271890a3714d538ed3362073c66893d.png)\n\n**调优原则总结**\n\nJVM的自动内存管理本来就是为了将开发人员从内存管理的泥潭里拉出来。JVM调优不是常规手段，性能问题一般第一选择是优化程序，最后的选择才是进行JVM调优。\n\n即使不得不进行JVM调优，也绝对不能拍脑门就去调整参数，一定要全面监控，详细分析性能数据。\n\n**附录：系统性能优化指导**\n\n![](https://pic.rmb.bdstatic.com/bjh/down/00d90cef8369568f8581a9850dcd42e6.png)\n\n### JVM运行参数设置\n\n**3.1、堆参数设置**\n\n**-XX:+PrintGC**使用这个参数，虚拟机启动后，只要遇到GC就会打印日志\n\n**-XX:+UseSerialGC**配置串行回收器\n\n**-XX:+PrintGCDetails**可以查看详细信息，包括各个区的情况\n\n**-Xms**设置Java程序启动时初始化堆大小\n\n**-Xmx**设置Java程序能获得最大的堆大小\n\n**-Xmx20m -Xms5m -XX:+PrintCommandLineFlags**可以将隐式或者显示传给虚拟机的参数输出\n\n**3.2、新生代参数配置**\n\n**-Xmn**可以设置新生代的大小，设置一个比较大的新生代会减少老年代的大小，这个参数对系统性能以及GC行为有很大的影响，新生代大小一般会设置整个堆空间的1/3到1/4左右\n\n**-XX:SurvivorRatio**用来设置新生代中eden空间和from/to空间的比例。含义：-XX:SurvivorRatio=eden/from**/**eden/to\n\n不同的堆分布情况，对系统执行会产生一定的影响，在实际工作中，应该根据系统的特点做出合理的配置，基本策略：尽可能将对象预留在新生代，减少老年代的GC次数\n\n除了可以设置新生代的绝对大小（-Xmn），还可以使用（-XX:NewRatio）设置新生代和老年代的比例：-XX：NewRatio=老年代/新生代\n\n**配置运行时参数：**\n\n-Xms20m -Xmx20m -Xmn1m -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC\n\n**3.3、堆溢出参数配置**\n\n在Java程序在运行过程中，如果对空间不足，则会抛出内存溢出的错误（Out Of Memory）OOM，一旦这类问题发生在生产环境，则可能引起严重的业务中断，Java虚拟机提供了**-XX:+\nHeapDumpOnOutOfMemoryError**，使用该参数可以在内存溢出时导出整个堆信息，与之配合使用的还有参数**-XX:HeapDumpPath**，可以设置导出堆的存放路径\n\n内存分析工具：Memory Analyzer\n\n**配置运行时参数**-Xms1m -Xmx1m -XX:+\nHeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d:/Demo3.dump\n\n**3.4、栈参数配置**\n\nJava虚拟机提供了参数**-Xss**来指定线程的最大栈空间，整个参数也直接决定了函数可调用的最大深度。\n\n**配置运行时参数：**-Xss1m\n\n3.5、方法区参数配置\n\n和Java堆一样，方法区是一块所有线程共享的内存区域，它用于保存系统的类信息，方法区（永久区）可以保存多少信息可以对其进行配置，在默认情况下，**-XX:MaxPermSize**为64M，如果系统运行时生产大量的类，就需要设置一个相对合适的方法区，以免出现永久区内存溢出的问题\n\n-XX:PermSize=64M -XX:MaxPermSize=64M\n\n**3.6、直接内存参数配置**\n\n直接内存也是Java程序中非常重要的组成部分，特别是广泛用在NIO中，直接内存跳过了Java堆，使用Java程序可以直接访问原生堆空间，因此在一定程度上加快了内存空间的访问速度\n\n但是说直接内存一定就可以提高内存访问速度也不见得，具体情况具体分析\n\n**相关配置参数：-XX:MaxDirectMemorySize**，如果不设置，默认值为最大堆空间，即-Xmx。直接内存使用达到上限时，就会触发垃圾回收，如果不能有效的释放空间，就会引起系统的OOM\n\n**3.7、对象进入老年代的参数配置**\n\n一般而言，对象首次创建会被放置在新生代的eden区，如果没有GC介入，则对象不会离开eden区，那么eden区的对象如何进入老年代呢？\n\n通常情况下，只要对象的年龄达到一定的大小，就会自动离开年轻代进入老年代，对象年龄是由对象经历数次GC决定的，在新生代每次GC之后如果对象没有被回收，则年龄加1\n\n虚拟机提供了一个参数来控制新生代对象的最大年龄，当超过这个年龄范围就会晋升老年代\n\n**-XX:MaxTenuringThreshold**，默认情况下为15\n\n**配置运行时参数：**-Xmx64M -Xms64M -XX:+PrintGCDetails\n\n**结论**：对象首次创建会被放置在新生代的eden区，因此输出结果中from和to区都为0%\n\n根据设置MaxTenuringThreshold参数，可以指定新生代对象经过多少次回收后进入老年代。另外，大对象新生代eden区无法装入时，也会直接进入老年代。\n\nJVM里有个参数可以设置对象的大小超过在指定的大小之后，直接晋升老年代 **-XX:PretenureSizeThreshold=15**\n\n参数：-Xmx1024M -Xms1024M -XX:+UseSerialGC -XX:MaxTenuringThreshold=15 -XX:+PrintGCDetails\n\n使用PretenureSizeThreshold可以进行指定进入老年代的对象大小，但是要注意TLAB区域优先分配空间。虚拟机对于体积不大的对象 会优先把数据分配到TLAB区域中，因此就失去了在老年代分配的机会\n\n参数：-Xmx30M -Xms30M -XX:+UseSerialGC -XX:+PrintGCDetails\n-XX:PretenureSizeThreshold=1000 -XX:-UseTLAB\n\n**3.8、TLAB参数配置**\n\nTLAB全称是Thread Local Allocation Buffer，即线程本地分配缓存，从名字上看是一个线程专用的内存分配区域，是为了加速对象分配对象而生的。每一个线程都会产生一个TLAB，该线程独享的工作区域，Java虚拟机使用这种TLAB区来避免多线程冲突问题，提高了对象分配的效率\n\nTLAB空间一般不会太大，当大对象无法在TLAB分配时，则会直接分配到堆上\n\n**-XX:+UseTLAB**使用TLAB\n\n**-XX:+TLABSize**设置TLAB大小\n\n**-XX:TLABRefillWasteFraction**设置维护进入TLAB空间的单个对象大小，它是一个比例值，默认为64，即如果对象大于整个空间的1/64，则在堆创建对象\n\n**-XX:+PrintTLAB**查看TLAB信息\n\n**-XX:ResizeTLAB**自调整TLABRefillWasteFraction阈值\n\n参数：-XX:+UseTLAB -XX:+PrintTLAB -XX:+PrintGC -XX:TLABSize=102400 -XX:-ResizeTLAB\n-XX:TLABRefillWasteFraction=100 -XX:-DoEscapeAnalysis -server\n\n内存参数\n\n\n\n\n\n![](https://pics7.baidu.com/feed/bba1cd11728b47102f9e1e7af46fe7f6fd03237b.png@f_auto?token=ca9d5541411861cfb09e792849a82a06)\n\n\n\n\n\n## JVM性能调优工具\n\n这个篇幅在这里就不过多介绍了，可以参照：\n\n深入理解JVM虚拟机——Java虚拟机的监控及诊断工具大全\n\n## 常用调优策略\n\n这里还是要提一下，及时确定要进行JVM调优，也不要陷入“知见障”，进行分析之后，发现可以通过优化程序提升性能，仍然首选优化程序。\n\n**5.1、选择合适的垃圾回收器**\n\nCPU单核，那么毫无疑问Serial 垃圾收集器是你唯一的选择。\n\nCPU多核，关注吞吐量 ，那么选择PS+PO组合。\n\nCPU多核，关注用户停顿时间，JDK版本1.6或者1.7，那么选择CMS。\n\nCPU多核，关注用户停顿时间，JDK1.8及以上，JVM可用内存6G以上，那么选择G1。\n\n参数配置：\n\n> //设置Serial垃圾收集器（新生代）\n>\n> 开启：-XX:+UseSerialGC\n>\n> //设置PS+PO,新生代使用功能Parallel Scavenge 老年代将会使用Parallel Old收集器\n>\n> 开启 -XX:+UseParallelOldGC\n>\n> //CMS垃圾收集器（老年代）\n>\n> 开启 -XX:+UseConcMarkSweepGC\n>\n> //设置G1垃圾收集器\n>\n> 开启 -XX:+UseG1GC\n\n**5.2、调整内存大小**\n\n现象：垃圾收集频率非常频繁。\n\n原因：如果内存太小，就会导致频繁的需要进行垃圾收集才能释放出足够的空间来创建新的对象，所以增加堆内存大小的效果是非常显而易见的。\n\n注意：如果垃圾收集次数非常频繁，但是每次能回收的对象非常少，那么这个时候并非内存太小，而可能是内存泄露导致对象无法回收，从而造成频繁GC。\n\n参数配置：\n\n> //设置堆初始值\n>\n> 指令1：-Xms2g\n>\n> 指令2：-XX:InitialHeapSize=2048m\n>\n> //设置堆区最大值\n>\n> 指令1：`-Xmx2g`\n>\n> 指令2： -XX:MaxHeapSize=2048m\n>\n> //新生代内存配置\n>\n> 指令1：-Xmn512m\n>\n> 指令2：-XX:MaxNewSize=512m\n\n**5.3、设置符合预期的停顿时间**\n\n**现象**：程序间接性的卡顿\n\n**原因**：如果没有确切的停顿时间设定，垃圾收集器以吞吐量为主，那么垃圾收集时间就会不稳定。\n\n**注意**：不要设置不切实际的停顿时间，单次时间越短也意味着需要更多的GC次数才能回收完原有数量的垃圾.\n\n**参数配置**：\n\n> //GC停顿时间，垃圾收集器会尝试用各种手段达到这个时间\n>\n> -XX:MaxGCPauseMillis\n\n**5.4、调整内存区域大小比率**\n\n现象：某一个区域的GC频繁，其他都正常。\n\n原因：如果对应区域空间不足，导致需要频繁GC来释放空间，在JVM堆内存无法增加的情况下，可以调整对应区域的大小比率。\n\n注意：也许并非空间不足，而是因为内存泄造成内存无法回收。从而导致GC频繁。\n\n参数配置：\n\n> //survivor区和Eden区大小比率\n>\n> 指令：-XX:SurvivorRatio=6 //S区和Eden区占新生代比率为1:6,两个S区2:6\n>\n> //新生代和老年代的占比\n>\n> -XX:NewRatio=4 //表示新生代:老年代 = 1:4 即老年代占整个堆的4/5；默认值=2\n\n**5.5、调整对象升老年代的年龄**\n\n**现象**：老年代频繁GC，每次回收的对象很多。\n\n**原因**：如果升代年龄小，新生代的对象很快就进入老年代了，导致老年代对象变多，而这些对象其实在随后的很短时间内就可以回收，这时候可以调整对象的升级代年龄，让对象不那么容易进入老年代解决老年代空间不足频繁GC问题。\n\n**注意**：增加了年龄之后，这些对象在新生代的时间会变长可能导致新生代的GC频率增加，并且频繁复制这些对象新生的GC时间也可能变长。\n\n配置参数：\n\n> //进入老年代最小的GC年龄,年轻代对象转换为老年代对象最小年龄值，默认值7\n>\n> -XX:InitialTenuringThreshol=7\n\n**5.6、调整大对象的标准**\n\n**现象**：老年代频繁GC，每次回收的对象很多,而且单个对象的体积都比较大。\n\n**原因**：如果大量的大对象直接分配到老年代，导致老年代容易被填满而造成频繁GC，可设置对象直接进入老年代的标准。\n\n**注意**：这些大对象进入新生代后可能会使新生代的GC频率和时间增加。\n\n配置参数：\n\n> //新生代可容纳的最大对象,大于则直接会分配到老年代，0代表没有限制。\n>\n> -XX:PretenureSizeThreshold=1000000\n\n**5.7、调整GC的触发时机**\n\n**现象**：CMS，G1 经常 Full GC，程序卡顿严重。\n\n**原因**：G1和CMS 部分GC阶段是并发进行的，业务线程和垃圾收集线程一起工作，也就说明垃圾收集的过程中业务线程会生成新的对象，所以在GC的时候需要预留一部分内存空间来容纳新产生的对象，如果这个时候内存空间不足以容纳新产生的对象，那么JVM就会停止并发收集暂停所有业务线程（STW）来保证垃圾收集的正常运行。这个时候可以调整GC触发的时机（比如在老年代占用60%就触发GC），这样就可以预留足够的空间来让业务线程创建的对象有足够的空间分配。\n\n**注意**：提早触发GC会增加老年代GC的频率。\n\n配置参数：\n\n> //使用多少比例的老年代后开始CMS收集，默认是68%，如果频繁发生SerialOld卡顿，应该调小\n>\n> -XX:CMSInitiatingOccupancyFraction\n>\n> //G1混合垃圾回收周期中要包括的旧区域设置占用率阈值。默认占用率为 65%\n>\n> -XX:G1MixedGCLiveThresholdPercent=65\n\n5.8、调整 JVM本地内存大小\n\n**现象**：GC的次数、时间和回收的对象都正常，堆内存空间充足，但是报OOM\n\n**原因**： JVM除了堆内存之外还有一块堆外内存，这片内存也叫本地内存，可是这块内存区域不足了并不会主动触发GC，只有在堆内存区域触发的时候顺带会把本地内存回收了，而一旦本地内存分配不足就会直接报OOM异常。\n\n**注意**： 本地内存异常的时候除了上面的现象之外，异常信息可能是OutOfMemoryError：Direct buffer memory。 解决方式除了调整本地内存大小之外，也可以在出现此异常时进行捕获，手动触发GC（System.gc()）。\n\n配置参数：\n\n> XX:MaxDirectMemorySize\n\n## 六、JVM调优实例\n\n整理的一些JVM调优实例：\n\n**6.1、网站流量浏览量暴增后，网站反应页面响很慢**\n\n> 1、问题推测：在测试环境测速度比较快，但是一到生产就变慢，所以推测可能是因为垃圾收集导致的业务线程停顿。\n>\n> 2、定位：为了确认推测的正确性，在线上通过jstat -gc 指令 看到JVM进行GC 次数频率非常高，GC所占用的时间非常长，所以基本推断就是因为GC频率非常高，所以导致业务线程经常停顿，从而造成网页反应很慢。\n>\n> 3、解决方案：因为网页访问量很高，所以对象创建速度非常快，导致堆内存容易填满从而频繁GC，所以这里问题在于新生代内存太小，所以这里可以增加JVM内存就行了，所以初步从原来的2G内存增加到16G内存。\n>\n> 4、第二个问题：增加内存后的确平常的请求比较快了，但是又出现了另外一个问题，就是不定期的会间断性的卡顿，而且单次卡顿的时间要比之前要长很多。\n>\n> 5、问题推测：练习到是之前的优化加大了内存，所以推测可能是因为内存加大了，从而导致单次GC的时间变长从而导致间接性的卡顿。\n>\n> 6、定位：还是通过jstat -gc 指令 查看到 的确FGC次数并不是很高，但是花费在FGC上的时间是非常高的,根据GC日志 查看到单次FGC的时间有达到几十秒的。\n>\n> 7、解决方案： 因为JVM默认使用的是PS+PO的组合，PS+PO垃圾标记和收集阶段都是STW，所以内存加大了之后，需要进行垃圾回收的时间就变长了，所以这里要想避免单次GC时间过长，所以需要更换并发类的收集器，因为当前的JDK版本为1.7，所以最后选择CMS垃圾收集器，根据之前垃圾收集情况设置了一个预期的停顿的时间，上线后网站再也没有了卡顿问题。\n\n**6.2、后台导出数据引发的OOM**\n\n**问题描述：**公司的后台系统，偶发性的引发OOM异常，堆内存溢出。\n\n> 1、因为是偶发性的，所以第一次简单的认为就是堆内存不足导致，所以单方面的加大了堆内存从4G调整到8G。\n>\n> 2、但是问题依然没有解决，只能从堆内存信息下手，通过开启了-XX:+\n> HeapDumpOnOutOfMemoryError参数 获得堆内存的dump文件。\n>\n> 3、VisualVM 对 堆dump文件进行分析，通过VisualVM查看到占用内存最大的对象是String对象，本来想跟踪着String对象找到其引用的地方，但dump文件太大，跟踪进去的时候总是卡死，而String对象占用比较多也比较正常，最开始也没有认定就是这里的问题，于是就从线程信息里面找突破点。\n>\n> 4、通过线程进行分析，先找到了几个正在运行的业务线程，然后逐一跟进业务线程看了下代码，发现有个引起我注意的方法，导出订单信息。\n>\n> 5、因为订单信息导出这个方法可能会有几万的数据量，首先要从数据库里面查询出来订单信息，然后把订单信息生成excel，这个过程会产生大量的String对象。\n>\n> 6、为了验证自己的猜想，于是准备登录后台去测试下，结果在测试的过程中发现到处订单的按钮前端居然没有做点击后按钮置灰交互事件，结果按钮可以一直点，因为导出订单数据本来就非常慢，使用的人员可能发现点击后很久后页面都没反应，结果就一直点，结果就大量的请求进入到后台，堆内存产生了大量的订单对象和EXCEL对象，而且方法执行非常慢，导致这一段时间内这些对象都无法被回收，所以最终导致内存溢出。\n>\n> 7、知道了问题就容易解决了，最终没有调整任何JVM参数，只是在前端的导出订单按钮上加上了置灰状态，等后端响应之后按钮才可以进行点击，然后减少了查询订单信息的非必要字段来减少生成对象的体积，然后问题就解决了。\n\n**6.3、单个缓存数据过大导致的系统CPU飚高**\n\n> 1、系统发布后发现CPU一直飚高到600%，发现这个问题后首先要做的是定位到是哪个应用占用CPU高，通过top 找到了对应的一个java应用占用CPU资源600%。\n>\n> 2、如果是应用的CPU飚高，那么基本上可以定位可能是锁资源竞争，或者是频繁GC造成的。\n>\n> 3、所以准备首先从GC的情况排查，如果GC正常的话再从线程的角度排查，首先使用jstat -gc PID 指令打印出GC的信息，结果得到得到的GC 统计信息有明显的异常，应用在运行了才几分钟的情况下GC的时间就占用了482秒，那么问这很明显就是频繁GC导致的CPU飚高。\n>\n> 4、定位到了是GC的问题，那么下一步就是找到频繁GC的原因了，所以可以从两方面定位了，可能是哪个地方频繁创建对象，或者就是有内存泄露导致内存回收不掉。\n>\n> 5、根据这个思路决定把堆内存信息dump下来看一下，使用jmap -dump 指令把堆内存信息dump下来（堆内存空间大的慎用这个指令否则容易导致会影响应用，因为我们的堆内存空间才2G所以也就没考虑这个问题了）。\n>\n> 6、把堆内存信息dump下来后，就使用visualVM进行离线分析了，首先从占用内存最多的对象中查找，结果排名第三看到一个业务VO占用堆内存约10%的空间，很明显这个对象是有问题的。\n>\n> 7、通过业务对象找到了对应的业务代码，通过代码的分析找到了一个可疑之处，这个业务对象是查看新闻资讯信息生成的对象，由于想提升查询的效率，所以把新闻资讯保存到了redis缓存里面，每次调用资讯接口都是从缓存里面获取。\n>\n> 8、把新闻保存到redis缓存里面这个方式是没有问题的，有问题的是新闻的50000多条数据都是保存在一个key里面，这样就导致每次调用查询新闻接口都会从redis里面把50000多条数据都拿出来，再做筛选分页拿出10条返回给前端。50000多条数据也就意味着会产生50000多个对象，每个对象280个字节左右，50000个对象就有13.3M，这就意味着只要查看一次新闻信息就会产生至少13.3M的对象，那么并发请求量只要到10，那么每秒钟都会产生133M的对象，而这种大对象会被直接分配到老年代，这样的话一个2G大小的老年代内存，只需要几秒就会塞满，从而触发GC。\n>\n> 9、知道了问题所在后那么就容易解决了，问题是因为单个缓存过大造成的，那么只需要把缓存减小就行了，这里只需要把缓存以页的粒度进行缓存就行了，每个key缓存10条作为返回给前端1页的数据，这样的话每次查询新闻信息只会从缓存拿出10条数据，就避免了此问题的 产生。\n\n**6.4、CPU经常100% 问题定位**\n\n问题分析：CPU高一定是某个程序长期占用了CPU资源。\n\n1、所以先需要找出那个进行占用CPU高。\n\n> top 列出系统各个进程的资源占用情况。\n\n2、然后根据找到对应进行里哪个线程占用CPU高。\n\n> top -Hp 进程ID 列出对应进程里面的线程占用资源情况\n\n3、找到对应线程ID后，再打印出对应线程的堆栈信息\n\n> printf \"%x\\n\" PID 把线程ID转换为16进制。\n>\n> jstack PID 打印出进程的所有线程信息，从打印出来的线程信息中找到上一步转换为16进制的线程ID对应的线程信息。\n\n4、最后根据线程的堆栈信息定位到具体业务方法,从代码逻辑中找到问题所在。\n\n> 查看是否有线程长时间的watting 或blocked\n>\n> 如果线程长期处于watting状态下， 关注watting on xxxxxx，说明线程在等待这把锁，然后根据锁的地址找到持有锁的线程。\n\n**6.5、内存飚高问题定位**\n\n分析： 内存飚高如果是发生在java进程上，一般是因为创建了大量对象所导致，持续飚高说明垃圾回收跟不上对象创建的速度，或者内存泄露导致对象无法回收。\n\n1、先观察垃圾回收的情况\n\n> jstat -gcPID 1000查看GC次数，时间等信息，每隔一秒打印一次。\n>\n> jmap -histo PID | head -20 查看堆内存占用空间最大的前20个对象类型,可初步查看是哪个对象占用了内存。\n\n如果每次GC次数频繁，而且每次回收的内存空间也正常，那说明是因为对象创建速度快导致内存一直占用很高；如果每次回收的内存非常少，那么很可能是因为内存泄露导致内存一直无法被回收。\n\n2、导出堆内存文件快照\n\n> jmap -dump:live,format=b,file=/home/myheapdump.hprof PID dump堆内存信息到文件。\n\n3、使用visualVM对dump文件进行离线分析,找到占用内存高的对象，再找到创建该对象的业务代码位置，从代码和业务场景中定位具体问题。\n\n**6.6、数据分析平台系统频繁 Full GC**\n\n平台主要对用户在 App 中行为进行定时分析统计，并支持报表导出，使用 CMS GC 算法。\n\n数据分析师在使用中发现系统页面打开经常卡顿，通过 jstat 命令发现系统每次 Young GC 后大约有 10% 的存活对象进入老年代。\n\n原来是因为 Survivor 区空间设置过小，每次 Young GC 后存活对象在 Survivor 区域放不下，提前进入老年代。\n\n通过调大 Survivor 区，使得 Survivor 区可以容纳 Young GC 后存活对象，对象在 Survivor 区经历多次 Young GC 达到年龄阈值才进入老年代。\n\n调整之后每次 Young GC 后进入老年代的存活对象稳定运行时仅几百 Kb，Full GC 频率大大降低。\n\n**6.7、业务对接网关 OOM**\n\n网关主要消费 Kafka 数据，进行数据处理计算然后转发到另外的 Kafka 队列，系统运行几个小时候出现 OOM，重启系统几个小时之后又 OOM。\n\n通过 jmap 导出堆内存，在 eclipse MAT 工具分析才找出原因：代码中将某个业务 Kafka 的 topic 数据进行日志异步打印，该业务数据量较大，大量对象堆积在内存中等待被打印，导致 OOM。\n\n**6.8、鉴权系统频繁长时间 Full GC**\n\n系统对外提供各种账号鉴权服务，使用时发现系统经常服务不可用，通过 Zabbix 的监控平台监控发现系统频繁发生长时间 Full GC，且触发时老年代的堆内存通常并没有占满，发现原来是业务代码中调用了\n\n## 七、一个具体的实战案例分析\n\n**7.1 典型调优参数设置**\n\n> 服务器配置： 4cpu,8GB内存 ---- jvm调优实际上是设置一个合理大小的jvm堆内存（既不能太大，也不能太小）\n\n> -Xmx3550m 设置jvm堆内存最大值 (经验值设置： 根据压力测试，根据线上程序运行效果情况)\n>\n> -Xms3550m 设置jvm堆内存初始化大小，一般情况下必须设置此值和最大的最大的堆内存空间保持一致，防止内存抖动，消耗性能\n>\n> -Xmn2g 设置年轻代占用的空间大小\n>\n> -Xss256k 设置线程堆栈的大小；jdk5.0以后默认线程堆栈大小为1MB; 在相同的内存情况下，减小堆栈大小，可以使得操作系统创建更多的业务线程；\n\njvm堆内存设置:\n\n> nohup java -Xmx3550m -Xms3550m -Xmn2g -Xss256k -jar jshop-web-1.0-SNAPSHOT.jar --spring.addition-location=application.yaml > jshop.log 2>&1 ><\n\nTPS性能曲线：\n\n![](https://pic.rmb.bdstatic.com/bjh/down/ee3949fc51c360330ece8f6729cd0560.png)\n\n**7.2 分析gc日志**\n\n如果需要分析gc日志，就必须使得服务gc输入gc详情到log日志文件中，然后使用相应gc日志分析工具来对日志进行分析即可；\n\n把gc详情输出到一个gc.log日志文件中，便于gc分析\n\n> -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:gc.log\n\nThroughput: 业务线程执行时间 / (gc时间+业务线程时间)\n\n![](https://pic.rmb.bdstatic.com/bjh/down/25529b786b50b8b30067e6f721101369.png)\n\n分析gc日志，发现，一开始就发生了3次fullgc，很明显jvm优化参数的设置是有问题的；\n\n![](https://pic.rmb.bdstatic.com/bjh/down/e70f3ffedc03bc0b387ff860cb8c948b.png)\n\n查看fullgc发生问题原因： jstat -gcutil pid\n\n![](https://pic.rmb.bdstatic.com/bjh/down/0e135e59c1e140aaded9223edd2f0177.png)\n\nMetaspace持久代： 初始化分配大小20m , 当metaspace被占满后，必须对持久代进行扩容，如果metaspace每进行一次扩容，fullgc就需要执行一次；（fullgc回收整个堆空间，非常占用时间）\n\n调整gc配置： 修改永久代空间初始化大小:\n\n> nohup java -Xmx3550m -Xms3550m -Xmn2g -Xss256k -XX:MetaspaceSize=256m -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:gc.log -jar jshop-web-1.0-SNAPSHOT.jar --spring.addition-location=application.yaml > jshop.log 2>&1 ><\n\n经过调优后，fullgc现象已经消失了：\n\n![](https://pic.rmb.bdstatic.com/bjh/down/0f8e8b90a8f9d810759ae43b3b0ed1b9.png)\n\n**7.3 Young&Old比例**\n\n年轻代和老年代比例：1:2 参数：-XX:NewRetio = 4 , 表示年轻代（eden,s0,s1）和老年代所占比值为1:4\n\n1） -XX:NewRetio = 4\n\n![](https://pic.rmb.bdstatic.com/bjh/down/f8353c852a2f65626b5b3a23f9616208.png)\n\n年轻代分配的内存大小变小了，这样YGC次数变多了，虽然fullgc不发生了，但是YGC花费的时间更多了！\n\n2） -XX:NewRetio = 2 YGC发生的次数必然会减少；因为eden区域的大小变大了，因此YGC就会变少；\n\n![](https://pic.rmb.bdstatic.com/bjh/down/11ca145ee5a93f511feda69e749582cb.png)\n\n7.4 Eden&S0S1\n\n为了进一步减少YGC, 可以设置 enden ,s 区域的比值大小； 设置方式： -XX:SurvivorRatio=8\n\n1） 设置比值：8:1:1\n\n![](https://pic.rmb.bdstatic.com/bjh/down/6330cbdd9acd6a5c18e91c5bea13cca2.png)\n\n2） Xmn2g 8:1:1\n\nnohup java -Xmx3550m -Xms3550m -Xmn2g -XX:SurvivorRatio=8 -Xss256k -XX:MetaspaceSize=256m -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:gc.log -jar jshop-web-1.0-SNAPSHOT.jar --spring.addition-location=application.yaml > jshop.log 2>&1 ><\n\n根据gc调优，垃圾回收次数，时间，吞吐量都是一个比较优的一个配置；\n\n![](https://pic.rmb.bdstatic.com/bjh/down/68edd8bfceb7d5e85d18a53e150cad2a.png)\n\n**7.5 吞吐量优先**\n\n使用并行的垃圾回收器，可以充分利用多核心cpu来帮助进行垃圾回收；这样的gc方式，就叫做吞吐量优先的调优方式\n\n垃圾回收器组合： ps(parallel scavenge) + po (parallel old) 此垃圾回收器是Jdk1.8 默认的垃圾回收器组合；\n\n> nohup java -Xmx3550m -Xms3550m -Xmn2g -XX:SurvivorRatio=8 -Xss256k -XX:+UseParallelGC -XX:UseParallelOldGC -XX:MetaspaceSize=256m -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:gc.log -jar jshop-web-1.0-SNAPSHOT.jar --spring.addition-location=application.yaml > jshop.log 2>&1 ><\n\n**7.6 响应时间优先**\n\n使用cms垃圾回收器，就是一个响应时间优先的组合； cms垃圾回收器（垃圾回收和业务线程交叉执行，不会让业务线程进行停顿stw）尽可能的减少stw的时间，因此使用cms垃圾回收器组合，是响应时间优先组合\n\n> nohup java -Xmx3550m -Xms3550m -Xmn2g -XX:SurvivorRatio=8 -Xss256k -XX:+UseParNewGC -XX:UseConcMarkSweepGC -XX:MetaspaceSize=256m -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:gc.log -jar jshop-web-1.0-SNAPSHOT.jar --spring.addition-location=application.yaml > jshop.log 2>&1 ><\n\n可以发现，cms垃圾回收器时间变长；\n\n**7.7 g1**\n\n配置方式如下所示：\n\n> nohup java -Xmx3550m -Xms3550m -Xmn2g -XX:SurvivorRatio=8 -Xss256k -XX:+UseG1GC -XX:MetaspaceSize=256m -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:gc.log -jar jshop-web-1.0-SNAPSHOT.jar --spring.addition-location=application.yaml > jshop.log 2>&1 ><\n\n![](https://pic.rmb.bdstatic.com/bjh/down/4146281952d45f65334e7ba97c6ba873.png)\n\n\n## 参考资料\n\n*   [Java HotSpot™ Virtual Machine Performance Enhancements](http://docs.oracle.com/javase/8/docs/technotes/guides/vm/performance-enhancements-7.html)\n*   [Java HotSpot Virtual Machine Garbage Collection Tuning Guide](http://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/index.html)\n*   [[HotSpot VM] JVM调优的”标准参数”的各种陷阱](http://hllvm.group.iteye.com/group/topic/27945)\n\n\n## 参考文章\n\n<https://segmentfault.com/a/1190000009707894>\n\n<https://www.cnblogs.com/hysum/p/7100874.html>\n\n<http://c.biancheng.net/view/939.html>\n\n<https://www.runoob.com/>\n\nhttps://blog.csdn.net/android_hl/article/details/53228348\n\n\n"
  },
  {
    "path": "docs/Java/JVM/深入理解JVM虚拟机：JVM性能管理神器VisualVM介绍与实战.md",
    "content": "# 目录\n  * [一、VisualVM是什么？](#一、visualvm是什么？)\n  * [二、如何获取VisualVM？](#二、如何获取visualvm？)\n  * [三、获取那个版本？](#三、获取那个版本？)\n  * [四、VisualVM能做什么？](#四、visualvm能做什么？)\n    * [监控远程主机上的JAVA应用程序](#监控远程主机上的java应用程序)\n  * [排查JAVA应用程序内存泄漏](#排查java应用程序内存泄漏)\n  * [查找JAVA应用程序耗时的方法函数](#查找java应用程序耗时的方法函数)\n  * [排查JAVA应用程序线程锁](#排查java应用程序线程锁)\n  * [参考文章](#参考文章)\n\n\n本文转自互联网，侵删\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章将同步到我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《深入理解JVM虚拟机》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，一步步地学习JVM基础知识，并上手进行JVM调优实战，JVM是每一个Java工程师必须要学习和理解的知识点，你必须要掌握其实现原理，才能更完整地了解整个Java技术体系，形成自己的知识框架。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n## 一、VisualVM是什么？\n    VisualVM是一款免费的JAVA虚拟机图形化监控分析工具。\n    1.  拥有图形化的监控界面。\n    2. 提供本地、远程的JVM监控分析功能。\n    3. 是一款免费的JAVA工具。\n    4. VisualVM拥有丰富的插件支持。\n## 二、如何获取VisualVM？\n    VisualVM官方网站：http://visualvm.java.net/\n\n    VisualVM各版本下载页面: http://visualvm.java.net/releases.html\n\n     下载VisualVM时也应该注意，不同的JDK版本对应不同版本的VisualVM，具体根据安装的JDK版本来下载第一的VisualVM。\n## 三、获取那个版本？\n\n       下载版本参考：Java虚拟机性能管理神器 - VisualVM（4） - JDK版本与VisualVM版本对应关系\n\n备注：下列表中显示1.3.6版本只适合JDK7和JDK8，可是我用1.3.6版还是可以监控JDK1.6_45的版本。\n\n## 四、VisualVM能做什么？\n\n1. 显示JAVA应用程序配置和运行时环境。\n显示JAVA应用程序JVM参数，系统属性，JVM的信息和运行环境。\n\n \n2. 显示本地和远程JAVA应用程序运行状态。\n可以连接到远程服务器上运行的JAVA应用程序，监控应用程序的运行状态。\n\n3. 监控应用程序的性能消耗。\n可以监控到应用程序热点方法的执行单次时间、总耗时、耗时占比。\n\n \n4. 显示应用程序内存分配，显示分析堆信息。\n显示应用程序在运行时的编译时间、加载时间、垃圾回收时间、内存区域的回收状态等。\n\n \n5. 监控应用程序线程状态和生命周期。\n监控应用程序线程的运行、休眠、等待、锁定状态。\n\n \n6. 显示、分析线程堆信息。\n显示线程当前运行状态和关联类信息。\n\n \n7. 支持第三方插件来分析JAVA应用程序。\n另外还提供更多更强大、方便的第三方插件。\n\n \n### 监控远程主机上的JAVA应用程序\n\n    使用VisualVM监控远程主机上JAVA应用程序时，需要开启远程主机上的远程监控访问，或者在远程JAVA应用程序启动时，开启远程监控选项，两种方法，选择其中一种就可以开启远程监控功能，配置完成后就可以在本地对远程主机上的JAVA应用程序进行监控。\n\n1.远程服务器、应用程序配置\n        1.1配合jstatd工具提供监控数据  \n        1.1.1创建安全访问文件\n        在JAVA_HOME/bin目录中，创建名称为jstatdAllPolicy文件（这个文件名称也可以顺便起，不过要与jstatd启动时指定名称相同），将以下内容拷贝到文件中。并保证文件的权限和用户都正确。\n\n        grant codebase\"file:${java.home}/../lib/tools.jar\"{ permission java.security.AllPermission; };\n\n \n\n1.1.2启动jstatd服务\n        在JAVA_HOME/bin目录中，执行以下命令：\n\n         ./jstatd -J-Djava.security.policy=jstatdAllPolicy-p 1099 -J-Djava.rmi.server.hostname=192.168.xxx.xxx\n\n \n\n        jstatd命令描述以及参数说明：\n\n           jstatd是一个基于RMI（Remove Method Invocation）的服务程序，它用于监控基于HotSpot的JVM中资源的创建及销毁，并且提供了一个远程接口允许远程的监控工具连接到本地的JVM执行命令。\n\n \n\n        -J-Djava.security.policy=jstatdAllPolicy 指定安全策略文件名称\n\n         -p 1099  指定启动端口\n\n         -J-Djava.rmi.server.hostname=192.168.xxx.xxx  指定本机IP地址，在hosts文件配置不正常时使用，最好加上。\n\n\n1.2JVM启动时配置远程监控选项\n        在需要远程监控的JVM启动时，开启远程监控选项\n\n        -Dcom.sun.management.jmxremote.port=1099\n        -Dcom.sun.management.jmxremote.ssl=false\n        -Dcom.sun.management.jmxremote.authenticate=false\n        -Djava.rmi.server.hostname=192.168.xxx.xxx\n\n \n\n2.本地VisualVM配置\n        在本地VisualVM的应用程序窗口，右键单击【远程】》【添加远程主机】》【主机名】中输入远程主机的IP地址，点击【高级设置】输入远程主机开启的监控端口，点击【确定】完成配置。\n\n\n\n        如果一切正常，就可以看到远程主机上的JAVA应用程序了。\n\n\n\n \n\n## 排查JAVA应用程序内存泄漏\n\n1. 发现问题\n    线上应用部署完成后，运行1~2天左右就会出现假死，或者某天早上8~10点高峰期间突然不处理数据了。由于在测试环境的压力测试没有做完全，也没有遇到相关问题。情况出现后对客户的使用造成很大影响，领导要求赶紧排查出问题原因！\n\n2. 排查原因\n        排查原因前，与运维沟通，了解线上服务器的运行状态，通过ganglila观察网络、CPU、内存、磁盘的运行历史状态，发现程序故障前，都有一波很高的负载，排查线上日志，负载来源在8~9点平台接入数据量成倍增加，通过与产品和市场人员分析，此时段是用户集中上班、接入平台的高峰时段，访问日志也显示，业务场景正常，无网络攻击和安全问题。属于产品业务正常的场景。\n\n        排除了网络安全因素后，就从程序的运行内部进行排查，首先想到的获取JVM的dmp文件。获取JVM的dmp文件有两中方式：\n\n        1. JVM启动时增加两个参数，出现 OOME 时生成堆 dump: \n\n                -XX:+HeapDumpOnOutOfMemoryError\n\n                生成堆文件地址：\n\n                -XX:HeapDumpPath=/home/test/jvmlogs/ \n\n        2. 发现程序异常前通过执行指令，直接生成当前JVM的dmp文件，15434是指JVM的进程号\n\n                jmap -dump:format=b,file=serviceDump.dat    15434 \n\n        由于第一种方式是一种事后方式，需要等待当前JVM出现问题后才能生成dmp文件，实时性不高，第二种方式在执行时，JVM是暂停服务的，所以对线上的运行会产生影响。所以建议第一种方式。\n\n3. 解决方案\n        获取到dmp文件后，就开始进行分析。将服务器上的dmp文件拷贝到本地，然后启动本地的VisualVM,点击菜单栏【文件】选项，装入dmp文件\n\n\n\n        打开dmp文件后，查看类标签，就能看到占用内存的一个排行。\n\n\n\n        然后通过检查中查找最大的对象，排查到具体线程和对象。\n\n\n\n \n\n        上列中的com.ctfo.trackservice.handler.TrackHandleThread#4就是重点排查对象。\n\n        通过代码的比对，在此线程中，有调用DAO接口，负责将数据存储到数据库中。而存储到数据库中时，由于存储速度较慢，导致此线程中的数据队列满了，数据积压，无法回收导致了队列锁定，结果就是程序假死，不处理数据。\n\n \n\n        通过进一步分析，发现数据库存储时有瓶颈，虽然当前是批量提交，速度也不快。平均8000/秒的存储速度。而数据库有一个DG（备份）节点，采用的是同步备份方式，即主库事务要等DG的事务也完成后才能返回成功，这样就会因为网络因素、DG性能因素等原因导致性能下降。通过与DBA、产品、沟通，将同步备份改为异步备份，实时同步改为异步（异步可能会导致主备有10分钟以内的数据延迟）。速度达到30000/秒。问题解决。\n\n        至此，通过VisualVM分析java程序内存泄漏到此结束。不过还有几个问题:1. 如果dmp文件较大，VisualVM分析时间可能很久；另外，VisualVM对堆的分析显示功能还不算全面。如果需要更全面的显示，就可以使用另外一个专业的dmp文件分析工具【Memory Analyzer (MAT)】，此工具可以作为eclipse的插件进行安装，也可以单独下载使用。如果有感兴趣的朋友，我个人建议还是单独下载使用。下载地址：http://www.eclipse.org/mat/   \n\n \n\n## 查找JAVA应用程序耗时的方法函数\n\n1.为什么要监控？\n        JAVA程序在开发前，根据设计文档的性能需求，是要对程序的性能指标进行测试的。比如接口每秒响应次数要求1000次/秒，就需要平均每次请求处理的时间在1ms以内，如果需要满足这个指标，就需要在开发阶段对接口执行函数进行监控，也可以通过打印日志进行监控，从而统计对应的性能指标，然后可以根据性能指标的要求进行相应优化。\n\n2. 那些方法函数需要监控？\n        根据具体业务的场景和需求，主要集中在IO通讯、文件读写、数据库操作、业务逻辑处理上，这些都是制约性能的重要因素，所以需要重点关注。\n\n        \n\n3. 如何排查\n        在研发环境，大部分会使用syso的方式或者日志方式打印性能损耗，如果代码没有加在运行时才想起来，或者想关注突然想起的函数，换做以前，是需要重启服务的，如果有VisualVM就可以直接查看耗时以及调用次数等情况。而不用打印、输出日志来查看性能损耗。\n\n\n\n4. 如何处理\n        对于性能损耗的函数，根据业务逻辑可以进行相应的优化，例如字符串处理、文件读写方式、SQL语句优化、多线程处理等等方式。\n \n       由于性能优化涉及的内容很多，这里就不深入了。主要是告诉大家通过VisualVM来排查问题的具体位置。\n \n\n \n\n## 排查JAVA应用程序线程锁\n\n \n\n1. JAVA应用程序线程锁原因\n        JAVA线程锁的例子和原因网上一大堆，我也不在这里深入说明，这里主要是否讲如何使用VisualVM进行排查。至于例子可以看这里：http://blog.csdn.net/fengzhe0411/article/details/6953370 \n\n这个例子比较极端，一般情况下，出现锁竞争激烈是比较常见的。\n\n2. 排查JAVA应用程序线程锁\n       启动 VisualVM，在应用程序窗口，选择对应的JAVA应用，在详情窗口》线程标签（勾选线程可视化），查看线程生命周期状态，主要留意线程生命周期中红色部分。\n\n\n\n（1）绿色：代表运行状态。一般属于正常情况。如果是多线程环境，生产者消费者模式下，消费者一直处于运行状态，说明消费者处理性能低，跟不上生产者的节奏，需要优化对应的代码，如果不处理，就可能导致消费者队列阻塞的现象。对应线程的【RUNNABLE】状态。\n\n（2）蓝色：代表线程休眠。线程中调用Thread.sleep()函数的线程状态时，就是蓝色。对应线程的【TIMED_WAITING】状态。\n\n（3）黄色：代表线程等待。调用线程的wait()函数就会出现黄色状态。对应线程的【WAITING】状态。\n\n（4）红色：代码线程锁定。对应线程的【BLOCKED】状态。\n\n\n\n3. 分析解决JAVA应用程序线程锁\n        发生线程锁的原因有很多，我所遇到比较多的情况是多线程同时访问同一资源，且此资源使用synchronized关键字，导致一个线程要等另外一个线程使用完资源后才能运行。例如再没有连接池的情况下，同时访问数据库接口。这种情况会导致性能的极具下降，解决的方案是增加连接池，或者修改访问方式。或者将资源粒度细化，类似ConCurrentHashMap中的处理方式，将资源分为多个更小粒度的资源，在更小粒度资源上来处理锁，就可以解决资源竞争激烈的问题。]\n\n## 参考文章\n\n<https://segmentfault.com/a/1190000009707894>\n\n<https://www.cnblogs.com/hysum/p/7100874.html>\n\n<http://c.biancheng.net/view/939.html>\n\n<https://www.runoob.com/>\n\nhttps://blog.csdn.net/android_hl/article/details/53228348\n\n\n"
  },
  {
    "path": "docs/Java/JVM/深入理解JVM虚拟机：JVM监控工具与诊断实践.md",
    "content": "# 目录\n  * [一、jvm常见监控工具&指令](#一、jvm常见监控工具指令)\n    * [1、 jps:jvm进程状况工具](#1、-jpsjvm进程状况工具)\n    * [2、jstat: jvm统计信息监控工具](#2、jstat-jvm统计信息监控工具)\n    * [3、jinfo： java配置信息](#3、jinfo：-java配置信息)\n    * [4、jmap: java 内存映射工具](#4、jmap-java-内存映射工具)\n    * [5、jhat:jvm堆快照分析工具](#5、jhatjvm堆快照分析工具)\n    * [6、jstack:java堆栈跟踪工具](#6、jstackjava堆栈跟踪工具)\n  * [二、可视化工具](#二、可视化工具)\n  * [三、应用](#三、应用)\n    * [1、cpu飙升](#1、cpu飙升)\n    * [2、线程死锁](#2、线程死锁)\n    * [2.查看java进程的线程快照信息](#2查看java进程的线程快照信息)\n    * [3、OOM内存泄露](#3、oom内存泄露)\n  * [参考文章](#参考文章)\n\n\n\n本文转自：https://juejin.im/post/59e6c1f26fb9a0451c397a8c\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章将同步到我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《深入理解JVM虚拟机》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，一步步地学习JVM基础知识，并上手进行JVM调优实战，JVM是每一个Java工程师必须要学习和理解的知识点，你必须要掌握其实现原理，才能更完整地了解整个Java技术体系，形成自己的知识框架。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n在常见的线上问题时候，我们多数会遇到以下问题：\n\n> *   内存泄露\n> *   某个进程突然cpu飙升\n> *   线程死锁\n> *   响应变慢...等等其他问题。\n\n如果遇到了以上这种问题，在线下可以有各种本地工具支持查看，但到线上了，就没有这么多的本地调试工具支持，我们该如何基于监控工具来进行定位问题?\n\n我们一般会基于数据收集来定位，而数据的收集离不开监控工具的处理，比如：运行日志、异常堆栈、GC日志、线程快照、堆快照等。经常使用恰当的分析和监控工具可以加快我们的分析数据、定位解决问题的速度。以下我们将会详细介绍。\n\n## 一、jvm常见监控工具&指令\n\n### 1、 jps:jvm进程状况工具\n\n\n\n```\njps [options] [hostid]\n```\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404222505.png)\n\n如果不指定hostid就默认为当前主机或服务器。\n\n命令行参数选项说明如下：\n\n\n\n```\n-q 不输出类名、Jar名和传入main方法的参数\n\n- l 输出main类或Jar的全限名\n\n-m 输出传入main方法的参数\n\n- v 输出传入JVM的参数复制代码\n```\n\n\n\n### 2、jstat: jvm统计信息监控工具\n\njstat 是用于见识虚拟机各种运行状态信息的命令行工具。它可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、jit编译等运行数据，它是线上定位jvm性能的首选工具。\n\n命令格式:\n\n\n\n```\njstat [ generalOption | outputOptions vmid [interval[s|ms] [count]] ]\n\ngeneralOption - 单个的常用的命令行选项，如-help, -options, 或 -version。\n\noutputOptions -一个或多个输出选项，由单个的statOption选项组成，可以和-t, -h, and -J等选项配合使用。复制代码\n```\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404222543.png)\n\n参数选项：\n\n| Option | Displays | Ex |\n| --- | --- | --- |\n| [class](http://docs.oracle.com/javase/1.5.0/docs/tooldocs/share/jstat.html#class_option) | 用于查看类加载情况的统计 | jstat -class pid:显示加载class的数量，及所占空间等信息。 |\n| [compiler](http://docs.oracle.com/javase/1.5.0/docs/tooldocs/share/jstat.html#compiler_option) | 查看HotSpot中即时编译器编译情况的统计 | jstat -compiler pid:显示VM实时编译的数量等信息。 |\n| [gc](http://docs.oracle.com/javase/1.5.0/docs/tooldocs/share/jstat.html#gc_option) | 查看JVM中堆的垃圾收集情况的统计 | jstat -gc pid:可以显示gc的信息，查看gc的次数，及时间。其中最后五项，分别是young gc的次数，young gc的时间，full gc的次数，full gc的时间，gc的总时间。 |\n| [gccapacity](http://docs.oracle.com/javase/1.5.0/docs/tooldocs/share/jstat.html#gccapacity_option) | 查看新生代、老生代及持久代的存储容量情况 | jstat -gccapacity:可以显示，VM内存中三代（young,old,perm）对象的使用和占用大小 |\n| [gccause](http://docs.oracle.com/javase/1.5.0/docs/tooldocs/share/jstat.html#gccause_option) | 查看垃圾收集的统计情况（这个和-gcutil选项一样），如果有发生垃圾收集，它还会显示最后一次及当前正在发生垃圾收集的原因。 | jstat -gccause:显示gc原因 |\n| [gcnew](http://docs.oracle.com/javase/1.5.0/docs/tooldocs/share/jstat.html#gcnew_option) | 查看新生代垃圾收集的情况 | jstat -gcnew pid:new对象的信息 |\n| [gcnewcapacity](http://docs.oracle.com/javase/1.5.0/docs/tooldocs/share/jstat.html#gcnewcapacity_option) | 用于查看新生代的存储容量情况 | jstat -gcnewcapacity pid:new对象的信息及其占用量 |\n| [gcold](http://docs.oracle.com/javase/1.5.0/docs/tooldocs/share/jstat.html#gcold_option) | 用于查看老生代及持久代发生GC的情况 | jstat -gcold pid:old对象的信息 |\n| [gcoldcapacity](http://docs.oracle.com/javase/1.5.0/docs/tooldocs/share/jstat.html#gcoldcapacity_option) | 用于查看老生代的容量 | jstat -gcoldcapacity pid:old对象的信息及其占用量 |\n| [gcpermcapacity](http://docs.oracle.com/javase/1.5.0/docs/tooldocs/share/jstat.html#gcpermcapacity_option) | 用于查看持久代的容量 | jstat -gcpermcapacity pid: perm对象的信息及其占用量 |\n| [gcutil](http://docs.oracle.com/javase/1.5.0/docs/tooldocs/share/jstat.html#gcutil_option) | 查看新生代、老生代及持代垃圾收集的情况 | jstat -util pid:统计gc信息统计 |\n| [printcompilation](http://docs.oracle.com/javase/1.5.0/docs/tooldocs/share/jstat.html#printcompilation_option) | HotSpot编译方法的统计 | jstat -printcompilation pid:当前VM执行的信息 |\n\n**例如**:\n\n查看gc 情况执行:jstat-gcutil 27777\n\n\n### 3、jinfo： java配置信息\n\n命令格式:\n\n\n\n```\njinfo[option] pid复制代码\n```\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404222602.png)\n\n比如:获取一些当前进程的jvm运行和启动信息。\n\n\n### 4、jmap: java 内存映射工具\n\njmap命令用于生产堆转存快照。打印出某个java进程（使用pid）内存内的，所有‘对象’的情况（如：产生那些对象，及其数量）。\n\n命令格式：\n\n\n\n```\njmap [ option ] pid\n\njmap [ option ] executable core\n\njmap [ option ] [server-id@]remote-hostname-or-IP复制代码\n```\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404222621.png)\n\n\n参数选项：\n\n\n\n```\n-dump:[live,]format=b,file=<filename> 使用hprof二进制形式,输出jvm的heap内容到文件=. live子选项是可选的，假如指定live选项,那么只输出活的对象到文件. \n\n-finalizerinfo 打印正等候回收的对象的信息.\n\n-heap 打印heap的概要信息，GC使用的算法，heap的配置及wise heap的使用情况.\n\n-histo[:live] 打印每个class的实例数目,内存占用,类全名信息. VM的内部类名字开头会加上前缀”*”. 如果live子参数加上后,只统计活的对象数量. \n\n-permstat 打印classload和jvm heap长久层的信息. 包含每个classloader的名字,活泼性,地址,父classloader和加载的class数量. 另外,内部String的数量和占用内存数也会打印出来. \n\n-F 强迫.在pid没有相应的时候使用-dump或者-histo参数. 在这个模式下,live子参数无效. \n\n-h | -help 打印辅助信息 \n\n-J 传递参数给jmap启动的jvm. 复制代码\n```\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404222638.png)\n### 5、jhat:jvm堆快照分析工具\n\njhat 命令与jamp搭配使用，用来分析map生产的堆快存储快照。jhat内置了一个微型http/Html服务器，可以在浏览器找那个查看。不过建议尽量不用，既然有dumpt文件，可以从生产环境拉取下来，然后通过本地可视化工具来分析，这样既减轻了线上服务器压力，有可以分析的足够详尽(比如 MAT/jprofile/visualVm)等。\n\n\n### 6、jstack:java堆栈跟踪工具\n\njstack用于生成java虚拟机当前时刻的线程快照。线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合，生成线程快照的主要目的是定位线程出现长时间停顿的原因，如线程间死锁、死循环、请求外部资源导致的长时间等待等。\n\n命令格式：\n\n\n\n```\njstack [ option ] pid\n\njstack [ option ] executable core\n\njstack [ option ] [server-id@]remote-hostname-or-IP复制代码\n```\n\n\n\n参数：\n\n\n\n```\n-F当’jstack [-l] pid’没有相应的时候强制打印栈信息\n\n-l长列表. 打印关于锁的附加信息,例如属于java.util.concurrent的ownable synchronizers列表.\n\n-m打印java和native c/c++框架的所有栈信息.\n\n-h | -help打印帮助信息\n\npid 需要被打印配置信息的java进程id,可以用jps查询.复制代码\n```\n\n\n\n后续的查找耗费最高cpu例子会用到。\n\n## 二、可视化工具\n\n对jvm监控的常见可视化工具，除了jdk本身提供的Jconsole和visualVm以外，还有第三方提供的jprofilter，perfino,Yourkit，Perf4j，JProbe，MAT等。这些工具都极大的丰富了我们定位以及优化jvm方式。\n\n这些工具的使用，网上有很多教程提供，这里就不再过多介绍了。对于VisualVm来说，比较推荐使用，它除了对jvm的侵入性比较低以外，还是jdk团队自己开发的，相信以后功能会更加丰富和完善。jprofilter对于第三方监控工具，提供的功能和可视化最为完善，目前多数ide都支持其插件，对于上线前的调试以及性能调优可以配合使用。\n\n另外对于线上dump的heap信息，应该尽量拉去到线下用于可视化工具来分析，这样分析更详细。如果对于一些紧急的问题，必须需要通过线上监控，可以采用 VisualVm的远程功能来进行，这需要使用tool.jar下的MAT功能。\n\n## 三、应用\n\n### 1、cpu飙升\n\n在线上有时候某个时刻，可能会出现应用某个时刻突然cpu飙升的问题。对此我们应该熟悉一些指令，快速排查对应代码。\n\n**_1.找到最耗CPU的进程_**\n\n\n\n```\n指令:top复制代码\n```\n\n\n\n\n**_2.找到该进程下最耗费cpu的线程_**\n\n\n\n```\n指令:top -Hp pid复制代码\n```\n\n\n\n\n\n**_3.转换进制_**\n\n\n\n```\nprintf “%x\\n” 15332 // 转换16进制（转换后为0x3be4） 复制代码\n```\n\n\n**_4.过滤指定线程，打印堆栈信息_**\n\n\n\n```\n指令:\njstack pid |grep 'threadPid'  -C5 --color \n\njstack 13525 |grep '0x3be4'  -C5 --color  //  打印进程堆栈 并通过线程id，过滤得到线程堆栈信息。复制代码\n```\n\n可以看到是一个上报程序，占用过多cpu了（以上例子只为示例，本身耗费cpu并不高）\n\n### 2、线程死锁\n\n有时候部署场景会有线程死锁的问题发生，但又不常见。此时我们采用jstack查看下一下。比如说我们现在已经有一个线程死锁的程序，导致某些操作waiting中。\n\n**_1.查找java进程id_**\n\n\n\n```\n指令:top 或者 jps 复制代码\n```\n\n\n### 2.查看java进程的线程快照信息\n\n\n\n```\n指令：jstack -l pid复制代码\n```\n\n从输出信息可以看到，有一个线程死锁发生，并且指出了那行代码出现的。如此可以快速排查问题。\n\n### 3、OOM内存泄露\n\njava堆内的OOM异常是实际应用中常见的内存溢出异常。一般我们都是先通过内存映射分析工具（比如MAT）对dump出来的堆转存快照进行分析，确认内存中对象是否出现问题。\n\n当然了出现OOM的原因有很多，并非是堆中申请资源不足一种情况。还有可能是申请太多资源没有释放，或者是频繁频繁申请，系统资源耗尽。针对这三种情况我需要一一排查。\n\nOOM的三种情况:\n\n> 1.申请资源（内存）过小，不够用。\n> \n> 2.申请资源太多，没有释放。\n> \n> 3.申请资源过多，资源耗尽。比如：线程过多，线程内存过大等。\n\n**1.排查申请申请资源问题。**\n\n\n\n```\n指令:jmap -heap 11869 复制代码\n```\n\n查看新生代，老生代堆内存的分配大小以及使用情况，看是否本身分配过小。\n\n\n从上述排查，发现程序申请的内存没有问题。\n\n**2.排查gc**\n\n特别是fgc情况下，各个分代内存情况。\n\n\n\n```\n指令:jstat -gcutil 11938 1000 每秒输出一次gc的分代内存分配情况，以及gc时间复制代码\n```\n\n\n**3.查找最费内存的对象**\n\n\n\n```\n指令: jmap -histo:live 11869 | more复制代码\n```\n\n上述输出信息中，最大内存对象才161kb,属于正常范围。如果某个对象占用空间很大，比如超过了100Mb，应该着重分析，为何没有释放。\n\n注意，上述指令:\n\n\n\n```\njmap -histo:live 11869 | more\n\n执行之后，会造成jvm强制执行一次fgc，在线上不推荐使用，可以采取dump内存快照，线下采用可视化工具进行分析，更加详尽。\n\njmap -dump:format=b,file=/tmp/dump.dat 11869 \n\n或者采用线上运维工具，自动化处理，方便快速定位，遗失出错时间。复制代码\n```\n\n\n**4.确认资源是否耗尽**\n\n> *   pstree 查看进程线程数量\n> *   netstat 查看网络连接数量\n\n或者采用:\n\n> *   ll /proc/${PID}/fd | wc -l // 打开的句柄数\n> *   ll /proc/${PID}/task | wc -l （效果等同pstree -p | wc -l） //打开的线程数\n\n以上就是一些常见的jvm命令应用。\n\n一种工具的应用并非是万能钥匙，包治百病，问题的解决往往是需要多种工具的结合才能更好的定位问题，无论使用何种分析工具，最重要的是熟悉每种工具的优势和劣势。这样才能取长补短，配合使用。\n\n\n\n\n## 参考文章\n\n<https://segmentfault.com/a/1190000009707894>\n\n<https://www.cnblogs.com/hysum/p/7100874.html>\n\n<http://c.biancheng.net/view/939.html>\n\n<https://www.runoob.com/>\n\nhttps://blog.csdn.net/android_hl/article/details/53228348\n\n\n"
  },
  {
    "path": "docs/Java/JVM/深入理解JVM虚拟机：Java内存异常原理与实践.md",
    "content": "# 目录\n  * [实战内存溢出异常](#实战内存溢出异常)\n  * [1 . 对象的创建过程](#1--对象的创建过程)\n  * [2 . 对象的内存布局](#2--对象的内存布局)\n  * [3 . 对象的访问定位](#3--对象的访问定位)\n  * [4 .实战内存异常](#4-实战内存异常)\n    * [Java堆内存异常](#java堆内存异常)\n    * [Java栈内存异常](#java栈内存异常)\n    * [方法区内存异常](#方法区内存异常)\n    * [方法区与运行时常量池OOM](#方法区与运行时常量池oom)\n    * [附加-直接内存异常](#附加-直接内存异常)\n  * [Java内存泄漏](#java内存泄漏)\n  * [Java是如何管理内存？](#java是如何管理内存？)\n  * [什么是Java中的内存泄露？](#什么是java中的内存泄露？)\n    * [其他常见内存泄漏](#其他常见内存泄漏)\n      * [1、静态集合类引起内存泄露：](#1、静态集合类引起内存泄露：)\n      * [2、当集合里面的对象属性被修改后，再调用remove（）方法时不起作用。](#2、当集合里面的对象属性被修改后，再调用remove（）方法时不起作用。)\n      * [3、监听器](#3、监听器)\n      * [4、各种连接](#4、各种连接)\n      * [5、内部类和外部模块等的引用](#5、内部类和外部模块等的引用)\n      * [6、单例模式](#6、单例模式)\n  * [如何检测内存泄漏](#如何检测内存泄漏)\n  * [参考文章](#参考文章)\n\n\n\n本文转自互联网，侵删\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章将同步到我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《深入理解JVM虚拟机》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，一步步地学习JVM基础知识，并上手进行JVM调优实战，JVM是每一个Java工程师必须要学习和理解的知识点，你必须要掌握其实现原理，才能更完整地了解整个Java技术体系，形成自己的知识框架。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n## 实战内存溢出异常\n\n大家好，相信大部分Javaer在code时经常会遇到本地代码运行正常，但在生产环境偶尔会莫名其妙的报一些关于内存的异常，StackOverFlowError,OutOfMemoryError异常是最常见的。今天就基于上篇文章JVM系列之Java内存结构详解讲解的各个内存区域重点实战分析下内存溢出的情况。在此之前，我还是想多余累赘一些其他关于对象的问题，具体内容如下:\n\n> 文章结构：\n> 对象的创建过程\n> 对象的内存布局\n> 对象的访问定位\n> 实战内存异常\n\n## 1 . 对象的创建过程\n\n关于对象的创建，第一反应是new关键字，那么本文就主要讲解new关键字创建对象的过程。\n\n```\nStudent stu =new Student(\"张三\"，\"18\");\n\n```\n\n就拿上面这句代码来说，虚拟机首先会去检查Student这个类有没有被加载，如果没有，首先去加载这个类到方法区，然后根据加载的Class类对象创建stu实例对象，需要注意的是，stu对象所需的内存大小在Student类加载完成后便可完全确定。内存分配完成后，虚拟机需要将分配到的内存空间的实例数据部分初始化为零值,这也就是为什么我们在编写Java代码时创建一个变量不需要初始化。紧接着，虚拟机会对对象的对象头进行必要的设置，如这个对象属于哪个类，如何找到类的元数据(Class对象),对象的锁信息，GC分代年龄等。设置完对象头信息后，调用类的构造函数。\n其实讲实话，虚拟机创建对象的过程远不止这么简单，我这里只是把大致的脉络讲解了一下，方便大家理解。\n\n## 2 . 对象的内存布局\n\n刚刚提到的实例数据，对象头，有些小伙伴也许有点陌生，这一小节就详细讲解一下对象的内存布局,对象创建完成后大致可以分为以下几个部分:\n\n*   对象头\n*   实例数据\n*   对齐填充\n\n**对象头:**对象头中包含了对象运行时一些必要的信息，如GC分代信息，锁信息，哈希码，指向Class类元信息的指针等，其中对Javaer比较有用的是**锁信息与指向Class对象的指针**，关于锁信息，后期有机会讲解并发编程JUC时再扩展，关于指向Class对象的指针其实很好理解。比如上面那个Student的例子，当我们拿到stu对象时，调用Class stuClass=stu.getClass();的时候，其实就是根据这个指针去拿到了stu对象所属的Student类在方法区存放的Class类对象。虽然说的有点拗口，但这句话我反复琢磨了好几遍，应该是说清楚了。\n\n**实例数据:**实例数据部分是对象真正存储的有效信息，就是程序代码中所定义的各种类型的字段内容。\n\n**对齐填充:**虚拟机规范要求对象大小必须是8字节的整数倍。对齐填充其实就是来补全对象大小的。\n\n## 3 . 对象的访问定位\n\n谈到对象的访问，还拿上面学生的例子来说，当我们拿到stu对象时，直接调用stu.getName();时，其实就完成了对对象的访问。但这里要累赘说一下的是，stu虽然通常被认为是一个对象，其实准确来说是不准确的，stu只是一个变量，变量里存储的是指向对象的指针，(如果干过C或者C++的小伙伴应该比较清楚指针这个概念)，当我们调用stu.getName()时，虚拟机会根据指针找到堆里面的对象然后拿到实例数据name.需要注意的是，当我们调用stu.getClass()时，虚拟机会首先根据stu指针定位到堆里面的对象，然后根据对象头里面存储的指向Class类元信息的指针再次到方法区拿到Class对象，进行了两次指针寻找。具体讲解图如下:\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404223333.png)\n\n## 4 .实战内存异常\n\n内存异常是我们工作当中经常会遇到问题，但如果仅仅会通过加大内存参数来解决问题显然是不够的，应该通过一定的手段定位问题，到底是因为参数问题，还是程序问题(无限创建，内存泄露)。定位问题后才能采取合适的解决方案，而不是一内存溢出就查找相关参数加大。\n\n> 概念\n> 内存泄露:代码中的某个对象本应该被虚拟机回收，但因为拥有GCRoot引用而没有被回收。关于GCRoot概念，下一篇文章讲解。\n> 内存溢出: 虚拟机由于堆中拥有太多不可回收对象没有回收，导致无法继续创建新对象。\n\n在分析问题之前先给大家讲一讲排查内存溢出问题的方法，内存溢出时JVM虚拟机会退出，**那么我们怎么知道JVM运行时的各种信息呢，Dump机制会帮助我们，可以通过加上VM参数-XX:+HeapDumpOnOutOfMemoryError让虚拟机在出现内存溢出异常时生成dump文件，然后通过外部工具(作者使用的是VisualVM)来具体分析异常的原因。**\n\n下面从以下几个方面来配合代码实战演示内存溢出及如何定位:\n\n*   Java堆内存异常\n*   Java栈内存异常\n*   方法区内存异常\n\n### Java堆内存异常\n\n```\n/**\n    VM Args:\n    //这两个参数保证了堆中的可分配内存固定为20M\n    -Xms20m\n    -Xmx20m  \n    //文件生成的位置，作则生成在桌面的一个目录\n    -XX:+HeapDumpOnOutOfMemoryError //文件生成的位置，作则生成在桌面的一个目录\n    //文件生成的位置，作则生成在桌面的一个目录\n    -XX:HeapDumpPath=/Users/zdy/Desktop/dump/ \n */\npublic class HeapOOM {\n    //创建一个内部类用于创建对象使用\n    static class OOMObject {\n    }\n    public static void main(String[] args) {\n        List<OOMObject> list = new ArrayList<OOMObject>();\n        //无限创建对象，在堆中\n        while (true) {\n            list.add(new OOMObject());\n        }\n    }\n}\n\n```\n\nRun起来代码后爆出异常如下:\n\njava.lang.OutOfMemoryError: Java heap space\nDumping heap to /Users/zdy/Desktop/dump/java_pid1099.hprof …\n\n可以看到生成了dump文件到指定目录。并且爆出了OutOfMemoryError，还告诉了你是哪一片区域出的问题:heap space\n\n打开VisualVM工具导入对应的heapDump文件(如何使用请读者自行查阅相关资料)，相应的说明见图:\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404223348.png)\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404223403.png)\n\n分析dump文件后，我们可以知道，OOMObject这个类创建了810326个实例。所以它能不溢出吗？接下来就在代码里找这个类在哪new的。排查问题。(我们的样例代码就不用排查了，While循环太凶猛了)分析dump文件后，我们可以知道，OOMObject这个类创建了810326个实例。所以它能不溢出吗？接下来就在代码里找这个类在哪new的。排查问题。(我们的样例代码就不用排查了，While循环太凶猛了)\n\n### Java栈内存异常\n\n老实说，在栈中出现异常(StackOverFlowError)的概率小到和去苹果专卖店买手机，买回来后发现是Android系统的概率是一样的。因为作者确实没有在生产环境中遇到过，除了自己作死写样例代码测试。先说一下异常出现的情况，前面讲到过，方法调用的过程就是方法帧进虚拟机栈和出虚拟机栈的过程，那么有两种情况可以导致StackOverFlowError,当一个方法帧(比如需要2M内存)进入到虚拟机栈(比如还剩下1M内存)的时候，就会报出StackOverFlow.这里先说一个概念，栈深度:指目前虚拟机栈中没有出栈的方法帧。虚拟机栈容量通过参数-Xss来控制,下面通过一段代码，把栈容量人为的调小一点，然后通过递归调用触发异常。\n\n```\n/**\n * VM Args：\n    //设置栈容量为160K，默认1M\n   -Xss160k\n */\npublic class JavaVMStackSOF {\n    private int stackLength = 1;\n    public void stackLeak() {\n        stackLength++;\n        //递归调用，触发异常\n        stackLeak();\n    }\n\n    public static void main(String[] args) throws Throwable {\n        JavaVMStackSOF oom = new JavaVMStackSOF();\n        try {\n            oom.stackLeak();\n        } catch (Throwable e) {\n            System.out.println(\"stack length:\" + oom.stackLength);\n            throw e;\n        }\n    }\n}\n\n```\n\n> 结果如下:\n> stack length:751 Exception in thread “main”\n> java.lang.StackOverflowError\n\n可以看到，递归调用了751次，栈容量不够用了。\n默认的栈容量在正常的方法调用时，栈深度可以达到1000-2000深度，所以，一般的递归是可以承受的住的。如果你的代码出现了StackOverflowError，首先检查代码，而不是改参数。\n\n这里顺带提一下，很多人在做多线程开发时，当创建很多线程时，**容易出现OOM(OutOfMemoryError),**这时可以通过具体情况，减少最大堆容量，或者栈容量来解决问题，这是为什么呢。请看下面的公式:\n\n<font color=\"red\">线程数*(最大栈容量)+最大堆值+其他内存(忽略不计或者一般不改动)=机器最大内存</font>\n\n当线程数比较多时，且无法通过业务上削减线程数，那么再不换机器的情况下，**你只能把最大栈容量设置小一点，或者把最大堆值设置小一点。**\n\n### 方法区内存异常\n\n写到这里时，作者本来想写一个无限创建动态代理对象的例子来演示方法区溢出，避开谈论JDK7与JDK8的内存区域变更的过渡，但细想一想，还是把这一块从始致终的说清楚。在上一篇文章中JVM系列之Java内存结构详解讲到方法区时提到，JDK7环境下方法区包括了(运行时常量池),其实这么说是不准确的。因为从JDK7开始，HotSpot团队就想到开始去\"永久代\",大家首先明确一个概念，**方法区和\"永久代\"(PermGen space)是两个概念，方法区是JVM虚拟机规范，任何虚拟机实现(J9等)都不能少这个区间，而\"永久代\"只是HotSpot对方法区的一个实现。**为了把知识点列清楚，我还是才用列表的形式:\n\n*   [ ] JDK7之前(包括JDK7)拥有\"永久代\"(PermGen space),用来实现方法区。但在JDK7中已经逐渐在实现中把永久代中把很多东西移了出来，比如:符号引用(Symbols)转移到了native heap,运行时常量池(interned strings)转移到了java heap；类的静态变量(class statics)转移到了java heap.\n*   [ ] 所以这就是为什么我说上一篇文章中说方法区中包含运行时常量池是不正确的，因为已经移动到了java heap;\n    **在JDK7之前(包括7)可以通过-XX:PermSize -XX:MaxPermSize来控制永久代的大小.\n    JDK8正式去除\"永久代\",换成Metaspace(元空间)作为JVM虚拟机规范中方法区的实现。**\n    元空间与永久代之间最大的区别在于：元空间并不在虚拟机中，而是使用本地内存。因此，默认情况下，元空间的大小仅受本地内存限制，但仍可以通过参数控制:-XX:MetaspaceSize与-XX:MaxMetaspaceSize来控制大小。\n\n### 方法区与运行时常量池OOM\n\nJava 永久代是非堆内存的组成部分，用来存放类名、访问修饰符、常量池、字段描述、方法描述等，因运行时常量池是方法区的一部分，所以这里也包含运行时常量池。我们可以通过 jvm 参数 -XX：PermSize=10M -XX：MaxPermSize=10M 来指定该区域的内存大小，-XX：PermSize 默认为物理内存的 1/64 ,-XX：MaxPermSize 默认为物理内存的 1/4 。String.intern() 方法是一个 Native 方法，它的作用是：如果字符串常量池中已经包含一个等于此 String 对象的字符串，则返回代表池中这个字符串的 String 对象；否则，将此 String 对象包含的字符串添加到常量池中，并且返回此 String 对象的引用。在 JDK 1.6 及之前的版本中，由于常量池分配在永久代内，我们可以通过 -XX：PermSize 和 -XX：MaxPermSize 限制方法区大小，从而间接限制其中常量池的容量,通过运行 java -XX：PermSize=8M -XX：MaxPermSize=8M RuntimeConstantPoolOom 下面的代码我们可以模仿一个运行时常量池内存溢出的情况：\n\n```\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class RuntimeConstantPoolOom {\n  public static void main(String[] args) {\n    List<String> list = new ArrayList<String>();\n    int i = 0;\n    while (true) {\n      list.add(String.valueOf(i++).intern());\n    }\n  }\n}\n\n```\n\n运行结果如下：\n\n```\n[root@9683817ada51 oom]# ../jdk1.6.0_45/bin/java -XX:PermSize=8m -XX:MaxPermSize=8m RuntimeConstantPoolOom\nException in thread \"main\" java.lang.OutOfMemoryError: PermGen space\n    at java.lang.String.intern(Native Method)\n    at RuntimeConstantPoolOom.main(RuntimeConstantPoolOom.java:9)\n\n```\n\n还有一种情况就是我们可以通过不停的加载class来模拟方法区内存溢出，《深入理解java虚拟机》中借助 CGLIB 这类字节码技术模拟了这个异常，我们这里使用不同的 classloader 来实现（同一个类在不同的 classloader 中是不同的），代码如下\n\n```\nimport java.io.File;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.net.URLClassLoader;\nimport java.util.HashSet;\nimport java.util.Set;\n\npublic class MethodAreaOom {\n  public static void main(String[] args) throws MalformedURLException, ClassNotFoundException {\n    Set<Class<?>> classes = new HashSet<Class<?>>();\n    URL url = new File(\"\").toURI().toURL();\n    URL[] urls = new URL[]{url};\n    while (true) {\n      ClassLoader loader = new URLClassLoader(urls);\n      Class<?> loadClass = loader.loadClass(Object.class.getName());\n      classes.add(loadClass);\n    }\n  }\n}\n\n```\n\n```\n[root@9683817ada51 oom]# ../jdk1.6.0_45/bin/java -XX:PermSize=2m -XX:MaxPermSize=2m MethodAreaOom\nError occurred during initialization of VM\njava.lang.OutOfMemoryError: PermGen space\n    at sun.net.www.ParseUtil.<clinit>(ParseUtil.java:31)\n    at sun.misc.Launcher.getFileURL(Launcher.java:476)\n    at sun.misc.Launcher$ExtClassLoader.getExtURLs(Launcher.java:187)\n    at sun.misc.Launcher$ExtClassLoader.<init>(Launcher.java:158)\n    at sun.misc.Launcher$ExtClassLoader$1.run(Launcher.java:142)\n    at java.security.AccessController.doPrivileged(Native Method)\n    at sun.misc.Launcher$ExtClassLoader.getExtClassLoader(Launcher.java:135)\n    at sun.misc.Launcher.<init>(Launcher.java:55)\n    at sun.misc.Launcher.<clinit>(Launcher.java:43)\n    at java.lang.ClassLoader.initSystemClassLoader(ClassLoader.java:1337)\n    at java.lang.ClassLoader.getSystemClassLoader(ClassLoader.java:1319)\n\n```\n\n在 jdk1.8 上运行上面的代码将不会出现异常，因为 jdk1.8 已结去掉了永久代，当然 -XX:PermSize=2m -XX:MaxPermSize=2m 也将被忽略，如下\n\n```\n[root@9683817ada51 oom]# java -XX:PermSize=2m -XX:MaxPermSize=2m MethodAreaOom\nJava HotSpot(TM) 64-Bit Server VM warning: ignoring option PermSize=2m; support was removed in 8.0\nJava HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=2m; support was removed in 8.0\n\n```\n\njdk1.8 使用元空间（ Metaspace ）替代了永久代（ PermSize ），因此我们可以在 1.8 中指定 Metaspace 的大小模拟上述情况\n\n```\n[root@9683817ada51 oom]# java -XX:MetaspaceSize=2m -XX:MaxMetaspaceSize=2m RuntimeConstantPoolOom\nError occurred during initialization of VM\njava.lang.OutOfMemoryError: Metaspace\n    <<no stack trace available>>\n\n```\n\n在JDK8的环境下将报出异常:\nException in thread “main” java.lang.OutOfMemoryError: Metaspace\n这是因为在调用CGLib的创建代理时会生成动态代理类，即Class对象到Metaspace,所以While一下就出异常了。\n**提醒一下:虽然我们日常叫\"堆Dump\",但是dump技术不仅仅是对于\"堆\"区域才有效，而是针对OOM的，也就是说不管什么区域，凡是能够报出OOM错误的，都可以使用dump技术生成dump文件来分析。**\n\n在经常动态生成大量Class的应用中，需要特别注意类的回收状况，这类场景除了例子中的CGLib技术，常见的还有，大量JSP，反射，OSGI等。需要特别注意，当出现此类异常，应该知道是哪里出了问题，然后看是调整参数，还是在代码层面优化。\n\n### 附加-直接内存异常\n\n直接内存异常非常少见，而且机制很特殊，因为直接内存不是直接向操作系统分配内存，而且通过计算得到的内存不够而手动抛出异常，所以当你发现你的dump文件很小，而且没有明显异常，只是告诉你OOM，你就可以考虑下你代码里面是不是直接或者间接使用了NIO而导致直接内存溢出。\n\n## Java内存泄漏\n\nJava的一个重要优点就是通过垃圾收集器(Garbage Collection，GC)自动管理内存的回收，程序员不需要通过调用函数来释放内存。因此，很多程序员认为Java不存在内存泄漏问题，或者认为即使有内存泄漏也不是程序的责任，而是GC或JVM的问题。其实，这种想法是不正确的，因为Java也存在内存泄露，但它的表现与C++不同。\n\n随着越来越多的服务器程序采用Java技术，例如JSP，Servlet， EJB等，服务器程序往往长期运行。另外，在很多嵌入式系统中，内存的总量非常有限。内存泄露问题也就变得十分关键，即使每次运行少量泄漏，长期运行之后，系统也是面临崩溃的危险。\n\n## Java是如何管理内存？\n\n为了判断Java中是否有内存泄露，我们首先必须了解Java是如何管理内存的。Java的内存管理就是对象的分配和释放问题。在Java中，程序员需要通过关键字new为每个对象申请内存空间 (基本类型除外)，所有的对象都在堆 (Heap)中分配空间。另外，对象的释放是由GC决定和执行的。在Java中，内存的分配是由程序完成的，而内存的释放是有GC完成的，这种收支两条线的方法确实简化了程序员的工作。但同时，它也加重了JVM的工作。这也是Java程序运行速度较慢的原因之一。因为，GC为了能够正确释放对象，GC必须监控每一个对象的运行状态，包括对象的申请、引用、被引用、赋值等，GC都需要进行监控。\n\n监视对象状态是为了更加准确地、及时地释放对象，而释放对象的根本原则就是该对象不再被引用。\n\n为了更好理解GC的工作原理，我们可以将对象考虑为有向图的顶点，将引用关系考虑为图的有向边，有向边从引用者指向被引对象。另外，每个线程对象可以作为一个图的起始顶点，例如大多程序从main进程开始执行，那么该图就是以main进程顶点开始的一棵根树。在这个有向图中，根顶点可达的对象都是有效对象，GC将不回收这些对象。如果某个对象 (连通子图)与这个根顶点不可达(注意，该图为有向图)，那么我们认为这个(这些)对象不再被引用，可以被GC回收。\n\n以下，我们举一个例子说明如何用有向图表示内存管理。对于程序的每一个时刻，我们都有一个有向图表示JVM的内存分配情况。以下右图，就是左边程序运行到第6行的示意图。\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404223428.png)\n\nJava使用有向图的方式进行内存管理，可以消除引用循环的问题，例如有三个对象，相互引用，只要它们和根进程不可达的，那么GC也是可以回收它们的。这种方式的优点是管理内存的精度很高，但是效率较低。另外一种常用的内存管理技术是使用计数器，例如COM模型采用计数器方式管理构件，它与有向图相比，精度行低(很难处理循环引用的问题)，但执行效率很高。\n\n## 什么是Java中的内存泄露？\n\n下面，我们就可以描述什么是内存泄漏。在Java中，内存泄漏就是存在一些被分配的对象，这些对象有下面两个特点，首先，这些对象是可达的，即在有向图中，存在通路可以与其相连；其次，这些对象是无用的，即程序以后不会再使用这些对象。如果对象满足这两个条件，这些对象就可以判定为Java中的内存泄漏，这些对象不会被GC所回收，然而它却占用内存。\n\n在C++中，内存泄漏的范围更大一些。有些对象被分配了内存空间，然后却不可达，由于C++中没有GC，这些内存将永远收不回来。在Java中，这些不可达的对象都由GC负责回收，因此程序员不需要考虑这部分的内存泄露。\n\n通过分析，我们得知，对于C++，程序员需要自己管理边和顶点，而对于Java程序员只需要管理边就可以了(不需要管理顶点的释放)。通过这种方式，Java提高了编程的效率。\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404223441.png)\n\n因此，通过以上分析，我们知道在Java中也有内存泄漏，但范围比C++要小一些。因为Java从语言上保证，任何对象都是可达的，所有的不可达对象都由GC管理。\n\n对于程序员来说，GC基本是透明的，不可见的。虽然，我们只有几个函数可以访问GC，例如运行GC的函数System.gc()，但是根据Java语言规范定义， 该函数不保证JVM的垃圾收集器一定会执行。因为，不同的JVM实现者可能使用不同的算法管理GC。通常，GC的线程的优先级别较低。JVM调用GC的策略也有很多种，有的是内存使用到达一定程度时，GC才开始工作，也有定时执行的，有的是平缓执行GC，有的是中断式执行GC。但通常来说，我们不需要关心这些。除非在一些特定的场合，GC的执行影响应用程序的性能，例如对于基于Web的实时系统，如网络游戏等，用户不希望GC突然中断应用程序执行而进行垃圾回收，那么我们需要调整GC的参数，让GC能够通过平缓的方式释放内存，例如将垃圾回收分解为一系列的小步骤执行，Sun提供的HotSpot JVM就支持这一特性。\n\n下面给出了一个简单的内存泄露的例子。在这个例子中，我们循环申请Object对象，并将所申请的对象放入一个Vector中，如果我们仅仅释放引用本身，那么Vector仍然引用该对象，所以这个对象对GC来说是不可回收的。因此，如果对象加入到Vector后，还必须从Vector中删除，最简单的方法就是将Vector对象设置为null。\n\n```\nVector v=new Vector(10);\nfor (int i=1;i<100; i++)\n{\n    Object o=new Object();\n    v.add(o);\n    o=null;\n}\n//此时，所有的Object对象都没有被释放，因为变量v引用这些对象\n\n```\n\n### 其他常见内存泄漏\n\n#### 1、静态集合类引起内存泄露：\n\n像HashMap、Vector等的使用最容易出现内存泄露，这些静态变量的生命周期和应用程序一致，他们所引用的所有的对象Object也不能被释放，因为他们也将一直被Vector等引用着。\n例:\n\n```\nStatic Vector v = new Vector(10); \nfor (int i = 1; i<100; i++) { \n\tObject o = new Object(); \n\tv.add(o); \n\to = null; \n}// \n在这个例子中，循环申请Object 对象，并将所申请的对象放入一个Vector 中，如果仅仅释放引用本身（o=null），那么Vector 仍然引用该对象，所以这个对象对GC 来说是不可回收的。因此，如果对象加入到Vector 后，还必须从Vector 中删除，最简单的方法就是将Vector对象设置为null。\n\n```\n\n#### 2、当集合里面的对象属性被修改后，再调用remove（）方法时不起作用。\n\n例：\n\n```\npublic static void main(String[] args) { \n\tSet<Person> set = new HashSet<Person>(); \n\tPerson p1 = new Person(\"唐僧\",\"pwd1\",25); \n\tPerson p2 = new Person(\"孙悟空\",\"pwd2\",26); \n\tPerson p3 = new Person(\"猪八戒\",\"pwd3\",27); \n\tset.add(p1); \n\tset.add(p2); \n\tset.add(p3); \n\tSystem.out.println(\"总共有:\"+set.size()+\" 个元素!\"); //结果：总共有:3 个元素! \n\tp3.setAge(2); //修改p3的年龄,此时p3元素对应的hashcode值发生改变 \n\n\tset.remove(p3); //此时remove不掉，造成内存泄漏\n\tset.add(p3); //重新添加，居然添加成功 \n\tSystem.out.println(\"总共有:\"+set.size()+\" 个元素!\"); //结果：总共有:4 个元素! \n\tfor (Person person : set) { \n\t\tSystem.out.println(person); \n\t} \n}\n\n```\n\n#### 3、监听器\n\n在java 编程中，我们都需要和监听器打交道，通常一个应用当中会用到很多监听器，我们会调用一个控件的诸如addXXXListener()等方法来增加监听器，但往往在释放对象的时候却没有记住去删除这些监听器，从而增加了内存泄漏的机会。\n\n#### 4、各种连接\n\n比如数据库连接（dataSourse.getConnection()），网络连接(socket)和io连接，除非其显式的调用了其close（）方法将其连接关闭，否则是不会自动被GC 回收的。对于Resultset 和Statement 对象可以不进行显式回收，但Connection 一定要显式回收，因为Connection 在任何时候都无法自动回收，而Connection一旦回收，Resultset 和Statement 对象就会立即为NULL。但是如果使用连接池，情况就不一样了，除了要显式地关闭连接，还必须显式地关闭Resultset Statement 对象（关闭其中一个，另外一个也会关闭），否则就会造成大量的Statement 对象无法释放，从而引起内存泄漏。这种情况下一般都会在try里面去的连接，在finally里面释放连接。\n\n#### 5、内部类和外部模块等的引用\n\n内部类的引用是比较容易遗忘的一种，而且一旦没释放可能导致一系列的后继类对象没有释放。此外程序员还要小心外部模块不经意的引用，例如程序员A 负责A 模块，调用了B 模块的一个方法如：\npublic void registerMsg(Object b);\n这种调用就要非常小心了，传入了一个对象，很可能模块B就保持了对该对象的引用，这时候就需要注意模块B 是否提供相应的操作去除引用。\n\n#### 6、单例模式\n\n不正确使用单例模式是引起内存泄露的一个常见问题，单例对象在被初始化后将在JVM的整个生命周期中存在（以静态变量的方式），如果单例对象持有外部对象的引用，那么这个外部对象将不能被jvm正常回收，导致内存泄露，考虑下面的例子：\n\n```\nclass A{ \n\tpublic A(){ \n\t\tB.getInstance().setA(this); \n\t} \n.... \n} \n//B类采用单例模式 \nclass B{ \n\tprivate A a; \n\tprivate static B instance=new B(); \n\tpublic B(){} \n\tpublic static B getInstance(){ \n\t\treturn instance; \n\t} \n\tpublic void setA(A a){ \n\t\tthis.a=a; \n\t} \n\t//getter... \n} \n\n```\n\n**显然B采用singleton模式，它持有一个A对象的引用，而这个A类的对象将不能被回收。想象下如果A是个比较复杂的对象或者集合类型会发生什么情况。**\n\n## 如何检测内存泄漏\n\n最后一个重要的问题，就是如何检测Java的内存泄漏。目前，我们通常使用一些工具来检查Java程序的内存泄漏问题。市场上已有几种专业检查Java内存泄漏的工具，它们的基本工作原理大同小异，都是通过监测Java程序运行时，所有对象的申请、释放等动作，将内存管理的所有信息进行统计、分析、可视化。开发人员将根据这些信息判断程序是否有内存泄漏问题。这些工具包括Optimizeit Profiler，JProbe Profiler，JinSight , Rational 公司的Purify等。\n\n下面，我们将简单介绍Optimizeit的基本功能和工作原理。\n\nOptimizeit Profiler版本4.11支持Application，Applet，Servlet和Romote Application四类应用，并且可以支持大多数类型的JVM，包括SUN JDK系列，IBM的JDK系列，和Jbuilder的JVM等。并且，该软件是由Java编写，因此它支持多种操作系统。Optimizeit系列还包括Thread Debugger和Code Coverage两个工具，分别用于监测运行时的线程状态和代码覆盖面。\n\n当设置好所有的参数了，我们就可以在OptimizeIt环境下运行被测程序，在程序运行过程中，Optimizeit可以监视内存的使用曲线(如下图)，包括JVM申请的堆(heap)的大小，和实际使用的内存大小。另外，在运行过程中，我们可以随时暂停程序的运行，甚至强行调用GC，让GC进行内存回收。通过内存使用曲线，我们可以整体了解程序使用内存的情况。这种监测对于长期运行的应用程序非常有必要，也很容易发现内存泄露。\n\n\n## 参考文章\n\n<https://segmentfault.com/a/1190000009707894>\n\n<https://www.cnblogs.com/hysum/p/7100874.html>\n\n<http://c.biancheng.net/view/939.html>\n\n<https://www.runoob.com/>\n\nhttps://blog.csdn.net/android_hl/article/details/53228348\n\n\n"
  },
  {
    "path": "docs/Java/JVM/深入理解JVM虚拟机：Java字节码介绍与解析实践.md",
    "content": "# 目录\n  * [前言](#前言)\n  * [Class文件](#class文件)\n    * [什么是Class文件？](#什么是class文件？)\n    * [基本结构](#基本结构)\n  * [解析](#解析)\n    * [字段类型](#字段类型)\n    * [常量池](#常量池)\n    * [字节码指令](#字节码指令)\n  * [运行](#运行)\n  * [总结](#总结)\n  * [参考：](#参考：)\n\n\n本文转自：https://juejin.im/post/589834a20ce4630056097a56\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章将同步到我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《深入理解JVM虚拟机》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，一步步地学习JVM基础知识，并上手进行JVM调优实战，JVM是每一个Java工程师必须要学习和理解的知识点，你必须要掌握其实现原理，才能更完整地了解整个Java技术体系，形成自己的知识框架。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n## 前言\n\n身为一个java程序员，怎么能不了解JVM呢，倘若想学习JVM，那就又必须要了解Class文件，Class之于虚拟机，就如鱼之于水，虚拟机因为Class而有了生命。《深入理解java虚拟机》中花了一整个章节来讲解Class文件，可是看完后，一直都还是迷迷糊糊，似懂非懂。正好前段时间看见一本书很不错：《自己动手写Java虚拟机》，作者利用go语言实现了一个简单的JVM，虽然没有完整实现JVM的所有功能，但是对于一些对JVM稍感兴趣的人来说，可读性还是很高的。作者讲解的很详细，每个过程都分为了一章，其中一部分就是讲解如何解析Class文件。\n\n这本书不太厚，很快就读完了，读完后，收获颇丰。但是纸上得来终觉浅，绝知此事要躬行，我便尝试着自己解析Class文件。go语言虽然很优秀，但是终究不熟练，尤其是不太习惯其把类型放在变量之后的语法，还是老老实实用java吧。\n\n**话不多说，先贴出项目地址：[github.com/HalfStackDe…](https://github.com/HalfStackDeveloper/ClassReader)**\n\n## Class文件\n\n### 什么是Class文件？\n\njava之所以能够实现跨平台，便在于其编译阶段不是将代码直接编译为平台相关的机器语言，而是先编译成二进制形式的java字节码，放在Class文件之中，虚拟机再加载Class文件，解析出程序运行所需的内容。每个类都会被编译成一个单独的class文件，内部类也会作为一个独立的类，生成自己的class。\n\n### 基本结构\n\n随便找到一个class文件，用Sublime Text打开是这样的：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404220426.png)\n\n是不是一脸懵逼，不过java虚拟机规范中给出了class文件的基本格式，只要按照这个格式去解析就可以了：\n\n```\nClassFile {\n    u4 magic;\n       u2 minor_version;\n       u2 major_version;\n       u2 constant_pool_count;\n       cp_info constant_pool[constant_pool_count-1];\n       u2 access_flags;\n       u2 this_class;\n       u2 super_class;\n       u2 interfaces_count;\n       u2 interfaces[interfaces_count];\n       u2 fields_count;\n       field_info fields[fields_count];\n       u2 methods_count;\n      method_info methods[methods_count];\n       u2 attributes_count;\n       attribute_info attributes[attributes_count];\n}\n```\n\nClassFile中的字段类型有u1、u2、u4,这是什么类型呢？其实很简单，就是分别表示1个字节，2个字节和4个字节。\n\n开头四个字节为：magic，是用来唯一标识文件格式的，一般被称作magic number（魔数），这样虚拟机才能识别出所加载的文件是否是class格式，class文件的魔数为cafebabe。不只是class文件，基本上大部分文件都有魔数，用来标识自己的格式。\n\n接下来的部分主要是class文件的一些信息，如常量池、类访问标志、父类、接口信息、字段、方法等，具体的信息可参考《Java虚拟机规范》。\n\n## 解析\n\n### 字段类型\n\n上面说到ClassFile中的字段类型有u1、u2、u4，分别表示1个字节，2个字节和4个字节的无符号整数。java中short、int、long分别为2、4、8个字节的有符号整数，去掉符号位，刚好可以用来表示u1、u2、u4。\n\n```\npublic class U1 {\n    public static short read(InputStream inputStream) {\n        byte[] bytes = new byte[1];\n        try {\n            inputStream.read(bytes);\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n        short value = (short) (bytes[0] & 0xFF);\n        return value;\n    }\n}\n\npublic class U2 {\n    public static int read(InputStream inputStream) {\n        byte[] bytes = new byte[2];\n        try {\n            inputStream.read(bytes);\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n        int num = 0;\n        for (int i= 0; i < bytes.length; i++) {\n            num <<= 8;\n            num |= (bytes[i] & 0xff);\n        }\n        return num;\n    }\n}                                                                                                                                                                                   \n\npublic class U4 {\n    public static long read(InputStream inputStream) {\n        byte[] bytes = new byte[4];\n        try {\n            inputStream.read(bytes);\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n        long num = 0;\n        for (int i= 0; i < bytes.length; i++) {\n            num <<= 8;\n            num |= (bytes[i] & 0xff);\n        }\n        return num;\n    }\n}\n```\n\n### 常量池\n\n定义好字段类型后，我们就可以读取class文件了，首先是读取魔数之类的基本信息，这部分很简单：\n\n```\nFileInputStream inputStream = new FileInputStream(file);\nClassFile classFile = new ClassFile();\nclassFile.magic = U4.read(inputStream);\nclassFile.minorVersion = U2.read(inputStream);\nclassFile.majorVersion = U2.read(inputStream);\n```\n\n这部分只是热热身，接下来的大头在于常量池。解析常量池之前，我们先来解释一下常量池是什么。\n\n常量池，顾名思义，存放常量的资源池，这里的常量指的是字面量和符号引用。字面量指的是一些字符串资源，而符号引用分为三类：类符号引用、方法符号引用和字段符号引用。通过将资源放在常量池中，其他项就可以直接定义成常量池中的索引了，避免了空间的浪费，不只是class文件，Android可执行文件dex也是同样如此，将字符串资源等放在DexData中，其他项通过索引定位资源。java虚拟机规范给出了常量池中每一项的格式：\n\n```\ncp_info {\n    u1 tag;\n    u1 info[]; \n}\n```\n\n\n由于格式太多，文章中只挑选一部分讲解：\n\n这里首先读取常量池的大小，初始化常量池：\n\n```\n//解析常量池\nint constant_pool_count = U2.read(inputStream);\nConstantPool constantPool = new ConstantPool(constant_pool_count);\nconstantPool.read(inputStream);\n```\n\n接下来再逐个读取每项内容，并存储到数组cpInfo中，这里需要注意的是，cpInfo[]下标从1开始，0无效，且真正的常量池大小为constant_pool_count-1。\n\n```\npublic class ConstantPool {\n    public int constant_pool_count;\n    public ConstantInfo[] cpInfo;\n\n    public ConstantPool(int count) {\n        constant_pool_count = count;\n        cpInfo = new ConstantInfo[constant_pool_count];\n    }\n\n    public void read(InputStream inputStream) {\n        for (int i = 1; i < constant_pool_count; i++) {\n            short tag = U1.read(inputStream);\n            ConstantInfo constantInfo = ConstantInfo.getConstantInfo(tag);\n            constantInfo.read(inputStream);\n            cpInfo[i] = constantInfo;\n            if (tag == ConstantInfo.CONSTANT_Double || tag == ConstantInfo.CONSTANT_Long) {\n                i++;\n            }\n        }\n    }\n}\n```\n\n我们先来看看CONSTANT_Utf8格式，这一项里面存放的是MUTF-8编码的字符串：\n\n```\nCONSTANT_Utf8_info { \n    u1 tag;\n    u2 length;\n    u1 bytes[length]; \n}\n```\n\n那么如何读取这一项呢？\n\n```\npublic class ConstantUtf8 extends ConstantInfo {\n    public String value;\n\n    @Override\n    public void read(InputStream inputStream) {\n        int length = U2.read(inputStream);\n        byte[] bytes = new byte[length];\n        try {\n            inputStream.read(bytes);\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n        try {\n            value = readUtf8(bytes);\n        } catch (UTFDataFormatException e) {\n            e.printStackTrace();\n        }\n    }\n\n    private String readUtf8(byte[] bytearr) throws UTFDataFormatException {\n        //copy from java.io.DataInputStream.readUTF()\n    }\n}\n```\n\n很简单，首先读取这一项的字节数组长度，接着调用readUtf8(),将字节数组转化为String字符串。\n\n再来看看CONSTANT_Class这一项，这一项存储的是类或者接口的符号引用：\n\n```\nCONSTANT_Class_info {\n    u1 tag;\n    u2 name_index;\n}\n```\n\n注意这里的name_index并不是直接的字符串，而是指向常量池中cpInfo数组的name_index项，且cpInfo[name_index]一定是CONSTANT_Utf8格式。\n\n```\npublic class ConstantClass extends ConstantInfo {\n    public int nameIndex;\n\n    @Override\n    public void read(InputStream inputStream) {\n        nameIndex = U2.read(inputStream);\n    }\n}\n```\n\n常量池解析完毕后，就可以供后面的数据使用了，比方说ClassFile中的this_class指向的就是常量池中格式为CONSTANT_Class的某一项,那么我们就可以读取出类名：\n\n```\nint classIndex = U2.read(inputStream);\nConstantClass clazz = (ConstantClass) constantPool.cpInfo[classIndex];\nConstantUtf8 className = (ConstantUtf8) constantPool.cpInfo[clazz.nameIndex];\nclassFile.className = className.value;\nSystem.out.print(\"classname:\" + classFile.className + \"\\n\");\n```\n\n### 字节码指令\n\n解析常量池之后还需要接着解析一些类信息，如父类、接口类、字段等，但是相信大家最好奇的还是java指令的存储，大家都知道，我们平时写的java代码会被编译成java字节码，那么这些字节码到底存储在哪呢？别急，讲解指令之前，我们先来了解下ClassFile中的method_info，其格式如下：\n\n```\nmethod_info {\n    u2 access_flags;\n    u2 name_index;\n    u2 descriptor_index;\n    u2 attributes_count;\n    attribute_info attributes[attributes_count];\n}\n```\n\nmethod_info里主要是一些方法信息：如访问标志、方法名索引、方法描述符索引及属性数组。这里要强调的是属性数组，因为字节码指令就存储在这个属性数组里。属性有很多种，比如说异常表就是一个属性，而存储字节码指令的属性为CODE属性，看这名字也知道是用来存储代码的了。属性的通用格式为：\n\n```\nattribute_info {\n    u2 attribute_name_index;\n    u4 attribute_length;\n    u1 info[attribute_length];\n}\n```\n\n根据attribute_name_index可以从常量池中拿到属性名，再根据属性名就可以判断属性种类了。\n\nCode属性的具体格式为：\n\n```\nCode_attribute {\n    u2 attribute_name_index; u4 attribute_length;\n    u2 max_stack;\n    u2 max_locals;\n    u4 code_length;\n    u1 code[code_length];\n    u2 exception_table_length; \n    {\n        u2 start_pc;\n        u2 end_pc;\n        u2 handler_pc;\n        u2 catch_type;\n    } exception_table[exception_table_length];\n    u2 attributes_count;\n    attribute_info attributes[attributes_count];\n}\n```\n\n其中code数组里存储就是字节码指令，那么如何解析呢？每条指令在code[]中都是一个字节，我们平时javap命令反编译看到的指令其实是助记符，只是方便阅读字节码使用的，jvm有一张字节码与助记符的对照表，根据对照表，就可以将指令翻译为可读的助记符了。这里我也是在网上随便找了一个对照表，保存到本地txt文件中，并在使用时解析成HashMap。代码很简单，就不贴了，可以参考我代码中InstructionTable.java。\n\n接下来我们就可以解析字节码了：\n\n```\nfor (int j = 0; j < methodInfo.attributesCount; j++) {\n    if (methodInfo.attributes[j] instanceof CodeAttribute) {\n        CodeAttribute codeAttribute = (CodeAttribute) methodInfo.attributes[j];\n        for (int m = 0; m < codeAttribute.codeLength; m++) {\n            short code = codeAttribute.code[m];\n            System.out.print(InstructionTable.getInstruction(code) + \"\\n\");\n        }\n    }\n}\n```\n\n## 运行\n\n整个项目终于写完了，接下来就来看看效果如何，随便找一个class文件解析运行：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404220510.png)\n\n哈哈，是不是很赞！\n\n**最后再贴一下项目地址：[github.com/HalfStackDe…](https://github.com/HalfStackDeveloper/ClassReader)，欢迎Fork And Star！**\n\n## 总结\n\nClass文件看起来很复杂，其实真正解析起来，也没有那么难，关键是要自己动手试试，才能彻底理解，希望各位看完后也能觉知此事要躬行！\n\n## 参考：\n\n[1\\. 周志明《java虚拟机规范（JavaSE7）》](https://book.douban.com/subject/25792515/)\n\n[2\\. 张秀宏《自己动手写Java虚拟机》](https://book.douban.com/subject/26802084/)\n\n[3\\. 周志明《深入理解Java虚拟机（第2版）》](https://book.douban.com/subject/26802084/)\n\n**（如有错误，欢迎指正！）**\n\n**(转载请标明ID：半栈工程师，个人博客：[halfstackdeveloper.github.io](https://halfstackdeveloper.github.io/))**\n\n"
  },
  {
    "path": "docs/Java/JVM/深入理解JVM虚拟机：Java的编译期优化与运行期优化.md",
    "content": "# 目录\n  * [java编译期优化](#[java编译期优化])\n    * [早期（编译期）优化](#早期（编译期）优化)\n        * [泛型与类型擦除](#泛型与类型擦除)\n        * [自动装箱、拆箱与遍历循环](#自动装箱、拆箱与遍历循环)\n        * [条件编译](#条件编译)\n    * [晚期（运行期）优化](#晚期（运行期）优化)\n        * [解释器与编译器](#解释器与编译器)\n        * [分层编译策略](#分层编译策略)\n        * [热点代码探测](#热点代码探测)\n        * [编译优化技术](#编译优化技术)\n    * [java与C/C++编译器对比](#java与cc编译器对比)\n  * [参考文章](#参考文章)\n\n\n\n本文转自互联网，侵删\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章将同步到我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《深入理解JVM虚拟机》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，一步步地学习JVM基础知识，并上手进行JVM调优实战，JVM是每一个Java工程师必须要学习和理解的知识点，你必须要掌握其实现原理，才能更完整地了解整个Java技术体系，形成自己的知识框架。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n## java编译期优化\n\njava语言的编译期其实是一段不确定的操作过程，因为它可以分为三类编译过程：\n1.前端编译：把_.java文件转变为_.class文件\n2.后端编译：把字节码转变为机器码\n3.静态提前编译：直接把*.java文件编译成本地机器代码\n从JDK1.3开始，虚拟机设计团队就把对性能的优化集中到了后端的即时编译中，这样可以让那些不是由Javac产生的Class文件（如JRuby、Groovy等语言的Class文件）也能享受到编译期优化所带来的好处\n**Java中即时编译在运行期的优化过程对于程序运行来说更重要，而前端编译期在编译期的优化过程对于程序编码来说关系更加密切**\n\n### 早期（编译期）优化\n\n```\n早期编译过程主要分为3个部分：1.解析与填充符号表过程：词法、语法分析；填充符号表  2.插入式注解处理器的注解处理过程  3.语义分析与字节码生成过程：标注检查、数据与控制流分析、解语法糖、字节码生成\n```\n\n##### 泛型与类型擦除\n\nJava语言中的泛型只在程序源码中存在，在编译后的字节码文件中，就已经替换成原来的原生类型了，并且在相应的地方插入了强制转型代码\n\n```\n泛型擦除前的例子    \npublic static void main( String[] args )\n{\n    Map<String,String> map = new HashMap<String, String>();\n    map.put(\"hello\",\"你好\");\n    System.out.println(map.get(\"hello\"));\n}\n \n泛型擦除后的例子    \npublic static void main( String[] args )\n{\n    Map map = new HashMap();\n    map.put(\"hello\",\"你好\");\n    System.out.println((String)map.get(\"hello\"));\n}\n```\n\n##### 自动装箱、拆箱与遍历循环\n\n自动装箱、拆箱在编译之后会被转化成对应的包装和还原方法，如Integer.valueOf()与Integer.intValue()，而遍历循环则把代码还原成了迭代器的实现，变长参数会变成数组类型的参数。\n**然而包装类的“==”运算在不遇到算术运算的情况下不会自动拆箱，以及它们的equals()方法不处理数据转型的关系。**\n\n##### 条件编译\n\nJava语言也可以进行条件编译，方法就是使用条件为常量的if语句，它在编译阶段就会被“运行”：\n\n```\npublic static void main(String[] args) {\n    if(true){\n        System.out.println(\"block 1\");\n    }\n    else{\n        System.out.println(\"block 2\");\n    }\n}\n \n编译后Class文件的反编译结果：\npublic static void main(String[] args) {\n    System.out.println(\"block 1\");\n}\n```\n\n**只能是条件为常量的if语句，这也是Java语言的语法糖，根据布尔常量值的真假，编译器会把分支中不成立的代码块消除掉**\n\n### 晚期（运行期）优化\n\n##### 解释器与编译器\n\nJava程序最初是通过解释器进行解释执行的，当程序需要迅速启动和执行时，解释器可以首先发挥作用，省去编译时间，立即执行；当程序运行后，随着时间的推移，编译期逐渐发挥作用，把越来越多的代码编译成本地代码，获得更高的执行效率。**解释执行节约内存，编译执行提升效率。**同时，解释器可以作为编译器激进优化时的一个“逃生门”，让编译器根据概率选择一些大多数时候都能提升运行速度的优化手段，当激进优化的假设不成立，则通过逆优化退回到解释状态继续执行。\n\nHotSpot虚拟机中内置了两个即时编译器，分别称为Client Compiler(C1编译器)和Server Compiler(C2编译器)，默认采用解释器与其中一个编译器直接配合的方式工作，使用哪个编译器取决于虚拟机运行的模式，也可以自己去指定。若强制虚拟机运行与“解释模式”，编译器完全不介入工作，若强制虚拟机运行于“编译模式”，则优先采用编译方式执行程序，解释器仍然要在编译无法进行的情况下介入执行过程。\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404222130.png)\n##### 分层编译策略\n\n```\n分层编译策略作为默认编译策略在JDK1.7的Server模式虚拟机中被开启，其中包括：\n第0层：程序解释执行，解释器不开启性能监控功能，可触发第1层编译；\n第1层：C1编译，将字节码编译成本地代码，进行简单可靠的优化，如有必要将加入性能监控的逻辑；\n第2层：C2编译，也是将字节码编译成本地代码，但是会启动一些编译耗时较长的优化，甚至会根据性能监控信息进行一些不可靠的激进优化。\n实施分层编译后，C1和C2将会同时工作，C1获取更高的编译速度，C2获取更好的编译质量，在解释执行的时候也无须再承担性能监控信息的任务。  \n```\n\n##### 热点代码探测\n\n```\n在运行过程中会被即时编译器编译的“热点代码”有两类：\n1.被多次调用的方法：由方法调用触发的编译，属于JIT编译方式\n2.被多次执行的循环体：也以整个方法作为编译对象，因为编译发生在方法执行过程中，因此成为栈上替换（OSR编译）\n \n热点探测判定方式有两种：\n1.基于采样的热点探测：虚拟机周期性的检查各个线程的栈顶，如果某个方法经常出现在栈顶，则判定为“热点方法”。（简单高效，可以获取方法的调用关系，但容易受线程阻塞或别的外界因素影响扰乱热点探测）\n2.基于计数的热点探测：虚拟机为每个方法建立一个计数器，统计方法的执行次数，超过一定阈值就是“热点方法”。（需要为每个方法维护计数器，不能直接获取方法的调用关系，但是统计结果精确严谨）  \n```\n\nHotSpot虚拟机使用的是第二种，它为每个方法准备了两类计数器：方法调用计数器和回边计数器，下图表示方法调用计数器触发即时编译：\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404222225.png)\n如果不做任何设置，执行引擎会继续进入解释器按照解释方式执行字节码，直到提交的请求被编译器编译完成，下次调用才会使用已编译的版本。另外，方法调用计数器的值也不是一个绝对次数，而是一段时间之内被调用的次数，超过这个时间，次数就减半，这称为计数器热度的衰减。\n\n下图表示回边计数器触发即时编译：\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404222324.png)\n回边计数器没有计数器热度衰减的过程，因此统计的就是绝对次数，并且当计数器溢出时，它还会把方法计数器的值也调整到溢出状态，这样下次进入该方法的时候就会执行标准编译过程。\n\n##### 编译优化技术\n\n虚拟机设计团队几乎把对代码的所有优化措施都集中在了即时编译器之中，那么在编译器编译的过程中，到底做了些什么事情呢？下面将介绍几种最有代表性的优化技术：\n**公共子表达式消除**\n如果一个表达式E已经计算过了，并且先前的计算到现在E中所有变量的值都没有发生变化，那么E的这次出现就成为了公共表达式，可以直接用之前的结果替换。\n例：int d = (c * b) * 12 + a + (a + b * c) => int d = E * 12 + a + (a + E)\n\n**数组边界检查消除**\nJava语言中访问数组元素都要进行上下界的范围检查，每次读写都有一次条件判定操作，这无疑是一种负担。编译器只要通过数据流分析就可以判定循环变量的取值范围永远在数组长度以内，那么整个循环中就可以把上下界检查消除，这样可以省很多次的条件判断操作。\n\n另一种方法叫做隐式异常处理，Java中空指针的判断和算术运算中除数为0的检查都采用了这个思路：\n\n```\nif(foo != null){\n    return foo.value;\n}else{\n    throw new NullPointException();\n}\n \n使用隐式异常优化以后：\ntry{\n    return foo.value;\n}catch(segment_fault){\n    uncommon_trap();\n}\n当foo极少为空时，隐式异常优化是值得的，但是foo经常为空，这样的优化反而会让程序变慢，而HotSpot虚拟机会根据运行期收集到的Profile信息自动选择最优方案。\n```\n\n**方法内联**\n方法内联能去除方法调用的成本，同时也为其他优化建立了良好的基础，因此各种编译器一般会把内联优化放在优化序列的最靠前位置，然而由于Java对象的方法默认都是虚方法，因此方法调用都需要在运行时进行多态选择，为了解决虚方法的内联问题，首先引入了“类型继承关系分析（CHA）”的技术。\n\n```\n1.在内联时，若是非虚方法，则可以直接内联  \n2.遇到虚方法，首先根据CHA判断此方法是否有多个目标版本，若只有一个，可以直接内联，但是需要预留一个“逃生门”，称为守护内联，若在程序的后续执行过程中，加载了导致继承关系发生变化的新类，就需要抛弃已经编译的代码，退回到解释状态执行，或者重新编译。\n3.若CHA判断此方法有多个目标版本，则编译器会使用“内联缓存”，第一次调用缓存记录下方法接收者的版本信息，并且每次调用都比较版本，若一致则可以一直使用，若不一致则取消内联，查找虚方法表进行方法分派。\n```\n\n**逃逸分析**\n逃逸分析的基本行为就是分析对象动态作用域，当一个对象被外部方法所引用，称为方法逃逸；当被外部线程访问，称为线程逃逸。若能证明一个对象不会被外部方法或进程引用，则可以为这个变量进行一些优化：\n\n```\n1.栈上分配：如果确定一个对象不会逃逸，则可以让它分配在栈上，对象所占用的内存空间就可以随栈帧出栈而销毁。这样可以减小垃圾收集系统的压力。  \n2.同步消除：线程同步相对耗时，如果确定一个变量不会逃逸出线程，那这个变量的读写不会有竞争，则对这个变量实施的同步措施也就可以消除掉。  \n3.标量替换：如果逃逸分析证明一个对象不会被外部访问，并且这个对象可以被拆散的话，那么程序真正执行的时候可以不创建这个对象，改为直接创建它的成员变量，这样就可以在栈上分配。\n```\n\n**可是目前还不能保证逃逸分析的性能收益必定高于它的消耗，所以这项技术还不是很成熟。**\n\n### java与C/C++编译器对比\n\n```\nJava虚拟机的即时编译器与C/C++的静态编译器相比，可能会由于下面的原因导致输出的本地代码有一些劣势：\n1.即时编译器运行占用的是用户程序的运行时间，具有很大的时间压力，因此不敢随便引入大规模的优化技术；\n2.Java语言是动态的类型安全语言，虚拟器需要频繁的进行动态检查，如空指针，上下界范围，继承关系等；\n3.Java中使用虚方法频率远高于C++，则需要进行多态选择的频率远高于C++；\n4.Java是可以动态扩展的语言，运行时加载新的类可能改变原有的继承关系，许多全局的优化措施只能以激进优化的方式来完成；\n5.Java语言的对象内存都在堆上分配，垃圾回收的压力比C++大\n \n然而，Java语言这些性能上的劣势换取了开发效率上的优势，并且由于C++编译器所有优化都是在编译期完成的，以运行期性能监控为基础的优化措施都无法进行，这也是Java编译器独有的优势。\n```\n\n\n\n\n\n\n\n\n\n\n\n## 参考文章\n\n<https://segmentfault.com/a/1190000009707894>\n\n<https://www.cnblogs.com/hysum/p/7100874.html>\n\n<http://c.biancheng.net/view/939.html>\n\n<https://www.runoob.com/>\n\nhttps://blog.csdn.net/android_hl/article/details/53228348\n\n\n"
  },
  {
    "path": "docs/Java/JVM/深入理解JVM虚拟机：再谈四种引用及GC实践.md",
    "content": "# 目录\n  * [一、背景](#一、背景)\n  * [二、简介](#二、简介)\n    * [1.强引用 StrongReference](#1强引用-strongreference)\n    * [2.弱引用 WeakReference](#2弱引用-weakreference)\n    * [3.软引用 SoftReference](#3软引用-softreference)\n    * [4.虚引用 PhantomReference](#4虚引用-phantomreference)\n  * [三、小结](#三、小结)\n  * [参考文章](#参考文章)\n\n\n\n本文转自互联网，侵删\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章将同步到我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《深入理解JVM虚拟机》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，一步步地学习JVM基础知识，并上手进行JVM调优实战，JVM是每一个Java工程师必须要学习和理解的知识点，你必须要掌握其实现原理，才能更完整地了解整个Java技术体系，形成自己的知识框架。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n## 一、背景\n\nJava的内存回收不需要程序员负责，JVM会在必要时启动Java GC完成垃圾回收。Java以便我们控制对象的生存周期，提供给了我们四种引用方式，引用强度从强到弱分别为：强引用、软引用、弱引用、虚引用。\n\n## 二、简介\n\n### 1.强引用 StrongReference\n\nStrongReference是Java的默认引用形式，使用时不需要显示定义。任何通过强引用所使用的对象不管系统资源有多紧张，Java GC都不会主动回收具有强引用的对象。\n````\npublic class StrongReferenceTest {\n\n\tpublic static int M = 1024*1024;\n\n\tpublic static void printlnMemory(String tag){\n\t\tRuntime runtime = Runtime.getRuntime();\n\t\tint M = StrongReferenceTest.M;\n\t\tSystem.out.println(\"\\n\"+tag+\":\");\n\t\tSystem.out.println(runtime.freeMemory()/M+\"M(free)/\" + runtime.totalMemory()/M+\"M(total)\");\n\t}\n\n\tpublic static void main(String[] args){\n\t\tStrongReferenceTest.printlnMemory(\"1.原可用内存和总内存\");\n\n\t\t//实例化10M的数组并与strongReference建立强引用\n\t\tbyte[] strongReference = new byte[10*StrongReferenceTest.M];\n\t\tStrongReferenceTest.printlnMemory(\"2.实例化10M的数组,并建立强引用\");\n\t\tSystem.out.println(\"strongReference : \"+strongReference);\n\n\t\tSystem.gc();\n\t\tStrongReferenceTest.printlnMemory(\"3.GC后\");\n\t\tSystem.out.println(\"strongReference : \"+strongReference);\n\n\t\t//strongReference = null;后,强引用断开了\n\t\tstrongReference = null;\n\t\tStrongReferenceTest.printlnMemory(\"4.强引用断开后\");\n\t\tSystem.out.println(\"strongReference : \"+strongReference);\n\n\t\tSystem.gc();\n\t\tStrongReferenceTest.printlnMemory(\"5.GC后\");\n\t\tSystem.out.println(\"strongReference : \"+strongReference);\n\t\t}\n}\n````\n\n运行结果：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404223852.png)\n### 2.弱引用 WeakReference\n\n如果一个对象只具有弱引用，无论内存充足与否，Java GC后对象如果只有弱引用将会被自动回收。\n````\npublic class WeakReferenceTest {\n\n\tpublic static int M = 1024*1024;\n\n\tpublic static void printlnMemory(String tag){\n\t\tRuntime runtime = Runtime.getRuntime();\n\t\tint M = WeakReferenceTest.M;\n\t\tSystem.out.println(\"\\n\"+tag+\":\");\n\t\tSystem.out.println(runtime.freeMemory()/M+\"M(free)/\" + runtime.totalMemory()/M+\"M(total)\");\n\t}\n\n\tpublic static void main(String[] args){  \n\t\tWeakReferenceTest.printlnMemory(\"1.原可用内存和总内存\");\n\n\t\t//创建弱引用\n\t\tWeakReference<Object> weakRerference = new WeakReference<Object>(new byte[10*WeakReferenceTest.M]);   \n\t\tWeakReferenceTest.printlnMemory(\"2.实例化10M的数组,并建立弱引用\");\n\t\tSystem.out.println(\"weakRerference.get() : \"+weakRerference.get());\n\n\t\tSystem.gc();\n\t\tStrongReferenceTest.printlnMemory(\"3.GC后\");\n\t\tSystem.out.println(\"weakRerference.get() : \"+weakRerference.get());\n\t}   \n}\n````\n\n运行结果：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404223905.png)\n### 3.软引用 SoftReference\n\n软引用和弱引用的特性基本一致， 主要的区别在于软引用在内存不足时才会被回收。如果一个对象只具有软引用，Java GC在内存充足的时候不会回收它，内存不足时才会被回收。\n````\npublic class SoftReferenceTest {\n\n\tpublic static int M = 1024*1024;\n\n\tpublic static void printlnMemory(String tag){\n\t\tRuntime runtime = Runtime.getRuntime();\n\t\tint M = StrongReferenceTest.M;\n\t\tSystem.out.println(\"\\n\"+tag+\":\");\n\t\tSystem.out.println(runtime.freeMemory()/M+\"M(free)/\" + runtime.totalMemory()/M+\"M(total)\");\n\t}\n\n\tpublic static void main(String[] args){\n\t\tSoftReferenceTest.printlnMemory(\"1.原可用内存和总内存\");\n\n\t\t//建立软引用\n\t\tSoftReference<Object> softRerference = new SoftReference<Object>(new byte[10*SoftReferenceTest.M]);\n\t\tSoftReferenceTest.printlnMemory(\"2.实例化10M的数组,并建立软引用\");\n\t\tSystem.out.println(\"softRerference.get() : \"+softRerference.get());\n\n\t\tSystem.gc();  \n\t\tSoftReferenceTest.printlnMemory(\"3.内存可用容量充足，GC后\");\n\t\tSystem.out.println(\"softRerference.get() : \"+softRerference.get());  \n\n\t\t//实例化一个4M的数组,使内存不够用,并建立软引用\n\t\t//free=10M=4M+10M-4M,证明内存可用量不足时，GC后byte[10*m]被回收\n\t\tSoftReference<Object> softRerference2 = new SoftReference<Object>(new byte[4*SoftReferenceTest.M]);\n\t\tSoftReferenceTest.printlnMemory(\"4.实例化一个4M的数组后\");\n\t\tSystem.out.println(\"softRerference.get() : \"+softRerference.get());\n\t\tSystem.out.println(\"softRerference2.get() : \"+softRerference2.get());  \n\t } \n}\n\n````\n运行结果：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404223922.png)\n### 4.虚引用 PhantomReference\n\n从PhantomReference类的源代码可以知道，它的get()方法无论何时返回的都只会是null。所以单独使用虚引用时，没有什么意义，需要和引用队列ReferenceQueue类联合使用。当执行Java GC时如果一个对象只有虚引用，就会把这个对象加入到与之关联的ReferenceQueue中。\n````\npublic class PhantomReferenceTest {\n\n\tpublic static int M = 1024*1024;\n\n\tpublic static void printlnMemory(String tag){\n\t\tRuntime runtime = Runtime.getRuntime();\n\t\tint M = PhantomReferenceTest.M;\n\t\tSystem.out.println(\"\\n\"+tag+\":\");\n\t\tSystem.out.println(runtime.freeMemory()/M+\"M(free)/\" + runtime.totalMemory()/M+\"M(total)\");\n\t}\n\n\tpublic static void main(String[] args) throws InterruptedException {\n\n\t\tPhantomReferenceTest.printlnMemory(\"1.原可用内存和总内存\");\n\t\tbyte[] object = new byte[10*PhantomReferenceTest.M];\t\t\n\t\tPhantomReferenceTest.printlnMemory(\"2.实例化10M的数组后\");\n\n\t    //建立虚引用\n\t    ReferenceQueue<Object> referenceQueue = new ReferenceQueue<Object>();\n\t    PhantomReference<Object> phantomReference = new PhantomReference<Object>(object,referenceQueue);  \n\n\t    PhantomReferenceTest.printlnMemory(\"3.建立虚引用后\");\n\t    System.out.println(\"phantomReference : \"+phantomReference); \n\t    System.out.println(\"phantomReference.get() : \"+phantomReference.get());\n\t    System.out.println(\"referenceQueue.poll() : \"+referenceQueue.poll());\n\n\t    //断开byte[10*PhantomReferenceTest.M]的强引用\n\t    object = null;  \n\t    PhantomReferenceTest.printlnMemory(\"4.执行object = null;强引用断开后\");\n\n\t    System.gc();\n\t    PhantomReferenceTest.printlnMemory(\"5.GC后\");\n\t    System.out.println(\"phantomReference : \"+phantomReference); \n\t    System.out.println(\"phantomReference.get() : \"+phantomReference.get());\n\t    System.out.println(\"referenceQueue.poll() : \"+referenceQueue.poll());\t    \n\n\t    //断开虚引用\n\t    phantomReference = null;\n\t\tSystem.gc(); \n\t\tPhantomReferenceTest.printlnMemory(\"6.断开虚引用后GC\");\n\t    System.out.println(\"phantomReference : \"+phantomReference);\n\t    System.out.println(\"referenceQueue.poll() : \"+referenceQueue.poll());\t    \t\n\t}\n}\n\n````\n运行结果：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404223938.png)\n## 三、小结\n\n强引用是 Java 的默认引用形式，使用时不需要显示定义，是我们平时最常使用到的引用方式。不管系统资源有多紧张，Java GC都不会主动回收具有强引用的对象。 弱引用和软引用一般在引用对象为非必需对象的时候使用。它们的区别是被弱引用关联的对象在垃圾回收时总是会被回收，被软引用关联的对象只有在内存不足时才会被回收。 虚引用的get()方法获取的永远是null，无法获取对象实例。Java GC会把虚引用的对象放到引用队列里面。可用来在对象被回收时做额外的一些资源清理或事物回滚等处理。 由于无法从虚引获取到引用对象的实例。它的使用情况比较特别，所以这里不把虚引用放入表格进行对比。这里对强引用、弱引用、软引用进行对比：\n\n\n\n## 参考文章\n\n<https://segmentfault.com/a/1190000009707894>\n\n<https://www.cnblogs.com/hysum/p/7100874.html>\n\n<http://c.biancheng.net/view/939.html>\n\n<https://www.runoob.com/>\n\nhttps://blog.csdn.net/android_hl/article/details/53228348\n\n\n"
  },
  {
    "path": "docs/Java/JVM/深入理解JVM虚拟机：垃圾回收器详解.md",
    "content": "# 目录\n  * [1 概述](#1-概述)\n  * [2 对象已经死亡？](#2-对象已经死亡？)\n    * [2.1引用计数法](#21引用计数法)\n    * [2.2可达性分析算法](#22可达性分析算法)\n    * [2.3 再谈引用](#23-再谈引用)\n    * [2.4 生存还是死亡](#24-生存还是死亡)\n    * [2.5 回收方法区](#25-回收方法区)\n  * [3 垃圾收集算法](#3-垃圾收集算法)\n    * [3.1 标记-清除算法](#31-标记-清除算法)\n    * [3.2 复制算法](#32-复制算法)\n    * [3.3 标记-整理算法](#33-标记-整理算法)\n    * [3.4分代收集算法](#34分代收集算法)\n  * [4 垃圾收集器](#4-垃圾收集器)\n    * [4.1 Serial收集器](#41-serial收集器)\n    * [4.2 ParNew收集器](#42-parnew收集器)\n    * [4.3 Parallel Scavenge收集器](#43-parallel-scavenge收集器)\n    * [4.4.Serial Old收集器](#44serial-old收集器)\n    * [4.5 Parallel Old收集器](#45-parallel-old收集器)\n    * [4.6 CMS收集器](#46-cms收集器)\n    * [4.7 G1收集器](#47-g1收集器)\n  * [5 内存分配与回收策略](#5-内存分配与回收策略)\n    * [5.1对象优先在Eden区分配](#51对象优先在eden区分配)\n    * [5.2 大对象直接进入老年代](#52-大对象直接进入老年代)\n    * [5.3长期存活的对象将进入老年代](#53长期存活的对象将进入老年代)\n    * [5.4 动态对象年龄判定](#54-动态对象年龄判定)\n  * [总结：](#总结：)\n\n\n本文转自：https://www.cnblogs.com/snailclimb/p/9086341.html\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章将同步到我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《深入理解JVM虚拟机》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，一步步地学习JVM基础知识，并上手进行JVM调优实战，JVM是每一个Java工程师必须要学习和理解的知识点，你必须要掌握其实现原理，才能更完整地了解整个Java技术体系，形成自己的知识框架。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n**本节常见面试题（推荐带着问题阅读，问题答案在文中都有提到）：**\n\n如何判断对象是否死亡（两种方法）。\n\n简单的介绍一下强引用、软引用、弱引用、虚引用（虚引用与软引用和弱引用的区别、使用软引用能带来的好处）。\n\n垃圾收集有哪些算法，各自的特点？\n\nHotSpot为什么要分为新生代和老年代？\n\n常见的垃圾回收器有那些？\n\n介绍一下CMS,G1收集器。\n\nMinor Gc和Full GC 有什么不同呢？\n\n## 1 概述\n\n首先所需要考虑：\n- 那些垃圾需要回收？\n- 什么时候回收？\n- 如何回收？\n\n当需要排查各种 内存溢出问题、当垃圾收集称为系统达到更高并发的瓶颈时，我们就需要对这些“自动化”的技术实施必要的监控和调节。\n\n## 2 对象已经死亡？\n\n堆中几乎放着所有的对象实例，对堆垃圾回收前的第一步就是要判断那些对象已经死亡（即不能再被任何途径使用的对象）\n\n### 2.1引用计数法\n\n给对象中添加一个引用计数器，每当有一个地方引用它，计数器就加1；当引用失效，计数器就减1；任何时候计数器为0的对象就是不可能再被使用的。\n\n这个方法实现简单，效率高，但是目前主流的虚拟机中并没有选择这个算法来管理内存，其最主要的原因是它很难解决对象之间相互循环引用的问题。\n\n### 2.2可达性分析算法\n\n这个算法的基本思想就是通过一系列的称为**“GC Roots”**的对象作为起点，从这些节点开始向下搜索，节点所走过的路径称为引用链，当一个对象到GC Roots没有任何引用链相连的话，则证明此对象是不可用的。\n\n### 2.3 再谈引用\n\nJDK1.2以后，Java对引用的感念进行了扩充，将引用分为强引用、软引用、弱引用、虚引用四种（引用强度逐渐减弱）\n\n**1．强引用**\n\n以前我们使用的大部分引用实际上都是强引用，这是使用最普遍的引用。如果一个对象具有强引用，那就类似于**必不可少的生活用品**，垃圾回收器绝不会回收它。当内存空 间不足，Java虚拟机宁愿抛出OutOfMemoryError错误，使程序异常终止，也不会靠随意回收具有强引用的对象来解决内存不足问题。\n\n**2．软引用（SoftReference）**\n\n如果一个对象只具有软引用，那就类似于**可有可物的生活用品**。如果内存空间足够，垃圾回收器就不会回收它，如果内存空间不足了，就会回收这些对象的内存。只要垃圾回收器没有回收它，该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。\n\n软引用可以和一个引用队列（ReferenceQueue）联合使用，如果软引用所引用的对象被垃圾回收，JAVA虚拟机就会把这个软引用加入到与之关联的引用队列中。\n\n**3．弱引用（WeakReference）**\n\n如果一个对象只具有弱引用，那就类似于**可有可物的生活用品**。弱引用与软引用的区别在于：只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中，一旦发现了只具有弱引用的对象，不管当前内存空间足够与否，都会回收它的内存。不过，由于垃圾回收器是一个优先级很低的线程， 因此不一定会很快发现那些只具有弱引用的对象。\n\n弱引用可以和一个引用队列（ReferenceQueue）联合使用，如果弱引用所引用的对象被垃圾回收，Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。\n\n**4．虚引用（PhantomReference）**\n\n“虚引用”顾名思义，就是形同虚设，与其他几种引用都不同，虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用，那么它就和没有任何引用一样，在任何时候都可能被垃圾回收。\n\n**虚引用主要用来跟踪对象被垃圾回收的活动**。\n\n**虚引用与软引用和弱引用的一个区别在于：**虚引用必须和引用队列（ReferenceQueue）联合使用。当垃 圾回收器准备回收一个对象时，如果发现它还有虚引用，就会在回收对象的内存之前，把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是 否已经加入了虚引用，来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列，那么就可以在所引用的对象的内存被回收之前采取必要的行动。\n\n特别注意，在程序设计中一般很少使用弱引用与虚引用，使用软引用的情况较多，这是因为**软引用可以加速JVM对垃圾内存的回收速度，可以维护系统的运行安全，防止内存溢出（OutOfMemory）等问题的产生**。\n\n### 2.4 生存还是死亡\n\n即使在可达性分析法中不可达的对象，也并非是“非死不可”的，这时候它们暂时处于“缓刑阶段”，要真正宣告一个对象死亡，至少要经历两次标记过程；可达性分析法中不可达的对象被第一次标记并且进行一次筛选，筛选的条件是此对象是否有必要执行finalize方法。当对象没有覆盖finalize方法，或finalize方法已经被虚拟机调用过时，虚拟机将这两种情况视为没有必要执行。被判定为需要执行的对象将会被放在一个队列中进行第二次标记，除非这个对象与引用链上的任何一个对象建立关联，否则就会被真的回收。\n\n### 2.5 回收方法区\n\n方法区（或Hotspot虚拟中的永久代）的垃圾收集主要回收两部分内容：**废弃常量和无用的类。**\n\n判定一个常量是否是“废弃常量”比较简单，而要判定一个类是否是“无用的类”的条件则相对苛刻许多。类需要同时满足下面3个条件才能算是**“无用的类”**：\n- 该类所有的实例都已经被回收，也就是Java堆中不存在该类的任何实例。\n- 加载该类的ClassLoader已经被回收。\n- 该类对应的java.lang.Class对象没有在任何地方被引用，无法在任何地方通过反射访问该类的方法。\n\n## 3 垃圾收集算法\n\n### 3.1 标记-清除算法\n\n算法分为“标记”和“清除”阶段：首先标记出所有需要回收的对象，在标记完成后统一回收所有被标记的对象。它是最基础的收集算法，会带来两个明显的问题；1：效率问题和2：空间问题（标记清除后会产生大量不连续的碎片）\n\n### 3.2 复制算法\n\n为了解决效率问题，“复制”收集算法出现了。它可以将内存分为大小相同的两块，每次使用其中的一块。当这一块的内存使用完后，就将还存活的对象复制到另一块去，然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。\n\n\n### 3.3 标记-整理算法\n\n根据老年代的特点特出的一种标记算法，标记过程仍然与“标记-清除”算法一样，但后续步骤不是直接对可回收对象回收，而是让所有存活的对象向一段移动，然后直接清理掉端边界以外的内存。\n\n\n### 3.4分代收集算法\n\n当前虚拟机的垃圾手机都采用分代收集算法，这种算法没有什么新的思想，只是根据对象存活周期的不同将内存分为几块。一般将java堆分为新生代和老年代，这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。\n\n比如在新生代中，每次收集都会有大量对象死去，所以可以选择复制算法，只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的所以我们可以选择“标记-清理”或“标记-整理”算法进行垃圾收集。\n\n**延伸面试问题：**HotSpot为什么要分为新生代和老年代？\n\n根据上面的对分代收集算法的介绍回答。\n\n## 4 垃圾收集器\n\n**如果说收集算法是内存回收的方法论，那么垃圾收集器就是内存回收的具体实现。**\n虽然我们对各个收集器进行比较，但并非了挑选出一个最好的收集器。因为知道现在位置还没有最好的垃圾收集器出现，更加没有万能的垃圾收集器，**我们能做的就是根据具体应用场景选择适合自己的垃圾收集器**。试想一下：如果有一种四海之内、任何场景下都适用的完美收集器存在，那么我们的HotSpot虚拟机就不会实现那么多不同的垃圾收集器了。\n\n### 4.1 Serial收集器\n\nSerial（串行）收集器收集器是最基本、历史最悠久的垃圾收集器了。大家看名字就知道这个收集器是一个单线程收集器了。它的**“单线程”**的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作，更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程（**“Stop The World”**了解一下），直到它收集结束。\n\n虚拟机的设计者们当然知道Stop The World带来的不良用户体验，所以在后续的垃圾收集器设计中停顿时间在不断缩短（仍然还有停顿，寻找最优秀的垃圾收集器的过程仍然在继续）。\n\n但是Serial收集器有没有优于其他垃圾收集器的地方呢？当然有，它**简单而高效（与其他收集器的单线程相比）**。Serial收集器由于没有线程交互的开销，自然可以获得很高的单线程收集效率。Serial收集器对于运行在Client模式下的虚拟机来说是个不错的选择。\n\n### 4.2 ParNew收集器\n\n**ParNew收集器其实就是Serial收集器的多线程版本，除了使用多线程进行垃圾收集外，其余行为（控制参数、收集算法、回收策略等等）和Serial收集器完全一样。**\n\n它是许多运行在Server模式下的虚拟机的首要选择，除了Serial收集器外，只有它能与CMS收集器（真正意义上的并发收集器，后面会介绍到）配合工作。\n\n**并行和并发概念补充：**\n\n*   **并行（Parallel）**：指多条垃圾收集线程并行工作，但此时用户线程仍然处于等待状态。\n\n*   **并发（Concurrent）**：指用户线程与垃圾收集线程同时执行（但不一定是并行，可能会交替执行），用户程序在继续运行，而垃圾收集器运行在另一个CPU上。\n\n### 4.3 Parallel Scavenge收集器\n\nParallel Scavenge收集器是一个新生代收集器，它也是使用复制算法的收集器，又是并行的的多线程收集器。。。那么它有什么特别之处呢？\n\n**Parallel Scavenge收集器关注点是吞吐量（高效率的利用CPU）。CMS等垃圾收集器的关注点更多的是用户线程的停顿时间（提高用户体验）。所谓吞吐量就是CPU中用于运行用户代码的时间与CPU总消耗时间的比值。**Parallel Scavenge收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量，如果对于收集器运作不太了解的话，手工优化存在的话可以选择把内存管理优化交给虚拟机去完成也是一个不错的选择。\n\n### 4.4.Serial Old收集器\n\n**Serial收集器的老年代版本**，它同样是一个单线程收集器。它主要有两大用途：一种用途是在JDK1.5以及以前的版本中与Parallel Scavenge收集器搭配使用，另一种用途是作为CMS收集器的后备方案。\n\n### 4.5 Parallel Old收集器\n\n**Parallel Scavenge收集器的老年代版本**。使用多线程和“标记-整理”算法。在注重吞吐量以及CPU资源的场合，都可以优先考虑 Parallel Scavenge收集器和Parallel Old收集器。\n\n### 4.6 CMS收集器\n\n**CMS（Concurrent Mark Sweep）收集器是一种以获取最短回收停顿时间为目标的收集器。它而非常符合在注重用户体验的应用上使用。**\n\n从名字中的**Mark Sweep**这两个词可以看出，CMS收集器是一种**“标记-清除”算法**实现的，它的运作过程相比于前面几种垃圾收集器来说更加复杂一些。整个过程分为四个步骤：\n\n*   **初始标记：**暂停所有的其他线程，并记录下直接与root相连的对象，速度很快 ；\n*   **并发标记：**同时开启GC和用户线程，用一个闭包结构去记录可达对象。但在这个阶段结束，这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域，所以GC线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。\n*   **重新标记：**重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录，这个阶段的停顿时间一般会比初始标记阶段的时间稍长，远远比并发标记阶段时间短\n*   **并发清除：**开启用户线程，同时GC线程开始对为标记的区域做清扫。\n\n从它的名字就可以看出它是一款优秀的垃圾收集器，主要优点：**并发收集、低停顿**。但是它有下面三个明显的缺点：\n-**对CPU资源敏感；**\n-**无法处理浮动垃圾；**\n-**它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生。**\n\n### 4.7 G1收集器\n\n上一代的垃圾收集器(串行serial, 并行parallel, 以及CMS)都把堆内存划分为固定大小的三个部分: 年轻代(young generation), 年老代(old generation), 以及持久代(permanent generation).\n\n\n**G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征.**\n\n被视为JDK1.7中HotSpot虚拟机的一个重要进化特征。它具备一下特点：\n-**并行与并发**：G1能充分利用CPU、多核环境下的硬件优势，使用多个CPU（CPU或者CPU核心）来缩短stop-The-World停顿时间。部分其他收集器原本需要停顿Java线程执行的GC动作，G1收集器仍然可以通过并发的方式让java程序继续执行。\n-**分代收集**：虽然G1可以不需要其他收集器配合就能独立管理整个GC堆，但是还是保留了分代的概念。\n-**空间整合**：与CMS的“标记–清理”算法不同，G1从整体来看是基于“标记整理”算法实现的收集器；从局部上来看是基于“复制”算法实现的。\n-**可预测的停顿**：这是G1相对于CMS的另一个大优势，降低停顿时间是G1和ＣＭＳ共同的关注点，但Ｇ１除了追求低停顿外，还能建立可预测的停顿时间模型，能让使用者明确指定在一个长度为M毫秒的时间片段内。\n\n**G1收集器在后台维护了一个优先列表，每次根据允许的收集时间，优先选择回收价值最大的Region(这也就是它的名字Garbage-First的由来)**。这种使用Region划分内存空间以及有优先级的区域回收方式，保证了GF收集器在有限时间内可以尽可能高的收集效率（把内存化整为零）。\n\nG1收集器的运作大致分为以下几个步骤：\n-**初始标记**\n-**并发标记**\n-**最终标记**\n-**筛选回收**\n\n上面几个步骤的运作过程和CMS有很多相似之处。初始标记阶段仅仅只是标记一下GC Roots能直接关联到的对象，并且修改TAMS的值，让下一个阶段用户程序并发运行时，能在正确可用的Region中创建新对象，这一阶段需要停顿线程，但是耗时很短，并发标记阶段是从GC Root开始对堆中对象进行可达性分析，找出存活的对象，这阶段时耗时较长，但可与用户程序并发执行。而最终标记阶段则是为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录，虚拟机将这段时间对象变化记录在线程Remenbered Set Logs里面，最终标记阶段需要把Remembered Set Logs的数据合并到Remembered Set Logs里面，最终标记阶段需要把Remembered Set Logs的数据合并到Remembered Set中，这一阶段需要停顿线程，但是可并行执行。最后在筛选回收阶段首先对各个Region的回收价值和成本进行排序，根据用户所期望的GC停顿时间来制定回收计划。\n\n\n## 5 内存分配与回收策略\n\n### 5.1对象优先在Eden区分配\n\n大多数情况下，对象在新生代中Eden区分配。当Eden区没有足够空间进行分配时，虚拟机将发起一次Minor GC.\n\n**Minor Gc和Full GC 有什么不同呢？**\n\n**新生代GC（Minor GC）**:指发生新生代的的垃圾收集动作，Minor GC非常频繁，回收速度一般也比较快。\n\n**老年代GC（Major GC/Full GC）**:指发生在老年代的GC，出现了Major GC经常会伴随至少一次的Minor GC（并非绝对），Major GC的速度一般会比Minor GC的慢10倍以上。\n\n### 5.2 大对象直接进入老年代\n\n大对象就是需要大量连续内存空间的对象（比如：字符串、数组）。\n\n### 5.3长期存活的对象将进入老年代\n\n既然虚拟机采用了分代收集的思想来管理内存，那么内存回收时就必须能识别那些对象应放在新生代，那些对象应放在老年代中。为了做到这一点，虚拟机给每个对象一个对象年龄（Age）计数器。\n\n### 5.4 动态对象年龄判定\n\n为了更好的适应不同程序的内存情况，虚拟机不是永远要求对象年龄必须达到了某个值才能进入老年代，如果Survivor 空间中相同年龄所有对象大小的总和大于Survivor空间的一半，年龄大于或等于该年龄的对象就可以直接进入老年代，无需达到要求的年龄。\n\n## 总结：\n\n本节介绍了垃圾收集算法，几款JDK1.7中提供的垃圾收集器特点以及运作原理。\n内存回收与垃圾收集器在很多时候都是影响系统性能、并发能力的主要因素之一，虚拟机之所以提供多种不同的收集器以及大量调节参数，是因为只有根据实际应用的需求、实现方式选择最优的收集方式才能获取最高的性能。没有固定收集器、参数组合、也没有最优的调优方法，那么必须了解每一个具体收集器的行为、优势和劣势、调节参数。\n"
  },
  {
    "path": "docs/Java/JVM/深入理解JVM虚拟机：深入理解JVM类加载机制.md",
    "content": "# 目录\n  * [一.目标：](#一目标：)\n  * [二.原理 （类的加载过程及其最终产品）:](#二原理-（类的加载过程及其最终产品）)\n  * [三.过程（类的生命周期）：](#三过程（类的生命周期）：)\n    * [加载：](#加载：)\n    * [校验：](#校验：)\n    * [准备：](#准备：)\n    * [解析：](#解析：)\n    * [初始化：](#初始化：)\n  * [四.类加载器：](#四类加载器：)\n  * [五.双亲委派机制：](#五双亲委派机制：)\n  * [参考文章](#参考文章)\n\n\n本文转自互联网，侵删\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章将同步到我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《深入理解JVM虚拟机》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，一步步地学习JVM基础知识，并上手进行JVM调优实战，JVM是每一个Java工程师必须要学习和理解的知识点，你必须要掌握其实现原理，才能更完整地了解整个Java技术体系，形成自己的知识框架。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n## 一.目标：\n\n1.什么是类的加载？\n\n2.类的生命周期？\n\n3.类加载器是什么？\n\n4.双亲委派机制是什么？\n\n## 二.原理 （类的加载过程及其最终产品）:\n\nJVM将class文件字节码文件加载到内存中， 并将这些静态数据转换成方法区中的运行时数据结构，在堆(并不一定在堆中，HotSpot在方法区中)中生成一个代表这个类的java.lang.Class 对象，作为方法区类数据的访问入口。\n\n## 三.过程（类的生命周期）：\n\nJVM类加载机制分为五个部分：加载，验证，准备，解析，初始化，下面我们就分别来看一下这五个过程。其中加载、检验、准备、初始化和卸载这个五个阶段的顺序是固定的，而解析则未必。为了支持动态绑定，解析这个过程可以发生在初始化阶段之后。\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404221551.png)\n\n### 加载：\n\n加载过程主要完成三件事情：\n\n1.  通过类的全限定名来获取定义此类的二进制字节流\n2.  将这个类字节流代表的静态存储结构转为方法区的运行时数据结构\n3.  在堆中生成一个代表此类的java.lang.Class对象，作为访问方法区这些数据结构的入口。\n\n这个过程主要就是类加载器完成。\n\n### 校验：\n\n此阶段主要确保Class文件的字节流中包含的信息符合当前虚拟机的要求，并且不会危害虚拟机的自身安全。\n\n1.  文件格式验证：基于字节流验证。\n2.  元数据验证：基于**_方法区_**的存储结构验证。\n3.  字节码验证：基于方法区的存储结构验证。\n4.  符号引用验证：基于方法区的存储结构验证。\n\n### 准备：\n\n为类变量分配内存，并将其初始化为默认值。（此时为默认值，在初始化的时候才会给变量赋值）即在方法区中分配这些变量所使用的内存空间。例如：\n\n```\npublic static int value = 123;\n\n```\n\n此时在准备阶段过后的初始值为0而不是123；将value赋值为123的putstatic指令是程序被编译后，存放于类构造器<client>方法之中.特例：\n\n```\npublic static final int value = 123;\n\n```\n\n此时value的值在准备阶段过后就是123。\n\n### 解析：\n\n把类型中的符号引用转换为直接引用。\n\n*   符号引用与虚拟机实现的布局无关，引用的目标并不一定要已经加载到内存中。各种虚拟机实现的内存布局可以各不相同，但是它们能接受的符号引用必须是一致的，因为符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中。\n*   直接引用可以是指向目标的指针，相对偏移量或是一个能间接定位到目标的句柄。如果有了直接引用，那引用的目标必定已经在内存中存在\n\n主要有以下四种：\n\n1.  类或接口的解析\n2.  字段解析\n3.  类方法解析\n4.  接口方法解析\n\n### 初始化：\n\n初始化阶段是执行类构造器<client>方法的过程。<client>方法是由编译器自动收集类中的类变量的赋值操作和静态语句块中的语句合并而成的。虚拟机会保证<client>方法执行之前，父类的<client>方法已经执行完毕。如果一个类中没有对静态变量赋值也没有静态语句块，那么编译器可以不为这个类生成<client>()方法。\n\njava中，对于初始化阶段，有且只有以下五种情况才会对要求类立刻“初始化”（加载，验证，准备，自然需要在此之前开始）：\n\n1.  使用new关键字实例化对象、访问或者设置一个类的静态字段（被final修饰、编译器优化时已经放入常量池的例外）、调用类方法，都会初始化该静态字段或者静态方法所在的类。\n2.  初始化类的时候，如果其父类没有被初始化过，则要先触发其父类初始化。\n3.  使用java.lang.reflect包的方法进行反射调用的时候，如果类没有被初始化，则要先初始化。\n4.  虚拟机启动时，用户会先初始化要执行的主类（含有main）\n5.  jdk 1.7后，如果java.lang.invoke.MethodHandle的实例最后对应的解析结果是 REF_getStatic、REF_putStatic、REF_invokeStatic方法句柄，并且这个方法所在类没有初始化，则先初始化。\n\n## 四.类加载器：\n\n把类加载阶段的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作交给虚拟机之外的类加载器来完成。这样的好处在于，我们可以自行实现类加载器来加载其他格式的类，只要是二进制字节流就行，这就大大增强了加载器灵活性。系统自带的类加载器分为三种：\n\n1.  启动类加载器。\n2.  扩展类加载器。\n3.  应用程序类加载器。\n\n## 五.双亲委派机制：\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404221618.png)\n\n\n双亲委派机制工作过程：\n\n如果一个类加载器收到了类加载器的请求.它首先不会自己去尝试加载这个类.而是把这个请求委派给父加载器去完成.每个层次的类加载器都是如此.因此所有的加载请求最终都会传送到Bootstrap类加载器(启动类加载器)中.只有父类加载反馈自己无法加载这个请求(它的搜索范围中没有找到所需的类)时.子加载器才会尝试自己去加载。\n\n双亲委派模型的优点：java类随着它的加载器一起具备了一种带有优先级的层次关系.\n\n例如类java.lang.Object,它存放在rt.jart之中.无论哪一个类加载器都要加载这个类.最终都是双亲委派模型最顶端的Bootstrap类加载器去加载.因此Object类在程序的各种类加载器环境中都是同一个类.相反.如果没有使用双亲委派模型.由各个类加载器自行去加载的话.如果用户编写了一个称为“java.lang.Object”的类.并存放在程序的ClassPath中.那系统中将会出现多个不同的Object类.java类型体系中最基础的行为也就无法保证.应用程序也将会一片混乱.\n\n\n## 参考文章\n\n<https://segmentfault.com/a/1190000009707894>\n\n<https://www.cnblogs.com/hysum/p/7100874.html>\n\n<http://c.biancheng.net/view/939.html>\n\n<https://www.runoob.com/>\n\nhttps://blog.csdn.net/android_hl/article/details/53228348\n\n\n"
  },
  {
    "path": "docs/Java/JVM/深入理解JVM虚拟机：虚拟机字节码执行引擎.md",
    "content": "# 目录\n  * [1 概述](#1-概述)\n  * [2 运行时栈帧结构](#2-运行时栈帧结构)\n    * [2.1 局部变量表](#21-局部变量表)\n    * [2.2 操作数栈](#22-操作数栈)\n    * [2.3 动态连接](#23-动态连接)\n    * [2.4 方法返回地址](#24-方法返回地址)\n    * [2.5 附加信息](#25-附加信息)\n  * [3 方法调用](#3-方法调用)\n    * [3.1 解析](#31-解析)\n    * [3.2 分派](#32-分派)\n    * [3.3 动态类型语言的支持](#33-动态类型语言的支持)\n  * [4 基于栈的字节码解释执行引擎](#4-基于栈的字节码解释执行引擎)\n    * [4.1 解释执行](#41-解释执行)\n    * [4.2 基于栈的指令集和基于寄存器的指令集](#42-基于栈的指令集和基于寄存器的指令集)\n  * [总结](#总结)\n\n\n本文转自：https://www.cnblogs.com/snailclimb/p/9086337.html\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章将同步到我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《深入理解JVM虚拟机》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，一步步地学习JVM基础知识，并上手进行JVM调优实战，JVM是每一个Java工程师必须要学习和理解的知识点，你必须要掌握其实现原理，才能更完整地了解整个Java技术体系，形成自己的知识框架。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n## 1 概述\n\n执行引擎是java虚拟机最核心的组成部件之一。虚拟机的执行引擎由自己实现，所以可以自行定制指令集与执行引擎的结构体系，并且能够执行那些不被硬件直接支持的指令集格式。\n\n所有的Java虚拟机的执行引擎都是一致的：**输入的是字节码文件，处理过程是字节码解析的等效过程，输出的是执行结果**。本节将主要从概念模型的角度来讲解**虚拟机的方法调用和字节码执行**。\n\n## 2 运行时栈帧结构\n\n**栈帧（Stack Frame）**是用于支持虚拟机方法调用和方法执行的数据结构，它是虚拟机运行时数据区中**虚拟机栈（Virtual Machine Stack）的栈元素**。\n\n栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。每一个方法从调用开始至执行完成的过程，都对应着一个栈帧在虚拟机栈里面从入栈到出栈的过程。\n\n**栈帧概念结构如下图所示：**\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404221305.png)\n### 2.1 局部变量表\n\n**局部变量表是一组变量值存储空间，用于存放方法参数和方法内定义的局部变量。\n局部变量表的容量以变量槽（Variable Slot）为最小单位。** 一个Slot可以存放一个32位以内（boolean、byte、char、short、int、float、reference和returnAddress）的数据类型，reference类型表示一个对象实例的引用，returnAddress已经很少见了，可以忽略。\n\n**对于64位的数据类型（Java语言中明确的64位数据类型只有long和double），虚拟机会以高位对齐的方式为其分配两个连续的Slot空间。**\n\n**虚拟机通过索引定位的方式使用局部变量表**，索引值的范围从0开始至局部变量表最大的Slot数量。访问的是32位数据类型的变量，索引n就代表了使用第n个Slot,如果是64位数据类型，就代表会同时使用n和n+1这两个Slot。\n\n**为了节省栈帧空间，局部变量Slot可以重用**，方法体中定义的变量，其作用域并不一定会覆盖整个方法体。如果当前字节码PC计数器的值超出了某个变量的作用域，那么这个变量的Slot就可以交给其他变量使用。这样的设计会带来一些额外的副作用，比如：在某些情况下，Slot的复用会直接影响到系统的收集行为。\n\n### 2.2 操作数栈\n\n**操作数栈（Operand Stack）**也常称为操作栈，它是一个**后入先出栈**。当一个方法执行开始时，这个方法的操作数栈是空的，在方法执行过程中，会有各种字节码指令往操作数栈中写入和提取内容，也就是**出栈/入栈**操作。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404221316.png)\n在概念模型中，一个活动线程中两个栈帧是相互独立的。但大多数虚拟机实现都会做一些优化处理：让下一个栈帧的部分操作数栈与上一个栈帧的部分局部变量表重叠在一起，这样的好处是方法调用时可以共享一部分数据，而无须进行额外的参数复制传递。\n\n### 2.3 动态连接\n\n每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用，持有这个引用是为了支持方法调用过程中的**动态连接**；\n\n字节码中方法调用指令是以常量池中的指向方法的符号引用为参数的，有一部分符号引用会在类加载阶段或第一次使用的时候转化为直接引用，这种转化称为**静态解析**，另外一部分在每次的运行期间转化为直接引用，这部分称为**动态连接**。\n\n### 2.4 方法返回地址\n\n当一个方法被执行后，有两种方式退出这个方法：\n\n*   第一种是执行引擎遇到任意一个方法返回的字节码指令，这种退出方法的方式称为**正常完成出口（Normal Method Invocation Completion）**。\n\n*   另外一种是在方法执行过程中遇到了异常，并且这个异常没有在方法体内得到处理（即本方法异常处理表中没有匹配的异常处理器），就会导致方法退出，这种退出方式称为**异常完成出口（Abrupt Method Invocation Completion）**。\n    注意：这种退出方式不会给上层调用者产生任何返回值。\n\n**无论采用何种退出方式，在方法退出后，都需要返回到方法被调用的位置，程序才能继续执行**，方法返回时可能需要在栈帧中保存一些信息，用来帮助恢复它的上层方法的执行状态。一般来说，方法正常退出时，调用者的PC计数器的值可以作为返回地址，栈帧中很可能会保存这个计数器值。而方法异常退出时，返回地址是通过异常处理器表来确定的，栈帧中一般不会保存这部分信息。\n\n方法退出的过程实际上等同于把当前栈帧出栈，因此退出时可能执行的操作有：恢复上层方法的局部变量表和操作数栈，把返回值（如果有的话）压入调用者栈帧的操作数栈中，调整PC计数器的值以指向方法调用指令后面的一条指令等。\n\n### 2.5 附加信息\n\n虚拟机规范允许虚拟机实现向栈帧中添加一些自定义的附加信息，例如与调试相关的信息等。\n\n## 3 方法调用\n\n方法调用阶段的目的：**确定被调用方法的版本（哪一个方法），不涉及方法内部的具体运行过程**，在程序运行时，进行方法调用是最普遍、最频繁的操作。\n\n**一切方法调用在Class文件里存储的都只是符号引用，这是需要在类加载期间或者是运行期间，才能确定为方法在实际 运行时内存布局中的入口地址（相当于之前说的直接引用）**。\n\n### 3.1 解析\n\n“编译期可知，运行期不可变”的方法（静态方法和私有方法），在类加载的解析阶段，会将其符号引用转化为直接引用（入口地址）。这类方法的调用称为“**解析（Resolution）**”。\n\n在Java虚拟机中提供了5条方法调用字节码指令：\n-**invokestatic**: 调用静态方法\n-**invokespecial**:调用实例构造器方法、私有方法、父类方法\n-**invokevirtual**:调用所有的虚方法\n-**invokeinterface**:调用接口方法，会在运行时在确定一个实现此接口的对象\n-**invokedynamic**:先在运行时动态解析出点限定符所引用的方法，然后再执行该方法，在此之前的4条调用命令的分派逻辑是固化在Java虚拟机内部的，而invokedynamic指令的分派逻辑是由用户所设定的引导方法决定的。\n\n### 3.2 分派\n\n**分派调用过程将会揭示多态性特征的一些最基本的体现，如“重载”和“重写”在Java虚拟中是如何实现的。**\n\n**1 静态分派**\n\n所有依赖静态类型来定位方法执行版本的分派动作，都称为静态分派。静态分派发生在编译阶段。\n\n静态分派最典型的应用就是方法重载。\n\n```\npackage jvm8_3_2;\n\npublic class StaticDispatch {\n    static abstract class Human {\n\n    }\n\n    static class Man extends Human {\n\n    }\n\n    static class Woman extends Human {\n\n    }\n\n    public void sayhello(Human guy) {\n        System.out.println(\"Human guy\");\n\n    }\n\n    public void sayhello(Man guy) {\n        System.out.println(\"Man guy\");\n\n    }\n\n    public void sayhello(Woman guy) {\n        System.out.println(\"Woman guy\");\n    }\n\n    public static void main(String[] args) {\n        Human man = new Man();\n        Human woman = new Woman();\n        StaticDispatch staticDispatch = new StaticDispatch();\n        staticDispatch.sayhello(man);// Human guy\n        staticDispatch.sayhello(woman);// Human guy\n    }\n\n}\n```\n\n运行结果：\n\nHuman guy\n\nHuman guy\n\n**为什么会出现这样的结果呢？**\n\nHuman man = new Man();其中的Human称为变量的**静态类型（Static Type）**,Man称为变量的**实际类型（Actual Type）**。\n**两者的区别是**：静态类型在编译器可知，而实际类型到运行期才确定下来。\n在重载时通过参数的静态类型而不是实际类型作为判定依据，因此，在编译阶段，Javac编译器会根据参数的静态类型决定使用哪个重载版本。所以选择了sayhello(Human)作为调用目标，并把这个方法的符号引用写到main()方法里的两条invokevirtual指令的参数中。\n\n**2 动态分派**\n\n在运行期根据实际类型确定方法执行版本的分派过程称为动态分派。最典型的应用就是方法重写。\n\n```\npackage jvm8_3_2;\n\npublic class DynamicDisptch {\n\n    static abstract class Human {\n        abstract void sayhello();\n    }\n\n    static class Man extends Human {\n\n        @Override\n        void sayhello() {\n            System.out.println(\"man\");\n        }\n\n    }\n\n    static class Woman extends Human {\n\n        @Override\n        void sayhello() {\n            System.out.println(\"woman\");\n        }\n\n    }\n\n    public static void main(String[] args) {\n        Human man = new Man();\n        Human woman = new Woman();\n        man.sayhello();\n        woman.sayhello();\n        man = new Woman();\n        man.sayhello();\n    }\n\n}\n\n```\n\n运行结果：\n\nman\n\nwoman\n\nwoman\n\n**3 单分派和多分派**\n\n方法的接收者、方法的参数都可以称为方法的宗量。根据分批基于多少种宗量，可以将分派划分为单分派和多分派。**单分派是根据一个宗量对目标方法进行选择的，多分派是根据多于一个的宗量对目标方法进行选择的。**\n\nJava在进行静态分派时，选择目标方法要依据两点：一是变量的静态类型是哪个类型，二是方法参数是什么类型。因为要根据两个宗量进行选择，所以Java语言的静态分派属于多分派类型。\n\n运行时阶段的动态分派过程，由于编译器已经确定了目标方法的签名（包括方法参数），运行时虚拟机只需要确定方法的接收者的实际类型，就可以分派。因为是根据一个宗量作为选择依据，所以Java语言的动态分派属于单分派类型。\n\n注：到JDK1.7时，Java语言还是静态多分派、动态单分派的语言，未来有可能支持动态多分派。\n\n**4 虚拟机动态分派的实现**\n\n由于动态分派是非常频繁的动作，而动态分派在方法版本选择过程中又需要在方法元数据中搜索合适的目标方法，虚拟机实现出于性能的考虑，通常不直接进行如此频繁的搜索，而是采用优化方法。\n\n其中一种“稳定优化”手段是：在类的方法区中建立一个**虚方法表**（Virtual Method Table, 也称vtable, 与此对应，也存在接口方法表——Interface Method Table，也称itable）。**使用虚方法表索引来代替元数据查找以提高性能。其原理与C++的虚函数表类似。**\n\n虚方法表中存放的是各个方法的实际入口地址。如果某个方法在子类中没有被重写，那子类的虚方法表里面的地址入口和父类中该方法相同，都指向父类的实现入口。虚方法表一般在类加载的连接阶段进行初始化。\n\n### 3.3 动态类型语言的支持\n\nJDK新增加了invokedynamic指令来是实现“动态类型语言”。\n\n**静态语言和动态语言的区别：**\n\n*   **静态语言（强类型语言）**：\n    静态语言是在编译时变量的数据类型即可确定的语言，多数静态类型语言要求在使用变量之前必须声明数据类型。\n    例如：C++、Java、Delphi、C#等。\n*   **动态语言（弱类型语言）**：\n    动态语言是在运行时确定数据类型的语言。变量使用之前不需要类型声明，通常变量的类型是被赋值的那个值的类型。\n    例如PHP/ASP/Ruby/Python/Perl/ABAP/SQL/JavaScript/Unix Shell等等。\n*   **强类型定义语言**：\n    强制数据类型定义的语言。也就是说，一旦一个变量被指定了某个数据类型，如果不经过强制转换，那么它就永远是这个数据类型了。举个例子：如果你定义了一个整型变量a,那么程序根本不可能将a当作字符串类型处理。强类型定义语言是类型安全的语言。\n*   **弱类型定义语言**：\n    数据类型可以被忽略的语言。它与强类型定义语言相反, 一个变量可以赋不同数据类型的值。强类型定义语言在速度上可能略逊色于弱类型定义语言，但是强类型定义语言带来的严谨性能够有效的避免许多错误。\n\n## 4 基于栈的字节码解释执行引擎\n\n虚拟机如何调用方法的内容已经讲解完毕，现在我们来探讨虚拟机是如何执行方法中的字节码指令。\n\n### 4.1 解释执行\n\nJava语言经常被人们定位为**“解释执行”语言**，在Java初生的JDK1.0时代，这种定义还比较准确的，但当主流的虚拟机中都包含了即时编译后，Class文件中的代码到底会被解释执行还是编译执行，就成了只有虚拟机自己才能准确判断的事情。再后来，Java也发展出来了直接生成本地代码的编译器[如何GCJ（GNU Compiler for the Java）]，而C/C++也出现了通过解释器执行的版本（如CINT），这时候再笼统的说“解释执行”，对于整个Java语言来说就成了几乎没有任何意义的概念，**只有确定了谈论对象是某种具体的Java实现版本和执行引擎运行模式时，谈解释执行还是编译执行才会比较确切**。\n\nJava语言中，javac编译器完成了程序代码经过词法分析、语法分析到抽象语法树，再遍历语法树生成线性的字节码指令流的过程，因为这一部分动作是在Java虚拟机之外进行的，而解释器在虚拟机内部，所以Java程序的编译就是半独立实现的，\n\n### 4.2 基于栈的指令集和基于寄存器的指令集\n\nJava编译器输出的指令流，基本上是一种**基于栈的指令集架构（Instruction Set Architecture，ISA）**，**依赖操作数栈进行工作**。与之相对应的另一套常用的指令集架构是**基于寄存器的指令集**，**依赖寄存器进行工作**。\n\n那么，**基于栈的指令集和基于寄存器的指令集这两者有什么不同呢？**\n\n举个简单例子，分别使用这两种指令计算1+1的结果，**基于栈的指令集会是这个样子：**\niconst_1\n\niconst_1\n\niadd\n\nistore_0\n\n两条iconst_1指令连续把两个常量1压入栈后，iadd指令把栈顶的两个值出栈、相加，然后将结果放回栈顶，最后istore_0把栈顶的值放到局部变量表中的第0个Slot中。\n\n**如果基于寄存器的指令集，那程序可能会是这个样子：**\n\nmov eax, 1\n\nadd eax, 1\n\nmov指令把EAX寄存器的值设置为1，然后add指令再把这个值加1，将结果就保存在EAX寄存器里面。\n\n**基于栈的指令集主要的优点就是可移植，寄存器是由硬件直接提供，程序直接依赖这些硬件寄存器则不可避免地要受到硬件的约束。**\n\n**栈架构的指令集还有一些其他的优点，如代码相对更加紧凑，编译器实现更加简单等。\n栈架构指令集的主要缺点是执行速度相对来说会稍微慢一些。**\n\n## 总结\n\n本节中，我们分析了虚拟机在执行代码时，如何找到正确的方法、如何执行方法内的字节码，以及执行代码时涉及的内存结构。\n"
  },
  {
    "path": "docs/Java/basic/Java8新特性终极指南.md",
    "content": "# 目录\n  * [Java语言新特性](#java语言新特性)\n    * [Lambda表达式](#lambda表达式)\n    * [函数式接口](#函数式接口)\n    * [方法引用](#方法引用)\n    * [接口的默认方法](#接口的默认方法)\n    * [重复注解](#重复注解)\n  * [Java编译器的新特性](#java编译器的新特性)\n    * [方法参数名字可以反射获取](#方法参数名字可以反射获取)\n  * [Java 类库的新特性](#java-类库的新特性)\n    * [Optional](#optional)\n    * [Stream](#stream)\n    * [Date/Time API (JSR 310)](#datetime-api-jsr-310)\n    * [并行（parallel）数组](#并行（parallel）数组)\n    * [CompletableFuture](#completablefuture)\n  * [Java虚拟机（JVM）的新特性](#java虚拟机（jvm）的新特性)\n  * [总结](#总结)\n  * [参考文章](#参考文章)\n\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章首发于我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《夯实Java基础系列博文》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n该系列博文会告诉你如何从入门到进阶，一步步地学习Java基础知识，并上手进行实战，接着了解每个Java知识点背后的实现原理，更完整地了解整个Java技术体系，形成自己的知识框架。为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n\n<!-- more -->\n\n\n这是一个Java8新增特性的总结图。接下来让我们一次实践一下这些新特性吧\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230403215737.png)\n## Java语言新特性\n\n### Lambda表达式\n\n\nLambda表达式（也称为闭包）是整个Java 8发行版中最受期待的在Java语言层面上的改变，Lambda允许把函数作为一个方法的参数（函数作为参数传递进方法中），或者把代码看成数据：函数式程序员对这一概念非常熟悉。在JVM平台上的很多语言（Groovy，Scala，……）从一开始就有Lambda，但是Java程序员不得不使用毫无新意的匿名类来代替lambda。\n\n关于Lambda设计的讨论占用了大量的时间与社区的努力。可喜的是，最终找到了一个平衡点，使得可以使用一种即简洁又紧凑的新方式来构造Lambdas。在最简单的形式中，一个lambda可以由用逗号分隔的参数列表、–>符号与函数体三部分表示。例如：\n\n    Arrays.asList( \"a\", \"b\", \"d\" ).forEach( e -> System.out.println( e ) );\n\n请注意参数e的类型是由编译器推测出来的。同时，你也可以通过把参数类型与参数包括在括号中的形式直接给出参数的类型：\n\n    Arrays.asList( \"a\", \"b\", \"d\" ).forEach( ( String e ) -> System.out.println( e ) );\n\n在某些情况下lambda的函数体会更加复杂，这时可以把函数体放到在一对花括号中，就像在Java中定义普通函数一样。例如：\n\n    Arrays.asList( \"a\", \"b\", \"d\" ).forEach( e -> {\n        System.out.print( e );\n        System.out.print( e );\n    } );\nLambda可以引用类的成员变量与局部变量（如果这些变量不是final的话，它们会被隐含的转为final，这样效率更高）。例如，下面两个代码片段是等价的：\n\n    String separator = \",\";\n    Arrays.asList( \"a\", \"b\", \"d\" ).forEach( \n        ( String e ) -> System.out.print( e + separator ) );\n和：\n\n    final String separator = \",\";\n    Arrays.asList( \"a\", \"b\", \"d\" ).forEach( \n        ( String e ) -> System.out.print( e + separator ) );\nLambda可能会返回一个值。返回值的类型也是由编译器推测出来的。如果lambda的函数体只有一行的话，那么没有必要显式使用return语句。下面两个代码片段是等价的：\n    \n    Arrays.asList( \"a\", \"b\", \"d\" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );\n和：\n\n    Arrays.asList( \"a\", \"b\", \"d\" ).sort( ( e1, e2 ) -> {\n        int result = e1.compareTo( e2 );\n        return result;\n    } );\n\n语言设计者投入了大量精力来思考如何使现有的函数友好地支持lambda。\n\n最终采取的方法是：增加函数式接口的概念。函数式接口就是一个具有一个方法的普通接口。像这样的接口，可以被隐式转换为lambda表达式。\n\njava.lang.Runnable与java.util.concurrent.Callable是函数式接口最典型的两个例子。\n\n在实际使用过程中，函数式接口是容易出错的：如有某个人在接口定义中增加了另一个方法，这时，这个接口就不再是函数式的了，并且编译过程也会失败。\n\n为了克服函数式接口的这种脆弱性并且能够明确声明接口作为函数式接口的意图，Java8增加了一种特殊的注解@FunctionalInterface（Java8中所有类库的已有接口都添加了@FunctionalInterface注解）。让我们看一下这种函数式接口的定义：\n````\n@FunctionalInterface\npublic interface Functional {\n    void method();\n}\n````\n需要记住的一件事是：默认方法与静态方法并不影响函数式接口的契约，可以任意使用：\n````\n@FunctionalInterface\npublic interface FunctionalDefaultMethods {\n    void method();\n         \n    default void defaultMethod() {            \n    }        \n}\n````\nLambda是Java 8最大的卖点。它具有吸引越来越多程序员到Java平台上的潜力，并且能够在纯Java语言环境中提供一种优雅的方式来支持函数式编程。更多详情可以参考官方文档。\n\n下面看一个例子：\n````\npublic class lambda和函数式编程 {\n    @Test\n    public void test1() {\n        List names = Arrays.asList(\"peter\", \"anna\", \"mike\", \"xenia\");\n\n        Collections.sort(names, new Comparator<String>() {\n            @Override\n            public int compare(String a, String b) {\n                return b.compareTo(a);\n            }\n        });\n        System.out.println(Arrays.toString(names.toArray()));\n    }\n\n    @Test\n    public void test2() {\n        List<String> names = Arrays.asList(\"peter\", \"anna\", \"mike\", \"xenia\");\n\n        Collections.sort(names, (String a, String b) -> {\n            return b.compareTo(a);\n        });\n\n        Collections.sort(names, (String a, String b) -> b.compareTo(a));\n\n        Collections.sort(names, (a, b) -> b.compareTo(a));\n        System.out.println(Arrays.toString(names.toArray()));\n    }\n\n}\n\n    static void add(double a,String b) {\n        System.out.println(a + b);\n    }\n    @Test\n    public void test5() {\n        D d = (a,b) -> add(a,b);\n//        interface D {\n//            void get(int i,String j);\n//        }\n        //这里要求，add的两个参数和get的两个参数吻合并且返回类型也要相等，否则报错\n//        static void add(double a,String b) {\n//            System.out.println(a + b);\n//        }\n    }\n\n    @FunctionalInterface\n    interface D {\n        void get(int i,String j);\n    }\n````\n接下来看看Lambda和匿名内部类的区别\n\n匿名内部类仍然是一个类，只是不需要我们显式指定类名，编译器会自动为该类取名。比如有如下形式的代码：\n````\npublic class LambdaTest {\n    public static void main(String[] args) {\n        new Thread(new Runnable() {\n            @Override\n            public void run() {\n                System.out.println(\"Hello World\");\n            }\n        }).start();\n    }\n}\n````\n编译之后将会产生两个 class 文件：\n\n    LambdaTest.class\n    LambdaTest$1.class\n\n使用 javap -c LambdaTest.class 进一步分析 LambdaTest.class 的字节码，部分结果如下：\n````\n    public static void main(java.lang.String[]);\n    Code:\n        0: new           #2                  // class java/lang/Thread\n        3: dup\n        4: new           #3                  // class com/example/myapplication/lambda/LambdaTest$1\n        7: dup\n        8: invokespecial #4                  // Method com/example/myapplication/lambda/LambdaTest$1.\"<init>\":()V\n        11: invokespecial #5                  // Method java/lang/Thread.\"<init>\":(Ljava/lang/Runnable;)V\n        14: invokevirtual #6                  // Method java/lang/Thread.start:()V\n        17: return\n````\n可以发现在 4: new #3 这一行创建了匿名内部类的对象。\n\n而对于 Lambda表达式的实现， 接下来我们将上面的示例代码使用 Lambda 表达式实现，代码如下：\n````\npublic class LambdaTest {\n    public static void main(String[] args) {\n        new Thread(() -> System.out.println(\"Hello World\")).start();\n    }\n}\n````\n此时编译后只会产生一个文件 LambdaTest.class，再来看看通过 javap 对该文件反编译后的结果：\n````\npublic static void main(java.lang.String[]);\nCode:\n    0: new           #2                  // class java/lang/Thread\n    3: dup\n    4: invokedynamic #3,  0              // InvokeDynamic #0:run:()Ljava/lang/Runnable;\n    9: invokespecial #4                  // Method java/lang/Thread.\"<init>\":(Ljava/lang/Runnable;)V\n    12: invokevirtual #5                  // Method java/lang/Thread.start:()V\n    15: return\n````\n从上面的结果我们发现 Lambda 表达式被封装成了主类的一个私有方法，并通过 invokedynamic 指令进行调用。\n\n因此，我们可以得出结论：Lambda 表达式是通过 invokedynamic 指令实现的，并且书写 Lambda 表达式不会产生新的类。\n\n既然 Lambda 表达式不会创建匿名内部类，那么在 Lambda 表达式中使用 this 关键字时，其指向的是外部类的引用。\n\n### 函数式接口\n\n所谓的函数式接口就是只有一个抽象方法的接口，注意这里说的是抽象方法，因为Java8中加入了默认方法的特性，但是函数式接口是不关心接口中有没有默认方法的。 一般函数式接口可以使用@FunctionalInterface注解的形式来标注表示这是一个函数式接口，该注解标注与否对函数式接口没有实际的影响， 不过一般还是推荐使用该注解，就像使用@Override注解一样。\n\nlambda表达式是如何符合 Java 类型系统的？每个lambda对应于一个给定的类型，用一个接口来说明。而这个被称为函数式接口（functional interface）的接口必须仅仅包含一个抽象方法声明。每个那个类型的lambda表达式都将会被匹配到这个抽象方法上。因此默认的方法并不是抽象的，你可以给你的函数式接口自由地增加默认的方法。\n\n\n我们可以使用任意的接口作为lambda表达式，只要这个接口只包含一个抽象方法。为了保证你的接口满足需求，你需要增加@FunctionalInterface注解。编译器知道这个注解，一旦你试图给这个接口增加第二个抽象方法声明时，它将抛出一个编译器错误。\n\n下面举几个例子\n````    \npublic class 函数式接口使用 {\n    @FunctionalInterface\n    interface A {\n        void say();\n        default void talk() {\n\n        }\n    }\n    @Test\n    public void test1() {\n        A a = () -> System.out.println(\"hello\");\n        a.say();\n    }\n\n    @FunctionalInterface\n    interface B {\n        void say(String i);\n    }\n    public void test2() {\n        //下面两个是等价的，都是通过B接口来引用一个方法，而方法可以直接使用::来作为方法引用\n        B b = System.out::println;\n        B b1 = a -> Integer.parseInt(\"s\");//这里的a其实换成别的也行，只是将方法传给接口作为其方法实现\n        B b2 = Integer::valueOf;//i与方法传入参数的变量类型一直时，可以直接替换\n        B b3 = String::valueOf;\n        //B b4 = Integer::parseInt;类型不符，无法使用\n\n    }\n    @FunctionalInterface\n    interface C {\n        int say(String i);\n    }\n    public void test3() {\n        C c = Integer::parseInt;//方法参数和接口方法的参数一样，可以替换。\n        int i = c.say(\"1\");\n        //当我把C接口的int替换为void时就会报错，因为返回类型不一致。\n        System.out.println(i);\n        //综上所述，lambda表达式提供了一种简便的表达方式，可以将一个方法传到接口中。\n        //函数式接口是只提供一个抽象方法的接口，其方法由lambda表达式注入，不需要写实现类，\n        //也不需要写匿名内部类，可以省去很多代码，比如实现runnable接口。\n        //函数式编程就是指把方法当做一个参数或引用来进行操作。除了普通方法以外，静态方法，构造方法也是可以这样操作的。\n    }\n}\n````\n请记住如果@FunctionalInterface 这个注解被遗漏，此代码依然有效。\n\n### 方法引用\n\nLambda表达式和方法引用\n\n有了函数式接口之后，就可以使用Lambda表达式和方法引用了。其实函数式接口的表中的函数描述符就是Lambda表达式，在函数式接口中Lambda表达式相当于匿名内部类的效果。 举个简单的例子：\n\n````\npublic class TestLambda {\n\n    public static void execute(Runnable runnable) {\n        runnable.run();\n    }\n     \n    public static void main(String[] args) {\n        //Java8之前\n        execute(new Runnable() {\n            @Override\n            public void run() {\n                System.out.println(\"run\");\n            }\n        });\n     \n        //使用Lambda表达式\n        execute(() -> System.out.println(\"run\"));\n    }\n}\n````\n可以看到，相比于使用匿名内部类的方式，Lambda表达式可以使用更少的代码但是有更清晰的表述。注意，Lambda表达式也不是完全等价于匿名内部类的， 两者的不同点在于this的指向和本地变量的屏蔽上。\n\n方法引用可以看作Lambda表达式的更简洁的一种表达形式，使用::操作符，方法引用主要有三类：\n\n    指向静态方法的方法引用(例如Integer的parseInt方法，写作Integer::parseInt)；\n    \n    指向任意类型实例方法的方法引用(例如String的length方法，写作String::length)；\n    \n    指向现有对象的实例方法的方法引用(例如假设你有一个本地变量localVariable用于存放Variable类型的对象，它支持实例方法getValue，那么可以写成localVariable::getValue)。\n\n举个方法引用的简单的例子：\n\n    Function<String, Integer> stringToInteger = (String s) -> Integer.parseInt(s);\n\n//使用方法引用\n\n    Function<String, Integer> stringToInteger = Integer::parseInt;\n\n方法引用中还有一种特殊的形式，构造函数引用，假设一个类有一个默认的构造函数，那么使用方法引用的形式为：\n\n    Supplier<SomeClass> c1 = SomeClass::new;\n    SomeClass s1 = c1.get();\n\n//等价于\n\n    Supplier<SomeClass> c1 = () -> new SomeClass();\n    SomeClass s1 = c1.get();\n如果是构造函数有一个参数的情况：\n\n    Function<Integer, SomeClass> c1 = SomeClass::new;\n    SomeClass s1 = c1.apply(100);\n\n//等价于\n     \n    Function<Integer, SomeClass> c1 = i -> new SomeClass(i);\n    SomeClass s1 = c1.apply(100);\n\n### 接口的默认方法\n\nJava 8 使我们能够使用default 关键字给接口增加非抽象的方法实现。这个特性也被叫做 扩展方法（Extension Methods）。如下例所示：\n````\npublic class 接口的默认方法 {\n    class B implements A {\n//        void a(){}实现类方法不能重名\n    }\n    interface A {\n        //可以有多个默认方法\n        public default void a(){\n            System.out.println(\"a\");\n        }\n        public default void b(){\n            System.out.println(\"b\");\n        }\n        //报错static和default不能同时使用\n//        public static default void c(){\n//            System.out.println(\"c\");\n//        }\n    }\n    public void test() {\n        B b = new B();\n        b.a();\n\n    }\n}\n````\n默认方法出现的原因是为了对原有接口的扩展，有了默认方法之后就不怕因改动原有的接口而对已经使用这些接口的程序造成的代码不兼容的影响。 在Java8中也对一些接口增加了一些默认方法，比如Map接口等等。一般来说，使用默认方法的场景有两个：可选方法和行为的多继承。\n\n默认方法的使用相对来说比较简单，唯一要注意的点是如何处理默认方法的冲突。关于如何处理默认方法的冲突可以参考以下三条规则：\n\n类中的方法优先级最高。类或父类中声明的方法的优先级高于任何声明为默认方法的优先级。\n\n如果无法依据第一条规则进行判断，那么子接口的优先级更高：函数签名相同时，优先选择拥有最具体实现的默认方法的接口。即如果B继承了A，那么B就比A更具体。\n\n最后，如果还是无法判断，继承了多个接口的类必须通过显式覆盖和调用期望的方法，显式地选择使用哪一个默认方法的实现。那么如何显式地指定呢:\n````\n    public class C implements B, A {\n     \n        public void hello() {\n            B.super().hello();    \n        }\n     \n    }\n````\n使用X.super.m(..)显式地调用希望调用的方法。\n\nJava 8用默认方法与静态方法这两个新概念来扩展接口的声明。默认方法使接口有点像Traits（Scala中特征(trait)类似于Java中的Interface，但它可以包含实现代码，也就是目前Java8新增的功能），但与传统的接口又有些不一样，它允许在已有的接口中添加新方法，而同时又保持了与旧版本代码的兼容性。\n\n默认方法与抽象方法不同之处在于抽象方法必须要求实现，但是默认方法则没有这个要求。相反，每个接口都必须提供一个所谓的默认实现，这样所有的接口实现者将会默认继承它（如果有必要的话，可以覆盖这个默认实现）。让我们看看下面的例子：\n````\nprivate interface Defaulable {\n    // Interfaces now allow default methods, the implementer may or \n    // may not implement (override) them.\n    default String notRequired() { \n        return \"Default implementation\"; \n    }        \n}\n         \nprivate static class DefaultableImpl implements Defaulable {\n}\n     \nprivate static class OverridableImpl implements Defaulable {\n    @Override\n    public String notRequired() {\n        return \"Overridden implementation\";\n    }\n}\n````\nDefaulable接口用关键字default声明了一个默认方法notRequired()，Defaulable接口的实现者之一DefaultableImpl实现了这个接口，并且让默认方法保持原样。Defaulable接口的另一个实现者OverridableImpl用自己的方法覆盖了默认方法。\n\nJava 8带来的另一个有趣的特性是接口可以声明（并且可以提供实现）静态方法。例如：\n\n````\n    private interface DefaulableFactory {\n        // Interfaces now allow static methods\n        static Defaulable create( Supplier< Defaulable > supplier ) {\n            return supplier.get();\n        }\n    }\n````\n下面的一小段代码片段把上面的默认方法与静态方法黏合到一起。\n\n````\n    public static void main( String[] args ) {\n        Defaulable defaulable = DefaulableFactory.create( DefaultableImpl::new );\n        System.out.println( defaulable.notRequired() );\n             \n        defaulable = DefaulableFactory.create( OverridableImpl::new );\n        System.out.println( defaulable.notRequired() );\n    }\n````\n这个程序的控制台输出如下：\n\nDefault implementation\nOverridden implementation\n在JVM中，默认方法的实现是非常高效的，并且通过字节码指令为方法调用提供了支持。默认方法允许继续使用现有的Java接口，而同时能够保障正常的编译过程。这方面好的例子是大量的方法被添加到java.util.Collection接口中去：stream()，parallelStream()，forEach()，removeIf()，……\n\n尽管默认方法非常强大，但是在使用默认方法时我们需要小心注意一个地方：在声明一个默认方法前，请仔细思考是不是真的有必要使用默认方法，因为默认方法会带给程序歧义，并且在复杂的继承体系中容易产生编译错误。更多详情请参考官方文档\n    \n    \n### 重复注解\n自从Java 5引入了注解机制，这一特性就变得非常流行并且广为使用。然而，使用注解的一个限制是相同的注解在同一位置只能声明一次，不能声明多次。Java 8打破了这条规则，引入了重复注解机制，这样相同的注解可以在同一地方声明多次。\n\n重复注解机制本身必须用@Repeatable注解。事实上，这并不是语言层面上的改变，更多的是编译器的技巧，底层的原理保持不变。让我们看一个快速入门的例子：\n````\npackage com.javacodegeeks.java8.repeatable.annotations;\n \nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Repeatable;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n \npublic class RepeatingAnnotations {\n    @Target( ElementType.TYPE )\n    @Retention( RetentionPolicy.RUNTIME )\n    public @interface Filters {\n        Filter[] value();\n    }\n     \n    @Target( ElementType.TYPE )\n    @Retention( RetentionPolicy.RUNTIME )\n    @Repeatable( Filters.class )\n    public @interface Filter {\n        String value();\n    };\n     \n    @Filter( \"filter1\" )\n    @Filter( \"filter2\" )\n    public interface Filterable {        \n    }\n     \n    public static void main(String[] args) {\n        for( Filter filter: Filterable.class.getAnnotationsByType( Filter.class ) ) {\n            System.out.println( filter.value() );\n        }\n    }\n}\n````\n正如我们看到的，这里有个使用@Repeatable( Filters.class )注解的注解类Filter，Filters仅仅是Filter注解的数组，但Java编译器并不想让程序员意识到Filters的存在。这样，接口Filterable就拥有了两次Filter（并没有提到Filter）注解。\n\n同时，反射相关的API提供了新的函数getAnnotationsByType()来返回重复注解的类型（请注意Filterable.class.getAnnotation( Filters.class )经编译器处理后将会返回Filters的实例）。\n\n程序输出结果如下：\n\n    filter1\n    filter2\n更多详情请参考官方文档\n\n## Java编译器的新特性\n\n### 方法参数名字可以反射获取\n\n很长一段时间里，Java程序员一直在发明不同的方式使得方法参数的名字能保留在Java字节码中，并且能够在运行时获取它们（比如，Paranamer类库）。最终，在Java 8中把这个强烈要求的功能添加到语言层面（通过反射API与Parameter.getName()方法）与字节码文件（通过新版的javac的–parameters选项）中。\n````\npackage com.javacodegeeks.java8.parameter.names;\n\nimport java.lang.reflect.Method;\nimport java.lang.reflect.Parameter;\n\npublic class ParameterNames {\n    public static void main(String[] args) throws Exception {\n        Method method = ParameterNames.class.getMethod( \"main\", String[].class );\n        for( final Parameter parameter: method.getParameters() ) {\n            System.out.println( \"Parameter: \" + parameter.getName() );\n        }\n    }\n}\n````\n如果不使用–parameters参数来编译这个类，然后运行这个类，会得到下面的输出：\n\nParameter: arg0\n如果使用–parameters参数来编译这个类，程序的结构会有所不同（参数的真实名字将会显示出来）：\n\nParameter: args\n\n## Java 类库的新特性\nJava 8 通过增加大量新类，扩展已有类的功能的方式来改善对并发编程、函数式编程、日期/时间相关操作以及其他更多方面的支持。\n\n### Optional\n到目前为止，臭名昭著的空指针异常是导致Java应用程序失败的最常见原因。以前，为了解决空指针异常，Google公司著名的Guava项目引入了Optional类，Guava通过使用检查空值的方式来防止代码污染，它鼓励程序员写更干净的代码。受到Google Guava的启发，Optional类已经成为Java 8类库的一部分。\n\nOptional实际上是个容器：它可以保存类型T的值，或者仅仅保存null。Optional提供很多有用的方法，这样我们就不用显式进行空值检测。更多详情请参考官方文档。\n\n我们下面用两个小例子来演示如何使用Optional类：一个允许为空值，一个不允许为空值。\n````\n    public class 空指针Optional {\n        public static void main(String[] args) {\n    \n            //使用of方法，仍然会报空指针异常\n    //        Optional optional = Optional.of(null);\n    //        System.out.println(optional.get());\n    \n            //抛出没有该元素的异常\n            //Exception in thread \"main\" java.util.NoSuchElementException: No value present\n    //        at java.util.Optional.get(Optional.java:135)\n    //        at com.javase.Java8.空指针Optional.main(空指针Optional.java:14)\n    //        Optional optional1 = Optional.ofNullable(null);\n    //        System.out.println(optional1.get());\n            Optional optional = Optional.ofNullable(null);\n            System.out.println(optional.isPresent());\n            System.out.println(optional.orElse(0));//当值为空时给与初始值\n            System.out.println(optional.orElseGet(() -> new String[]{\"a\"}));//使用回调函数设置默认值\n            //即使传入Optional容器的元素为空，使用optional.isPresent()方法也不会报空指针异常\n            //所以通过optional.orElse这种方式就可以写出避免空指针异常的代码了\n            //输出Optional.empty。\n        }\n    }\n````\n如果Optional类的实例为非空值的话，isPresent()返回true，否从返回false。为了防止Optional为空值，orElseGet()方法通过回调函数来产生一个默认值。map()函数对当前Optional的值进行转化，然后返回一个新的Optional实例。orElse()方法和orElseGet()方法类似，但是orElse接受一个默认值而不是一个回调函数。下面是这个程序的输出：\n\nFull Name is set? false\nFull Name: [none]\nHey Stranger!\n让我们来看看另一个例子：\n\n\n    Optional< String > firstName = Optional.of( \"Tom\" );\n    System.out.println( \"First Name is set? \" + firstName.isPresent() );        \n    System.out.println( \"First Name: \" + firstName.orElseGet( () -> \"[none]\" ) ); \n    System.out.println( firstName.map( s -> \"Hey \" + s + \"!\" ).orElse( \"Hey Stranger!\" ) );\n    System.out.println();\n\n下面是程序的输出：\n\nFirst Name is set? true\nFirst Name: Tom\nHey Tom!\n\n### Stream\n最新添加的Stream API（java.util.stream） 把真正的函数式编程风格引入到Java中。这是目前为止对Java类库最好的补充，因为Stream API可以极大提供Java程序员的生产力，让程序员写出高效率、干净、简洁的代码。\n\nStream API极大简化了集合框架的处理（但它的处理的范围不仅仅限于集合框架的处理，这点后面我们会看到）。让我们以一个简单的Task类为例进行介绍：\n\nTask类有一个分数的概念（或者说是伪复杂度），其次是还有一个值可以为OPEN或CLOSED的状态.让我们引入一个Task的小集合作为演示例子：\n````\n    final Collection< Task > tasks = Arrays.asList(\n        new Task( Status.OPEN, 5 ),\n        new Task( Status.OPEN, 13 ),\n        new Task( Status.CLOSED, 8 ) \n    );\n````\n我们下面要讨论的第一个问题是所有状态为OPEN的任务一共有多少分数？在Java 8以前，一般的解决方式用foreach循环，但是在Java 8里面我们可以使用stream：一串支持连续、并行聚集操作的元素。\n````\n    // Calculate total points of all active tasks using sum()\n    final long totalPointsOfOpenTasks = tasks\n        .stream()\n        .filter( task -> task.getStatus() == Status.OPEN )\n        .mapToInt( Task::getPoints )\n        .sum();\n             \n    System.out.println( \"Total points: \" + totalPointsOfOpenTasks );\n````\n程序在控制台上的输出如下：\n\n    Total points: 18\n\n这里有几个注意事项。\n\n第一，task集合被转换化为其相应的stream表示。然后，filter操作过滤掉状态为CLOSED的task。\n\n下一步，mapToInt操作通过Task::getPoints这种方式调用每个task实例的getPoints方法把Task的stream转化为Integer的stream。最后，用sum函数把所有的分数加起来，得到最终的结果。\n\n在继续讲解下面的例子之前，关于stream有一些需要注意的地方（详情在这里）.stream操作被分成了中间操作与最终操作这两种。\n\n中间操作返回一个新的stream对象。中间操作总是采用惰性求值方式，运行一个像filter这样的中间操作实际上没有进行任何过滤，相反它在遍历元素时会产生了一个新的stream对象，这个新的stream对象包含原始stream\n中符合给定谓词的所有元素。\n\n像forEach、sum这样的最终操作可能直接遍历stream，产生一个结果或副作用。当最终操作执行结束之后，stream管道被认为已经被消耗了，没有可能再被使用了。在大多数情况下，最终操作都是采用及早求值方式，及早完成底层数据源的遍历。\n\nstream另一个有价值的地方是能够原生支持并行处理。让我们来看看这个算task分数和的例子。\n\nstream另一个有价值的地方是能够原生支持并行处理。让我们来看看这个算task分数和的例子。\n````\n    // Calculate total points of all tasks\n    final double totalPoints = tasks\n       .stream()\n       .parallel()\n       .map( task -> task.getPoints() ) // or map( Task::getPoints ) \n       .reduce( 0, Integer::sum );\n         \n    System.out.println( \"Total points (all tasks): \" + totalPoints );\n````\n这个例子和第一个例子很相似，但这个例子的不同之处在于这个程序是并行运行的，其次使用reduce方法来算最终的结果。\n下面是这个例子在控制台的输出：\n\nTotal points (all tasks): 26.0\n经常会有这个一个需求：我们需要按照某种准则来对集合中的元素进行分组。Stream也可以处理这样的需求，下面是一个例子：\n\n````\n    // Group tasks by their status\n    final Map< Status, List< Task > > map = tasks\n        .stream()\n        .collect( Collectors.groupingBy( Task::getStatus ) );\n    System.out.println( map );\n````\n\n这个例子的控制台输出如下：\n\n    {CLOSED=[[CLOSED, 8]], OPEN=[[OPEN, 5], [OPEN, 13]]}\n\n让我们来计算整个集合中每个task分数（或权重）的平均值来结束task的例子。\n\n````\n    // Calculate the weight of each tasks (as percent of total points) \n    final Collection< String > result = tasks\n        .stream()                                        // Stream< String >\n        .mapToInt( Task::getPoints )                     // IntStream\n        .asLongStream()                                  // LongStream\n        .mapToDouble( points -> points / totalPoints )   // DoubleStream\n        .boxed()                                         // Stream< Double >\n        .mapToLong( weigth -> ( long )( weigth * 100 ) ) // LongStream\n        .mapToObj( percentage -> percentage + \"%\" )      // Stream< String> \n        .collect( Collectors.toList() );                 // List< String > \n             \n    System.out.println( result );\n````\n\n下面是这个例子的控制台输出：\n\n[19%, 50%, 30%]\n最后，就像前面提到的，Stream API不仅仅处理Java集合框架。像从文本文件中逐行读取数据这样典型的I/O操作也很适合用Stream API来处理。下面用一个例子来应证这一点。\n````\n    final Path path = new File( filename ).toPath();\n    try( Stream< String > lines = Files.lines( path, StandardCharsets.UTF_8 ) ) {\n        lines.onClose( () -> System.out.println(\"Done!\") ).forEach( System.out::println );\n    }\n````\n\n对一个stream对象调用onClose方法会返回一个在原有功能基础上新增了关闭功能的stream对象，当对stream对象调用close()方法时，与关闭相关的处理器就会执行。\n\nStream API、Lambda表达式与方法引用在接口默认方法与静态方法的配合下是Java 8对现代软件开发范式的回应。更多详情请参考官方文档。\n\n### Date/Time API (JSR 310)\nJava 8通过发布新的Date-Time API (JSR 310)来进一步加强对日期与时间的处理。对日期与时间的操作一直是Java程序员最痛苦的地方之一。标准的 java.util.Date以及后来的java.util.Calendar一点没有改善这种情况（可以这么说，它们一定程度上更加复杂）。\n\n这种情况直接导致了Joda-Time——一个可替换标准日期/时间处理且功能非常强大的Java API的诞生。Java 8新的Date-Time API (JSR 310)在很大程度上受到Joda-Time的影响，并且吸取了其精髓。新的java.time包涵盖了所有处理日期，时间，日期/时间，时区，时刻（instants），过程（during）与时钟（clock）的操作。在设计新版API时，十分注重与旧版API的兼容性：不允许有任何的改变（从java.util.Calendar中得到的深刻教训）。如果需要修改，会返回这个类的一个新实例。\n\n让我们用例子来看一下新版API主要类的使用方法。第一个是Clock类，它通过指定一个时区，然后就可以获取到当前的时刻，日期与时间。Clock可以替换System.currentTimeMillis()与TimeZone.getDefault()。\n````\n    // Get the system clock as UTC offset \n    final Clock clock = Clock.systemUTC();\n    System.out.println( clock.instant() );\n    System.out.println( clock.millis() );\n````\n下面是程序在控制台上的输出：\n\n    2014-04-12T15:19:29.282Z\n    1397315969360\n\n我们需要关注的其他类是LocaleDate与LocalTime。LocaleDate只持有ISO-8601格式且无时区信息的日期部分。相应的，LocaleTime只持有ISO-8601格式且无时区信息的时间部分。LocaleDate与LocalTime都可以从Clock中得到。\n````\n    // Get the local date and local time\n    final LocalDate date = LocalDate.now();\n    final LocalDate dateFromClock = LocalDate.now( clock );\n             \n    System.out.println( date );\n    System.out.println( dateFromClock );\n             \n    // Get the local date and local time\n    final LocalTime time = LocalTime.now();\n    final LocalTime timeFromClock = LocalTime.now( clock );\n         \n    System.out.println( time );\n    System.out.println( timeFromClock );\n````\n下面是程序在控制台上的输出：\n\n    2014-04-12\n    2014-04-12\n    11:25:54.568\n    15:25:54.568\n\n下面是程序在控制台上的输出：\n\n    2014-04-12T11:47:01.017-04:00[America/New_York]\n    2014-04-12T15:47:01.017Z\n    2014-04-12T08:47:01.017-07:00[America/Los_Angeles]\n\n最后，让我们看一下Duration类：在秒与纳秒级别上的一段时间。Duration使计算两个日期间的不同变的十分简单。下面让我们看一个这方面的例子。\n````\n    // Get duration between two dates\n    final LocalDateTime from = LocalDateTime.of( 2014, Month.APRIL, 16, 0, 0, 0 );\n    final LocalDateTime to = LocalDateTime.of( 2015, Month.APRIL, 16, 23, 59, 59 );\n     \n    final Duration duration = Duration.between( from, to );\n    System.out.println( \"Duration in days: \" + duration.toDays() );\n    System.out.println( \"Duration in hours: \" + duration.toHours() );\n````\n上面的例子计算了两个日期2014年4月16号与2014年4月16号之间的过程。下面是程序在控制台上的输出：\n\nDuration in days: 365\nDuration in hours: 8783\n对Java 8在日期/时间API的改进整体印象是非常非常好的。一部分原因是因为它建立在“久战杀场”的Joda-Time基础上，另一方面是因为用来大量的时间来设计它，并且这次程序员的声音得到了认可。更多详情请参考官方文档。\n\n\n### 并行（parallel）数组\nJava 8增加了大量的新方法来对数组进行并行处理。可以说，最重要的是parallelSort()方法，因为它可以在多核机器上极大提高数组排序的速度。下面的例子展示了新方法（parallelXxx）的使用。\n````\n    package com.javacodegeeks.java8.parallel.arrays;\n     \n    import java.util.Arrays;\n    import java.util.concurrent.ThreadLocalRandom;\n     \n    public class ParallelArrays {\n        public static void main( String[] args ) {\n            long[] arrayOfLong = new long [ 20000 ];        \n             \n            Arrays.parallelSetAll( arrayOfLong, \n                index -> ThreadLocalRandom.current().nextInt( 1000000 ) );\n            Arrays.stream( arrayOfLong ).limit( 10 ).forEach( \n                i -> System.out.print( i + \" \" ) );\n            System.out.println();\n             \n            Arrays.parallelSort( arrayOfLong );     \n            Arrays.stream( arrayOfLong ).limit( 10 ).forEach( \n                i -> System.out.print( i + \" \" ) );\n            System.out.println();\n        }\n    }\n````\n上面的代码片段使用了parallelSetAll()方法来对一个有20000个元素的数组进行随机赋值。然后，调用parallelSort方法。这个程序首先打印出前10个元素的值，之后对整个数组排序。这个程序在控制台上的输出如下（请注意数组元素是随机生产的）：\n\nUnsorted: 591217 891976 443951 424479 766825 351964 242997 642839 119108 552378 \nSorted: 39 220 263 268 325 607 655 678 723 793\n\n### CompletableFuture\n\n在Java8之前，我们会使用JDK提供的Future接口来进行一些异步的操作，其实CompletableFuture也是实现了Future接口， 并且基于ForkJoinPool来执行任务，因此本质上来讲，CompletableFuture只是对原有API的封装， 而使用CompletableFuture与原来的Future的不同之处在于可以将两个Future组合起来，或者如果两个Future是有依赖关系的，可以等第一个执行完毕后再实行第二个等特性。\n\n**先来看看基本的使用方式：**\n````\n    public Future<Double> getPriceAsync(final String product) {\n        final CompletableFuture<Double> futurePrice = new CompletableFuture<>();\n        new Thread(() -> {\n            double price = calculatePrice(product);\n            futurePrice.complete(price);  //完成后使用complete方法，设置future的返回值\n        }).start();\n        return futurePrice;\n    }\n````\n得到Future之后就可以使用get方法来获取结果，CompletableFuture提供了一些工厂方法来简化这些API，并且使用函数式编程的方式来使用这些API，例如：\n\nFufure<Double> price = CompletableFuture.supplyAsync(() -> calculatePrice(product));  \n代码是不是一下子简洁了许多呢。之前说了，CompletableFuture可以组合多个Future，不管是Future之间有依赖的，还是没有依赖的。 \n\n**如果第二个请求依赖于第一个请求的结果，那么可以使用thenCompose方法来组合两个Future**\n````\n    public List<String> findPriceAsync(String product) {\n        List<CompletableFutute<String>> priceFutures = tasks.stream()\n        .map(task -> CompletableFuture.supplyAsync(() -> task.getPrice(product),executor))\n        .map(future -> future.thenApply(Work::parse))\n        .map(future -> future.thenCompose(work -> CompletableFuture.supplyAsync(() -> Count.applyCount(work), executor)))\n        .collect(Collectors.toList());\n    \n        return priceFutures.stream().map(CompletableFuture::join).collect(Collectors.toList());\n    }\n````\n上面这段代码使用了thenCompose来组合两个CompletableFuture。supplyAsync方法第二个参数接受一个自定义的Executor。 首先使用CompletableFuture执行一个任务，调用getPrice方法，得到一个Future，之后使用thenApply方法，将Future的结果应用parse方法， 之后再使用执行完parse之后的结果作为参数再执行一个applyCount方法，然后收集成一个CompletableFuture<String>的List， 最后再使用一个流，调用CompletableFuture的join方法，这是为了等待所有的异步任务执行完毕，获得最后的结果。\n\n注意，这里必须使用两个流，如果在一个流里调用join方法，那么由于Stream的延迟特性，所有的操作还是会串行的执行，并不是异步的。\n\n**再来看一个两个Future之间没有依赖关系的例子：**\n````\n    Future<String> futurePriceInUsd = CompletableFuture.supplyAsync(() -> shop.getPrice(“price1”))\n                                        .thenCombine(CompletableFuture.supplyAsync(() -> shop.getPrice(“price2”)), (s1, s2) -> s1 + s2);\n````\n这里有两个异步的任务，使用thenCombine方法来组合两个Future，thenCombine方法的第二个参数就是用来合并两个Future方法返回值的操作函数。\n\n有时候，我们并不需要等待所有的异步任务结束，只需要其中的一个完成就可以了，CompletableFuture也提供了这样的方法：\n````\n    //假设getStream方法返回一个Stream<CompletableFuture<String>>\n    CompletableFuture[] futures = getStream(“listen”).map(f -> f.thenAccept(System.out::println)).toArray(CompletableFuture[]::new);\n    //等待其中的一个执行完毕\n    CompletableFuture.anyOf(futures).join();\n    使用anyOf方法来响应CompletableFuture的completion事件。\n````\n## Java虚拟机（JVM）的新特性\nPermGen空间被移除了，取而代之的是Metaspace（JEP 122）。JVM选项-XX:PermSize与-XX:MaxPermSize分别被-XX:MetaSpaceSize与-XX:MaxMetaspaceSize所代替。\n\n\n## 总结\n更多展望：Java 8通过发布一些可以增加程序员生产力的特性来推进这个伟大的平台的进步。现在把生产环境迁移到Java 8还为时尚早，但是在接下来的几个月里，它会被大众慢慢的接受。毫无疑问，现在是时候让你的代码与Java 8兼容，并且在Java 8足够安全稳定的时候迁移到Java 8。\n\n## 参考文章\n\nhttps://blog.csdn.net/shuaicihai/article/details/72615495\nhttps://blog.csdn.net/qq_34908167/article/details/79286697\nhttps://www.jianshu.com/p/4df02599aeb2\nhttps://www.cnblogs.com/yangzhilong/p/10973006.html\nhttps://www.cnblogs.com/JackpotHan/p/9701147.html\n\n"
  },
  {
    "path": "docs/Java/basic/JavaIO流.md",
    "content": "# 目录\n  * [IO概述](#io概述)\n    * [什么是Java IO流](#什么是java-io流)\n    * [IO文件](#io文件)\n    * [字符流和字节流](#字符流和字节流)\n    * [IO管道](#io管道)\n    * [Java IO：网络](#java-io：网络)\n    * [字节和字符数组](#字节和字符数组)\n    * [System.in, System.out, System.err](#systemin-systemout-systemerr)\n    * [字符流的Buffered和Filter](#字符流的buffered和filter)\n  * [JavaIO流面试题](#javaio流面试题)\n    * [什么是IO流？](#什么是io流？)\n    * [字节流和字符流的区别。](#字节流和字符流的区别。)\n    * [Java中流类的超类主要由那些？](#java中流类的超类主要由那些？)\n    * [FileInputStream和FileOutputStream是什么？](#fileinputstream和fileoutputstream是什么？)\n    * [System.out.println()是什么？](#systemoutprintln是什么？)\n    * [什么是Filter流？](#什么是filter流？)\n    * [有哪些可用的Filter流？](#有哪些可用的filter流？)\n    * [在文件拷贝的时候，那一种流可用提升更多的性能？](#在文件拷贝的时候，那一种流可用提升更多的性能？)\n    * [说说管道流(Piped Stream)](#说说管道流piped-stream)\n    * [说说File类](#说说file类)\n    * [说说RandomAccessFile?](#说说randomaccessfile)\n  * [参考文章](#参考文章)\n\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章首发于我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《夯实Java基础系列博文》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n该系列博文会告诉你如何从入门到进阶，一步步地学习Java基础知识，并上手进行实战，接着了解每个Java知识点背后的实现原理，更完整地了解整个Java技术体系，形成自己的知识框架。为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n\n<!-- more -->\n\n\n本文参考\n\n并发编程网 – ifeve.com\n\n## IO概述\n\n> 在这一小节，我会试着给出Java IO(java.io)包下所有类的概述。更具体地说，我会根据类的用途对类进行分组。这个分组将会使你在未来的工作中，进行类的用途判定时，或者是为某个特定用途选择类时变得更加容易。\n\n\n**输入和输出**\n\n    术语“输入”和“输出”有时候会有一点让人疑惑。一个应用程序的输入往往是另外一个应用程序的输出\n    \n    那么OutputStream流到底是一个输出到目的地的流呢，还是一个产生输出的流？InputStream流到底会不会输出它的数据给读取数据的程序呢？就我个人而言，在第一天学习Java IO的时候我就感觉到了一丝疑惑。\n    \n    为了消除这个疑惑，我试着给输入和输出起一些不一样的别名，让它们从概念上与数据的来源和数据的流向相联系。\n\nJava的IO包主要关注的是从原始数据源的读取以及输出原始数据到目标媒介。以下是最典型的数据源和目标媒介：\n    \n    文件\n    管道\n    网络连接\n    内存缓存\n    System.in, System.out, System.error(注：Java标准输入、输出、错误输出)\n下面这张图描绘了一个程序从数据源读取数据，然后将数据输出到其他媒介的原理：\n\n![](http://ifeve.com/wp-content/uploads/2014/10/%E6%97%A0%E6%A0%87%E9%A2%981.png)\n\n**流**\n\n    在Java IO中，流是一个核心的概念。流从概念上来说是一个连续的数据流。你既可以从流中读取数据，也可以往流中写数据。流与数据源或者数据流向的媒介相关联。在Java IO中流既可以是字节流(以字节为单位进行读写)，也可以是字符流(以字符为单位进行读写)。\n\n类InputStream, OutputStream, Reader 和Writer\n一个程序需要InputStream或者Reader从数据源读取数据，需要OutputStream或者Writer将数据写入到目标媒介中。以下的图说明了这一点：\n\n![](http://ifeve.com/wp-content/uploads/2014/10/%E6%97%A0%E6%A0%87%E9%A2%982.png)\n\nInputStream和Reader与数据源相关联，OutputStream和writer与目标媒介相关联。\n\n**Java IO的用途和特征**\n\nJava IO中包含了许多InputStream、OutputStream、Reader、Writer的子类。这样设计的原因是让每一个类都负责不同的功能。这也就是为什么IO包中有这么多不同的类的缘故。各类用途汇总如下：\n    \n    文件访问\n    网络访问\n    内存缓存访问\n    线程内部通信(管道)\n    缓冲\n    过滤\n    解析\n    读写文本 (Readers / Writers)\n    读写基本类型数据 (long, int etc.)\n    读写对象\n\n当通读过Java IO类的源代码之后，我们很容易就能了解这些用途。这些用途或多或少让我们更加容易地理解，不同的类用于针对不同业务场景。\n\nJava IO类概述表\n已经讨论了数据源、目标媒介、输入、输出和各类不同用途的Java IO类，接下来是一张通过输入、输出、基于字节或者字符、以及其他比如缓冲、解析之类的特定用途划分的大部分Java IO类的表格。\n\n![](http://ifeve.com/wp-content/uploads/2014/10/QQ%E6%88%AA%E5%9B%BE20141020174145.png)\n\nJava IO类图\n\n![](https://images.cnblogs.com/cnblogs_com/davidgu/java_io_hierarchy.jpg)\n\n### 什么是Java IO流\n\nJava IO流是既可以从中读取，也可以写入到其中的数据流。正如这个系列教程之前提到过的，流通常会与数据源、数据流向目的地相关联，比如文件、网络等等。\n\n流和数组不一样，不能通过索引读写数据。在流中，你也不能像数组那样前后移动读取数据，除非使用RandomAccessFile 处理文件。流仅仅只是一个连续的数据流。\n\n某些类似PushbackInputStream 流的实现允许你将数据重新推回到流中，以便重新读取。然而你只能把有限的数据推回流中，并且你不能像操作数组那样随意读取数据。流中的数据只能够顺序访问。\n>\n> Java IO流通常是基于字节或者基于字符的。字节流通常以“stream”命名，比如InputStream和OutputStream。除了DataInputStream 和DataOutputStream 还能够读写int, long, float和double类型的值以外，其他流在一个操作时间内只能读取或者写入一个原始字节。\n>\n> 字符流通常以“Reader”或者“Writer”命名。字符流能够读写字符(比如Latin1或者Unicode字符)。可以浏览Java Readers and Writers获取更多关于字符流输入输出的信息。\n\n**InputStream**\n\njava.io.InputStream类是所有Java IO输入流的基类。如果你正在开发一个从流中读取数据的组件，请尝试用InputStream替代任何它的子类(比如FileInputStream)进行开发。这么做能够让你的代码兼容任何类型而非某种确定类型的输入流。\n\n**组合流**\n\n你可以将流整合起来以便实现更高级的输入和输出操作。比如，一次读取一个字节是很慢的，所以可以从磁盘中一次读取一大块数据，然后从读到的数据块中获取字节。为了实现缓冲，可以把InputStream包装到BufferedInputStream中。\n\n代码示例\n    InputStream input = new BufferedInputStream(new FileInputStream(\"c:\\\\data\\\\input-file.txt\"));\n    \n> 缓冲同样可以应用到OutputStream中。你可以实现将大块数据批量地写入到磁盘(或者相应的流)中，这个功能由BufferedOutputStream实现。\n>\n> 缓冲只是通过流整合实现的其中一个效果。你可以把InputStream包装到PushbackInputStream中，之后可以将读取过的数据推回到流中重新读取，在解析过程中有时候这样做很方便。或者，你可以将两个InputStream整合成一个SequenceInputStream。\n>\n> 将不同的流整合到一个链中，可以实现更多种高级操作。通过编写包装了标准流的类，可以实现你想要的效果和过滤器。\n\n### IO文件\n\n在Java应用程序中，文件是一种常用的数据源或者存储数据的媒介。所以这一小节将会对Java中文件的使用做一个简短的概述。这篇文章不会对每一个技术细节都做出解释，而是会针对文件存取的方法提供给你一些必要的知识点。在之后的文章中，将会更加详细地描述这些方法或者类，包括方法示例等等。\n\n\n**通过Java IO读文件**\n\n    如果你需要在不同端之间读取文件，你可以根据该文件是二进制文件还是文本文件来选择使用FileInputStream或者FileReader。\n    \n    这两个类允许你从文件开始到文件末尾一次读取一个字节或者字符，或者将读取到的字节写入到字节数组或者字符数组。你不必一次性读取整个文件，相反你可以按顺序地读取文件中的字节和字符。\n\n如果你需要跳跃式地读取文件其中的某些部分，可以使用RandomAccessFile。\n\n**通过Java IO写文件**\n\n    如果你需要在不同端之间进行文件的写入，你可以根据你要写入的数据是二进制型数据还是字符型数据选用FileOutputStream或者FileWriter。\n    \n    你可以一次写入一个字节或者字符到文件中，也可以直接写入一个字节数组或者字符数据。数据按照写入的顺序存储在文件当中。\n\n**通过Java IO随机存取文件**\n\n正如我所提到的，你可以通过RandomAccessFile对文件进行随机存取。\n    \n    随机存取并不意味着你可以在真正随机的位置进行读写操作，它只是意味着你可以跳过文件中某些部分进行操作，并且支持同时读写，不要求特定的存取顺序。\n    \n    这使得RandomAccessFile可以覆盖一个文件的某些部分、或者追加内容到它的末尾、或者删除它的某些内容，当然它也可以从文件的任何位置开始读取文件。\n\n下面是具体例子：\n````\n    @Test\n        //文件流范例，打开一个文件的输入流，读取到字节数组，再写入另一个文件的输出流\n        public void test1() {\n            try {\n                FileInputStream fileInputStream = new FileInputStream(new File(\"a.txt\"));\n                FileOutputStream fileOutputStream = new FileOutputStream(new File(\"b.txt\"));\n                byte []buffer = new byte[128];\n                while (fileInputStream.read(buffer) != -1) {\n                    fileOutputStream.write(buffer);\n                }\n                //随机读写，通过mode参数来决定读或者写\n                RandomAccessFile randomAccessFile = new RandomAccessFile(new File(\"c.txt\"), \"rw\");\n            } catch (FileNotFoundException e) {\n                e.printStackTrace();\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n````\n### 字符流和字节流\n\nJava IO的Reader和Writer除了基于字符之外，其他方面都与InputStream和OutputStream非常类似。他们被用于读写文本。InputStream和OutputStream是基于字节的，还记得吗？\n\nReader\nReader类是Java IO中所有Reader的基类。子类包括BufferedReader，PushbackReader，InputStreamReader，StringReader和其他Reader。\n\nWriter\nWriter类是Java IO中所有Writer的基类。子类包括BufferedWriter和PrintWriter等等。\n\n这是一个简单的Java IO Reader的例子：\n````\nReader reader = new FileReader(\"c:\\\\data\\\\myfile.txt\");\n\nint data = reader.read();\n\nwhile(data != -1){\n\n    char dataChar = (char) data;\n\n    data = reader.read();\n\n}\n````\n你通常会使用Reader的子类，而不会直接使用Reader。Reader的子类包括InputStreamReader，CharArrayReader，FileReader等等。可以查看Java IO概述浏览完整的Reader表格。\n\n**整合Reader与InputStream**\n\n一个Reader可以和一个InputStream相结合。如果你有一个InputStream输入流，并且想从其中读取字符，可以把这个InputStream包装到InputStreamReader中。把InputStream传递到InputStreamReader的构造函数中：\n````\nReader reader = new InputStreamReader(inputStream);\n````\n在构造函数中可以指定解码方式。\n\n**Writer**\n\nWriter类是Java IO中所有Writer的基类。子类包括BufferedWriter和PrintWriter等等。这是一个Java IO Writer的例子：\n````\nWriter writer = new FileWriter(\"c:\\\\data\\\\file-output.txt\"); \n\nwriter.write(\"Hello World Writer\"); \n\nwriter.close();\n````\n同样，你最好使用Writer的子类，不需要直接使用Writer，因为子类的实现更加明确，更能表现你的意图。常用子类包括OutputStreamWriter，CharArrayWriter，FileWriter等。Writer的write(int c)方法，会将传入参数的低16位写入到Writer中，忽略高16位的数据。\n\n**整合Writer和OutputStream**\n\n与Reader和InputStream类似，一个Writer可以和一个OutputStream相结合。把OutputStream包装到OutputStreamWriter中，所有写入到OutputStreamWriter的字符都将会传递给OutputStream。这是一个OutputStreamWriter的例子：\n````\nWriter writer = new OutputStreamWriter(outputStream);\n````\n### IO管道\n\nJava IO中的管道为运行在同一个JVM中的两个线程提供了通信的能力。所以管道也可以作为数据源以及目标媒介。\n\n你不能利用管道与不同的JVM中的线程通信(不同的进程)。在概念上，Java的管道不同于Unix/Linux系统中的管道。在Unix/Linux中，运行在不同地址空间的两个进程可以通过管道通信。在Java中，通信的双方应该是运行在同一进程中的不同线程。\n\n通过Java IO创建管道\n\n    可以通过Java IO中的PipedOutputStream和PipedInputStream创建管道。一个PipedInputStream流应该和一个PipedOutputStream流相关联。\n    \n    一个线程通过PipedOutputStream写入的数据可以被另一个线程通过相关联的PipedInputStream读取出来。\n\nJava IO管道示例\n这是一个如何将PipedInputStream和PipedOutputStream关联起来的简单例子：\n````\n//使用管道来完成两个线程间的数据点对点传递\n    @Test\n    public void test2() throws IOException {\n        PipedInputStream pipedInputStream = new PipedInputStream();\n        PipedOutputStream pipedOutputStream = new PipedOutputStream(pipedInputStream);\n        new Thread(new Runnable() {\n            @Override\n            public void run() {\n                try {\n                    pipedOutputStream.write(\"hello input\".getBytes());\n                    pipedOutputStream.close();\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n            }\n        }).start();\n        new Thread(new Runnable() {\n            @Override\n            public void run() {\n                try {\n                    byte []arr = new byte[128];\n                    while (pipedInputStream.read(arr) != -1) {\n                        System.out.println(Arrays.toString(arr));\n                    }\n                    pipedInputStream.close();\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n            }\n        }).start();\n````\n\n管道和线程\n请记得，当使用两个相关联的管道流时，务必将它们分配给不同的线程。read()方法和write()方法调用时会导致流阻塞，这意味着如果你尝试在一个线程中同时进行读和写，可能会导致线程死锁。\n\n管道的替代\n除了管道之外，一个JVM中不同线程之间还有许多通信的方式。实际上，线程在大多数情况下会传递完整的对象信息而非原始的字节数据。但是，如果你需要在线程之间传递字节数据，Java IO的管道是一个不错的选择。\n\n### Java IO：网络\n\nJava中网络的内容或多或少的超出了Java IO的范畴。关于Java网络更多的是在我的Java网络教程中探讨。但是既然网络是一个常见的数据来源以及数据流目的地，并且因为你使用Java IO的API通过网络连接进行通信，所以本文将简要的涉及网络应用。\n\n\n当两个进程之间建立了网络连接之后，他们通信的方式如同操作文件一样：利用InputStream读取数据，利用OutputStream写入数据。换句话来说，Java网络API用来在不同进程之间建立网络连接，而Java IO则用来在建立了连接之后的进程之间交换数据。\n\n基本上意味着如果你有一份能够对文件进行写入某些数据的代码，那么这些数据也可以很容易地写入到网络连接中去。你所需要做的仅仅只是在代码中利用OutputStream替代FileOutputStream进行数据的写入。因为FileOutputStream是OuputStream的子类，所以这么做并没有什么问题。\n````\n    //从网络中读取字节流也可以直接使用OutputStream\n    public void test3() {\n        //读取网络进程的输出流\n        OutputStream outputStream = new OutputStream() {\n            @Override\n            public void write(int b) throws IOException {\n            }\n        };\n    }\n    public void process(OutputStream ouput) throws IOException {\n        //处理网络信息\n        //do something with the OutputStream\n    }\n````\n### 字节和字符数组\n\n\n从InputStream或者Reader中读入数组\n\n从OutputStream或者Writer中写数组\n\n在java中常用字节和字符数组在应用中临时存储数据。而这些数组又是通常的数据读取来源或者写入目的地。如果你需要在程序运行时需要大量读取文件里的内容，那么你也可以把一个文件加载到数组中。\n\n前面的例子中，字符数组或字节数组是用来缓存数据的临时存储空间，不过它们同时也可以作为数据来源或者写入目的地。\n举个例子：\n````\n    //字符数组和字节数组在io过程中的作用\n        public void test4() {\n            //arr和brr分别作为数据源\n            char []arr = {'a','c','d'};\n            CharArrayReader charArrayReader = new CharArrayReader(arr);\n            byte []brr = {1,2,3,4,5};\n            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(brr);\n        }\n````\n### System.in, System.out, System.err    \n\nSystem.in, System.out, System.err这3个流同样是常见的数据来源和数据流目的地。使用最多的可能是在控制台程序里利用System.out将输出打印到控制台上。\n\nJVM启动的时候通过Java运行时初始化这3个流，所以你不需要初始化它们(尽管你可以在运行时替换掉它们)。\n\n\n    System.in\n    System.in是一个典型的连接控制台程序和键盘输入的InputStream流。通常当数据通过命令行参数或者配置文件传递给命令行Java程序的时候，System.in并不是很常用。图形界面程序通过界面传递参数给程序，这是一块单独的Java IO输入机制。\n    \n    System.out\n    System.out是一个PrintStream流。System.out一般会把你写到其中的数据输出到控制台上。System.out通常仅用在类似命令行工具的控制台程序上。System.out也经常用于打印程序的调试信息(尽管它可能并不是获取程序调试信息的最佳方式)。\n    \n    System.err\n    System.err是一个PrintStream流。System.err与System.out的运行方式类似，但它更多的是用于打印错误文本。一些类似Eclipse的程序，为了让错误信息更加显眼，会将错误信息以红色文本的形式通过System.err输出到控制台上。\n\nSystem.out和System.err的简单例子：\n这是一个System.out和System.err结合使用的简单示例：\n````\n     //测试System.in, System.out, System.err    \n        public static void main(String[] args) {\n            int in = new Scanner(System.in).nextInt();\n            System.out.println(in);\n            System.out.println(\"out\");\n            System.err.println(\"err\");\n            //输入10，结果是\n    //        err（红色）\n    //        10\n    //        out\n        }\n````\n\n### 字符流的Buffered和Filter\n\nBufferedReader能为字符输入流提供缓冲区，可以提高许多IO处理的速度。你可以一次读取一大块的数据，而不需要每次从网络或者磁盘中一次读取一个字节。特别是在访问大量磁盘数据时，缓冲通常会让IO快上许多。\n\nBufferedReader和BufferedInputStream的主要区别在于，BufferedReader操作字符，而BufferedInputStream操作原始字节。只需要把Reader包装到BufferedReader中，就可以为Reader添加缓冲区(译者注：默认缓冲区大小为8192字节，即8KB)。代码如下：\n\n    Reader input = new BufferedReader(new FileReader(\"c:\\\\data\\\\input-file.txt\"));\n\n你也可以通过传递构造函数的第二个参数，指定缓冲区大小，代码如下：\n\n    Reader input = new BufferedReader(new FileReader(\"c:\\\\data\\\\input-file.txt\"), 8 * 1024);\n\n这个例子设置了8KB的缓冲区。最好把缓冲区大小设置成1024字节的整数倍，这样能更高效地利用内置缓冲区的磁盘。\n\n除了能够为输入流提供缓冲区以外，其余方面BufferedReader基本与Reader类似。BufferedReader还有一个额外readLine()方法，可以方便地一次性读取一整行字符。\n\n**BufferedWriter**\n\n与BufferedReader类似，BufferedWriter可以为输出流提供缓冲区。可以构造一个使用默认大小缓冲区的BufferedWriter(译者注：默认缓冲区大小8 * 1024B)，代码如下：\n\n    Writer writer = new BufferedWriter(new FileWriter(\"c:\\\\data\\\\output-file.txt\"));\n\n也可以手动设置缓冲区大小，代码如下：\n\n    Writer writer = new BufferedWriter(new FileWriter(\"c:\\\\data\\\\output-file.txt\"), 8 * 1024);\n\n为了更好地使用内置缓冲区的磁盘，同样建议把缓冲区大小设置成1024的整数倍。除了能够为输出流提供缓冲区以外，其余方面BufferedWriter基本与Writer类似。类似地，BufferedWriter也提供了writeLine()方法，能够把一行字符写入到底层的字符输出流中。\n\n\n**值得注意是，你需要手动flush()方法确保写入到此输出流的数据真正写入到磁盘或者网络中。**\n\n**FilterReader**\n\n与FilterInputStream类似，FilterReader是实现自定义过滤输入字符流的基类，基本上它仅仅只是简单覆盖了Reader中的所有方法。\n\n就我自己而言，我没发现这个类明显的用途。除了构造函数取一个Reader变量作为参数之外，我没看到FilterReader任何对Reader新增或者修改的地方。如果你选择继承FilterReader实现自定义的类，同样也可以直接继承自Reader从而避免额外的类层级结构。\n\n## JavaIO流面试题\n\n### 什么是IO流？\n它是一种数据的流从源头流到目的地。比如文件拷贝，输入流和输出流都包括了。输入流从文件中读取数据存储到进程(process)中，输出流从进程中读取数据然后写入到目标文件。\n\n### 字节流和字符流的区别。\n字节流在JDK1.0中就被引进了，用于操作包含ASCII字符的文件。JAVA也支持其他的字符如Unicode，为了读取包含Unicode字符的文件，JAVA语言设计者在JDK1.1中引入了字符流。ASCII作为Unicode的子集，对于英语字符的文件，可以可以使用字节流也可以使用字符流。\n\n### Java中流类的超类主要由那些？\n\njava.io.InputStream\njava.io.OutputStream\njava.io.Reader\njava.io.Writer\n\n### FileInputStream和FileOutputStream是什么？\n这是在拷贝文件操作的时候，经常用到的两个类。在处理小文件的时候，它们性能表现还不错，在大文件的时候，最好使用BufferedInputStream (或 BufferedReader) 和 BufferedOutputStream (或 BufferedWriter)\n\n### System.out.println()是什么？\nprintln是PrintStream的一个方法。out是一个静态PrintStream类型的成员变量，System是一个java.lang包中的类，用于和底层的操作系统进行交互。\n\n### 什么是Filter流？\nFilter Stream是一种IO流主要作用是用来对存在的流增加一些额外的功能，像给目标文件增加源文件中不存在的行数，或者增加拷贝的性能。\n\n### 有哪些可用的Filter流？\n\n在java.io包中主要由4个可用的filter Stream。两个字节filter stream，两个字符filter stream. 分别是FilterInputStream, FilterOutputStream, FilterReader and FilterWriter.这些类是抽象类，不能被实例化的。\n\n### 在文件拷贝的时候，那一种流可用提升更多的性能？\n在字节流的时候，使用BufferedInputStream和BufferedOutputStream。\n在字符流的时候，使用BufferedReader 和 BufferedWriter\n\n### 说说管道流(Piped Stream)\n有四种管道流， PipedInputStream, PipedOutputStream, PipedReader 和 PipedWriter.在多个线程或进程中传递数据的时候管道流非常有用。\n\n### 说说File类\n它不属于 IO流，也不是用于文件操作的，它主要用于知道一个文件的属性，读写权限，大小等信息。\n\n### 说说RandomAccessFile?\n它在java.io包中是一个特殊的类，既不是输入流也不是输出流，它两者都可以做到。他是Object的直接子类。通常来说，一个流只有一个功能，要么读，要么写。但是RandomAccessFile既可以读文件，也可以写文件。 DataInputStream 和 DataOutStream有的方法，在RandomAccessFile中都存在。\n\n## 参考文章\n\nhttps://www.imooc.com/article/24305\nhttps://www.cnblogs.com/UncleWang001/articles/10454685.html\nhttps://www.cnblogs.com/Jixiangwei/p/Java.html\nhttps://blog.csdn.net/baidu_37107022/article/details/76890019\n\n"
  },
  {
    "path": "docs/Java/basic/Java中的Class类和Object类.md",
    "content": "# 目录\n\n  * [Java中Class类及用法](#java中class类及用法)\n    * [Class类原理](#class类原理)\n    * [如何获得一个Class类对象](#如何获得一个class类对象)\n    * [使用Class类的对象来生成目标类的实例](#使用class类的对象来生成目标类的实例)\n  * [Object类](#object类)\n    * [类构造器public Object();](#类构造器public-object)\n    * [registerNatives()方法;](#registernatives方法)\n    * [Clone()方法实现浅拷贝](#clone方法实现浅拷贝)\n    * [getClass()方法](#getclass方法)\n    * [equals()方法](#equals方法)\n    * [hashCode()方法;](#hashcode方法)\n    * [toString()方法](#tostring方法)\n    * [wait() notify() notifAll()](#wait-notify-notifall)\n    * [finalize()方法](#finalize方法)\n  * [CLass类和Object类的关系](#class类和object类的关系)\n  * [参考文章](#参考文章)\n\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章首发于我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《夯实Java基础系列博文》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n该系列博文会告诉你如何从入门到进阶，一步步地学习Java基础知识，并上手进行实战，接着了解每个Java知识点背后的实现原理，更完整地了解整个Java技术体系，形成自己的知识框架。为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n\n<!-- more -->\n\n## Java中Class类及用法\nJava程序在运行时，Java运行时系统一直对所有的对象进行所谓的运行时类型标识，即所谓的RTTI。\n\n> 这项信息纪录了每个对象所属的类。虚拟机通常使用运行时类型信息选准正确方法去执行，用来保存这些类型信息的类是Class类。Class类封装一个对象和接口运行时的状态，当装载类时，Class类型的对象自动创建。\n\n说白了就是：\n\n> Class类也是类的一种，只是名字和class关键字高度相似。Java是大小写敏感的语言。\n\n> Class类的对象内容是你创建的类的类型信息，比如你创建一个shapes类，那么，Java会生成一个内容是shapes的Class类的对象\n\n> Class类的对象不能像普通类一样，以 new shapes() 的方式创建，它的对象只能由JVM创建，因为这个类没有public构造函数\n````\n/*\n * Private constructor. Only the Java Virtual Machine creates Class objects.\n * This constructor is not used and prevents the default constructor being\n * generated.\n */\n //私有构造方法，只能由jvm进行实例化\nprivate Class(ClassLoader loader) {\n    // Initialize final field for classLoader.  The initialization value of non-null\n    // prevents future JIT optimizations from assuming this final field is null.\n    classLoader = loader;\n}\n````\n> Class类的作用是运行时提供或获得某个对象的类型信息，和C++中的typeid()函数类似。这些信息也可用于反射。\n\n### Class类原理\n\n看一下Class类的部分源码\n````\n    //Class类中封装了类型的各种信息。在jvm中就是通过Class类的实例来获取每个Java类的所有信息的。\n    \n    public class Class类 {\n        Class aClass = null;\n    \n    //    private EnclosingMethodInfo getEnclosingMethodInfo() {\n    //        Object[] enclosingInfo = getEnclosingMethod0();\n    //        if (enclosingInfo == null)\n    //            return null;\n    //        else {\n    //            return new EnclosingMethodInfo(enclosingInfo);\n    //        }\n    //    }\n    \n        /**提供原子类操作\n         * Atomic operations support.\n         */\n    //    private static class Atomic {\n    //        // initialize Unsafe machinery here, since we need to call Class.class instance method\n    //        // and have to avoid calling it in the static initializer of the Class class...\n    //        private static final Unsafe unsafe = Unsafe.getUnsafe();\n    //        // offset of Class.reflectionData instance field\n    //        private static final long reflectionDataOffset;\n    //        // offset of Class.annotationType instance field\n    //        private static final long annotationTypeOffset;\n    //        // offset of Class.annotationData instance field\n    //        private static final long annotationDataOffset;\n    //\n    //        static {\n    //            Field[] fields = Class.class.getDeclaredFields0(false); // bypass caches\n    //            reflectionDataOffset = objectFieldOffset(fields, \"reflectionData\");\n    //            annotationTypeOffset = objectFieldOffset(fields, \"annotationType\");\n    //            annotationDataOffset = objectFieldOffset(fields, \"annotationData\");\n    //        }\n    \n            //提供反射信息\n        // reflection data that might get invalidated when JVM TI RedefineClasses() is called\n    //    private static class ReflectionData<T> {\n    //        volatile Field[] declaredFields;\n    //        volatile Field[] publicFields;\n    //        volatile Method[] declaredMethods;\n    //        volatile Method[] publicMethods;\n    //        volatile Constructor<T>[] declaredConstructors;\n    //        volatile Constructor<T>[] publicConstructors;\n    //        // Intermediate results for getFields and getMethods\n    //        volatile Field[] declaredPublicFields;\n    //        volatile Method[] declaredPublicMethods;\n    //        volatile Class<?>[] interfaces;\n    //\n    //        // Value of classRedefinedCount when we created this ReflectionData instance\n    //        final int redefinedCount;\n    //\n    //        ReflectionData(int redefinedCount) {\n    //            this.redefinedCount = redefinedCount;\n    //        }\n    //    }\n            //方法数组\n    //    static class MethodArray {\n    //        // Don't add or remove methods except by add() or remove() calls.\n    //        private Method[] methods;\n    //        private int length;\n    //        private int defaults;\n    //\n    //        MethodArray() {\n    //            this(20);\n    //        }\n    //\n    //        MethodArray(int initialSize) {\n    //            if (initialSize < 2)\n    //                throw new IllegalArgumentException(\"Size should be 2 or more\");\n    //\n    //            methods = new Method[initialSize];\n    //            length = 0;\n    //            defaults = 0;\n    //        }\n    \n        //注解信息\n        // annotation data that might get invalidated when JVM TI RedefineClasses() is called\n    //    private static class AnnotationData {\n    //        final Map<Class<? extends Annotation>, Annotation> annotations;\n    //        final Map<Class<? extends Annotation>, Annotation> declaredAnnotations;\n    //\n    //        // Value of classRedefinedCount when we created this AnnotationData instance\n    //        final int redefinedCount;\n    //\n    //        AnnotationData(Map<Class<? extends Annotation>, Annotation> annotations,\n    //                       Map<Class<? extends Annotation>, Annotation> declaredAnnotations,\n    //                       int redefinedCount) {\n    //            this.annotations = annotations;\n    //            this.declaredAnnotations = declaredAnnotations;\n    //            this.redefinedCount = redefinedCount;\n    //        }\n    //    }\n    }\n````\n\n> 我们都知道所有的java类都是继承了object这个类，在object这个类中有一个方法：getclass().这个方法是用来取得该类已经被实例化了的对象的该类的引用，这个引用指向的是Class类的对象。\n> \n> 我们自己无法生成一个Class对象（构造函数为private)，而 这个Class类的对象是在当各类被调入时，由 Java 虚拟机自动创建 Class 对象，或通过类装载器中的 defineClass 方法生成。\n\n    //通过该方法可以动态地将字节码转为一个Class类对象\n    protected final Class<?> defineClass(String name, byte[] b, int off, int len)\n        throws ClassFormatError\n    {\n        return defineClass(name, b, off, len, null);\n    }\n>\n\n### 如何获得一个Class类对象\n\n请注意，以下这些方法都是值、指某个类对应的Class对象已经在堆中生成以后，我们通过不同方式获取对这个Class对象的引用。而上面说的DefineClass才是真正将字节码加载到虚拟机的方法，会在堆中生成新的一个Class对象。\n\n第一种办法，Class类的forName函数\n> \n> public class shapes{}  \n> Class obj= Class.forName(\"shapes\");\n> 第二种办法，使用对象的getClass()函数\n\n> public class shapes{}\n> shapes s1=new shapes();\n> Class obj=s1.getClass();\n> Class obj1=s1.getSuperclass();//这个函数作用是获取shapes类的父类的类型\n\n第三种办法，使用类字面常量\n\n> Class obj=String.class;\n> Class obj1=int.class;\n> 注意，使用这种办法生成Class类对象时，不会使JVM自动加载该类（如String类）。==而其他办法会使得JVM初始化该类。==\n\n### 使用Class类的对象来生成目标类的实例\n> \n> 生成不精确的object实例\n> \n\n获取一个Class类的对象后，可以用 newInstance() 函数来生成目标类的一个实例。然而，该函数并不能直接生成目标类的实例，只能生成object类的实例\n\n> Class obj=Class.forName(\"shapes\");\n> Object ShapesInstance=obj.newInstance();\n> 使用泛化Class引用生成带类型的目标实例\n\n>  \n> Class<shapes> obj=shapes.class;\n> shapes newShape=obj.newInstance();\n> 因为有了类型限制，所以使用泛化Class语法的对象引用不能指向别的类。\n    \n    Class obj1=int.class;\n    Class<Integer> obj2=int.class;\n    obj1=double.class;\n    //obj2=double.class; 这一行代码是非法的，obj2不能改指向别的类\n    \n    然而，有个灵活的用法，使得你可以用Class的对象指向基类的任何子类。\n    Class<? extends Number> obj=int.class;\n    obj=Number.class;\n    obj=double.class;\n    \n    因此，以下语法生成的Class对象可以指向任何类。\n    Class<?> obj=int.class;\n    obj=double.class;\n    obj=shapes.class;\n    最后一个奇怪的用法是，当你使用这种泛型语法来构建你手头有的一个Class类的对象的基类对象时，必须采用以下的特殊语法\n    \n    public class shapes{}\n    class round extends shapes{}\n    Class<round> rclass=round.class;\n    Class<? super round> sclass= rclass.getSuperClass();\n    //Class<shapes> sclass=rclass.getSuperClass();\n\n    我们明知道，round的基类就是shapes，但是却不能直接声明 Class < shapes >，必须使用特殊语法\n    \n    Class < ? super round >\n    \n    这个记住就可以啦。\n\n## Object类\n\n这部分主要参考http://ihenu.iteye.com/blog/2233249\n\nObject类是Java中其他所有类的祖先，没有Object类Java面向对象无从谈起。作为其他所有类的基类，Object具有哪些属性和行为，是Java语言设计背后的思维体现。\n\nObject类位于java.lang包中，java.lang包包含着Java最基础和核心的类，在编译时会自动导入。Object类没有定义属性，一共有13个方法，13个方法之中并不是所有方法都是子类可访问的，一共有9个方法是所有子类都继承了的。\n\n先大概介绍一下这些方法\n\n    1．clone方法\n    保护方法，实现对象的浅复制，只有实现了Cloneable接口才可以调用该方法，否则抛出CloneNotSupportedException异常。\n    2．getClass方法\n    final方法，获得运行时类型。\n    3．toString方法\n    该方法用得比较多，一般子类都有覆盖。\n    4．finalize方法\n    该方法用于释放资源。因为无法确定该方法什么时候被调用，很少使用。\n    5．equals方法\n    该方法是非常重要的一个方法。一般equals和==是不一样的，但是在Object中两者是一样的。子类一般都要重写这个方法。\n    6．hashCode方法\n    该方法用于哈希查找，重写了equals方法一般都要重写hashCode方法。这个方法在一些具有哈希功能的Collection中用到。\n    一般必须满足obj1.equals(obj2)==true。可以推出obj1.hash- Code()==obj2.hashCode()，但是hashCode相等不一定就满足equals。不过为了提高效率，应该尽量使上面两个条件接近等价。\n    7．wait方法\n    wait方法就是使当前线程等待该对象的锁，当前线程必须是该对象的拥有者，也就是具有该对象的锁。wait()方法一直等待，直到获得锁或者被中断。wait(long timeout)设定一个超时间隔，如果在规定时间内没有获得锁就返回。\n    调用该方法后当前线程进入睡眠状态，直到以下事件发生。\n    （1）其他线程调用了该对象的notify方法。\n    （2）其他线程调用了该对象的notifyAll方法。\n    （3）其他线程调用了interrupt中断该线程。\n    （4）时间间隔到了。\n    此时该线程就可以被调度了，如果是被中断的话就抛出一个InterruptedException异常。\n    8．notify方法\n    该方法唤醒在该对象上等待的某个线程。\n    9．notifyAll方法\n    该方法唤醒在该对象上等待的所有线程。\n\n### 类构造器public Object();\n\n> 大部分情况下，Java中通过形如 new A(args..)形式创建一个属于该类型的对象。其中A即是类名，A(args..)即此类定义中相对应的构造函数。通过此种形式创建的对象都是通过类中的构造函数完成。\n\n> 为体现此特性，Java中规定：在类定义过程中，对于未定义构造函数的类，默认会有一个无参数的构造函数，作为所有类的基类，Object类自然要反映出此特性，在源码中，未给出Object类构造函数定义，但实际上，此构造函数是存在的。\n>  \n> 当然，并不是所有的类都是通过此种方式去构建，也自然的，并不是所有的类构造函数都是public。\n\n\n### registerNatives()方法;\n````\nprivate static native void registerNatives();\n````\n\n> registerNatives函数前面有native关键字修饰，Java中，用native关键字修饰的函数表明该方法的实现并不是在Java中去完成，而是由C/C++去完成，并被编译成了.dll，由Java去调用。\n> \n> 方法的具体实现体在dll文件中，对于不同平台，其具体实现应该有所不同。用native修饰，即表示操作系统，需要提供此方法，Java本身需要使用。\n> \n> 具体到registerNatives()方法本身，其主要作用是将C/C++中的方法映射到Java中的native方法，实现方法命名的解耦。\n>  \n> 既然如此，可能有人会问，registerNatives()修饰符为private，且并没有执行，作用何以达到？其实，在Java源码中，此方法的声明后有紧接着一段静态代码块：\n\n````\nprivate static native void registerNatives();  \nstatic {  \n     registerNatives();  \n}  \n````\n### Clone()方法实现浅拷贝\n````\nprotected native Object clone() throwsCloneNotSupportedException;\n````\n> 看，clode()方法又是一个被声明为native的方法，因此，我们知道了clone()方法并不是Java的原生方法，具体的实现是有C/C++完成的。clone英文翻译为\"克隆\"，其目的是创建并返回此对象的一个副本。\n\n> 形象点理解，这有一辆科鲁兹，你看着不错，想要个一模一样的。你调用此方法即可像变魔术一样变出一辆一模一样的科鲁兹出来。配置一样，长相一样。但从此刻起，原来的那辆科鲁兹如果进行了新的装饰，与你克隆出来的这辆科鲁兹没有任何关系了。\n> \n> 你克隆出来的对象变不变完全在于你对克隆出来的科鲁兹有没有进行过什么操作了。Java术语表述为：clone函数返回的是一个引用，指向的是新的clone出来的对象，此对象与原对象分别占用不同的堆空间。\n\n明白了clone的含义后，接下来看看如果调用clone()函数对象进行此克隆操作。\n\n首先看一下下面的这个例子：\n\n````\n    package com.corn.objectsummary;  \n      \n    import com.corn.Person;  \n      \n    public class ObjectTest {  \n      \n        public static void main(String[] args) {  \n      \n            Object o1 = new Object();  \n            // The method clone() from the type Object is not visible  \n            Object clone = o1.clone();  \n        }  \n      \n    }  \n````\n\n> 例子很简单，在main()方法中，new一个Oject对象后，想直接调用此对象的clone方法克隆一个对象，但是出现错误提示：\"The method clone() from the type Object is not visible\"\n>  \n> why? 根据提示，第一反应是ObjectTest类中定义的Oject对象无法访问其clone()方法。回到Object类中clone()方法的定义，可以看到其被声明为protected，估计问题就在这上面了，protected修饰的属性或方法表示：在同一个包内或者不同包的子类可以访问。\n> \n> 显然，Object类与ObjectTest类在不同的包中，但是ObjectTest继承自Object，是Object类的子类，于是，现在却出现子类中通过Object引用不能访问protected方法，原因在于对\"不同包中的子类可以访问\"没有正确理解。\n>  \n> \"不同包中的子类可以访问\"，是指当两个类不在同一个包中的时候，继承自父类的子类内部且主调（调用者）为子类的引用时才能访问父类用protected修饰的成员（属性/方法）。 在子类内部，主调为父类的引用时并不能访问此protected修饰的成员。！（super关键字除外）\n\n于是，上例改成如下形式，我们发现，可以正常编译：\n\n````\npublic class clone方法 {\n    public static void main(String[] args) {\n    \n    }\n    public void test1() {\n    \n    User user = new User();\n    //User copy = user.clone();\n    }\n    public void test2() {\n    User user = new User();\n    //User copy = (User)user.clone();\n    }\n}\n\n````\n是的，因为此时的主调已经是子类的引用了。\n\n> 上述代码在运行过程中会抛出\"java.lang.CloneNotSupportedException\",表明clone()方法并未正确执行完毕，问题的原因在与Java中的语法规定：\n>  \n> clone()的正确调用是需要实现Cloneable接口，如果没有实现Cloneable接口，并且子类直接调用Object类的clone()方法，则会抛出CloneNotSupportedException异常。\n>  \n> Cloneable接口仅是一个表示接口，接口本身不包含任何方法，用来指示Object.clone()可以合法的被子类引用所调用。\n>  \n> 于是，上述代码改成如下形式，即可正确指定clone()方法以实现克隆。\n````\npublic class User implements Cloneable{\n    public int id;\n    public String name;\n    public UserInfo userInfo;\n    \n    public static void main(String[] args) {\n        User user = new User();\n        UserInfo userInfo = new UserInfo();\n        user.userInfo = userInfo;\n        System.out.println(user);\n        System.out.println(user.userInfo);\n        try {\n            User copy = (User) user.clone();\n            System.out.println(copy);\n            System.out.println(copy.userInfo);\n        } catch (CloneNotSupportedException e) {\n            e.printStackTrace();\n        }\n    }\n    //拷贝的User实例与原来不一样，是两个对象。\n    //    com.javase.Class和Object.Object方法.用到的类.User@4dc63996\n    //    com.javase.Class和Object.Object方法.用到的类.UserInfo@d716361\n            //而拷贝后对象的userinfo引用对象是同一个。\n        //所以这是浅拷贝\n    //    com.javase.Class和Object.Object方法.用到的类.User@6ff3c5b5\n    //    com.javase.Class和Object.Object方法.用到的类.UserInfo@d716361\n    }\n````\n\n总结：\nclone方法实现的是浅拷贝，只拷贝当前对象，并且在堆中分配新的空间，放这个复制的对象。但是对象如果里面有其他类的子对象，那么就不会拷贝到新的对象中。\n\n深拷贝和浅拷贝的区别\n\n> 浅拷贝\n> 浅拷贝是按位拷贝对象，它会创建一个新对象，这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型，拷贝的就是基本类型的值；如果属性是内存地址（引用类型），拷贝的就是内存地址 ，因此如果其中一个对象改变了这个地址，就会影响到另一个对象。\n> \n> 深拷贝\n> 深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。\n> 现在为了要在clone对象时进行深拷贝， 那么就要Clonable接口，覆盖并实现clone方法，除了调用父类中的clone方法得到新的对象， 还要将该类中的引用变量也clone出来。如果只是用Object中默认的clone方法，是浅拷贝的。\n\n那么这两种方式有什么相同和不同呢？\n\n> new操作符的本意是分配内存。程序执行到new操作符时， 首先去看new操作符后面的类型，因为知道了类型，才能知道要分配多大的内存空间。\n> \n> 分配完内存之后，再调用构造函数，填充对象的各个域，这一步叫做对象的初始化，构造方法返回后，一个对象创建完毕，可以把他的引用（地址）发布到外部，在外部就可以使用这个引用操纵这个对象。\n> \n> 而clone在第一步是和new相似的， 都是分配内存，调用clone方法时，分配的内存和源对象（即调用clone方法的对象）相同，然后再使用原对象中对应的各个域，填充新对象的域，\n> \n> 填充完成之后，clone方法返回，一个新的相同的对象被创建，同样可以把这个新对象的引用发布到外部。\n\n也就是说，一个对象在浅拷贝以后，只是把对象复制了一份放在堆空间的另一个地方，但是成员变量如果有引用指向其他对象，这个引用指向的对象和被拷贝的对象中引用指向的对象是一样的。当然，基本数据类型还是会重新拷贝一份的。\n\n### getClass()方法\n````\npublic final native Class<?> getClass();\n\n````\n\n> getClass()也是一个native方法，返回的是此Object对象的类对象/运行时类对象Class<?>。效果与Object.class相同。\n>  \n> 首先解释下\"类对象\"的概念：在Java中，类是是对具有一组相同特征或行为的实例的抽象并进行描述，对象则是此类所描述的特征或行为的具体实例。\n> \n> 作为概念层次的类，其本身也具有某些共同的特性，如都具有类名称、由类加载器去加载，都具有包，具有父类，属性和方法等。\n> \n> 于是，Java中定义了一个类，Class，去描述其他类所具有的这些特性，因此，从此角度去看，类本身也都是属于Class类的对象。为与经常意义上的对象相区分，在此称之为\"类对象\"。\n````\n    public class getClass方法 {\n        public static void main(String[] args) {\n            User user = new User();\n            //getclass方法是native方法，可以取到堆区唯一的Class<User>对象\n            Class<?> aClass = user.getClass();\n            Class bClass = User.class;\n            try {\n                Class cClass = Class.forName(\"com.javase.Class和Object.Object方法.用到的类.User\");\n            } catch (ClassNotFoundException e) {\n                e.printStackTrace();\n            }\n            System.out.println(aClass);\n            System.out.println(bClass);\n    //        class com.javase.Class和Object.Object方法.用到的类.User\n    //        class com.javase.Class和Object.Object方法.用到的类.User\n            try {\n                User a = (User) aClass.newInstance();\n    \n            } catch (InstantiationException e) {\n                e.printStackTrace();\n            } catch (IllegalAccessException e) {\n                e.printStackTrace();\n            }\n        }\n    } \n````\n\n此处主要大量涉及到Java中的反射知识\n\n### equals()方法\n\n5.public boolean equals(Object obj);\n>  \n> 与equals在Java中经常被使用，大家也都知道与equals的区别：\n>  \n> ==表示的是变量值完成相同（对于基础类型，地址中存储的是值，引用类型则存储指向实际对象的地址）；\n>  \n> equals表示的是对象的内容完全相同，此处的内容多指对象的特征/属性。\n\n实际上，上面说法是不严谨的，更多的只是常见于String类中。首先看一下Object类中关于equals()方法的定义：\n\n\n    public boolean equals(Object obj) {  \n         return (this == obj);  \n    }  \n\n> 由此可见，Object原生的equals()方法内部调用的正是==，与==具有相同的含义。既然如此，为什么还要定义此equals()方法？\n>  \n> equals()方法的正确理解应该是：判断两个对象是否相等。那么判断对象相等的标尺又是什么？\n>  \n> 如上，在object类中，此标尺即为==。当然，这个标尺不是固定的，其他类中可以按照实际的需要对此标尺含义进行重定义。如String类中则是依据字符串内容是否相等来重定义了此标尺含义。如此可以增加类的功能型和实际编码的灵活性。当然了，如果自定义的类没有重写equals()方法来重新定义此标尺，那么默认的将是其父类的equals()，直到object基类。\n>  \n> 如下场景的实际业务需求，对于User bean，由实际的业务需求可知当属性uid相同时，表示的是同一个User，即两个User对象相等。则可以重写equals以重定义User对象相等的标尺。\n\n\nObjectTest中打印出true，因为User类定义中重写了equals()方法，这很好理解，很可能张三是一个人小名，张三丰才是其大名，判断这两个人是不是同一个人，这时只用判断uid是否相同即可。\n\n> 如上重写equals方法表面上看上去是可以了，实则不然。因为它破坏了Java中的约定：重写equals()方法必须重写hasCode()方法。\n\n### hashCode()方法;\n````\npublic native int hashCode()\n````\n\nhashCode()方法返回一个整形数值，表示该对象的哈希码值。\n\nhashCode()具有如下约定：\n\n> 1).在Java应用程序程序执行期间，对于同一对象多次调用hashCode()方法时，其返回的哈希码是相同的，前提是将对象进行equals比较时所用的标尺信息未做修改。在Java应用程序的一次执行到另外一次执行，同一对象的hashCode()返回的哈希码无须保持一致；\n>  \n> 2).如果两个对象相等（依据：调用equals()方法），那么这两个对象调用hashCode()返回的哈希码也必须相等；\n>  \n> 3).反之，两个对象调用hasCode()返回的哈希码相等，这两个对象不一定相等。\n\n    即严格的数学逻辑表示为： 两个对象相等 <=>  equals()相等  => hashCode()相等。因此，重写equlas()方法必须重写hashCode()方法，以保证此逻辑严格成立，同时可以推理出：hasCode()不相等 => equals（）不相等 <=> 两个对象不相等。\n     \n    可能有人在此产生疑问：既然比较两个对象是否相等的唯一条件（也是冲要条件）是equals，那么为什么还要弄出一个hashCode()，并且进行如此约定，弄得这么麻烦？\n     \n    其实，这主要体现在hashCode()方法的作用上，其主要用于增强哈希表的性能。\n     \n    以集合类中，以Set为例，当新加一个对象时，需要判断现有集合中是否已经存在与此对象相等的对象，如果没有hashCode()方法，需要将Set进行一次遍历，并逐一用equals()方法判断两个对象是否相等，此种算法时间复杂度为o(n)。通过借助于hasCode方法，先计算出即将新加入对象的哈希码，然后根据哈希算法计算出此对象的位置，直接判断此位置上是否已有对象即可。（注：Set的底层用的是Map的原理实现）\n\n> 在此需要纠正一个理解上的误区：对象的hashCode()返回的不是对象所在的物理内存地址。甚至也不一定是对象的逻辑地址，hashCode()相同的两个对象，不一定相等，换言之，不相等的两个对象，hashCode()返回的哈希码可能相同。\n>  \n> 因此，在上述代码中，重写了equals()方法后，需要重写hashCode()方法。\n\n    public class equals和hashcode方法 {\n        @Override\n        //修改equals时必须同时修改hashcode方法，否则在作为key时会出问题\n        public boolean equals(Object obj) {\n            return (this == obj);\n        }\n        \n        @Override\n        //相同的对象必须有相同hashcode，不同对象可能有相同hashcode\n        public int hashCode() {\n            return hashCode() >> 2;\n        }\n    }\n\n\n### toString()方法\n````\npublic String toString();\n\n    toString()方法返回该对象的字符串表示。先看一下Object中的具体方法体：\n    \n     public String toString() {  \n        return getClass().getName() + \"@\" + Integer.toHexString(hashCode());  \n    }  \n    \n````\n\n> toString()方法相信大家都经常用到，即使没有显式调用，但当我们使用System.out.println(obj)时，其内部也是通过toString()来实现的。\n>  \n> getClass()返回对象的类对象，getClassName()以String形式返回类对象的名称（含包名）。Integer.toHexString(hashCode())则是以对象的哈希码为实参，以16进制无符号整数形式返回此哈希码的字符串表示形式。\n>  \n> 如上例中的u1的哈希码是638，则对应的16进制为27e，调用toString()方法返回的结果为：com.corn.objectsummary.User@27e。\n>  \n> 因此：toString()是由对象的类型和其哈希码唯一确定，同一类型但不相等的两个对象分别调用toString()方法返回的结果可能相同。\n\n\n### wait() notify() notifAll()\n\n>  \n> 一说到wait(...) / notify() | notifyAll()几个方法，首先想到的是线程。确实，这几个方法主要用于java多线程之间的协作。先具体看下这几个方法的主要含义：\n>  \n> wait()：调用此方法所在的当前线程等待，直到在其他线程上调用此方法的主调（某一对象）的notify()/notifyAll()方法。\n>  \n> wait(long timeout)/wait(long timeout, int nanos)：调用此方法所在的当前线程等待，直到在其他线程上调用此方法的主调（某一对象）的notisfy()/notisfyAll()方法，或超过指定的超时时间量。\n>  \n> notify()/notifyAll()：唤醒在此对象监视器上等待的单个线程/所有线程。\n>  \n> wait(...) / notify() | notifyAll()一般情况下都是配套使用。下面来看一个简单的例子：\n\n这是一个生产者消费者的模型，只不过这里只用flag来标识哪个线程需要工作\n````\n    public class wait和notify {\n        //volatile保证线程可见性\n        volatile static int flag = 1;\n        //object作为锁对象，用于线程使用wait和notify方法\n        volatile static Object o = new Object();\n        public static void main(String[] args) {\n            new Thread(new Runnable() {\n                @Override\n                public void run() {\n                    //wait和notify只能在同步代码块内使用\n                    synchronized (o) {\n                        while (true) {\n                            if (flag == 0) {\n                                try {\n                                    Thread.sleep(2000);\n                                    System.out.println(\"thread1 wait\");\n                                    //释放锁，线程挂起进入object的等待队列，后续代码运行\n                                    o.wait();\n                                } catch (InterruptedException e) {\n                                    e.printStackTrace();\n                                }\n                            }\n                            System.out.println(\"thread1 run\");\n                            System.out.println(\"notify t2\");\n                            flag = 0;\n                            //通知等待队列的一个线程获取锁\n                            o.notify();\n                        }\n                    }\n                }\n            }).start();\n            //解释同上\n            new Thread(new Runnable() {\n                @Override\n                public void run() {\n                    while (true) {\n                        synchronized (o) {\n                            if (flag == 1) {\n                                try {\n                                    Thread.sleep(2000);\n                                    System.out.println(\"thread2 wait\");\n                                    o.wait();\n                                } catch (InterruptedException e) {\n                                    e.printStackTrace();\n                                }\n                            }\n                            System.out.println(\"thread2 run\");\n                            System.out.println(\"notify t1\");\n                            flag = 1;\n                            o.notify();\n                        }\n                    }\n                }\n            }).start();\n        }\n    \n        //输出结果是\n    //    thread1 run\n    //    notify t2\n    //    thread1 wait\n    //    thread2 run\n    //    notify t1\n    //    thread2 wait\n    //    thread1 run\n    //    notify t2\n    //不断循环\n    }\n````\n\n>  从上述例子的输出结果中可以得出如下结论：\n>  \n> 1、wait(...)方法调用后当前线程将立即阻塞，且适当其所持有的同步代码块中的锁，直到被唤醒或超时或打断后且重新获取到锁后才能继续执行；\n>  \n> 2、notify()/notifyAll()方法调用后，其所在线程不会立即释放所持有的锁，直到其所在同步代码块中的代码执行完毕，此时释放锁，因此，如果其同步代码块后还有代码，其执行则依赖于JVM的线程调度。\n\n在Java源码中，可以看到wait()具体定义如下：\n\n````\npublic final void wait() throws InterruptedException {  \n     wait(0);  \n}  \n````\n\n> 且wait(long timeout, int nanos)方法定义内部实质上也是通过调用wait(long timeout)完成。而wait(long timeout)是一个native方法。因此，wait(...)方法本质上都是native方式实现。\n\nnotify()/notifyAll()方法也都是native方法。\n\nJava中线程具有较多的知识点，是一块比较大且重要的知识点。后期会有博文专门针对Java多线程作出详细总结。此处不再细述。\n\n### finalize()方法\nfinalize方法主要与Java垃圾回收机制有关。首先我们看一下finalized方法在Object中的具体定义：\n\n````\nprotected void finalize() throws Throwable { }  \n````\n\n> 我们发现Object类中finalize方法被定义成一个空方法，为什么要如此定义呢？finalize方法的调用时机是怎么样的呢？\n>  \n> 首先，Object中定义finalize方法表明Java中每一个对象都将具有finalize这种行为，其具体调用时机在：JVM准备对此对形象所占用的内存空间进行垃圾回收前，将被调用。由此可以看出，此方法并不是由我们主动去调用的（虽然可以主动去调用，此时与其他自定义方法无异）。\n\n## CLass类和Object类的关系\n\n> Object类和Class类没有直接的关系。\n> \n> Object类是一切java类的父类，对于普通的java类，即便不声明，也是默认继承了Object类。典型的，可以使用Object类中的toString()方法。\n> \n> Class类是用于java反射机制的，一切java类，都有一个对应的Class对象，他是一个final类。Class 类的实例表示，正在运行的 Java 应用程序中的类和接口。\n\n转一个知乎很有趣的问题\nhttps://www.zhihu.com/question/30301819\n\n    Java的对象模型中：\n    1 所有的类都是Class类的实例，Object是类，那么Object也是Class类的一个实例。\n    \n    2 所有的类都最终继承自Object类，Class是类，那么Class也继承自Object。\n    \n    3 这就像是先有鸡还是先有蛋的问题，请问实际中JVM是怎么处理的？\n\n\n> 这个问题中，第1个假设是错的：java.lang.Object是一个Java类，但并不是java.lang.Class的一个实例。后者只是一个用于描述Java类与接口的、用于支持反射操作的类型。这点上Java跟其它一些更纯粹的面向对象语言（例如Python和Ruby）不同。\n> \n> 而第2个假设是对的：java.lang.Class是java.lang.Object的派生类，前者继承自后者。虽然第1个假设不对，但“鸡蛋问题”仍然存在：在一个已经启动完毕、可以使用的Java对象系统里，必须要有一个java.lang.Class实例对应java.lang.Object这个类；而java.lang.Class是java.lang.Object的派生类，按“一般思维”前者应该要在后者完成初始化之后才可以初始化…\n> \n> 事实是：这些相互依赖的核心类型完全可以在“混沌”中一口气都初始化好，然后对象系统的状态才叫做完成了“bootstrap”，后面就可以按照Java对象系统的一般规则去运行。JVM、JavaScript、Python、Ruby等的运行时都有这样的bootstrap过程。\n> \n> 在“混沌”（boostrap过程）里，JVM可以为对象系统中最重要的一些核心类型先分配好内存空间，让它们进入[已分配空间]但[尚未完全初始化]状态。此时这些对象虽然已经分配了空间，但因为状态还不完整所以尚不可使用。\n> \n> 然后，通过这些分配好的空间把这些核心类型之间的引用关系串好。到此为止所有动作都由JVM完成，尚未执行任何Java字节码。然后这些核心类型就进入了[完全初始化]状态，对象系统就可以开始自我运行下去，也就是可以开始执行Java字节码来进一步完成Java系统的初始化了。\n\n## 参考文章\n\nhttps://www.cnblogs.com/congsg2016/p/5317362.html\nhttps://www.jb51.net/article/125936.htm\nhttps://blog.csdn.net/dufufd/article/details/80537638\nhttps://blog.csdn.net/farsight1/article/details/80664104\nhttps://blog.csdn.net/xiaomingdetianxia/article/details/77429180\n\n### Java技术江湖\n\n如果大家想要实时关注我更新的文章以及分享的干货的话，可以关注我的公众号【Java技术江湖】一位阿里 Java 工程师的技术小站，作者黄小斜，专注 Java 相关技术：SSM、SpringBoot、MySQL、分布式、中间件、集群、Linux、网络、多线程，偶尔讲点Docker、ELK，同时也分享技术干货和学习经验，致力于Java全栈开发！\n\n**Java工程师必备学习资源:** 一些Java工程师常用学习资源，关注公众号后，后台回复关键字 **“Java”** 即可免费无套路获取。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1.png)\n\n"
  },
  {
    "path": "docs/Java/basic/Java基本数据类型.md",
    "content": "\n# 目录\n\n* [Java 基本数据类型](#java-基本数据类型)\n  * [Java 的两大数据类型:](#java-的两大数据类型)\n    * [内置数据类型](#内置数据类型)\n    * [引用类型](#引用类型)\n    * [Java 常量](#java-常量)\n  * [自动拆箱和装箱（详解）](#自动拆箱和装箱（详解）)\n    * [实现](#实现)\n    * [自动装箱与拆箱中的“坑”](#自动装箱与拆箱中的坑)\n    * [了解基本类型缓存（常量池）的最佳实践](#了解基本类型缓存（常量池）的最佳实践)\n    * [总结：](#总结：)\n  * [基本数据类型的存储方式](#基本数据类型的存储方式)\n    * [存在栈中](#存在栈中)\n    * [存在堆里](#存在堆里)\n  * [参考文章](#参考文章)\n\n\n\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章首发于我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《夯实Java基础系列博文》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n该系列博文会告诉你如何从入门到进阶，一步步地学习Java基础知识，并上手进行实战，接着了解每个Java知识点背后的实现原理，更完整地了解整个Java技术体系，形成自己的知识框架。为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n# Java 基本数据类型\n\n变量就是申请内存来存储值。也就是说，当创建变量的时候，需要在内存中申请空间。\n\n内存管理系统根据变量的类型为变量分配存储空间，分配的空间只能用来储存该类型数据。\n\n![](https://www.runoob.com/wp-content/uploads/2013/12/memorypic1.jpg)\n\n因此，通过定义不同类型的变量，可以在内存中储存整数、小数或者字符。\n\n## Java 的两大数据类型:\n\n- 内置数据类型\n- 引用数据类型\n\n### 内置数据类型\n\nJava语言提供了八种基本类型。六种数字类型（四个整数型，两个浮点型），一种字符类型，还有一种布尔型。\n\n**byte：**\n\n- byte 数据类型是8位、有符号的，以二进制补码表示的整数；\n- 最小值是 -128（-2^7）；\n- 最大值是 127（2^7-1）；\n- 默认值是 0；\n- byte 类型用在大型数组中节约空间，主要代替整数，因为 byte 变量占用的空间只有 int 类型的四分之一；\n- 例子：byte a = 100，byte b = -50。\n\n**short：**\n\n- short 数据类型是 16 位、有符号的以二进制补码表示的整数\n- 最小值是 -32768（-2^15）；\n- 最大值是 32767（2^15 - 1）；\n- Short 数据类型也可以像 byte 那样节省空间。一个short变量是int型变量所占空间的二分之一；\n- 默认值是 0；\n- 例子：short s = 1000，short r = -20000。\n\n**int：**\n\n- int 数据类型是32位、有符号的以二进制补码表示的整数；\n- 最小值是 -2,147,483,648（-2^31）；\n- 最大值是 2,147,483,647（2^31 - 1）；\n- 一般地整型变量默认为 int 类型；\n- 默认值是 0 ；\n- 例子：int a = 100000, int b = -200000。\n\n**long：**\n\n- long 数据类型是 64 位、有符号的以二进制补码表示的整数；\n- 最小值是 -9,223,372,036,854,775,808（-2^63）；\n- 最大值是 9,223,372,036,854,775,807（2^63 -1）；\n- 这种类型主要使用在需要比较大整数的系统上；\n- 默认值是 0L；\n- 例子： long a = 100000L，Long b = -200000L。\n  \"L\"理论上不分大小写，但是若写成\"l\"容易与数字\"1\"混淆，不容易分辩。所以最好大写。\n\n**float：**\n\n- float 数据类型是单精度、32位、符合IEEE 754标准的浮点数；\n- float 在储存大型浮点数组的时候可节省内存空间；\n- 默认值是 0.0f；\n- 浮点数不能用来表示精确的值，如货币；\n- 例子：float f1 = 234.5f。\n\n**double：**\n\n- double 数据类型是双精度、64 位、符合IEEE 754标准的浮点数；\n- 浮点数的默认类型为double类型；\n- double类型同样不能表示精确的值，如货币；\n- 默认值是 0.0d；\n- 例子：double d1 = 123.4。\n\n**boolean：**\n\n- boolean数据类型表示一位的信息；\n- 只有两个取值：true 和 false；\n- 这种类型只作为一种标志来记录 true/false 情况；\n- 默认值是 false；\n- 例子：boolean one = true。\n\n**char：**\n\n- char类型是一个单一的 16 位 Unicode 字符；\n- 最小值是 \\u0000（即为0）；\n- 最大值是 \\uffff（即为65,535）；\n- char 数据类型可以储存任何字符；\n- 例子：char letter = 'A';。\n\n### 引用类型\n\n- 在Java中，引用类型的变量非常类似于C/C++的指针。引用类型指向一个对象，指向对象的变量是引用变量。这些变量在声明时被指定为一个特定的类型，比如 Employee、Puppy 等。变量一旦声明后，类型就不能被改变了。\n- 对象、数组都是引用数据类型。\n- 所有引用类型的默认值都是null。\n- 一个引用变量可以用来引用任何与之兼容的类型。\n- 例子：Site site = new Site(\"Runoob\")。\n\n### Java 常量\n\n常量在程序运行时是不能被修改的。\n\n在 Java 中使用 final 关键字来修饰常量，声明方式和变量类似：\n\n```\nfinal double PI = 3.1415927;\n```\n\n虽然常量名也可以用小写，但为了便于识别，通常使用大写字母表示常量。\n\n字面量可以赋给任何内置类型的变量。例如：\n\n```\nbyte a = 68;\nchar a = 'A'\n```\n\n## 自动拆箱和装箱（详解）\n\nJava 5增加了自动装箱与自动拆箱机制，方便基本类型与包装类型的相互转换操作。在Java 5之前，如果要将一个int型的值转换成对应的包装器类型Integer，必须显式的使用new创建一个新的Integer对象，或者调用静态方法Integer.valueOf()。\n````\n//在Java 5之前，只能这样做\nInteger value = new Integer(10);\n//或者这样做\nInteger value = Integer.valueOf(10);\n//直接赋值是错误的\n//Integer value = 10;\n````\n在Java 5中，可以直接将整型赋给Integer对象，由编译器来完成从int型到Integer类型的转换，这就叫自动装箱。\n\n````\n//在Java 5中，直接赋值是合法的，由编译器来完成转换\nInteger value = 10;\n与此对应的，自动拆箱就是可以将包装类型转换为基本类型，具体的转换工作由编译器来完成。\n//在Java 5 中可以直接这么做\nInteger value = new Integer(10);\nint i = value;\n````\n\n自动装箱与自动拆箱为程序员提供了很大的方便，而在实际的应用中，自动装箱与拆箱也是使用最广泛的特性之一。自动装箱和自动拆箱其实是Java编译器提供的一颗语法糖（语法糖是指在计算机语言中添加的某种语法，这种语法对语言的功能并没有影响，但是更方便程序员使用。通过可提高开发效率，增加代码可读性，增加代码的安全性）。\n\n### 实现\n\n在八种包装类型中，每一种包装类型都提供了两个方法：\n\n静态方法valueOf(基本类型)：将给定的基本类型转换成对应的包装类型；\n\n实例方法xxxValue()：将具体的包装类型对象转换成基本类型；\n下面我们以int和Integer为例，说明Java中自动装箱与自动拆箱的实现机制。看如下代码：\n````\n  class Auto //code1\n  {\n      public static void main(String[] args) \n      {\n          //自动装箱\n          Integer inte = 10;\n          //自动拆箱\n          int i = inte;\n  \n      //再double和Double来验证一下\n      Double doub = 12.40;\n      double d = doub;\n      \n      }\n  \n  }\n````\n上面的代码先将int型转为Integer对象，再讲Integer对象转换为int型，毫无疑问，这是可以正确运行的。可是，这种转换是怎么进行的呢？使用反编译工具，将生成的Class文件在反编译为Java文件，让我们看看发生了什么：\n\n````\n    class Auto//code2\n    {\n      public static void main(String[] paramArrayOfString)\n      {\n        Integer localInteger = Integer.valueOf(10);\n        \n        int i = localInteger.intValue();\n    \n        Double localDouble = Double.valueOf(12.4D);\n        double d = localDouble.doubleValue();\n    \n      }\n    }\n````\n\n我们可以看到经过javac编译之后，code1的代码被转换成了code2，实际运行时，虚拟机运行的就是code2的代码。也就是说，虚拟机根本不知道有自动拆箱和自动装箱这回事；在将Java源文件编译为class文件的过程中，javac编译器在自动装箱的时候，调用了Integer.valueOf()方法，在自动拆箱时，又调用了intValue()方法。我们可以看到，double和Double也是如此。\n实现总结：其实自动装箱和自动封箱是编译器为我们提供的一颗语法糖。在自动装箱时，编译器调用包装类型的valueOf()方法；在自动拆箱时，编译器调用了相应的xxxValue()方法。\n\n### 自动装箱与拆箱中的“坑”\n\n在使用自动装箱与自动拆箱时，要注意一些陷阱，为了避免这些陷阱，我们有必要去看一下各种包装类型的源码。\n\nInteger源码\n\n````\npublic final class Integer extends Number implements Comparable<Integer> {\n\tprivate final int value;\n\t\n\n/*Integer的构造方法，接受一个整型参数,Integer对象表示的int值，保存在value中*/\n public Integer(int value) {\n        this.value = value;\n }\n \n/*equals()方法判断的是:所代表的int型的值是否相等*/\n public boolean equals(Object obj) {\n        if (obj instanceof Integer) {\n            return value == ((Integer)obj).intValue();\n        }\n        return false;\n}\n \n/*返回这个Integer对象代表的int值，也就是保存在value中的值*/\n public int intValue() {\n        return value;\n }\n \n /**\n  * 首先会判断i是否在[IntegerCache.low,Integer.high]之间\n  * 如果是，直接返回Integer.cache中相应的元素\n  * 否则，调用构造方法，创建一个新的Integer对象\n  */\n public static Integer valueOf(int i) {\n    assert IntegerCache.high >= 127;\n    if (i >= IntegerCache.low && i <= IntegerCache.high)\n        return IntegerCache.cache[i + (-IntegerCache.low)];\n    return new Integer(i);\n }\n\n/**\n  * 静态内部类，缓存了从[low,high]对应的Integer对象\n  * low -128这个值不会被改变\n  * high 默认是127，可以改变，最大不超过：Integer.MAX_VALUE - (-low) -1\n  * cache 保存从[low,high]对象的Integer对象\n */\n private static class IntegerCache {\n    static final int low = -128;\n    static final int high;\n    static final Integer cache[];\n \n    static {\n        // high value may be configured by property\n        int h = 127;\n        String integerCacheHighPropValue =\n            sun.misc.VM.getSavedProperty(\"java.lang.Integer.IntegerCache.high\");\n        if (integerCacheHighPropValue != null) {\n            int i = parseInt(integerCacheHighPropValue);\n            i = Math.max(i, 127);\n            // Maximum array size is Integer.MAX_VALUE\n            h = Math.min(i, Integer.MAX_VALUE - (-low) -1);\n        }\n        high = h;\n \n        cache = new Integer[(high - low) + 1];\n        int j = low;\n        for(int k = 0; k < cache.length; k++)\n            cache[k] = new Integer(j++);\n    }\n \n    private IntegerCache() {}\n}\n````\n\n\n以上是Oracle(Sun)公司JDK 1.7中Integer源码的一部分，通过分析上面的代码，得到：\n\n1）Integer有一个实例域value，它保存了这个Integer所代表的int型的值，且它是final的，也就是说这个Integer对象一经构造完成，它所代表的值就不能再被改变。\n\n2）Integer重写了equals()方法，它通过比较两个Integer对象的value，来判断是否相等。\n\n3）重点是静态内部类IntegerCache，通过类名就可以发现：它是用来缓存数据的。它有一个数组，里面保存的是连续的Integer对象。\n   (a) low：代表缓存数据中最小的值，固定是-128。\n\n   (b) high：代表缓存数据中最大的值，它可以被该改变，默认是127。high最小是127，最大是Integer.MAX_VALUE-(-low)-1，如果high超过了这个值，那么cache[ ]的长度就超过Integer.MAX_VALUE了，也就溢出了。\n\n   (c) cache[]：里面保存着从[low,high]所对应的Integer对象，长度是high-low+1(因为有元素0，所以要加1)。\n\n4）调用valueOf(inti)方法时，首先判断i是否在[low,high]之间，如果是，则复用Integer.cache[i-low]。比如，如果Integer.valueOf(3)，直接返回Integer.cache[131]；如果i不在这个范围，则调用构造方法，构造出一个新的Integer对象。\n\n5）调用intValue()，直接返回value的值。\n通过3）和4）可以发现，默认情况下，在使用自动装箱时，VM会复用[-128,127]之间的Integer对象。\n````\nInteger  a1 = 1;\nInteger  a2 = 1;\nInteger  a3 = new Integer(1);\n//会打印true，因为a1和a2是同一个对象,都是Integer.cache[129]\nSystem.out.println(a1 == a2);\n//false，a3构造了一个新的对象，不同于a1,a2\nSystem.out.println(a1 == a3);\n````\n### 了解基本类型缓存（常量池）的最佳实践\n\n```\n//基本数据类型的常量池是-128到127之间。\n// 在这个范围中的基本数据类的包装类可以自动拆箱，比较时直接比较数值大小。\npublic static void main(String[] args) {\n\n    //int的自动拆箱和装箱只在-128到127范围中进行，超过该范围的两个integer的 == 判断是会返回false的。\n    Integer a1 = 128;\n    Integer a2 = -128;\n    Integer a3 = -128;\n    Integer a4 = 128;\n    System.out.println(a1 == a4);\n    System.out.println(a2 == a3);\n\n    Byte b1 = 127;\n    Byte b2 = 127;\n    Byte b3 = -128;\n    Byte b4 = -128;\n    //byte都是相等的，因为范围就在-128到127之间\n    System.out.println(b1 == b2);\n    System.out.println(b3 == b4);\n\n    Long c1 = 128L;\n    Long c2 = 128L;\n    Long c3 = -128L;\n    Long c4 = -128L;\n    System.out.println(c1 == c2);\n    System.out.println(c3 == c4);\n\n    //char没有负值\n    //发现char也是在0到127之间自动拆箱\n    Character d1 = 128;\n    Character d2 = 128;\n    Character d3 = 127;\n    Character d4 = 127;\n    System.out.println(d1 == d2);\n    System.out.println(d3 == d4);\n\n    `结果`\n    \n    `false`\n    `true`\n    `true`\n    `true`\n    `false`\n    `true`\n    `false`\n    `true`\n    \n    Integer i = 10;\n    Byte b = 10;\n    //比较Byte和Integer.两个对象无法直接比较，报错\n    //System.out.println(i == b);\n    System.out.println(\"i == b \" + i.equals(b));\n    //答案是false,因为包装类的比较时先比较是否是同一个类，不是的话直接返回false.\n    int ii = 128;\n    short ss = 128;\n    long ll = 128;\n    char cc = 128;\n    System.out.println(\"ii == bb \" + (ii == ss));\n    System.out.println(\"ii == ll \" + (ii == ll));\n    System.out.println(\"ii == cc \" + (ii == cc));\n    \n    结果\n    i == b false\n    ii == bb true\n    ii == ll true\n    ii == cc true\n    \n    //这时候都是true，因为基本数据类型直接比较值，值一样就可以。\n```\n\n### 总结：\n\n通过上面的代码，我们分析一下自动装箱与拆箱发生的时机：\n\n（1）当需要一个对象的时候会自动装箱，比如Integer a = 10;equals(Object o)方法的参数是Object对象，所以需要装箱。\n\n（2）当需要一个基本类型时会自动拆箱，比如int a = new Integer(10);算术运算是在基本类型间进行的，所以当遇到算术运算时会自动拆箱，比如代码中的 c == (a + b);\n\n（3） 包装类型 == 基本类型时，包装类型自动拆箱；\n\n需要注意的是：“==”在没遇到算术运算时，不会自动拆箱；基本类型只会自动装箱为对应的包装类型，代码中最后一条说明的内容。\n\n在JDK 1.5中提供了自动装箱与自动拆箱，这其实是Java 编译器的语法糖，编译器通过调用包装类型的valueOf()方法实现自动装箱，调用xxxValue()方法自动拆箱。自动装箱和拆箱会有一些陷阱，那就是包装类型复用了某些对象。\n\n（1）Integer默认复用了[-128,127]这些对象，其中高位置可以修改；\n\n（2）Byte复用了全部256个对象[-128,127]；\n\n（3）Short复用了[-128,127]这些对象；\n\n（4）Long复用了[-128,127];\n\n（5）Character复用了[0,127],Charater不能表示负数;\n\nDouble和Float是连续不可数的，所以没法复用对象，也就不存在自动装箱复用陷阱。\n\nBoolean没有自动装箱与拆箱，它也复用了Boolean.TRUE和Boolean.FALSE，通过Boolean.valueOf(boolean b)返回的Blooean对象要么是TRUE，要么是FALSE，这点也要注意。\n\n本文介绍了“真实的”自动装箱与拆箱，为了避免写出错误的代码，又从包装类型的源码入手，指出了各种包装类型在自动装箱和拆箱时存在的陷阱，同时指出了自动装箱与拆箱发生的时机。\n\n## 基本数据类型的存储方式\n\n上面自动拆箱和装箱的原理其实与常量池有关。\n\n### 存在栈中\n\npublic void(int a)\n{\nint i = 1;\nint j = 1;\n}\n方法中的i 存在虚拟机栈的局部变量表里，i是一个引用，j也是一个引用，它们都指向局部变量表里的整型值 1.\nint a是传值引用，所以a也会存在局部变量表。\n\n### 存在堆里\n\nclass A{\nint i = 1;\nA a = new A();\n}\ni是类的成员变量。类实例化的对象存在堆中，所以成员变量也存在堆中，引用a存的是对象的地址，引用i存的是值，这个值1也会存在堆中。可以理解为引用i指向了这个值1。也可以理解为i就是1.\n\n3 包装类对象怎么存\n其实我们说的常量池也可以叫对象池。\n比如String a= new String(\"a\").intern()时会先在常量池找是否有“a\"对象如果有的话直接返回“a\"对象在常量池的地址，即让引用a指向常量”a\"对象的内存地址。\npublic native String intern();\nInteger也是同理。\n\n下图是Integer类型在常量池中查找同值对象的方法。\n\n```\npublic static Integer valueOf(int i) {\n    if (i >= IntegerCache.low && i <= IntegerCache.high)\n        return IntegerCache.cache[i + (-IntegerCache.low)];\n    return new Integer(i);\n}\nprivate static class IntegerCache {\n    static final int low = -128;\n    static final int high;\n    static final Integer cache[];\n\n    static {\n        // high value may be configured by property\n        int h = 127;\n        String integerCacheHighPropValue =\n            sun.misc.VM.getSavedProperty(\"java.lang.Integer.IntegerCache.high\");\n        if (integerCacheHighPropValue != null) {\n            try {\n                int i = parseInt(integerCacheHighPropValue);\n                i = Math.max(i, 127);\n                // Maximum array size is Integer.MAX_VALUE\n                h = Math.min(i, Integer.MAX_VALUE - (-low) -1);\n            } catch( NumberFormatException nfe) {\n                // If the property cannot be parsed into an int, ignore it.\n            }\n        }\n        high = h;\n\n        cache = new Integer[(high - low) + 1];\n        int j = low;\n        for(int k = 0; k < cache.length; k++)\n            cache[k] = new Integer(j++);\n\n        // range [-128, 127] must be interned (JLS7 5.1.7)\n        assert IntegerCache.high >= 127;\n    }\n\n    private IntegerCache() {}\n}\n```\n\n所以基本数据类型的包装类型可以在常量池查找对应值的对象，找不到就会自动在常量池创建该值的对象。\n\n而String类型可以通过intern来完成这个操作。\n\nJDK1.7后，常量池被放入到堆空间中，这导致intern()函数的功能不同，具体怎么个不同法，且看看下面代码，这个例子是网上流传较广的一个例子，分析图也是直接粘贴过来的，这里我会用自己的理解去解释这个例子：\n\n```\n[java] view plain copy\nString s = new String(\"1\");  \ns.intern();  \nString s2 = \"1\";  \nSystem.out.println(s == s2);  \n  \nString s3 = new String(\"1\") + new String(\"1\");  \ns3.intern();  \nString s4 = \"11\";  \nSystem.out.println(s3 == s4);  \n输出结果为：\n\n[java] view plain copy\nJDK1.6以及以下：false false  \nJDK1.7以及以上：false true\n```\nJDK1.6查找到常量池存在相同值的对象时会直接返回该对象的地址。\n\nJDK 1.7后，intern方法还是会先去查询常量池中是否有已经存在，如果存在，则返回常量池中的引用，这一点与之前没有区别，区别在于，如果在常量池找不到对应的字符串，则不会再将字符串拷贝到常量池，而只是在常量池中生成一个对原字符串的引用。\n\n那么其他字符串在常量池找值时就会返回另一个堆中对象的地址。\n\n下一节详细介绍String以及相关包装类。\n\n具体请见：https://blog.csdn.net/a724888/article/details/80042298\n\n关于Java面向对象三大特性，请参考：\n\nhttps://blog.csdn.net/a724888/article/details/80033043\n\n## 参考文章\n\n<https://www.runoob.com/java/java-basic-datatypes.html>\n\n<https://www.cnblogs.com/zch1126/p/5335139.html>\n\n<https://blog.csdn.net/jreffchen/article/details/81015884>\n\n<https://blog.csdn.net/yuhongye111/article/details/31850779>\n\n\n"
  },
  {
    "path": "docs/Java/basic/Java异常.md",
    "content": "# 目录\n\n  * [为什么要使用异常](#为什么要使用异常)\n  * [异常基本定义](#异常基本定义)\n  * [异常体系](#异常体系)\n  * [初识异常](#初识异常)\n  * [异常和错误](#异常和错误)\n  * [异常的处理方式](#异常的处理方式)\n  * [\"不负责任\"的throws](#不负责任的throws)\n  * [纠结的finally](#纠结的finally)\n  * [throw : JRE也使用的关键字](#throw--jre也使用的关键字)\n  * [异常调用链](#异常调用链)\n  * [自定义异常](#自定义异常)\n  * [异常的注意事项](#异常的注意事项)\n  * [当finally遇上return](#当finally遇上return)\n  * [JAVA异常常见面试题](#java异常常见面试题)\n  * [参考文章](#参考文章)\n\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章首发于我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《夯实Java基础系列博文》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n该系列博文会告诉你如何从入门到进阶，一步步地学习Java基础知识，并上手进行实战，接着了解每个Java知识点背后的实现原理，更完整地了解整个Java技术体系，形成自己的知识框架。为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n## 为什么要使用异常\n\n>   首先我们可以明确一点就是异常的处理机制可以确保我们程序的健壮性，提高系统可用率。虽然我们不是特别喜欢看到它，但是我们不能不承认它的地位，作用。\n\n在没有异常机制的时候我们是这样处理的：通过函数的返回值来判断是否发生了异常（这个返回值通常是已经约定好了的），调用该函数的程序负责检查并且分析返回值。虽然可以解决异常问题，但是这样做存在几个缺陷：\n> \n>   1、 容易混淆。如果约定返回值为-11111时表示出现异常，那么当程序最后的计算结果真的为-1111呢？\n> \n>   2、 代码可读性差。将异常处理代码和程序代码混淆在一起将会降低代码的可读性。\n> \n>   3、 由调用函数来分析异常，这要求程序员对库函数有很深的了解。\n> \n\n在OO中提供的异常处理机制是提供代码健壮的强有力的方式。使用异常机制它能够降低错误处理代码的复杂度，如果不使用异常，那么就必须检查特定的错误，并在程序中的许多地方去处理它。\n\n而如果使用异常，那就不必在方法调用处进行检查，因为异常机制将保证能够捕获这个错误，并且，只需在一个地方处理错误，即所谓的异常处理程序中。\n\n这种方式不仅节约代码，而且把“概述在正常执行过程中做什么事”的代码和“出了问题怎么办”的代码相分离。总之，与以前的错误处理方法相比，异常机制使代码的阅读、编写和调试工作更加井井有条。（摘自《Think in java 》）。\n\n该部分内容选自http://www.cnblogs.com/chenssy/p/3438130.html\n\n## 异常基本定义\n\n> 在《Think in java》中是这样定义异常的：异常情形是指阻止当前方法或者作用域继续执行的问题。在这里一定要明确一点：异常代码某种程度的错误，尽管Java有异常处理机制，但是我们不能以“正常”的眼光来看待异常，异常处理机制的原因就是告诉你：这里可能会或者已经产生了错误，您的程序出现了不正常的情况，可能会导致程序失败！\n\n> 那么什么时候才会出现异常呢？只有在你当前的环境下程序无法正常运行下去，也就是说程序已经无法来正确解决问题了，这时它所就会从当前环境中跳出，并抛出异常。抛出异常后，它首先会做几件事。\n\n> 首先，它会使用new创建一个异常对象，然后在产生异常的位置终止程序，并且从当前环境中弹出对异常对象的引用，这时。异常处理机制就会接管程序，并开始寻找一个恰当的地方来继续执行程序，这个恰当的地方就是异常处理程序。\n\n> 总的来说异常处理机制就是当程序发生异常时，它强制终止程序运行，记录异常信息并将这些信息反馈给我们，由我们来确定是否处理异常。\n\n## 异常体系\n\n![img](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/22185952-834d92bc2bfe498f9a33414cc7a2c8a4.png)\n\n从上面这幅图可以看出，Throwable是java语言中所有错误和异常的超类（万物即可抛）。它有两个子类：Error、Exception。\n\nJava标准库内建了一些通用的异常，这些类以Throwable为顶层父类。\n\nThrowable又派生出Error类和Exception类。\n\n错误：Error类以及他的子类的实例，代表了JVM本身的错误。错误不能被程序员通过代码处理，Error很少出现。因此，程序员应该关注Exception为父类的分支下的各种异常类。\n\n异常：Exception以及他的子类，代表程序运行时发送的各种不期望发生的事件。可以被Java异常处理机制使用，是异常处理的核心。\n\n总体上我们根据Javac对异常的处理要求，将异常类分为2类。\n\n> 非检查异常（unckecked exception）：Error 和 RuntimeException 以及他们的子类。javac在编译时，不会提示和发现这样的异常，不要求在程序处理这些异常。所以如果愿意，我们可以编写代码处理（使用try…catch…finally）这样的异常，也可以不处理。\n\n> 对于这些异常，我们应该修正代码，而不是去通过异常处理器处理 。这样的异常发生的原因多半是代码写的有问题。如除0错误ArithmeticException，错误的强制类型转换错误ClassCastException，数组索引越界ArrayIndexOutOfBoundsException，使用了空对象NullPointerException等等。\n\n> 检查异常（checked exception）：除了Error 和 RuntimeException的其它异常。javac强制要求程序员为这样的异常做预备处理工作（使用try…catch…finally或者throws）。在方法中要么用try-catch语句捕获它并处理，要么用throws子句声明抛出它，否则编译不会通过。\n\n> 这样的异常一般是由程序的运行环境导致的。因为程序可能被运行在各种未知的环境下，而程序员无法干预用户如何使用他编写的程序，于是程序员就应该为这样的异常时刻准备着。如SQLException , IOException,ClassNotFoundException 等。\n\n需要明确的是：检查和非检查是对于javac来说的，这样就很好理解和区分了。\n\n这部分内容摘自http://www.importnew.com/26613.html\n\n## 初识异常\n\n异常是在执行某个函数时引发的，而函数又是层级调用，形成调用栈的，因为，只要一个函数发生了异常，那么他的所有的caller都会被异常影响。当这些被影响的函数以异常信息输出时，就形成的了异常追踪栈。\n\n异常最先发生的地方，叫做异常抛出点。\n````\n    public class 异常 {\n        public static void main (String [] args )\n        {\n            System . out. println( \"----欢迎使用命令行除法计算器----\" ) ;\n            CMDCalculate ();\n        }\n        public static void CMDCalculate ()\n        {\n            Scanner scan = new Scanner ( System. in );\n            int num1 = scan .nextInt () ;\n            int num2 = scan .nextInt () ;\n            int result = devide (num1 , num2 ) ;\n            System . out. println( \"result:\" + result) ;\n            scan .close () ;\n        }\n        public static int devide (int num1, int num2 ){\n            return num1 / num2 ;\n        }\n    \n    //    ----欢迎使用命令行除法计算器----\n    //            1\n    //            0\n    //    Exception in thread \"main\" java.lang.ArithmeticException: / by zero\n    //    at com.javase.异常.异常.devide(异常.java:24)\n    //    at com.javase.异常.异常.CMDCalculate(异常.java:19)\n    //    at com.javase.异常.异常.main(异常.java:12)\n \n    //  ----欢迎使用命令行除法计算器----\n    //    r\n    //    Exception in thread \"main\" java.util.InputMismatchException\n    //    at java.util.Scanner.throwFor(Scanner.java:864)\n    //    at java.util.Scanner.next(Scanner.java:1485)\n    //    at java.util.Scanner.nextInt(Scanner.java:2117)\n    //    at java.util.Scanner.nextInt(Scanner.java:2076)\n    //    at com.javase.异常.异常.CMDCalculate(异常.java:17)\n    //    at com.javase.异常.异常.main(异常.java:12)\n\n````\n\n从上面的例子可以看出，当devide函数发生除0异常时，devide函数将抛出ArithmeticException异常，因此调用他的CMDCalculate函数也无法正常完成，因此也发送异常，而CMDCalculate的caller——main 因为CMDCalculate抛出异常，也发生了异常，这样一直向调用栈的栈底回溯。\n\n这种行为叫做异常的冒泡，异常的冒泡是为了在当前发生异常的函数或者这个函数的caller中找到最近的异常处理程序。由于这个例子中没有使用任何异常处理机制，因此异常最终由main函数抛给JRE，导致程序终止。\n\n> 上面的代码不使用异常处理机制，也可以顺利编译，因为2个异常都是非检查异常。但是下面的例子就必须使用异常处理机制，因为异常是检查异常。\n\n代码中我选择使用throws声明异常，让函数的调用者去处理可能发生的异常。但是为什么只throws了IOException呢？因为FileNotFoundException是IOException的子类，在处理范围内。\n\n## 异常和错误\n\n下面看一个例子\n````    \n    //错误即error一般指jvm无法处理的错误\n    //异常是Java定义的用于简化错误处理流程和定位错误的一种工具。\n    public class 错误和错误 {\n        Error error = new Error();\n    \n        public static void main(String[] args) {\n            throw new Error();\n        }\n    \n        //下面这四个异常或者错误有着不同的处理方法\n        public void error1 (){\n            //编译期要求必须处理，因为这个异常是最顶层异常，包括了检查异常，必须要处理\n            try {\n                throw new Throwable();\n            } catch (Throwable throwable) {\n                throwable.printStackTrace();\n            }\n        }\n        //Exception也必须处理。否则报错，因为检查异常都继承自exception，所以默认需要捕捉。\n        public void error2 (){\n            try {\n                throw new Exception();\n            } catch (Exception e) {\n                e.printStackTrace();\n            }\n        }\n    \n        //error可以不处理，编译不报错,原因是虚拟机根本无法处理，所以啥都不用做\n        public void error3 (){\n            throw new Error();\n        }\n    \n        //runtimeexception众所周知编译不会报错\n        public void error4 (){\n            throw new RuntimeException();\n        }\n    //    Exception in thread \"main\" java.lang.Error\n    //    at com.javase.异常.错误.main(错误.java:11)\n    \n    }\n````\n## 异常的处理方式\n\n在编写代码处理异常时，对于检查异常，有2种不同的处理方式：\n\n> 使用try…catch…finally语句块处理它。\n> \n\n> 或者，在函数签名中使用throws 声明交给函数调用者caller去解决。\n> \n\n下面看几个具体的例子，包括error，exception和throwable\n\n上面的例子是运行时异常，不需要显示捕获。\n下面这个例子是可检查异常需，要显示捕获或者抛出。\n````\n    @Test\n    public void testException() throws IOException\n    {\n        //FileInputStream的构造函数会抛出FileNotFoundException\n        FileInputStream fileIn = new FileInputStream(\"E:\\\\a.txt\");\n    \n        int word;\n        //read方法会抛出IOException\n        while((word =  fileIn.read())!=-1)\n        {\n            System.out.print((char)word);\n        }\n        //close方法会抛出IOException\n        fileIn.close();\n    }\n\n一般情况下的处理方式 try catch finally\n\n    public class 异常处理方式 {\n    \n    @Test\n    public void main() {\n        try{\n            //try块中放可能发生异常的代码。\n            InputStream inputStream = new FileInputStream(\"a.txt\");\n    \n            //如果执行完try且不发生异常，则接着去执行finally块和finally后面的代码（如果有的话）。\n            int i = 1/0;\n            //如果发生异常，则尝试去匹配catch块。\n            throw new SQLException();\n            //使用1.8jdk同时捕获多个异常，runtimeexception也可以捕获。只是捕获后虚拟机也无法处理，所以不建议捕获。\n        }catch(SQLException | IOException | ArrayIndexOutOfBoundsException exception){\n            System.out.println(exception.getMessage());\n            //每一个catch块用于捕获并处理一个特定的异常，或者这异常类型的子类。Java7中可以将多个异常声明在一个catch中。\n    \n            //catch后面的括号定义了异常类型和异常参数。如果异常与之匹配且是最先匹配到的，则虚拟机将使用这个catch块来处理异常。\n    \n            //在catch块中可以使用这个块的异常参数来获取异常的相关信息。异常参数是这个catch块中的局部变量，其它块不能访问。\n    \n            //如果当前try块中发生的异常在后续的所有catch中都没捕获到，则先去执行finally，然后到这个函数的外部caller中去匹配异常处理器。\n    \n            //如果try中没有发生异常，则所有的catch块将被忽略。\n    \n        }catch(Exception exception){\n            System.out.println(exception.getMessage());\n            //...\n        }finally{\n            //finally块通常是可选的。\n            //无论异常是否发生，异常是否匹配被处理，finally都会执行。\n    \n            //finally主要做一些清理工作，如流的关闭，数据库连接的关闭等。\n        }\n\n一个try至少要跟一个catch或者finally\n\n        try {\n            int i = 1;\n        }finally {\n            //一个try至少要有一个catch块，否则， 至少要有1个finally块。但是finally不是用来处理异常的，finally不会捕获异常。\n        }\n    }\n\n\n异常出现时该方法后面的代码不会运行，即使异常已经被捕获。这里举出一个奇特的例子，在catch里再次使用try catch finally\n\n    @Test\n    public void test() {\n        try {\n            throwE();\n            System.out.println(\"我前面抛出异常了\");\n            System.out.println(\"我不会执行了\");\n        } catch (StringIndexOutOfBoundsException e) {\n            System.out.println(e.getCause());\n        }catch (Exception ex) {\n        //在catch块中仍然可以使用try catch finally\n            try {\n                throw new Exception();\n            }catch (Exception ee) {\n                \n            }finally {\n                System.out.println(\"我所在的catch块没有执行，我也不会执行的\");\n            }\n        }\n    }\n    //在方法声明中抛出的异常必须由调用方法处理或者继续往上抛，\n    // 当抛到jre时由于无法处理终止程序\n    public void throwE (){\n    //        Socket socket = new Socket(\"127.0.0.1\", 80);\n    \n            //手动抛出异常时，不会报错，但是调用该方法的方法需要处理这个异常，否则会出错。\n    //        java.lang.StringIndexOutOfBoundsException\n    //        at com.javase.异常.异常处理方式.throwE(异常处理方式.java:75)\n    //        at com.javase.异常.异常处理方式.test(异常处理方式.java:62)\n            throw new StringIndexOutOfBoundsException();\n        }\n````\n\n其实有的语言在遇到异常后仍然可以继续运行\n\n> 有的编程语言当异常被处理后，控制流会恢复到异常抛出点接着执行，这种策略叫做：resumption model of exception handling（恢复式异常处理模式 ）\n> \n> 而Java则是让执行流恢复到处理了异常的catch块后接着执行，这种策略叫做：termination model of exception handling（终结式异常处理模式）\n\n## \"不负责任\"的throws\n\nthrows是另一种处理异常的方式，它不同于try…catch…finally，throws仅仅是将函数中可能出现的异常向调用者声明，而自己则不具体处理。\n\n采取这种异常处理的原因可能是：方法本身不知道如何处理这样的异常，或者说让调用者处理更好，调用者需要为可能发生的异常负责。\n````\npublic void foo() throws ExceptionType1 , ExceptionType2 ,ExceptionTypeN\n{ \n     //foo内部可以抛出 ExceptionType1 , ExceptionType2 ,ExceptionTypeN 类的异常，或者他们的子类的异常对象。\n}\n````\n\n## 纠结的finally\n\nfinally块不管异常是否发生，只要对应的try执行了，则它一定也执行。只有一种方法让finally块不执行：System.exit()。因此finally块通常用来做资源释放操作：关闭文件，关闭数据库连接等等。\n\n良好的编程习惯是：在try块中打开资源，在finally块中清理释放这些资源。\n\n需要注意的地方:\n\n1、finally块没有处理异常的能力。处理异常的只能是catch块。\n\n2、在同一try…catch…finally块中 ，如果try中抛出异常，且有匹配的catch块，则先执行catch块，再执行finally块。如果没有catch块匹配，则先执行finally，然后去外面的调用者中寻找合适的catch块。\n\n3、在同一try…catch…finally块中 ，try发生异常，且匹配的catch块中处理异常时也抛出异常，那么后面的finally也会执行：首先执行finally块，然后去外围调用者中寻找合适的catch块。\n\n````\n    public class finally使用 {\n        public static void main(String[] args) {\n            try {\n                throw new IllegalAccessException();\n            }catch (IllegalAccessException e) {\n                // throw new Throwable();\n                //此时如果再抛异常，finally无法执行，只能报错。\n                //finally无论何时都会执行\n                //除非我显示调用。此时finally才不会执行\n                System.exit(0);\n    \n            }finally {\n                System.out.println(\"算你狠\");\n            }\n        }\n    }\n````\n\n## throw : JRE也使用的关键字\n\nthrow exceptionObject\n\n程序员也可以通过throw语句手动显式的抛出一个异常。throw语句的后面必须是一个异常对象。\n\nthrow 语句必须写在函数中，执行throw 语句的地方就是一个异常抛出点，==它和由JRE自动形成的异常抛出点没有任何差别。==\n````\npublic void save(User user)\n{\n      if(user  == null) \n          throw new IllegalArgumentException(\"User对象为空\");\n      //......\n}\n````\n后面开始的大部分内容都摘自http://www.cnblogs.com/lulipro/p/7504267.html\n\n该文章写的十分细致到位，令人钦佩，是我目前为之看到关于异常最详尽的文章，可以说是站在巨人的肩膀上了。\n\n## 异常调用链\n\n异常的链化\n\n在一些大型的，模块化的软件开发中，一旦一个地方发生异常，则如骨牌效应一样，将导致一连串的异常。假设B模块完成自己的逻辑需要调用A模块的方法，如果A模块发生异常，则B也将不能完成而发生异常。\n\n==但是B在抛出异常时，会将A的异常信息掩盖掉，这将使得异常的根源信息丢失。异常的链化可以将多个模块的异常串联起来，使得异常信息不会丢失。==\n\n> 异常链化:以一个异常对象为参数构造新的异常对象。新的异对象将包含先前异常的信息。这项技术主要是异常类的一个带Throwable参数的函数来实现的。这个当做参数的异常，我们叫他根源异常（cause）。\n\n查看Throwable类源码，可以发现里面有一个Throwable字段cause，就是它保存了构造时传递的根源异常参数。这种设计和链表的结点类设计如出一辙，因此形成链也是自然的了。\n````\npublic class Throwable implements Serializable {\n    private Throwable cause = this;\n    public Throwable(String message, Throwable cause) {\n        fillInStackTrace();\n        detailMessage = message;\n        this.cause = cause;\n    }\n     public Throwable(Throwable cause) {\n        fillInStackTrace();\n        detailMessage = (cause==null ? null : cause.toString());\n        this.cause = cause;\n    }\n    //........\n}\n````\n\n下面看一个比较实在的异常链例子哈\n````\npublic class 异常链 {\n    @Test\n    public void test() {\n        C();\n    }\n    public void A () throws Exception {\n        try {\n            int i = 1;\n            i = i / 0;\n            //当我注释掉这行代码并使用B方法抛出一个error时，运行结果如下\n//            四月 27, 2018 10:12:30 下午 org.junit.platform.launcher.core.ServiceLoaderTestEngineRegistry loadTestEngines\n//            信息: Discovered TestEngines with IDs: [junit-jupiter]\n//            java.lang.Error: B也犯了个错误\n//            at com.javase.异常.异常链.B(异常链.java:33)\n//            at com.javase.异常.异常链.C(异常链.java:38)\n//            at com.javase.异常.异常链.test(异常链.java:13)\n//            at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n//            Caused by: java.lang.Error\n//            at com.javase.异常.异常链.B(异常链.java:29)\n\n        }catch (ArithmeticException e) {\n            //这里通过throwable类的构造方法将最底层的异常重新包装并抛出，此时注入了A方法的信息。最后打印栈信息时可以看到caused by\n            A方法的异常。\n            //如果直接抛出，栈信息打印结果只能看到上层方法的错误信息，不能看到其实是A发生了错误。\n            //所以需要包装并抛出\n            throw new Exception(\"A方法计算错误\", e);\n        }\n\n    }\n    public void B () throws Exception,Error {\n        try {\n            //接收到A的异常，\n            A();\n            throw new Error();\n        }catch (Exception e) {\n            throw e;\n        }catch (Error error) {\n            throw new Error(\"B也犯了个错误\", error);\n        }\n    }\n    public void C () {\n        try {\n            B();\n        }catch (Exception | Error e) {\n            e.printStackTrace();\n        }\n\n    }\n\n    //最后结果\n//    java.lang.Exception: A方法计算错误\n//    at com.javase.异常.异常链.A(异常链.java:18)\n//    at com.javase.异常.异常链.B(异常链.java:24)\n//    at com.javase.异常.异常链.C(异常链.java:31)\n//    at com.javase.异常.异常链.test(异常链.java:11)\n//    省略\n//    Caused by: java.lang.ArithmeticException: / by zero\n//    at com.javase.异常.异常链.A(异常链.java:16)\n//            ... 31 more\n}\n````\n## 自定义异常\n\n如果要自定义异常类，则扩展Exception类即可，因此这样的自定义异常都属于检查异常（checked exception）。如果要自定义非检查异常，则扩展自RuntimeException。\n\n按照国际惯例，自定义的异常应该总是包含如下的构造函数：\n\n一个无参构造函数\n一个带有String参数的构造函数，并传递给父类的构造函数。\n一个带有String参数和Throwable参数，并都传递给父类构造函数\n一个带有Throwable 参数的构造函数，并传递给父类的构造函数。\n下面是IOException类的完整源代码，可以借鉴。\n````\n    public class IOException extends Exception\n    {\n        static final long serialVersionUID = 7818375828146090155L;\n     \n        public IOException()\n        {\n            super();\n        }\n     \n        public IOException(String message)\n        {\n            super(message);\n        }\n     \n        public IOException(String message, Throwable cause)\n        {\n            super(message, cause);\n        }\n     \n        public IOException(Throwable cause)\n        {\n            super(cause);\n        }\n    }\n````\n## 异常的注意事项\n\n异常的注意事项\n\n> 当子类重写父类的带有 throws声明的函数时，其throws声明的异常必须在父类异常的可控范围内——用于处理父类的throws方法的异常处理器，必须也适用于子类的这个带throws方法 。这是为了支持多态。\n> \n> 例如，父类方法throws 的是2个异常，子类就不能throws 3个及以上的异常。父类throws IOException，子类就必须throws IOException或者IOException的子类。\n\n至于为什么？我想，也许下面的例子可以说明。\n````\nclass Father\n{\n    public void start() throws IOException\n    {\n        throw new IOException();\n    }\n}\n \nclass Son extends Father\n{\n    public void start() throws Exception\n    {\n        throw new SQLException();\n    }\n}\n/**********************假设上面的代码是允许的（实质是错误的）***********************/\n\nclass Test\n{\n    public static void main(String[] args)\n    {\n        Father[] objs = new Father[2];\n        objs[0] = new Father();\n        objs[1] = new Son();\n \n        for(Father obj:objs)\n        {\n        //因为Son类抛出的实质是SQLException，而IOException无法处理它。\n        //那么这里的try。。catch就不能处理Son中的异常。\n        //多态就不能实现了。\n            try {\n                 obj.start();\n            }catch(IOException)\n            {\n                 //处理IOException\n            }\n         }\n   }\n}\n````\n==Java的异常执行流程是线程独立的，线程之间没有影响==\n\n> Java程序可以是多线程的。每一个线程都是一个独立的执行流，独立的函数调用栈。如果程序只有一个线程，那么没有被任何代码处理的异常 会导致程序终止。如果是多线程的，那么没有被任何代码处理的异常仅仅会导致异常所在的线程结束。\n> \n> 也就是说，Java中的异常是线程独立的，线程的问题应该由线程自己来解决，而不要委托到外部，也不会直接影响到其它线程的执行。\n\n下面看一个例子\n````\npublic class 多线程的异常 {\n    @Test\n    public void test() {\n        go();\n    }\n    public void go () {\n        ExecutorService executorService = Executors.newFixedThreadPool(3);\n        for (int i = 0;i <= 2;i ++) {\n            int finalI = i;\n            try {\n                Thread.sleep(2000);\n            } catch (InterruptedException e) {\n                e.printStackTrace();\n            }\n            executorService.execute(new Runnable() {\n                @Override\n                //每个线程抛出异常时并不会影响其他线程的继续执行\n                public void run() {\n                    try {\n                        System.out.println(\"start thread\" + finalI);\n                        throw new Exception();\n                    }catch (Exception e) {\n                        System.out.println(\"thread\" + finalI + \" go wrong\");\n                    }\n                }\n            });\n        }\n//        结果：\n//        start thread0\n//        thread0 go wrong\n//        start thread1\n//        thread1 go wrong\n//        start thread2\n//        thread2 go wrong\n    }\n}\n````\n\n## 当finally遇上return\n\n\n首先一个不容易理解的事实：\n\n在 try块中即便有return，break，continue等改变执行流的语句，finally也会执行。\n````\npublic static void main(String[] args)\n{\n    int re = bar();\n    System.out.println(re);\n}\nprivate static int bar() \n{\n    try{\n        return 5;\n    } finally{\n        System.out.println(\"finally\");\n    }\n}\n/*输出：\nfinally\n*/\n````\n\n也就是说：try…catch…finally中的return 只要能执行，就都执行了，他们共同向同一个内存地址（假设地址是0×80）写入返回值，后执行的将覆盖先执行的数据，而真正被调用者取的返回值就是最后一次写入的。那么，按照这个思想，下面的这个例子也就不难理解了。\n\nfinally中的return 会覆盖 try 或者catch中的返回值。\n````\npublic static void main(String[] args)\n    {\n        int result;\n \n        result  =  foo();\n        System.out.println(result);     /////////2\n \n        result = bar();\n        System.out.println(result);    /////////2\n    }\n \n    @SuppressWarnings(\"finally\")\n    public static int foo()\n    {\n        trz{\n            int a = 5 / 0;\n        } catch (Exception e){\n            return 1;\n        } finally{\n            return 2;\n        }\n \n    }\n \n    @SuppressWarnings(\"finally\")\n    public static int bar()\n    {\n        try {\n            return 1;\n        }finally {\n            return 2;\n        }\n    }\n````\nfinally中的return会抑制（消灭）前面try或者catch块中的异常\n````\nclass TestException\n{\n    public static void main(String[] args)\n    {\n        int result;\n        try{\n            result = foo();\n            System.out.println(result);           //输出100\n        } catch (Exception e){\n            System.out.println(e.getMessage());    //没有捕获到异常\n        }\n \n        try{\n            result  = bar();\n            System.out.println(result);           //输出100\n        } catch (Exception e){\n            System.out.println(e.getMessage());    //没有捕获到异常\n        }\n    }\n \n    //catch中的异常被抑制\n    @SuppressWarnings(\"finally\")\n    public static int foo() throws Exception\n    {\n        try {\n            int a = 5/0;\n            return 1;\n        }catch(ArithmeticException amExp) {\n            throw new Exception(\"我将被忽略，因为下面的finally中使用了return\");\n        }finally {\n            return 100;\n        }\n    }\n \n    //try中的异常被抑制\n    @SuppressWarnings(\"finally\")\n    public static int bar() throws Exception\n    {\n        try {\n            int a = 5/0;\n            return 1;\n        }finally {\n            return 100;\n        }\n    }\n}\n````\nfinally中的异常会覆盖（消灭）前面try或者catch中的异常\n````\nclass TestException\n{\n    public static void main(String[] args)\n    {\n        int result;\n        try{\n            result = foo();\n        } catch (Exception e){\n            System.out.println(e.getMessage());    //输出：我是finaly中的Exception\n        }\n \n        try{\n            result  = bar();\n        } catch (Exception e){\n            System.out.println(e.getMessage());    //输出：我是finaly中的Exception\n        }\n    }\n \n    //catch中的异常被抑制\n    @SuppressWarnings(\"finally\")\n    public static int foo() throws Exception\n    {\n        try {\n            int a = 5/0;\n            return 1;\n        }catch(ArithmeticException amExp) {\n            throw new Exception(\"我将被忽略，因为下面的finally中抛出了新的异常\");\n        }finally {\n            throw new Exception(\"我是finaly中的Exception\");\n        }\n    }\n \n    //try中的异常被抑制\n    @SuppressWarnings(\"finally\")\n    public static int bar() throws Exception\n    {\n        try {\n            int a = 5/0;\n            return 1;\n        }finally {\n            throw new Exception(\"我是finaly中的Exception\");\n        }\n \n    }\n}\n````\n上面的3个例子都异于常人的编码思维，因此我建议：\n\n> 不要在fianlly中使用return。\n\n> 不要在finally中抛出异常。\n\n> 减轻finally的任务，不要在finally中做一些其它的事情，finally块仅仅用来释放资源是最合适的。\n\n> 将尽量将所有的return写在函数的最后面，而不是try … catch … finally中。\n\n## JAVA异常常见面试题\n\n　　下面是我个人总结的在Java和J2EE开发者在面试中经常被问到的有关Exception和Error的知识。在分享我的回答的时候，我也给这些问题作了快速修订，并且提供源码以便深入理解。我总结了各种难度的问题，适合新手码农和高级Java码农。如果你遇到了我列表中没有的问题，并且这个问题非常好，请在下面评论中分享出来。你也可以在评论中分享你面试时答错的情况。\n\n**1) Java中什么是Exception?**\n这个问题经常在第一次问有关异常的时候或者是面试菜鸟的时候问。我从来没见过面高级或者资深工程师的时候有人问这玩意，但是对于菜鸟，是很愿意问这个的。简单来说，异常是Java传达给你的系统和程序错误的方式。在java中，异常功能是通过实现比如Throwable，Exception，RuntimeException之类的类，然后还有一些处理异常时候的关键字，比如throw，throws，try，catch，finally之类的。所有的异常都是通过Throwable衍生出来的。Throwable把错误进一步划分为java.lang.Exception\n和 java.lang.Error. \n\njava.lang.Error 用来处理系统错误，例如java.lang.StackOverFlowError 之类的。然后Exception用来处理程序错误，请求的资源不可用等等。\n\n**2) Java中的检查型异常和非检查型异常有什么区别？**\n\n这又是一个非常流行的Java异常面试题，会出现在各种层次的Java面试中。检查型异常和非检查型异常的主要区别在于其处理方式。检查型异常需要使用try, catch和finally关键字在编译期进行处理，否则会出现编译器会报错。对于非检查型异常则不需要这样做。Java中所有继承自java.lang.Exception类的异常都是检查型异常，所有继承自RuntimeException的异常都被称为非检查型异常。\n\n**3) Java中的NullPointerException和ArrayIndexOutOfBoundException之间有什么相同之处？**\n\n在Java异常面试中这并不是一个很流行的问题，但会出现在不同层次的初学者面试中，用来测试应聘者对检查型异常和非检查型异常的概念是否熟悉。顺便说一下，该题的答案是，这两个异常都是非检查型异常，都继承自RuntimeException。该问题可能会引出另一个问题，即Java和C的数组有什么不同之处，因为C里面的数组是没有大小限制的，绝对不会抛出ArrayIndexOutOfBoundException。\n\n**4)在Java异常处理的过程中，你遵循的那些最好的实践是什么？**\n\n这个问题在面试技术经理是非常常见的一个问题。因为异常处理在项目设计中是非常关键的，所以精通异常处理是十分必要的。异常处理有很多最佳实践，下面列举集中，它们提高你代码的健壮性和灵活性：\n\n　　1) 调用方法的时候返回布尔值来代替返回null，这样可以NullPointerException。由于空指针是java异常里最恶心的异常\n\n　　2) catch块里别不写代码。空catch块是异常处理里的错误事件，因为它只是捕获了异常，却没有任何处理或者提示。通常你起码要打印出异常信息，当然你最好根据需求对异常信息进行处理。\n\n　　3)能抛受控异常（checked Exception）就尽量不抛受非控异常(checked Exception)。通过去掉重复的异常处理代码，可以提高代码的可读性。\n\n　　4) 绝对不要让你的数据库相关异常显示到客户端。由于绝大多数数据库和SQLException异常都是受控异常，在Java中，你应该在DAO层把异常信息处理，然后返回处理过的能让用户看懂并根据异常提示信息改正操作的异常信息。\n\n　　5) 在Java中，一定要在数据库连接，数据库查询，流处理后，在finally块中调用close()方法。\n\n**5) 既然我们可以用RuntimeException来处理错误，那么你认为为什么Java中还存在检查型异常?**\n\n这是一个有争议的问题，在回答该问题时你应当小心。虽然他们肯定愿意听到你的观点，但其实他们最感兴趣的还是有说服力的理由。我认为其中一个理由是，存在检查型异常是一个设计上的决定，受到了诸如C++等比Java更早编程语言设计经验的影响。绝大多数检查型异常位于java.io包内，这是合乎情理的，因为在你请求了不存在的系统资源的时候，一段强壮的程序必须能够优雅的处理这种情况。通过把IOException声明为检查型异常，Java 确保了你能够优雅的对异常进行处理。另一个可能的理由是，可以使用catch或finally来确保数量受限的系统资源（比如文件描述符）在你使用后尽早得到释放。Joshua\nBloch编写的[Effective Java 一书](http://www.amazon.com/dp/0321356683/?tag=javamysqlanta-20)中多处涉及到了该话题，值得一读。\n\n**6) throw 和 throws这两个关键字在java中有什么不同?**\n\n　　一个java初学者应该掌握的面试问题。throw 和 throws乍看起来是很相似的尤其是在你还是一个java初学者的时候。尽管他们看起来相似，都是在处理异常时候使用到的。但在代码里的使用方法和用到的地方是不同的。throws总是出现在一个函数头中，用来标明该成员函数可能抛出的各种异常, 你也可以申明未检查的异常，但这不是编译器强制的。如果方法抛出了异常那么调用这个方法的时候就需要将这个异常处理。另一个关键字 throw 是用来抛出任意异常的，按照语法你可以抛出任意 Throwable(i.e. Throwable\n或任何Throwable的衍生类) , throw可以中断程序运行，因此可以用来代替return . 最常见的例子是用 throw 在一个空方法中需要return的地方抛出 UnSupportedOperationException.\n可以看下这篇[文章](http://javarevisited.blogspot.com/2012/02/difference-between-throw-and-throws-in.html)查看这两个关键字在java中更多的差异 。\n\n**7) 什么是“异常链”?**\n\n　　“异常链”是Java中非常流行的异常处理概念，是指在进行一个异常处理时抛出了另外一个异常，由此产生了一个异常链条。该技术大多用于将“ 受检查异常” （ checked exception）封装成为“非受检查异常”（unchecked exception)或者RuntimeException。顺便说一下，如果因为因为异常你决定抛出一个新的异常，你一定要包含原有的异常，这样，处理程序才可以通过getCause()和initCause()方法来访问异常最终的根源。\n\n**8) 你曾经自定义实现过异常吗？怎么写的?**\n\n　　很显然，我们绝大多数都写过自定义或者业务异常，像AccountNotFoundException。在面试过程中询问这个Java异常问题的主要原因是去发现你如何使用这个特性的。这可以更准确和精致的去处理异常，当然这也跟你选择checked 还是unchecked exception息息相关。通过为每一个特定的情况创建一个特定的异常，你就为调用者更好的处理异常提供了更好的选择。相比通用异常（general exception)，我更倾向更为精确的异常。大量的创建自定义异常会增加项目class的个数，因此，在自定义异常和通用异常之间维持一个平衡是成功的关键。\n\n**9) JDK7中对异常处理做了什么改变？**\n\n　　这是最近新出的Java异常处理的面试题。JDK7中对错误(Error)和异常(Exception)处理主要新增加了2个特性，一是在一个catch块中可以出来多个异常，就像原来用多个catch块一样。另一个是自动化资源管理(ARM), 也称为try-with-resource块。这2个特性都可以在处理异常时减少代码量，同时提高代码的可读性。对于这些特性了解，不仅帮助开发者写出更好的异常处理的代码，也让你在面试中显的更突出。我推荐大家读一下Java 7攻略，这样可以更深入的了解这2个非常有用的特性。\n\n**10) 你遇到过 OutOfMemoryError 错误嘛？你是怎么搞定的？**\n\n　　这个面试题会在面试高级程序员的时候用，面试官想知道你是怎么处理这个危险的OutOfMemoryError错误的。必须承认的是，不管你做什么项目，你都会碰到这个问题。所以你要是说没遇到过，面试官肯定不会买账。要是你对这个问题不熟悉，甚至就是没碰到过，而你又有3、4年的Java经验了，那么准备好处理这个问题吧。在回答这个问题的同时，你也可以借机向面试秀一下你处理内存泄露、调优和调试方面的牛逼技能。我发现掌握这些技术的人都能给面试官留下深刻的印象。\n\n**11) 如果执行finally代码块之前方法返回了结果，或者JVM退出了，finally块中的代码还会执行吗？**\n\n　　这个问题也可以换个方式问：“如果在try或者finally的代码块中调用了System.exit()，结果会是怎样”。了解finally块是怎么执行的，即使是try里面已经使用了return返回结果的情况，对了解Java的异常处理都非常有价值。只有在try里面是有System.exit(0)来退出JVM的情况下finally块中的代码才不会执行。\n\n**12)Java中final,finalize,finally关键字的区别**\n\n　　这是一个经典的Java面试题了。我的一个朋友为Morgan Stanley招电信方面的核心Java开发人员的时候就问过这个问题。final和finally是Java的关键字，而finalize则是方法。final关键字在创建不可变的类的时候非常有用，只是声明这个类是final的。而finalize()方法则是垃圾回收器在回收一个对象前调用，但也Java规范里面没有保证这个方法一定会被调用。finally关键字是唯一一个和这篇文章讨论到的异常处理相关的关键字。在你的产品代码中，在关闭连接和资源文件的是时候都必须要用到finally块。\n\n## 参考文章\n\nhttps://www.xuebuyuan.com/3248044.html\nhttps://www.jianshu.com/p/49d2c3975c56\nhttp://c.biancheng.net/view/1038.html\nhttps://blog.csdn.net/Lisiluan/article/details/88745820\nhttps://blog.csdn.net/michaelgo/article/details/82790253\n\n\n"
  },
  {
    "path": "docs/Java/basic/Java注解和最佳实践.md",
    "content": "# 目录\n  * [Java注解简介](#java注解简介)\n    * [注解如同标签](#注解如同标签)\n  * [Java 注解概述](#java-注解概述)\n    * [什么是注解？](#什么是注解？)\n    * [注解的用处](#注解的用处)\n    * [注解的原理](#注解的原理)\n    * [元注解](#元注解)\n    * [JDK里的注解](#jdk里的注解)\n  * [注解处理器实战](#注解处理器实战)\n  * [不同类型的注解](#不同类型的注解)\n    * [类注解](#类注解)\n    * [方法注解](#方法注解)\n    * [参数注解](#参数注解)\n    * [变量注解](#变量注解)\n  * [Java注解相关面试题](#java注解相关面试题)\n    * [什么是注解？他们的典型用例是什么？](#什么是注解？他们的典型用例是什么？)\n    * [描述标准库中一些有用的注解。](#描述标准库中一些有用的注解。)\n    * [可以从注解方法声明返回哪些对象类型？](#可以从注解方法声明返回哪些对象类型？)\n    * [哪些程序元素可以注解？](#哪些程序元素可以注解？)\n    * [有没有办法限制可以应用注解的元素？](#有没有办法限制可以应用注解的元素？)\n    * [什么是元注解？](#什么是元注解？)\n    * [下面的代码会编译吗？](#下面的代码会编译吗？)\n  * [参考文章](#参考文章)\n\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章首发于我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《夯实Java基础系列博文》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n该系列博文会告诉你如何从入门到进阶，一步步地学习Java基础知识，并上手进行实战，接着了解每个Java知识点背后的实现原理，更完整地了解整个Java技术体系，形成自己的知识框架。为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。 \n\n<!-- more -->\n\n## Java注解简介\n\nAnnotation 中文译过来就是注解、标释的意思，在 Java 中注解是一个很重要的知识点，但经常还是有点让新手不容易理解。\n\n**我个人认为，比较糟糕的技术文档主要特征之一就是：用专业名词来介绍专业名词。**\n比如：\n\n> Java 注解用于为 Java 代码提供元数据。作为元数据，注解不直接影响你的代码执行，但也有一些类型的注解实际上可以用于这一目的。Java 注解是从 Java5 开始添加到 Java 的。\n> 这是大多数网站上对于 Java 注解，解释确实正确，但是说实在话，我第一次学习的时候，头脑一片空白。这什么跟什么啊？听了像没有听一样。因为概念太过于抽象，所以初学者实在是比较吃力才能够理解，然后随着自己开发过程中不断地强化练习，才会慢慢对它形成正确的认识。\n\n我在写这篇文章的时候，我就在思考。如何让自己或者让读者能够比较直观地认识注解这个概念？是要去官方文档上翻译说明吗？我马上否定了这个答案。\n\n后来，我想到了一样东西————墨水，墨水可以挥发、可以有不同的颜色，用来解释注解正好。\n\n不过，我继续发散思维后，想到了一样东西能够更好地代替墨水，那就是印章。印章可以沾上不同的墨水或者印泥，可以定制印章的文字或者图案，如果愿意它也可以被戳到你任何想戳的物体表面。\n\n但是，我再继续发散思维后，又想到一样东西能够更好地代替印章，那就是标签。标签是一张便利纸，标签上的内容可以自由定义。常见的如货架上的商品价格标签、图书馆中的书本编码标签、实验室中化学材料的名称类别标签等等。\n\n并且，往抽象地说，标签并不一定是一张纸，它可以是对人和事物的属性评价。也就是说，标签具备对于抽象事物的解释。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230403213103.png)\n\n所以，基于如此，我完成了自我的知识认知升级，我决定用标签来解释注解。\n\n### 注解如同标签\n\n之前某新闻客户端的评论有盖楼的习惯，于是 “乔布斯重新定义了手机、罗永浩重新定义了傻X” 就经常极为工整地出现在了评论楼层中，并且广大网友在相当长的一段时间内对于这种行为乐此不疲。这其实就是等同于贴标签的行为。\n在某些网友眼中，罗永浩就成了傻X的代名词。\n\n广大网友给罗永浩贴了一个名为“傻x”的标签，他们并不真正了解罗永浩，不知道他当教师、砸冰箱、办博客的壮举，但是因为“傻x”这样的标签存在，这有助于他们直接快速地对罗永浩这个人做出评价，然后基于此，罗永浩就可以成为茶余饭后的谈资，这就是标签的力量。\n\n而在网络的另一边，老罗靠他的人格魅力自然收获一大批忠实的拥泵，他们对于老罗贴的又是另一种标签。 \n\n![](https://img-blog.csdn.net/20170627213530055?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYnJpYmx1ZQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)\n\n\n老罗还是老罗，但是由于人们对于它贴上的标签不同，所以造成对于他的看法大相径庭，不喜欢他的人整天在网络上评论抨击嘲讽，而崇拜欣赏他的人则会愿意挣钱购买锤子手机的发布会门票。\n\n我无意于评价这两种行为，我再引个例子。\n\n《奇葩说》是近年网络上非常火热的辩论节目，其中辩手陈铭被另外一个辩手马薇薇攻击说是————“站在宇宙中心呼唤爱”，然后贴上了一个大大的标签————“鸡汤男”，自此以后，观众再看到陈铭的时候，首先映入脑海中便是“鸡汤男”三个大字，其实本身而言陈铭非常优秀，为人师表、作风正派、谈吐举止得体，但是在网络中，因为娱乐至上的环境所致，人们更愿意以娱乐的心态来认知一切，于是“鸡汤男”就如陈铭自己所说成了一个撕不了的标签。\n\n**我们可以抽象概括一下，标签是对事物行为的某些角度的评价与解释。**\n\n到这里，终于可以引出本文的主角注解了。\n\n**初学者可以这样理解注解：想像代码具有生命，注解就是对于代码中某些鲜活个体的贴上去的一张标签。简化来讲，注解如同一张标签。**\n\n在未开始学习任何注解具体语法而言，你可以把注解看成一张标签。这有助于你快速地理解它的大致作用。如果初学者在学习过程有大脑放空的时候，请不要慌张，对自己说：\n\n注解，标签。注解，标签。\n\n## Java 注解概述\n### 什么是注解？\n\n\n>   对于很多初次接触的开发者来说应该都有这个疑问？Annontation是Java5开始引入的新特征，中文名称叫注解。它提供了一种安全的类似注释的机制，用来将任何的信息或元数据（metadata）与程序元素（类、方法、成员变量等）进行关联。为程序的元素（类、方法、成员变量）加上更直观更明了的说明，这些说明信息是与程序的业务逻辑无关，并且供指定的工具或框架使用。Annontation像一种修饰符一样，应用于包、类型、构造方法、方法、成员变量、参数及本地变量的声明语句中。\n\n\n　　Java注解是附加在代码中的一些元信息，用于一些工具在编译、运行时进行解析和使用，起到说明、配置的功能。注解不会也不能影响代码的实际逻辑，仅仅起到辅助性的作用。包含在 java.lang.annotation 包中。\n\n \n\n### 注解的用处\n\n  1、生成文档。这是最常见的，也是java 最早提供的注解。常用的有@param @return 等\n  2、跟踪代码依赖性，实现替代配置文件功能。比如Dagger 2依赖注入，未来java开发，将大量注解配置，具有很大用处;\n  3、在编译时进行格式检查。如@override 放在方法前，如果你这个方法并不是覆盖了超类方法，则编译时就能检查出。\n\n \n\n### 注解的原理\n　　注解本质是一个继承了Annotation的特殊接口，其具体实现类是Java运行时生成的动态代理类。而我们通过反射获取注解时，返回的是Java运行时生成的动态代理对象$Proxy1。通过代理对象调用自定义注解（接口）的方法，会最终调用AnnotationInvocationHandler的invoke方法。该方法会从memberValues这个Map中索引出对应的值。而memberValues的来源是Java常量池。\n\n \n\n### 元注解\njava.lang.annotation提供了四种元注解，专门注解其他的注解（在自定义注解的时候，需要使用到元注解）：\n   @Documented –注解是否将包含在JavaDoc中\n   @Retention –什么时候使用该注解\n   @Target –注解用于什么地方\n   @Inherited – 是否允许子类继承该注解\n\n  1.）@Retention– 定义该注解的生命周期\n\n      ●   RetentionPolicy.SOURCE : 在编译阶段丢弃。这些注解在编译结束之后就不再有任何意义，所以它们不会写入字节码。@Override, @SuppressWarnings都属于这类注解。\n      \n      ●   RetentionPolicy.CLASS : 在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式\n      \n      ●   RetentionPolicy.RUNTIME : 始终不会丢弃，运行期也保留该注解，因此可以使用反射机制读取该注解的信息。我们自定义的注解通常使用这种方式。\n\n  2.）Target – 表示该注解用于什么地方。默认值为任何元素，表示该注解用于什么地方。可用的ElementType参数包括\n\n      ● ElementType.CONSTRUCTOR:用于描述构造器\n      ● ElementType.FIELD:成员变量、对象、属性（包括enum实例）\n      ● ElementType.LOCAL_VARIABLE:用于描述局部变量\n      ● ElementType.METHOD:用于描述方法\n      ● ElementType.PACKAGE:用于描述包\n      ● ElementType.PARAMETER:用于描述参数\n      ● ElementType.TYPE:用于描述类、接口(包括注解类型) 或enum声明\n\n 3.)@Documented–一个简单的Annotations标记注解，表示是否将注解信息添加在java文档中。\n\n 4.)@Inherited – 定义该注释和子类的关系\n     @Inherited 元注解是一个标记注解，@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class，则这个annotation将被用于该class的子类。\n\n\n### JDK里的注解\nJDK 内置注解\n先来看几个 Java 内置的注解，让大家热热身。\n\n@Override 演示\n````\n    class Parent {\n        public void run() {\n        }\n    }\n    \n    class Son extends Parent {\n        /**\n         * 这个注解是为了检查此方法是否真的是重写父类的方法\n         * 这时候就不用我们用肉眼去观察到底是不是重写了\n         */\n        @Override\n        public void run() {\n        }\n    }\n````\n@Deprecated 演示\n````\nclass Parent {\n\n    /**\n     * 此注解代表过时了，但是如果可以调用到，当然也可以正常使用\n     * 但是，此方法有可能在以后的版本升级中会被慢慢的淘汰\n     * 可以放在类，变量，方法上面都起作用\n     */\n    @Deprecated\n    public void run() {\n    }\n    }\n    \n    public class JDKAnnotationDemo {\n        public static void main(String[] args) {\n            Parent parent = new Parent();\n            parent.run(); // 在编译器中此方法会显示过时标志\n        }\n    }\n````\n@SuppressWarnings 演示\n````\nclass Parent {\n\n    // 因为定义的 name 没有使用，那么编译器就会有警告，这时候使用此注解可以屏蔽掉警告\n    // 即任意不想看到的编译时期的警告都可以用此注解屏蔽掉，但是不推荐，有警告的代码最好还是处理一下\n    @SuppressWarnings(\"all\")\n    private String name;\n    }\n````\n@FunctionalInterface 演示\n\n````\n/**\n * 此注解是 Java8 提出的函数式接口，接口中只允许有一个抽象方法\n * 加上这个注解之后，类中多一个抽象方法或者少一个抽象方法都会报错\n */\n@FunctionalInterface\ninterface Func {\n    void run();\n}\n````\n\n## 注解处理器实战\n\n注解处理器\n注解处理器才是使用注解整个流程中最重要的一步了。所有在代码中出现的注解，它到底起了什么作用，都是在注解处理器中定义好的。\n概念：注解本身并不会对程序的编译方式产生影响，而是注解处理器起的作用；注解处理器能够通过在运行时使用反射获取在程序代码中的使用的注解信息，从而实现一些额外功能。前提是我们自定义的注解使用的是 RetentionPolicy.RUNTIME 修饰的。这也是我们在开发中使用频率很高的一种方式。\n\n我们先来了解下如何通过在运行时使用反射获取在程序中的使用的注解信息。如下类注解和方法注解。\n\n类注解\n\n````\nClass aClass = ApiController.class;\nAnnotation[] annotations = aClass.getAnnotations();\n\nfor(Annotation annotation : annotations) {\n    if(annotation instanceof ApiAuthAnnotation) {\n        ApiAuthAnnotation apiAuthAnnotation = (ApiAuthAnnotation) annotation;\n        System.out.println(\"name: \" + apiAuthAnnotation.name());\n        System.out.println(\"age: \" + apiAuthAnnotation.age());\n    }\n}\n方法注解\nMethod method = ... //通过反射获取方法对象\nAnnotation[] annotations = method.getDeclaredAnnotations();\n\nfor(Annotation annotation : annotations) {\n    if(annotation instanceof ApiAuthAnnotation) {\n        ApiAuthAnnotation apiAuthAnnotation = (ApiAuthAnnotation) annotation;\n        System.out.println(\"name: \" + apiAuthAnnotation.name());\n        System.out.println(\"age: \" + apiAuthAnnotation.age());\n    }\n}   \n````\n此部分内容可参考: 通过反射获取注解信息\n\n注解处理器实战\n接下来我通过在公司中的一个实战改编来演示一下注解处理器的真实使用场景。\n需求: 网站后台接口只能是年龄大于 18 岁的才能访问，否则不能访问\n前置准备: 定义注解（这里使用上文的完整注解），使用注解（这里使用上文中使用注解的例子）\n接下来要做的事情: 写一个切面，拦截浏览器访问带注解的接口，取出注解信息，判断年龄来确定是否可以继续访问。\n\n在 dispatcher-servlet.xml 文件中定义 aop 切面\n````\n<aop:config>\n    <!--定义切点，切的是我们自定义的注解-->\n    <aop:pointcut id=\"apiAuthAnnotation\" expression=\"@annotation(cn.caijiajia.devops.aspect.ApiAuthAnnotation)\"/>\n    <!--定义切面，切点是 apiAuthAnnotation，切面类即注解处理器是 apiAuthAspect，主处理逻辑在方法名为 auth 的方法中-->\n    <aop:aspect ref=\"apiAuthAspect\">\n        <aop:around method=\"auth\" pointcut-ref=\"apiAuthAnnotation\"/>\n    </aop:aspect>\n</aop:config>\n````\n切面类处理逻辑即注解处理器代码如\n````\n@Component(\"apiAuthAspect\")\npublic class ApiAuthAspect {\n\n    public Object auth(ProceedingJoinPoint pjp) throws Throwable {\n        Method method = ((MethodSignature) pjp.getSignature()).getMethod();\n        ApiAuthAnnotation apiAuthAnnotation = method.getAnnotation(ApiAuthAnnotation.class);\n        Integer age = apiAuthAnnotation.age();\n        if (age > 18) {\n            return pjp.proceed();\n        } else {\n            throw new RuntimeException(\"你未满18岁，禁止访问\");\n        }\n    }\n}\n````\n\n## 不同类型的注解\n\n### 类注解\n\n你可以在运行期访问类，方法或者变量的注解信息，下是一个访问类注解的例子：\n\n```\nClass aClass = TheClass.class;\nAnnotation[] annotations = aClass.getAnnotations();\n\nfor(Annotation annotation : annotations){\n    if(annotation instanceof MyAnnotation){\n        MyAnnotation myAnnotation = (MyAnnotation) annotation;\n        System.out.println(\"name: \" + myAnnotation.name());\n        System.out.println(\"value: \" + myAnnotation.value());\n    }\n}\n```\n\n你还可以像下面这样指定访问一个类的注解：\n\n```\nClass aClass = TheClass.class;\nAnnotation annotation = aClass.getAnnotation(MyAnnotation.class);\n\nif(annotation instanceof MyAnnotation){\n    MyAnnotation myAnnotation = (MyAnnotation) annotation;\n    System.out.println(\"name: \" + myAnnotation.name());\n    System.out.println(\"value: \" + myAnnotation.value());\n}\n```\n\n### 方法注解\n\n下面是一个方法注解的例子：\n\n```\npublic class TheClass {\n  @MyAnnotation(name=\"someName\",  value = \"Hello World\")\n  public void doSomething(){}\n}\n```\n\n你可以像这样访问方法注解：\n\n```\nMethod method = ... //获取方法对象\nAnnotation[] annotations = method.getDeclaredAnnotations();\n\nfor(Annotation annotation : annotations){\n    if(annotation instanceof MyAnnotation){\n        MyAnnotation myAnnotation = (MyAnnotation) annotation;\n        System.out.println(\"name: \" + myAnnotation.name());\n        System.out.println(\"value: \" + myAnnotation.value());\n    }\n}\n```\n\n你可以像这样访问指定的方法注解：\n\n```\nMethod method = ... // 获取方法对象\nAnnotation annotation = method.getAnnotation(MyAnnotation.class);\n\nif(annotation instanceof MyAnnotation){\n    MyAnnotation myAnnotation = (MyAnnotation) annotation;\n    System.out.println(\"name: \" + myAnnotation.name());\n    System.out.println(\"value: \" + myAnnotation.value());\n}\n```\n\n### 参数注解\n\n方法参数也可以添加注解，就像下面这样：\n\n```\npublic class TheClass {\n  public static void doSomethingElse(\n        @MyAnnotation(name=\"aName\", value=\"aValue\") String parameter){\n  }\n}\n```\n\n你可以通过 Method对象来访问方法参数注解：\n\n```\nMethod method = ... //获取方法对象\nAnnotation[][] parameterAnnotations = method.getParameterAnnotations();\nClass[] parameterTypes = method.getParameterTypes();\n\nint i=0;\nfor(Annotation[] annotations : parameterAnnotations){\n  Class parameterType = parameterTypes[i++];\n\n  for(Annotation annotation : annotations){\n    if(annotation instanceof MyAnnotation){\n        MyAnnotation myAnnotation = (MyAnnotation) annotation;\n        System.out.println(\"param: \" + parameterType.getName());\n        System.out.println(\"name : \" + myAnnotation.name());\n        System.out.println(\"value: \" + myAnnotation.value());\n    }\n  }\n}\n```\n\n需要注意的是 Method.getParameterAnnotations()方法返回一个注解类型的二维数组，每一个方法的参数包含一个注解数组。\n\n### 变量注解\n\n下面是一个变量注解的例子：\n\n```\npublic class TheClass {\n\n  @MyAnnotation(name=\"someName\",  value = \"Hello World\")\n  public String myField = null;\n}\n```\n\n你可以像这样来访问变量的注解：\n\n```\nField field = ... //获取方法对象目录\nAnnotation[] annotations = field.getDeclaredAnnotations();\n\nfor(Annotation annotation : annotations){\n if(annotation instanceof MyAnnotation){\n MyAnnotation myAnnotation = (MyAnnotation) annotation;\n System.out.println(\"name: \" + myAnnotation.name());\n System.out.println(\"value: \" + myAnnotation.value());\n }\n}\n```\n\n你可以像这样访问指定的变量注解：\n\n```\nField field = ...//获取方法对象目录\n\nAnnotation annotation = field.getAnnotation(MyAnnotation.class);\n\nif(annotation instanceof MyAnnotation){\n MyAnnotation myAnnotation = (MyAnnotation) annotation;\n System.out.println(\"name: \" + myAnnotation.name());\n System.out.println(\"value: \" + myAnnotation.value());\n}\n```\n\n## Java注解相关面试题\n\n### 什么是注解？他们的典型用例是什么？\n\n注解是绑定到程序源代码元素的元数据，对运行代码的操作没有影响。\n\n他们的典型用例是：\n\n*   编译器的信息 - 使用注解，编译器可以检测错误或抑制警告\n*   编译时和部署时处理 - 软件工具可以处理注解并生成代码，配置文件等。\n*   运行时处理 - 可以在运行时检查注解以自定义程序的行为\n\n### 描述标准库中一些有用的注解。\n\njava.lang和java.lang.annotation包中有几个注解，更常见的包括但不限于此：\n\n*   @Override -标记方法是否覆盖超类中声明的元素。如果它无法正确覆盖该方法，编译器将发出错误\n*   @Deprecated - 表示该元素已弃用且不应使用。如果程序使用标有此批注的方法，类或字段，编译器将发出警告\n*   @SuppressWarnings - 告诉编译器禁止特定警告。在与泛型出现之前编写的遗留代码接口时最常用的\n*   @FunctionalInterface - 在Java 8中引入，表明类型声明是一个功能接口，可以使用Lambda Expression提供其实现\n\n\n### 可以从注解方法声明返回哪些对象类型？\n\n返回类型必须是基本类型，String，Class，Enum或数组类型之一。否则，编译器将抛出错误。\n\n这是一个成功遵循此原则的示例代码：\n\n```\nenum Complexity {\n    LOW, HIGH\n}\n\npublic @interface ComplexAnnotation {\n    Class<? extends Object> value();\n\n    int[] types();\n\n    Complexity complexity();\n}\n\n```\n\n下一个示例将无法编译，因为Object不是有效的返回类型：\n\n```\npublic @interface FailingAnnotation {\n    Object complexity();\n}\n\n```\n\n### 哪些程序元素可以注解？\n\n注解可以应用于整个源代码的多个位置。它们可以应用于类，构造函数和字段的声明：\n\n```\n@SimpleAnnotation\npublic class Apply {\n    @SimpleAnnotation\n    private String aField;\n\n    @SimpleAnnotation\n    public Apply() {\n        // ...\n    }\n}\n\n```\n\n方法及其参数：\n\n```\n@SimpleAnnotation\npublic void aMethod(@SimpleAnnotation String param) {\n    // ...\n}\n\n```\n\n局部变量，包括循环和资源变量：\n\n```\n@SimpleAnnotation\nint i = 10;\n\nfor (@SimpleAnnotation int j = 0; j < i; j++) {\n    // ...\n}\n\ntry (@SimpleAnnotation FileWriter writer = getWriter()) {\n    // ...\n} catch (Exception ex) {\n    // ...\n}\n\n```\n\n其他注解类型：\n\n```\n@SimpleAnnotation\npublic @interface ComplexAnnotation {\n    // ...\n}\n\n```\n\n甚至包，通过package-info.java文件：\n\n```\n@PackageAnnotation\npackage com.baeldung.interview.annotations;\n\n```\n\n从Java 8开始，它们也可以应用于类型的使用。为此，注解必须指定值为ElementType.USE的@Target注解：\n\n```\n@Target(ElementType.TYPE_USE)\npublic @interface SimpleAnnotation {\n    // ...\n}\n\n```\n\n现在，注解可以应用于类实例创建：\n\n```\nnew @SimpleAnnotation Apply();\n\n```\n\n类型转换：\n\n```\naString = (@SimpleAnnotation String) something;\n\n```\n\n接口中：\n\n```\npublic class SimpleList<T>\n  implements @SimpleAnnotation List<@SimpleAnnotation T> {\n    // ...\n}\n\n```\n\n抛出异常上：\n\n```\nvoid aMethod() throws @SimpleAnnotation Exception {\n    // ...\n}\n\n```\n\n### 有没有办法限制可以应用注解的元素？\n\n有，@ Target注解可用于此目的。如果我们尝试在不适用的上下文中使用注解，编译器将发出错误。\n\n以下是仅将@SimpleAnnotation批注的用法限制为字段声明的示例：\n\n```\n@Target(ElementType.FIELD)\npublic @interface SimpleAnnotation {\n    // ...\n}\n\n```\n\n如果我们想让它适用于更多的上下文，我们可以传递多个常量：\n\n```\n@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PACKAGE })\n\n```\n\n我们甚至可以制作一个注解，因此它不能用于注解任何东西。当声明的类型仅用作复杂注解中的成员类型时，这可能会派上用场：\n\n```\n@Target({})\npublic @interface NoTargetAnnotation {\n    // ...\n}\n\n```\n\n### 什么是元注解？\n\n元注解适用于其他注解的注解。\n\n所有未使用@Target标记或使用它标记但包含ANNOTATION_TYPE常量的注解也是元注解：\n\n```\n@Target(ElementType.ANNOTATION_TYPE)\npublic @interface SimpleAnnotation {\n    // ...\n}\n\n```\n\n\n### 下面的代码会编译吗？\n\n```\n@Target({ ElementType.FIELD, ElementType.TYPE, ElementType.FIELD })\npublic @interface TestAnnotation {\n    int[] value() default {};\n}\n\n```\n\n不能。如果在@Target注解中多次出现相同的枚举常量，那么这是一个编译时错误。\n\n删除重复常量将使代码成功编译：\n\n```\n@Target({ ElementType.FIELD, ElementType.TYPE})\n\n```\n\n## 参考文章\n\nhttps://blog.fundodoo.com/2018/04/19/130.html\nhttps://blog.csdn.net/qq_37939251/article/details/83215703\nhttps://blog.51cto.com/4247649/2109129\nhttps://www.jianshu.com/p/2f2460e6f8e7\nhttps://blog.csdn.net/yuzongtao/article/details/83306182\n\n\n\n"
  },
  {
    "path": "docs/Java/basic/Java类和包.md",
    "content": "# 目录\n\n* [Java中的包概念](#java中的包概念)\n    * [包的作用](#包的作用)\n    * [package 的目录结构](#package-的目录结构)\n    * [设置 CLASSPATH 系统变量](#设置-classpath-系统变量)\n* [常用jar包](#常用jar包)\n    * [java软件包的类型](#java软件包的类型)\n    * [dt.jar](#dtjar)\n    * [rt.jar](#rtjar)\n* [*.java文件的奥秘](#java文件的奥秘)\n    * [*.Java文件简介](#java文件简介)\n    * [为什么一个java源文件中只能有一个public类？](#为什么一个java源文件中只能有一个public类？)\n    * [Main方法](#main方法)\n    * [外部类的访问权限](#外部类的访问权限)\n    * [Java包的命名规则](#java包的命名规则)\n* [参考文章](#参考文章)\n\n\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章首发于我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《夯实Java基础系列博文》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n该系列博文会告诉你如何从入门到进阶，一步步地学习Java基础知识，并上手进行实战，接着了解每个Java知识点背后的实现原理，更完整地了解整个Java技术体系，形成自己的知识框架。为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n\n<!-- more -->\n\n## Java中的包概念\n\nJava中的包是封装一组类，子包和接口的机制。软件包用于：\n\n防止命名冲突。例如，可以有两个名称分别为Employee的类，college.staff.cse.Employee和college.staff.ee.Employee\n更轻松地搜索/定位和使用类，接口，枚举和注释\n\n提供受控访问：受保护和默认有包级别访问控制。受保护的成员可以通过同一个包及其子类中的类访问。默认成员（没有任何访问说明符）只能由同一个包中的类访问。\n\n包可以被视为数据封装（或数据隐藏）。\n\n我们所需要做的就是将相关类放入包中。之后，我们可以简单地从现有的软件包中编写一个导入类，并将其用于我们的程序中。一个包是一组相关类的容器，其中一些类可以访问，并且其他类被保存用于内部目的。\n我们可以在程序中尽可能多地重用包中的现有类。\n\n为了更好地组织类，Java 提供了包机制，用于区别类名的命名空间。\n\n### 包的作用\n\n*   1、把功能相似或相关的类或接口组织在同一个包中，方便类的查找和使用。\n\n*   2、如同文件夹一样，包也采用了树形目录的存储方式。同一个包中的类名字是不同的，不同的包中的类的名字是可以相同的，当同时调用两个不同包中相同类名的类时，应该加上包名加以区别。因此，包可以避免名字冲突。\n\n*   3、包也限定了访问权限，拥有包访问权限的类才能访问某个包中的类。\n\nJava 使用包（package）这种机制是为了防止命名冲突，访问控制，提供搜索和定位类（class）、接口、枚举（enumerations）和注释（annotation）等。\n\n    包语句的语法格式为：\n    \n    package  pkg1[．pkg2[．pkg3…]];\n    \n    例如,一个Something.java 文件它的内容\n    \n    package  net.java.util; public  class  Something{ ... }\n\n那么它的路径应该是**net/java/util/Something.java**这样保存的。 package(包) 的作用是把不同的 java 程序分类保存，更方便的被其他 java 程序调用。\n\n一个包（package）可以定义为一组相互联系的类型（类、接口、枚举和注释），为这些类型提供访问保护和命名空间管理的功能。\n\n以下是一些 Java 中的包：\n\n*   **java.lang**-打包基础的类\n*   **java.io**-包含输入输出功能的函数\n\n开发者可以自己把一组类和接口等打包，并定义自己的包。而且在实际开发中这样做是值得提倡的，当你自己完成类的实现之后，将相关的类分组，可以让其他的编程者更容易地确定哪些类、接口、枚举和注释等是相关的。\n\n由于包创建了新的命名空间（namespace），所以不会跟其他包中的任何名字产生命名冲突。使用包这种机制，更容易实现访问控制，并且让定位相关类更加简单。\n\n### package 的目录结构\n\n类放在包中会有两种主要的结果：\n\n*   包名成为类名的一部分，正如我们前面讨论的一样。\n*   包名必须与相应的字节码所在的目录结构相吻合。\n\n下面是管理你自己 java 中文件的一种简单方式：\n\n将类、接口等类型的源码放在一个文本中，这个文件的名字就是这个类型的名字，并以.java作为扩展名。例如：\n\n````\n// 文件名 : Car.java  \npackage  vehicle; \npublic  class  Car  {  \n// 类实现 \n}\n````\n\n接下来，把源文件放在一个目录中，这个目录要对应类所在包的名字。\n\n....\\vehicle\\Car.java\n\n现在，正确的类名和路径将会是如下样子：\n\n*   类名 -> vehicle.Car\n\n*   路径名 -> vehicle\\Car.java (在 windows 系统中)\n\n通常，一个公司使用它互联网域名的颠倒形式来作为它的包名.例如：互联网域名是 runoob.com，所有的包名都以 com.runoob 开头。包名中的每一个部分对应一个子目录。\n\n例如：有一个**com.runoob.test**的包，这个包包含一个叫做 Runoob.java 的源文件，那么相应的，应该有如下面的一连串子目录：\n\n....\\com\\runoob\\test\\Runoob.java\n\n编译的时候，编译器为包中定义的每个类、接口等类型各创建一个不同的输出文件，输出文件的名字就是这个类型的名字，并加上 .class 作为扩展后缀。 例如：\n````\n// 文件名: Runoob.java  \npackage  com.runoob.test; \npublic  class  Runoob  {  }  \nclass  Google  {  }\n````\n\n\n现在，我们用-d选项来编译这个文件，如下：\n````\n    $javac -d .  Runoob.java\n````\n这样会像下面这样放置编译了的文件：\n\n````\n    .\\com\\runoob\\test\\Runoob.class  \n    .\\com\\runoob\\test\\Google.class\n````\n\n你可以像下面这样来导入所有**\\com\\runoob\\test\\**中定义的类、接口等：\n````\n    import com.runoob.test.*;\n````\n\n编译之后的 .class 文件应该和 .java 源文件一样，它们放置的目录应该跟包的名字对应起来。但是，并不要求 .class 文件的路径跟相应的 .java 的路径一样。你可以分开来安排源码和类的目录。\n\n````\n    <path-one>\\sources\\com\\runoob\\test\\Runoob.java \n    <path-two>\\classes\\com\\runoob\\test\\Google.class\n````\n\n这样，你可以将你的类目录分享给其他的编程人员，而不用透露自己的源码。用这种方法管理源码和类文件可以让编译器和java 虚拟机（JVM）可以找到你程序中使用的所有类型。\n\n类目录的绝对路径叫做**class path**。设置在系统变量**CLASSPATH**中。编译器和 java 虚拟机通过将 package 名字加到 class path 后来构造 .class 文件的路径。\n\n````\n    <path- two>\\classes 是 class path，package 名字是 com.runoob.test,而编译器和 JVM 会在 <path-two>\\classes\\com\\runoob\\test 中找 .class 文件。\n````\n\n一个 class path 可能会包含好几个路径，多路径应该用分隔符分开。默认情况下，编译器和 JVM 查找当前目录。JAR 文件按包含 Java 平台相关的类，所以他们的目录默认放在了 class path 中。\n\n### 设置 CLASSPATH 系统变量\n\n用下面的命令显示当前的CLASSPATH变量：\n\n*   Windows 平台（DOS 命令行下）：C:\\> set CLASSPATH\n*   UNIX 平台（Bourne shell 下）：# echo $CLASSPATH\n\n删除当前CLASSPATH变量内容：\n\n*   Windows 平台（DOS 命令行下）：C:\\> set CLASSPATH=\n*   UNIX 平台（Bourne shell 下）：# unset CLASSPATH; export CLASSPATH\n\n设置CLASSPATH变量:\n\n*   Windows 平台（DOS 命令行下）： C:\\> set CLASSPATH=C:\\users\\jack\\java\\classes\n*   UNIX 平台（Bourne shell 下）：# CLASSPATH=/home/jack/java/classes; export CLASSPATH\n\nJava包（package）详解\njava包的作用是为了区别类名的命名空间　　\n\n1、把功能相似或相关的类或接口组织在同一个包中，方便类的查找和使用。、\n\n2、如同文件夹一样，包也采用了树形目录的存储方式。同一个包中的类名字是不同的，不同的包中的类的名字是可以相同的，\n\n当同时调用两个不同包中相同类名的类时，应该加上包名加以区别。因此，包可以避免名字冲突。\n\n3、包也限定了访问权限，拥有包访问权限的类才能访问某个包中的类。\n\n创建包\n创建包的时候，你需要为这个包取一个合适的名字。之后，如果其他的一个源文件包含了这个包提供的类、接口、枚举或者注释类型的时候，都必须将这个包的声明放在这个源文件的开头。\n\n包声明应该在源文件的第一行，每个源文件只能有一个包声明，这个文件中的每个类型都应用于它。\n\n如果一个源文件中没有使用包声明，那么其中的类，函数，枚举，注释等将被放在一个无名的包（unnamed package）中。\n\n例子\n让我们来看一个例子，这个例子创建了一个叫做animals的包。通常使用小写的字母来命名避免与类、接口名字的冲突。\n\n在 animals 包中加入一个接口（interface）：\n\n````\npackage animals;\ninterface Animal {\n   public void eat();\n   public void travel();\n}\n````\n\n接下来，在同一个包中加入该接口的实现：\n\n````\npackage animals;\n/* 文件名 : MammalInt.java */\npublic class MammalInt implements Animal{\n   public void eat(){\n      System.out.println(\"Mammal eats\");\n   }\n   public void travel(){\n      System.out.println(\"Mammal travels\");\n   }\n   public int noOfLegs(){\n      return 0;\n   }\n   public static void main(String args[]){\n      MammalInt m = new MammalInt();\n      m.eat();\n      m.travel();\n   }\n}\n````\n\nimport 关键字\n为了能够使用某一个包的成员，我们需要在 Java 程序中明确导入该包。使用 \"import\" 语句可完成此功能。\n\n在 java 源文件中 import 语句应位于 package 语句之后，所有类的定义之前，可以没有，也可以有多条，其语法格式为：\n\n````\n    import package1[.package2…].(classname|*);\n````\n\n如果在一个包中，一个类想要使用本包中的另一个类，那么该包名可以省略。\n\n通常，一个公司使用它互联网域名的颠倒形式来作为它的包名.例如：互联网域名是 runoob.com，所有的包名都以 com.runoob 开头。包名中的每一个部分对应一个子目录。\n\n例如：有一个 com.runoob.test 的包，这个包包含一个叫做 Runoob.java 的源文件，那么相应的，应该有如下面的一连串子目录：\n```\n    ....\\com\\runoob\\test\\Runoob.java\n```\n## 常用jar包\n\n### java软件包的类型\n\n软件包的类型有内置的软件包和用户定义的软件包内置软件包\n这些软件包由大量的类组成，这些类是Java API的一部分。一些常用的内置软件包有：\n\n1）java.lang：包含语言支持类（例如分类，用于定义基本数据类型，数学运算）。该软件包会自动导入。\n\n2） java.io：包含分类以支持输入/输出操作。\n\n3） java.util：包含实现像链接列表，字典和支持等数据结构的实用类; 用于日期/时间操作。\n\n4） java.applet：包含用于创建Applets的类。\n\n5） java.awt：包含用于实现图形用户界面组件的类（如按钮，菜单等）。\n\n6） java.net：包含支持网络操作的类。\n\n### dt.jar\n\n> SUN对于dt.jar的定义：Also includesdt.jar, the DesignTime archive of BeanInfo files that tell interactive development environments (IDE's) how to display the Java components and how to let the developer customize them for the application。\n\n中文翻译过来就是：dt.jar是BeanInfo文件的DesignTime归档，BeanInfo文件用来告诉集成开发环境（IDE）如何显示Java组件还有如何让开发人员根据应用程序自定义它们。这段文字中提到了几个关键字：DesignTime,BeanInfo,IDE，Java components。其实dt.jar就是DesignTime Archive的缩写。那么何为DesignTime。\n\n何为DesignTime?翻译过来就是设计时。其实了解JavaBean的人都知道design time和runtime（运行时）这两个术语的含义。设计时（DesignTIme）是指在开发环境中通过添加控件，设置控件或窗体属性等方法，建立应用程序的时间。\n\n与此相对应的运行时（RunTIme）是指可以象用户那样与应用程序交互作用的时间。那么现在再理解一下上面的翻译，其实dt.jar包含了swing控件中的BeanInfo，而IDE的GUI Designer需要这些信息。那让我们看一下dt.jar中到底有什么？\n\ndt.jar中全部是Swing组件的BeanInfo。那么到底什么是BeanInfo呢？\n\n何为BeanInfo?JavaBean和BeanInfo有很大的关系。Sun所制定的JavaBean规范，很大程度上是为IDE准备的——它让IDE能够以可视化的方式设置JavaBean的属性。如果在IDE中开发一个可视化应用程序，我们需要通过属性设置的方式对组成应用的各种组件进行定制，IDE通过属性编辑器让开发人员使用可视化的方式设置组件的属性。\n\ndt.jar里面主要是swing组件的BeanInfo。IDE根据这些BeanInfo显示这些组件以及开发人员如何定制他们。\n\n### rt.jar\nrt.jar是runtime的归档。Java基础类库，也就是Java doc里面看到的所有的类的class文件。\n\n![image](https://www.pianshen.com/images/75/856cbbdf52da90fa4f9bbb7b0597ce63.png)\n\nrt.jar 默认就在Root Classloader的加载路径里面的，而在Claspath配置该变量是不需要的；同时jre/lib目录下的其他jar:jce.jar、jsse.jar、charsets.jar、resources.jar都在Root Classloader中。\n\n## java文件的奥秘\n### Java文件简介\n\n.java文件你可以认为只是一个文本文件， 这个文件即是用java语言写成的程序，或者说任务的代码块。\n\n.class文件本质上是一种二进制文件， 它一般是由.java文件通过 javac这个命令（jdk本身提供的工具）生成的一个文件， 而这个文件可以由jvm(java虚拟机)装载（类装载），然后进java解释执行， 这也就是运行你的程序。\n\n你也可以这样比较一下：\n.java与 .c , .cpp, .asm等等文件，本质 上一样的， 只是用一种 语言来描述你要怎么去完成一件事（一个任务）， 而这种语言 计算机本身 是没有办法知道是什么含义的， 它面向的只是程序员本身， 程序员可以通过 语言本身（语法） 来描述或组织这个任务，这也就 是所谓的编程。\n\n最后你当然是需要计算机按照你的意图来运行你的程序， 这时候就先得有一个翻译（编译， 汇编， 链接等等复杂的过程）把它变成机器可理解的指令（这就是大家说的机器语言，机器语言本身也是一种编程语言，只是程序很难写，很难读懂，基本上没有办法维护）。\n\n\n这里的.class文件在计算的体系结构中本质上对应的是一种机器语言（而这里的机器叫作JVM），所以JVM本身是可以直接运行这里的.class文件。所以 你可以进一步地认为，.java与.class与其它的编程语法一样，它们都是程序员用来描述自己的任务的一种语言，只是它们面向的对象不一样，而计算机本身只能识别它自已定义的那些指令什么的（再次强调，这里的计算机本身没有那么严格的定义）\n\n> In short：\n>\n> .java是Java的源文件后缀，里面存放程序员编写的功能代码。\n>\n> .class文件是字节码文件，由.java源文件通过javac命令编译后生成的文件。是可以运行在任何支持Java虚拟机的硬件平台和操作系统上的二进制文件。\n>\n> .class文件并不本地的可执行程序。Java虚拟机就是去运行.class文件从而实现程序的运行。\n\n\n### 为什么一个java源文件中只能有一个public类？\n\n在java编程思想（第四版）一书中有这样3段话（6.4 类的访问权限）：\n\n> 　　1.每个编译单元（文件）都只能有一个public类，这表示，每个编译单元都有单一的公共接口，用public类来表现。该接口可以按要求包含众多的支持包访问权限的类。如果在某个编译单元内有一个以上的public类，编译器就会给出错误信息。\n>\n> 　　2.public类的名称必须完全与含有该编译单元的文件名相同，包含大小写。如果不匹配，同样将得到编译错误。\n>\n> 　　3.虽然不是很常用，但编译单元内完全不带public类也是可能的。在这种情况下，可以随意对文件命名。\n\n总结相关的几个问题：\n\n1、一个”.java”源文件中是否可以包括多个类（不是内部类）？有什么限制？\n\n> 答：可以有多个类，但只能有一个public的类，并且public的类名必须与文件名相一致。\n\n2、为什么一个文件中只能有一个public的类\n\n> 答：编译器在编译时，针对一个java源代码文件（也称为“编译单元”）只会接受一个public类。否则报错。\n\n3、在java文件中是否可以没有public类\n\n> 答：public类不是必须的，java文件中可以没有public类。\n\n4、为什么这个public的类的类名必须和文件名相同\n\n> 答： 是为了方便虚拟机在相应的路径中找到相应的类所对应的字节码文件。\n\n### Main方法\n\n主函数：是一个特殊的函数，作为程序的入口，可以被JVM调用\n\n主函数的定义：\n> public：代表着该函数访问权限是最大的\n\n> static：代表主函数随着类的加载就已经存在了\n\n> void：主函数没有具体的返回值\n\n> main：不是关键字，但是一个特殊的单词，能够被JVM识别\n\n> （String[] args）：函数的参数，参数类型是一个数组，该数组中的元素师字符串，字符串数组。main(String[] args) 字符串数组的 此时空数组的长度是0，但也可以在 运行的时候向其中传入参数。\n\n主函数时固定格式的，JVM识别\n\n主函数可以被重载，但是JVM只识别main（String[] args），其他都是作为一般函数。这里面的args知识数组变量可以更改，其他都不能更改。\n\n一个java文件中可以包含很多个类，每个类中有且仅有一个主函数，但是每个java文件中可以包含多个主函数，在运行时，需要指定JVM入口是哪个。例如一个类的主函数可以调用另一个类的主函数。不一定会使用public类的主函数。\n\n### 外部类的访问权限\n\n外部类只能用public和default修饰。\n\n为什么要对外部类或类做修饰呢？\n\n> 1.存在包概念：public 和 default 能区分这个外部类能对不同包作一个划分                       （default修饰的类，其他包中引入不了这个类，public修饰的类才能被import）\n>\n> 2.protected是包内可见并且子类可见，但是当一个外部类想要继承一个protected修饰的非同包类时，压根找不到这个类，更别提几层了\n>\n> 3.private修饰的外部类，其他任何外部类都无法导入它。\n\n````\n//Java中的文件名要和public修饰的类名相同，否则会报错\n//如果没有public修饰的类，则文件可以随意命名\npublic class Java中的类文件 {\n}\n//非公共开类的访问权限默认是包访问权限，不能用private和protected\n//一个外部类的访问权限只有两种，一种是包内可见，一种是包外可见。\n//如果用private修饰，其他类根本无法看到这个类，也就没有意义了。\n//如果用protected，虽然也是包内可见，但是如果有子类想要继承该类但是不同包时，\n//压根找不到这个类，也不可能继承它了，所以干脆用default代替。\nclass A{\n}\n````\n\n### Java包的命名规则\n\n> 以 java.* 开头的是Java的核心包，所有程序都会使用这些包中的类；\n\n> 以 javax.* 开头的是扩展包，x 是 extension 的意思，也就是扩展。虽然 javax.* 是对 java.* 的优化和扩展，但是由于 javax.* 使用的越来越多，很多程序都依赖于 javax.*，所以 javax.* 也是核心的一部分了，也随JDK一起发布。\n\n> 以 org.* 开头的是各个机构或组织发布的包，因为这些组织很有影响力，它们的代码质量很高，所以也将它们开发的部分常用的类随JDK一起发布。\n\n> 在包的命名方面，为了防止重名，有一个惯例：大家都以自己域名的倒写形式作为开头来为自己开发的包命名，例如百度发布的包会以 com.baidu.* 开头，w3c组织发布的包会以 org.w3c.* 开头，微学苑发布的包会以 net.weixueyuan.* 开头……\n\n> 组织机构的域名后缀一般为 org，公司的域名后缀一般为 com，可以认为 org.* 开头的包为非盈利组织机构发布的包，它们一般是开源的，可以免费使用在自己的产品中，不用考虑侵权问题，而以 com.* 开头的包往往由盈利性的公司发布，可能会有版权问题，使用时要注意。\n\n\n\n## 参考文章\n\nhttps://www.cnblogs.com/ryanzheng/p/8465701.html\nhttps://blog.csdn.net/fuhanghang/article/details/84102404\nhttps://www.runoob.com/java/java-package.html\nhttps://www.breakyizhan.com/java/4260.html\nhttps://blog.csdn.net/qq_36626914/article/details/80627454\n\n"
  },
  {
    "path": "docs/Java/basic/Java自动拆箱装箱里隐藏的秘密.md",
    "content": "# 目录\n\n* [Java 基本数据类型](#java-基本数据类型)\n  * [Java 的两大数据类型:](#java-的两大数据类型)\n    * [内置数据类型](#内置数据类型)\n    * [引用类型](#引用类型)\n    * [Java 常量](#java-常量)\n  * [自动拆箱和装箱（详解）](#自动拆箱和装箱（详解）)\n    * [简易实现](#简易实现)\n    * [自动装箱与拆箱中的“坑”](#自动装箱与拆箱中的坑)\n    * [了解基本类型缓存（常量池）的最佳实践](#了解基本类型缓存（常量池）的最佳实践)\n    * [总结：](#总结：)\n  * [基本数据类型的存储方式](#基本数据类型的存储方式)\n    * [存在栈中：](#存在栈中：)\n    * [存在堆里](#存在堆里)\n  * [参考文章](#参考文章)\n\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章首发于我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《夯实Java基础系列博文》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n该系列博文会告诉你如何从入门到进阶，一步步地学习Java基础知识，并上手进行实战，接着了解每个Java知识点背后的实现原理，更完整地了解整个Java技术体系，形成自己的知识框架。为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n\n<!-- more -->\n\n# Java 基本数据类型\n\n变量就是申请内存来存储值。也就是说，当创建变量的时候，需要在内存中申请空间。\n\n内存管理系统根据变量的类型为变量分配存储空间，分配的空间只能用来储存该类型数据。\n\n![](https://www.runoob.com/wp-content/uploads/2013/12/memorypic1.jpg)\n\n因此，通过定义不同类型的变量，可以在内存中储存整数、小数或者字符。\n\n## Java 的两大数据类型:\n\n*   内置数据类型\n*   引用数据类型\n\n* * *\n\n### 内置数据类型\n\nJava语言提供了八种基本类型。六种数字类型（四个整数型，两个浮点型），一种字符类型，还有一种布尔型。\n\n**byte：**\n\n*   byte 数据类型是8位、有符号的，以二进制补码表示的整数；\n*   最小值是 -128（-2^7）；\n*   最大值是 127（2^7-1）；\n*   默认值是 0；\n*   byte 类型用在大型数组中节约空间，主要代替整数，因为 byte 变量占用的空间只有 int 类型的四分之一；\n*   例子：byte a = 100，byte b = -50。\n\n**short：**\n\n*   short 数据类型是 16 位、有符号的以二进制补码表示的整数\n*   最小值是 -32768（-2^15）；\n*   最大值是 32767（2^15 - 1）；\n*   Short 数据类型也可以像 byte 那样节省空间。一个short变量是int型变量所占空间的二分之一；\n*   默认值是 0；\n*   例子：short s = 1000，short r = -20000。\n\n**int：**\n\n*   int 数据类型是32位、有符号的以二进制补码表示的整数；\n*   最小值是 -2,147,483,648（-2^31）；\n*   最大值是 2,147,483,647（2^31 - 1）；\n*   一般地整型变量默认为 int 类型；\n*   默认值是 0 ；\n*   例子：int a = 100000, int b = -200000。\n\n**long：**\n\n*   long 数据类型是 64 位、有符号的以二进制补码表示的整数；\n*   最小值是 -9,223,372,036,854,775,808（-2^63）；\n*   最大值是 9,223,372,036,854,775,807（2^63 -1）；\n*   这种类型主要使用在需要比较大整数的系统上；\n*   默认值是 0L；\n*   例子： long a = 100000L，Long b = -200000L。\n    \"L\"理论上不分大小写，但是若写成\"l\"容易与数字\"1\"混淆，不容易分辩。所以最好大写。\n\n**float：**\n\n*   float 数据类型是单精度、32位、符合IEEE 754标准的浮点数；\n*   float 在储存大型浮点数组的时候可节省内存空间；\n*   默认值是 0.0f；\n*   浮点数不能用来表示精确的值，如货币；\n*   例子：float f1 = 234.5f。\n\n**double：**\n\n*   double 数据类型是双精度、64 位、符合IEEE 754标准的浮点数；\n*   浮点数的默认类型为double类型；\n*   double类型同样不能表示精确的值，如货币；\n*   默认值是 0.0d；\n*   例子：double d1 = 123.4。\n\n**boolean：**\n\n*   boolean数据类型表示一位的信息；\n*   只有两个取值：true 和 false；\n*   这种类型只作为一种标志来记录 true/false 情况；\n*   默认值是 false；\n*   例子：boolean one = true。\n\n**char：**\n\n*   char类型是一个单一的 16 位 Unicode 字符；\n*   最小值是 \\u0000（即为0）；\n*   最大值是 \\uffff（即为65,535）；\n*   char 数据类型可以储存任何字符；\n*   例子：char letter = 'A';。\n\n\n```\n//8位\nbyte bx = Byte.MAX_VALUE;\nbyte bn = Byte.MIN_VALUE;\n//16位\nshort sx = Short.MAX_VALUE;\nshort sn = Short.MIN_VALUE;\n//32位\nint ix = Integer.MAX_VALUE;\nint in = Integer.MIN_VALUE;\n//64位\nlong lx = Long.MAX_VALUE;\nlong ln = Long.MIN_VALUE;\n//32位\nfloat fx = Float.MAX_VALUE;\nfloat fn = Float.MIN_VALUE;\n//64位\ndouble dx = Double.MAX_VALUE;\ndouble dn = Double.MIN_VALUE;\n//1位\nboolean bt = Boolean.TRUE;\nboolean bf = Boolean.FALSE;\n```\n\n```\n打印它们的结果可以得到\n\n`127`\n`-128`\n`32767`\n`-32768`\n`2147483647`\n`-2147483648`\n`9223372036854775807`\n`-9223372036854775808`\n`3.4028235E38`\n`1.4E-45`\n`1.7976931348623157E308`\n`4.9E-324`\n`true`\n`false`\n```\n\n### 引用类型\n\n- 在Java中，引用类型的变量非常类似于C/C++的指针。引用类型指向一个对象，指向对象的变量是引用变量。这些变量在声明时被指定为一个特定的类型，比如 Employee、Puppy 等。变量一旦声明后，类型就不能被改变了。\n- 对象、数组都是引用数据类型。\n- 所有引用类型的默认值都是null。\n- 一个引用变量可以用来引用任何与之兼容的类型。\n- 例子：Site site = new Site(\"Runoob\")。\n\n### Java 常量\n\n常量在程序运行时是不能被修改的。\n\n在 Java 中使用 final 关键字来修饰常量，声明方式和变量类似：\n\n```\nfinal double PI = 3.1415927;\n```\n\n虽然常量名也可以用小写，但为了便于识别，通常使用大写字母表示常量。\n\n字面量可以赋给任何内置类型的变量。例如：\n\n```\nbyte a = 68;\nchar a = 'A'\n```\n\n## 自动拆箱和装箱（详解）\n\nJava 5增加了自动装箱与自动拆箱机制，方便基本类型与包装类型的相互转换操作。在Java 5之前，如果要将一个int型的值转换成对应的包装器类型Integer，必须显式的使用new创建一个新的Integer对象，或者调用静态方法Integer.valueOf()。\n````\n//在Java 5之前，只能这样做\nInteger value = new Integer(10);\n//或者这样做\nInteger value = Integer.valueOf(10);\n//直接赋值是错误的\n//Integer value = 10;`\n````\n在Java 5中，可以直接将整型赋给Integer对象，由编译器来完成从int型到Integer类型的转换，这就叫自动装箱。\n````\n//在Java 5中，直接赋值是合法的，由编译器来完成转换\nInteger value = 10;\n与此对应的，自动拆箱就是可以将包装类型转换为基本类型，具体的转换工作由编译器来完成。\n//在Java 5 中可以直接这么做\nInteger value = new Integer(10);\nint i = value;\n````\n自动装箱与自动拆箱为程序员提供了很大的方便，而在实际的应用中，自动装箱与拆箱也是使用最广泛的特性之一。自动装箱和自动拆箱其实是Java编译器提供的一颗语法糖（语法糖是指在计算机语言中添加的某种语法，这种语法对语言的功能并没有影响，但是更方便程序员使用。通过可提高开发效率，增加代码可读性，增加代码的安全性）。\n\n###  简易实现\n\n在八种包装类型中，每一种包装类型都提供了两个方法：\n\n静态方法valueOf(基本类型)：将给定的基本类型转换成对应的包装类型；\n\n实例方法xxxValue()：将具体的包装类型对象转换成基本类型；\n下面我们以int和Integer为例，说明Java中自动装箱与自动拆箱的实现机制。看如下代码：\n\n````\nclass Auto //code1\n{\n\tpublic static void main(String[] args) \n\t{\n\t\t//自动装箱\n\t\tInteger inte = 10;\n\t\t//自动拆箱\n\t\tint i = inte;\n\n\t\t//再double和Double来验证一下\n\t\tDouble doub = 12.40;\n\t\tdouble d = doub;\n\t\t\n\t}\n}\n````\n\n上面的代码先将int型转为Integer对象，再讲Integer对象转换为int型，毫无疑问，这是可以正确运行的。可是，这种转换是怎么进行的呢？使用反编译工具，将生成的Class文件在反编译为Java文件，让我们看看发生了什么：\n````\nclass Auto//code2\n{\n  public static void main(String[] paramArrayOfString)\n  {\n    Integer localInteger = Integer.valueOf(10);\n    \n    int i = localInteger.intValue();\n    Double localDouble = Double.valueOf(12.4D);\n    double d = localDouble.doubleValue();\n  }\n}\n````\n我们可以看到经过javac编译之后，code1的代码被转换成了code2，实际运行时，虚拟机运行的就是code2的代码。也就是说，虚拟机根本不知道有自动拆箱和自动装箱这回事；在将Java源文件编译为class文件的过程中，javac编译器在自动装箱的时候，调用了Integer.valueOf()方法，在自动拆箱时，又调用了intValue()方法。我们可以看到，double和Double也是如此。\n实现总结：其实自动装箱和自动封箱是编译器为我们提供的一颗语法糖。在自动装箱时，编译器调用包装类型的valueOf()方法；在自动拆箱时，编译器调用了相应的xxxValue()方法。\n\n###  自动装箱与拆箱中的“坑”\n\n在使用自动装箱与自动拆箱时，要注意一些陷阱，为了避免这些陷阱，我们有必要去看一下各种包装类型的源码。\n\nInteger源码\n\n````\npublic final class Integer extends Number implements Comparable<Integer> {\n    private final int value;\n    \n    /*Integer的构造方法，接受一个整型参数,Integer对象表示的int值，保存在value中*/\n     public Integer(int value) {\n            this.value = value;\n     }\n     \n    /*equals()方法判断的是:所代表的int型的值是否相等*/\n     public boolean equals(Object obj) {\n            if (obj instanceof Integer) {\n                return value == ((Integer)obj).intValue();\n            }\n            return false;\n    }\n     \n    /*返回这个Integer对象代表的int值，也就是保存在value中的值*/\n     public int intValue() {\n            return value;\n     }\n     \n     /**\n      * 首先会判断i是否在[IntegerCache.low,Integer.high]之间\n      * 如果是，直接返回Integer.cache中相应的元素\n      * 否则，调用构造方法，创建一个新的Integer对象\n      */\n     public static Integer valueOf(int i) {\n        assert IntegerCache.high >= 127;\n        if (i >= IntegerCache.low && i <= IntegerCache.high)\n            return IntegerCache.cache[i + (-IntegerCache.low)];\n        return new Integer(i);\n     }\n    \n    /**\n      * 静态内部类，缓存了从[low,high]对应的Integer对象\n      * low -128这个值不会被改变\n      * high 默认是127，可以改变，最大不超过：Integer.MAX_VALUE - (-low) -1\n      * cache 保存从[low,high]对象的Integer对象\n     */\n     private static class IntegerCache {\n        static final int low = -128;\n        static final int high;\n        static final Integer cache[];\n     \n        static {\n            // high value may be configured by property\n            int h = 127;\n            String integerCacheHighPropValue =\n                sun.misc.VM.getSavedProperty(\"java.lang.Integer.IntegerCache.high\");\n            if (integerCacheHighPropValue != null) {\n                int i = parseInt(integerCacheHighPropValue);\n                i = Math.max(i, 127);\n                // Maximum array size is Integer.MAX_VALUE\n                h = Math.min(i, Integer.MAX_VALUE - (-low) -1);\n            }\n            high = h;\n     \n            cache = new Integer[(high - low) + 1];\n            int j = low;\n            for(int k = 0; k < cache.length; k++)\n                cache[k] = new Integer(j++);\n        }\n     \n        private IntegerCache() {}\n    }\n}\n````\n\n    以上是Oracle(Sun)公司JDK 1.7中Integer源码的一部分，通过分析上面的代码，得到：\n    1）Integer有一个实例域value，它保存了这个Integer所代表的int型的值，且它是final的，也就是说这个Integer对象一经构造完成，它所代表的值就不能再被改变。\n    2）Integer重写了equals()方法，它通过比较两个Integer对象的value，来判断是否相等。\n    3）重点是静态内部类IntegerCache，通过类名就可以发现：它是用来缓存数据的。它有一个数组，里面保存的是连续的Integer对象。\n       (a) low：代表缓存数据中最小的值，固定是-128。\n       (b) high：代表缓存数据中最大的值，它可以被该改变，默认是127。high最小是127，最大是Integer.MAX_VALUE-(-low)-1，如果high超过了这个值，那么cache[ ]的长度就超过Integer.MAX_VALUE了，也就溢出了。\n       (c) cache[]：里面保存着从[low,high]所对应的Integer对象，长度是high-low+1(因为有元素0，所以要加1)。\n    4）调用valueOf(int i)方法时，首先判断i是否在[low,high]之间，如果是，则复用Integer.cache[i-low]。比如，如果Integer.valueOf(3)，直接返回Integer.cache[131]；如果i不在这个范围，则调用构造方法，构造出一个新的Integer对象。\n    5）调用intValue()，直接返回value的值。\n    通过3）和4）可以发现，默认情况下，在使用自动装箱时，VM会复用[-128,127]之间的Integer对象。\n    \n    Integer  a1 = 1;\n    Integer  a2 = 1;\n    Integer  a3 = new Integer(1);\n    //会打印true，因为a1和a2是同一个对象,都是Integer.cache[129]\n    System.out.println(a1 == a2);\n    //false，a3构造了一个新的对象，不同于a1,a2\n    System.out.println(a1 == a3);\n\n### 了解基本类型缓存（常量池）的最佳实践\n\n````\n//基本数据类型的常量池是-128到127之间。\n// 在这个范围中的基本数据类的包装类可以自动拆箱，比较时直接比较数值大小。\npublic static void main(String[] args) {\n\n//int的自动拆箱和装箱只在-128到127范围中进行，超过该范围的两个integer的 == 判断是会返回false的。\nInteger a1 = 128;\nInteger a2 = -128;\nInteger a3 = -128;\nInteger a4 = 128;\nSystem.out.println(a1 == a4);\nSystem.out.println(a2 == a3);\n\nByte b1 = 127;\nByte b2 = 127;\nByte b3 = -128;\nByte b4 = -128;\n//byte都是相等的，因为范围就在-128到127之间\nSystem.out.println(b1 == b2);\nSystem.out.println(b3 == b4);\n\nLong c1 = 128L;\nLong c2 = 128L;\nLong c3 = -128L;\nLong c4 = -128L;\nSystem.out.println(c1 == c2);\nSystem.out.println(c3 == c4);\n\n//char没有负值\n//发现char也是在0到127之间自动拆箱\nCharacter d1 = 128;\nCharacter d2 = 128;\nCharacter d3 = 127;\nCharacter d4 = 127;\nSystem.out.println(d1 == d2);\nSystem.out.println(d3 == d4);\n\n`结果`\n\n`false`\n`true`\n`true`\n`true`\n`false`\n`true`\n`false`\n`true`\n\n\nInteger i = 10;\nByte b = 10;\n//比较Byte和Integer.两个对象无法直接比较，报错\n//System.out.println(i == b);\nSystem.out.println(\"i == b \" + i.equals(b));\n//答案是false,因为包装类的比较时先比较是否是同一个类，不是的话直接返回false.\n\nint ii = 128;\nshort ss = 128;\nlong ll = 128;\nchar cc = 128;\nSystem.out.println(\"ii == bb \" + (ii == ss));\nSystem.out.println(\"ii == ll \" + (ii == ll));\nSystem.out.println(\"ii == cc \" + (ii == cc));\n\n结果\ni == b false\nii == bb true\nii == ll true\nii == cc true\n\n//这时候都是true，因为基本数据类型直接比较值，值一样就可以。\n````\n\n### 总结：\n\n通过上面的代码，我们分析一下自动装箱与拆箱发生的时机：\n\n（1）当需要一个对象的时候会自动装箱，比如Integer a = 10;equals(Object o)方法的参数是Object对象，所以需要装箱。\n\n（2）当需要一个基本类型时会自动拆箱，比如int a = new Integer(10);算术运算是在基本类型间进行的，所以当遇到算术运算时会自动拆箱，比如代码中的 c == (a + b);\n\n（3）包装类型 == 基本类型时，包装类型自动拆箱；\n\n需要注意的是：“==”在没遇到算术运算时，不会自动拆箱；基本类型只会自动装箱为对应的包装类型，代码中最后一条说明的内容。\n\n在JDK 1.5中提供了自动装箱与自动拆箱，这其实是Java 编译器的语法糖，编译器通过调用包装类型的valueOf()方法实现自动装箱，调用xxxValue()方法自动拆箱。自动装箱和拆箱会有一些陷阱，那就是包装类型复用了某些对象。\n\n（1）Integer默认复用了[-128,127]这些对象，其中高位置可以修改；\n\n（2）Byte复用了全部256个对象[-128,127]；\n\n（3）Short服用了[-128,127]这些对象；\n\n（4）Long服用了[-128,127];\n\n（5）Character复用了[0,127],Charater不能表示负数;\n\nDouble和Float是连续不可数的，所以没法复用对象，也就不存在自动装箱复用陷阱。\n\nBoolean没有自动装箱与拆箱，它也复用了Boolean.TRUE和Boolean.FALSE，通过Boolean.valueOf(boolean b)返回的Blooean对象要么是TRUE，要么是FALSE，这点也要注意。\n\n本文介绍了“真实的”自动装箱与拆箱，为了避免写出错误的代码，又从包装类型的源码入手，指出了各种包装类型在自动装箱和拆箱时存在的陷阱，同时指出了自动装箱与拆箱发生的时机。\n\n\n## 基本数据类型的存储方式\n上面自动拆箱和装箱的原理其实与常量池有关。\n\n### 存在栈中：\n\n    public void(int a)\n    {\n    int i = 1;\n    int j = 1;\n    }\n\n方法中的i 存在虚拟机栈的局部变量表里，i是一个引用，j也是一个引用，它们都指向局部变量表里的整型值 1.\nint a是传值引用，所以a也会存在局部变量表。\n\n###  存在堆里\nclass A{\nint i = 1;\nA a = new A();\n}\ni是类的成员变量。类实例化的对象存在堆中，所以成员变量也存在堆中，引用a存的是对象的地址，引用i存的是值，这个值1也会存在堆中。可以理解为引用i指向了这个值1。也可以理解为i就是1.\n\n3 包装类对象怎么存\n其实我们说的常量池也可以叫对象池。\n\n比如String a= new String(\"a\").intern()时会先在常量池找是否有“a\"对象如果有的话直接返回“a\"对象在常量池的地址，即让引用a指向常量”a\"对象的内存地址。\nInteger也是同理。\n\n下图是Integer类型在常量池中查找同值对象的方法。\n\n````\npublic static Integer valueOf(int i) {\n    if (i >= IntegerCache.low && i <= IntegerCache.high)\n        return IntegerCache.cache[i + (-IntegerCache.low)];\n    return new Integer(i);\n}\nprivate static class IntegerCache {\n    static final int low = -128;\n    static final int high;\n    static final Integer cache[];\n\n    static {\n        // high value may be configured by property\n        int h = 127;\n        String integerCacheHighPropValue =\n            sun.misc.VM.getSavedProperty(\"java.lang.Integer.IntegerCache.high\");\n        if (integerCacheHighPropValue != null) {\n            try {\n                int i = parseInt(integerCacheHighPropValue);\n                i = Math.max(i, 127);\n                // Maximum array size is Integer.MAX_VALUE\n                h = Math.min(i, Integer.MAX_VALUE - (-low) -1);\n            } catch( NumberFormatException nfe) {\n                // If the property cannot be parsed into an int, ignore it.\n            }\n        }\n        high = h;\n\n        cache = new Integer[(high - low) + 1];\n        int j = low;\n        for(int k = 0; k < cache.length; k++)\n            cache[k] = new Integer(j++);\n\n        // range [-128, 127] must be interned (JLS7 5.1.7)\n        assert IntegerCache.high >= 127;\n    }\n\n    private IntegerCache() {}\n}\n````\n\n所以基本数据类型的包装类型可以在常量池查找对应值的对象，找不到就会自动在常量池创建该值的对象。\n\n而String类型可以通过intern来完成这个操作。\n\nJDK1.7后，常量池被放入到堆空间中，这导致intern()函数的功能不同，具体怎么个不同法，且看看下面代码，这个例子是网上流传较广的一个例子，分析图也是直接粘贴过来的，这里我会用自己的理解去解释这个例子：\n\n\n```\nString s = new String(\"1\");  \ns.intern();  \nString s2 = \"1\";  \nSystem.out.println(s == s2);  \n  \nString s3 = new String(\"1\") + new String(\"1\");  \ns3.intern();  \nString s4 = \"11\";  \nSystem.out.println(s3 == s4);  \n输出结果为：\n\n[java] view plain copy\nJDK1.6以及以下：false false  \nJDK1.7以及以上：false true\n```\n\nJDK1.6查找到常量池存在相同值的对象时会直接返回该对象的地址。\n\nJDK 1.7后，intern方法还是会先去查询常量池中是否有已经存在，如果存在，则返回常量池中的引用，这一点与之前没有区别，区别在于，如果在常量池找不到对应的字符串，则不会再将字符串拷贝到常量池，而只是在常量池中生成一个对原字符串的引用。\n\n那么其他字符串在常量池找值时就会返回另一个堆中对象的地址。\n\n下一节详细介绍String以及相关包装类。\n\n具体请见：https://blog.csdn.net/a724888/article/details/80042298\n\n关于Java面向对象三大特性，请参考：\n\nhttps://blog.csdn.net/a724888/article/details/80033043\n\n## 参考文章\n\n<https://www.runoob.com/java/java-basic-datatypes.html>\n\n<https://www.cnblogs.com/zch1126/p/5335139.html>\n\n<https://blog.csdn.net/jreffchen/article/details/81015884>\n\n<https://blog.csdn.net/yuhongye111/article/details/31850779>\n\n\n\n\n"
  },
  {
    "path": "docs/Java/basic/Java集合框架梳理.md",
    "content": "# 目录\n  * [集合类大图](#集合类大图)\n  * [Collection接口](#collection接口)\n  * [List接口](#list接口)\n  * [Set接口](#set接口)\n  * [Map接口](#map接口)\n  * [Queue](#queue)\n  * [关于Java集合的小抄](#关于java集合的小抄)\n    * [List](#list)\n      * [ArrayList](#arraylist)\n      * [LinkedList](#linkedlist)\n      * [CopyOnWriteArrayList](#copyonwritearraylist)\n      * [遗憾](#遗憾)\n    * [Map](#map)\n      * [HashMap](#hashmap)\n      * [LinkedHashMap](#linkedhashmap)\n      * [TreeMap](#treemap)\n      * [EnumMap](#enummap)\n      * [ConcurrentHashMap](#concurrenthashmap)\n      * [ConcurrentSkipListMap](#concurrentskiplistmap)\n    * [Set](#set)\n    * [Queue](#queue-1)\n      * [普通队列](#普通队列)\n      * [PriorityQueue](#priorityqueue)\n      * [线程安全的队列](#线程安全的队列)\n      * [线程安全的阻塞队列](#线程安全的阻塞队列)\n      * [同步队列](#同步队列)\n  * [参考文章](#参考文章)\n\n---\ntitle: 夯实Java基础系列19：一文搞懂Java集合类框架，以及常见面试题\ndate: 2019-9-19  15:56:26 # 文章生成时间，一般不改\ncategories:\n    - Java技术江湖\n    - Java基础\ntags:\n    - Java集合类\n---\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章首发于我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《夯实Java基础系列博文》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n该系列博文会告诉你如何从入门到进阶，一步步地学习Java基础知识，并上手进行实战，接着了解每个Java知识点背后的实现原理，更完整地了解整个Java技术体系，形成自己的知识框架。为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n\n<!-- more -->\n\n本文参考 https://www.cnblogs.com/chenssy/p/3495238.html\n\n## 集合类大图\n\n在编写java程序中，我们最常用的除了八种基本数据类型，String对象外还有一个集合类，在我们的的程序中到处充斥着集合类的身影！\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230403214433.png)\njava中集合大家族的成员实在是太丰富了，有常用的ArrayList、HashMap、HashSet，也有不常用的Stack、Queue，有线程安全的Vector、HashTable，也有线程不安全的LinkedList、TreeMap等等！\n\n![](https://images0.cnblogs.com/blog/381060/201312/28124707-3a873160808e457686d67c118af6fa70.png)\n\n上面的图展示了整个集合大家族的成员以及他们之间的关系。下面就上面的各个接口、基类做一些简单的介绍(主要介绍各个集合的特点。区别)。\n\n下面几张图更清晰地介绍了结合类接口间的关系：\n\n> Collections和Collection。\n> Arrays和Collections。\n>\n> ![](https://www.programcreek.com/wp-content/uploads/2009/02/CollectionVsCollections.jpeg)\n\n> Collection的子接口\n\n![](https://www.programcreek.com/wp-content/uploads/2009/02/java-collection-hierarchy.jpeg)\n> map的实现类\n\n![](https://www.programcreek.com/wp-content/uploads/2009/02/MapClassHierarchy-600x354.jpg)\n\n## Collection接口\n\n  Collection接口是最基本的集合接口，它不提供直接的实现，Java SDK提供的类都是继承自Collection的“子接口”如List和Set。Collection所代表的是一种规则，它所包含的元素都必须遵循一条或者多条规则。如有些允许重复而有些则不能重复、有些必须要按照顺序插入而有些则是散列，有些支持排序但是有些则不支持。\n\n  在Java中所有实现了Collection接口的类都必须提供两套标准的构造函数，一个是无参，用于创建一个空的Collection，一个是带有Collection参数的有参构造函数，用于创建一个新的Collection，这个新的Collection与传入进来的Collection具备相同的元素。\n//要求实现基本的增删改查方法，并且需要能够转换为数组类型\n\n````\npublic class Collection接口 {\n    class collect implements Collection {\n\n        @Override\n        public int size() {\n            return 0;\n        }\n\n        @Override\n        public boolean isEmpty() {\n            return false;\n        }\n\n        @Override\n        public boolean contains(Object o) {\n            return false;\n        }\n\n        @Override\n        public Iterator iterator() {\n            return null;\n        }\n\n        @Override\n        public Object[] toArray() {\n            return new Object[0];\n        }\n\n        @Override\n        public boolean add(Object o) {\n            return false;\n        }\n\n        @Override\n        public boolean remove(Object o) {\n            return false;\n        }\n\n        @Override\n        public boolean addAll(Collection c) {\n            return false;\n        }\n\n        @Override\n        public void clear() {\n\n        }\n//省略部分代码  \n\n        @Override\n        public Object[] toArray(Object[] a) {\n            return new Object[0];\n        }\n    }\n}\n````\n## List接口\n\n>   List接口为Collection直接接口。List所代表的是有序的Collection，即它用某种特定的插入顺序来维护元素顺序。用户可以对列表中每个元素的插入位置进行精确地控制，同时可以根据元素的整数索引（在列表中的位置）访问元素，并搜索列表中的元素。实现List接口的集合主要有：ArrayList、LinkedList、Vector、Stack。\n\n2.1、ArrayList\n\n>   ArrayList是一个动态数组，也是我们最常用的集合。它允许任何符合规则的元素插入甚至包括null。每一个ArrayList都有一个初始容量（10），该容量代表了数组的大小。随着容器中的元素不断增加，容器的大小也会随着增加。在每次向容器中增加元素的同时都会进行容量检查，当快溢出时，就会进行扩容操作。所以如果我们明确所插入元素的多少，最好指定一个初始容量值，避免过多的进行扩容操作而浪费时间、效率。\n>\n>   size、isEmpty、get、set、iterator 和 listIterator 操作都以固定时间运行。add 操作以分摊的固定时间运行，也就是说，添加 n 个元素需要 O(n) 时间（由于要考虑到扩容，所以这不只是添加元素会带来分摊固定时间开销那样简单）。\n>\n>   ArrayList擅长于随机访问。同时ArrayList是非同步的。\n>\n>   2.2、LinkedList\n\n>   同样实现List接口的LinkedList与ArrayList不同，ArrayList是一个动态数组，而LinkedList是一个双向链表。所以它除了有ArrayList的基本操作方法外还额外提供了get，remove，insert方法在LinkedList的首部或尾部。\n>\n>   由于实现的方式不同，LinkedList不能随机访问，它所有的操作都是要按照双重链表的需要执行。在列表中索引的操作将从开头或结尾遍历列表（从靠近指定索引的一端）。这样做的好处就是可以通过较低的代价在List中进行插入和删除操作。\n>\n>   与ArrayList一样，LinkedList也是非同步的。如果多个线程同时访问一个List，则必须自己实现访问同步。一种解决方法是在创建List时构造一个同步的List： \n>   List list = Collections.synchronizedList(new LinkedList(...));\n\n> 2.3、Vector\n>    与ArrayList相似，但是Vector是同步的。所以说Vector是线程安全的动态数组。它的操作与ArrayList几乎一样。\n>\n> 2.4、Stack\n>    Stack继承自Vector，实现一个后进先出的堆栈。Stack提供5个额外的方法使得Vector得以被当作堆栈使用。基本的push和pop 方法，还有peek方法得到栈顶的元素，empty方法测试堆栈是否为空，search方法检测一个元素在堆栈中的位置。Stack刚创建后是空栈。。\n````\npublic class List接口 {\n    //下面是List的继承关系，由于List接口规定了包括诸如索引查询，迭代器的实现，所以实现List接口的类都会有这些方法。\n    //所以不管是ArrayList和LinkedList底层都可以使用数组操作，但一般不提供这样外部调用方法。\n    //    public interface Iterable<T>\n//    public interface Collection<E> extends Iterable<E>\n//    public interface List<E> extends Collection<E>\n    class MyList implements List {\n\n        @Override\n        public int size() {\n            return 0;\n        }\n\n        @Override\n        public boolean isEmpty() {\n            return false;\n        }\n\n        @Override\n        public boolean contains(Object o) {\n            return false;\n        }\n\n        @Override\n        public Iterator iterator() {\n            return null;\n        }\n\n        @Override\n        public Object[] toArray() {\n            return new Object[0];\n        }\n\n        @Override\n        public boolean add(Object o) {\n            return false;\n        }\n\n        @Override\n        public boolean remove(Object o) {\n            return false;\n        }\n\n        @Override\n        public void clear() {\n\n        }\n\n       //省略部分代码\n       \n        @Override\n        public Object get(int index) {\n            return null;\n        }\n\n        @Override\n        public ListIterator listIterator() {\n            return null;\n        }\n\n        @Override\n        public ListIterator listIterator(int index) {\n            return null;\n        }\n\n        @Override\n        public List subList(int fromIndex, int toIndex) {\n            return null;\n        }\n\n        @Override\n        public Object[] toArray(Object[] a) {\n            return new Object[0];\n        }\n    }\n}\n````\n## Set接口\n\n> Set是一种不包括重复元素的Collection。它维持它自己的内部排序，所以随机访问没有任何意义。与List一样，它同样运行null的存在但是仅有一个。由于Set接口的特殊性，所有传入Set集合中的元素都必须不同，同时要注意任何可变对象，如果在对集合中元素进行操作时，导致e1.equals(e2)==true，则必定会产生某些问题。实现了Set接口的集合有：EnumSet、HashSet、TreeSet。\n>\n> 3.1、EnumSet\n>    是枚举的专用Set。所有的元素都是枚举类型。\n>\n> 3.2、HashSet\n>    HashSet堪称查询速度最快的集合，因为其内部是以HashCode来实现的。它内部元素的顺序是由哈希码来决定的，所以它不保证set 的迭代顺序；特别是它不保证该顺序恒久不变。\n\n````\npublic class Set接口 {\n    // Set接口规定将set看成一个集合，并且使用和数组类似的增删改查方式，同时提供iterator迭代器\n    //    public interface Set<E> extends Collection<E>\n    //    public interface Collection<E> extends Iterable<E>\n    //    public interface Iterable<T>\n    class MySet implements Set {\n\n        @Override\n        public int size() {\n            return 0;\n        }\n\n        @Override\n        public boolean isEmpty() {\n            return false;\n        }\n\n        @Override\n        public boolean contains(Object o) {\n            return false;\n        }\n\n        @Override\n        public Iterator iterator() {\n            return null;\n        }\n\n        @Override\n        public Object[] toArray() {\n            return new Object[0];\n        }\n\n        @Override\n        public boolean add(Object o) {\n            return false;\n        }\n\n        @Override\n        public boolean remove(Object o) {\n            return false;\n        }\n\n        @Override\n        public boolean addAll(Collection c) {\n            return false;\n        }\n\n        @Override\n        public void clear() {\n\n        }\n\n        @Override\n        public boolean removeAll(Collection c) {\n            return false;\n        }\n\n        @Override\n        public boolean retainAll(Collection c) {\n            return false;\n        }\n\n        @Override\n        public boolean containsAll(Collection c) {\n            return false;\n        }\n\n        @Override\n        public Object[] toArray(Object[] a) {\n            return new Object[0];\n        }\n    }\n}\n````\n## Map接口\n\n> Map与List、Set接口不同，它是由一系列键值对组成的集合，提供了key到Value的映射。同时它也没有继承Collection。在Map中它保证了key与value之间的一一对应关系。也就是说一个key对应一个value，所以它不能存在相同的key值，当然value值可以相同。实现map的有：HashMap、TreeMap、HashTable、Properties、EnumMap。\n\n> 4.1、HashMap\n>    以哈希表数据结构实现，查找对象时通过哈希函数计算其位置，它是为快速查询而设计的，其内部定义了一个hash表数组（Entry[] table），元素会通过哈希转换函数将元素的哈希地址转换成数组中存放的索引，如果有冲突，则使用散列链表的形式将所有相同哈希地址的元素串起来，可能通过查看HashMap.Entry的源码它是一个单链表结构。\n>\n> 4.2、TreeMap\n>    键以某种排序规则排序，内部以red-black（红-黑）树数据结构实现，实现了SortedMap接口\n>\n> 4.3、HashTable\n>    也是以哈希表数据结构实现的，解决冲突时与HashMap也一样也是采用了散列链表的形式，不过性能比HashMap要低\n````\npublic class Map接口 {\n    //Map接口是最上层接口，Map接口实现类必须实现put和get等哈希操作。\n    //并且要提供keyset和values，以及entryset等查询结构。\n    //public interface Map<K,V>\n    class MyMap implements Map {\n\n        @Override\n        public int size() {\n            return 0;\n        }\n\n        @Override\n        public boolean isEmpty() {\n            return false;\n        }\n\n        @Override\n        public boolean containsKey(Object key) {\n            return false;\n        }\n\n        @Override\n        public boolean containsValue(Object value) {\n            return false;\n        }\n\n        @Override\n        public Object get(Object key) {\n            return null;\n        }\n\n        @Override\n        public Object put(Object key, Object value) {\n            return null;\n        }\n\n        @Override\n        public Object remove(Object key) {\n            return null;\n        }\n\n        @Override\n        public void putAll(Map m) {\n\n        }\n\n        @Override\n        public void clear() {\n\n        }\n\n        @Override\n        public Set keySet() {\n            return null;\n        }\n\n        @Override\n        public Collection values() {\n            return null;\n        }\n\n        @Override\n        public Set<Entry> entrySet() {\n            return null;\n        }\n    }\n}\n````\n## Queue\n\n>   队列，它主要分为两大类，一类是阻塞式队列，队列满了以后再插入元素则会抛出异常，主要包括ArrayBlockQueue、PriorityBlockingQueue、LinkedBlockingQueue。另一种队列则是双端队列，支持在头、尾两端插入和移除元素，主要包括：ArrayDeque、LinkedBlockingDeque、LinkedList。\n````\npublic class Queue接口 {\n    //queue接口是对队列的一个实现，需要提供队列的进队出队等方法。一般使用linkedlist作为实现类\n    class MyQueue implements Queue {\n\n        @Override\n        public int size() {\n            return 0;\n        }\n\n        @Override\n        public boolean isEmpty() {\n            return false;\n        }\n\n        @Override\n        public boolean contains(Object o) {\n            return false;\n        }\n\n        @Override\n        public Iterator iterator() {\n            return null;\n        }\n\n        @Override\n        public Object[] toArray() {\n            return new Object[0];\n        }\n\n        @Override\n        public Object[] toArray(Object[] a) {\n            return new Object[0];\n        }\n\n        @Override\n        public boolean add(Object o) {\n            return false;\n        }\n\n        @Override\n        public boolean remove(Object o) {\n            return false;\n        }\n\n        //省略部分代码\n        @Override\n        public boolean offer(Object o) {\n            return false;\n        }\n\n        @Override\n        public Object remove() {\n            return null;\n        }\n\n        @Override\n        public Object poll() {\n            return null;\n        }\n\n        @Override\n        public Object element() {\n            return null;\n        }\n\n        @Override\n        public Object peek() {\n            return null;\n        }\n    }\n}\n\n````\n## 关于Java集合的小抄\n\n这部分内容转自我偶像 江南白衣 的博客：http://calvin1978.blogcn.com/articles/collection.html\n在尽可能短的篇幅里，将所有集合与并发集合的特征、实现方式、性能捋一遍。适合所有\"精通Java\"，其实还不那么自信的人阅读。\n\n期望能不止用于面试时，平时选择数据结构，也能考虑一下其成本与效率，不要看着API合适就用了。\n\n### List\n\n#### ArrayList\n以数组实现。节约空间，但数组有容量限制。超出限制时会增加50%容量，用System.arraycopy（）复制到新的数组。因此最好能给出数组大小的预估值。默认第一次插入元素时创建大小为10的数组。\n\n按数组下标访问元素－get（i）、set（i,e） 的性能很高，这是数组的基本优势。\n\n如果按下标插入元素、删除元素－add（i,e）、 remove（i）、remove（e），则要用System.arraycopy（）来复制移动部分受影响的元素，性能就变差了。\n\n越是前面的元素，修改时要移动的元素越多。直接在数组末尾加入元素－常用的add（e），删除最后一个元素则无影响。\n\n \n\n#### LinkedList\n以双向链表实现。链表无容量限制，但双向链表本身使用了更多空间，每插入一个元素都要构造一个额外的Node对象，也需要额外的链表指针操作。\n\n \n\n按下标访问元素－get（i）、set（i,e） 要悲剧的部分遍历链表将指针移动到位 （如果i>数组大小的一半，会从末尾移起）。\n\n插入、删除元素时修改前后节点的指针即可，不再需要复制移动。但还是要部分遍历链表的指针才能移动到下标所指的位置。\n\n只有在链表两头的操作－add（）、addFirst（）、removeLast（）或用iterator（）上的remove（）倒能省掉指针的移动。\n\nApache Commons 有个TreeNodeList，里面是棵二叉树，可以快速移动指针到位。\n\n \n\n#### CopyOnWriteArrayList\n并发优化的ArrayList。基于不可变对象策略，在修改时先复制出一个数组快照来修改，改好了，再让内部指针指向新数组。\n\n因为对快照的修改对读操作来说不可见，所以读读之间不互斥，读写之间也不互斥，只有写写之间要加锁互斥。但复制快照的成本昂贵，典型的适合读多写少的场景。\n\n虽然增加了addIfAbsent（e）方法，会遍历数组来检查元素是否已存在，性能可想像的不会太好。\n\n \n\n#### 遗憾\n无论哪种实现，按值返回下标contains（e）, indexOf（e）, remove（e） 都需遍历所有元素进行比较，性能可想像的不会太好。\n\n没有按元素值排序的SortedList。\n\n除了CopyOnWriteArrayList，再没有其他线程安全又并发优化的实现如ConcurrentLinkedList。凑合着用Set与Queue中的等价类时，会缺少一些List特有的方法如get（i）。如果更新频率较高，或数组较大时，还是得用Collections.synchronizedList（list），对所有操作用同一把锁来保证线程安全。\n\n### Map\n#### HashMap\n\n\n以Entry[]数组实现的哈希桶数组，用Key的哈希值取模桶数组的大小可得到数组下标。\n\n插入元素时，如果两条Key落在同一个桶（比如哈希值1和17取模16后都属于第一个哈希桶），我们称之为哈希冲突。\n\nJDK的做法是链表法，Entry用一个next属性实现多个Entry以单向链表存放。查找哈希值为17的key时，先定位到哈希桶，然后链表遍历桶里所有元素，逐个比较其Hash值然后key值。\n\n在JDK8里，新增默认为8的阈值，当一个桶里的Entry超过閥值，就不以单向链表而以红黑树来存放以加快Key的查找速度。\n\n当然，最好还是桶里只有一个元素，不用去比较。所以默认当Entry数量达到桶数量的75%时，哈希冲突已比较严重，就会成倍扩容桶数组，并重新分配所有原来的Entry。扩容成本不低，所以也最好有个预估值。\n\n取模用与操作（hash & （arrayLength-1））会比较快，所以数组的大小永远是2的N次方， 你随便给一个初始值比如17会转为32。默认第一次放入元素时的初始值是16。\n\niterator（）时顺着哈希桶数组来遍历，看起来是个乱序。\n\n \n\n#### LinkedHashMap\n扩展HashMap，每个Entry增加双向链表，号称是最占内存的数据结构。\n\n支持iterator（）时按Entry的插入顺序来排序（如果设置accessOrder属性为true，则所有读写访问都排序）。\n\n插入时，Entry把自己加到Header Entry的前面去。如果所有读写访问都要排序，还要把前后Entry的before/after拼接起来以在链表中删除掉自己，所以此时读操作也是线程不安全的了。\n\n \n\n#### TreeMap\n以红黑树实现，红黑树又叫自平衡二叉树：\n\n对于任一节点而言，其到叶节点的每一条路径都包含相同数目的黑结点。\n上面的规定，使得树的层数不会差的太远，使得所有操作的复杂度不超过 O（lgn），但也使得插入，修改时要复杂的左旋右旋来保持树的平衡。\n\n支持iterator（）时按Key值排序，可按实现了Comparable接口的Key的升序排序，或由传入的Comparator控制。可想象的，在树上插入/删除元素的代价一定比HashMap的大。\n\n支持SortedMap接口，如firstKey（），lastKey（）取得最大最小的key，或sub（fromKey, toKey）, tailMap（fromKey）剪取Map的某一段。\n\n \n\n#### EnumMap\nEnumMap的原理是，在构造函数里要传入枚举类，那它就构建一个与枚举的所有值等大的数组，按Enum. ordinal（）下标来访问数组。性能与内存占用俱佳。\n\n美中不足的是，因为要实现Map接口，而 V get（Object key）中key是Object而不是泛型K，所以安全起见，EnumMap每次访问都要先对Key进行类型判断，在JMC里录得不低的采样命中频率。\n\n \n\n#### ConcurrentHashMap\n并发优化的HashMap。\n\n在JDK5里的经典设计，默认16把写锁（可以设置更多），有效分散了阻塞的概率。数据结构为Segment[]，每个Segment一把锁。Segment里面才是哈希桶数组。Key先算出它在哪个Segment里，再去算它在哪个哈希桶里。\n\n也没有读锁，因为put/remove动作是个原子动作（比如put的整个过程是一个对数组元素/Entry 指针的赋值操作），读操作不会看到一个更新动作的中间状态。\n\n但在JDK8里，Segment[]的设计被抛弃了，改为精心设计的，只在需要锁的时候加锁。\n\n支持ConcurrentMap接口，如putIfAbsent（key，value）与相反的replace（key，value）与以及实现CAS的replace（key, oldValue, newValue）。\n\n \n\n####  ConcurrentSkipListMap\nJDK6新增的并发优化的SortedMap，以SkipList结构实现。Concurrent包选用它是因为它支持基于CAS的无锁算法，而红黑树则没有好的无锁算法。\n\n原理上，可以想象为多个链表组成的N层楼，其中的元素从稀疏到密集，每个元素有往右与往下的指针。从第一层楼开始遍历，如果右端的值比期望的大，那就往下走一层，继续往前走。\n\n \n\n\n\n典型的空间换时间。每次插入，都要决定在哪几层插入，同时，要决定要不要多盖一层楼。\n\n它的size（）同样不能随便调，会遍历来统计。\n\n \n\n### Set\n\n\n所有Set几乎都是内部用一个Map来实现, 因为Map里的KeySet就是一个Set，而value是假值，全部使用同一个Object即可。\n\nSet的特征也继承了那些内部的Map实现的特征。\n\nHashSet：内部是HashMap。\n\nLinkedHashSet：内部是LinkedHashMap。\n\nTreeSet：内部是TreeMap的SortedSet。\n\nConcurrentSkipListSet：内部是ConcurrentSkipListMap的并发优化的SortedSet。\n\nCopyOnWriteArraySet：内部是CopyOnWriteArrayList的并发优化的Set，利用其addIfAbsent（）方法实现元素去重，如前所述该方法的性能很一般。\n\n好像少了个ConcurrentHashSet，本来也该有一个内部用ConcurrentHashMap的简单实现，但JDK偏偏没提供。Jetty就自己简单封了一个，Guava则直接用java.util.Collections.newSetFromMap（new ConcurrentHashMap（）） 实现。\n\n \n\n\n### Queue\nQueue是在两端出入的List，所以也可以用数组或链表来实现。\n\n#### 普通队列\n\nLinkedList\n是的，以双向链表实现的LinkedList既是List，也是Queue。\n\nArrayDeque\n以循环数组实现的双向Queue。大小是2的倍数，默认是16。\n\n为了支持FIFO，即从数组尾压入元素（快），从数组头取出元素（超慢），就不能再使用普通ArrayList的实现了，改为使用循环数组。\n\n有队头队尾两个下标：弹出元素时，队头下标递增；加入元素时，队尾下标递增。如果加入元素时已到数组空间的末尾，则将元素赋值到数组[0]，同时队尾下标指向0，再插入下一个元素则赋值到数组[1]，队尾下标指向1。如果队尾的下标追上队头，说明数组所有空间已用完，进行双倍的数组扩容。\n\n#### PriorityQueue\n用平衡二叉最小堆实现的优先级队列，不再是FIFO，而是按元素实现的Comparable接口或传入Comparator的比较结果来出队，数值越小，优先级越高，越先出队。但是注意其iterator（）的返回不会排序。\n\n平衡最小二叉堆，用一个简单的数组即可表达，可以快速寻址，没有指针什么的。最小的在queue[0] ，比如queue[4]的两个孩子，会在queue[2*4+1] 和 queue[2*（4+1）]，即queue[9]和queue[10]。\n\n入队时，插入queue[size]，然后二叉地往上比较调整堆。\n\n出队时，弹出queue[0]，然后把queque[size]拿出来二叉地往下比较调整堆。\n\n初始大小为11，空间不够时自动50%扩容。\n\n \n\n#### 线程安全的队列\nConcurrentLinkedQueue/Deque\n无界的并发优化的Queue，基于链表，实现了依赖于CAS的无锁算法。\n\nConcurrentLinkedQueue的结构是单向链表和head/tail两个指针，因为入队时需要修改队尾元素的next指针，以及修改tail指向新入队的元素两个CAS动作无法原子，所以需要的特殊的算法。\n\n#### 线程安全的阻塞队列\nBlockingQueue，一来如果队列已空不用重复的查看是否有新数据而会阻塞在那里，二来队列的长度受限，用以保证生产者与消费者的速度不会相差太远。当入队时队列已满，或出队时队列已空，不同函数的效果见下表\n\n\nArrayBlockingQueue\n定长的并发优化的BlockingQueue，也是基于循环数组实现。有一把公共的锁与notFull、notEmpty两个Condition管理队列满或空时的阻塞状态。\n\nLinkedBlockingQueue/Deque\n可选定长的并发优化的BlockingQueue，基于链表实现，所以可以把长度设为Integer.MAX_VALUE成为无界无等待的。\n\n利用链表的特征，分离了takeLock与putLock两把锁，继续用notEmpty、notFull管理队列满或空时的阻塞状态。\n\nPriorityBlockingQueue\n无界的PriorityQueue，也是基于数组存储的二叉堆（见前）。一把公共的锁实现线程安全。因为无界，空间不够时会自动扩容，所以入列时不会锁，出列为空时才会锁。\n\n\nDelayQueue\n内部包含一个PriorityQueue，同样是无界的，同样是出列时才会锁。一把公共的锁实现线程安全。元素需实现Delayed接口，每次调用时需返回当前离触发时间还有多久，小于0表示该触发了。\n\npull（）时会用peek（）查看队头的元素，检查是否到达触发时间。ScheduledThreadPoolExecutor用了类似的结构。\n\n\n\n#### 同步队列\nSynchronousQueue同步队列本身无容量，放入元素时，比如等待元素被另一条线程的消费者取走再返回。JDK线程池里用它。\n\nJDK7还有个LinkedTransferQueue，在普通线程安全的BlockingQueue的基础上，增加一个transfer（e） 函数，效果与SynchronousQueue一样。\n\n## 参考文章\n\nhttps://blog.csdn.net/zzw1531439090/article/details/87872424\nhttps://blog.csdn.net/weixin_40374341/article/details/86496343\nhttps://www.cnblogs.com/uodut/p/7067162.html\nhttps://www.jb51.net/article/135672.htm\nhttps://www.cnblogs.com/suiyue-/p/6052456.html\n\n"
  },
  {
    "path": "docs/Java/basic/final关键字特性.md",
    "content": "# 目录\n\n  * [final使用](#final使用)\n    * [final变量](#final变量)\n    * [final修饰基本数据类型变量和引用](#final修饰基本数据类型变量和引用)\n    * [final类](#final类)\n    * [final关键字的知识点](#final关键字的知识点)\n  * [final关键字的最佳实践](#final关键字的最佳实践)\n    * [final的用法](#final的用法)\n    * [关于空白final](#关于空白final)\n    * [final内存分配](#final内存分配)\n    * [使用final修饰方法会提高速度和效率吗](#使用final修饰方法会提高速度和效率吗)\n    * [使用final修饰变量会让变量的值不能被改变吗；](#使用final修饰变量会让变量的值不能被改变吗；)\n    * [如何保证数组内部不被修改](#如何保证数组内部不被修改)\n    * [final方法的三条规则](#final方法的三条规则)\n  * [final 和 jvm的关系](#final-和-jvm的关系)\n    * [写 final 域的重排序规则](#写-final-域的重排序规则)\n    * [读 final 域的重排序规则](#读-final-域的重排序规则)\n    * [如果 final 域是引用类型](#如果-final-域是引用类型)\n  * [参考文章](#参考文章)\n\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章首发于我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《夯实Java基础系列博文》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n该系列博文会告诉你如何从入门到进阶，一步步地学习Java基础知识，并上手进行实战，接着了解每个Java知识点背后的实现原理，更完整地了解整个Java技术体系，形成自己的知识框架。为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n\n<!-- more -->\nfinal关键字在java中使用非常广泛，可以申明成员变量、方法、类、本地变量。一旦将引用声明为final，将无法再改变这个引用。final关键字还能保证内存同步，本博客将会从final关键字的特性到从java内存层面保证同步讲解。这个内容在面试中也有可能会出现。\n\n## final使用\n\n### final变量\n\nfinal变量有成员变量或者是本地变量(方法内的局部变量)，在类成员中final经常和static一起使用，作为类常量使用。**其中类常量必须在声明时初始化，final成员常量可以在构造函数初始化。**\n\n```\npublic class Main {\n    public static final int i; //报错，必须初始化 因为常量在常量池中就存在了，调用时不需要类的初始化，所以必须在声明时初始化\n    public static final int j;\n    Main() {\n        i = 2;\n        j = 3;\n    }\n}\n\n```\n\n就如上所说的，对于类常量，JVM会缓存在常量池中，在读取该变量时不会加载这个类。\n\n```\n\npublic class Main {\n    public static final int i = 2;\n    Main() {\n        System.out.println(\"调用构造函数\"); // 该方法不会调用\n    }\n    public static void main(String[] args) {\n        System.out.println(Main.i);\n    }\n}\n\n```\n### final修饰基本数据类型变量和引用\n    @Test\n    public void final修饰基本类型变量和引用() {\n        final int a = 1;\n        final int[] b = {1};\n        final int[] c = {1};\n    //  b = c;报错\n        b[0] = 1;\n        final String aa = \"a\";\n        final Fi f = new Fi();\n        //aa = \"b\";报错\n        // f = null;//报错\n        f.a = 1;\n    }\n\nfinal方法表示该方法不能被子类的方法重写，将方法声明为final，在编译的时候就已经静态绑定了，不需要在运行时动态绑定。final方法调用时使用的是invokespecial指令。\n\n```\nclass PersonalLoan{\n    public final String getName(){\n        return\"personal loan”;\n    }\n}\n\nclass CheapPersonalLoan extends PersonalLoan{\n    @Override\n    public final String getName(){\n        return\"cheap personal loan\";//编译错误，无法被重载\n    }\n\n    public String test() {\n        return getName(); //可以调用，因为是public方法\n    }\n}\n\n```\n\n### final类\n\nfinal类不能被继承，final类中的方法默认也会是final类型的，java中的String类和Integer类都是final类型的。\n\n    class Si{\n        //一般情况下final修饰的变量一定要被初始化。\n        //只有下面这种情况例外，要求该变量必须在构造方法中被初始化。\n        //并且不能有空参数的构造方法。\n        //这样就可以让每个实例都有一个不同的变量，并且这个变量在每个实例中只会被初始化一次\n        //于是这个变量在单个实例里就是常量了。\n        final int s ;\n        Si(int s) {\n            this.s = s;\n        }\n    }\n    class Bi {\n        final int a = 1;\n        final void go() {\n            //final修饰方法无法被继承\n        }\n    }\n    class Ci extends Bi {\n        final int a = 1;\n    //        void go() {\n    //            //final修饰方法无法被继承\n    //        }\n    }\n    final char[]a = {'a'};\n    final int[]b = {1};\n\n```\nfinal class PersonalLoan{}\n\nclass CheapPersonalLoan extends PersonalLoan {  //编译错误，无法被继承 \n}\n\n```\n\n    @Test\n    public void final修饰类() {\n        //引用没有被final修饰，所以是可变的。\n        //final只修饰了Fi类型，即Fi实例化的对象在堆中内存地址是不可变的。\n        //虽然内存地址不可变，但是可以对内部的数据做改变。\n        Fi f = new Fi();\n        f.a = 1;\n        System.out.println(f);\n        f.a = 2;\n        System.out.println(f);\n        //改变实例中的值并不改变内存地址。\n    \n        Fi ff = f;\n        //让引用指向新的Fi对象，原来的f对象由新的引用ff持有。\n        //引用的指向改变也不会改变原来对象的地址\n        f = new Fi();\n        System.out.println(f);\n        System.out.println(ff);\n    }\n\n### final关键字的知识点\n\n1.  final成员变量必须在声明的时候初始化或者在构造器中初始化，否则就会报编译错误。final变量一旦被初始化后不能再次赋值。\n2.  本地变量必须在声明时赋值。 因为没有初始化的过程\n3.  在匿名类中所有变量都必须是final变量。\n4.  final方法不能被重写, final类不能被继承\n5.  接口中声明的所有变量本身是final的。类似于匿名类\n6.  final和abstract这两个关键字是反相关的，final类就不可能是abstract的。\n7.  final方法在编译阶段绑定，称为静态绑定(static binding)。\n8.  将类、方法、变量声明为final能够提高性能，这样JVM就有机会进行估计，然后优化。\n\nfinal方法的好处:\n\n1.  提高了性能，JVM在常量池中会缓存final变量\n2.  final变量在多线程中并发安全，无需额外的同步开销\n3.  final方法是静态编译的，提高了调用速度\n4.  **final类创建的对象是只可读的，在多线程可以安全共享**\n\n## final关键字的最佳实践\n\n### final的用法 \n1、final 对于常量来说，意味着值不能改变，例如 final int i=100。这个i的值永远都是100。 \n但是对于变量来说又不一样，只是标识这个引用不可被改变，例如 final File f=new File(&quot;c:\\\\test.txt&quot;);\n\n那么这个f一定是不能被改变的，如果f本身有方法修改其中的成员变量，例如是否可读，是允许修改的。有个形象的比喻：一个女子定义了一个final的老公，这个老公的职业和收入都是允许改变的，只是这个女人不会换老公而已。 \n\n### 关于空白final \nfinal修饰的变量有三种：静态变量、实例变量和局部变量，分别表示三种类型的常量。  \n　另外，final变量定义的时候，可以先声明，而不给初值，这中变量也称为final空白，无论什么情况，编译器都确保空白final在使用之前必须被初始化。\n　\n但是，final空白在final关键字final的使用上提供了更大的灵活性，为此，一个类中的final数据成员就可以实现依对象而有所不同，却有保持其恒定不变的特征。 \n````\n    public class FinalTest { \n    final int p; \n    final int q=3; \n    FinalTest(){ \n    p=1; \n    } \n    FinalTest(int i){ \n    p=i;//可以赋值，相当于直接定义p \n    q=i;//不能为一个final变量赋值 \n    } \n    } \n````\n### final内存分配 \n刚提到了内嵌机制，现在详细展开。 \n要知道调用一个函数除了函数本身的执行时间之外，还需要额外的时间去寻找这个函数（类内部有一个函数签名和函数地址的映射表）。所以减少函数调用次数就等于降低了性能消耗。 \n\nfinal修饰的函数会被编译器优化，优化的结果是减少了函数调用的次数。如何实现的，举个例子给你看：\n````\n    public class Test{ \n    final void func(){System.out.println(\"g\");}; \n    public void main(String[] args){ \n    for(int j=0;j<1000;j++)   \n    func(); \n    }} \n\n    经过编译器优化之后，这个类变成了相当于这样写： \n    public class Test{ \n    final void func(){System.out.println(\"g\");}; \n    public void main(String[] args){ \n    for(int j=0;j<1000;j++)  \n    {System.out.println(\"g\");} \n    }} \n````\n看出来区别了吧？编译器直接将func的函数体内嵌到了调用函数的地方，这样的结果是节省了1000次函数调用，当然编译器处理成字节码，只是我们可以想象成这样，看个明白。 \n\n不过，当函数体太长的话，用final可能适得其反，因为经过编译器内嵌之后代码长度大大增加，于是就增加了jvm解释字节码的时间。\n\n在使用final修饰方法的时候，编译器会将被final修饰过的方法插入到调用者代码处，提高运行速度和效率，但被final修饰的方法体不能过大，编译器可能会放弃内联，但究竟多大的方法会放弃，我还没有做测试来计算过。 \n\n**下面这些内容是通过两个疑问来继续阐述的**\n\n### 使用final修饰方法会提高速度和效率吗\n\n见下面的测试代码，我会执行五次：\n\n````\n    public class Test   \n    {   \n        public static void getJava()   \n        {   \n            String str1 = \"Java \";   \n            String str2 = \"final \";   \n            for (int i = 0; i < 10000; i++)   \n            {   \n                str1 += str2;   \n            }   \n        }   \n        public static final void getJava_Final()   \n        {   \n            String str1 = \"Java \";   \n            String str2 = \"final \";   \n            for (int i = 0; i < 10000; i++)   \n            {   \n                str1 += str2;   \n            }   \n        }   \n        public static void main(String[] args)   \n        {   \n            long start = System.currentTimeMillis();   \n            getJava();   \n            System.out.println(\"调用不带final修饰的方法执行时间为:\" + (System.currentTimeMillis() - start) + \"毫秒时间\");   \n            start = System.currentTimeMillis();   \n            String str1 = \"Java \";   \n            String str2 = \"final \";   \n            for (int i = 0; i < 10000; i++)   \n            {   \n                str1 += str2;   \n            }   \n            System.out.println(\"正常的执行时间为:\" + (System.currentTimeMillis() - start) + \"毫秒时间\");   \n            start = System.currentTimeMillis();   \n            getJava_Final();   \n            System.out.println(\"调用final修饰的方法执行时间为:\" + (System.currentTimeMillis() - start) + \"毫秒时间\");   \n        }   \n    }  \n\n````\n\n    结果为： \n    第一次： \n    调用不带final修饰的方法执行时间为:1732毫秒时间 \n    正常的执行时间为:1498毫秒时间 \n    调用final修饰的方法执行时间为:1593毫秒时间 \n    第二次： \n    调用不带final修饰的方法执行时间为:1217毫秒时间 \n    正常的执行时间为:1031毫秒时间 \n    调用final修饰的方法执行时间为:1124毫秒时间 \n    第三次： \n    调用不带final修饰的方法执行时间为:1154毫秒时间 \n    正常的执行时间为:1140毫秒时间 \n    调用final修饰的方法执行时间为:1202毫秒时间 \n    第四次： \n    调用不带final修饰的方法执行时间为:1139毫秒时间 \n    正常的执行时间为:999毫秒时间 \n    调用final修饰的方法执行时间为:1092毫秒时间 \n    第五次： \n    调用不带final修饰的方法执行时间为:1186毫秒时间 \n    正常的执行时间为:1030毫秒时间 \n    调用final修饰的方法执行时间为:1109毫秒时间 \n    \n    由以上运行结果不难看出，执行最快的是“正常的执行”即代码直接编写，而使用final修饰的方法，不像有些书上或者文章上所说的那样，速度与效率与“正常的执行”无异，而是位于第二位，最差的是调用不加final修饰的方法。 \n\n    观点：加了比不加好一点。 \n\n\n### 使用final修饰变量会让变量的值不能被改变吗； \n见代码：\n\n````\n    public class Final   \n    {   \n        public static void main(String[] args)   \n        {   \n            Color.color[3] = \"white\";   \n            for (String color : Color.color)   \n                System.out.print(color+\" \");   \n        }   \n    }   \n      \n    class Color   \n    {   \n        public static final String[] color = { \"red\", \"blue\", \"yellow\", \"black\" };   \n    }  \n\n\n    执行结果： \n    red blue yellow white \n    看！，黑色变成了白色。 \n\n````\n    \n    在使用findbugs插件时，就会提示public static String[] color = { \"red\", \"blue\", \"yellow\", \"black\" };这行代码不安全，但加上final修饰，这行代码仍然是不安全的，因为final没有做到保证变量的值不会被修改！\n    \n    原因是：final关键字只能保证变量本身不能被赋与新值，而不能保证变量的内部结构不被修改。例如在main方法有如下代码Color.color = new String[]{\"\"};就会报错了。\n\n### 如何保证数组内部不被修改\n\n    那可能有的同学就会问了，加上final关键字不能保证数组不会被外部修改，那有什么方法能够保证呢？答案就是降低访问级别，把数组设为private。这样的话，就解决了数组在外部被修改的不安全性，但也产生了另一个问题，那就是这个数组要被外部使用的。 \n\n解决这个问题见代码：\n````\n    import java.util.AbstractList;   \n    import java.util.List;   \n    \n    public class Final   \n    {   \n        public static void main(String[] args)   \n        {   \n            for (String color : Color.color)   \n                System.out.print(color + \" \");   \n            Color.color.set(3, \"white\");   \n        }   \n    }   \n      \n    class Color   \n    {   \n        private static String[] _color = { \"red\", \"blue\", \"yellow\", \"black\" };   \n        public static List<String> color = new AbstractList<String>()   \n        {   \n            @Override  \n            public String get(int index)   \n            {   \n                return _color[index];   \n            }   \n            @Override  \n            public String set(int index, String value)   \n            {   \n                throw new RuntimeException(\"为了代码安全,不能修改数组\");   \n            }   \n            @Override  \n            public int size()   \n            {   \n                return _color.length;   \n            }   \n        };  \n\n\n    }\n````\n这样就OK了，既保证了代码安全，又能让数组中的元素被访问了。\n\n\n### final方法的三条规则\n\n规则1：final修饰的方法不可以被重写。\n\n规则2：final修饰的方法仅仅是不能重写，但它完全可以被重载。\n\n规则3：父类中private final方法，子类可以重新定义，这种情况不是重写。\n\n代码示例\n````\n    规则1代码\n    \n    public class FinalMethodTest\n    {\n    \tpublic final void test(){}\n    }\n    class Sub extends FinalMethodTest\n    {\n    \t// 下面方法定义将出现编译错误，不能重写final方法\n    \tpublic void test(){}\n    }\n    \n    规则2代码\n    \n    public class Finaloverload {\n    \t//final 修饰的方法只是不能重写，完全可以重载\n    \tpublic final void test(){}\n    \tpublic final void test(String arg){}\n    }\n    \n    规则3代码\n    \n    public class PrivateFinalMethodTest\n    {\n    \tprivate final void test(){}\n    }\n    class Sub extends PrivateFinalMethodTest\n    {\n    \t// 下面方法定义将不会出现问题\n    \tpublic void test(){}\n    }\n````\n\n## final 和 jvm的关系\n\n与前面介绍的锁和 volatile 相比较，对 final 域的读和写更像是普通的变量访问。对于 final 域，编译器和处理器要遵守两个重排序规则：\n\n1.  在构造函数内对一个 final 域的写入，与随后把这个被构造对象的引用赋值给一个引用变量，这两个操作之间不能重排序。\n2.  初次读一个包含 final 域的对象的引用，与随后初次读这个 final 域，这两个操作之间不能重排序。\n\n下面，我们通过一些示例性的代码来分别说明这两个规则：\n````\npublic class FinalExample {\n    int i;                            // 普通变量 \n    final int j;                      //final 变量 \n    static FinalExample obj;\n\n    public void FinalExample () {     // 构造函数 \n        i = 1;                        // 写普通域 \n        j = 2;                        // 写 final 域 \n    }\n    \n    public static void writer () {    // 写线程 A 执行 \n        obj = new FinalExample ();\n    }\n    \n    public static void reader () {       // 读线程 B 执行 \n        FinalExample object = obj;       // 读对象引用 \n        int a = object.i;                // 读普通域 \n        int b = object.j;                // 读 final 域 \n    }\n}\n````\n\n这里假设一个线程 A 执行 writer () 方法，随后另一个线程 B 执行 reader () 方法。下面我们通过这两个线程的交互来说明这两个规则。\n\n### 写 final 域的重排序规则\n\n写 final 域的重排序规则禁止把 final 域的写重排序到构造函数之外。这个规则的实现包含下面 2 个方面：\n\n*   JMM 禁止编译器把 final 域的写重排序到构造函数之外。\n*   编译器会在 final 域的写之后，构造函数 return 之前，插入一个 StoreStore 屏障。这个屏障禁止处理器把 final 域的写重排序到构造函数之外。\n\n现在让我们分析 writer () 方法。writer () 方法只包含一行代码：finalExample = new FinalExample ()。这行代码包含两个步骤：\n\n1.  构造一个 FinalExample 类型的对象；\n2.  把这个对象的引用赋值给引用变量 obj。\n\n假设线程 B 读对象引用与读对象的成员域之间没有重排序（马上会说明为什么需要这个假设），下图是一种可能的执行时序：\n\n![img](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/6628576a54f0ba625c8c3af4586cef3a.jpg)\n\n在上图中，写普通域的操作被编译器重排序到了构造函数之外，读线程 B 错误的读取了普通变量 i 初始化之前的值。而写 final 域的操作，被写 final 域的重排序规则“限定”在了构造函数之内，读线程 B 正确的读取了 final 变量初始化之后的值。\n\n写 final 域的重排序规则可以确保：在对象引用为任意线程可见之前，对象的 final 域已经被正确初始化过了，而普通域不具有这个保障。以上图为例，在读线程 B“看到”对象引用 obj 时，很可能 obj 对象还没有构造完成（对普通域 i 的写操作被重排序到构造函数外，此时初始值 1 还没有写入普通域 i）。\n\n### 读 final 域的重排序规则\n\n读 final 域的重排序规则如下：\n\n*   在一个线程中，初次读对象引用与初次读该对象包含的 final 域，JMM 禁止处理器重排序这两个操作（注意，这个规则仅仅针对处理器）。编译器会在读 final 域操作的前面插入一个 LoadLoad 屏障。\n\n初次读对象引用与初次读该对象包含的 final 域，这两个操作之间存在间接依赖关系。由于编译器遵守间接依赖关系，因此编译器不会重排序这两个操作。大多数处理器也会遵守间接依赖，大多数处理器也不会重排序这两个操作。但有少数处理器允许对存在间接依赖关系的操作做重排序（比如 alpha 处理器），这个规则就是专门用来针对这种处理器。\n\nreader() 方法包含三个操作：\n\n1.  初次读引用变量 obj;\n2.  初次读引用变量 obj 指向对象的普通域 i。\n3.  初次读引用变量 obj 指向对象的 final 域 j。\n\n现在我们假设写线程 A 没有发生任何重排序，同时程序在不遵守间接依赖的处理器上执行，下面是一种可能的执行时序：\n\n![](https://static001.infoq.cn/resource/image/a0/36/a0a9b023bc56ab97bbda8812cdca7236.png)\n\n在上图中，读对象的普通域的操作被处理器重排序到读对象引用之前。读普通域时，该域还没有被写线程 A 写入，这是一个错误的读取操作。而读 final 域的重排序规则会把读对象 final 域的操作“限定”在读对象引用之后，此时该 final 域已经被 A 线程初始化过了，这是一个正确的读取操作。\n\n读 final 域的重排序规则可以确保：在读一个对象的 final 域之前，一定会先读包含这个 final 域的对象的引用。在这个示例程序中，如果该引用不为 null，那么引用对象的 final 域一定已经被 A 线程初始化过了。\n\n### 如果 final 域是引用类型\n\n上面我们看到的 final 域是基础数据类型，下面让我们看看如果 final 域是引用类型，将会有什么效果？\n\n请看下列示例代码：\n````\npublic class FinalReferenceExample {\nfinal int[] intArray;                     //final 是引用类型 \nstatic FinalReferenceExample obj;\n\npublic FinalReferenceExample () {        // 构造函数 \n    intArray = new int[1];              //1\n    intArray[0] = 1;                   //2\n}\n\npublic static void writerOne () {          // 写线程 A 执行 \n    obj = new FinalReferenceExample ();  //3\n}\n\npublic static void writerTwo () {          // 写线程 B 执行 \n    obj.intArray[0] = 2;                 //4\n}\n\npublic static void reader () {              // 读线程 C 执行 \n    if (obj != null) {                    //5\n        int temp1 = obj.intArray[0];       //6\n    }\n}\n}\n````\n\n这里 final 域为一个引用类型，它引用一个 int 型的数组对象。对于引用类型，写 final 域的重排序规则对编译器和处理器增加了如下约束：\n\n1.  在构造函数内对一个 final 引用的对象的成员域的写入，与随后在构造函数外把这个被构造对象的引用赋值给一个引用变量，这两个操作之间不能重排序。\n\n对上面的示例程序，我们假设首先线程 A 执行 writerOne() 方法，执行完后线程 B 执行 writerTwo() 方法，执行完后线程 C 执行 reader () 方法。下面是一种可能的线程执行时序：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/29b097c36fd531028991826bb7c835db.png)\n在上图中，1 是对 final 域的写入，2 是对这个 final 域引用的对象的成员域的写入，3 是把被构造的对象的引用赋值给某个引用变量。这里除了前面提到的 1 不能和 3 重排序外，2 和 3 也不能重排序。\n\nJMM 可以确保读线程 C 至少能看到写线程 A 在构造函数中对 final 引用对象的成员域的写入。即 C 至少能看到数组下标 0 的值为 1。而写线程 B 对数组元素的写入，读线程 C 可能看的到，也可能看不到。JMM 不保证线程 B 的写入对读线程 C 可见，因为写线程 B 和读线程 C 之间存在数据竞争，此时的执行结果不可预知。\n\n如果想要确保读线程 C 看到写线程 B 对数组元素的写入，写线程 B 和读线程 C 之间需要使用同步原语（lock 或 volatile）来确保内存可见性。\n\n\n## 参考文章\n\nhttps://www.infoq.cn/article/java-memory-model-6\nhttps://www.jianshu.com/p/067b6c89875a\nhttps://www.jianshu.com/p/f68d6ef2dcf0\nhttps://www.cnblogs.com/xiaoxi/p/6392154.html\nhttps://www.iteye.com/blog/cakin24-2334965\nhttps://blog.csdn.net/chengqiuming/article/details/70139503\nhttps://blog.csdn.net/hupuxiang/article/details/7362267\n\n\n"
  },
  {
    "path": "docs/Java/basic/javac和javap.md",
    "content": "# 目录\n  * [聊聊IDE的实现原理](#聊聊ide的实现原理)\n    * [源代码保存](#源代码保存)\n    * [编译为class文件](#编译为class文件)\n    * [查找class](#查找class)\n    * [生成对象，并调用对象方法](#生成对象，并调用对象方法)\n  * [javac命令初窥](#javac命令初窥)\n    * [classpath是什么](#classpath是什么)\n      * [IDE中的classpath](#ide中的classpath)\n      * [Java项目和Java web项目的本质区别](#java项目和java-web项目的本质区别)\n    * [javac命令后缀](#javac命令后缀)\n      * [-g、-g:none、-g:{lines,vars,source}](#-g、-gnone、-g{linesvarssource})\n      * [-bootclasspath、-extdirs](#-bootclasspath、-extdirs)\n      * [-sourcepath和-classpath（-cp）](#-sourcepath和-classpath（-cp）)\n      * [-d](#-d)\n      * [-implicit:{none,class}](#-implicit{noneclass})\n      * [-source和-target](#-source和-target)\n      * [-encoding](#-encoding)\n      * [-verbose](#-verbose)\n      * [其他命令](#其他命令)\n    * [使用javac构建项目](#使用javac构建项目)\n  * [javap 的使用](#javap-的使用)\n  * [参考文章](#参考文章)\n\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章首发于我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《夯实Java基础系列博文》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n该系列博文会告诉你如何从入门到进阶，一步步地学习Java基础知识，并上手进行实战，接着了解每个Java知识点背后的实现原理，更完整地了解整个Java技术体系，形成自己的知识框架。为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n## 聊聊IDE的实现原理\n\n> IDE是把双刃剑，它可以什么都帮你做了，你只要敲几行代码，点几下鼠标，程序就跑起来了，用起来相当方便。\n>\n> 你不用去关心它后面做了些什么，执行了哪些命令，基于什么原理。然而也是这种过分的依赖往往让人散失了最基本的技能，当到了一个没有IDE的地方，你便觉得无从下手，给你个代码都不知道怎么去跑。好比给你瓶水，你不知道怎么打开去喝，然后活活给渴死。\n>\n> 之前用惯了idea，Java文件编译运行的命令基本忘得一干二净。\n\n那好，不如咱们先来了解一下IDE的实现原理，这样一来，即使离开IDE，我们还是知道如何运行Java程序了。\n\n像Eclipse等java IDE是怎么编译和查找java源代码的呢？\n\n### 源代码保存\n这个无需多说，在编译器写入代码，并保存到文件。这个利用流来实现。\n\n### 编译为class文件\njava提供了JavaCompiler，我们可以通过它来编译java源文件为class文件。\n\n### 查找class\n可以通过Class.forName(fullClassPath)或自定义类加载器来实现。\n\n### 生成对象，并调用对象方法\n通过上面一个查找class，得到Class对象后，可以通过newInstance()或构造器的newInstance()得到对象。然后得到Method，最后调用方法，传入相关参数即可。\n\n示例代码：\n````\npublic class MyIDE {\n\n    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {\n        // 定义java代码，并保存到文件（Test.java）\n        StringBuilder sb = new StringBuilder();\n        sb.append(\"package com.tommy.core.test.reflect;\\n\");\n        sb.append(\"public class Test {\\n\");\n        sb.append(\"    private String name;\\n\");\n        sb.append(\"    public Test(String name){\\n\");\n        sb.append(\"        this.name = name;\\n\");\n        sb.append(\"        System.out.println(\\\"hello,my name is \\\" + name);\\n\");\n        sb.append(\"    }\\n\");\n        sb.append(\"    public String sayHello(String name) {\\n\");\n        sb.append(\"        return \\\"hello,\\\" + name;\\n\");\n        sb.append(\"    }\\n\");\n        sb.append(\"}\\n\");\n\n        System.out.println(sb.toString());\n\n        String baseOutputDir = \"F:\\\\output\\\\classes\\\\\";\n        String baseDir = baseOutputDir + \"com\\\\tommy\\\\core\\\\test\\\\reflect\\\\\";\n        String targetJavaOutputPath = baseDir + \"Test.java\";\n        // 保存为java文件\n        FileWriter fileWriter = new FileWriter(targetJavaOutputPath);\n        fileWriter.write(sb.toString());\n        fileWriter.flush();\n        fileWriter.close();\n\n        // 编译为class文件\n        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();\n        StandardJavaFileManager manager = compiler.getStandardFileManager(null,null,null);\n        List<File> files = new ArrayList<>();\n        files.add(new File(targetJavaOutputPath));\n        Iterable compilationUnits = manager.getJavaFileObjectsFromFiles(files);\n\n        // 编译\n        // 设置编译选项，配置class文件输出路径\n        Iterable<String> options = Arrays.asList(\"-d\",baseOutputDir);\n        JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, options, null, compilationUnits);\n        // 执行编译任务\n        task.call();\n\n\n   \n        // 通过反射得到对象\n//        Class clazz = Class.forName(\"com.tommy.core.test.reflect.Test\");\n        // 使用自定义的类加载器加载class\n        Class clazz = new MyClassLoader(baseOutputDir).loadClass(\"com.tommy.core.test.reflect.Test\");\n        // 得到构造器\n        Constructor constructor = clazz.getConstructor(String.class);\n        // 通过构造器new一个对象\n        Object test = constructor.newInstance(\"jack.tsing\");\n        // 得到sayHello方法\n        Method method = clazz.getMethod(\"sayHello\", String.class);\n        // 调用sayHello方法\n        String result = (String) method.invoke(test, \"jack.ma\");\n        System.out.println(result);\n    }\n}\n````\n自定义类加载器代码：\n\n````\n    \npublic class MyClassLoader extends ClassLoader {\n    private String baseDir;\n    public MyClassLoader(String baseDir) {\n        this.baseDir = baseDir;\n    }\n    @Override\n    protected Class<?> findClass(String name) throws ClassNotFoundException {\n        String fullClassFilePath = this.baseDir + name.replace(\"\\\\.\",\"/\") + \".class\";\n        File classFilePath = new File(fullClassFilePath);\n        if (classFilePath.exists()) {\n            FileInputStream fileInputStream = null;\n            ByteArrayOutputStream byteArrayOutputStream = null;\n            try {\n                fileInputStream = new FileInputStream(classFilePath);\n                byte[] data = new byte[1024];\n                int len = -1;\n                byteArrayOutputStream = new ByteArrayOutputStream();\n                while ((len = fileInputStream.read(data)) != -1) {\n                    byteArrayOutputStream.write(data,0,len);\n                }\n\n                return defineClass(name,byteArrayOutputStream.toByteArray(),0,byteArrayOutputStream.size());\n            } catch (FileNotFoundException e) {\n                e.printStackTrace();\n            } catch (IOException e) {\n                e.printStackTrace();\n            } finally {\n                if (null != fileInputStream) {\n                    try {\n                        fileInputStream.close();\n                    } catch (IOException e) {\n                        e.printStackTrace();\n                    }\n                }\n\n                if (null != byteArrayOutputStream) {\n                    try {\n                        byteArrayOutputStream.close();\n                    } catch (IOException e) {\n                        e.printStackTrace();\n                    }\n                }\n            }\n        }\n        return super.findClass(name);\n    }\n}    \n````\n## javac命令初窥\n\n注：以下红色标记的参数在下文中有所讲解。\n\n本部分参考https://www.cnblogs.com/xiazdong/p/3216220.html\n\n用法: javac <options> <source files>\n\n其中, 可能的选项包括:\n\n>   -g                         生成所有调试信息\n>\n>   -g:none                    不生成任何调试信息\n>\n>   -g:{lines,vars,source}     只生成某些调试信息\n>\n>   -nowarn                    不生成任何警告\n>\n>   -verbose                   输出有关编译器正在执行的操作的消息\n>\n>   -deprecation               输出使用已过时的 API 的源位置\n>\n>   -classpath <路径>            指定查找用户类文件和注释处理程序的位置\n>\n>   -cp <路径>                   指定查找用户类文件和注释处理程序的位置\n>\n>   -sourcepath <路径>           指定查找输入源文件的位置\n>\n>   -bootclasspath <路径>        覆盖引导类文件的位置\n>\n>   -extdirs <目录>              覆盖所安装扩展的位置\n>\n>   -endorseddirs <目录>         覆盖签名的标准路径的位置\n>\n>   -proc:{none,only}          控制是否执行注释处理和/或编译。\n>\n>   -processor <class1>[,<class2>,<class3>...] 要运行的注释处理程序的名称; 绕过默认的搜索进程\n>\n>   -processorpath <路径>        指定查找注释处理程序的位置\n>\n>   -d <目录>                    指定放置生成的类文件的位置\n>\n>   -s <目录>                    指定放置生成的源文件的位置\n>\n>   -implicit:{none,class}     指定是否为隐式引用文件生成类文件\n>\n>   -encoding <编码>             指定源文件使用的字符编码\n>\n>   -source <发行版>              提供与指定发行版的源兼容性\n>\n>   -target <发行版>              生成特定 VM 版本的类文件\n>\n>   -version                   版本信息\n>\n>   -help                      输出标准选项的提要\n>\n>   -A关键字[=值]                  传递给注释处理程序的选项\n>\n>   -X                         输出非标准选项的提要\n>\n>   -J<标记>                     直接将 <标记> 传递给运行时系统\n>\n>   -Werror                    出现警告时终止编译\n>\n>   @<文件名>                     从文件读取选项和文件名\n\n\n在详细介绍javac命令之前，先看看这个classpath是什么\n\n\n### classpath是什么\n\n在dos下编译java程序，就要用到classpath这个概念，尤其是在没有设置环境变量的时候。classpath就是存放.class等编译后文件的路径。\n\njavac：如果当前你要编译的java文件中引用了其它的类(比如说：继承)，但该引用类的.class文件不在当前目录下，这种情况下就需要在javac命令后面加上-classpath参数，通过使用以下三种类型的方法 来指导编译器在编译的时候去指定的路径下查找引用类。\n\n> (1).绝对路径：javac -classpath c:/junit3.8.1/junit.jar   Xxx.java\n>\n> (2).相对路径：javac -classpath ../junit3.8.1/Junit.javr  Xxx.java\n>\n> (3).系统变量：javac -classpath %CLASSPATH% Xxx.java (注意：%CLASSPATH%表示使用系统变量CLASSPATH的值进行查找，这里假设Junit.jar的路径就包含在CLASSPATH系统变量中)\n\n\n#### IDE中的classpath\n\n对于一个普通的Javaweb项目，一般有这样的配置：\n\n> 1 WEB-INF/classes,lib才是classpath，WEB-INF/ 是资源目录, 客户端不能直接访问。\n>\n> 2、WEB-INF/classes目录存放src目录java文件编译之后的class文件，xml、properties等资源配置文件，这是一个定位资源的入口。\n>\n> 3、引用classpath路径下的文件，只需在文件名前加classpath:\n>\n> <param-value>classpath:applicationContext-*.xml</param-value> \n> <!-- 引用其子目录下的文件,如 -->\n> <param-value>classpath:context/conf/controller.xml</param-value>\n>\n> 4、lib和classes同属classpath，两者的访问优先级为: lib>classes。\n>\n> 5、classpath 和 classpath* 区别：\n>\n> classpath：只会到你的class路径中查找找文件;\n> classpath*：不仅包含class路径，还包括jar文件中(class路径)进行查找。\n\n总结：\n\n(1).何时需要使用-classpath：当你要编译或执行的类引用了其它的类，但被引用类的.class文件不在当前目录下时，就需要通过-classpath来引入类\n\n(2).何时需要指定路径：当你要编译的类所在的目录和你执行javac命令的目录不是同一个目录时，就需要指定源文件的路径(CLASSPATH是用来指定.class路径的，不是用来指定.java文件的路径的) \n#### Java项目和Java web项目的本质区别\n\n（看清IDE及classpath本质）\n\n> 现在只是说说Java Project和Web Project，那么二者有区别么？回答：没有！都是Java语言的应用，只是应用场合不同罢了，那么他们的本质到底是什么？\n\n> 回答：编译后路径！虚拟机执行的是class文件而不是java文件，那么我们不管是何种项目都是写的java文件，怎么就不一样了呢？分成java和web两种了呢？\n\n> 从.classpath文件入手来看，这个文件在每个项目目录下都是存在的，很少有人打开看吧，那么我们就来一起看吧。这是一个XML文件，使用文本编辑器打开即可。\n>\n> 这里展示一个web项目的.classpath\n\nXml代码\n````\n    <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n    <classpath>\n    <classpathentry kind=\"src\" path=\"src\"/>\n    <classpathentry kind=\"src\" path=\"resources\"/>\n    <classpathentry kind=\"src\" path=\"test\"/>\n    <classpathentry kind=\"con\" path=\"org.eclipse.jdt.launching.JRE_CONTAINER\"/>\n    <classpathentry kind=\"lib\" path=\"lib/servlet-api.jar\"/>\n    <classpathentry kind=\"lib\" path=\"webapp/WEB-INF/lib/struts2-core-2.1.8.1.jar\"/>\n         ……\n    <classpathentry kind=\"output\" path=\"webapp/WEB-INF/classes\"/>\n    </classpath>\n````\n> XML文档包含一个根元素，就是classpath，类路径，那么这里面包含了什么信息呢？子元素是classpathentry，kind属性区别了种 类信息，src源码，con你看看后面的path就知道是JRE容器的信息。lib是项目依赖的第三方类库，output是src编译后的位置。\n\n> 既然是web项目，那么就是WEB-INF/classes目录，可能用MyEclipse的同学会说他们那里是WebRoot或者是WebContext而不是webapp，有区别么？回答：完全没有！\n\n> 既然看到了编译路径的本来面目后，还区分什么java项目和web项目么？回答:不区分！普通的java 项目你这样写就行了：<classpathentry kind=\"output\" path=\"bin\"/>，看看Eclipse是不是这样生成的？这个问题解决了吧。\n\n> 再说说webapp目录命名的问题，这个无所谓啊，web项目是要发布到服务器上的对吧，那么服务器读取的是类文件和页面文件吧，它不管源文件，它也无法去理解源文件。那么webapp目录的命名有何关系呢？只要让服务器找到不就行了。\n\n### javac命令后缀\n\n#### -g、-g:none、-g:{lines,vars,source}\n\n> •-g：在生成的class文件中包含所有调试信息（行号、变量、源文件）\n> •-g:none ：在生成的class文件中不包含任何调试信息。\n>\n> 这个参数在javac编译中是看不到什么作用的，因为调试信息都在class文件中，而我们看不懂这个class文件。\n>\n> 为了看出这个参数的作用，我们在eclipse中进行实验。在eclipse中，我们经常做的事就是“debug”，而在debug的时候，我们会\n> •加入“断点”，这个是靠-g:lines起作用，如果不记录行号，则不能加断点。\n> •在“variables”窗口中查看当前的变量，如下图所示，这是靠-g:vars起作用，否则不能查看变量信息。\n> •在多个文件之间来回调用，比如 A.java的main()方法中调用了B.java的fun()函数，而我想看看程序进入fun()后的状态，这是靠-g:source，如果没有这个参数，则不能查看B.java的源代码。\n\n#### -bootclasspath、-extdirs\n\n> -bootclasspath和-extdirs 几乎不需要用的，因为他是用来改变 “引导类”和“扩展类”。\n> •引导类(组成Java平台的类)：Java\\jdk1.7.0_25\\jre\\lib\\rt.jar等，用-bootclasspath设置。\n> •扩展类：Java\\jdk1.7.0_25\\jre\\lib\\ext目录中的文件，用-extdirs设置。\n> •用户自定义类：用-classpath设置。\n>\n> 我们用-verbose编译后出现的“类文件的搜索路径”，就是由上面三个路径组成，如下：\n\n\n    [类文件的搜索路径: C:\\Java\\jdk1.7.0_25\\jre\\lib\\resources.jar,C:\\Java\\jdk1.7.0_25\n    \n    \\jre\\lib\\rt.jar,C:\\Java\\jdk1.7.0_25\\jre\\lib\\sunrsasign.jar,C:\\Java\\jdk1.7.0_25\\j\n    \n    re\\lib\\jsse.jar,C:\\Java\\jdk1.7.0_25\\jre\\lib\\jce.jar,C:\\Java\\jdk1.7.0_25\\jre\\lib\\\n    \n    charsets.jar,C:\\Java\\jdk1.7.0_25\\jre\\lib\\jfr.jar,C:\\Java\\jdk1.7.0_25\\jre\\classes\n    \n    ,C:\\Java\\jdk1.7.0_25\\jre\\lib\\ext\\access-bridge-32.jar,C:\\Java\\jdk1.7.0_25\\jre\\li\n    \n    b\\ext\\dnsns.jar,C:\\Java\\jdk1.7.0_25\\jre\\lib\\ext\\jaccess.jar,C:\\Java\\jdk1.7.0_25\\\n    \n    jre\\lib\\ext\\localedata.jar,C:\\Java\\jdk1.7.0_25\\jre\\lib\\ext\\sunec.jar,C:\\Java\\jdk\n    \n    1.7.0_25\\jre\\lib\\ext\\sunjce_provider.jar,C:\\Java\\jdk1.7.0_25\\jre\\lib\\ext\\sunmsca\n    \n    pi.jar,C:\\Java\\jdk1.7.0_25\\jre\\lib\\ext\\sunpkcs11.jar,C:\\Java\\jdk1.7.0_25\\jre\\lib\n    \\ext\\zipfs.jar,..\\bin]             \n\n如果利用 -bootclasspath 重新定义： javac -bootclasspath src Xxx.java，则会出现下面错误：\n\n\n致命错误: 在类路径或引导类路径中找不到程序包 java.lang\n\n#### -sourcepath和-classpath（-cp）\n\n•-classpath(-cp)指定你依赖的类的class文件的查找位置。在Linux中，用“:”分隔classpath，而在windows中，用“;”分隔。\n•-sourcepath指定你依赖的类的java文件的查找位置。\n\n举个例子，\n\n\n````\npublic class A\n{\n    public static void main(String[] args) {\n        B b = new B();\n        b.print();\n    }\n}\n\n\n\n\npublic class B\n{\n    public void print()\n    {\n        System.out.println(\"old\");\n    }\n}\n````\n\n目录结构如下：\n\n\nsourcepath          //此处为当前目录\n\n```\n|-src\n　　　　|-com\n　　　　　　|- B.java\n　　　　|- A.java\n　　|-bin\n　　　　|- B.class               //是 B.java\n```\n 编译后的类文件\n\n如果要编译 A.java，则必须要让编译器找到类B的位置，你可以指定B.class的位置，也可以是B.java的位置，也可以同时都存在。\n\n\n    javac -classpath bin src/A.java                            //查找到B.class\n    \n    javac -sourcepath src/com src/A.java                   //查找到B.java\n    \n    javac -sourcepath src/com -classpath bin src/A.java    //同时查找到B.class和B.java\n\n如果同时找到了B.class和B.java，则：\n•如果B.class和B.java内容一致，则遵循B.class。\n•如果B.class和B.java内容不一致，则遵循B.java，并编译B.java。\n\n以上规则可以通过 -verbose选项看出。\n\n#### -d\n\n•d就是 destination，用于指定.class文件的生成目录，在eclipse中，源文件都在src中，编译的class文件都是在bin目录中。\n\n这里我用来实现一下这个功能，假设项目名称为project，此目录为当前目录，且在src/com目录中有一个Main.java文件。‘\n\n\n````  \n    package com;\n    public class Main\n    {\n        public static void main(String[] args) {\n            System.out.println(\"Hello\");\n        }\n    }\n\n\n````    \n    \njavac -d bin src/com/Main.java\n\n上面的语句将Main.class生成在bin/com目录下。\n\n#### -implicit:{none,class}\n\n•如果有文件为A.java（其中有类A），且在类A中使用了类B，类B在B.java中，则编译A.java时，默认会自动编译B.java，且生成B.class。\n•implicit:none：不自动生成隐式引用的类文件。\n•implicit:class（默认）：自动生成隐式引用的类文件。\n````\npublic class A\n{\n    public static void main(String[] args) {\n        B b = new B();\n    }\n}\n\npublic class B\n{\n}\n````\n如果使用：\n\n\n    \njavac -implicit:none A.java\n\n则不会生成 B.class。\n\n#### -source和-target\n\n•-source：使用指定版本的JDK编译，比如：-source 1.4表示用JDK1.4的标准编译，如果在源文件中使用了泛型，则用JDK1.4是不能编译通过的。\n•-target：指定生成的class文件要运行在哪个JVM版本，以后实际运行的JVM版本必须要高于这个指定的版本。\n\n\njavac -source 1.4 Xxx.java\n\njavac -target 1.4 Xxx.java\n\n#### -encoding\n\n默认会使用系统环境的编码，比如我们一般用的中文windows就是GBK编码，所以直接javac时会用GBK编码，而Java文件一般要使用utf-8，如果用GBK就会出现乱码。 \n\n•指定源文件的编码格式，如果源文件是UTF-8编码的，而-encoding GBK，则源文件就变成了乱码（特别是有中文时）。\n\n\njavac -encoding UTF-8 Xxx.java\n\n#### -verbose\n\n输出详细的编译信息，包括：classpath、加载的类文件信息。\n\n比如，我写了一个最简单的HelloWorld程序，在命令行中输入：\n\n\nD:\\Java>javac -verbose -encoding UTF-8 HelloWorld01.java\n\n输出：\n\n\n    [语法分析开始时间 RegularFileObject[HelloWorld01.java]]\n    [语法分析已完成, 用时 21 毫秒]\n    [源文件的搜索路径: .,D:\\大三下\\编译原理\\cup\\java-cup-11a.jar,E:\\java\\jflex\\lib\\J           //-sourcepath\n    Flex.jar]\n    [类文件的搜索路径: C:\\Java\\jdk1.7.0_25\\jre\\lib\\resources.jar,C:\\Java\\jdk1.7.0_25      //-classpath、-bootclasspath、-extdirs\n    省略............................................\n    [正在加载ZipFileIndexFileObject[C:\\Java\\jdk1.7.0_25\\lib\\ct.sym(META-INF/sym/rt.j\n    ar/java/lang/Object.class)]]\n    [正在加载ZipFileIndexFileObject[C:\\Java\\jdk1.7.0_25\\lib\\ct.sym(META-INF/sym/rt.j\n    ar/java/lang/String.class)]]\n    [正在检查Demo]\n    省略............................................\n    [已写入RegularFileObject[Demo.class]]\n    [共 447 毫秒]\n\n编写一个程序时，比如写了一句：System.out.println(\"hello\")，实际上还需要加载：Object、PrintStream、String等类文件，而上面就显示了加载的全部类文件。\n\n#### 其他命令\n\n-J <标记>\n•传递一些信息给 Java Launcher.\n\n\n    javac -J-Xms48m   Xxx.java          //set the startup memory to 48M.\n\n-@<文件名>\n\n> 如果同时需要编译数量较多的源文件(比如1000个)，一个一个编译是不现实的（当然你可以直接 javac *.java ），比较好的方法是：将你想要编译的源文件名都写在一个文件中（比如sourcefiles.txt），其中每行写一个文件名，如下所示：\n>\n>\n> HelloWorld01.java\n> HelloWorld02.java\n> HelloWorld03.java\n\n则使用下面的命令：\n\n\njavac @sourcefiles.txt\n\n编译这三个源文件。\n\n\n\n## 使用javac构建项目\n\n这部分参考：\nhttps://blog.csdn.net/mingover/article/details/57083176\n\n一个简单的javac编译\n\n新建两个文件夹,src和 build \nsrc/com/yp/test/HelloWorld.java \nbuild/\n\n\n````\n├─build\n└─src\n    └─com\n        └─yp\n            └─test\n                    HelloWorld.java\n````\n\n\njava文件非常简单\n````\n    package com.yp.test;\n    public class HelloWorld {\n    \n        public static void main(String[] args) {\n            System.out.println(\"helloWorld\");\n        }\n    }\n````\n\n编译:\njavac src/com/yp/test/HelloWorld.java -d build\n\n-d 表示编译到 build文件夹下\n\n````\n查看build文件夹\n├─build\n│  └─com\n│      └─yp\n│          └─test\n│                  HelloWorld.class\n│\n└─src\n    └─com\n        └─yp\n            └─test\n                    HelloWorld.java\n````\n\n\n运行文件\n\n> E:\\codeplace\\n_learn\\java\\javacmd> java com/yp/test/HelloWorld.class\n> 错误: 找不到或无法加载主类 build.com.yp.test.HelloWorld.class\n>\n> 运行时要指定main\n> E:\\codeplace\\n_learn\\java\\javacmd\\build> java com.yp.test.HelloWorld\n> helloWorld\n\n如果引用到多个其他的类，应该怎么做呢 ？\n\n> 编译\n>\n> E:\\codeplace\\n_learn\\java\\javacmd>javac src/com/yp/test/HelloWorld.java -sourcepath src -d build -g\n> 1\n> -sourcepath 表示 从指定的源文件目录中找到需要的.java文件并进行编译。\n> 也可以用-cp指定编译好的class的路径\n> 运行,注意:运行在build目录下\n>\n> E:\\codeplace\\n_learn\\java\\javacmd\\build>java com.yp.test.HelloWorld\n\n怎么打成jar包?\n\n> 生成:\n> E:\\codeplace\\n_learn\\java\\javacmd\\build>jar cvf h.jar *\n> 运行:\n> E:\\codeplace\\n_learn\\java\\javacmd\\build>java h.jar\n> 错误: 找不到或无法加载主类 h.jar\n\n> 这个错误是没有指定main类，所以类似这样来指定:\n> E:\\codeplace\\n_learn\\java\\javacmd\\build>java -cp h.jar com.yp.test.HelloWorld\n\n\n生成可以运行的jar包\n\n需要指定jar包的应用程序入口点，用-e选项：\n\n    E:\\codeplace\\n_learn\\java\\javacmd\\build> jar cvfe h.jar com.yp.test.HelloWorld *\n    已添加清单\n    正在添加: com/(输入 = 0) (输出 = 0)(存储了 0%)\n    正在添加: com/yp/(输入 = 0) (输出 = 0)(存储了 0%)\n    正在添加: com/yp/test/(输入 = 0) (输出 = 0)(存储了 0%)\n    正在添加: com/yp/test/entity/(输入 = 0) (输出 = 0)(存储了 0%)\n    正在添加: com/yp/test/entity/Cat.class(输入 = 545) (输出 = 319)(压缩了 41%)\n    正在添加: com/yp/test/HelloWorld.class(输入 = 844) (输出 = 487)(压缩了 42%)\n\n直接运行\n\n    java -jar h.jar\n    \n    额外发现 \n    指定了Main类后，jar包里面的 META-INF/MANIFEST.MF 是这样的， 比原来多了一行Main-Class….\n    Manifest-Version: 1.0\n    Created-By: 1.8.0 (Oracle Corporation)\n    Main-Class: com.yp.test.HelloWorld\n\n如果类里有引用jar包呢?\n\n先下一个jar包 这里直接下 log4j \n    \n* main函数改成\n\n````    \nimport com.yp.test.entity.Cat;\nimport org.apache.log4j.Logger;\n\npublic class HelloWorld {\n\n    static Logger log = Logger.getLogger(HelloWorld.class);\n\n    public static void main(String[] args) {\n        Cat c = new Cat(\"keyboard\");\n        log.info(\"这是log4j\");\n        System.out.println(\"hello,\" + c.getName());\n    }\n\n}\n````\n现的文件是这样的\n\n\n````\n├─build\n├─lib\n│      log4j-1.2.17.jar\n│\n└─src\n    └─com\n        └─yp\n            └─test\n                │  HelloWorld.java\n                │\n                └─entity\n                        Cat.java\n````\n\n    这个时候 javac命令要接上 -cp ./lib/*.jar\n    E:\\codeplace\\n_learn\\java\\javacmd>javac -encoding \"utf8\" src/com/yp/test/HelloWorld.java -sourcepath src -d build -g -cp ./lib/*.jar\n\n\n    运行要加上-cp, -cp 选项貌似会把工作目录给换了， 所以要加上 ;../build\n    E:\\codeplace\\n_learn\\java\\javacmd\\build>java -cp ../lib/log4j-1.2.17.jar;../build com.yp.test.HelloWorld\n\n结果:\n\n    log4j:WARN No appenders could be found for logger(com.yp.test.HelloWorld).\n    log4j:WARN Please initialize the log4j system properly.\n    log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.\n    hello,keyboard\n\n由于没有 log4j的配置文件，所以提示上面的问题,往 build 里面加上 log4j.xml\n\n````\n<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<!DOCTYPE log4j:configuration SYSTEM \"log4j.dtd\">\n<log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/'>\n    <appender name=\"stdout\" class=\"org.apache.log4j.ConsoleAppender\">\n        <layout class=\"org.apache.log4j.PatternLayout\">\n            <param name=\"ConversionPattern\" value=\"%d{ABSOLUTE} %-5p [%c{1}] %m%n\" />\n        </layout>\n    </appender>\n\n    <root>\n        <level value=\"info\" />\n        <appender-ref ref=\"stdout\" />\n    </root>\n</log4j:configuration>\n````\n再运行\n\n    E:\\codeplace\\n_learn\\java\\javacmd>java -cp lib/log4j-1.2.17.jar;build com.yp.tes t.HelloWorld\n    15:19:57,359 INFO  [HelloWorld] 这是log4j\n    hello,keyboard\n\n说明: \n这个log4j配置文件，习惯的做法是放在src目录下, 在编译过程中 copy到build中的,但根据ant的做法，不是用javac的，而是用来处理,我猜测javac是不能copy的，如果想在命令行直接 使用，应该是用cp命令主动去执行 copy操作\n\nok 一个简单的java 工程就运行完了\n但是  貌似有些繁琐,  需要手动键入 java文件 以及相应的jar包 很是麻烦,\nso 可以用 shell 来脚本来简化相关操作 \nshell 文件整理如下:\n````\n    #!/bin/bash  \n    echo \"build start\"  \n      \n    JAR_PATH=libs  \n    BIN_PATH=bin  \n    SRC_PATH=src  \n      \n    # java文件列表目录  \n    SRC_FILE_LIST_PATH=src/sources.list  \n      \n    #生所有的java文件列表 放入列表文件中 \n    rm -f $SRC_PATH/sources  \n    find $SRC_PATH/ -name *.java > $SRC_FILE_LIST_PATH  \n      \n    #删除旧的编译文件 生成bin目录  \n    rm -rf $BIN_PATH/  \n    mkdir $BIN_PATH/  \n      \n    #生成依赖jar包 列表  \n    for file in  ${JAR_PATH}/*.jar;  \n    do  \n    jarfile=${jarfile}:${file}  \n    done  \n    echo \"jarfile = \"$jarfile  \n      \n    #编译 通过-cp指定所有的引用jar包，将src下的所有java文件进行编译\n    javac -d $BIN_PATH/ -cp $jarfile @$SRC_FILE_LIST_PATH  \n      \n    #运行 通过-cp指定所有的引用jar包，指定入口函数运行\n    java -cp $BIN_PATH$jarfile com.zuiapps.danmaku.server.Main  \n````\n\n> 有一点需要注意的是,  javac -d $BIN_PATH/ -cp $jarfile @$SRC_FILE_LIST_PATH\n> 在要编译的文件很多时候，一个个敲命令会显得很长，也不方便修改，\n\n> 可以把要编译的源文件列在文件中，在文件名前加@，这样就可以对多个文件进行编译，\n\n> 以上就是吧java文件放到 $SRC_FILE_LIST_PATH 中去了\n\n    编译 :\n         1. 需要编译所有的java文件\n         2. 依赖的java 包都需要加入到 classpath 中去\n         3. 最后设置 编译后的 class 文件存放目录  即 -d bin/\n         4. java文件过多是可以使用  @$SRC_FILE_LIST_PATH 把他们放到一个文件中去\n    运行:\n       1.需要吧 编译时设置的bin目录和 所有jar包加入到 classpath 中去\n\n\n    \n## javap 的使用\n\n> javap是jdk自带的一个工具，可以对代码反编译，也可以查看java编译器生成的字节码。\n>\n> 情况下，很少有人使用javap对class文件进行反编译，因为有很多成熟的反编译工具可以使用，比如jad。但是，javap还可以查看java编译器为我们生成的字节码。通过它，可以对照源代码和字节码，从而了解很多编译器内部的工作。\n>\n> \n>\n> javap命令分解一个class文件，它根据options来决定到底输出什么。如果没有使用options,那么javap将会输出包，类里的protected和public域以及类里的所有方法。javap将会把它们输出在标准输出上。来看这个例子，先编译(javac)下面这个类。\n````\nimport java.awt.*;\nimport java.applet.*;\n \npublic class DocFooter extends Applet {\n        String date;\n        String email;\n \n        public void init() {\n                resize(500,100);\n                date = getParameter(\"LAST_UPDATED\");\n                email = getParameter(\"EMAIL\");\n        }\n}\n````\n在命令行上键入javap DocFooter后，输出结果如下\n\n\nCompiled from \"DocFooter.java\"\n````\npublic class DocFooter extends java.applet.Applet {\n  java.lang.String date;\n  java.lang.String email;\n  public DocFooter();\n  public void init();\n}\n````\n如果加入了-c，即javap -c DocFooter，那么输出结果如下\n\nCompiled from \"DocFooter.java\"\n````\n    public class DocFooter extends java.applet.Applet {\n      java.lang.String date;\n     \n      java.lang.String email;\n     \n      public DocFooter();\n        Code:\n           0: aload_0       \n           1: invokespecial #1                  // Method java/applet/Applet.\"<init>\":()V\n           4: return       \n     \n      public void init();\n        Code:\n           0: aload_0       \n           1: sipush        500\n           4: bipush        100\n           6: invokevirtual #2                  // Method resize:(II)V\n           9: aload_0       \n          10: aload_0       \n          11: ldc           #3                  // String LAST_UPDATED\n          13: invokevirtual #4                  // Method getParameter:(Ljava/lang/String;)Ljava/lang/String;\n          16: putfield      #5                  // Field date:Ljava/lang/String;\n          19: aload_0       \n          20: aload_0       \n          21: ldc           #6                  // String EMAIL\n          23: invokevirtual #4                  // Method getParameter:(Ljava/lang/String;)Ljava/lang/String;\n          26: putfield      #7                  // Field email:Ljava/lang/String;\n          29: return       \n    \n    }\n````\n上面输出的内容就是字节码。\n\n用法摘要\n\n-help 帮助\n-l 输出行和变量的表\n-public 只输出public方法和域\n-protected 只输出public和protected类和成员\n-package 只输出包，public和protected类和成员，这是默认的\n-p -private 输出所有类和成员\n-s 输出内部类型签名\n-c 输出分解后的代码，例如，类中每一个方法内，包含java字节码的指令，\n-verbose 输出栈大小，方法参数的个数\n-constants 输出静态final常量\n总结\n\njavap可以用于反编译和查看编译器编译后的字节码。平时一般用javap -c比较多，该命令用于列出每个方法所执行的JVM指令，并显示每个方法的字节码的实际作用。可以通过字节码和源代码的对比，深入分析java的编译原理，了解和解决各种Java原理级别的问题。\n\n## 参考文章\n\nhttps://blog.csdn.net/Anbernet/article/details/81449390\nhttps://www.cnblogs.com/luobiao320/p/7975442.html\nhttps://www.jianshu.com/p/f7330dbdc051\nhttps://www.jianshu.com/p/6a8997560b05\nhttps://blog.csdn.net/w372426096/article/details/81664431\nhttps://blog.csdn.net/qincidong/article/details/82492140\n\n"
  },
  {
    "path": "docs/Java/basic/string和包装类.md",
    "content": "# 目录\n\n  * [string基础](#string基础)\n    * [Java String 类](#java-string-类)\n    * [创建字符串](#创建字符串)\n    * [StringDemo.java 文件代码：](#stringdemojava-文件代码：)\n  * [String基本用法](#string基本用法)\n    * [创建String对象的常用方法](#创建string对象的常用方法)\n    * [String中常用的方法，用法如图所示，具体问度娘](#string中常用的方法，用法如图所示，具体问度娘)\n    * [三个方法的使用： lenth()   substring()   charAt()](#三个方法的使用：-lenth---substring---charat)\n    * [字符串与byte数组间的相互转换](#字符串与byte数组间的相互转换)\n    * [==运算符和equals之间的区别：](#运算符和equals之间的区别：)\n    * [字符串的不可变性](#字符串的不可变性)\n    * [String的连接](#string的连接)\n    * [String、String builder和String buffer的区别](#string、string-builder和string-buffer的区别)\n  * [String类的源码分析](#string类的源码分析)\n    * [String类型的intern](#string类型的intern)\n    * [String类型的equals](#string类型的equals)\n    * [StringBuffer和Stringbuilder](#stringbuffer和stringbuilder)\n    * [append方法](#append方法)\n    * [扩容](#扩容)\n                                        * [](#)\n    * [删除](#删除)\n    * [system.arraycopy方法](#systemarraycopy方法)\n  * [String和JVM的关系](#string和jvm的关系)\n  * [String为什么不可变？](#string为什么不可变？)\n    * [不可变有什么好处？](#不可变有什么好处？)\n  * [String常用工具类](#string常用工具类)\n  * [参考文章](#参考文章)\n\n\n\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章首发于我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《夯实Java基础系列博文》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n该系列博文会告诉你如何从入门到进阶，一步步地学习Java基础知识，并上手进行实战，接着了解每个Java知识点背后的实现原理，更完整地了解整个Java技术体系，形成自己的知识框架。为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n## string基础\n\n### Java String 类\n\n字符串广泛应用 在 Java 编程中，在 Java 中字符串属于对象，Java 提供了 String 类来创建和操作字符串。\n\n### 创建字符串\n\n创建字符串最简单的方式如下:\n\nString greeting = \"菜鸟教程\";\n\n在代码中遇到字符串常量时，这里的值是 \"**菜鸟教程**\"\"，编译器会使用该值创建一个 String 对象。\n\n和其它对象一样，可以使用关键字和构造方法来创建 String 对象。\n\nString 类有 11 种构造方法，这些方法提供不同的参数来初始化字符串，比如提供一个字符数组参数:\n\n### StringDemo.java 文件代码：\n````\npublic class StringDemo{    \npublic static void main(String args[]){       \nchar[] helloArray = { 'r', 'u', 'n', 'o', 'o', 'b'};       \nString helloString = new String(helloArray);         \nSystem.out.println( helloString );    \n} }\n````\n以上实例编译运行结果如下：\n\n```\nrunoob\n```\n\n**注意:**String 类是不可改变的，所以你一旦创建了 String 对象，那它的值就无法改变了（详看笔记部分解析）。\n\n如果需要对字符串做很多修改，那么应该选择使用 [StringBuffer & StringBuilder 类](https://www.runoob.com/java/java-stringbuffer.html)。\n\n## String基本用法\n\n###  创建String对象的常用方法\n\n(1) String s1 = \"mpptest\"\n\n(2) String s2 = new String();\n\n(3) String s3 = new String(\"mpptest\")\n\n### String中常用的方法，用法如图所示，具体问度娘\n\n![](https://img2018.cnblogs.com/blog/710412/201902/710412-20190213220237169-1966705420.png)\n\n### 三个方法的使用： lenth()   substring()   charAt()\n````\npackage com.mpp.string; \npublic class StringDemo1 {\n    public static void main(String[] args) { //定义一个字符串\"晚来天欲雪 能饮一杯无\"\n        String str = \"晚来天欲雪 能饮一杯无\";\n        System.out.println(\"字符串的长度是：\"+str.length()); //字符串的雪字打印输出  charAt(int index)\n        System.out.println(str.charAt(4)); //取出子串  天欲\n        System.out.println(str.substring(2));   //取出从index2开始直到最后的子串，包含2\n        System.out.println(str.substring(2,4));  //取出index从2到4的子串，包含2不包含4  顾头不顾尾\n    }\n}\n````\n\n\n两个方法的使用,求字符或子串第一次/最后一次在字符串中出现的位置： \nindexOf()   lastIndexOf()  \n\n````\npackage com.mpp.string; public class StringDemo2 { \n    public static void main(String[] args) {\n        String str = new String(\"赵客缦胡缨 吴钩胡缨霜雪明\"); //查找胡在字符串中第一次出现的位置\n        System.out.println(\"\\\"胡\\\"在字符串中第一次出现的位置：\"+str.indexOf(\"胡\")); //查找子串\"胡缨\"在字符串中第一次出现的位置\n        System.out.println(\"\\\"胡缨\\\"在字符串中第一次出现的位置\"+str.indexOf(\"胡缨\")); //查找胡在字符串中最后一次次出现的位置\n        System.out.println(str.lastIndexOf(\"胡\")); //查找子串\"胡缨\"在字符串中最后一次出现的位置\n        System.out.println(str.lastIndexOf(\"胡缨\")); //从indexof为5的位置，找第一次出现的\"吴\"\n        System.out.println(str.indexOf(\"吴\",5));\n    }\n}\n````\n\n\n\n### 字符串与byte数组间的相互转换\n\n````\npackage com.mpp.string; import java.io.UnsupportedEncodingException; \npublic class StringDemo3 { \n    public static void main(String[] args) throws UnsupportedEncodingException { \n        \n        //字符串和byte数组之间的相互转换\n        String str = new String(\"hhhabc银鞍照白马 飒沓如流星\"); //将字符串转换为byte数组，并打印输出\n        byte[] arrs = str.getBytes(\"GBK\"); \n        for(int i=0;i){\n            System.out.print(arrs[i]);\n        } \n        \n        //将byte数组转换成字符串\n        System.out.println();\n        String str1 = new String(arrs,\"GBK\");  //保持字符集的一致，否则会出现乱码\n        System.out.println(str1);\n    }\n}\n````\n\n### ==运算符和equals之间的区别：\n\n引用指向的内容和引用指向的地址\n\n````\npackage com.mpp.string; public class StringDemo5 { \n    public static void main(String[] args) {\n        String str1 = \"mpp\";\n        String str2 = \"mpp\";\n        String str3 = new String(\"mpp\");\n\n        System.out.println(str1.equals(str2)); //true  内容相同\n        System.out.println(str1.equals(str3));   //true  内容相同\n        System.out.println(str1==str2);   //true   地址相同\n        System.out.println(str1==str3);   //false  地址不同\n    }\n}\n````\n\n### 字符串的不可变性\n\nString的对象一旦被创建，则不能修改，是不可变的\n\n所谓的修改其实是创建了新的对象，所指向的内存空间不变\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/blog/Java%E6%8A%80%E6%9C%AF%E6%B1%9F%E6%B9%96/%E4%BA%8C%E7%BB%B4%E7%A0%81/710412-20190214224055939-746946317.png)\n\n上图中，s1不再指向imooc所在的内存空间，而是指向了hello,imooc\n### String的连接\n\n    @Test\n    public void contact () {\n        //1连接方式\n        String s1 = \"a\";\n        String s2 = \"a\";\n        String s3 = \"a\" + s2;\n        String s4 = \"a\" + \"a\";\n        String s5 = s1 + s2;\n        //表达式只有常量时，编译期完成计算\n        //表达式有变量时，运行期才计算，所以地址不一样\n        System.out.println(s3 == s4); //f\n        System.out.println(s3 == s5); //f\n        System.out.println(s4 == \"aa\"); //t\n    \n    }\n### String、String builder和String buffer的区别\nString是Java中基础且重要的类，并且String也是Immutable类的典型实现，被声明为final class，除了hash这个属性其它属性都声明为final,因为它的不可变性，所以例如拼接字符串时候会产生很多无用的中间对象，如果频繁的进行这样的操作对性能有所影响。\n\nStringBuffer就是为了解决大量拼接字符串时产生很多中间对象问题而提供的一个类，提供append和add方法，可以将字符串添加到已有序列的末尾或指定位置，它的本质是一个线程安全的可修改的字符序列，把所有修改数据的方法都加上了synchronized。但是保证了线程安全是需要性能的代价的。\n\n在很多情况下我们的字符串拼接操作不需要线程安全，这时候StringBuilder登场了，StringBuilder是JDK1.5发布的，它和StringBuffer本质上没什么区别，就是去掉了保证线程安全的那部分，减少了开销。\n\nStringBuffer 和 StringBuilder 二者都继承了 AbstractStringBuilder ，底层都是利用可修改的char数组(JDK 9 以后是 byte数组)。\n\n所以如果我们有大量的字符串拼接，如果能预知大小的话最好在new StringBuffer 或者StringBuilder 的时候设置好capacity，避免多次扩容的开销。扩容要抛弃原有数组，还要进行数组拷贝创建新的数组。\n\n我们平日开发通常情况下少量的字符串拼接其实没太必要担心，例如\n\nString str = \"aa\"+\"bb\"+\"cc\";\n\n像这种没有变量的字符串，编译阶段就直接合成\"aabbcc\"了，然后看字符串常量池（下面会说到常量池）里有没有，有也直接引用，没有就在常量池中生成，返回引用。\n\n如果是带变量的，其实影响也不大，JVM会帮我们优化了。\n\n> 1、在字符串不经常发生变化的业务场景优先使用String(代码更清晰简洁)。如常量的声明，少量的字符串操作(拼接，删除等)。\n>\n> 2、在单线程情况下，如有大量的字符串操作情况，应该使用StringBuilder来操作字符串。不能使用String\"+\"来拼接而是使用，避免产生大量无用的中间对象，耗费空间且执行效率低下（新建对象、回收对象花费大量时间）。如JSON的封装等。\n>\n> 3、在多线程情况下，如有大量的字符串操作情况，应该使用StringBuffer。如HTTP参数解析和封装等。\n\n## String类的源码分析\n\n### String类型的intern\n````\npublic void intern () {\n    //2：string的intern使用\n    //s1是基本类型，比较值。s2是string实例，比较实例地址\n    //字符串类型用equals方法比较时只会比较值\n    String s1 = \"a\";\n    String s2 = new String(\"a\");\n    //调用intern时,如果s2中的字符不在常量池，则加入常量池并返回常量的引用\n    String s3 = s2.intern();\n    System.out.println(s1 == s2);\n    System.out.println(s1 == s3);\n}\n````\n### String类型的equals\n````\n//字符串的equals方法\n//    public boolean equals(Object anObject) {\n//            if (this == anObject) {\n//                return true;\n//            }\n//            if (anObject instanceof String) {\n//                String anotherString = (String)anObject;\n//                int n = value.length;\n//                if (n == anotherString.value.length) {\n//                    char v1[] = value;\n//                    char v2[] = anotherString.value;\n//                    int i = 0;\n//                    while (n-- != 0) {\n//                        if (v1[i] != v2[i])\n//                            return false;\n//                        i++;\n//                    }\n//                    return true;\n//                }\n//            }\n//            return false;\n//        }\n````\n### StringBuffer和Stringbuilder\n底层是继承父类的可变字符数组value\n````\n/**\n\n- The value is used for character storage.\n  */\n  char[] value;\n  初始化容量为16\n\n/**\n\n- Constructs a string builder with no characters in it and an\n- initial capacity of 16 characters.\n  */\n  public StringBuilder() {\n  super(16);\n  }\n  这两个类的append方法都是来自父类AbstractStringBuilder的方法\n\npublic AbstractStringBuilder append(String str) {\n    if (str == null)\n        return appendNull();\n    int len = str.length();\n    ensureCapacityInternal(count + len);\n    str.getChars(0, len, value, count);\n    count += len;\n    return this;\n}\n@Override\npublic StringBuilder append(String str) {\n    super.append(str);\n    return this;\n}\n\n@Override\npublic synchronized StringBuffer append(String str) {\n    toStringCache = null;\n    super.append(str);\n    return this;\n}\n````\n\n### append方法\nStringbuffer在大部分涉及字符串修改的操作上加了synchronized关键字来保证线程安全，效率较低。\n\nString类型在使用 + 运算符例如\n\nString a = \"a\"\n\na = a + a;时，实际上先把a封装成stringbuilder，调用append方法后再用tostring返回，所以当大量使用字符串加法时，会大量地生成stringbuilder实例，这是十分浪费的，这种时候应该用stringbuilder来代替string。\n\n### 扩容\n#注意在append方法中调用到了一个函数\n\nensureCapacityInternal(count + len);\n该方法是计算append之后的空间是否足够，不足的话需要进行扩容\n````\npublic void ensureCapacity(int minimumCapacity) {\n    if (minimumCapacity > 0)\n        ensureCapacityInternal(minimumCapacity);\n}\nprivate void ensureCapacityInternal(int minimumCapacity) {\n    // overflow-conscious code\n    if (minimumCapacity - value.length > 0) {\n        value = Arrays.copyOf(value,\n                newCapacity(minimumCapacity));\n    }\n}\n````\n如果新字符串长度大于value数组长度则进行扩容\n\n扩容后的长度一般为原来的两倍 + 2；\n\n假如扩容后的长度超过了jvm支持的最大数组长度MAX_ARRAY_SIZE。\n\n考虑两种情况\n\n如果新的字符串长度超过int最大值，则抛出异常，否则直接使用数组最大长度作为新数组的长度。\n````\nprivate int hugeCapacity(int minCapacity) {\n    if (Integer.MAX_VALUE - minCapacity < 0) { // overflow\n        throw new OutOfMemoryError();\n    }\n    return (minCapacity > MAX_ARRAY_SIZE)\n        ? minCapacity : MAX_ARRAY_SIZE;\n}\n````\n### 删除\n这两个类型的删除操作：\n\n都是调用父类的delete方法进行删除\n````\npublic AbstractStringBuilder delete(int start, int end) {\n    if (start < 0)\n        throw new StringIndexOutOfBoundsException(start);\n    if (end > count)\n        end = count;\n    if (start > end)\n        throw new StringIndexOutOfBoundsException();\n    int len = end - start;\n    if (len > 0) {\n        System.arraycopy(value, start+len, value, start, count-end);\n        count -= len;\n    }\n    return this;\n}\n````\n事实上是将剩余的字符重新拷贝到字符数组value。\n\n这里用到了system.arraycopy来拷贝数组，速度是比较快的\n\n### system.arraycopy方法\n转自知乎：\n\n> 在主流高性能的JVM上（HotSpot VM系、IBM J9 VM系、JRockit系等等），可以认为System.arraycopy()在拷贝数组时是可靠高效的——如果发现不够高效的情况，请报告performance bug，肯定很快就会得到改进。\n>\n> java.lang.System.arraycopy()方法在Java代码里声明为一个native方法。所以最naïve的实现方式就是通过JNI调用JVM里的native代码来实现。\n>\n> String的不可变性\n> 关于String的不可变性，这里转一个不错的回答\n>\n> 什么是不可变？\n> String不可变很简单，如下图，给一个已有字符串\"abcd\"第二次赋值成\"abcedl\"，不是在原内存地址上修改数据，而是重新指向一个新对象，新地址。\n>\n\n## String和JVM的关系\n\n下面我们了解下Java栈、Java堆、方法区和常量池：\n\nJava栈（线程私有数据区）：\n\n```\n    每个Java虚拟机线程都有自己的Java虚拟机栈，Java虚拟机栈用来存放栈帧，每个方法被执行的时候都会同时创建一个栈帧（Stack Frame）用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程，就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。\n```\n\nJava堆（线程共享数据区）：\n\n```\n   在虚拟机启动时创建，此内存区域的唯一目的就是存放对象实例，几乎所有的对象实例都在这里分配。\n```\n\n方法区（线程共享数据区）：\n\n```\n   方法区在虚拟机启动的时候被创建，它存储了每一个类的结构信息，例如运行时常量池、字段和方法数据、构造函数和普通方法的字节码内容、还包括在类、实例、接口初始化时用到的特殊方法。在JDK8之前永久代是方法区的一种实现，而JDK8元空间替代了永久代，永久代被移除，也可以理解为元空间是方法区的一种实现。\n```\n\n常量池（线程共享数据区）：\n\n```\n    常量池常被分为两大类：静态常量池和运行时常量池。\n\n    静态常量池也就是Class文件中的常量池，存在于Class文件中。\n\n    运行时常量池（Runtime Constant Pool）是方法区的一部分，存放一些运行时常量数据。\n```\n\n下面重点了解的是字符串常量池：\n\n```\n    字符串常量池存在运行时常量池之中（在JDK7之前存在运行时常量池之中，在JDK7已经将其转移到堆中）。\n\n    字符串常量池的存在使JVM提高了性能和减少了内存开销。\n\n    使用字符串常量池，每当我们使用字面量（String s=”1”;）创建字符串常量时，JVM会首先检查字符串常量池，如果该字符串已经存在常量池中，那么就将此字符串对象的地址赋值给引用s（引用s在Java栈中）。如果字符串不存在常量池中，就会实例化该字符串并且将其放到常量池中，并将此字符串对象的地址赋值给引用s（引用s在Java栈中）。\n```\n\n```\n    使用字符串常量池，每当我们使用关键字new（String s=new String(”1”);）创建字符串常量时，JVM会首先检查字符串常量池，如果该字符串已经存在常量池中，那么不再在字符串常量池创建该字符串对象，而直接堆中复制该对象的副本，然后将堆中对象的地址赋值给引用s，如果字符串不存在常量池中，就会实例化该字符串并且将其放到常量池中，然后在堆中复制该对象的副本，然后将堆中对象的地址赋值给引用s。\n```\n\n## String为什么不可变？\n翻开JDK源码，java.lang.String类起手前三行，是这样写的：\n````\npublic final class String implements java.io.Serializable, Comparable<String>, CharSequence {   \n  /** String本质是个char数组. 而且用final关键字修饰.*/     \nprivate final char value[];  ...  ...\n } \n````\n首先String类是用final关键字修饰，这说明String不可继承。再看下面，String类的主力成员字段value是个char[]数组，而且是用final修饰的。\n\nfinal修饰的字段创建以后就不可改变。 有的人以为故事就这样完了，其实没有。因为虽然value是不可变，也只是value这个引用地址不可变。挡不住Array数组是可变的事实。\n\nArray的数据结构看下图。\n\n也就是说Array变量只是stack上的一个引用，数组的本体结构在heap堆。\n\nString类里的value用final修饰，只是说stack里的这个叫value的引用地址不可变。没有说堆里array本身数据不可变。看下面这个例子，\n````\nfinal int[] value={1,2,3} ；\nint[] another={4,5,6};\n value=another;    //编译器报错，final不可变 value用final修饰，编译器不允许我把value指向堆区另一个地址。\n但如果我直接对数组元素动手，分分钟搞定。\n\n final int[] value={1,2,3};\n value[2]=100;  //这时候数组里已经是{1,2,100}   所以String是不可变，关键是因为SUN公司的工程师。\n 在后面所有String的方法里很小心的没有去动Array里的元素，没有暴露内部成员字段。private final char value[]这一句里，private的私有访问权限的作用都比final大。而且设计师还很小心地把整个String设成final禁止继承，避免被其他人继承后破坏。所以String是不可变的关键都在底层的实现，而不是一个final。考验的是工程师构造数据类型，封装数据的功力。 \n````\n### 不可变有什么好处？\n这个最简单地原因，就是为了安全。看下面这个场景（有评论反应例子不够清楚，现在完整地写出来），一个函数appendStr( )在不可变的String参数后面加上一段“bbb”后返回。appendSb( )负责在可变的StringBuilder后面加“bbb”。\n\n总结以下String的不可变性。\n\n> 1 首先final修饰的类只保证不能被继承，并且该类的对象在堆内存中的地址不会被改变。\n>\n> 2 但是持有String对象的引用本身是可以改变的，比如他可以指向其他的对象。\n>\n> 3 final修饰的char数组保证了char数组的引用不可变。但是可以通过char[0] = 'a’来修改值。不过String内部并不提供方法来完成这一操作，所以String的不可变也是基于代码封装和访问控制的。\n\n举个例子\n````\nfinal class Fi {\n    int a;\n    final int b = 0;\n    Integer s;\n\n}\nfinal char[]a = {'a'};\nfinal int[]b = {1};\n@Test\npublic void final修饰类() {\n    //引用没有被final修饰，所以是可变的。\n    //final只修饰了Fi类型，即Fi实例化的对象在堆中内存地址是不可变的。\n    //虽然内存地址不可变，但是可以对内部的数据做改变。\n    Fi f = new Fi();\n    f.a = 1;\n    System.out.println(f);\n    f.a = 2;\n    System.out.println(f);\n    //改变实例中的值并不改变内存地址。\n````\n````\nFi ff = f;\n//让引用指向新的Fi对象，原来的f对象由新的引用ff持有。\n//引用的指向改变也不会改变原来对象的地址\nf = new Fi();\nSystem.out.println(f);\nSystem.out.println(ff);\n\n}\n````\n\n这里的对f.a的修改可以理解为char[0] = 'a'这样的操作。只改变数据值，不改变内存值。\n\n## String常用工具类\n问题描述\n很多时候我们需要对字符串进行很多固定的操作,而这些操作在JDK/JRE中又没有预置,于是我们想到了apache-commons组件,但是它也不能完全覆盖我们的业务需求,所以很多时候还是要自己写点代码的,下面就是基于apache-commons组件写的部分常用方法:\n\n\n````\n    <dependency>\n    \t<groupId>org.apache.commons</groupId>\n    \t<artifactId>commons-lang3</artifactId>\n    \t<version>${commons-lang3.version}</version>\n     </dependency>\n````\n\n代码成果\n\n````\npublic class StringUtils extends org.apache.commons.lang3.StringUtils {\n\n/** 值为\"NULL\"的字符串 */\nprivate static final String NULL_STRING = \"NULL\";\n\nprivate static final char SEPARATOR = '_';\n\n\n/**\n * 满足一下情况返回true<br/>\n * ①.入参为空\n * ②.入参为空字符串\n * ③.入参为\"null\"字符串\n *\n * @param string 需要判断的字符型\n * @return boolean\n */\npublic static boolean isNullOrEmptyOrNULLString(String string) {\n    return isBlank(string) || NULL_STRING.equalsIgnoreCase(string);\n}\n\n/**\n * 把字符串转为二进制码<br/>\n * 本方法不会返回null\n *\n * @param str 需要转换的字符串\n * @return 二进制字节码数组\n */\npublic static byte[] toBytes(String str) {\n    return isBlank(str) ? new byte[]{} : str.getBytes();\n}\n\n/**\n * 把字符串转为二进制码<br/>\n * 本方法不会返回null\n *\n * @param str     需要转换的字符串\n * @param charset 编码类型\n * @return 二进制字节码数组\n * @throws UnsupportedEncodingException 字符串转换的时候编码不支持时出现\n */\npublic static byte[] toBytes(String str, Charset charset) throws UnsupportedEncodingException {\n    return isBlank(str) ? new byte[]{} : str.getBytes(charset.displayName());\n}\n\n/**\n * 把字符串转为二进制码<br/>\n * 本方法不会返回null\n *\n * @param str     需要转换的字符串\n * @param charset 编码类型\n * @param locale  编码类型对应的地区\n * @return 二进制字节码数组\n * @throws UnsupportedEncodingException 字符串转换的时候编码不支持时出现\n */\npublic static byte[] toBytes(String str, Charset charset, Locale locale) throws UnsupportedEncodingException {\n    return isBlank(str) ? new byte[]{} : str.getBytes(charset.displayName(locale));\n}\n\n/**\n * 二进制码转字符串<br/>\n * 本方法不会返回null\n *\n * @param bytes 二进制码\n * @return 字符串\n */\npublic static String bytesToString(byte[] bytes) {\n    return bytes == null || bytes.length == 0 ? EMPTY : new String(bytes);\n}\n\n/**\n * 二进制码转字符串<br/>\n * 本方法不会返回null\n *\n * @param bytes   二进制码\n * @param charset 编码集\n * @return 字符串\n * @throws UnsupportedEncodingException 当前二进制码可能不支持传入的编码\n */\npublic static String byteToString(byte[] bytes, Charset charset) throws UnsupportedEncodingException {\n    return bytes == null || bytes.length == 0 ? EMPTY : new String(bytes, charset.displayName());\n}\n\n/**\n * 二进制码转字符串<br/>\n * 本方法不会返回null\n *\n * @param bytes   二进制码\n * @param charset 编码集\n * @param locale  本地化\n * @return 字符串\n * @throws UnsupportedEncodingException 当前二进制码可能不支持传入的编码\n */\npublic static String byteToString(byte[] bytes, Charset charset, Locale locale) throws UnsupportedEncodingException {\n    return bytes == null || bytes.length == 0 ? EMPTY : new String(bytes, charset.displayName(locale));\n}\n\n/**\n * 把对象转为字符串\n *\n * @param object 需要转化的字符串\n * @return 字符串, 可能为空\n */\npublic static String parseString(Object object) {\n    if (object == null) {\n        return null;\n    }\n    if (object instanceof byte[]) {\n        return bytesToString((byte[]) object);\n    }\n    return object.toString();\n}\n\n/**\n * 把字符串转为int类型\n *\n * @param str 需要转化的字符串\n * @return int\n * @throws NumberFormatException 字符串格式不正确时抛出\n */\npublic static int parseInt(String str) throws NumberFormatException {\n    return isBlank(str) ? 0 : Integer.parseInt(str);\n}\n\n/**\n * 把字符串转为double类型\n *\n * @param str 需要转化的字符串\n * @return double\n * @throws NumberFormatException 字符串格式不正确时抛出\n */\npublic static double parseDouble(String str) throws NumberFormatException {\n    return isBlank(str) ? 0D : Double.parseDouble(str);\n}\n\n/**\n * 把字符串转为long类型\n *\n * @param str 需要转化的字符串\n * @return long\n * @throws NumberFormatException 字符串格式不正确时抛出\n */\npublic static long parseLong(String str) throws NumberFormatException {\n    return isBlank(str) ? 0L : Long.parseLong(str);\n}\n\n/**\n * 把字符串转为float类型\n *\n * @param str 需要转化的字符串\n * @return float\n * @throws NumberFormatException 字符串格式不正确时抛出\n */\npublic static float parseFloat(String str) throws NumberFormatException {\n    return isBlank(str) ? 0L : Float.parseFloat(str);\n}\n\n/**\n * 获取i18n字符串\n *\n * @param code\n * @param args\n * @return\n */\npublic static String getI18NMessage(String code, Object[] args) {\n    //LocaleResolver localLocaleResolver = (LocaleResolver) SpringContextHolder.getBean(LocaleResolver.class);\n    //HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();\n    //Locale locale = localLocaleResolver.resolveLocale(request);\n    //return SpringContextHolder.getApplicationContext().getMessage(code, args, locale);\n    return \"\";\n}\n\n/**\n * 获得用户远程地址\n *\n * @param request 请求头\n * @return 用户ip\n */\npublic static String getRemoteAddr(HttpServletRequest request) {\n    String remoteAddr = request.getHeader(\"X-Real-IP\");\n    if (isNotBlank(remoteAddr)) {\n        remoteAddr = request.getHeader(\"X-Forwarded-For\");\n    } else if (isNotBlank(remoteAddr)) {\n        remoteAddr = request.getHeader(\"Proxy-Client-IP\");\n    } else if (isNotBlank(remoteAddr)) {\n        remoteAddr = request.getHeader(\"WL-Proxy-Client-IP\");\n    }\n    return remoteAddr != null ? remoteAddr : request.getRemoteAddr();\n}\n\n/**\n * 驼峰命名法工具\n *\n * @return toCamelCase(\" hello_world \") == \"helloWorld\"\n * toCapitalizeCamelCase(\"hello_world\") == \"HelloWorld\"\n * toUnderScoreCase(\"helloWorld\") = \"hello_world\"\n */\npublic static String toCamelCase(String s, Locale locale, char split) {\n    if (isBlank(s)) {\n        return \"\";\n    }\n\n    s = s.toLowerCase(locale);\n\n    StringBuilder sb = new StringBuilder();\n    for (char c : s.toCharArray()) {\n        sb.append(c == split ? Character.toUpperCase(c) : c);\n    }\n\n    return sb.toString();\n}\n\npublic static String toCamelCase(String s) {\n    return toCamelCase(s, Locale.getDefault(), SEPARATOR);\n}\n\npublic static String toCamelCase(String s, Locale locale) {\n    return toCamelCase(s, locale, SEPARATOR);\n}\n\npublic static String toCamelCase(String s, char split) {\n    return toCamelCase(s, Locale.getDefault(), split);\n}\n\npublic static String toUnderScoreCase(String s, char split) {\n    if (isBlank(s)) {\n        return \"\";\n    }\n\n    StringBuilder sb = new StringBuilder();\n    for (int i = 0; i < s.length(); i++) {\n        char c = s.charAt(i);\n        boolean nextUpperCase = (i < (s.length() - 1)) && Character.isUpperCase(s.charAt(i + 1));\n        boolean upperCase = (i > 0) && Character.isUpperCase(c);\n        sb.append((!upperCase || !nextUpperCase) ? split : \"\").append(Character.toLowerCase(c));\n    }\n\n    return sb.toString();\n}\n\npublic static String toUnderScoreCase(String s) {\n    return toUnderScoreCase(s, SEPARATOR);\n}\n\n/**\n * 把字符串转换为JS获取对象值的三目运算表达式\n *\n * @param objectString 对象串\n * 例如：入参:row.user.id/返回：!row?'':!row.user?'':!row.user.id?'':row.user.id\n */\npublic static String toJsGetValueExpression(String objectString) {\n    StringBuilder result = new StringBuilder();\n    StringBuilder val = new StringBuilder();\n    String[] fileds = split(objectString, \".\");\n    for (int i = 0; i < fileds.length; i++) {\n        val.append(\".\" + fileds[i]);\n        result.append(\"!\" + (val.substring(1)) + \"?'':\");\n    }\n    result.append(val.substring(1));\n    return result.toString();\n}\n\n\n}\n````\n\n## 参考文章\nhttps://blog.csdn.net/qq_34490018/article/details/82110578\nhttps://www.runoob.com/java/java-string.html\nhttps://www.cnblogs.com/zhangyinhua/p/7689974.html\nhttps://blog.csdn.net/sinat_21925975/article/details/86493248\nhttps://www.cnblogs.com/niew/p/9597379.html\n\n"
  },
  {
    "path": "docs/Java/basic/代码块和代码执行顺序.md",
    "content": "# 目录\n\n  * [Java中的构造方法](#java中的构造方法)\n    * [构造方法简介](#构造方法简介)\n    * [构造方法实例](#构造方法实例)\n      * [例 1](#例-1)\n      * [例 2](#例-2)\n  * [Java中的几种构造方法详解](#java中的几种构造方法详解)\n    * [普通构造方法](#普通构造方法)\n    * [默认构造方法](#默认构造方法)\n    * [重载构造方法](#重载构造方法)\n    * [java子类构造方法调用父类构造方法](#java子类构造方法调用父类构造方法)\n  * [Java中的代码块简介](#java中的代码块简介)\n  * [Java代码块使用](#java代码块使用)\n    * [局部代码块](#局部代码块)\n    * [构造代码块](#构造代码块)\n    * [静态代码块](#静态代码块)\n  * [Java代码块、构造方法（包含继承关系）的执行顺序](#java代码块、构造方法（包含继承关系）的执行顺序)\n  * [参考文章](#参考文章)\n\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章首发于我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《夯实Java基础系列博文》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n该系列博文会告诉你如何从入门到进阶，一步步地学习Java基础知识，并上手进行实战，接着了解每个Java知识点背后的实现原理，更完整地了解整个Java技术体系，形成自己的知识框架。为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n\n<!-- more -->\n\n## Java中的构造方法\n### 构造方法简介\n\n构造方法是类的一种特殊方法，用来初始化类的一个新的对象。[Java](http://c.biancheng.net/java/)中的每个类都有一个默认的构造方法，它必须具有和类名相同的名称，而且没有返回类型。构造方法的默认返回类型就是对象类型本身，并且构造方法不能被 static、final、synchronized、abstract 和 native 修饰。\n\n提示：构造方法用于初始化一个新对象，所以用 static 修饰没有意义；构造方法不能被子类继承，所以用 final 和 abstract 修饰没有意义；多个线程不会同时创建内存地址相同的同一个对象，所以用 synchronized 修饰没有必要。\n\n构造方法的语法格式如下：\n````\nclass class_name\n{\n    public class_name(){}    //默认无参构造方法\n    public ciass_name([paramList]){}    //定义构造方法\n    …\n    //类主体\n}\n````\n在一个类中，与类名相同的方法就是构造方法。每个类可以具有多个构造方法，但要求它们各自包含不同的方法参数。\n\n### 构造方法实例\n\n#### 例 1\n\n构造方法主要有无参构造方法和有参构造方法两种，示例如下：\n````\npublic class MyClass\n{\n    private int m;    //定义私有变量\n    MyClass()\n    {\n        //定义无参的构造方法\n        m=0;\n    }\n    MyCiass(int m)\n    {\n        //定义有参的构造方法\n        this.m=m;\n    }\n}\n````\n该示例定义了两个构造方法，分别是无参构造方法和有参构造方法。在一个类中定义多个具有不同参数的同名方法，这就是方法的重载。这两个构造方法的名称都与类名相同，均为 MyClass。在实例化该类时可以调用不同的构造方法进行初始化。\n\n注意：类的构造方法不是要求必须定义的。如果在类中没有定义任何一个构造方法，则 Java 会自动为该类生成一个默认的构造方法。默认的构造方法不包含任何参数，并且方法体为空。如果类中显式地定义了一个或多个构造方法，则 Java 不再提供默认构造方法。\n\n#### 例 2\n\n要在不同的条件下使用不同的初始化行为创建类的对象，这时候就需要在一个类中创建多个构造方法。下面通过一个示例来演示构造方法的使用。\n\n(1) 首先在员工类 Worker 中定义两个构造方法，代码如下：\n````\npublic class Worker\n{\n    public String name;    //姓名\n    private int age;    //年龄\n    //定义带有一个参数的构造方法\n    public Worker(String name)\n    {\n        this.name=name;\n    }\n    //定义带有两个参数的构造方法\n    public Worker(String name,int age)\n    {\n        this.name=name;\n        this.age=age;\n    }\n    public String toString()\n    {\n        return\"大家好！我是新来的员工，我叫\"+name+\"，今年\"+age+\"岁。\";\n    }\n}\n````\n在 Worker 类中定义了两个属性，其中 name 属性不可改变。分别定义了带有一个参数和带有两个参数的构造方法，并对其属性进行初始化。最后定义了该类的 toString() 方法，返回一条新进员工的介绍语句。\n\n提示：Object 类具有一个 toString() 方法，该方法是个特殊的方法，创建的每个类都会继承该方法，它返回一个 String 类型的字符串。如果一个类中定义了该方法，则在调用该类对象时，将会自动调用该类对象的 toString() 方法返回一个字符串，然后使用“System.out.println(对象名)”就可以将返回的字符串内容打印出来。\n\n(2) 在 TestWorker 类中创建 main() 方法作为程序的入口处，在 main() 方法中调用不同的构造方法实例化 Worker 对象，并对该对象中的属性进行初始化，代码如下：\n````\npublic class TestWorker\n{\n    public static void main(String[] args)\n    {\n        System.out.println(\"-----------带有一个参数的构造方法-----------\");\n        //调用带有一个参数的构造方法，Staff类中的sex和age属性值不变\n        Worker worker1=new Worker(\"张强\");\n        System.out.println(worker1);\n        System.out.println(\"-----------带有两个参数的构造方法------------\");\n        //调用带有两个参数的构造方法，Staff类中的sex属性值不变\n        Worker worker2=new Worker(\"李丽\",25);\n        System.out.println(worker2);\n    }\n}\n````\n在上述代码中，创建了两个不同的 Worker 对象：一个是姓名为张强的员工对象，一个是姓名为李丽、年龄为 25 的员工对象。对于第一个 Worker 对象 Worker1，并未指定 age 属性值，因此程序会将其值采用默认值 0。对于第二个 Worker 对象 Worker2，分别对其指定了 name 属性值和 age 属性值，因此程序会将传递的参数值重新赋值给 Worker 类中的属性值。\n\n运行 TestWorker 类，输出的结果如下：\n\n    -----------带有一个参数的构造方法-----------\n    大家好！我是新来的员工，我叫张强，今年0岁。\n    -----------带有两个参数的构造方法------------\n    大家好！我是新来的员工，我叫李丽，今年25岁。\n\n通过调用带参数的构造方法，在创建对象时，一并完成了对象成员的初始化工作，简化了对象初始化的代码。\n\n## Java中的几种构造方法详解\n### 普通构造方法\n\n方法名与类名相同\n\n无返回类型\n\n子类不能继承父类的构造方法\n\n不能被static、final、abstract修饰（有final和static修饰的是不能被子类继承的，abstract修饰的是抽象类，抽象类是不能实例化的，也就是不能new）\n\n可以被private修饰，可以在本类里面实例化，但是外部不能实例化对象（注意！！！）\n````\n    public class A{\n        int i=0;\n        public A(){\n            i=2;\n        }\n        public A(int i){\n            this.i=i;\n        }\n    }\n````\n### 默认构造方法\n如果没有任何的构造方法，编译时系统会自动添加一个默认无参构造方法\n\n隐含的默认构造方法\n````\n        public A(){}\n````\n显示的默认构造方法\n````\n        public A(){\n        System.out.print(\"显示的默认构造方法\")\n        }\n````\n\n### 重载构造方法\n比如原本的类里的构造方法是一个参数的，现在新建的对象是有三个参数，此时就要重载构造方法\n\n当一个类中有多个构造方法，有可能会出现重复性操作，这时可以用this语句调用其他的构造方法。\n````\n    public class A{\n        private int age;\n        private String name;\n        public A(int age,String name){\n            this.age=age;\n            this.name=name;\n        }\n        public A(int age){\n            this(age,\"无名氏\");//调用 A(int age,String name)构造方法\n        }\n        public A(){\n            this(1);//调用 A(int age)构造方法\n        }\n        public void setName(String name) {this.name=name;}\n        public String getName() {return name;}\n        public void setAge(int age) {this.age=age;}\n        public int getAge() {return age;}\n    }\n    \n    A a=new A(20,\"周一\");\n    A b=new A(20);\n    A c=new A();\n    String name = a.getName();\n    String name1 = b.getName();\n    int age = c.getAge();\n    System.out.println(name);\n    System.out.println(name1);\n    System.out.println(age);\n````\n\n### java子类构造方法调用父类构造方法\n\n首先父类构造方法是绝对不能被子类继承的。\n\n子类构造方法调用父类的构造方法重点是：子类构造方法无论如何都要调用父类的构造方法。\n\n子类构造方法要么调用父类无参构造方法（包括当父类没有构造方法时。系统默认给的无参构造方法），要么调用父类有参构造方法。当子类构造方法调用父类无参构造方法，一般都是默认不写的，要写的话就是super（），且要放在构造方法的第一句。当子类构造方法要调用父类有参数的构造方法，那么子类的构造方法中必须要用super（参数）调用父类构造方法，且要放在构造方法的第一句。\n\n当子类的构造方法是无参构造方法时，必须调用父类无参构造方法。因为系统会自动找父类有没有无参构造方法，如果没有的话系统会报错：说父类没有定义无参构造方法。\n\n当子类构造方法是有参构造方法时，这时就会有两种情况。\n第一种：子类构造方法没有写super，也就是说你默认调用父类无参构造方法，这样的话就和子类是无参构造方法一样。\n\n第二种：子类构造方法有super（参数）时，就是调用父类有参构造方法，系统会找父类有没有参数一致（参数数量，且类型顺序要相同）的有参构造方法，如果没有的话，同样也会报错。\n\n但是这里会遇到和重载构造方法this一样问题，一个参数的构造方法可以调用多个参数构造方法，没有的参数给一个自己定义值也是可以的。\n\n## Java中的代码块简介\n\n在java中用{}括起来的称为代码块，代码块可分为以下四种：\n\n**一.简介**\n\n**1.普通代码块：**\n\n类中方法的方法体\n\n**2.构造代码块**：\n\n构造块会在创建对象时被调用，每次创建时都会被调用，优先于类构造函数执行。\n\n**3.静态代码块：**\n\n用static{}包裹起来的代码片段，只会执行一次。静态代码块优先于构造块执行。\n\n**4.同步代码块：**\n\n使用synchronized（）{}包裹起来的代码块，在多线程环境下，对共享数据的读写操作是需要互斥进行的，否则会导致数据的不一致性。同步代码块需要写在方法中。\n\n**二.静态代码块和构造代码块的异同点**\n\n相同点：都是JVM加载类后且在构造函数执行之前执行，在类中可定义多个，一般在代码块中对一些static变量进行赋值。\n\n不同点：静态代码块在非静态代码块之前执行。静态代码块只在第一次new时执行一次，之后不在执行。而非静态代码块每new一次就执行一次。\n\n## Java代码块使用\n\n### 局部代码块\n\n> 位置：局部位置（方法内部）\n\n> 作用：限定变量的生命周期，尽早释放，节约内存\n\n> 调用：调用其所在的方法时执行\n\n     \n````\npublic class 局部代码块 {\n    @Test\n    public void test (){\n        B b = new B();\n        b.go();\n    }\n    }\n    class B {\n        B(){}\n        public void go() {\n            //方法中的局部代码块，一般进行一次性地调用，调用完立刻释放空间，避免在接下来的调用过程中占用栈空间\n            //因为栈空间内存是有限的，方法调用可能会会生成很多局部变量导致栈内存不足。\n            //使用局部代码块可以避免这样的情况发生。\n            {\n                int i = 1;\n                ArrayList<Integer> list = new ArrayList<>();\n                while (i < 1000) {\n                    list.add(i ++);\n                }\n                for (Integer j : list) {\n                    System.out.println(j);\n                }\n                System.out.println(\"gogogo\");\n            }\n            System.out.println(\"hello\");\n        }\n    }\n````\n### 构造代码块\n\n >位置：类成员的位置，就是类中方法之外的位置\n\n >作用：把多个构造方法共同的部分提取出来，共用构造代码块\n\n >调用：每次调用构造方法时，都会优先于构造方法执行，也就是每次new一个对象时自动调用，对  对象的初始化\n````\n    class A{\n        int i = 1;\n        int initValue;//成员变量的初始化交给代码块来完成\n        {\n            //代码块的作用体现于此：在调用构造方法之前，用某段代码对成员变量进行初始化。\n            //而不是在构造方法调用时再进行。一般用于将构造方法的相同部分提取出来。\n            //\n            for (int i = 0;i < 100;i ++) {\n                initValue += i;\n            }\n        }\n        {\n            System.out.println(initValue);\n            System.out.println(i);//此时会打印1\n            int i = 2;//代码块里的变量和成员变量不冲突，但会优先使用代码块的变量\n            System.out.println(i);//此时打印2\n            //System.out.println(j);//提示非法向后引用，因为此时j的的初始化还没开始。\n            //\n        }\n        {\n            System.out.println(\"代码块运行\");\n        }\n        int j = 2;\n        {\n            System.out.println(j);\n            System.out.println(i);//代码块中的变量运行后自动释放，不会影响代码块之外的代码\n        }\n        A(){\n            System.out.println(\"构造方法运行\");\n        }\n    }\n    public class 构造代码块 {\n        @Test\n        public void test() {\n            A a = new A();\n        }\n    }\n````\n### 静态代码块\n\n     位置：类成员位置，用static修饰的代码块\n    \n     作用：对类进行一些初始化  只加载一次，当new多个对象时，只有第一次会调用静态代码块，因为，静态代码块                  是属于类的，所有对象共享一份\n    \n     调用: new 一个对象时自动调用\n\n ````   \n     public class 静态代码块 {\n    \n    @Test\n    public void test() {\n        C c1 = new C();\n        C c2 = new C();\n        //结果,静态代码块只会调用一次，类的所有对象共享该代码块\n        //一般用于类的全局信息初始化\n        //静态代码块调用\n        //代码块调用\n        //构造方法调用\n        //代码块调用\n        //构造方法调用\n    }\n    \n    }\n    class C{\n        C(){\n            System.out.println(\"构造方法调用\");\n        }\n        {\n            System.out.println(\"代码块调用\");\n        }\n        static {\n            System.out.println(\"静态代码块调用\");\n        }\n    }\n````\n## Java代码块、构造方法（包含继承关系）的执行顺序\n\n这是一道常见的面试题，要回答这个问题，先看看这个实例吧。\n\n一共3个类：A、B、C \n其中A是B的父类，C无继承仅作为输出\n\nA类：\n````\n    public class A {\n    \n    static {\n        Log.i(\"HIDETAG\", \"A静态代码块\");\n    }\n    \n    private static C c = new C(\"A静态成员\");\n    private  C c1 = new C(\"A成员\");\n    \n    {\n        Log.i(\"HIDETAG\", \"A代码块\");\n    }\n    \n    static {\n        Log.i(\"HIDETAG\", \"A静态代码块2\");\n    }\n    \n    public A() {\n        Log.i(\"HIDETAG\", \"A构造方法\");\n    }\n    \n    }\n````\nB类：\n````\n    public class B extends A {\n    \n    private static C c1 = new C(\"B静态成员\");\n    \n    {\n        Log.i(\"HIDETAG\", \"B代码块\");\n    }\n    \n    private C c = new C(\"B成员\");\n    \n    static {\n        Log.i(\"HIDETAG\", \"B静态代码块2\");\n    }\n    \n    static {\n        Log.i(\"HIDETAG\", \"B静态代码块\");\n    }\n    \n    public B() {\n        Log.i(\"HIDETAG\", \"B构造方法\");\n    \n    }\n    \n    }\n````\nC类：\n````\n    public class C {\n    \n    public C(String str) {\n        Log.i(\"HIDETAG\", str + \"构造方法\");\n    }\n    }\n````\n执行语句：new B();\n\n输出结果如下：\n\n     I/HIDETAG: A静态代码块\n     I/HIDETAG: A静态成员构造方法\n     I/HIDETAG: A静态代码块2\n     I/HIDETAG: B静态成员构造方法\n     I/HIDETAG: B静态代码块2\n     I/HIDETAG: B静态代码块\n     I/HIDETAG: A成员构造方法\n     I/HIDETAG: A代码块\n     I/HIDETAG: A构造方法\n     I/HIDETAG: B代码块\n     I/HIDETAG: B成员构造方法\n     I/HIDETAG: B构造方法\n得出结论：\n\n    执行顺序依次为：\n    父类的静态成员和代码块\n    子类静态成员和代码块\n    父类成员初始化和代码快\n    父类构造方法\n    子类成员初始化和代码块\n    子类构造方法\n\n注意：可以发现，同一级别的代码块和成员初始化是按照代码顺序从上到下依次执行\n\n**看完上面这个demo，再来看看下面这道题，看看你搞得定吗？**\n\n看下面一段代码，求执行顺序：\n````\n    class A {\n        public A() {\n            System.out.println(\"1A类的构造方法\");\n        }\n        {\n            System.out.println(\"2A类的构造快\");\n        }\n        static {\n            System.out.println(\"3A类的静态块\");\n        }\n    }\n  \n    public class B extends A {\n        public B() {\n            System.out.println(\"4B类的构造方法\");\n        }\n        {\n            System.out.println(\"5B类的构造快\");\n        }\n        static {\n            System.out.println(\"6B类的静态块\");\n        }\n        public static void main(String[] args) {\n            System.out.println(\"7\");\n            new B();\n            new B();\n            System.out.println(\"8\");\n        }\n    }\n````\n\n执行顺序结果为：367215421548\n\n为什么呢？\n\n首先我们要知道下面这5点：\n\n每次new都会执行构造方法以及构造块。\n构造块的内容会在构造方法之前执行。\n非主类的静态块会在类加载时，构造方法和构造块之前执行，切只执行一次。\n主类（public class）里的静态块会先于main执行。\n继承中，子类实例化，会先执行父类的构造方法，产生父类对象，再调用子类构造方法。\n所以题目里，由于主类B继承A，所以会先加载A，所以第一个执行的是第3句。\n\n从第4点我们知道6会在7之前执行，所以前三句是367。\n\n之后实例化了B两次，每次都会先实例化他的父类A，然后再实例化B，而根据第1、2、5点，知道顺序为2154。\n\n最后执行8\n\n所以顺序是367215421548\n\n## 参考文章\n\nhttps://blog.csdn.net/likunkun__/article/details/83066062\nhttps://www.jianshu.com/p/6877aae403f7\nhttps://www.jianshu.com/p/49e45af288ea\nhttps://blog.csdn.net/du_du1/article/details/91383128\nhttp://c.biancheng.net/view/976.html\nhttps://blog.csdn.net/evilcry2012/article/details/79499786\nhttps://www.jb51.net/article/129990.htm\n\n"
  },
  {
    "path": "docs/Java/basic/反射.md",
    "content": "# 目录\n\n  * [回顾：什么是反射？](#回顾：什么是反射？)\n  * [反射的主要用途](#反射的主要用途)\n  * [反射的基础：关于Class类](#反射的基础：关于class类)\n  * [Java为什么需要反射？反射要解决什么问题？](#java为什么需要反射？反射要解决什么问题？)\n  * [反射的基本运用](#反射的基本运用)\n  * [判断是否为某个类的实例](#判断是否为某个类的实例)\n  * [创建实例](#创建实例)\n  * [获取方法](#获取方法)\n  * [获取构造器信息](#获取构造器信息)\n  * [获取类的成员变量（字段）信息](#获取类的成员变量（字段）信息)\n  * [调用方法](#调用方法)\n  * [利用反射创建数组](#利用反射创建数组)\n  * [Java反射常见面试题](#java反射常见面试题)\n    * [什么是反射？](#什么是反射？)\n    * [哪里用到反射机制？](#哪里用到反射机制？)\n    * [什么叫对象序列化，什么是反序列化，实现对象序列化需要做哪些工作？](#什么叫对象序列化，什么是反序列化，实现对象序列化需要做哪些工作？)\n    * [反射机制的优缺点？](#反射机制的优缺点？)\n    * [动态代理是什么？有哪些应用？](#动态代理是什么？有哪些应用？)\n    * [怎么实现动态代理？](#怎么实现动态代理？)\n    * [Java反射机制的作用](#java反射机制的作用)\n    * [如何使用Java的反射?](#如何使用java的反射)\n  * [参考文章](#参考文章)\n\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章首发于我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《夯实Java基础系列博文》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n该系列博文会告诉你如何从入门到进阶，一步步地学习Java基础知识，并上手进行实战，接着了解每个Java知识点背后的实现原理，更完整地了解整个Java技术体系，形成自己的知识框架。为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n\n<!-- more -->\n\n## 回顾：什么是反射？\n\n反射(Reflection)是Java 程序开发语言的特征之一，它允许运行中的 Java 程序获取自身的信息，并且可以操作类或对象的内部属性。\nOracle官方对反射的解释是\n\n> Reflection enables Java code to discover information about the fields, methods and constructors of loaded classes, and to use reflected fields, methods, and constructors to operate on their underlying counterparts, within security restrictions.\n\n> The API accommodates applications that need access to either the public members of a target object (based on its runtime class) or the members declared by a given class. It also allows programs to suppress default reflective access control.\n> \n> 简而言之，通过反射，我们可以在运行时获得程序或程序集中每一个类型的成员和成员的信息。\n>\n> 程序中一般的对象的类型都是在编译期就确定下来的，而Java反射机制可以动态地创建对象并调用其属性，这样的对象的类型在编译期是未知的。所以我们可以通过反射机制直接创建对象，即使这个对象的类型在编译期是未知的。\n> \n> 反射的核心是JVM在运行时才动态加载类或调用方法/访问属性，它不需要事先（写代码的时候或编译期）知道运行对象是谁。\n\nJava反射框架主要提供以下功能：\n\n> 1.在运行时判断任意一个对象所属的类；\n\n> 2.在运行时构造任意一个类的对象；\n\n> 3.在运行时判断任意一个类所具有的成员变量和方法（通过反射甚至可以调用private方法）；\n\n> 4.在运行时调用任意一个对象的方法\n\n> 重点：是运行时而不是编译时\n\n\n## 反射的主要用途\n\n> 很多人都认为反射在实际的Java开发应用中并不广泛，其实不然。\n\n> 当我们在使用IDE(如Eclipse，IDEA)时，当我们输入一个对象或类并想调用它的属性或方法时，一按点号，编译器就会自动列出它的属性或方法，这里就会用到反射。\n\n> 反射最重要的用途就是开发各种通用框架。\n\n> 很多框架（比如Spring）都是配置化的（比如通过XML文件配置JavaBean,Action之类的），为了保证框架的通用性，它们可能需要根据配置文件加载不同的对象或类，调用不同的方法，这个时候就必须用到反射——运行时动态加载需要加载的对象。\n\n> 对于框架开发人员来说，反射虽小但作用非常大，它是各种容器实现的核心。而对于一般的开发者来说，不深入框架开发则用反射用的就会少一点，不过了解一下框架的底层机制有助于丰富自己的编程思想，也是很有益的。\n\n## 反射的基础：关于Class类\n\n更多关于Class类和Object类的原理和介绍请见上一节\n\n> 1、Class是一个类，一个描述类的类（也就是描述类本身），封装了描述方法的Method，描述字段的Filed，描述构造器的Constructor等属性\n> \n> 2、对象照镜子后（反射）可以得到的信息：某个类的数据成员名、方法和构造器、某个类到底实现了哪些接口。\n> \n> 3、对于每个类而言，JRE 都为其保留一个不变的 Class 类型的对象。一个Class对象包含了特定某个类的有关信息。\n>    \n> 4、Class 对象只能由系统建立对象\n> \n> 5、一个类在 JVM 中只会有一个Class实例\n> \n````\n//总结一下就是，JDK有一个类叫做Class，这个类用来封装所有Java类型，包括这些类的所有信息，JVM中类信息是放在方法区的。\n\n//所有类在加载后，JVM会为其在堆中创建一个Class<类名称>的对象，并且每个类只会有一个Class对象，这个类的所有对象都要通过Class<类名称>来进行实例化。\n\n//上面说的是JVM进行实例化的原理，当然实际上在Java写代码时只需要用 类名称就可以进行实例化了。\n\npublic final class Class<T> implements java.io.Serializable,\n                          GenericDeclaration,\n                          Type,\n                          AnnotatedElement {\n            //通过类名.class获得唯一的Class对象。\n            Class<UserBean> cls = UserBean.class;\n            //通过integer.TYPEl来获取Class对象\n            Class<Integer> inti = Integer.TYPE;\n          //接口本质也是一个类，一样可以通过.class获取\n            Class<User> userClass = User.class;\n}\n````\nJAVA反射机制是在运行状态中，对于任意一个类，都能够知道这个类的所有属性和方法；对于任意一个对象，都能够调用它的任意一个方法和属性；这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。\n\n要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.\n\n以上的总结就是什么是反射\n反射就是把java类中的各种成分映射成一个个的Java对象\n例如：一个类有：成员变量、方法、构造方法、包等等信息，利用反射技术可以对一个类进行解剖，把个个组成部分映射成一个个对象。（其实：一个类中这些成员方法、构造方法、在加入类中都有一个类来描述）\n如图是类的正常加载过程：反射的原理在与class对象。\n熟悉一下加载的时候：Class对象的由来是将class文件读入内存，并为之创建一个Class对象。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230403211245.png)\n## Java为什么需要反射？反射要解决什么问题？\nJava中编译类型有两种：\n\n静态编译：在编译时确定类型，绑定对象即通过。\n动态编译：运行时确定类型，绑定对象。动态编译最大限度地发挥了Java的灵活性，体现了多态的应用，可以减低类之间的耦合性。\nJava反射是Java被视为动态（或准动态）语言的一个关键性质。这个机制允许程序在运行时透过Reflection APIs取得任何一个已知名称的class的内部信息，包括其modifiers（诸如public、static等）、superclass（例如Object）、实现之interfaces（例如Cloneable），也包括fields和methods的所有信息，并可于运行时改变fields内容或唤起methods。\n\nReflection可以在运行时加载、探知、使用编译期间完全未知的classes。即Java程序可以加载一个运行时才得知名称的class，获取其完整构造，并生成其对象实体、或对其fields设值、或唤起其methods。\n\n反射（reflection）允许静态语言在运行时（runtime）检查、修改程序的结构与行为。\n在静态语言中，使用一个变量时，必须知道它的类型。在Java中，变量的类型信息在编译时都保存到了class文件中，这样在运行时才能保证准确无误；换句话说，程序在运行时的行为都是固定的。如果想在运行时改变，就需要反射这东西了。\n\n实现Java反射机制的类都位于java.lang.reflect包中：\n\nClass类：代表一个类\nField类：代表类的成员变量（类的属性）\nMethod类：代表类的方法\nConstructor类：代表类的构造方法\nArray类：提供了动态创建数组，以及访问数组的元素的静态方法\n一句话概括就是使用反射可以赋予jvm动态编译的能力，否则类的元数据信息只能用静态编译的方式实现，例如热加载，Tomcat的classloader等等都没法支持。\n\n## 反射的基本运用\n\n上面我们提到了反射可以用于判断任意对象所属的类，获得Class对象，构造任意一个对象以及调用一个对象。这里我们介绍一下基本反射功能的实现(反射相关的类一般都在java.lang.relfect包里)。\n\n1、获得Class对象方法有三种\n\n(1)使用Class类的forName静态方法:\n````  \npublic static Class<?> forName(String className)\n````\n在JDBC开发中常用此方法加载数据库驱动:\n要使用全类名来加载这个类，一般数据库驱动的配置信息会写在配置文件中。加载这个驱动前要先导入jar包\n````\nClass.forName(driver);\n````\n(2)直接获取某一个对象的class，比如:\n````\n//Class<?>是一个泛型表示，用于获取一个类的类型。\nClass<?> klass = int.class;\nClass<?> classInt = Integer.TYPE;\n````\n(3)调用某个对象的getClass()方法,比如:\n````    \nStringBuilder str = new StringBuilder(\"123\");\nClass<?> klass = str.getClass();\n````\n## 判断是否为某个类的实例\n\n一般地，我们用instanceof关键字来判断是否为某个类的实例。同时我们也可以借助反射中Class对象的isInstance()方法来判断是否为某个类的实例，它是一个Native方法：\n````\npublic native boolean isInstance(Object obj);\n````\n## 创建实例\n\n通过反射来生成对象主要有两种方式。\n\n（1）使用Class对象的newInstance()方法来创建Class对象对应类的实例。\n\n注意：利用newInstance创建对象：调用的类必须有无参的构造器\n````\n//Class<?>代表任何类的一个类对象。\n//使用这个类对象可以为其他类进行实例化\n//因为jvm加载类以后自动在堆区生成一个对应的*.Class对象\n//该对象用于让JVM对进行所有*对象实例化。\nClass<?> c = String.class;\n\n//Class<?> 中的 ? 是通配符，其实就是表示任意符合泛类定义条件的类，和直接使用 Class\n//效果基本一致，但是这样写更加规范，在某些类型转换时可以避免不必要的 unchecked 错误。\n\nObject str = c.newInstance();\n````\n（2）先通过Class对象获取指定的Constructor对象，再调用Constructor对象的newInstance()方法来创建实例。这种方法可以用指定的构造器构造类的实例。\n````\n    //获取String所对应的Class对象\n    Class<?> c = String.class;\n    //获取String类带一个String参数的构造器\n    Constructor constructor = c.getConstructor(String.class);\n    //根据构造器创建实例\n    Object obj = constructor.newInstance(\"23333\");\n    System.out.println(obj);\n````\n## 获取方法\n获取某个Class对象的方法集合，主要有以下几个方法：\n\ngetDeclaredMethods()方法返回类或接口声明的所有方法，==包括公共、保护、默认（包）访问和私有方法，但不包括继承的方法==。\n````\npublic Method[] getDeclaredMethods() throws SecurityException\n````\ngetMethods()方法返回某个类的所有公用（public）方法，==包括其继承类的公用方法。==\n````    \npublic Method[] getMethods() throws SecurityException\n````\ngetMethod方法返回一个特定的方法，其中第一个参数为方法名称，后面的参数为方法的参数对应Class的对象\n````\npublic Method getMethod(String name, Class<?>... parameterTypes)\n````\n只是这样描述的话可能难以理解，我们用例子来理解这三个方法：\n本文中的例子用到了以下这些类，用于反射的测试。\n\n````\n    //注解类，可可用于表示方法，可以通过反射获取注解的内容。\n        //Java注解的实现是很多注框架实现注解配置的基础\n    @Target(ElementType.METHOD)\n    @Retention(RetentionPolicy.RUNTIME)\n    public @interface Invoke {\n    }\n````\nuserbean的父类personbean\n````\n    public class PersonBean {\n    private String name;\n    \n    int id;\n    \n    public String getName() {\n        return name;\n    }\n    \n    public void setName(String name) {\n        this.name = name;\n    }\n}\n````\n接口user\n````\n    public interface User {\n        public void login ();\n    \n    }\n````\nuserBean实现user接口，继承personbean\n````\n    public class UserBean extends PersonBean implements User{\n        @Override\n        public void login() {\n    \n        }\n    \n        class B {\n    \n        }\n    \n        public String userName;\n        protected int i;\n        static int j;\n        private int l;\n        private long userId;\n        public UserBean(String userName, long userId) {\n            this.userName = userName;\n            this.userId = userId;\n        }\n        public String getName() {\n            return userName;\n        }\n        public long getId() {\n            return userId;\n        }\n        @Invoke\n        public static void staticMethod(String devName,int a) {\n            System.out.printf(\"Hi %s, I'm a static method\", devName);\n        }\n        @Invoke\n        public void publicMethod() {\n            System.out.println(\"I'm a public method\");\n        }\n        @Invoke\n        private void privateMethod() {\n            System.out.println(\"I'm a private method\");\n        }\n    }\n````\n1 getMethods和getDeclaredMethods的区别\n```` \npublic class 动态加载类的反射 {\n    public static void main(String[] args) {\n        try {\n            Class clazz = Class.forName(\"com.javase.反射.UserBean\");\n            for (Field field : clazz.getDeclaredFields()) {\n//                field.setAccessible(true);\n                System.out.println(field);\n            }\n            //getDeclaredMethod*()获取的是类自身声明的所有方法，包含public、protected和private方法。\n            System.out.println(\"------共有方法------\");\n//        getDeclaredMethod*()获取的是类自身声明的所有方法，包含public、protected和private方法。\n//            getMethod*()获取的是类的所有共有方法，这就包括自身的所有public方法，和从基类继承的、从接口实现的所有public方法。\n            for (Method method : clazz.getMethods()) {\n                String name = method.getName();\n                System.out.println(name);\n                //打印出了UserBean.java的所有方法以及父类的方法\n            }\n            System.out.println(\"------独占方法------\");\n\n            for (Method method : clazz.getDeclaredMethods()) {\n                String name = method.getName();\n                System.out.println(name);\n            }\n        } catch (ClassNotFoundException e) {\n            e.printStackTrace();\n        }\n    }\n}\n\n````\n\n2 打印一个类的所有方法及详细信息：\n````\npublic class 打印所有方法 {\n\n    public static void main(String[] args) {\n        Class userBeanClass = UserBean.class;\n        Field[] fields = userBeanClass.getDeclaredFields();\n        //注意，打印方法时无法得到局部变量的名称，因为jvm只知道它的类型\n        Method[] methods = userBeanClass.getDeclaredMethods();\n        for (Method method : methods) {\n            //依次获得方法的修饰符，返回类型和名称，外加方法中的参数\n            String methodString = Modifier.toString(method.getModifiers()) + \" \" ; // private static\n            methodString += method.getReturnType().getSimpleName() + \" \"; // void\n            methodString += method.getName() + \"(\"; // staticMethod\n            Class[] parameters = method.getParameterTypes();\n            Parameter[] p = method.getParameters();\n\n            for (Class parameter : parameters) {\n                methodString += parameter.getSimpleName() + \" \" ; // String\n            }\n            methodString += \")\";\n            System.out.println(methodString);\n        }\n        //注意方法只能获取到其类型，拿不到变量名\n/*        public String getName()\n        public long getId()\n        public static void staticMethod(String int )\n        public void publicMethod()\n        private void privateMethod()*/\n    }\n}\n````\n## 获取构造器信息\n\n获取类构造器的用法与上述获取方法的用法类似。主要是通过Class类的getConstructor方法得到Constructor类的一个实例，而Constructor类有一个newInstance方法可以创建一个对象实例:\n````\npublic class 打印构造方法 {\n    public static void main(String[] args) {\n        // constructors\n        Class<?> clazz = UserBean.class;\n\n        Class userBeanClass = UserBean.class;\n        //获得所有的构造方法\n        Constructor[] constructors = userBeanClass.getDeclaredConstructors();\n        for (Constructor constructor : constructors) {\n            String s = Modifier.toString(constructor.getModifiers()) + \" \";\n            s += constructor.getName() + \"(\";\n            //构造方法的参数类型\n            Class[] parameters = constructor.getParameterTypes();\n            for (Class parameter : parameters) {\n                s += parameter.getSimpleName() + \", \";\n            }\n            s += \")\";\n            System.out.println(s);\n            //打印结果//public com.javase.反射.UserBean(String, long, )\n\n        }\n    }\n}\n````\n## 获取类的成员变量（字段）信息\n主要是这几个方法，在此不再赘述：\n\ngetFiled: 访问公有的成员变量\ngetDeclaredField：所有已声明的成员变量。但不能得到其父类的成员变量\ngetFileds和getDeclaredFields用法同上（参照Method）\n\n````\npublic class 打印成员变量 {\n    public static void main(String[] args) {\n        Class userBeanClass = UserBean.class;\n        //获得该类的所有成员变量，包括static private\n        Field[] fields = userBeanClass.getDeclaredFields();\n\n        for(Field field : fields) {\n            //private属性即使不用下面这个语句也可以访问\n//            field.setAccessible(true);\n\n            //因为类的私有域在反射中默认可访问，所以flag默认为true。\n            String fieldString = \"\";\n            fieldString += Modifier.toString(field.getModifiers()) + \" \"; // `private`\n            fieldString += field.getType().getSimpleName() + \" \"; // `String`\n            fieldString += field.getName(); // `userName`\n            fieldString += \";\";\n            System.out.println(fieldString);\n            \n            //打印结果\n//            public String userName;\n//            protected int i;\n//            static int j;\n//            private int l;\n//            private long userId;\n        }\n\n    }\n}\n````\n## 调用方法\n当我们从类中获取了一个方法后，我们就可以用invoke()方法来调用这个方法。invoke方法的原型为:\n````\npublic Object invoke(Object obj, Object... args)\n        throws IllegalAccessException, IllegalArgumentException,\n           InvocationTargetException\n       \npublic class 使用反射调用方法 {\n    public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, InstantiationException, NoSuchMethodException {\n        Class userBeanClass = UserBean.class;\n        //获取该类所有的方法，包括静态方法，实例方法。\n        //此处也包括了私有方法，只不过私有方法在用invoke访问之前要设置访问权限\n        //也就是使用setAccessible使方法可访问，否则会抛出异常\n//       // IllegalAccessException的解释是\n//        * An IllegalAccessException is thrown when an application tries\n// * to reflectively create an instance (other than an array),\n// * set or get a field, or invoke a method, but the currently\n// * executing method does not have access to the definition of\n// * the specified class, field, method or constructor.\n\n//        getDeclaredMethod*()获取的是类自身声明的所有方法，包含public、protected和private方法。\n//            getMethod*()获取的是类的所有共有方法，这就包括自身的所有public方法，和从基类继承的、从接口实现的所有public方法。\n\n        //就是说，当这个类，域或者方法被设为私有访问，使用反射调用但是却没有权限时会抛出异常。\n        Method[] methods = userBeanClass.getDeclaredMethods(); // 获取所有成员方法\n        for (Method method : methods) {\n            //反射可以获取方法上的注解，通过注解来进行判断\n            if (method.isAnnotationPresent(Invoke.class)) { // 判断是否被 @Invoke 修饰\n                //判断方法的修饰符是是static\n                if (Modifier.isStatic(method.getModifiers())) { // 如果是 static 方法\n                    //反射调用该方法\n                    //类方法可以直接调用，不必先实例化\n                    method.invoke(null, \"wingjay\",2); // 直接调用，并传入需要的参数 devName\n                } else {\n                    //如果不是类方法，需要先获得一个实例再调用方法\n                    //传入构造方法需要的变量类型\n                    Class[] params = {String.class, long.class};\n                    //获取该类指定类型的构造方法\n                    //如果没有这种类型的方法会报错\n                    Constructor constructor = userBeanClass.getDeclaredConstructor(params); // 获取参数格式为 String,long 的构造函数\n                    //通过构造方法的实例来进行实例化\n                    Object userBean = constructor.newInstance(\"wingjay\", 11); // 利用构造函数进行实例化，得到 Object\n                    if (Modifier.isPrivate(method.getModifiers())) {\n                        method.setAccessible(true); // 如果是 private 的方法，需要获取其调用权限\n//                        Set the {@code accessible} flag for this object to\n//     * the indicated boolean value.  A value of {@code true} indicates that\n//     * the reflected object should suppress Java language access\n//     * checking when it is used.  A value of {@code false} indicates\n//                                * that the reflected object should enforce Java language access checks.\n                        //通过该方法可以设置其可见或者不可见，不仅可以用于方法\n                        //后面例子会介绍将其用于成员变量\n                                            //打印结果\n//            I'm a public method\n// Hi wingjay, I'm a static methodI'm a private method\n                    }\n                    method.invoke(userBean); // 调用 method，无须参数  \n                }\n            }\n        }\n    }\n}\n````\n## 利用反射创建数组\n\n数组在Java里是比较特殊的一种类型，它可以赋值给一个Object Reference。下面我们看一看利用反射创建数组的例子：\n````\npublic class 用反射创建数组 {\n    public static void main(String[] args) {\n        Class<?> cls = null;\n        try {\n            cls = Class.forName(\"java.lang.String\");\n        } catch (ClassNotFoundException e) {\n            e.printStackTrace();\n        }\n        Object array = Array.newInstance(cls,25);\n        //往数组里添加内容\n        Array.set(array,0,\"hello\");\n        Array.set(array,1,\"Java\");\n        Array.set(array,2,\"fuck\");\n        Array.set(array,3,\"Scala\");\n        Array.set(array,4,\"Clojure\");\n        //获取某一项的内容\n        System.out.println(Array.get(array,3));\n        //Scala\n    }\n\n}\n````\n其中的Array类为java.lang.reflect.Array类。我们通过Array.newInstance()创建数组对象，它的原型是:\n````\npublic static Object newInstance(Class<?> componentType, int length)\n        throws NegativeArraySizeException {\n        return newArray(componentType, length);\n        }\n````\n而newArray()方法是一个Native方法，它在Hotspot JVM里的具体实现我们后边再研究，这里先把源码贴出来\n````\nprivate static native Object newArray(Class<?> componentType, int length)\n        throws NegativeArraySizeException;\n\n````\n            \n\n## Java反射常见面试题\n\n### 什么是反射？\n\n反射是在运行状态中，对于任意一个类，都能够知道这个类的所有属性和方法；对于任意一个对象，都能够调用它的任意一个方法和属性；这种动态获取的信息以及动态调用对象的方法的功能称为 Java 语言的反射机制。\n\n### 哪里用到反射机制？\n\nJDBC中，利用反射动态加载了数据库驱动程序。\nWeb服务器中利用反射调用了Sevlet的服务方法。\nEclispe等开发工具利用反射动态刨析对象的类型与结构，动态提示对象的属性和方法。\n很多框架都用到反射机制，注入属性，调用方法，如Spring。\n\n###  什么叫对象序列化，什么是反序列化，实现对象序列化需要做哪些工作？\n\n对象序列化，将对象中的数据编码为字节序列的过程。\n反序列化；将对象的编码字节重新反向解码为对象的过程。\nJAVA提供了API实现了对象的序列化和反序列化的功能，使用这些API时需要遵守如下约定：\n被序列化的对象类型需要实现序列化接口，此接口是标志接口，没有声明任何的抽象方法，JAVA编译器识别这个接口，自动的为这个类添加序列化和反序列化方法。\n为了保持序列化过程的稳定，建议在类中添加序列化版本号。\n不想让字段放在硬盘上就加transient\n以下情况需要使用 Java 序列化：\n想把的内存中的对象状态保存到一个文件中或者数据库中时候；\n想用套接字在网络上传送对象的时候；\n想通过RMI（远程方法调用）传输对象的时候。\n\n### 反射机制的优缺点？\n\n优点：可以动态执行，在运行期间根据业务功能动态执行方法、访问属性，最大限度发挥了java的灵活性。\n缺点：对性能有影响，这类操作总是慢于直接执行java代码。\n\n### 动态代理是什么？有哪些应用？\n\n动态代理是运行时动态生成代理类。\n动态代理的应用有 Spring AOP数据查询、测试框架的后端 mock、rpc，Java注解对象获取等。\n\n### 怎么实现动态代理？\n\nJDK 原生动态代理和 cglib 动态代理。\nJDK 原生动态代理是基于接口实现的，而 cglib 是基于继承当前类的子类实现的。\n\n### Java反射机制的作用\n\n在运行时判断任意一个对象所属的类\n在运行时构造任意一个类的对象\n在运行时判断任意一个类所具有的成员变量和方法\n在运行时调用任意一个对象的方法\n\n### 如何使用Java的反射?\n\n通过一个全限类名创建一个对象\n\nClass.forName(“全限类名”); 例如：com.mysql.jdbc.Driver Driver类已经被加载到 jvm中，并且完成了类的初始化工作就行了\n类名.class; 获取Class<？> clz 对象\n对象.getClass();\n\n获取构造器对象，通过构造器new出一个对象\n\nClazz.getConstructor([String.class]);\nCon.newInstance([参数]);\n通过class对象创建一个实例对象（就相当与new类名（）无参构造器)\nCls.newInstance();\n\n通过class对象获得一个属性对象\n\nField c=cls.getFields()：获得某个类的所有的公共（public）的字段，包括父类中的字段。\nField c=cls.getDeclaredFields()：获得某个类的所有声明的字段，即包括public、private和proteced，但是不包括父类的声明字段\n\n通过class对象获得一个方法对象\n\nCls.getMethod(“方法名”,class……parameaType);（只能获取公共的）\nCls.getDeclareMethod(“方法名”);（获取任意修饰的方法，不能执行私有）\nM.setAccessible(true);（让私有的方法可以执行）\n让方法执行\n1）. Method.invoke(obj实例对象,obj可变参数);-----（是有返回值的）\n\n## 参考文章\nhttp://www.cnblogs.com/peida/archive/2013/04/26/3038503.html\nhttp://www.cnblogs.com/whoislcj/p/5671622.html\nhttps://blog.csdn.net/grandgrandpa/article/details/84832343\nhttp://blog.csdn.net/lylwo317/article/details/52163304\nhttps://blog.csdn.net/qq_37875585/article/details/89340495\n\n"
  },
  {
    "path": "docs/Java/basic/多线程.md",
    "content": "# 目录\n  * [Java中的线程](#java中的线程)\n  * [Java线程状态机](#java线程状态机)\n    * [一个线程的生命周期](#一个线程的生命周期)\n  * [Java多线程实战](#java多线程实战)\n  * [多线程的实现](#多线程的实现)\n  * [线程状态转换](#线程状态转换)\n  * [Java Thread常用方法](#java-thread常用方法)\n    * [Thread#yield()：](#threadyield：)\n    * [Thread.interrupt()：](#threadinterrupt：)\n    * [Thread#interrupted()，返回true或者false：](#threadinterrupted，返回true或者false：)\n    * [Thread.isInterrupted()，返回true或者false：](#threadisinterrupted，返回true或者false：)\n    * [Thread#join()，Thread#join(time)：](#threadjoin，threadjointime：)\n  * [构造方法和守护线程](#构造方法和守护线程)\n  * [启动线程的方式和isAlive方法](#启动线程的方式和isalive方法)\n  * [Java多线程优先级](#java多线程优先级)\n  * [Java多线程面试题](#java多线程面试题)\n  * [参考文章](#参考文章)\n\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章首发于我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《夯实Java基础系列博文》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n该系列博文会告诉你如何从入门到进阶，一步步地学习Java基础知识，并上手进行实战，接着了解每个Java知识点背后的实现原理，更完整地了解整个Java技术体系，形成自己的知识框架。为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n\n<!-- more -->\n\n\n\n## Java中的线程\n\nJava之父对线程的定义是：\n> 线程是一个独立执行的调用序列，同一个进程的线程在同一时刻共享一些系统资源（比如文件句柄等）也能访问同一个进程所创建的对象资源（内存资源）。java.lang.Thread对象负责统计和控制这种行为。\n\n> 每个程序都至少拥有一个线程-即作为Java虚拟机(JVM)启动参数运行在主类main方法的线程。在Java虚拟机初始化过程中也可能启动其他的后台线程。这种线程的数目和种类因JVM的实现而异。然而所有用户级线程都是显式被构造并在主线程或者是其他用户线程中被启动。\n\n      本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。在这之前，首先让我们来了解下在操作系统中进程和线程的区别：\n    \n    　　进程：每个进程都有独立的代码和数据空间（进程上下文），进程间的切换会有较大的开销，一个进程包含1--n个线程。（进程是资源分配的最小单位）\n    \n    　　线程：同一类线程共享代码和数据空间，每个线程有独立的运行栈和程序计数器(PC)，线程切换开销小。（线程是cpu调度的最小单位）\n    \n    　　线程和进程一样分为五个阶段：创建、就绪、运行、阻塞、终止。\n    \n    　　多进程是指操作系统能同时运行多个任务（程序）。\n    \n    　　多线程是指在同一程序中有多个顺序流在执行。\n    \n    在java中要想实现多线程，有两种手段，一种是继续Thread类，另外一种是实现Runable接口.(其实准确来讲，应该有三种，还有一种是实现Callable接口，并与Future、线程池结合使用\n    \n## Java线程状态机\n\n\nJava 给多线程编程提供了内置的支持。 一条线程指的是进程中一个单一顺序的控制流，一个进程中可以并发多个线程，每条线程并行执行不同的任务。\n\n多线程是多任务的一种特别的形式，但多线程使用了更小的资源开销。\n\n这里定义和线程相关的另一个术语 - 进程：一个进程包括由操作系统分配的内存空间，包含一个或多个线程。一个线程不能独立的存在，它必须是进程的一部分。一个进程一直运行，直到所有的非守护线程都结束运行后才能结束。\n\n多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的。\n\n* * *\n\n### 一个线程的生命周期\n\n线程是一个动态执行的过程，它也有一个从产生到死亡的过程。\n\n下图显示了一个线程完整的生命周期。\n\n![](https://www.runoob.com/wp-content/uploads/2014/01/java-thread.jpg)\n\n*   **新建状态:**\n\n    使用**new**关键字和**Thread**类或其子类建立一个线程对象后，该线程对象就处于新建状态。它保持这个状态直到程序**start()**这个线程。\n\n*   **就绪状态:**\n\n    当线程对象调用了start()方法之后，该线程就进入就绪状态。就绪状态的线程处于就绪队列中，要等待JVM里线程调度器的调度。\n\n*   **运行状态:**\n\n    如果就绪状态的线程获取 CPU 资源，就可以执行**run()**，此时线程便处于运行状态。处于运行状态的线程最为复杂，它可以变为阻塞状态、就绪状态和死亡状态。\n\n*   **阻塞状态:**\n\n    如果一个线程执行了sleep（睡眠）、suspend（挂起）等方法，失去所占用资源之后，该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种：\n\n    *   等待阻塞：运行状态中的线程执行 wait() 方法，使线程进入到等待阻塞状态。\n\n    *   同步阻塞：线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。\n\n    *   其他阻塞：通过调用线程的 sleep() 或 join() 发出了 I/O 请求时，线程就会进入到阻塞状态。当sleep() 状态超时，join() 等待线程终止或超时，或者 I/O 处理完毕，线程重新转入就绪状态。\n\n*   **死亡状态:**\n\n    一个运行状态的线程完成任务或者其他终止条件发生时，该线程就切换到终止状态。\n## Java多线程实战\n## 多线程的实现\n\n    public class 多线程实例 {\n    \n        //继承thread\n        @Test\n        public void test1() {\n            class A extends Thread {\n                @Override\n                public void run() {\n                    System.out.println(\"A run\");\n                }\n            }\n            A a = new A();\n            a.start();\n        }\n    \n        //实现Runnable\n        @Test\n        public void test2() {\n            class B implements Runnable {\n    \n                @Override\n                public void run() {\n                    System.out.println(\"B run\");\n                }\n            }\n            B b = new B();\n            //Runable实现类需要由Thread类包装后才能执行\n            new Thread(b).start();\n        }\n    \n        //有返回值的线程\n        @Test\n        public void test3() {\n            Callable callable = new Callable() {\n                int sum = 0;\n                @Override\n                public Object call() throws Exception {\n                    for (int i = 0;i < 5;i ++) {\n                        sum += i;\n                    }\n                    return sum;\n                }\n            };\n            //这里要用FutureTask，否则不能加入Thread构造方法\n            FutureTask futureTask = new FutureTask(callable);\n            new Thread(futureTask).start();\n            try {\n                System.out.println(futureTask.get());\n            } catch (InterruptedException e) {\n                e.printStackTrace();\n            } catch (ExecutionException e) {\n                e.printStackTrace();\n            }\n        }\n    \n        //线程池实现\n        @Test\n        public void test4() {\n            ExecutorService executorService = Executors.newFixedThreadPool(5);\n            //execute直接执行线程\n            executorService.execute(new Thread());\n            executorService.execute(new Runnable() {\n                @Override\n                public void run() {\n                    System.out.println(\"runnable\");\n                }\n            });\n            //submit提交有返回结果的任务，运行完后返回结果。\n            Future future = executorService.submit(new Callable<String>() {\n                @Override\n                public String call() throws Exception {\n                    return \"a\";\n                }\n            });\n            try {\n                System.out.println(future.get());\n            } catch (InterruptedException e) {\n                e.printStackTrace();\n            } catch (ExecutionException e) {\n                e.printStackTrace();\n            }\n    \n            ArrayList<String> list = new ArrayList<>();\n            //有返回值的线程组将返回值存进集合\n            for (int i = 0;i < 5;i ++ ) {\n                int finalI = i;\n                Future future1 = executorService.submit(new Callable<String>() {\n                    @Override\n                    public String call() throws Exception {\n                        return \"res\" + finalI;\n                    }\n                });\n                try {\n                    list.add((String) future1.get());\n                } catch (InterruptedException e) {\n                    e.printStackTrace();\n                } catch (ExecutionException e) {\n                    e.printStackTrace();\n                }\n            }\n            for (String s : list) {\n                System.out.println(s);\n            }\n        }\n    }\n\n## 线程状态转换\n\n    public class 线程的状态转换 {\n    //一开始线程是init状态，结束时是terminated状态\n    class t implements Runnable {\n        private String name;\n        public t(String name) {\n            this.name = name;\n        }\n        @Override\n        public void run() {\n            System.out.println(name + \"run\");\n        }\n    }\n\n    //测试join，父线程在子线程运行时进入waiting状态\n    @Test\n    public void test1() throws InterruptedException {\n        Thread dad = new Thread(new Runnable() {\n            Thread son = new Thread(new t(\"son\"));\n            @Override\n            public void run() {\n                System.out.println(\"dad init\");\n                son.start();\n                try {\n                    //保证子线程运行完再运行父线程\n                    son.join();\n                    System.out.println(\"dad run\");\n                } catch (InterruptedException e) {\n                    e.printStackTrace();\n                }\n            }\n        });\n        //调用start，线程进入runnable状态，等待系统调度\n        dad.start();\n        //在父线程中对子线程实例使用join，保证子线程在父线程之前执行完\n\n    }\n\n    //测试sleep\n    @Test\n    public void test2(){\n        Thread t1 = new Thread(new Runnable() {\n            @Override\n            public void run() {\n                System.out.println(\"t1 run\");\n                try {\n                    Thread.sleep(3000);\n                } catch (InterruptedException e) {\n                    e.printStackTrace();\n                }\n            }\n        });\n\n        //主线程休眠。进入time waiting状态\n        try {\n            Thread.sleep(3000);\n        } catch (InterruptedException e) {\n            e.printStackTrace();\n        }\n        t1.start();\n\n    }\n\n    //线程2进入blocked状态。\n    public static void main(String[] args) {\n        test4();\n        Thread.yield();//进入runnable状态\n    }\n\n    //测试blocked状态\n    public static void test4() {\n        class A {\n            //线程1获得实例锁以后线程2无法获得实例锁，所以进入blocked状态\n            synchronized void run() {\n                while (true) {\n                    System.out.println(\"run\");\n                }\n            }\n        }\n        A a = new A();\n        new Thread(new Runnable() {\n            @Override\n            public void run() {\n                System.out.println(\"t1 get lock\");\n                a.run();\n            }\n        }).start();\n        new Thread(new Runnable() {\n            @Override\n            public void run() {\n                System.out.println(\"t2 get lock\");\n                a.run();\n            }\n        }).start();\n\n    }\n\n    //volatile保证线程可见性\n    volatile static int flag = 1;\n    //object作为锁对象，用于线程使用wait和notify方法\n    volatile static Object o = new Object();\n    //测试wait和notify\n    //wait后进入waiting状态，被notify进入blocked（阻塞等待锁释放）或者runnable状态（获取到锁）\n    public void test5() {\n        new Thread(new Runnable() {\n            @Override\n            public void run() {\n                //wait和notify只能在同步代码块内使用\n                synchronized (o) {\n                    while (true) {\n                        if (flag == 0) {\n                            try {\n                                Thread.sleep(2000);\n                                System.out.println(\"thread1 wait\");\n                                //释放锁，线程挂起进入object的等待队列，后续代码运行\n                                o.wait();\n                            } catch (InterruptedException e) {\n                                e.printStackTrace();\n                            }\n                        }\n                        System.out.println(\"thread1 run\");\n                        System.out.println(\"notify t2\");\n                        flag = 0;\n                        //通知等待队列的一个线程获取锁\n                        o.notify();\n                    }\n                }\n            }\n        }).start();\n        //解释同上\n        new Thread(new Runnable() {\n            @Override\n            public void run() {\n                while (true) {\n                    synchronized (o) {\n                        if (flag == 1) {\n                            try {\n                                Thread.sleep(2000);\n                                System.out.println(\"thread2 wait\");\n                                o.wait();\n                            } catch (InterruptedException e) {\n                                e.printStackTrace();\n                            }\n                        }\n                        System.out.println(\"thread2 run\");\n                        System.out.println(\"notify t1\");\n                        flag = 1;\n                        o.notify();\n                    }\n                }\n            }\n        }).start();\n    }\n\n    //输出结果是\n    //    thread1 run\n    //    notify t2\n    //    thread1 wait\n    //    thread2 run\n    //    notify t1\n    //    thread2 wait\n    //    thread1 run\n    //    notify t2\n    //不断循环\n\n    }\n## Java Thread常用方法\n\n### Thread#yield()：\n\n执行此方法会向系统线程调度器（Schelduler）发出一个暗示，告诉其当前JAVA线程打算放弃对CPU的使用，但该暗示，有可能被调度器忽略。使用该方法，可以防止线程对CPU的过度使用，提高系统性能。\n\nThread#sleep(time)或Thread.sleep(time, nanos)：\n\n使当前线程进入休眠阶段，状态变为：TIME_WAITING\n\n### Thread.interrupt()：\n\n中断当前线程的执行，允许当前线程对自身进行中断，否则将会校验调用方线程是否有对该线程的权限。\n\n如果当前线程因被调用Object#wait(),Object#wait(long, int), 或者线程本身的join(), join(long),sleep()处于阻塞状态中，此时调用interrupt方法会使抛出InterruptedException，而且线程的阻塞状态将会被清除。\n\n### Thread#interrupted()，返回true或者false：\n\n查看当前线程是否处于中断状态，这个方法比较特殊之处在于，如果调用成功，会将当前线程的interrupt status清除。所以如果连续2次调用该方法，第二次将返回false。\n\n### Thread.isInterrupted()，返回true或者false：\n\n与上面方法相同的地方在于，该方法返回当前线程的中断状态。不同的地方在于，它不会清除当前线程的interrupt status状态。\n\n### Thread#join()，Thread#join(time)：\n\nA线程调用B线程的join()方法，将会使A等待B执行，直到B线程终止。如果传入time参数，将会使A等待B执行time的时间，如果time时间到达，将会切换进A线程，继续执行A线程。\n\n\n## 构造方法和守护线程\n\n    构造方法\n    Thread类中不同的构造方法接受如下参数的不同组合：\n    \n    一个Runnable对象，这种情况下，Thread.start方法将会调用对应Runnable对象的run方法。如果没有提供Runnable对象，那么就会立即得到一个Thread.run的默认实现。\n    \n    一个作为线程标识名的String字符串，该标识在跟踪和调试过程中会非常有用，除此别无它用。\n    \n    线程组（ThreadGroup），用来放置新创建的线程，如果提供的ThreadGroup不允许被访问，那么就会抛出一个SecurityException 。\n    \n  \n    \n    Thread对象拥有一个守护(daemon)标识属性，这个属性无法在构造方法中被赋值，但是可以在线程启动之前设置该属性(通过setDaemon方法)。\n    \n    当程序中所有的非守护线程都已经终止，调用setDaemon方法可能会导致虚拟机粗暴的终止线程并退出。\n    \n    isDaemon方法能够返回该属性的值。守护状态的作用非常有限，即使是后台线程在程序退出的时候也经常需要做一些清理工作。\n    \n    （daemon的发音为”day-mon”,这是系统编程传统的遗留，系统守护进程是一个持续运行的进程，比如打印机队列管理，它总是在系统中运行。）\n    \n## 启动线程的方式和isAlive方法\n\n启动线程\n调用start方法会触发Thread实例以一个新的线程启动其run方法。新线程不会持有调用线程的任何同步锁。\n\n当一个线程正常地运行结束或者抛出某种未检测的异常（比如，运行时异常(RuntimeException)，错误(ERROR) 或者其子类）线程就会终止。\n\n**当线程终止之后，是不能被重新启动的。在同一个Thread上调用多次start方法会抛出InvalidThreadStateException异常。**\n\n如果线程已经启动但是还没有终止，那么调用isAlive方法就会返回true.即使线程由于某些原因处于阻塞(Blocked)状态该方法依然返回true。\n\n如果线程已经被取消(cancelled),那么调用其isAlive在什么时候返回false就因各Java虚拟机的实现而异了。没有方法可以得知一个处于非活动状态的线程是否已经被启动过了。\n\n## Java多线程优先级\n\n**Java的线程实现基本上都是内核级线程的实现，所以Java线程的具体执行还取决于操作系统的特性。**\n\nJava虚拟机为了实现跨平台(不同的硬件平台和各种操作系统)的特性，Java语言在线程调度与调度公平性上未作出任何的承诺，甚至都不会严格保证线程会被执行。但是Java线程却支持优先级的方法，这些方法会影响线程的调度：\n\n每个线程都有一个优先级，分布在Thread.MIN_PRIORITY和Thread.MAX_PRIORITY之间（分别为1和10）\n默认情况下，新创建的线程都拥有和创建它的线程相同的优先级。main方法所关联的初始化线程拥有一个默认的优先级，这个优先级是Thread.NORM_PRIORITY (5).\n\n线程的当前优先级可以通过getPriority方法获得。\n线程的优先级可以通过setPriority方法来动态的修改，一个线程的最高优先级由其所在的线程组限定。\n\n## Java多线程面试题\n\n这篇文章主要是对多线程的问题进行总结的，因此罗列了40个多线程的问题。\n\n这些多线程的问题，有些来源于各大网站、有些来源于自己的思考。可能有些问题网上有、可能有些问题对应的答案也有、也可能有些各位网友也都看过，但是本文写作的重心就是所有的问题都会按照自己的理解回答一遍，不会去看网上的答案，因此可能有些问题讲的不对，能指正的希望大家不吝指教。\n\n> **1、多线程有什么用？**\n\n一个可能在很多人看来很扯淡的一个问题：我会用多线程就好了，还管它有什么用？在我看来，这个回答更扯淡。所谓\"知其然知其所以然\"，\"会用\"只是\"知其然\"，\"为什么用\"才是\"知其所以然\"，只有达到\"知其然知其所以然\"的程度才可以说是把一个知识点运用自如。OK，下面说说我对这个问题的看法：\n\n**1）发挥多核CPU的优势**\n\n随着工业的进步，现在的笔记本、台式机乃至商用的应用服务器至少也都是双核的，4核、8核甚至16核的也都不少见，如果是单线程的程序，那么在双核CPU上就浪费了50%，在4核CPU上就浪费了75%。**单核CPU上所谓的\"多线程\"那是假的多线程，同一时间处理器只会处理一段逻辑，只不过线程之间切换得比较快，看着像多个线程\"同时\"运行罢了**。多核CPU上的多线程才是真正的多线程，它能让你的多段逻辑同时工作，多线程，可以真正发挥出多核CPU的优势来，达到充分利用CPU的目的。\n\n**2）防止阻塞**\n\n从程序运行效率的角度来看，单核CPU不但不会发挥出多线程的优势，反而会因为在单核CPU上运行多线程导致线程上下文的切换，而降低程序整体的效率。但是单核CPU我们还是要应用多线程，就是为了防止阻塞。试想，如果单核CPU使用单线程，那么只要这个线程阻塞了，比方说远程读取某个数据吧，对端迟迟未返回又没有设置超时时间，那么你的整个程序在数据返回回来之前就停止运行了。多线程可以防止这个问题，多条线程同时运行，哪怕一条线程的代码执行读取数据阻塞，也不会影响其它任务的执行。\n\n**3）便于建模**\n\n这是另外一个没有这么明显的优点了。假设有一个大的任务A，单线程编程，那么就要考虑很多，建立整个程序模型比较麻烦。但是如果把这个大的任务A分解成几个小任务，任务B、任务C、任务D，分别建立程序模型，并通过多线程分别运行这几个任务，那就简单很多了。\n\n> **2、创建线程的方式**\n\n比较常见的一个问题了，一般就是两种：\n\n1）继承Thread类\n\n2）实现Runnable接口\n\n至于哪个好，不用说肯定是后者好，因为实现接口的方式比继承类的方式更灵活，也能减少程序之间的耦合度，**面向接口编程**也是设计模式6大原则的核心。\n\n\n> **3、start()方法和run()方法的区别**\n\n只有调用了start()方法，才会表现出多线程的特性，不同线程的run()方法里面的代码交替执行。如果只是调用run()方法，那么代码还是同步执行的，必须等待一个线程的run()方法里面的代码全部执行完毕之后，另外一个线程才可以执行其run()方法里面的代码。\n\n> **4、Runnable接口和Callable接口的区别**\n\n有点深的问题了，也看出一个Java程序员学习知识的广度。\n\nRunnable接口中的run()方法的返回值是void，它做的事情只是纯粹地去执行run()方法中的代码而已；Callable接口中的call()方法是有返回值的，是一个泛型，和Future、FutureTask配合可以用来获取异步执行的结果。\n\n这其实是很有用的一个特性，因为**多线程相比单线程更难、更复杂的一个重要原因就是因为多线程充满着未知性**，某条线程是否执行了？某条线程执行了多久？某条线程执行的时候我们期望的数据是否已经赋值完毕？无法得知，我们能做的只是等待这条多线程的任务执行完毕而已。而Callable+Future/FutureTask却可以获取多线程运行的结果，可以在等待时间太长没获取到需要的数据的情况下取消该线程的任务，真的是非常有用。\n\n> **5、CyclicBarrier和CountDownLatch的区别**\n\n两个看上去有点像的类，都在java.util.concurrent下，都可以用来表示代码运行到某个点上，二者的区别在于：\n\n1）CyclicBarrier的某个线程运行到某个点上之后，该线程即停止运行，直到所有的线程都到达了这个点，所有线程才重新运行；CountDownLatch则不是，某线程运行到某个点上之后，只是给某个数值-1而已，该线程继续运行。\n\n2）CyclicBarrier只能唤起一个任务，CountDownLatch可以唤起多个任务。\n\n3) CyclicBarrier可重用，CountDownLatch不可重用，计数值为0该CountDownLatch就不可再用了。\n\n> **6、volatile关键字的作用**\n\n一个非常重要的问题，是每个学习、应用多线程的Java程序员都必须掌握的。理解volatile关键字的作用的前提是要理解Java内存模型，这里就不讲Java内存模型了，可以参见第31点，volatile关键字的作用主要有两个：\n\n1）多线程主要围绕可见性和原子性两个特性而展开，使用volatile关键字修饰的变量，保证了其在多线程之间的可见性，即每次读取到volatile变量，一定是最新的数据。\n\n2）代码底层执行不像我们看到的高级语言----Java程序这么简单，它的执行是**Java代码-->字节码-->根据字节码执行对应的C/C++代码-->C/C++代码被编译成汇编语言-->和硬件电路交互**，现实中，为了获取更好的性能JVM可能会对指令进行重排序，多线程下可能会出现一些意想不到的问题。使用volatile则会对禁止语义重排序，当然这也一定程度上降低了代码执行效率。\n\n从实践角度而言，volatile的一个重要作用就是和CAS结合，保证了原子性，详细的可以参见java.util.concurrent.atomic包下的类，比如AtomicInteger，更多详情请点击[这里](http://mp.weixin.qq.com/s?__biz=MzI3ODcxMzQzMw==&mid=2247483916&idx=1&sn=89daf388da0d6fe40dc54e9a4018baeb&chksm=eb53873adc240e2cf55400f3261228d08fc943c4f196566e995681549c47630b70ac01b75031&scene=21#wechat_redirect)进行学习。\n\n> **7、什么是线程安全**\n\n又是一个理论的问题，各式各样的答案有很多，我给出一个个人认为解释地最好的：**如果你的代码在多线程下执行和在单线程下执行永远都能获得一样的结果，那么你的代码就是线程安全的**。\n\n这个问题有值得一提的地方，就是线程安全也是有几个级别的：\n\n**1）不可变**\n\n像String、Integer、Long这些，都是final类型的类，任何一个线程都改变不了它们的值，要改变除非新创建一个，因此这些不可变对象不需要任何同步手段就可以直接在多线程环境下使用\n\n**2）绝对线程安全**\n\n不管运行时环境如何，调用者都不需要额外的同步措施。要做到这一点通常需要付出许多额外的代价，Java中标注自己是线程安全的类，实际上绝大多数都不是线程安全的，不过绝对线程安全的类，Java中也有，比方说CopyOnWriteArrayList、CopyOnWriteArraySet\n\n**3）相对线程安全**\n\n相对线程安全也就是我们通常意义上所说的线程安全，像Vector这种，add、remove方法都是原子操作，不会被打断，但也仅限于此，如果有个线程在遍历某个Vector、有个线程同时在add这个Vector，99%的情况下都会出现ConcurrentModificationException，也就是**fail-fast机制**。\n\n**4）线程非安全**\n\n这个就没什么好说的了，ArrayList、LinkedList、HashMap等都是线程非安全的类，点击[这里](http://mp.weixin.qq.com/s?__biz=MzI3ODcxMzQzMw==&mid=2247486446&idx=2&sn=cb4f3aff0427c5ac3ffe5b61e150f506&chksm=eb538ed8dc2407ceb91fffe3c3bd559d9b15537446f84eb3bfb1a80e67f5efee176ca468a07b&scene=21#wechat_redirect)了解为什么不安全。\n\n> **8、Java中如何获取到线程dump文件**\n\n死循环、死锁、阻塞、页面打开慢等问题，打线程dump是最好的解决问题的途径。所谓线程dump也就是线程堆栈，获取到线程堆栈有两步：\n\n1）获取到线程的pid，可以通过使用jps命令，在Linux环境下还可以使用ps -ef | grep java\n\n2）打印线程堆栈，可以通过使用jstack pid命令，在Linux环境下还可以使用kill -3 pid\n\n另外提一点，Thread类提供了一个getStackTrace()方法也可以用于获取线程堆栈。这是一个实例方法，因此此方法是和具体线程实例绑定的，每次获取获取到的是具体某个线程当前运行的堆栈。\n\n> **9、一个线程如果出现了运行时异常会怎么样**\n\n如果这个异常没有被捕获的话，这个线程就停止执行了。另外重要的一点是：**如果这个线程持有某个某个对象的监视器，那么这个对象监视器会被立即释放**\n\n> **10、如何在两个线程之间共享数据**\n\n通过在线程之间共享对象就可以了，然后通过wait/notify/notifyAll、await/signal/signalAll进行唤起和等待，比方说阻塞队列BlockingQueue就是为线程之间共享数据而设计的\n\n> **11、sleep方法和wait方法有什么区别**\n\n这个问题常问，sleep方法和wait方法都可以用来放弃CPU一定的时间，不同点在于如果线程持有某个对象的监视器，sleep方法不会放弃这个对象的监视器，wait方法会放弃这个对象的监视器\n\n> **12、生产者消费者模型的作用是什么**\n\n这个问题很理论，但是很重要：\n\n1）**通过平衡生产者的生产能力和消费者的消费能力来提升整个系统的运行效率**，这是生产者消费者模型最重要的作用\n\n2）解耦，这是生产者消费者模型附带的作用，解耦意味着生产者和消费者之间的联系少，联系越少越可以独自发展而不需要收到相互的制约\n\n> **13、ThreadLocal有什么用**\n\n简单说ThreadLocal就是一种以**空间换时间**的做法，在每个Thread里面维护了一个以开地址法实现的ThreadLocal.ThreadLocalMap，把数据进行隔离，数据不共享，自然就没有线程安全方面的问题了\n\n> **14、为什么wait()方法和notify()/notifyAll()方法要在同步块中被调用**\n\n这是JDK强制的，wait()方法和notify()/notifyAll()方法在调用前都必须先获得对象的锁\n\n> **15、wait()方法和notify()/notifyAll()方法在放弃对象监视器时有什么区别**\n\nwait()方法和notify()/notifyAll()方法在放弃对象监视器的时候的区别在于：**wait()方法立即释放对象监视器，notify()/notifyAll()方法则会等待线程剩余代码执行完毕才会放弃对象监视器**。\n\n> **16、为什么要使用线程池**\n\n避免频繁地创建和销毁线程，达到线程对象的重用。另外，使用线程池还可以根据项目灵活地控制并发的数目。点击[这里](https://mp.weixin.qq.com/s?__biz=MzI3ODcxMzQzMw==&mid=2247483824&idx=1&sn=7e34a3944a93d649d78d618cf04e0619&scene=21#wechat_redirect)学习线程池详解。\n\n> **17、怎么唤醒一个阻塞的线程**\n\n如果线程是因为调用了wait()、sleep()或者join()方法而导致的阻塞，可以中断线程，并且通过抛出InterruptedException来唤醒它；如果线程遇到了IO阻塞，无能为力，因为IO是操作系统实现的，Java代码并没有办法直接接触到操作系统。\n\n> **18、不可变对象对多线程有什么帮助**\n\n前面有提到过的一个问题，不可变对象保证了对象的内存可见性，对不可变对象的读取不需要进行额外的同步手段，提升了代码执行效率。\n\n> **19、什么是多线程的上下文切换**\n\n多线程的上下文切换是指CPU控制权由一个已经正在运行的线程切换到另外一个就绪并等待获取CPU执行权的线程的过程。\n\n> **20、线程类的构造方法、静态块是被哪个线程调用的**\n\n这是一个非常刁钻和狡猾的问题。请记住：线程类的构造方法、静态块是被new这个线程类所在的线程所调用的，而run方法里面的代码才是被线程自身所调用的。\n\n如果说上面的说法让你感到困惑，那么我举个例子，假设Thread2中new了Thread1，main函数中new了Thread2，那么：\n\n1）Thread2的构造方法、静态块是main线程调用的，Thread2的run()方法是Thread2自己调用的\n\n2）Thread1的构造方法、静态块是Thread2调用的，Thread1的run()方法是Thread1自己调用的\n\n\n> **21、高并发、任务执行时间短的业务怎样使用线程池？并发不高、任务执行时间长的业务怎样使用线程池？并发高、业务执行时间长的业务怎样使用线程池？**\n\n这是我在并发编程网上看到的一个问题，把这个问题放在最后一个，希望每个人都能看到并且思考一下，因为这个问题非常好、非常实际、非常专业。关于这个问题，个人看法是：\n\n1）高并发、任务执行时间短的业务，线程池线程数可以设置为CPU核数+1，减少线程上下文的切换\n\n2）并发不高、任务执行时间长的业务要区分开看：\n\na）假如是业务时间长集中在IO操作上，也就是IO密集型的任务，因为IO操作并不占用CPU，所以不要让所有的CPU闲下来，可以加大线程池中的线程数目，让CPU处理更多的业务\n\nb）假如是业务时间长集中在计算操作上，也就是计算密集型任务，这个就没办法了，和（1）一样吧，线程池中的线程数设置得少一些，减少线程上下文的切换\n\nc）并发高、业务执行时间长，解决这种类型任务的关键不在于线程池而在于整体架构的设计，看看这些业务里面某些数据是否能做缓存是第一步，增加服务器是第二步，至于线程池的设置，设置参考其他有关线程池的文章。最后，业务执行时间长的问题，也可能需要分析一下，看看能不能使用中间件对任务进行拆分和解耦。\n\n## 参考文章\nhttps://blog.csdn.net/zl1zl2zl3/article/details/81868173\nhttps://www.runoob.com/java/java-multithreading.html\nhttps://blog.csdn.net/qq_38038480/article/details/80584715\nhttps://blog.csdn.net/tongxuexie/article/details/80145663\nhttps://www.cnblogs.com/snow-flower/p/6114765.html\n\n\n"
  },
  {
    "path": "docs/Java/basic/序列化和反序列化.md",
    "content": "# 目录\n  * [序列化与反序列化概念](#序列化与反序列化概念)\n    * [Java对象的序列化与反序列化](#java对象的序列化与反序列化)\n    * [相关接口及类](#相关接口及类)\n  * [序列化ID](#序列化id)\n    * [静态变量不参与序列化](#静态变量不参与序列化)\n  * [探究ArrayList的序列化](#探究arraylist的序列化)\n  * [如何自定义的序列化和反序列化策略](#如何自定义的序列化和反序列化策略)\n  * [为什么要实现Serializable](#为什么要实现serializable)\n  * [序列化知识点总结](#序列化知识点总结)\n  * [参考文章](#参考文章)\n\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章首发于我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《夯实Java基础系列博文》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n该系列博文会告诉你如何从入门到进阶，一步步地学习Java基础知识，并上手进行实战，接着了解每个Java知识点背后的实现原理，更完整地了解整个Java技术体系，形成自己的知识框架。为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n本文参考 http://www.importnew.com/17964.html和\nhttps://www.ibm.com/developerworks/cn/java/j-lo-serial/\n\n## 序列化与反序列化概念\n\n序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。一般将一个对象存储至一个储存媒介，例如档案或是记亿体缓冲等。在网络传输过程中，可以是字节或是XML等格式。而字节的或XML编码格式可以还原完全相等的对象。这个相反的过程又称为反序列化。\n\n### Java对象的序列化与反序列化\n\n在Java中，我们可以通过多种方式来创建对象，并且只要对象没有被回收我们都可以复用该对象。但是，我们创建出来的这些Java对象都是存在于JVM的堆内存中的。\n\n只有JVM处于运行状态的时候，这些对象才可能存在。一旦JVM停止运行，这些对象的状态也就随之而丢失了。\n\n但是在真实的应用场景中，我们需要将这些对象持久化下来，并且能够在需要的时候把对象重新读取出来。Java的对象序列化可以帮助我们实现该功能。\n\n> 对象序列化机制（object serialization）是Java语言内建的一种对象持久化方式，通过对象序列化，可以把对象的状态保存为字节数组，并且可以在有需要的时候将这个字节数组通过反序列化的方式再转换成对象。\n\n对象序列化可以很容易的在JVM中的活动对象和字节数组（流）之间进行转换。\n\n在Java中，对象的序列化与反序列化被广泛应用到RMI(远程方法调用)及网络传输中。\n\n### 相关接口及类\n\nJava为了方便开发人员将Java对象进行序列化及反序列化提供了一套方便的API来支持。其中包括以下接口和类：\n\n    java.io.Serializable\n    \n    java.io.Externalizable\n    \n    ObjectOutput\n    \n    ObjectInput\n    \n    ObjectOutputStream\n    \n    ObjectInputStream\n    \n    Serializable 接口\n\n**类通过实现 java.io.Serializable 接口以启用其序列化功能。**\n\n未实现此接口的类将无法使其任何状态序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。序列化接口没有方法或字段，仅用于标识可序列化的语义。 (该接口并没有方法和字段，为什么只有实现了该接口的类的对象才能被序列化呢？)\n\n当试图对一个对象进行序列化的时候，如果遇到不支持 Serializable 接口的对象。在此情况下，将抛出NotSerializableException。\n\n如果要序列化的类有父类，要想同时将在父类中定义过的变量持久化下来，那么父类也应该集成java.io.Serializable接口。\n\n下面是一个实现了java.io.Serializable接口的类\n````\npublic class 序列化和反序列化 {\n    public static void main(String[] args) {\n\n    }\n    //注意，内部类不能进行序列化，因为它依赖于外部类\n    @Test\n    public void test() throws IOException {\n        A a = new A();\n        a.i = 1;\n        a.s = \"a\";\n        FileOutputStream fileOutputStream = null;\n        FileInputStream fileInputStream = null;\n        try {\n            //将obj写入文件\n            fileOutputStream = new FileOutputStream(\"temp\");\n            ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);\n            objectOutputStream.writeObject(a);\n            fileOutputStream.close();\n            //通过文件读取obj\n            fileInputStream = new FileInputStream(\"temp\");\n            ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);\n            A a2 = (A) objectInputStream.readObject();\n            fileInputStream.close();\n            System.out.println(a2.i);\n            System.out.println(a2.s);\n            //打印结果和序列化之前相同\n        } catch (IOException e) {\n            e.printStackTrace();\n        } catch (ClassNotFoundException e) {\n            e.printStackTrace();\n        }\n    }\n}\n\nclass A implements Serializable {\n\n    int i;\n    String s;\n}\n````\n**Externalizable接口**\n\n除了Serializable 之外，java中还提供了另一个序列化接口Externalizable\n\n为了了解Externalizable接口和Serializable接口的区别，先来看代码，我们把上面的代码改成使用Externalizable的形式。\n````\nclass B implements Externalizable {\n    //必须要有公开无参构造函数。否则报错\n    public B() {\n\n    }\n    int i;\n    String s;\n    @Override\n    public void writeExternal(ObjectOutput out) throws IOException {\n\n    }\n\n    @Override\n    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {\n\n    }\n}\n\n@Test\n    public void test2() throws IOException, ClassNotFoundException {\n        B b = new B();\n        b.i = 1;\n        b.s = \"a\";\n        //将obj写入文件\n        FileOutputStream fileOutputStream = new FileOutputStream(\"temp\");\n        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);\n        objectOutputStream.writeObject(b);\n        fileOutputStream.close();\n        //通过文件读取obj\n        FileInputStream fileInputStream = new FileInputStream(\"temp\");\n        ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);\n        B b2 = (B) objectInputStream.readObject();\n        fileInputStream.close();\n        System.out.println(b2.i);\n        System.out.println(b2.s);\n        //打印结果为0和null，即初始值，没有被赋值\n        //0\n        //null\n    }\n````\n通过上面的实例可以发现，对B类进行序列化及反序列化之后得到的对象的所有属性的值都变成了默认值。也就是说，之前的那个对象的状态并没有被持久化下来。这就是Externalizable接口和Serializable接口的区别：\n\nExternalizable继承了Serializable，该接口中定义了两个抽象方法：writeExternal()与readExternal()。\n\n当使用Externalizable接口来进行序列化与反序列化的时候需要开发人员重写writeExternal()与readExternal()方法。由于上面的代码中，并没有在这两个方法中定义序列化实现细节，所以输出的内容为空。\n\n> 还有一点值得注意：在使用Externalizable进行序列化的时候，在读取对象时，会调用被序列化类的无参构造器去创建一个新的对象，然后再将被保存对象的字段的值分别填充到新对象中。所以，实现Externalizable接口的类必须要提供一个public的无参的构造器。\n\n````\nclass C implements Externalizable {\n    int i;\n    int j;\n    String s;\n    public C() {\n\n    }\n    //实现下面两个方法可以选择序列化中需要被复制的成员。\n    //并且，写入顺序和读取顺序要一致，否则报错。\n    //可以写入多个同类型变量，顺序保持一致即可。\n    @Override\n    public void writeExternal(ObjectOutput out) throws IOException {\n        out.writeInt(i);\n        out.writeInt(j);\n        out.writeObject(s);\n    }\n\n    @Override\n    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {\n        i = in.readInt();\n        j = in.readInt();\n        s = (String) in.readObject();\n    }\n}\n\n@Test\npublic void test3() throws IOException, ClassNotFoundException {\n    C c = new C();\n    c.i = 1;\n    c.j = 2;\n    c.s = \"a\";\n    //将obj写入文件\n    FileOutputStream fileOutputStream = new FileOutputStream(\"temp\");\n    ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);\n    objectOutputStream.writeObject(c);\n    fileOutputStream.close();\n    //通过文件读取obj\n    FileInputStream fileInputStream = new FileInputStream(\"temp\");\n    ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);\n    C c2 = (C) objectInputStream.readObject();\n    fileInputStream.close();\n    System.out.println(c2.i);\n    System.out.println(c2.j);\n    System.out.println(c2.s);\n    //打印结果为0和null，即初始值，没有被赋值\n    //0\n    //null\n}\n````\n\n## 序列化ID\n\n序列化 ID 问题\n情境：两个客户端 A 和 B 试图通过网络传递对象数据，A 端将对象 C 序列化为二进制数据再传给 B，B 反序列化得到 C。\n\n问题：C 对象的全类路径假设为 com.inout.Test，在 A 和 B 端都有这么一个类文件，功能代码完全一致。也都实现了 Serializable 接口，但是反序列化时总是提示不成功。\n\n解决：虚拟机是否允许反序列化，不仅取决于类路径和功能代码是否一致，一个非常重要的一点是两个类的序列化 ID 是否一致（就是 private static final long serialVersionUID = 1L）。清单 1 中，虽然两个类的功能代码完全一致，但是序列化 ID 不同，他们无法相互序列化和反序列化。\n````\npackage com.inout; \n \nimport java.io.Serializable; \n \npublic class A implements Serializable { \n \n    private static final long serialVersionUID = 1L; \n \n    private String name; \n    \n    public String getName() \n    { \n        return name; \n    } \n    \n    public void setName(String name) \n    { \n        this.name = name; \n    } \n} \n \npackage com.inout; \n \nimport java.io.Serializable; \n \npublic class A implements Serializable { \n \n    private static final long serialVersionUID = 2L; \n    \n    private String name; \n    \n    public String getName() \n    { \n        return name; \n    } \n    \n    public void setName(String name) \n    { \n        this.name = name; \n    } \n}\n````\n\n### 静态变量不参与序列化\n\n清单 2 中的 main 方法，将对象序列化后，修改静态变量的数值，再将序列化对象读取出来，然后通过读取出来的对象获得静态变量的数值并打印出来。依照清单 2，这个 System.out.println(t.staticVar) 语句输出的是 10 还是 5 呢？\n````\npublic class Test implements Serializable {\n \n    private static final long serialVersionUID = 1L;\n \n    public static int staticVar = 5;\n \n    public static void main(String[] args) {\n        try {\n            //初始时staticVar为5\n            ObjectOutputStream out = new ObjectOutputStream(\n                    new FileOutputStream(\"result.obj\"));\n            out.writeObject(new Test());\n            out.close();\n \n            //序列化后修改为10\n            Test.staticVar = 10;\n \n            ObjectInputStream oin = new ObjectInputStream(new FileInputStream(\n                    \"result.obj\"));\n            Test t = (Test) oin.readObject();\n            oin.close();\n             \n            //再读取，通过t.staticVar打印新的值\n            System.out.println(t.staticVar);\n             \n        } catch (FileNotFoundException e) {\n            e.printStackTrace();\n        } catch (IOException e) {\n            e.printStackTrace();\n        } catch (ClassNotFoundException e) {\n            e.printStackTrace();\n        }\n    }\n}\n````\n\n最后的输出是 10，对于无法理解的读者认为，打印的 staticVar 是从读取的对象里获得的，应该是保存时的状态才对。之所以打印 10 的原因在于序列化时，并不保存静态变量，这其实比较容易理解，序列化保存的是对象的状态，静态变量属于类的状态，因此 序列化并不保存静态变量。\n\n## 探究ArrayList的序列化\n\nArrayList的序列化\n在介绍ArrayList序列化之前，先来考虑一个问题：\n\n如何自定义的序列化和反序列化策略\n\n带着这个问题，我们来看java.util.ArrayList的源码\n\n````\n    public class ArrayList<E> extends AbstractList<E>\n            implements List<E>, RandomAccess, Cloneable, java.io.Serializable\n    {\n        private static final long serialVersionUID = 8683452581122892189L;\n        transient Object[] elementData; // non-private to simplify nested class access\n        private int size;\n    }\n````\n\n笔者省略了其他成员变量，从上面的代码中可以知道ArrayList实现了java.io.Serializable接口，那么我们就可以对它进行序列化及反序列化。\n\n因为elementData是transient的（1.8好像改掉了这一点)，所以我们认为这个成员变量不会被序列化而保留下来。我们写一个Demo，验证一下我们的想法：\n````\npublic class ArrayList的序列化 {\n    public static void main(String[] args) throws IOException, ClassNotFoundException {\n        ArrayList list = new ArrayList();\n        list.add(\"a\");\n        list.add(\"b\");\n        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(\"arr\"));\n        objectOutputStream.writeObject(list);\n        objectOutputStream.close();\n        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(\"arr\"));\n        ArrayList list1 = (ArrayList) objectInputStream.readObject();\n        objectInputStream.close();\n        System.out.println(Arrays.toString(list.toArray()));\n        //序列化成功，里面的元素保持不变。\n    }\n````\n了解ArrayList的人都知道，ArrayList底层是通过数组实现的。那么数组elementData其实就是用来保存列表中的元素的。通过该属性的声明方式我们知道，他是无法通过序列化持久化下来的。那么为什么code 4的结果却通过序列化和反序列化把List中的元素保留下来了呢？\n\n**writeObject和readObject方法**\n\n在ArrayList中定义了来个方法： writeObject和readObject。\n\n这里先给出结论:\n\n> 在序列化过程中，如果被序列化的类中定义了writeObject 和 readObject 方法，虚拟机会试图调用对象类里的 writeObject 和 readObject 方法，进行用户自定义的序列化和反序列化。\n>\n> 如果没有这样的方法，则默认调用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。\n>\n> 用户自定义的 writeObject 和 readObject 方法可以允许用户控制序列化的过程，比如可以在序列化的过程中动态改变序列化的数值。\n\n来看一下这两个方法的具体实现：\n````\nprivate void readObject(java.io.ObjectInputStream s)\n    throws java.io.IOException, ClassNotFoundException {\n    elementData = EMPTY_ELEMENTDATA;\n\n    // Read in size, and any hidden stuff\n    s.defaultReadObject();\n\n    // Read in capacity\n    s.readInt(); // ignored\n\n    if (size > 0) {\n        // be like clone(), allocate array based upon size not capacity\n        ensureCapacityInternal(size);\n\n        Object[] a = elementData;\n        // Read in all elements in the proper order.\n        for (int i=0; i<size; i++) {\n            a[i] = s.readObject();\n        }\n    }\n}\n\nprivate void writeObject(java.io.ObjectOutputStream s)\n    throws java.io.IOException{\n    // Write out element count, and any hidden stuff\n    int expectedModCount = modCount;\n    s.defaultWriteObject();\n\n    // Write out size as capacity for behavioural compatibility with clone()\n    s.writeInt(size);\n\n    // Write out all elements in the proper order.\n    for (int i=0; i<size; i++) {\n        s.writeObject(elementData[i]);\n    }\n\n    if (modCount != expectedModCount) {\n        throw new ConcurrentModificationException();\n    }\n}\n````\n那么为什么ArrayList要用这种方式来实现序列化呢？\n\n    why transient\n    ArrayList实际上是动态数组，每次在放满以后自动增长设定的长度值，如果数组自动增长长度设为100，而实际只放了一个元素，那就会序列化99个null元素。为了保证在序列化的时候不会将这么多null同时进行序列化，ArrayList把元素数组设置为transient。\n    \n    why writeObject and readObject\n    前面说过，为了防止一个包含大量空对象的数组被序列化，为了优化存储，所以，ArrayList使用transient来声明elementData。 但是，作为一个集合，在序列化过程中还必须保证其中的元素可以被持久化下来，所以，通过重写writeObject 和 readObject方法的方式把其中的元素保留下来。\n    \n    writeObject方法把elementData数组中的元素遍历的保存到输出流（ObjectOutputStream）中。\n    \n    readObject方法从输入流（ObjectInputStream）中读出对象并保存赋值到elementData数组中。\n\n\n\n## 如何自定义的序列化和反序列化策略\n\n延续上一部分，刚刚我们明白了ArrayList序列化数组元素的原理。\n\n至此，我们先试着来回答刚刚提出的问题：\n\n如何自定义的序列化和反序列化策略\n\n答：可以通过在被序列化的类中增加writeObject 和 readObject方法。那么问题又来了：\n\n> 虽然ArrayList中写了writeObject 和 readObject 方法，但是这两个方法并没有显示的被调用啊。\n>\n> 那么如果一个类中包含writeObject 和 readObject 方法，那么这两个方法是怎么被调用的呢?\n\nObjectOutputStream\n从code 4中，我们可以看出，对象的序列化过程通过ObjectOutputStream和ObjectInputputStream来实现的，那么带着刚刚的问题，我们来分析一下ArrayList中的writeObject 和 readObject 方法到底是如何被调用的呢？\n\n为了节省篇幅，这里给出ObjectOutputStream的writeObject的调用栈：\n\nwriteObject ---> writeObject0 --->writeOrdinaryObject--->writeSerialData--->invokeWriteObject\n\n这里看一下invokeWriteObject：\n````\nvoid invokeWriteObject(Object obj, ObjectOutputStream out)\n        throws IOException, UnsupportedOperationException\n    {\n        if (writeObjectMethod != null) {\n            try {\n                writeObjectMethod.invoke(obj, new Object[]{ out });\n            } catch (InvocationTargetException ex) {\n                Throwable th = ex.getTargetException();\n                if (th instanceof IOException) {\n                    throw (IOException) th;\n                } else {\n                    throwMiscException(th);\n                }\n            } catch (IllegalAccessException ex) {\n                // should not occur, as access checks have been suppressed\n                throw new InternalError(ex);\n            }\n        } else {\n            throw new UnsupportedOperationException();\n        }\n    }\n````\n其中writeObjectMethod.invoke(obj, new Object[]{ out });是关键，通过反射的方式调用writeObjectMethod方法。官方是这么解释这个writeObjectMethod的：\n\nclass-defined writeObject method, or null if none\n\n在我们的例子中，这个方法就是我们在ArrayList中定义的writeObject方法。通过反射的方式被调用了。\n\n至此，我们先试着来回答刚刚提出的问题：\n\n    如果一个类中包含writeObject 和 readObject 方法，那么这两个方法是怎么被调用的?\n    \n    答：在使用ObjectOutputStream的writeObject方法和ObjectInputStream的readObject方法时，会通过反射的方式调用。\n    \n## 为什么要实现Serializable\n至此，我们已经介绍完了ArrayList的序列化方式。那么，不知道有没有人提出这样的疑问：\n\nSerializable明明就是一个空的接口，它是怎么保证只有实现了该接口的方法才能进行序列化与反序列化的呢？\n````\nSerializable接口的定义：\n\npublic interface Serializable {\n}\n````\n\n读者可以尝试把code 1中的继承Serializable的代码去掉，再执行code 2，会抛出java.io.NotSerializableException。\n\n其实这个问题也很好回答，我们再回到刚刚ObjectOutputStream的writeObject的调用栈：\n\n    writeObject ---> writeObject0 --->writeOrdinaryObject--->writeSerialData--->invokeWriteObject\n\nwriteObject0方法中有这么一段代码：\n````\nif (obj instanceof String) {\n    writeString((String) obj, unshared);\n} else if (cl.isArray()) {\n    writeArray(obj, desc, unshared);\n} else if (obj instanceof Enum) {\n    writeEnum((Enum<?>) obj, desc, unshared);\n} else if (obj instanceof Serializable) {\n    writeOrdinaryObject(obj, desc, unshared);\n} else {\n    if (extendedDebugInfo) {\n        throw new NotSerializableException(\n            cl.getName() + \"\\n\" + debugInfoStack.toString());\n    } else {\n        throw new NotSerializableException(cl.getName());\n    }\n}\n````\n在进行序列化操作时，会判断要被序列化的类是否是Enum、Array和Serializable类型，如果不是则直接抛出NotSerializableException。\n\n## 序列化知识点总结\n\n> 1、如果一个类想被序列化，需要实现Serializable接口。否则将抛出NotSerializableException异常，这是因为，在序列化操作过程中会对类型进行检查，要求被序列化的类必须属于Enum、Array和Serializable类型其中的任何一种。\n>\n> 2、通过ObjectOutputStream和ObjectInputStream对对象进行序列化及反序列化\n>\n> 3、虚拟机是否允许反序列化，不仅取决于类路径和功能代码是否一致，一个非常重要的一点是两个类的序列化 ID 是否一致（就是 private static final long serialVersionUID）\n>\n> 序列化 ID 在 Eclipse 下提供了两种生成策略，一个是固定的 1L，一个是随机生成一个不重复的 long 类型数据（实际上是使用 JDK 工具生成），在这里有一个建议，如果没有特殊需求，就是用默认的 1L 就可以，这样可以确保代码一致时反序列化成功。那么随机生成的序列化 ID 有什么作用呢，有些时候，通过改变序列化 ID 可以用来限制某些用户的使用。\n>\n> 4、序列化并不保存静态变量。\n>\n> 5、要想将父类对象也序列化，就需要让父类也实现Serializable 接口。\n>\n> 6、Transient 关键字的作用是控制变量的序列化，在变量声明前加上该关键字，可以阻止该变量被序列化到文件中，在被反序列化后，transient 变量的值被设为初始值，如 int 型的是 0，对象型的是 null。\n>\n> 7、服务器端给客户端发送序列化对象数据，对象中有一些数据是敏感的，比如密码字符串等，希望对该密码字段在序列化时，进行加密，而客户端如果拥有解密的密钥，只有在客户端进行反序列化时，才可以对密码进行读取，这样可以一定程度保证序列化对象的数据安全。\n>\n> 8、在类中增加writeObject 和 readObject 方法可以实现自定义序列化策略\n\n\n## 参考文章\n\nhttps://blog.csdn.net/qq_34988624/article/details/86592229\nhttps://www.meiwen.com.cn/subject/slhvhqtx.html\nhttps://blog.csdn.net/qq_34988624/article/details/86592229\nhttps://segmentfault.com/a/1190000012220863\nhttps://my.oschina.net/wuxinshui/blog/1511484\nhttps://blog.csdn.net/hukailee/article/details/81107412\n\n"
  },
  {
    "path": "docs/Java/basic/抽象类和接口.md",
    "content": "# 目录\n  * [抽象类介绍](#抽象类介绍)\n    * [为什么要用抽象类](#为什么要用抽象类)\n    * [一个抽象类小故事](#一个抽象类小故事)\n    * [一个抽象类小游戏](#一个抽象类小游戏)\n  * [接口介绍](#接口介绍)\n    * [接口与类相似点：](#接口与类相似点：)\n    * [接口与类的区别：](#接口与类的区别：)\n    * [接口特性](#接口特性)\n    * [抽象类和接口的区别](#抽象类和接口的区别)\n    * [接口的使用：](#接口的使用：)\n    * [接口最佳实践：设计模式中的工厂模式](#接口最佳实践：设计模式中的工厂模式)\n  * [接口与抽象类的本质区别是什么？](#接口与抽象类的本质区别是什么？)\n    * [基本语法区别](#基本语法区别)\n    * [设计思想区别](#设计思想区别)\n    * [如何回答面试题：接口和抽象类的区别?](#如何回答面试题：接口和抽象类的区别)\n  * [参考文章](#参考文章)\n\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章首发于我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《夯实Java基础系列博文》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n该系列博文会告诉你如何从入门到进阶，一步步地学习Java基础知识，并上手进行实战，接着了解每个Java知识点背后的实现原理，更完整地了解整个Java技术体系，形成自己的知识框架。为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n\n## 抽象类介绍\n\n什么是抽象？\n\n百度给出的解释是：从具体事物抽出、概括出它们共同的方面、本质属性与关系等，而将个别的、非本质的方面、属性与关系舍弃，这种思维过程，称为抽象。\n\n这句话概括了抽象的概念，而在Java中，你可以只给出方法的定义不去实现方法的具体事物，由子类去根据具体需求来具体实现。\n\n这种只给出方法定义而不具体实现的方法被称为抽象方法，抽象方法是没有方法体的，在代码的表达上就是没有“{}”。\n\n包含一个或多个抽象方法的类也必须被声明为抽象类。\n\n使用abstract修饰符来表示抽象方法以及抽象类。\n````\n    //有抽象方法的类也必须被声明为abstract\n    public abstract class Test1 {\n     \n    \t//抽象方法，不能有“{}”\n    \tpublic abstract void f();\n    \t\n    }\n````\n抽象类除了包含抽象方法外，还可以包含具体的变量和具体的方法。类即使不包含抽象方法，也可以被声明为抽象类，防止被实例化。\n\n抽象类不能被实例化，也就是不能使用new关键字来得到一个抽象类的实例，抽象方法必须在子类中被实现。\n````\n    //有抽象方法的类也必须被声明为abstract\n    public class Test1 {\n     \n    \tpublic static void main(String[] args) {\n    \t\tTeacher teacher=new Teacher(\"教师\");\n    \t\tteacher.work();\n    \t\t\n    \t\tDriver driver=new Driver(\"驾驶员\");\n    \t\tdriver.work();\n    \t}\n    }\n    //一个抽象类\n    abstract class People{\n    \t//抽象方法\n    \tpublic abstract void work();\n    }\n    class Teacher extends People{\n    \tprivate String work;\n    \tpublic Teacher(String work) {\n    \t\tthis.work=work;\n    \t}\n    \t@Override\n    \tpublic void work() {\n    \t\tSystem.out.println(\"我的职业是\"+this.work);\n    \t}\t\n    }\n    class Driver extends People{\n    \tprivate String work;\n    \tpublic Driver(String work) {\n    \t\tthis.work=work;\n    \t}\n    \t@Override\n    \tpublic void work() {\n    \t\tSystem.out.println(\"我的职业是\"+this.work);\n    \t}\n    }\n````\n\n> 运行结果：\n> 我的职业是教师\n> 我的职业是驾驶员\n> 几点说明:\n\n抽象类不能直接使用，需要子类去实现抽象类，然后使用其子类的实例。然而可以创建一个变量，其类型也是一个抽象类，并让他指向具体子类的一个实例，也就是可以使用抽象类来充当形参，实际实现类为实参，也就是多态的应用。\n````\n    People people=new Teacher(\"教师\");\n    people.work();\n````\n不能有抽象构造方法或抽象静态方法。\n\n如果非要使用new关键在来创建一个抽象类的实例的话，可以这样：\n````\n    People people=new People() {\n    \t@Override\n    \tpublic void work() {\n    \t    //实现这个方法的具体功能\n    \t}\n    \t\t};\n````\n个人不推荐这种方法，代码读起来有点累。\n\n在下列情况下，一个类将成为抽象类：\n\n    当一个类的一个或多个方法是抽象方法时。\n    当类是一个抽象类的子类，并且不能实现父类的所有抽象方法时。\n    当一个类实现一个接口，并且不能实现接口的所有抽象方法时。\n    注意：\n    上面说的是这些情况下一个类将称为抽象类，没有说抽象类就一定会是这些情况。\n    抽象类可以不包含抽象方法，包含抽象方法的类就一定是抽象类。\n    事实上，抽象类可以是一个完全正常实现的类。\n\n### 为什么要用抽象类\n\n老是在想为什么要引用抽象类，一般类不就够用了吗。一般类里定义的方法，子类也可以覆盖，没必要定义成抽象的啊。\n\n看了下面的文章，明白了一点。\n\n其实不是说抽象类有什么用，一般类确实也能满足应用，但是现实中确实有些父类中的方法确实没有必要写，因为各个子类中的这个方法肯定会有不同，所以没有必要再父类里写。当然你也可以把抽象类都写成非抽象类，但是这样没有必要。\n\n而写成抽象类，这样别人看到你的代码，或你看到别人的代码，你就会注意抽象方法，而知道这个方法是在子类中实现的，所以，有个提示作用。\n\n### 一个抽象类小故事\n\n下面看一个关于抽象类的小故事\n    \n    问你个问题，你知道什么是“东西”吗？什么是“物体”吗？ \n    “麻烦你，小王。帮我把那个东西拿过来好吗” \n    在生活中，你肯定用过这个词－－东西。 \n    小王：“你要让我帮你拿那个水杯吗？” \n    你要的是水杯类的对象。而东西是水杯的父类。通常东西类没有实例对象，但我们有时需要东西的引用指向它的子类实例。 \n    \n    你看你的房间乱成什么样子了，以后不要把东西乱放了，知道么？ \n\n上面讲的只是子类和父类。而没有说明抽象类的作用。抽象类是据有一个或多个抽象方法的类，必须声明为抽象类。抽象类的特点是，不能创建实例。 \n\n这些该死的抽象类，也不知道它有什么屁用。我非要把它改一改不可。把抽象类中的抽象方法都改为空实现。也就是给抽象方法加上一个方法体，不过这个方法体是空的。这回抽象类就没有抽象方法了。它就可以不在抽象了。 \n\n当你这么尝试之后，你发现，原来的代码没有任何变化。大家都还是和原来一样，工作的很好。你这回可能更加相信，抽象类根本就没有什么用。但总是不死心，它应该有点用吧，不然创造Java的这伙传说中的天才不成了傻子了吗？ \n\n### 一个抽象类小游戏\n\n接下来，我们来写一个小游戏。俄罗斯方块！我们来分析一下它需要什么类？\n\n我知道它要在一个矩形的房子里完成。这个房子的上面出现一个方块，慢慢的下落，当它接触到地面或是其它方块的尸体时，它就停止下落了。然后房子的上面又会出现一个新的方块，与前一个方块一样，也会慢慢的下落。在它还没有死亡之前，我可以尽量的移动和翻转它。这样可以使它起到落地时起到一定的作用，如果好的话，还可以减下少几行呢。这看起来好象人生一样，它在为后来人努力着。\n当然，我们不是真的要写一个游戏。所以我们简化它。我抽象出两个必须的类，一个是那个房间，或者就它地图也行。另一个是方块。我发现方块有很多种，数一下，共6种。它们都是四个小矩形构成的。但是它们还有很多不同，例如：它们的翻转方法不同。先把这个问题放到一边去，我们回到房子这个类中。\n\n房子上面总是有方块落下来，房子应该有个属性是方块。当一个方块死掉后，再创建一个方块，让它出现在房子的上面。当玩家要翻转方法时，它翻转的到底是哪个方块呢？当然，房子中只有一个方块可以被翻转，就是当前方块。它是房子的一个属性。那这个属性到底是什么类型的呢？方块有很多不同啊，一共有6种之多，我需要写六个类。一个属性不可能有六种类型吧。当然一个属性只能有一种类型。\n\n我们写一个方块类，用它来派生出6个子类。而房子类的当前方块属性的类型是方块类型。它可以指向任何子类。但是，当我调用当前方块的翻转方法时，它的子类都有吗？如果你把翻转方法写到方块类中，它的子类自然也就有了。可以这六种子类的翻转方法是不同的。我们知道'田'方块，它只有一种状态，无论你怎么翻转它。而长条的方块有两种状态。一种是‘－’，另一种是‘｜’。这可怎么办呢？我们知道Java的多态性，你可以让子类来重写父类的方法。也就是说，在父类中定义这个方法，子类在重写这个方法。\n\n那么在父类的这个翻转方法中，我写一些什么代码呢？让它有几种状态呢？因为我们不可能实例化一个方块类的实例，所以它的翻转方法中的代码并不重要。而子类必须去重写它。那么你可以在父类的翻转方法中不写任何代码，也就是空方法。\n\n我们发现，方法类不可能有实例，它的翻转方法的内容可以是任何的代码。而子类必须重写父类的翻转方法。这时，你可以把方块类写成抽象类，而它的抽象方法就是翻转方法。当然，你也可以把方块类写为非抽象的，也可以在方块类的翻转方法中写上几千行的代码。但这样好吗？难道你是微软派来的，非要说Java中的很多东西都是没有用的吗？\n\n当我看到方块类是抽象的，我会很关心它的抽象方法。我知道它的子类一定会重写它，而且，我会去找到抽象类的引用。它一定会有多态性的体现。\n\n但是，如果你没有这样做，我会认为可能会在某个地方，你会实例化一个方块类的实例，但我找了所有的地方都没有找到。最后我会大骂你一句，你是来欺骗我的吗，你这个白痴。\n\n把那些和“东西”差不多的类写成抽象的。而水杯一样的类就可以不是抽象的了。当然水杯也有几千块钱一个的和几块钱一个的。水杯也有子类，例如，我用的水杯都很高档，大多都是一次性的纸水杯。\n\n记住一点，面向对象不是来自于Java，面向对象就在你的生活中。而Java的面向对象是方便你解决复杂的问题。这不是说面向对象很简单，虽然面向对象很复杂，但Java知道，你很了解面向对象，因为它就在你身边。\n\n## 接口介绍\n\n接口（英文：Interface），在JAVA编程语言中是一个抽象类型，是抽象方法的集合，接口通常以interface来声明。一个类通过继承接口的方式，从而来继承接口的抽象方法。\n\n接口并不是类，编写接口的方式和类很相似，但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。\n\n除非实现接口的类是抽象类，否则该类要定义接口中的所有方法。\n\n接口无法被实例化，但是可以被实现。一个实现接口的类，必须实现接口内所描述的所有方法，否则就必须声明为抽象类。另外，在 Java 中，接口类型可用来声明一个变量，他们可以成为一个空指针，或是被绑定在一个以此接口实现的对象。\n\n### 接口与类相似点：\n\n*   一个接口可以有多个方法。\n*   接口文件保存在 .java 结尾的文件中，文件名使用接口名。\n*   接口的字节码文件保存在 .class 结尾的文件中。\n*   接口相应的字节码文件必须在与包名称相匹配的目录结构中。\n\n### 接口与类的区别：\n\n*   接口不能用于实例化对象。\n*   接口没有构造方法。\n*   接口中所有的方法必须是抽象方法。\n*   接口不能包含成员变量，除了 static 和 final 变量。\n*   接口不是被类继承了，而是要被类实现。\n*   接口支持多继承。\n\n### 接口特性\n\n*   接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 **public abstract**（只能是 public abstract，其他修饰符都会报错）。\n*   接口中可以含有变量，但是接口中的变量会被隐式的指定为 **public static final** 变量（并且只能是 public，用 private 修饰会报编译错误）。\n*   接口中的方法是不能在接口中实现的，只能由实现接口的类来实现接口中的方法。\n\n### 抽象类和接口的区别\n\n*   1\\. 抽象类中的方法可以有方法体，就是能实现方法的具体功能，但是接口中的方法不行。\n*   2\\. 抽象类中的成员变量可以是各种类型的，而接口中的成员变量只能是 **public static final** 类型的。\n*   3\\. 接口中不能含有静态代码块以及静态方法(用 static 修饰的方法)，而抽象类是可以有静态代码块和静态方法。\n*   4\\. 一个类只能继承一个抽象类，而一个类却可以实现多个接口。\n\n> **注**：JDK 1.8 以后，接口里可以有静态方法和方法体了。\n\n\n### 接口的使用：\n  我们来举个例子，定义一个抽象类People，一个普通子类Student，两个接口。子类Student继承父类People,并实现接口Study，Write\n\n代码演示：\n````\n    package demo;\n    //构建一个抽象类People\n    abstract class People{\n    \t//父类属性私有化\n    \tprivate String name;\n    \tprivate int age;\n    \t//提供父类的构造器\n    \tpublic People(String name,int age){\n    \t\tthis.name = name;\n    \t\tthis.age = age;\n    \t}\n    \t//提供获取和设置属性的getter()/setter()方法\n    \tpublic String getName() {\n    \t\treturn name;\n    \t}\n     \n    \tpublic int getAge() {\n    \t\treturn age;\n    \t}\n     \n    \tpublic void setName(String name) {\n    \t\tthis.name = name;\n    \t}\n     \n    \tpublic void setAge(int age) {\n    \t\tthis.age = age;\n    \t}\n    \t\n    \t//提供一个抽象方法\n    \tpublic abstract void talk();\t\n    }\n     \n    //定义一个接口\n    interface Study{\n    \t//设置课程数量为3\n    \tint COURSENUM = 3;\n    \t//构建一个默认方法\n    \tdefault void stu(){\n    \t\tSystem.out.println(\"学生需要学习\"+COURSENUM+\"门课程\");\n    \t}\n    }\n     \n    //再定义一个接口\n    interface Write{\n    \t//定义一个抽象方法\n    \tvoid print();\n    }\n     \n    //子类继承People,实现接口Study,Write\n    class Student extends People implements Study,Write{\n    \t//通过super关键字调用父类的构造器\n    \tpublic Student(String name, int age) {\n    \t\tsuper(name, age);\n    \t}\n    \t//实现父类的抽象方法\n    \tpublic void talk() {\n    \t\tSystem.out.println(\"我的名字叫\"+this.getName()+\",今年\"+this.getAge()+\"岁\");\n    \t}\n    \t//实现Write接口的抽象方法\n    \tpublic void print() {\n    \t\tSystem.out.println(\"学生会写作业\");\n    \t}\n    }\n     \n    public class InterfaceDemo{\n    \tpublic static void main(String[] args) {\n    \t\t//构建student对象\n    \t\tStudent student = new Student(\"dodo\", 22);\n    \t\t//调用父类的抽象方法\n    \t\tstudent.talk();\n    \t\t//调用接口Write中的抽象方法\n    \t\tstudent.print();\n    \t\t//调用接口Study中的默认方法\n    \t\tstudent.stu();\n    \t}\n    }\n````\n代码讲解：上述例子结合了抽象类和接口的知识，内容较多，同学们可以多看多敲一下，学习学习。\n\n接口的实现：类名 implements 接口名，有多个接口名，用“，”隔开即可。\n\n\n\n接口的作用——制定标准\n   接口师表尊，所谓的标准，指的是各方共同遵守一个守则，只有操作标准统一了，所有的参与者才可以按照统一的规则操作。\n\n    如电脑可以和各个设备连接，提供统一的USB接口，其他设备只能通过USB接口和电脑相连\n    \n    代码实现：\n````   \n    package demo;\n     \n    interface USB \n    {\n        public void work() ;    // 拿到USB设备就表示要进行工作\n    }\n     \n    class Print implements USB \t\t//实现类（接口类）\t\n    {   \t\t\t\t\t\t\t// 打印机实现了USB接口标准（对接口的方法实现）\n        public void work() \n       {\n            System.out.println(\"打印机用USB接口，连接,开始工作。\") ;\n        }\n    }\n    class Flash implements USB \t\t//实现类（接口类）\t \t\t \t\n    {  \t\t\t\t\t\t\t\t// U盘实现了USB接口标准（对接口的方法实现）\n        public void work() \n        {\n            System.out.println(\"U盘使用USB接口，连接,开始工作。\") ;\n        }\n    }\n     \n    class Computer \n    {\n        public void plugin(USB usb) \t\t\t//plugin的意思是插件，参数为接收接口类\n        {\n            usb.work() ;    // 按照固定的方式进行工作\n        }\n    }\n    public class InterfaceStandards { public static void main(String args[]) { Computer computer = new Computer() ; computer.plugin(new Print()) ; //实例化接口类， 在电脑上使用打印机 computer.plugin(new Flash()) ; //实例化接口类， 在电脑上使用U盘 }}\n\n代码讲解：上述例子，就给我们展示了接口制定标准的作用，怎么指定的呢？看下面代码\n    \n    class Computer \n    {\n        public void plugin(USB usb) \t\t\t//plugin的意思是插件，参数为接收接口类\n        {\n            usb.work() ;    // 按照固定的方式进行工作\n        }\n    }\n\n我们可以看到，Computer类里面定义了一个方法plugin()，它的参数内写的是USB usb,即表示plugin()方法里，接收的是一个usb对象，而打印机和U盘对象可以通过向上转型当参数，传入方法里。我们来重新写一个main方法帮助大家理解\n\n代码演示：\n\n    public class InterfaceStandards \n    {\n        public static void main(String args[]) \n        {   \n            Computer computer = new Computer() ;\n            \n            USB usb = new Print();\n            computer.plugin(usb) ;   //实例化接口类， 在电脑上使用打印机\n            usb = new Flash();\n            computer.plugin(usb) ;   //实例化接口类， 在电脑上使用U盘\n        }\n    }\n````\n代码讲解：我们修改了主函数后，发现，使用了两次的向上转型给了USB，虽然使用的都是usb对象，但赋值的子类对象不一样，实现的方法体也不同，这就很像现实生活，无论我使用的是打印机，还是U盘，我都是通过USB接口和电脑连接的，这就是接口的作用之一——制定标准\n\n我们来个图继续帮助大家理解一下：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230403203707.png)\n上面的图：我们学习前面的章节多态可以知道对象的多态可以通过动态绑定来实现，即使用向上转型，我们知道类，数组，接口都是引用类型变量，什么是引用类型变量？\n\n引用类型变量都会有一个地址的概念，即指向性的概念，当USB usb = new Print(),此时usb对象是指向new Print()的，当usb = new Flash()后，这时候usb变量就会指向new Flash()，我们会说这是子类对象赋值给了父类对象usb，而在内存中，我们应该说，usb指向了new Flash();\n\n### 接口最佳实践：设计模式中的工厂模式\n\n    首先我们来认识一下什么是工厂模式？工厂模式是为了解耦：把对象的创建和使用的过程分开。就是Class A 想调用 Class B ，那么A只是调用B的方法，而至于B的实例化，就交给工厂类。\n    \n    其次，工厂模式可以降低代码重复。如果创建对象B的过程都很复杂，需要一定的代码量，而且很多地方都要用到，那么就会有很多的重复代码。我们可以这些创建对象B的代码放到工厂里统一管理。既减少了重复代码，也方便以后对B的创建过程的修改维护。\n    \n    由于创建过程都由工厂统一管理，所以发生业务逻辑变化，不需要找到所有需要创建B的地方去逐个修正，只需要在工厂里修改即可，降低维护成本。同理，想把所有调用B的地方改成B的子类C，只需要在对应生产B的工厂中或者工厂的方法中修改其生产的对象为C即可，而不需要找到所有的new B（）改为newC()。\n\n代码演示：\n````\n    package demo;\n     \n    import java.util.Scanner;\n     \n    interface Fruit\t\t\t\t\t\t//定义一个水果标准\n    {\n    \tpublic abstract void eat();\n    }\n     \n    class Apple implements Fruit\n    {\n    \tpublic void eat()\n    \t{\n    \t\tSystem.out.println(\"吃苹果\");\n    \t}\n    }\n    class Orange implements Fruit\n    {\n    \tpublic void eat()\n    \t{\n    \t\tSystem.out.println(\"吃橘子\");\n    \t}\n    }\n     \n    class factory\n    {\n    \tpublic static Fruit getInstance(String className)  //返回值是Fruit的子类\n    \t{\n    \t\tif(\"apple\".equals(className))\n    \t\t{\n    \t\t\treturn new Apple();\n    \t\t}\n    \t\telse if(\"orange\".equals(className))\n    \t\t{\n    \t\t\treturn new Orange();\n    \t\t}\n    \t\telse\n    \t\t{\n    \t\t\treturn null;\n    \t\t}\n    \t}\n    }\n     \n    public class ComplexFactory {\n    \tpublic static void main(String[] args)\n    \t{\t\n    \t\tSystem.out.println(\"请输入水果的英文名:\");\n    \t\tScanner sc = new Scanner(System.in);\n    \t\tString ans = sc.nextLine();\n    \t\tFruit f = factory.getInstance(ans);   //初始化参数\n    \t\tf.eat();\n    \t\tsc.close();\n    \t}\n    }\n````\n代码讲解：上述代码部分我们讲一下factory这个类，类中有一个getInstance方法，我们用了static关键字修饰，在使用的时候我们就在main中使用类名.方法名调用。\n````\nFruit f = factory.getInstance(ans);   //初始化参数\n\n````\n在Factory的getInstance()方法中，我们就可以通过逻辑的实现，将对象的创建和使用的过程分开了。\n\n\n\n    总结点评：在接口的学习中，大家可以理解接口是特殊的抽象类，java中类可以实现多个接口，接口中成员属性默认是public static final修饰，可以省略；成员方法默认是public abstract修饰，同样可以省略，接口中还可定义带方法体的默认方法，需要使用default修饰。利用接口我们还可以制定标准，还能够使用工厂模式，将对象的创建和使用过程分开。\n\n## 接口与抽象类的本质区别是什么？\n\n### 基本语法区别\n\n在 Java 中，接口和抽象类的定义语法是不一样的。这里以动物类为例来说明，其中定义接口的示意代码如下：\n````\npublic interface Animal\n{\n    //所有动物都会吃\n    public void eat();\n\n    //所有动物都会飞\n    public void fly();\n}\n````\n\n定义抽象类的示意代码如下：\n\n````\npublic abstract class Animal\n{\n    //所有动物都会吃\n    public abstract void eat();\n\n    //所有动物都会飞\n    public void fly(){};\n}\n````\n\n可以看到，在接口内只能是功能的定义，而抽象类中则可以包括功能的定义和功能的实现。在接口中，所有的属性肯定是 public、static 和 final，所有的方法都是 abstract，所以可以默认不写上述标识符；在抽象类中，既可以包含抽象的定义，也可以包含具体的实现方法。\n\n在具体的实现类上，接口和抽象类的实 现类定义方式也是不一样的，其中接口实现类的示意代码如下：\n\n````\npublic class concreteAnimal implements Animal\n{\n    //所有动物都会吃\n    public void eat(){}\n\n    //所有动物都会飞\n    public void fly(){}\n}\n````\n\n抽象类的实现类示意代码如下：\n\n````\npublic class concreteAnimal extends Animal\n{\n    //所有动物都会吃\n    public void eat(){}\n\n    //所有动物都会飞\n    public void fly(){}\n}\n````\n\n可以看到，在接口的实现类中使用 implements 关键字；而在抽象类的实现类中，则使用 extends 关键字。一个接口的实现类可以实现多个接口，而一个抽象类的实现类则只能实现一个抽象类。\n\n### 设计思想区别\n\n从前面抽象类的具体实现类的实现方式可以看出，其实在 Java 中，抽象类和具体实现类之间是一种继承关系，也就是说如果釆用抽象类的方式，则父类和子类在概念上应该是相同的。接口却不一样，如果采用接口的方式，则父类和子类在概念上不要求相同。\n\n接口只是抽取相互之间没有关系的类的共同特征，而不用关注类之间的关系，它可以使没有层次关系的类具有相同的行为。因此，可以这样说：抽象类是对一组具有相同属性和方法的逻辑上有关系的事物的一种抽象，而接口则是对一组具有相同属性和方法的逻辑上不相关的事物的一种抽象。\n\n仍然以前面动物类的设计为例来说明接口和抽象类关于设计思想的区别，该动物类默认所有的动物都具有吃的功能，其中定义接口的示意代码如下：\n\n````\npublic interface Animal\n{\n    //所有动物都会吃\n    public void eat();\n}\n````\n\n定义抽象类的示意代码如下：\n\n````\npublic abstract class Animal\n{\n    //所有动物都会吃\n    public abstract void eat();\n}\n````\n\n不管是实现接口，还是继承抽象类的具体动物，都具有吃的功能，具体的动物类的示意代码如下。\n\n接口实现类的示意代码如下：\n\n````\npublic class concreteAnimal implements Animal\n{\n    //所有动物都会吃\n    public void eat(){}\n}\n\n抽象类的实现类示意代码如下：\n\npublic class concreteAnimal extends Animal\n{\n    //所有动物都会吃\n    public void eat(){}\n}\n\n当然，具体的动物类不光具有吃的功能，比如有些动物还会飞，而有些动物却会游泳，那么该如何设计这个抽象的动物类呢？可以别在接口和抽象类中增加飞的功能，其中定义接口的示意代码如下：\n\npublic interface Animal\n{\n    //所有动物都会吃\n    public void eat();\n\n    //所有动物都会飞\n    public void fly();\n}\n\n定义抽象类的示意代码如下：\n\n    public abstract class Animal\n    {\n        //所有动物都会吃\n        public abstract void eat();\n    \n        //所有动物都会飞\n        public void fly(){};\n    }\n\n这样一来，不管是接口还是抽象类的实现类，都具有飞的功能，这显然不能满足要求，因为只有一部分动物会飞，而会飞的却不一定是动物，比如飞机也会飞。那该如何设计呢？有很多种方案，比如再设计一个动物的接口类，该接口具有飞的功能，示意代码如下：\n\npublic interface AnimaiFly\n{\n    //所有动物都会飞\n    public void fly();\n}\n\n那些具体的动物类，如果有飞的功能的话，除了实现吃的接口外，再实现飞的接口，示意代码如下：\n\npublic class concreteAnimal implements Animal,AnimaiFly\n{\n    //所有动物都会吃\n    public void eat(){}\n\n    //动物会飞\n    public void fly();\n}\n\n那些不需要飞的功能的具体动物类只实现具体吃的功能的接口即可。另外一种解决方案是再设计一个动物的抽象类，该抽象类具有飞的功能，示意代码如下：\n\npublic abstract class AnimaiFly\n{\n    //动物会飞\n    public void fly();\n}\n\n但此时没有办法实现那些既有吃的功能，又有飞的功能的具体动物类。因为在 Java 中具体的实现类只能实现一个抽象类。一个折中的解决办法是，让这个具有飞的功能的抽象类，继承具有吃的功能的抽象类，示意代码如下：\n\npublic abstract class AnimaiFly extends Animal\n{\n    //动物会飞\n    public void fly();\n}\n\n此时，对那些只需要吃的功能的具体动物类来说，继承 Animal 抽象类即可。对那些既有吃的功能又有飞的功能的具体动物类来说，则需要继承 AnimalFly 抽象类。\n\n但此时对客户端有一个问题，那就是不能针对所有的动物类都使用 Animal 抽象类来进行编程，因为 Animal 抽象类不具有飞的功能，这不符合面向对象的设计原则，因此这种解决方案其实是行不通的。\n\n还有另外一种解决方案，即具有吃的功能的抽象动物类用抽象类来实现，而具有飞的功能的类用接口实现；或者具有吃的功能的抽象动物类用接口来实现，而具有飞的功能的类用抽象类实现。\n\n具有吃的功能的抽象动物类用抽象类来实现，示意代码如下：\n\npublic abstract class Animal\n{\n    //所有动物都会吃\n    public abstract void eat();\n}\n\n具有飞的功能的类用接口实现，示意代码如下：\n\npublic interface AnimaiFly\n{\n    //动物会飞\n    public void fly();\n}\n\n既具有吃的功能又具有飞的功能的具体的动物类，则继承 Animal 动物抽象类，实现 AnimalFly 接口，示意代码如下：\n\npublic class concreteAnimal extends Animal implements AnimaiFly\n{\n    //所有动物都会吃\n    public void eat(){}\n\n    //动物会飞\n    public void fly();\n}\n\n或者具有吃的功能的抽象动物类用接口来实现，示意代码如下：\n\npublic interface Animal\n{\n    //所有动物都会吃\n    public abstract void eat();\n}\n\n具有飞的功能的类用抽象类实现，示意代码如下：\n\npublic abstract class AnimaiFly\n{\n    //动物会飞\n    public void fly(){};\n}\n\n既具有吃的功能又具有飞的功能的具体的动物类，则实现 Animal 动物类接口，继承 AnimaiFly 抽象类，示意代码如下：\n\npublic class concreteAnimal extends AnimaiFly implements Animal\n{\n    //所有动物都会吃\n    public void eat(){}\n\n    //动物会飞\n    public void fly();\n}\n````\n\n这些解决方案有什么不同呢？再回过头来看接口和抽象类的区别：抽象类是对一组具有相同属性和方法的逻辑上有关系的事物的一种抽象，而接口则是对一组具有相同属性和方法的逻辑上不相关的事物的一种抽象，因此抽象类表示的是“is a”关系，接口表示的是“like a”关系。\n\n假设现在要研究的系统只是动物系统，如果设计人员认为对既具有吃的功能又具有飞的功能的具体的动物类来说，它和只具有吃的功能的动物一样，都是动物，是一组逻辑上有关系的事物，因此这里应该使用抽象类来抽象具有吃的功能的动物类，即继承 Animal 动物抽象类，实现 AnimalFly 接口。\n\n如果设计人员认为对既具有吃的功能，又具有飞的功能的具体的动物类来说，它和只具有飞的功能的动物一样，都是动物，是一组逻辑上有关系的事物，因此这里应该使用抽象类来抽象具有飞的功能的动物类，即实现 Animal 动物类接口，继承 AnimaiFly 抽象类。\n\n假设现在要研究的系统不只是动物系统，如果设计人员认为不管是吃的功能，还是飞的功能和动物类没有什么关系，因为飞机也会飞，人也会吃，则这里应该实现两个接口来分别抽象吃的功能和飞的功能，即除实现吃的 Animal 接口外，再实现飞的 AnimalFly 接口。\n\n从上面的分析可以看出，对于接口和抽象类的选择，反映出设计人员看待问题的不同角度，即抽象类用于一组相关的事物，表示的是“is a”的关系，而接口用于一组不相关的事物，表示的是“like a”的关系。\n\n\n### 如何回答面试题：接口和抽象类的区别?\n\n接口(interface)和抽象类(abstract class)是支持抽象类定义的两种机制。\n\n接口是公开的，不能有私有的方法或变量，接口中的所有方法都没有方法体，通过关键字interface实现。\n\n抽象类是可以有私有方法或私有变量的，通过把类或者类中的方法声明为abstract来表示一个类是抽象类，被声明为抽象的方法不能包含方法体。子类实现方法必须含有相同的或者更低的访问级别(public->protected->private)。抽象类的子类为父类中所有抽象方法的具体实现，否则也是抽象类。\n\n接口可以被看作是抽象类的变体，接口中所有的方法都是抽象的，可以通过接口来间接的实现多重继承。接口中的成员变量都是static final类型，由于抽象类可以包含部分方法的实现，所以，在一些场合下抽象类比接口更有优势。\n\n **相同点**\n\n（1）都不能被实例化\n（2）接口的实现类或抽象类的子类都只有实现了接口或抽象类中的方法后才能实例化。\n\n **不同点**\n\n（1）接口只有定义，不能有方法的实现，java 1.8中可以定义default方法体，而抽象类可以有定义与实现，方法可在抽象类中实现。\n\n（2）实现接口的关键字为implements，继承抽象类的关键字为extends。一个类可以实现多个接口，但一个类只能继承一个抽象类。所以，使用接口可以间接地实现多重继承。\n\n（3）接口强调特定功能的实现，而抽象类强调所属关系。\n\n（4）接口成员变量默认为public static final，必须赋初值，不能被修改；其所有的成员方法都是public、abstract的。抽象类中成员变量默认default，可在子类中被重新定义，也可被重新赋值；抽象方法被abstract修饰，不能被private、static、synchronized和native等修饰，必须以分号结尾，不带花括号。\n\n（5）接口被用于常用的功能，便于日后维护和添加删除，而抽象类更倾向于充当公共类的角色，不适用于日后重新对立面的代码修改。功能需要累积时用抽象类，不需要累积时用接口。\n\n## 参考文章\nhttp://c.biancheng.net/view/1012.html\nhttps://blog.csdn.net/wxw20147854/article/details/88712029\nhttps://blog.csdn.net/zhangquan2015/article/details/82808399\nhttps://blog.csdn.net/qq_38741971/article/details/80099567\nhttps://www.runoob.com/java/java-interfaces.html\nhttps://blog.csdn.net/fengyunjh/article/details/6605085\nhttps://blog.csdn.net/xkfanhua/article/details/80567557\n\n\n"
  },
  {
    "path": "docs/Java/basic/枚举类.md",
    "content": "# 目录\n  * [初探枚举类](#初探枚举类)\n    * [枚举类-语法](#枚举类-语法)\n    * [枚举类的具体使用](#枚举类的具体使用)\n      * [常量](#常量)\n      * [switch](#switch)\n      * [向枚举中添加新方法](#向枚举中添加新方法)\n      * [覆盖枚举的方法](#覆盖枚举的方法)\n      * [实现接口](#实现接口)\n      * [使用接口组织枚举](#使用接口组织枚举)\n      * [枚举类集合](#枚举类集合)\n  * [使用枚举类的注意事项](#使用枚举类的注意事项)\n  * [枚举类的实现原理](#枚举类的实现原理)\n  * [枚举类实战](#枚举类实战)\n    * [实战一无参](#实战一无参)\n    * [实战二有一参](#实战二有一参)\n    * [实战三有两参](#实战三有两参)\n  * [枚举类总结](#枚举类总结)\n  * [枚举 API](#枚举-api)\n  * [总结](#总结)\n  * [参考文章](#参考文章)\n\n\n---\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章首发于我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《夯实Java基础系列博文》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n该系列博文会告诉你如何从入门到进阶，一步步地学习Java基础知识，并上手进行实战，接着了解每个Java知识点背后的实现原理，更完整地了解整个Java技术体系，形成自己的知识框架。为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n\n枚举（enum）类型是Java 5新增的特性，它是一种新的类型，允许用常量来表示特定的数据片断，而且全部都以类型安全的形式来表示。\n\n ## 初探枚举类\n\n>  在程序设计中，有时会用到由若干个有限数据元素组成的集合，如一周内的星期一到星期日七个数据元素组成的集合，由三种颜色红、黄、绿组成的集合，一个工作班组内十个职工组成的集合等等，程序中某个变量取值仅限于集合中的元素。此时，可将这些数据集合定义为枚举类型。\n\n> 因此，枚举类型是某类数据可能取值的集合，如一周内星期可能取值的集合为：\n> 　　{ Sun,Mon,Tue,Wed,Thu,Fri,Sat}\n> 　　该集合可定义为描述星期的枚举类型，该枚举类型共有七个元素，因而用枚举类型定义的枚举变量只能取集合中的某一元素值。由于枚举类型是导出数据类型，因此，必须先定义枚举类型，然后再用枚举类型定义枚举型变量。\n> 　\n\n    enum <枚举类型名> \n    　　{ <枚举元素表> };\n    　　\n    　　其中：关键词enum表示定义的是枚举类型，枚举类型名由标识符组成，而枚举元素表由枚举元素或枚举常量组成。例如： \n    　　\n    enum weekdays \n    　　{ Sun,Mon,Tue,Wed,Thu,Fri,Sat };\n    　　定义了一个名为 weekdays的枚举类型，它包含七个元素：Sun、Mon、Tue、Wed、Thu、Fri、Sat。\n\n> 在编译器编译程序时，给枚举类型中的每一个元素指定一个整型常量值(也称为序号值)。若枚举类型定义中没有指定元素的整型常量值，则整型常量值从0开始依次递增，因此，weekdays枚举类型的七个元素Sun、Mon、Tue、Wed、Thu、Fri、Sat对应的整型常量值分别为0、1、2、3、4、5、6。\n> 　　注意：在定义枚举类型时，也可指定元素对应的整型常量值。\n\n    例如，描述逻辑值集合{TRUE、FALSE}的枚举类型boolean可定义如下：\n    enum boolean \n    　　{ TRUE=1 ,FALSE=0 };\n    该定义规定：TRUE的值为1，而FALSE的值为0。\n    　　\n    而描述颜色集合{red,blue,green,black,white,yellow}的枚举类型colors可定义如下：\n    enum colors \n    　　{red=5,blue=1,green,black,white,yellow};\n    　　该定义规定red为5 ，blue为1，其后元素值从2 开始递增加1。green、black、white、yellow的值依次为2、3、4、5。\n\n　　此时，整数5将用于表示二种颜色red与yellow。通常两个不同元素取相同的整数值是没有意义的。枚举类型的定义只是定义了一个新的数据类型，只有用枚举类型定义枚举变量才能使用这种数据类型。 \n\n ### 枚举类-语法\n\n> enum 与 class、interface 具有相同地位；\n> 可以继承多个接口；\n> 可以拥有构造器、成员方法、成员变量；\n> 1.2 枚举类与普通类不同之处\n>\n> 默认继承 java.lang.Enum 类，所以不能继承其他父类；其中 java.lang.Enum 类实现了 java.lang.Serializable 和 java.lang.Comparable 接口；\n\n> 使用 enum 定义，默认使用 final 修饰，因此不能派生子类；\n\n> 构造器默认使用 private 修饰，且只能使用 private 修饰；\n\n> 枚举类所有实例必须在第一行给出，默认添加 public static final 修饰，否则无法产生实例；\n\n### 枚举类的具体使用\n这部分内容参考https://blog.csdn.net/qq_27093465/article/details/52180865\n#### 常量\n````\npublic class 常量 {\n}\nenum Color {\n    Red, Green, Blue, Yellow\n}\n````\n#### switch\n\nJDK1.6之前的switch语句只支持int,char,enum类型，使用枚举，能让我们的代码可读性更强。\n````\npublic static void showColor(Color color) {\n    switch (color) {\n        case Red:\n            System.out.println(color);\n            break;\n        case Blue:\n            System.out.println(color);\n            break;\n        case Yellow:\n            System.out.println(color);\n            break;\n        case Green:\n            System.out.println(color);\n            break;\n    }\n}\n````\n#### 向枚举中添加新方法\n\n如果打算自定义自己的方法，那么必须在enum实例序列的最后添加一个分号。而且 Java 要求必须先定义 enum 实例。\n````\nenum Color {\n    //每个颜色都是枚举类的一个实例，并且构造方法要和枚举类的格式相符合。\n    //如果实例后面有其他内容，实例序列结束时要加分号。\n    Red(\"红色\", 1), Green(\"绿色\", 2), Blue(\"蓝色\", 3), Yellow(\"黄色\", 4);\n    String name;\n    int index;\n    Color(String name, int index) {\n        this.name = name;\n        this.index = index;\n    }\n    public void showAllColors() {\n        //values是Color实例的数组，在通过index和name可以获取对应的值。\n        for (Color color : Color.values()) {\n            System.out.println(color.index + \":\" + color.name);\n        }\n    }\n}\n````\n#### 覆盖枚举的方法\n所有枚举类都继承自Enum类，所以可以重写该类的方法\n下面给出一个toString()方法覆盖的例子。 \n````\n@Override\npublic String toString() {\n    return this.index + \":\" + this.name;\n}\n````\n#### 实现接口\n\n所有的枚举都继承自java.lang.Enum类。由于Java 不支持多继承，所以枚举对象不能再继承其他类。\n````\nenum Color implements Print{\n    @Override\n    public void print() {\n        System.out.println(this.name);\n    }\n}\n````\n#### 使用接口组织枚举  \n\n 搞个实现接口，来组织枚举，简单讲，就是分类吧。如果大量使用枚举的话，这么干，在写代码的时候，就很方便调用啦。  \n````     \npublic class 用接口组织枚举 {\n    public static void main(String[] args) {\n        Food cf = chineseFood.dumpling;\n        Food jf = Food.JapaneseFood.fishpiece;\n        for (Food food : chineseFood.values()) {\n            System.out.println(food);\n        }\n        for (Food food : Food.JapaneseFood.values()) {\n            System.out.println(food);\n        }\n    }\n}\ninterface Food {\n    enum JapaneseFood implements Food {\n        suse, fishpiece\n    }\n}\nenum chineseFood implements Food {\n    dumpling, tofu\n}\n````\n#### 枚举类集合\n\njava.util.EnumSet和java.util.EnumMap是两个枚举集合。EnumSet保证集合中的元素不重复；EnumMap中的 key是enum类型，而value则可以是任意类型。\n\nEnumSet在JDK中没有找到实现类，这里写一个EnumMap的例子\n````    \npublic class 枚举类集合 {\n    public static void main(String[] args) {\n        EnumMap<Color, String> map = new EnumMap<Color, String>(Color.class);\n        map.put(Color.Blue, \"Blue\");\n        map.put(Color.Yellow, \"Yellow\");\n        map.put(Color.Red, \"Red\");\n        System.out.println(map.get(Color.Red));\n    }\n}\n````\n## 使用枚举类的注意事项\n![image](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230403212903.png)\n\n枚举类型对象之间的值比较，是可以使用==，直接来比较值，是否相等的，不是必须使用equals方法的哟。\n\n因为枚举类Enum已经重写了equals方法\n````\n/**\n * Returns true if the specified object is equal to this\n * enum constant.\n *\n * @param other the object to be compared for equality with this object.\n * @return  true if the specified object is equal to this\n *          enum constant.\n */\npublic final boolean equals(Object other) {\n    return this==other;\n}\n````\n## 枚举类的实现原理\n\n这部分参考https://blog.csdn.net/mhmyqn/article/details/48087247\n\n> Java从JDK1.5开始支持枚举，也就是说，Java一开始是不支持枚举的，就像泛型一样，都是JDK1.5才加入的新特性。通常一个特性如果在一开始没有提供，在语言发展后期才添加，会遇到一个问题，就是向后兼容性的问题。\n>\n> 像Java在1.5中引入的很多特性，为了向后兼容，编译器会帮我们写的源代码做很多事情，比如泛型为什么会擦除类型，为什么会生成桥接方法，foreach迭代，自动装箱/拆箱等，这有个术语叫“语法糖”，而编译器的特殊处理叫“解语法糖”。那么像枚举也是在JDK1.5中才引入的，又是怎么实现的呢？\n\n> Java在1.5中添加了java.lang.Enum抽象类，它是所有枚举类型基类。提供了一些基础属性和基础方法。同时，对把枚举用作Set和Map也提供了支持，即java.util.EnumSet和java.util.EnumMap。\n\n接下来定义一个简单的枚举类\n````\npublic enum Day {\n    MONDAY {\n        @Override\n        void say() {\n            System.out.println(\"MONDAY\");\n        }\n    }\n    , TUESDAY {\n        @Override\n        void say() {\n            System.out.println(\"TUESDAY\");\n        }\n    }, FRIDAY(\"work\"){\n        @Override\n        void say() {\n            System.out.println(\"FRIDAY\");\n        }\n    }, SUNDAY(\"free\"){\n        @Override\n        void say() {\n            System.out.println(\"SUNDAY\");\n        }\n    };\n    String work;\n    //没有构造参数时，每个实例可以看做常量。\n    //使用构造参数时，每个实例都会变得不一样，可以看做不同的类型，所以编译后会生成实例个数对应的class。\n    private Day(String work) {\n        this.work = work;\n    }\n    private Day() {\n\n    }\n    //枚举实例必须实现枚举类中的抽象方法\n    abstract void say ();\n\n}\n````\n反编译结果\n````\n    D:\\MyTech\\out\\production\\MyTech\\com\\javase\\枚举类>javap Day.class\n    Compiled from \"Day.java\"\n    \n    public abstract class com.javase.枚举类.Day extends java.lang.Enum<com.javase.枚举类.Day> {\n      public static final com.javase.枚举类.Day MONDAY;\n      public static final com.javase.枚举类.Day TUESDAY;\n      public static final com.javase.枚举类.Day FRIDAY;\n      public static final com.javase.枚举类.Day SUNDAY;\n      java.lang.String work;\n      public static com.javase.枚举类.Day[] values();\n      public static com.javase.枚举类.Day valueOf(java.lang.String);\n      abstract void say();\n      com.javase.枚举类.Day(java.lang.String, int, com.javase.枚举类.Day$1);\n      com.javase.枚举类.Day(java.lang.String, int, java.lang.String, com.javase.枚举类.Day$1);\n      static {};\n    }\n````\n> 可以看到，一个枚举在经过编译器编译过后，变成了一个抽象类，它继承了java.lang.Enum；而枚举中定义的枚举常量，变成了相应的public static final属性，而且其类型就抽象类的类型，名字就是枚举常量的名字.\n>\n> 同时我们可以在Operator.class的相同路径下看到四个内部类的.class文件com/mikan/Day$1.class、com/mikan/Day$2.class、com/mikan/Day$3.class、com/mikan/Day$4.class，也就是说这四个命名字段分别使用了内部类来实现的；同时添加了两个方法values()和valueOf(String)；我们定义的构造方法本来只有一个参数，但却变成了三个参数；同时还生成了一个静态代码块。这些具体的内容接下来仔细看看。\n\n下面分析一下字节码中的各部分，其中：\n````\nInnerClasses:\n     static #23; //class com/javase/枚举类/Day$4\n     static #18; //class com/javase/枚举类/Day$3\n     static #14; //class com/javase/枚举类/Day$2\n     static #10; //class com/javase/枚举类/Day$1\n````\n从中可以看到它有4个内部类，这四个内部类的详细信息后面会分析。\n````\nstatic {};\n    descriptor: ()V\n    flags: ACC_STATIC\n    Code:\n      stack=5, locals=0, args_size=0\n         0: new           #10                 // class com/javase/枚举类/Day$1\n         3: dup\n         4: ldc           #11                 // String MONDAY\n         6: iconst_0\n         7: invokespecial #12                 // Method com/javase/枚举类/Day$1.\"<init>\":(Ljava/lang/String;I)V\n        10: putstatic     #13                 // Field MONDAY:Lcom/javase/枚举类/Day;\n        13: new           #14                 // class com/javase/枚举类/Day$2\n        16: dup\n        17: ldc           #15                 // String TUESDAY\n        19: iconst_1\n        20: invokespecial #16                 // Method com/javase/枚举类/Day$2.\"<init>\":(Ljava/lang/String;I)V\n        //后面类似，这里省略\n}\n````\n其实编译器生成的这个静态代码块做了如下工作：分别设置生成的四个公共静态常量字段的值，同时编译器还生成了一个静态字段$VALUES，保存的是枚举类型定义的所有枚举常量\n编译器添加的values方法：\n````\n    public static com.javase.Day[] values();  \n      flags: ACC_PUBLIC, ACC_STATIC  \n      Code:  \n        stack=1, locals=0, args_size=0  \n           0: getstatic     #2                  // Field $VALUES:[Lcom/javase/Day;  \n           3: invokevirtual #3                  // Method \"[Lcom/mikan/Day;\".clone:()Ljava/lang/Object;  \n           6: checkcast     #4                  // class \"[Lcom/javase/Day;\"  \n           9: areturn  \n````   \n这个方法是一个公共的静态方法，所以我们可以直接调用该方法（Day.values()）,返回这个枚举值的数组，另外，这个方法的实现是，克隆在静态代码块中初始化的$VALUES字段的值，并把类型强转成Day[]类型返回。\n\n造方法为什么增加了两个参数？\n\n\n有一个问题，构造方法我们明明只定义了一个参数，为什么生成的构造方法是三个参数呢？\n\n从Enum类中我们可以看到，为每个枚举都定义了两个属性，name和ordinal，name表示我们定义的枚举常量的名称，如FRIDAY、TUESDAY，而ordinal是一个顺序号，根据定义的顺序分别赋予一个整形值，从0开始。在枚举常量初始化时，会自动为初始化这两个字段，设置相应的值，所以才在构造方法中添加了两个参数。即：\n\n另外三个枚举常量生成的内部类基本上差不多，这里就不重复说明了。\n\n> 我们可以从Enum类的代码中看到，定义的name和ordinal属性都是final的，而且大部分方法也都是final的，特别是clone、readObject、writeObject这三个方法，这三个方法和枚举通过静态代码块来进行初始化一起。\n\n> 它保证了枚举类型的不可变性，不能通过克隆，不能通过序列化和反序列化来复制枚举，这能保证一个枚举常量只是一个实例，即是单例的，所以在effective java中推荐使用枚举来实现单例。\n\n## 枚举类实战\n\n### 实战一无参\n\n（1）定义一个无参枚举类\n\n```\nenum SeasonType {\n    SPRING, SUMMER, AUTUMN, WINTER\n}\n```\n\n（2）实战中的使用\n\n```\n// 根据实际情况选择下面的用法即可\nSeasonType springType = SeasonType.SPRING;    // 输出 SPRING \nString springString = SeasonType.SPRING.toString();    // 输出 SPRING\n```\n\n### 实战二有一参\n\n（1）定义只有一个参数的枚举类\n\n```\nenum SeasonType {\n    // 通过构造函数传递参数并创建实例\n    SPRING(\"spring\"),\n    SUMMER(\"summer\"),\n    AUTUMN(\"autumn\"),\n    WINTER(\"winter\");\n\n    // 定义实例对应的参数\n    private String msg;\n\n    // 必写：通过此构造器给枚举值创建实例\n    SeasonType(String msg) {\n        this.msg = msg;\n    }\n\n    // 通过此方法可以获取到对应实例的参数值\n    public String getMsg() {\n        return msg;\n    }\n}\n```\n\n（2）实战中的使用\n\n```\n// 当我们为某个实例类赋值的时候可使用如下方式\nString msg = SeasonType.SPRING.getMsg();    // 输出 spring\n```\n\n### 实战三有两参\n\n（1）定义有两个参数的枚举类\n\n```\npublic enum Season {\n    // 通过构造函数传递参数并创建实例\n    SPRING(1, \"spring\"),\n    SUMMER(2, \"summer\"),\n    AUTUMN(3, \"autumn\"),\n    WINTER(4, \"winter\");\n\n    // 定义实例对应的参数\n    private Integer key;\n    private String msg;\n\n    // 必写：通过此构造器给枚举值创建实例\n    Season(Integer key, String msg) {\n        this.key = key;\n        this.msg = msg;\n    }\n\n    // 很多情况，我们可能从前端拿到的值是枚举类的 key ，然后就可以通过以下静态方法获取到对应枚举值\n    public static Season valueofKey(Integer key) {\n        for (Season season : Season.values()) {\n            if (season.key.equals(key)) {\n                return season;\n            }\n        }\n        throw new IllegalArgumentException(\"No element matches \" + key);\n    }\n\n    // 通过此方法可以获取到对应实例的 key 值\n    public Integer getKey() {\n        return key;\n    }\n\n    // 通过此方法可以获取到对应实例的 msg 值\n    public String getMsg() {\n        return msg;\n    }\n}\n```\n\n（2）实战中的使用\n\n```\n// 输出 key 为 1 的枚举值实例\nSeason season = Season.valueofKey(1);\n// 输出 SPRING 实例对应的 key\nInteger key = Season.SPRING.getKey();\n// 输出 SPRING 实例对应的 msg\nString msg = Season.SPRING.getMsg();\n```\n\n## 枚举类总结\n\n其实枚举类懂了其概念后，枚举就变得相当简单了，随手就可以写一个枚举类出来。所以如上几个实战小例子一定要先搞清楚概念，然后在练习几遍就 ok 了。\n\n重要的概念，我在这里在赘述一遍，帮助老铁们快速掌握这块知识，首先记住，枚举类中的枚举值可以没有参数，也可以有多个参数，每一个枚举值都是一个实例；\n\n并且还有一点很重要，就是如果枚举值有 n 个参数，那么构造函数中的参数值肯定有 n 个，因为声明的每一个枚举值都会调用构造函数去创建实例，所以参数一定是一一对应的；既然明白了这一点，那么我们只需要在枚举类中把这 n 个参数定义为 n 个成员变量，然后提供对应的 get() 方法，之后通过实例就可以随意的获取实例中的任意参数值了。\n\n如果想让枚举类更加的好用，就可以模仿我在实战三中的写法那样，通过某一个参数值，比如 key 参数值，就能获取到其对应的枚举值，然后想要什么值，就 get 什么值就好了。\n\n## 枚举 API\n\n我们使用 enum 定义的枚举类都是继承 java.lang.Enum 类的，那么就会继承其 API ，常用的 API 如下：\n\n*   String name()\n\n获取枚举名称\n\n*   int ordinal()\n\n获取枚举的位置（下标，初始值为 0 ）\n\n*   valueof(String msg)\n\n通过 msg 获取其对应的枚举类型。（比如实战二中的枚举类或其它枚举类都行，只要使用得当都可以使用此方法）\n\n*   values()\n\n获取枚举类中的所有枚举值（比如在实战三中就使用到了）\n\n## 总结\n\n枚举本质上是通过普通的类来实现的，只是编译器为我们进行了处理。**每个枚举类型都继承自java.lang.Enum，并自动添加了values和valueOf方法。**\n\n而每个枚举常量是一个静态常量字段，**使用内部类实现**，该内部类继承了枚举类。**所有枚举常量都通过静态代码块来进行初始化，即在类加载期间就初始化**。\n\n另外通过把clone、readObject、writeObject这三个方法定义为final的，同时实现是抛出相应的异常。这样保证了每个枚举类型及枚举常量都是不可变的。**可以利用枚举的这两个特性来实现线程安全的单例。**\n\n## 参考文章\n\nhttps://blog.csdn.net/qq_34988624/article/details/86592229\nhttps://www.meiwen.com.cn/subject/slhvhqtx.html\nhttps://blog.csdn.net/qq_34988624/article/details/86592229\nhttps://segmentfault.com/a/1190000012220863\nhttps://my.oschina.net/wuxinshui/blog/1511484\nhttps://blog.csdn.net/hukailee/article/details/81107412\n\n"
  },
  {
    "path": "docs/Java/basic/泛型.md",
    "content": "# 目录\n  * [泛型概述](#泛型概述)\n    * [一个栗子](#一个栗子)\n    * [特性](#特性)\n  * [泛型的使用方式](#泛型的使用方式)\n    * [泛型类](#泛型类)\n    * [泛型接口](#泛型接口)\n    * [泛型通配符](#泛型通配符)\n    * [泛型方法](#泛型方法)\n    * [泛型方法的基本用法](#泛型方法的基本用法)\n    * [类中的泛型方法](#类中的泛型方法)\n    * [泛型方法与可变参数](#泛型方法与可变参数)\n    * [静态方法与泛型](#静态方法与泛型)\n  * [泛型方法总结](#泛型方法总结)\n  * [泛型上下边界](#泛型上下边界)\n  * [泛型常见面试题](#泛型常见面试题)\n  * [参考文章](#参考文章)\n\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章首发于我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《夯实Java基础系列博文》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n该系列博文会告诉你如何从入门到进阶，一步步地学习Java基础知识，并上手进行实战，接着了解每个Java知识点背后的实现原理，更完整地了解整个Java技术体系，形成自己的知识框架。为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。   \n\n<!-- more -->\n\n## 泛型概述\n\n泛型在java中有很重要的地位，在面向对象编程及各种设计模式中有非常广泛的应用。\n\n什么是泛型？为什么要使用泛型？\n\n> 泛型，即“参数化类型”。一提到参数，最熟悉的就是定义方法时有形参，然后调用此方法时传递实参。那么参数化类型怎么理解呢？顾名思义，就是将类型由原来的具体的类型参数化，类似于方法中的变量参数，此时类型也定义成参数形式（可以称之为类型形参），然后在使用/调用时传入具体的类型（类型实参）。\n> \n> 泛型的本质是为了参数化类型（在不创建新的类型的情况下，通过泛型指定的不同类型来控制形参具体限制的类型）。也就是说在泛型使用过程中，操作的数据类型被指定为一个参数，这种参数类型可以用在类、接口和方法中，分别被称为泛型类、泛型接口、泛型方法。\n\n### 一个栗子\n\n一个被举了无数次的例子：\n````\nList arrayList = new ArrayList();\narrayList.add(\"aaaa\");\narrayList.add(100);\n\nfor(int i = 0; i< arrayList.size();i++){\n    String item = (String)arrayList.get(i);\n    Log.d(\"泛型测试\",\"item = \" + item);\n}\n````\n毫无疑问，程序的运行结果会以崩溃结束：\n\n    java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String\n\nArrayList可以存放任意类型，例子中添加了一个String类型，添加了一个Integer类型，再使用时都以String的方式使用，因此程序崩溃了。为了解决类似这样的问题（在编译阶段就可以解决），泛型应运而生。\n\n我们将第一行声明初始化list的代码更改一下，编译器会在编译阶段就能够帮我们发现类似这样的问题。\n````\nList<String> arrayList = new ArrayList<String>();\n...\n//arrayList.add(100); 在编译阶段，编译器就会报错\n````\n### 特性\n\n泛型只在编译阶段有效。看下面的代码：\n````\nList<String> stringArrayList = new ArrayList<String>();\nList<Integer> integerArrayList = new ArrayList<Integer>();\n\nClass classStringArrayList = stringArrayList.getClass();\nClass classIntegerArrayList = integerArrayList.getClass();\n\nif(classStringArrayList.equals(classIntegerArrayList)){\n    Log.d(\"泛型测试\",\"类型相同\");\n}\n````\n> 通过上面的例子可以证明，在编译之后程序会采取去泛型化的措施。也就是说Java中的泛型，只在编译阶段有效。在编译过程中，正确检验泛型结果后，会将泛型的相关信息擦出，并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。也就是说，泛型信息不会进入到运行时阶段。\n> \n对此总结成一句话：泛型类型在逻辑上看以看成是多个不同的类型，实际上都是相同的基本类型。\n\n\n\n## 泛型的使用方式\n\n泛型有三种使用方式，分别为：泛型类、泛型接口、泛型方法\n\n### 泛型类\n\n> 泛型类型用于类的定义中，被称为泛型类。通过泛型可以完成对一组类的操作对外开放相同的接口。最典型的就是各种容器类，如：List、Set、Map。\n> \n> 泛型类的最基本写法（这么看可能会有点晕，会在下面的例子中详解）：\n````\n    class 类名称 <泛型标识：可以随便写任意标识号，标识指定的泛型的类型>{\n      private 泛型标识 /*（成员变量类型）*/ var; \n      .....\n    \n      }\n````\n一个最普通的泛型类：\n\n//此处T可以随便写为任意标识，常见的如T、E、K、V等形式的参数常用于表示泛型\n````\n//在实例化泛型类时，必须指定T的具体类型\npublic class Generic<T>{\n    //在类中声明的泛型整个类里面都可以用，除了静态部分，因为泛型是实例化时声明的。\n    //静态区域的代码在编译时就已经确定，只与类相关\n    class A <E>{\n        T t;\n    }\n    //类里面的方法或类中再次声明同名泛型是允许的，并且该泛型会覆盖掉父类的同名泛型T\n    class B <T>{\n        T t;\n    }\n    //静态内部类也可以使用泛型，实例化时赋予泛型实际类型\n    static class C <T> {\n        T t;\n    }\n    public static void main(String[] args) {\n        //报错，不能使用T泛型，因为泛型T属于实例不属于类\n//        T t = null;\n    }\n\n    //key这个成员变量的类型为T,T的类型由外部指定\n    private T key;\n\n    public Generic(T key) { //泛型构造方法形参key的类型也为T，T的类型由外部指定\n        this.key = key;\n    }\n\n    public T getKey(){ //泛型方法getKey的返回值类型为T，T的类型由外部指定\n        return key;\n    }\n}\n````\n> 12-27 09:20:04.432 13063-13063/? D/泛型测试: key is 123456\n\n> 12-27 09:20:04.432 13063-13063/? D/泛型测试: key is key_vlaue\n\n> 定义的泛型类，就一定要传入泛型类型实参么？并不是这样，在使用泛型的时候如果传入泛型实参，则会根据传入的泛型实参做相应的限制，此时泛型才会起到本应起到的限制作用。如果不传入泛型类型实参的话，在泛型类中使用泛型的方法或成员变量定义的类型可以为任何的类型。\n\n看一个例子：\n````\nGeneric generic = new Generic(\"111111\");\nGeneric generic1 = new Generic(4444);\nGeneric generic2 = new Generic(55.55);\nGeneric generic3 = new Generic(false);\n\nLog.d(\"泛型测试\",\"key is \" + generic.getKey());\nLog.d(\"泛型测试\",\"key is \" + generic1.getKey());\nLog.d(\"泛型测试\",\"key is \" + generic2.getKey());\nLog.d(\"泛型测试\",\"key is \" + generic3.getKey());\n\nD/泛型测试: key is 111111\nD/泛型测试: key is 4444\nD/泛型测试: key is 55.55\nD/泛型测试: key is false\n````\n注意：\n泛型的类型参数只能是类类型，不能是简单类型。\n不能对确切的泛型类型使用instanceof操作。如下面的操作是非法的，编译时会出错。\n        if(ex_num instanceof Generic<Number>){   \n        } \n\n### 泛型接口\n\n泛型接口与泛型类的定义及使用基本相同。泛型接口常被用在各种类的生产器中，可以看一个例子：\n````\n//定义一个泛型接口\npublic interface Generator<T> {\n    public T next();\n}\n````\n当实现泛型接口的类，未传入泛型实参时：\n````\n/**\n * 未传入泛型实参时，与泛型类的定义相同，在声明类的时候，需将泛型的声明也一起加到类中\n * 即：class FruitGenerator<T> implements Generator<T>{\n * 如果不声明泛型，如：class FruitGenerator implements Generator<T>，编译器会报错：\"Unknown class\"\n */\nclass FruitGenerator<T> implements Generator<T>{\n    @Override\n    public T next() {\n        return null;\n    }\n}\n````\n当实现泛型接口的类，传入泛型实参时：\n````\n/**\n * 传入泛型实参时：\n * 定义一个生产器实现这个接口,虽然我们只创建了一个泛型接口Generator<T>\n * 但是我们可以为T传入无数个实参，形成无数种类型的Generator接口。\n * 在实现类实现泛型接口时，如已将泛型类型传入实参类型，则所有使用泛型的地方都要替换成传入的实参类型\n * 即：Generator<T>，public T next();中的的T都要替换成传入的String类型。\n */\npublic class FruitGenerator implements Generator<String> {\n\n    private String[] fruits = new String[]{\"Apple\", \"Banana\", \"Pear\"};\n\n    @Override\n    public String next() {\n        Random rand = new Random();\n        return fruits[rand.nextInt(3)];\n    }\n}\n````\n### 泛型通配符\n\n我们知道Ingeter是Number的一个子类，同时在特性章节中我们也验证过Generic<Ingeter>与Generic<Number>实际上是相同的一种基本类型。那么问题来了，在使用Generic<Number>作为形参的方法中，能否使用Generic<Ingeter>的实例传入呢？在逻辑上类似于Generic<Number>和Generic<Ingeter>是否可以看成具有父子关系的泛型类型呢？\n\n为了弄清楚这个问题，我们使用Generic<T>这个泛型类继续看下面的例子：\n````\npublic void showKeyValue1(Generic<Number> obj){\n    Log.d(\"泛型测试\",\"key value is \" + obj.getKey());\n}\n\nGeneric<Integer> gInteger = new Generic<Integer>(123);\nGeneric<Number> gNumber = new Generic<Number>(456);\n\nshowKeyValue(gNumber);\n\n// showKeyValue这个方法编译器会为我们报错：Generic<java.lang.Integer> \n// cannot be applied to Generic<java.lang.Number>\n// showKeyValue(gInteger);\n````\n通过提示信息我们可以看到Generic<Integer>不能被看作为`Generic<Number>的子类。由此可以看出:同一种泛型可以对应多个版本（因为参数类型是不确定的），不同版本的泛型类实例是不兼容的。\n\n回到上面的例子，如何解决上面的问题？总不能为了定义一个新的方法来处理Generic<Integer>类型的类，这显然与java中的多台理念相违背。因此我们需要一个在逻辑上可以表示同时是Generic<Integer>和Generic<Number>父类的引用类型。由此类型通配符应运而生。\n\n我们可以将上面的方法改一下：\n````\npublic void showKeyValue1(Generic<?> obj){\n    Log.d(\"泛型测试\",\"key value is \" + obj.getKey());\n````\n类型通配符一般是使用？代替具体的类型实参，注意， 此处的？和Number、String、Integer一样都是一种实际的类型，可以把？看成所有类型的父类。是一种真实的类型。\n\n可以解决当具体类型不确定的时候，这个通配符就是 ?  ；当操作类型时，不需要使用类型的具体功能时，只使用Object类中的功能。那么可以用 ? 通配符来表未知类型\n````\npublic void showKeyValue(Generic<Number> obj){\n        System.out.println(obj);\n    }\n\n    Generic<Integer> gInteger = new Generic<Integer>(123);\n    Generic<Number> gNumber = new Generic<Number>(456);\n    \n    public void test () {\n    //        showKeyValue(gInteger);该方法会报错\n        showKeyValue1(gInteger);\n    }\n    \n    public void showKeyValue1(Generic<?> obj) {\n        System.out.println(obj);\n    }\n    // showKeyValue这个方法编译器会为我们报错：Generic<java.lang.Integer>\n    // cannot be applied to Generic<java.lang.Number>\n    // showKeyValue(gInteger);\n````\n\n### 泛型方法\n\n在java中,泛型类的定义非常简单，但是泛型方法就比较复杂了。\n\n尤其是我们见到的大多数泛型类中的成员方法也都使用了泛型，有的甚至泛型类中也包含着泛型方法，这样在初学者中非常容易将泛型方法理解错了。\n泛型类，是在实例化类的时候指明泛型的具体类型；泛型方法，是在调用方法的时候指明泛型的具体类型 。\n````\n/**\n * 泛型方法的基本介绍\n * @param tClass 传入的泛型实参\n * @return T 返回值为T类型\n * 说明：\n *     1）public 与 返回值中间<T>非常重要，可以理解为声明此方法为泛型方法。\n *     2）只有声明了<T>的方法才是泛型方法，泛型类中的使用了泛型的成员方法并不是泛型方法。\n *     3）<T>表明该方法将使用泛型类型T，此时才可以在方法中使用泛型类型T。\n *     4）与泛型类的定义一样，此处T可以随便写为任意标识，常见的如T、E、K、V等形式的参数常用于表示泛型。\n */\n    public <T> T genericMethod(Class<T> tClass)throws InstantiationException ,\n      IllegalAccessException{\n            T instance = tClass.newInstance();\n            return instance;\n    }\n\nObject obj = genericMethod(Class.forName(\"com.test.test\"));\n````\n### 泛型方法的基本用法\n\n光看上面的例子有的同学可能依然会非常迷糊，我们再通过一个例子，把我泛型方法再总结一下。\n````\n/** \n * 这才是一个真正的泛型方法。\n * 首先在public与返回值之间的<T>必不可少，这表明这是一个泛型方法，并且声明了一个泛型T\n * 这个T可以出现在这个泛型方法的任意位置.\n * 泛型的数量也可以为任意多个 \n *    如：public <T,K> K showKeyName(Generic<T> container){\n *        ...\n *        }\n */\n\n    public class 泛型方法 {\n    @Test\n    public void test() {\n        test1();\n        test2(new Integer(2));\n        test3(new int[3],new Object());\n\n        //打印结果\n        //null\n        //2\n        //[I@3d8c7aca\n        //java.lang.Object@5ebec15\n    }\n    //该方法使用泛型T\n    public <T> void test1() {\n        T t = null;\n        System.out.println(t);\n    }\n    //该方法使用泛型T\n    //并且参数和返回值都是T类型\n    public <T> T test2(T t) {\n        System.out.println(t);\n        return t;\n    }\n\n    //该方法使用泛型T,E\n    //参数包括T,E\n    public <T, E> void test3(T t, E e) {\n        System.out.println(t);\n        System.out.println(e);\n    }\n}\n````\n\n### 类中的泛型方法\n\n当然这并不是泛型方法的全部，泛型方法可以出现杂任何地方和任何场景中使用。但是有一种情况是非常特殊的，当泛型方法出现在泛型类中时，我们再通过一个例子看一下\n````\n//注意泛型类先写类名再写泛型，泛型方法先写泛型再写方法名\n//类中声明的泛型在成员和方法中可用\nclass A <T, E>{\n    {\n        T t1 ;\n    }\n    A (T t){\n        this.t = t;\n    }\n    T t;\n\n    public void test1() {\n        System.out.println(this.t);\n    }\n\n    public void test2(T t,E e) {\n        System.out.println(t);\n        System.out.println(e);\n    }\n}\n@Test\npublic void run () {\n    A <Integer,String > a = new A<>(1);\n    a.test1();\n    a.test2(2,\"ds\");\n//        1\n//        2\n//        ds\n}\n\nstatic class B <T>{\n    T t;\n    public void go () {\n        System.out.println(t);\n    }\n}\n````\n### 泛型方法与可变参数\n\n再看一个泛型方法和可变参数的例子：\n````\npublic class 泛型和可变参数 {\n    @Test\n    public void test () {\n        printMsg(\"dasd\",1,\"dasd\",2.0,false);\n        print(\"dasdas\",\"dasdas\", \"aa\");\n    }\n    //普通可变参数只能适配一种类型\n    public void print(String ... args) {\n        for(String t : args){\n            System.out.println(t);\n        }\n    }\n    //泛型的可变参数可以匹配所有类型的参数。。有点无敌\n    public <T> void printMsg( T... args){\n        for(T t : args){\n            System.out.println(t);\n        }\n    }\n        //打印结果：\n    //dasd\n    //1\n    //dasd\n    //2.0\n    //false\n\n}\n````\n### 静态方法与泛型\n\n静态方法有一种情况需要注意一下，那就是在类中的静态方法使用泛型：静态方法无法访问类上定义的泛型；如果静态方法操作的引用数据类型不确定的时候，必须要将泛型定义在方法上。\n\n即：如果静态方法要使用泛型的话，必须将静态方法也定义成泛型方法 。\n````\npublic class StaticGenerator<T> {\n    ....\n    ....\n    /**\n     * 如果在类中定义使用泛型的静态方法，需要添加额外的泛型声明（将这个方法定义成泛型方法）\n     * 即使静态方法要使用泛型类中已经声明过的泛型也不可以。\n     * 如：public static void show(T t){..},此时编译器会提示错误信息：\n          \"StaticGenerator cannot be refrenced from static context\"\n     */\n    public static <T> void show(T t){\n\n    }\n}\n````\n## 泛型方法总结\n\n泛型方法能使方法独立于类而产生变化，以下是一个基本的指导原则：\n\n无论何时，如果你能做到，你就该尽量使用泛型方法。也就是说，如果使用泛型方法将整个类泛型化，那么就应该使用泛型方法。另外对于一个static的方法而已，无法访问泛型类型的参数。所以如果static方法要使用泛型能力，就必须使其成为泛型方法。\n## 泛型上下边界\n\n在使用泛型的时候，我们还可以为传入的泛型类型实参进行上下边界的限制，如：类型实参只准传入某种类型的父类或某种类型的子类。\n\n为泛型添加上边界，即传入的类型实参必须是指定类型的子类型\n````\npublic class 泛型通配符与边界 {\n    public void showKeyValue(Generic<Number> obj){\n        System.out.println(\"key value is \" + obj.getKey());\n    }\n    @Test\n    public void main() {\n        Generic<Integer> gInteger = new Generic<Integer>(123);\n        Generic<Number> gNumber = new Generic<Number>(456);\n        showKeyValue(gNumber);\n        //泛型中的子类也无法作为父类引用传入\n//        showKeyValue(gInteger);\n    }\n    //直接使用？通配符可以接受任何类型作为泛型传入\n    public void showKeyValueYeah(Generic<?> obj) {\n        System.out.println(obj);\n    }\n    //只能传入number的子类或者number\n    public void showKeyValue1(Generic<? extends Number> obj){\n        System.out.println(obj);\n    }\n\n    //只能传入Integer的父类或者Integer\n    public void showKeyValue2(Generic<? super Integer> obj){\n        System.out.println(obj);\n    }\n\n    @Test\n    public void testup () {\n        //这一行代码编译器会提示错误，因为String类型并不是Number类型的子类\n        //showKeyValue1(generic1);\n        Generic<String> generic1 = new Generic<String>(\"11111\");\n        Generic<Integer> generic2 = new Generic<Integer>(2222);\n        Generic<Float> generic3 = new Generic<Float>(2.4f);\n        Generic<Double> generic4 = new Generic<Double>(2.56);\n\n        showKeyValue1(generic2);\n        showKeyValue1(generic3);\n        showKeyValue1(generic4);\n    }\n\n    @Test\n    public void testdown () {\n\n        Generic<String> generic1 = new Generic<String>(\"11111\");\n        Generic<Integer> generic2 = new Generic<Integer>(2222);\n        Generic<Number> generic3 = new Generic<Number>(2);\n//        showKeyValue2(generic1);本行报错，因为String并不是Integer的父类\n        showKeyValue2(generic2);\n        showKeyValue2(generic3);\n    }\n}\n````\n== 关于泛型数组要提一下 ==\n\n看到了很多文章中都会提起泛型数组，经过查看sun的说明文档，在java中是”不能创建一个确切的泛型类型的数组”的。\n\n也就是说下面的这个例子是不可以的：\n````    \nList<String>[] ls = new ArrayList<String>[10];  \n\n而使用通配符创建泛型数组是可以的，如下面这个例子：\n\nList<?>[] ls = new ArrayList<?>[10];  \n\n这样也是可以的：\n\nList<String>[] ls = new ArrayList[10];\n````\n下面使用Sun的一篇文档的一个例子来说明这个问题：\n\n````\nList<String>[] lsa = new List<String>[10]; // Not really allowed.    \nObject o = lsa;    \nObject[] oa = (Object[]) o;    \nList<Integer> li = new ArrayList<Integer>();    \nli.add(new Integer(3));    \noa[1] = li; // Unsound, but passes run time store check    \nString s = lsa[1].get(0); // Run-time error: ClassCastException.\n````\n\n> 这种情况下，由于JVM泛型的擦除机制，在运行时JVM是不知道泛型信息的，所以可以给oa[1]赋上一个ArrayList而不会出现异常，但是在取出数据的时候却要做一次类型转换，所以就会出现ClassCastException，如果可以进行泛型数组的声明，上面说的这种情况在编译期将不会出现任何的警告和错误，只有在运行时才会出错。\n> \n> 而对泛型数组的声明进行限制，对于这样的情况，可以在编译期提示代码有类型安全问题，比没有任何提示要强很多。\n> 下面采用通配符的方式是被允许的:数组的类型不可以是类型变量，除非是采用通配符的方式，因为对于通配符的方式，最后取出数据是要做显式的类型转换的。\n\n````\n    List<?>[] lsa = new List<?>[10]; // OK, array of unbounded wildcard type.    \n    Object o = lsa;    \n    Object[] oa = (Object[]) o;    \n    List<Integer> li = new ArrayList<Integer>();    \n    li.add(new Integer(3));    \n    oa[1] = li; // Correct.    \n    Integer i = (Integer) lsa[1].get(0); // OK \n````\n\n最后\n\n本文中的例子主要是为了阐述泛型中的一些思想而简单举出的，并不一定有着实际的可用性。另外，一提到泛型，相信大家用到最多的就是在集合中，其实，在实际的编程过程中，自己可以使用泛型去简化开发，且能很好的保证代码质量。\n\n## 泛型常见面试题\n\n1. Java中的泛型是什么 ? 使用泛型的好处是什么?\n\n这是在各种Java泛型面试中，一开场你就会被问到的问题中的一个，主要集中在初级和中级面试中。那些拥有Java1.4或更早版本的开发背景的人 都知道，在集合中存储对象并在使用前进行类型转换是多么的不方便。泛型防止了那种情况的发生。它提供了编译期的类型安全，确保你只能把正确类型的对象放入 集合中，避免了在运行时出现ClassCastException。\n\n2. Java的泛型是如何工作的 ? 什么是类型擦除 ?\n\n这是一道更好的泛型面试题。泛型是通过类型擦除来实现的，编译器在编译时擦除了所有类型相关的信息，所以在运行时不存在任何类型相关的信息。例如 List<String>在运行时仅用一个List来表示。这样做的目的，是确保能和Java 5之前的版本开发二进制类库进行兼容。你无法在运行时访问到类型参数，因为编译器已经把泛型类型转换成了原始类型。根据你对这个泛型问题的回答情况，你会 得到一些后续提问，比如为什么泛型是由类型擦除来实现的或者给你展示一些会导致编译器出错的错误泛型代码。请阅读我的Java中泛型是如何工作的来了解更 多信息。\n\n3. 什么是泛型中的限定通配符和非限定通配符 ?\n\n这是另一个非常流行的Java泛型面试题。限定通配符对类型进行了限制。有两种限定通配符，一种是<? extends T>它通过确保类型必须是T的子类来设定类型的上界，另一种是<? super T>它通过确保类型必须是T的父类来设定类型的下界。泛型类型必须用限定内的类型来进行初始化，否则会导致编译错误。另一方面<?>表 示了非限定通配符，因为<?>可以用任意类型来替代。更多信息请参阅我的文章泛型中限定通配符和非限定通配符之间的区别。\n\n4. List<? extends T>和List <? super T>之间有什么区别 ?\n\n这和上一个面试题有联系，有时面试官会用这个问题来评估你对泛型的理解，而不是直接问你什么是限定通配符和非限定通配符。这两个List的声明都是 限定通配符的例子，List<? extends T>可以接受任何继承自T的类型的List，而List<? super T>可以接受任何T的父类构成的List。例如List<? extends Number>可以接受List<Integer>或List<Float>。在本段出现的连接中可以找到更多信息。\n\n5. 如何编写一个泛型方法，让它能接受泛型参数并返回泛型类型?\n\n编写泛型方法并不困难，你需要用泛型类型来替代原始类型，比如使用T, E or K,V等被广泛认可的类型占位符。泛型方法的例子请参阅Java集合类框架。最简单的情况下，一个泛型方法可能会像这样:\n\npublic V put(K key, V value) {\n\nreturn cache.put(key, value);\n\n}\n\n6. Java中如何使用泛型编写带有参数的类?\n\n这是上一道面试题的延伸。面试官可能会要求你用泛型编写一个类型安全的类，而不是编写一个泛型方法。关键仍然是使用泛型类型来代替原始类型，而且要使用JDK中采用的标准占位符。\n\n7. 编写一段泛型程序来实现LRU缓存?\n\n对于喜欢Java编程的人来说这相当于是一次练习。给你个提示，LinkedHashMap可以用来实现固定大小的LRU缓存，当LRU缓存已经满 了的时候，它会把最老的键值对移出缓存。LinkedHashMap提供了一个称为removeEldestEntry()的方法，该方法会被put() 和putAll()调用来删除最老的键值对。当然，如果你已经编写了一个可运行的JUnit测试，你也可以随意编写你自己的实现代码。\n\n8. 你可以把List<String>传递给一个接受List<Object>参数的方法吗？\n\n对任何一个不太熟悉泛型的人来说，这个Java泛型题目看起来令人疑惑，因为乍看起来String是一种Object，所以 List<String>应当可以用在需要List<Object>的地方，但是事实并非如此。真这样做的话会导致编译错误。如 果你再深一步考虑，你会发现Java这样做是有意义的，因为List<Object>可以存储任何类型的对象包括String, Integer等等，而List<String>却只能用来存储Strings。\n````\nList<Object> objectList;\n\nList<String> stringList;\n\nobjectList = stringList; //compilation error incompatible types\n````\n9. Array中可以用泛型吗?\n\n这可能是Java泛型面试题中最简单的一个了，当然前提是你要知道Array事实上并不支持泛型，这也是为什么Joshua Bloch在Effective Java一书中建议使用List来代替Array，因为List可以提供编译期的类型安全保证，而Array却不能。\n\n10. 如何阻止Java中的类型未检查的警告?\n\n如果你把泛型和原始类型混合起来使用，例如下列代码，Java 5的javac编译器会产生类型未检查的警告，例如\n````\nList<String> rawList = new ArrayList()\n````\n注意: Hello.java使用了未检查或称为不安全的操作;\n\n这种警告可以使用@SuppressWarnings(“unchecked”)注解来屏蔽。\n\n## 参考文章\n\nhttps://www.cnblogs.com/huajiezh/p/6411123.html\nhttps://www.cnblogs.com/jpfss/p/9929045.html\nhttps://www.cnblogs.com/dengchengchao/p/9717097.html\nhttps://www.cnblogs.com/cat520/p/9353291.html\nhttps://www.cnblogs.com/coprince/p/8603492.html\n\n"
  },
  {
    "path": "docs/Java/basic/深入理解内部类.md",
    "content": "# 目录\n  * [内部类初探](#内部类初探)\n    * [什么是内部类？](#什么是内部类？)\n    * [内部类的共性](#内部类的共性)\n    * [使用内部类的好处：](#使用内部类的好处：)\n    * [那静态内部类与普通内部类有什么区别呢？](#那静态内部类与普通内部类有什么区别呢？)\n    * [为什么普通内部类不能有静态变量呢？](#为什么普通内部类不能有静态变量呢？)\n  * [内部类的加载](#内部类的加载)\n    * [成员内部类](#成员内部类)\n    * [匿名内部类](#匿名内部类)\n    * [匿名内部类里的final](#匿名内部类里的final)\n  * [内部类初始化](#内部类初始化)\n  * [内部类的重载](#内部类的重载)\n  * [内部类的继承](#内部类的继承)\n  * [参考文章](#参考文章)\n\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章首发于我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《夯实Java基础系列博文》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n该系列博文会告诉你如何从入门到进阶，一步步地学习Java基础知识，并上手进行实战，接着了解每个Java知识点背后的实现原理，更完整地了解整个Java技术体系，形成自己的知识框架。为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n\n## 内部类初探\n### 什么是内部类？\n\n　　内部类是指在一个外部类的内部再定义一个类。内部类作为外部类的一个成员，并且依附于外部类而存在的。内部类可为静态，可用protected和private修饰（而外部类只能使用public和缺省的包访问权限）。内部类主要有以下几类：成员内部类、局部内部类、静态内部类、匿名内部类\n\n### 内部类的共性\n> (1)内部类仍然是一个独立的类，在编译之后内部类会被编译成独立的.class文件，但是前面冠以外部类的类名和$符号 。\n> \n> (2)内部类不能用普通的方式访问。\n> \n> (3)内部类声明成静态的，就不能随便的访问外部类的成员变量了，此时内部类只能访问外部类的静态成员变量 。\n> \n> (4)外部类不能直接访问内部类的的成员，但可以通过内部类对象来访问\n\n　　内部类是外部类的一个成员，因此内部类可以自由地访问外部类的成员变量，无论是否是private的。\n\n　　因为当某个外围类的对象创建内部类的对象时，此内部类会捕获一个隐式引用，它引用了实例化该内部对象的外围类对象。通过这个指针，可以访问外围类对象的全部状态。\n\n通过反编译内部类的字节码，分析之后主要是通过以下几步做到的： \n> 　　1 编译器自动为内部类添加一个成员变量， 这个成员变量的类型和外部类的类型相同， 这个成员变量就是指向外部类对象的引用； \n\n> 　　2 编译器自动为内部类的构造方法添加一个参数， 参数的类型是外部类的类型， 在构造方法内部使用这个参数为1中添加的成员变量赋值；\n\n> 　　3 在调用内部类的构造函数初始化内部类对象时， 会默认传入外部类的引用。\n\n### 使用内部类的好处：\n\n> 静态内部类的作用：\n\n>1 只是为了降低包的深度，方便类的使用，静态内部类适用于包含类当中，但又不依赖与外在的类。\n> \n>2 由于Java规定静态内部类不能用使用外在类的非静态属性和方法，所以只是为了方便管理类结构而定义。于是我们在创建静态内部类的时候，不需要外部类对象的引用。\n\n> 非静态内部类的作用：\n\n>1 内部类继承自某个类或实现某个接口，内部类的代码操作创建其他外围类的对象。所以你可以认为内部类提供了某种进入其外围类的窗口。\n> \n>2 使用内部类最吸引人的原因是:每个内部类都能独立地继承自一个(接口的)实现，所以无论外围类是否已经继承了某个(接口的)实现，对于内部类都没有影响\n\n>3 如果没有内部类提供的可以继承多个具体的或抽象的类的能力，一些设计与编程问题就很难解决。\n>  从这个角度看，内部类使得多重继承的解决方案变得完整。接口解决了部分问题，而内部类有效地实现了\"多重继承\"。\n\n\n### 那静态内部类与普通内部类有什么区别呢？\n\n问得好，区别如下：\n> （1）静态内部类不持有外部类的引用\n> 在普通内部类中，我们可以直接访问外部类的属性、方法，即使是private类型也可以访问，这是因为内部类持有一个外部类的引用，可以自由访问。而静态内部类，则只可以访问外部类的静态方法和静态属性（如果是private权限也能访问，这是由其代码位置所决定的），其他则不能访问。\n> \n> （2）静态内部类不依赖外部类\n> 普通内部类与外部类之间是相互依赖的关系，内部类实例不能脱离外部类实例，也就是说它们会同生同死，一起声明，一起被垃圾回收器回收。而静态内部类是可以独立存在的，即使外部类消亡了，静态内部类还是可以存在的。\n> \n> （3）普通内部类不能声明static的方法和变量\n> 普通内部类不能声明static的方法和变量，注意这里说的是变量，常量（也就是final static修饰的属性）还是可以的，而静态内部类形似外部类，没有任何限制。\n\n### 为什么普通内部类不能有静态变量呢？\n\n>1 成员内部类 之所以叫做成员 就是说他是类实例的一部分 而不是类的一部分 \n\n>2 结构上来说 他和你声明的成员变量是一样的地位 一个特殊的成员变量 而静态的变量是类的一部分和实例无关\n\n>3 你若声明一个成员内部类 让他成为主类的实例一部分 然后又想在内部类声明和实例无关的静态的东西 你让JVM情何以堪啊\n\n>4 若想在内部类内声明静态字段 就必须将其内部类本身声明为静态 \n\n非静态内部类有一个很大的优点：可以自由使用外部类的所有变量和方法\n\n下面的例子大概地介绍了\n\n1 非静态内部类和静态内部类的区别。\n\n2 不同访问权限的内部类的使用。\n\n3 外部类和它的内部类之间的关系\n\n````\n//本节讨论内部类以及不同访问权限的控制\n//内部类只有在使用时才会被加载。\n//外部类B\npublic class B{\n    int i = 1;\n    int j = 1;\n    static int s = 1;\n    static int ss = 1;\n    A a;\n    AA aa;\n    AAA aaa;\n    //内部类A\n\n    public class A {\n//        static void go () {\n//\n//        }\n//        static {\n//\n//        }\n//      static int b = 1;//非静态内部类不能有静态成员变量和静态代码块和静态方法，\n        // 因为内部类在外部类加载时并不会被加载和初始化。\n        //所以不会进行静态代码的调用\n        int i = 2;//外部类无法读取内部类的成员，而内部类可以直接访问外部类成员\n\n        public void test() {\n            System.out.println(j);\n            j = 2;\n            System.out.println(j);\n            System.out.println(s);//可以访问类的静态成员变量\n        }\n        public void test2() {\n            AA aa = new AA();\n            AAA aaa = new AAA();\n        }\n\n    }\n    //静态内部类S，可以被外部访问\n    public static class S {\n        int i = 1;//访问不到非静态变量。\n        static int s = 0;//可以有静态变量\n\n        public static void main(String[] args) {\n            System.out.println(s);\n        }\n        @Test\n        public void test () {\n//            System.out.println(j);//报错，静态内部类不能读取外部类的非静态变量\n            System.out.println(s);\n            System.out.println(ss);\n            s = 2;\n            ss = 2;\n            System.out.println(s);\n            System.out.println(ss);\n        }\n    }\n\n    //内部类AA，其实这里加protected相当于default\n    //因为外部类要调用内部类只能通过B。并且无法直接继承AA，所以必须在同包\n    //的类中才能调用到(这里不考虑静态内部类)，那么就和default一样了。\n    protected class AA{\n        int i = 2;//内部类之间不共享变量\n        public void test (){\n            A a = new A();\n            AAA aaa = new AAA();\n            //内部类之间可以互相访问。\n        }\n    }\n    //包外部依然无法访问，因为包没有继承关系，所以找不到这个类\n    protected static class SS{\n        int i = 2;//内部类之间不共享变量\n        public void test (){\n\n            //内部类之间可以互相访问。\n        }\n    }\n    //私有内部类A，对外不可见，但对内部类和父类可见\n    private class AAA {\n        int i = 2;//内部类之间不共享变量\n\n        public void test() {\n            A a = new A();\n            AA aa = new AA();\n            //内部类之间可以互相访问。\n        }\n    }\n    @Test\n    public void test(){\n        A a = new A();\n        a.test();\n        //内部类可以修改外部类的成员变量\n        //打印出 1 2\n        B b = new B();\n\n    }\n}\n````\n\n````   \n//另一个外部类\nclass C {\n    @Test\n    public void test() {\n        //首先，其他类内部类只能通过外部类来获取其实例。\n        B.S s = new B.S();\n        //静态内部类可以直接通过B类直接获取，不需要B的实例，和静态成员变量类似。\n        //B.A a = new B.A();\n        //当A不是静态类时这行代码会报错。\n        //需要使用B的实例来获取A的实例\n        B b = new B();\n        B.A a = b.new A();\n        B.AA aa = b.new AA();//B和C同包，所以可以访问到AA\n//      B.AAA aaa = b.new AAA();AAA为私有内部类，外部类不可见\n        //当A使用private修饰时，使用B的实例也无法获取A的实例，这一点和私有变量是一样的。\n        //所有普通的内部类与类中的一个变量是类似的。静态内部类则与静态成员类似。\n    }\n}\n````\n## 内部类的加载\n可能刚才的例子中没办法直观地看到内部类是如何加载的，接下来用例子展示一下内部类加载的过程。\n\n\n> 1  内部类是延时加载的，也就是说只会在第一次使用时加载。不使用就不加载，所以可以很好的实现单例模式。\n> \n>2 不论是静态内部类还是非静态内部类都是在第一次使用时才会被加载。\n> \n>3 对于非静态内部类是不能出现静态模块（包含静态块，静态属性，静态方法等）\n> \n>4 非静态类的使用需要依赖于外部类的对象，详见上述对象innerClass 的初始化。\n\n\n简单来说，类的加载都是发生在类要被用到的时候。内部类也是一样\n\n> 1 普通内部类在第一次用到时加载，并且每次实例化时都会执行内部成员变量的初始化，以及代码块和构造方法。\n\n> 2 静态内部类也是在第一次用到时被加载。但是当它加载完以后就会将静态成员变量初始化，运行静态代码块，并且只执行一次。当然，非静态成员和代码块每次实例化时也会执行。\n\n总结一下Java类代码加载的顺序，万变不离其宗。\n\n> 规律一、初始化构造时，先父后子；只有在父类所有都构造完后子类才被初始化\n> \n> 规律二、类加载先是静态、后非静态、最后是构造函数。\n> \n> 静态构造块、静态类属性按出现在类定义里面的先后顺序初始化，同理非静态的也是一样的，只是静态的只在加载字节码时执行一次，不管你new多少次，非静态会在new多少次就执行多少次\n> \n> 规律三、java中的类只有在被用到的时候才会被加载\n> \n> 规律四、java类只有在类字节码被加载后才可以被构造成对象实例\n\n\n### 成员内部类\n在方法中定义的内部类称为局部内部类。与局部变量类似，局部内部类不能有访问说明符，因为它不是外围类的一部分，但是它可以访问当前代码块内的常量，和此外围类所有的成员。\n\n需要注意的是：\n局部内部类只能在定义该内部类的方法内实例化，不可以在此方法外对其实例化。\n````\npublic class 局部内部类 {\n    class A {//局部内部类就是写在方法里的类，只在方法执行时加载，一次性使用。\n        public void test() {\n            class B {\n                public void test () {\n                    class C {\n\n                    }\n                }\n            }\n        }\n    }\n    @Test\n    public void test () {\n        int i = 1;\n        final int j = 2;\n        class A {\n            @Test\n            public void test () {\n                System.out.println(i);\n                System.out.println(j);\n            }\n        }\n        A a = new A();\n        System.out.println(a);\n    }\n\n    static class B {\n        public static void test () {\n            //static class A报错，方法里不能定义静态内部类。\n            //因为只有在方法调用时才能进行类加载和初始化。\n\n        }\n    }\n}\n````\n### 匿名内部类\n\n简单地说：匿名内部类就是没有名字的内部类，并且，匿名内部类是局部内部类的一种特殊形式。什么情况下需要使用匿名内部类？如果满足下面的一些条件，使用匿名内部类是比较合适的：\n只用到类的一个实例。\n类在定义后马上用到。\n类非常小（SUN推荐是在4行代码以下）\n给类命名并不会导致你的代码更容易被理解。\n在使用匿名内部类时，要记住以下几个原则：\n\n>1 　匿名内部类不能有构造方法。\n> \n>2 　匿名内部类不能定义任何静态成员、方法和类。\n> \n>3 　匿名内部类不能是public,protected,private,static。\n> \n>4 　只能创建匿名内部类的一个实例。\n> \n>5   一个匿名内部类一定是在new的后面，用其隐含实现一个接口或实现一个类。\n>\n>6 　因匿名内部类为局部内部类，所以局部内部类的所有限制都对其生效。\n\n一个匿名内部类的例子：\n````\npublic class 匿名内部类 {\n\n}\ninterface D{\n    void run ();\n}\nabstract class E{\n    E (){\n\n    }\n    abstract void work();\n}\nclass A {\n\n    @Test\n    public void test (int k) {\n        //利用接口写出一个实现该接口的类的实例。\n        //有且仅有一个实例，这个类无法重用。\n        new Runnable() {\n            @Override\n            public void run() {\n//                    k = 1;报错，当外部方法中的局部变量在内部类使用中必须改为final类型。\n                //因为方外部法中即使改变了这个变量也不会反映到内部类中。\n                //所以对于内部类来讲这只是一个常量。\n                System.out.println(100);\n                System.out.println(k);\n            }\n        };\n        new D(){\n            //实现接口的匿名类\n            int i =1;\n            @Override\n            public void run() {\n                System.out.println(\"run\");\n                System.out.println(i);\n                System.out.println(k);\n            }\n        }.run();\n        new E(){\n            //继承抽象类的匿名类\n            int i = 1;\n            void run (int j) {\n                j = 1;\n            }\n\n            @Override\n            void work() {\n\n            }\n        };\n    }\n\n}\n````\n### 匿名内部类里的final\n使用的形参为何要为final\n\n参考文件：http://android.blog.51cto.com/268543/384844\n\n> 我们给匿名内部类传递参数的时候，若该形参在内部类中需要被使用，那么该形参必须要为final。也就是说：当所在的方法的形参需要被内部类里面使用时，该形参必须为final。\n> \n> 为什么必须要为final呢？\n> \n> 首先我们知道在内部类编译成功后，它会产生一个class文件，该class文件与外部类并不是同一class文件，仅仅只保留对外部类的引用。当外部类传入的参数需要被内部类调用时，从java程序的角度来看是直接被调用：\n````  \npublic class OuterClass {\n    public void display(final String name,String age){\n        class InnerClass{\n            void display(){\n                System.out.println(name);\n            }\n        }\n    }\n}\n````\n从上面代码中看好像name参数应该是被内部类直接调用？其实不然，在java编译之后实际的操作如下：\n\n````\npublic class OuterClass$InnerClass {\n    public InnerClass(String name,String age){\n        this.InnerClass$name = name;\n        this.InnerClass$age = age;\n    }\n\n\n    \n    public void display(){\n        System.out.println(this.InnerClass$name + \"----\" + this.InnerClass$age );\n    }\n}\n````\n  所以从上面代码来看，内部类并不是直接调用方法传递的参数，而是利用自身的构造器对传入的参数进行备份，自己内部方法调用的实际上时自己的属性而不是外部方法传递进来的参数。\n\n> 直到这里还没有解释为什么是final\n\n> 在内部类中的属性和外部方法的参数两者从外表上看是同一个东西，但实际上却不是，所以他们两者是可以任意变化的，也就是说在内部类中我对属性的改变并不会影响到外部的形参，而然这从程序员的角度来看这是不可行的。\n> \n> 毕竟站在程序的角度来看这两个根本就是同一个，如果内部类该变了，而外部方法的形参却没有改变这是难以理解和不可接受的，所以为了保持参数的一致性，就规定使用final来避免形参的不改变。\n\n  简单理解就是，拷贝引用，为了避免引用值发生改变，例如被外部类的方法修改等，而导致内部类得到的值不一致，于是用final来让该引用不可改变。\n\n  故如果定义了一个匿名内部类，并且希望它使用一个其外部定义的参数，那么编译器会要求该参数引用是final的。\n\n## 内部类初始化\n\n我们一般都是利用构造器来完成某个实例的初始化工作的，但是匿名内部类是没有构造器的！那怎么来初始化匿名内部类呢？使用构造代码块！利用构造代码块能够达到为匿名内部类创建一个构造器的效果。\n````\npublic class OutClass {\n    public InnerClass getInnerClass(final int age,final String name){\n        return new InnerClass() {\n            int age_ ;\n            String name_;\n            //构造代码块完成初始化工作\n            {\n                if(0 < age && age < 200){\n                    age_ = age;\n                    name_ = name;\n                }\n            }\n            public String getName() {\n                return name_;\n            }\n            \n            public int getAge() {\n                return age_;\n            }\n        };\n    }\n````\n        \n## 内部类的重载\n如果你创建了一个内部类，然后继承其外围类并重新定义此内部类时，会发生什么呢？也就是说，内部类可以被重载吗？这看起来似乎是个很有用的点子，但是“重载”内部类就好像它是外围类的一个方法，其实并不起什么作用：\n\n````\nclass Egg {\n       private Yolk y;\n \n       protected class Yolk {\n              public Yolk() {\n                     System.out.println(\"Egg.Yolk()\");\n              }\n       }\n \n       public Egg() {\n              System.out.println(\"New Egg()\");\n              y = new Yolk();\n       }\n}\n \npublic class BigEgg extends Egg {\n       public class Yolk {\n              public Yolk() {\n                     System.out.println(\"BigEgg.Yolk()\");\n              }\n       }\n \n       public static void main(String[] args) {\n              new BigEgg();\n       }\n}\n\n````\n    复制代码\n    输出结果为：\n    New Egg()\n    Egg.Yolk()\n\n缺省的构造器是编译器自动生成的，这里是调用基类的缺省构造器。你可能认为既然创建了BigEgg 的对象，那么所使用的应该是被“重载”过的Yolk，但你可以从输出中看到实际情况并不是这样的。\n这个例子说明，当你继承了某个外围类的时候，内部类并没有发生什么特别神奇的变化。这两个内部类是完全独立的两个实体，各自在自己的命名空间内。\n\n## 内部类的继承\n\n因为内部类的构造器要用到其外围类对象的引用，所以在你继承一个内部类的时候，事情变得有点复杂。问题在于，那个“秘密的”外围类对象的引用必须被初始化，而在被继承的类中并不存在要联接的缺省对象。要解决这个问题，需使用专门的语法来明确说清它们之间的关联：\n````\n    class WithInner {\n            class Inner {\n                    Inner(){\n                            System.out.println(\"this is a constructor in WithInner.Inner\");\n                    };\n            }\n    }\n     \n    public class InheritInner extends WithInner.Inner {\n            // ! InheritInner() {} // Won't compile\n            InheritInner(WithInner wi) {\n                    wi.super();\n                    System.out.println(\"this is a constructor in InheritInner\");\n            }\n     \n            public static void main(String[] args) {\n                    WithInner wi = new WithInner();\n                    InheritInner ii = new InheritInner(wi);\n            }\n    }\n````\n    复制代码\n    输出结果为：\n    this is a constructor in WithInner.Inner\n    this is a constructor in InheritInner\n\n可以看到，InheritInner 只继承自内部类，而不是外围类。但是当要生成一个构造器时，缺省的构造器并不算好，而且你不能只是传递一个指向外围类对象的引用。此外，你必须在构造器内使用如下语法：\nenclosingClassReference.super();\n这样才提供了必要的引用，然后程序才能编译通过。\n\n有关匿名内部类实现回调，事件驱动，委托等机制的文章将在下一节讲述。\n\n## 参考文章\n\nhttps://www.cnblogs.com/hujingnb/p/10181621.html\nhttps://blog.csdn.net/codingtu/article/details/79336026\nhttps://www.cnblogs.com/woshimrf/p/java-inner-class.html\nhttps://www.cnblogs.com/dengchengchao/p/9713979.html\n\n\n\n"
  },
  {
    "path": "docs/Java/basic/继承、封装、多态的实现原理.md",
    "content": "# 目录\n  * [从JVM结构开始谈多态](#从jvm结构开始谈多态)\n    * [JVM 的结构](#jvm-的结构)\n    * [Java 的方法调用方式](#java-的方法调用方式)\n    * [常量池（constant pool）](#常量池（constant-pool）)\n  * [继承的实现原理](#继承的实现原理)\n  * [重载和重写的实现原理](#重载和重写的实现原理)\n  * [参考文章](#参考文章)\n\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章首发于我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《夯实Java基础系列博文》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n该系列博文会告诉你如何从入门到进阶，一步步地学习Java基础知识，并上手进行实战，接着了解每个Java知识点背后的实现原理，更完整地了解整个Java技术体系，形成自己的知识框架。为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n## 从JVM结构开始谈多态\n\nJava 对于方法调用动态绑定的实现主要依赖于方法表，但通过类引用调用和接口引用调用的实现则有所不同。总体而言，当某个方法被调用时，JVM 首先要查找相应的常量池，得到方法的符号引用，并查找调用类的方法表以确定该方法的直接引用，最后才真正调用该方法。以下分别对该过程中涉及到的相关部分做详细介绍。\n\n### JVM 的结构\n\n典型的 Java 虚拟机的运行时结构如下图所示\n\n 图 1.JVM 运行时结构\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230403220955.png)\n\n此结构中，我们只探讨和本文密切相关的方法区 (method area)。当程序运行需要某个类的定义时，载入子系统 (class loader subsystem) 装入所需的 class 文件，并在内部建立该类的类型信息，这个类型信息就存贮在方法区。类型信息一般包括该类的方法代码、类变量、成员变量的定义等等。可以说，类型信息就是类的 Java 文件在运行时的内部结构，包含了改类的所有在 Java 文件中定义的信息。\n\n注意到，该类型信息和 class 对象是不同的。class 对象是 JVM 在载入某个类后于堆 (heap) 中创建的代表该类的对象，可以通过该 class 对象访问到该类型信息。比如最典型的应用，在 Java 反射中应用 class 对象访问到该类支持的所有方法，定义的成员变量等等。可以想象，JVM 在类型信息和 class 对象中维护着它们彼此的引用以便互相访问。两者的关系可以类比于进程对象与真正的进程之间的关系。\n\n### Java 的方法调用方式\n\nJava 的方法调用有两类，动态方法调用与静态方法调用。静态方法调用是指对于类的静态方法的调用方式，是静态绑定的；而动态方法调用需要有方法调用所作用的对象，是动态绑定的。类调用 (invokestatic) 是在编译时刻就已经确定好具体调用方法的情况，而实例调用 (invokevirtual) 则是在调用的时候才确定具体的调用方法，这就是动态绑定，也是多态要解决的核心问题。\n\nJVM 的方法调用指令有四个，分别是 invokestatic，invokespecial，invokesvirtual 和 invokeinterface。前两个是静态绑定，后两个是动态绑定的。本文也可以说是对于 JVM 后两种调用实现的考察。\n\n### 常量池（constant pool）\n\n常量池中保存的是一个 Java 类引用的一些常量信息，包含一些字符串常量及对于类的符号引用信息等。Java 代码编译生成的类文件中的常量池是静态常量池，当类被载入到虚拟机内部的时候，在内存中产生类的常量池叫运行时常量池。\n\n常量池在逻辑上可以分成多个表，每个表包含一类的常量信息，本文只探讨对于 Java 调用相关的常量池表。\n\nCONSTANT_Utf8_info\n\n字符串常量表，该表包含该类所使用的所有字符串常量，比如代码中的字符串引用、引用的类名、方法的名字、其他引用的类与方法的字符串描述等等。其余常量池表中所涉及到的任何常量字符串都被索引至该表。\n\nCONSTANT_Class_info\n\n类信息表，包含任何被引用的类或接口的符号引用，每一个条目主要包含一个索引，指向 CONSTANT_Utf8_info 表，表示该类或接口的全限定名。\n\nCONSTANT_NameAndType_info\n\n名字类型表，包含引用的任意方法或字段的名称和描述符信息在字符串常量表中的索引。\n\nCONSTANT_InterfaceMethodref_info\n\n接口方法引用表，包含引用的任何接口方法的描述信息，主要包括类信息索引和名字类型索引。\n\nCONSTANT_Methodref_info\n\n类方法引用表，包含引用的任何类型方法的描述信息，主要包括类信息索引和名字类型索引。\n\n图2. 常量池各表的关系\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230403221053.png)\n\n可以看到，给定任意一个方法的索引，在常量池中找到对应的条目后，可以得到该方法的类索引（class_index）和名字类型索引 (name_and_type_index), 进而得到该方法所属的类型信息和名称及描述符信息（参数，返回值等）。注意到所有的常量字符串都是存储在 CONSTANT_Utf8_info 中供其他表索引的。\n\n## 继承的实现原理\n\nJava 的继承机制是一种复用类的技术，从原理上来说，是更好的使用了组合技术，因此要理解继承，首先需要了解类的组合技术是如何实现类的复用的。\n\n使用组合技术复用类\n假设现在的需求是要创建一个具有基本类型，String 类型以及一个其他非基本类型的对象。该如何处理呢？\n\n对于基本类型的变量，在新类中成员变量处直接定义即可，但对于非基本类型变量，不仅需要在类中声明其引用，并且还需要手动初始化这个对象。\n\n这里需要注意的是，编译器并不会默认将所有的引用都创建对象，因为这样的话在很多情况下会增加不必要的负担，因此，在合适的时机初始化合适的对象，可以通过以下几个位置做初始化操作：\n\n在定义对象的地方，先于构造方法执行。\n在构造方法中。\n在正要使用之前，这个被称为惰性初始化。\n使用实例初始化。\n````\nclass Soap {\n    private String s;\n    Soap() {\n        System.out.println(\"Soap()\");\n        s = \"Constructed\";\n    }\n    public String tiString(){\n        return s;\n    }\n}\n\npublic class Bath {\n    // s1 初始化先于构造函数\n    private String s1 = \"Happy\", s2 = \"Happy\", s3, s4;\n    private Soap soap;\n    private int i;\n    private float f;\n    \n    public Both() {\n        System.out.println(\"inSide Both\");\n        s3 = \"Joy\";\n        f = 3.14f;\n        soap = new Soap();\n    }\n    \n    {\n        i = 88;\n    }\n    \n    public String toString() {\n        if(s4 == null){\n            s4 = \"Joy\"\n        }\n        return \"s1 = \" + s1 +\"\\n\" +\n               \"s2 = \" + s2 +\"\\n\" +\n               \"s3 = \" + s3 +\"\\n\" +\n               \"s4 = \" + s4 +\"\\n\" +\n               \"i = \" + i +\"\\n\" +\n               \"f = \" + f +\"\\n\" +\n               \"soap = \" + soap;\n    }\n}\n````\n继承\nJava 中的继承由 extend 关键字实现，组合的语法比较平实，而继承是一种特殊的语法。当一个类继承自另一个类时，那么这个类就可以拥有另一个类的域和方法。\n````\nclass Cleanser{\n    private String s = \"Cleanser\";\n    \n    public void append(String a){\n        s += a;\n    }\n    public void apply(){\n        append(\"apply\");\n    }\n    public void scrub(){\n        append(\"scrub\");\n    }\n    public String toString(){\n        return s;\n    }\n    public static void main(String args){\n        Cleanser c = new Cleanser();\n        \n        c.apply();\n        System.out.println(c);\n    }\n}\n\npublic class Deter extends Cleanser{\n    public void apply(){\n        append(\"Deter.apply\");\n        super.scrub();\n    }\n    public void foam(){\n        append(\"foam\");\n    }\n    public static void main(String args){\n        Deter d = new Deter();\n        \n        d.apply();\n        d.scrub();\n        d.foam();\n        System.out.println(d);\n        Cleanser.main(args);\n    }\n}\n````\n上面的代码中，展示了继承语法中的一些特性：\n\n子类可以直接使用父类中公共的方法和成员变量（通常为了保护数据域，成员变量均为私有）\n子类中可以覆盖父类中的方法，也就是子类重写了父类的方法，此时若还需要调用被覆盖的父类的方法，则需要用到 super 来指定是调用父类中的方法。\n子类中可以自定义父类中没有的方法。\n可以发现上面两个类中均有 main 方法，命令行中调用的哪个类就执行哪个类的 main 方法，例如：java Deter。\n继承语法的原理\n接下来我们将通过创建子类对象来分析继承语法在我们看不到的地方做了什么样的操作。\n\n可以先思考一下，如何理解使用子类创建的对象呢，首先这个对象中包含子类的所有信息，但是也包含父类的所有公共的信息。\n\n下面来看一段代码，观察一下子类在创建对象初始化的时候，会不会用到父类相关的方法。\n````\nclass Art{\n    Art() {\n        System.out.println(\"Art Construct\");\n    }\n}\n\nclass Drawing extends Art {\n    Drawing() {\n        System.out.println(\"Drawing Construct\");\n    }\n}\n\npublic class Cartoon extends Drawing {\n    public Cartoon() {\n        System.out.println(\"Cartoon construct\");\n    }\n    public void static main(String args) {\n        Cartoon c = new Cartoon();\n    }\n}\n/*output:\nArt Construct\nDrawing Construct\nCartoon construct\n*/\n````\n通过观察代码可以发现，在实例化Cartoon时，事实上是从最顶层的父类开始向下逐个实例化，也就是最终实例化了三个对象。编译器会默认在子类的构造方法中增加调用父类默认构造方法的代码。\n\n因此，继承可以理解为编译器帮我们完成了类的特殊组合技术，即在子类中存在一个父类的对象，使得我们可以用子类对象调用父类的方法。而在开发者看来只不过是使用了一个关键字。\n\n注意：虽然继承很接近组合技术，但是继承拥有其他更多的区别于组合的特性，例如父类的对象我们是不可见的，对于父类中的方法也做了相应的权限校验等。\n\n那么，如果类中的构造方法是带参的，该如何操作呢？（使用super关键字显示调用）\n\n见代码：\n````\nclass Game {\n    Game(int i){\n        System.out.println(\"Game Construct\");\n    }\n}\n\nclass BoardGame extends Game {\n    BoardGame(int j){\n        super(j);\n        System.out.println(\"BoardGame Construct\");\n    }\n}\npublic class Chess extends BoardGame{\n    Chess(){\n        super(99);\n        System.out.println(\"Chess construct\");\n    }\n    public static void main(String args) {\n        Chess c = new Chess();\n    }\n}\n\n/*output:\nGame Construct\nBoardGame Construct\nChess construc\n*/\n\n````\n## 重载和重写的实现原理\n\n刚开始学习Java的时候，就了解了Java这个比较有意思的特性：重写 和 重载。开始的有时候从名字上还总是容易弄混。我相信熟悉Java这门语言的同学都应该了解这两个特性，可能只是从语言层面上了解这种写法，但是jvm是如何实现他们的呢 ?\n\n重载官方给出的介绍：\n\n>  verload:\n> The Java programming language supports overloading methods, and Java can distinguish between methods with different method signatures. This means that methods within a class can have the same name if they have different parameter lists .\n> \n> Overloaded methods are differentiated by the number and the type of the arguments passed into the method.\n> \n> You cannot declare more than one method with the same name and the same number and type of arguments, because the compiler cannot tell them apart.\n> \n> The compiler does not consider return type when differentiating methods, so you cannot declare two methods with the same signature even if they have a different return type.\n> \n首先看一段代码，来看看代码的执行结果：\n````\npublic class OverrideTest {\n \n    class Father{}\n \n    class Sun extends Father {}\n \n    public void doSomething(Father father){\n        System.out.println(\"Father do something\");\n    }\n \n    public void doSomething(Sun father){\n        System.out.println(\"Sun do something\");\n    }\n \n    public static void main(String [] args){\n        OverrideTest overrideTest = new OverrideTest();\n        Father sun = overrideTest.new Sun();\n        Father father = overrideTest.new Father();\n        overrideTest.doSomething(father);\n        overrideTest.doSomething(sun);\n    }\n}\n````\n看下这段代码的执行结果，最后会打印：\n\n    Father do something\n    Father do something\n\n为什么会打印出这样的结果呢？ 首先要介绍两个概念：静态分派和动态分派\n\n静态分派：依赖静态类型来定位方法执行版本的分派动作称为静态分派\n\n动态分派：运行期根据实际类型确定方法执行版本的分派过程。\n\n他们的区别是：\n\n1.静态分派发生在编译期，动态分派发生在运行期；\n\n2.private,static,final 方法发生在编译期，并且不能被重写，一旦发生了重写，将会在运行期处理。\n\n3.重载是静态分派，重写是动态分派\n\n回到上面的问题，因为重载是发生在编译期，所以在编译期已经确定两次 doSomething 方法的参数都是Father类型，在class文件中已经指向了Father类的符号引用，所以最后会打印两次Father do something。\n\n> 二. override:\n> An instance method in a subclass with the same signature (name, plus the number and the type of its parameters) and return type as an instance method in the superclassoverridesthe superclass's method.\n> \n> The ability of a subclass to override a method allows a class to inherit from a superclass whose behavior is \"close enough\" and then to modify behavior as needed. The overriding method has the same name, number and type of parameters, and return type as the method that it overrides. An overriding method can also return a subtype of the type returned by the overridden method. This subtype is called acovariant return type.\n\n还是上面那个代码，稍微改动下\n````\npublic class OverrideTest {\n \n    class Father{}\n \n    class Sun extends Father {}\n \n    public void doSomething(){\n        System.out.println(\"Father do something\");\n    }\n \n    public void doSomething(){\n        System.out.println(\"Sun do something\");\n    }\n \n    public static void main(String [] args){\n        OverrideTest overrideTest = new OverrideTest();\n        Father sun = overrideTest.new Sun();\n        Father father = overrideTest.new Father();\n        overrideTest.doSomething();\n        overrideTest.doSomething();\n    }\n}\n````\n\n最后会打印：\n\n    Father do something\n    \n    Sun do something\n\n相信大家都会知道这个结果，那么这个结果jvm是怎么实现的呢？\n\n在编译期，只会识别到是调用Father类的doSomething方法，到运行期才会真正找到对象的实际类型。\n\n首先该方法的执行，jvm会调用invokevirtual指令，该指令会找栈顶第一个元素所指向的对象的实际类型，如果该类型存在调用的方法，则会走验证流程，否则继续找其父类。这也是为什么子类可以直接调用父类具有访问权限的方法的原因。简而言之，就是在运行期才会去确定对象的实际类型，根据这个实际类型确定方法执行版本，这个过程称为动态分派。override 的实现依赖jvm的动态分派。\n\n## 参考文章\nhttps://blog.csdn.net/dj_dengjian/article/details/80811348\nhttps://blog.csdn.net/chenssy/article/details/12757911\nhttps://blog.csdn.net/fan2012huan/article/details/51007517\nhttps://blog.csdn.net/fan2012huan/article/details/50999777\nhttps://www.cnblogs.com/serendipity-fly/p/9469289.html\nhttps://blog.csdn.net/m0_37264516/article/details/86709537\n\n"
  },
  {
    "path": "docs/Java/basic/解读Java中的回调.md",
    "content": "# 目录\n  * [模块间的调用](#模块间的调用)\n  * [多线程中的“回调”](#多线程中的回调)\n  * [Java回调机制实战](#java回调机制实战)\n    * [实例一 ： 同步调用](#实例一-：-同步调用)\n    * [实例二：由浅入深](#实例二：由浅入深)\n    * [实例三：Tom做题](#实例三：tom做题)\n  * [参考文章](#参考文章)\n\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章首发于我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《夯实Java基础系列博文》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n该系列博文会告诉你如何从入门到进阶，一步步地学习Java基础知识，并上手进行实战，接着了解每个Java知识点背后的实现原理，更完整地了解整个Java技术体系，形成自己的知识框架。为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n## 模块间的调用\n\n本部分摘自https://www.cnblogs.com/xrq730/p/6424471.html\n\n\n在一个应用系统中，无论使用何种语言开发，必然存在模块之间的调用，调用的方式分为几种：\n\n（1）同步调用\n\n> 同步调用是最基本并且最简单的一种调用方式，类A的方法a()调用类B的方法b()，一直等待b()方法执行完毕，a()方法继续往下走。这种调用方式适用于方法b()执行时间不长的情况，因为b()方法执行时间一长或者直接阻塞的话，a()方法的余下代码是无法执行下去的，这样会造成整个流程的阻塞。\n\n\n（2）异步调用\n\n\n> 异步调用是为了解决同步调用可能出现阻塞，导致整个流程卡住而产生的一种调用方式。类A的方法方法a()通过新起线程的方式调用类B的方法b()，代码接着直接往下执行，这样无论方法b()执行时间多久，都不会阻塞住方法a()的执行。\n> \n> 但是这种方式，由于方法a()不等待方法b()的执行完成，在方法a()需要方法b()执行结果的情况下（视具体业务而定，有些业务比如启异步线程发个微信通知、刷新一个缓存这种就没必要），必须通过一定的方式对方法b()的执行结果进行监听。\n> \n> 在Java中，可以使用Future+Callable的方式做到这一点，具体做法可以参见我的这篇文章Java多线程21：多线程下其他组件之CyclicBarrier、Callable、Future和FutureTask。\n\n（3）回调\n\n\n1、什么是回调？\n一般来说，模块之间都存在一定的调用关系，从调用方式上看，可以分为三类同步调用、异步调用和回调。同步调用是一种阻塞式调用，即在函数A的函数体里通过书写函数B的函数名来调用之，使内存中对应函数B的代码得以执行。异步调用是一种类似消息或事件的机制解决了同步阻塞的问题，例如 A通知 B后，他们各走各的路，互不影响，不用像同步调用那样， A通知 B后，非得等到 B走完后， A才继续走 。回调是一种双向的调用模式，也就是说，被调用的接口被调用时也会调用对方的接口，例如A要调用B，B在执行完又要调用A。\n\n2、回调的用途\n回调一般用于层间协作，上层将本层函数安装在下层，这个函数就是回调，而下层在一定条件下触发回调。例如作为一个驱动，是一个底层，他在收到一个数据时，除了完成本层的处理工作外，还将进行回调，将这个数据交给上层应用层来做进一步处理，这在分层的数据通信中很普遍。\n\n    \n## 多线程中的“回调”\n\nJava多线程中可以通过callable和future或futuretask结合来获取线程执行后的返回值。实现方法是通过get方法来调用callable的call方法获取返回值。\n\n其实这种方法本质上不是回调，回调要求的是任务完成以后被调用者主动回调调用者的接口。而这里是调用者主动使用get方法阻塞获取返回值。\n````\npublic class 多线程中的回调 {\n    //这里简单地使用future和callable实现了线程执行完后\n    public static void main(String[] args) throws ExecutionException, InterruptedException {\n        ExecutorService executor = Executors.newCachedThreadPool();\n        Future<String> future = executor.submit(new Callable<String>() {\n            @Override\n            public String call() throws Exception {\n                System.out.println(\"call\");\n                TimeUnit.SECONDS.sleep(1);\n                return \"str\";\n            }\n        });\n        //手动阻塞调用get通过call方法获得返回值。\n        System.out.println(future.get());\n        //需要手动关闭，不然线程池的线程会继续执行。\n        executor.shutdown();\n\n    //使用futuretask同时作为线程执行单元和数据请求单元。\n    FutureTask<Integer> futureTask = new FutureTask(new Callable<Integer>() {\n        @Override\n        public Integer call() throws Exception {\n            System.out.println(\"dasds\");\n            return new Random().nextInt();\n        }\n    });\n    new Thread(futureTask).start();\n    //阻塞获取返回值\n    System.out.println(futureTask.get());\n}\n@Test\npublic void test () {\n    Callable callable = new Callable() {\n        @Override\n        public Object call() throws Exception {\n            return null;\n        }\n    };\n    FutureTask futureTask = new FutureTask(callable);\n\n}\n}\n````\n## Java回调机制实战\n\n曾经自己偶尔听说过回调机制，隐隐约约能够懂一些意思，但是当让自己写一个简单的示例程序时，自己就傻眼了。随着工作经验的增加，自己经常听到这儿使用了回调，那儿使用了回调，自己是时候好好研究一下Java回调机制了。网上关于Java回调的文章一抓一大把，但是看完总是云里雾里，不知所云，特别是看到抓取别人的代码走两步时，总是现眼。于是自己决定写一篇关于Java机制的文章，以方便大家和自己更深入的学习Java回调机制。\n\n首先，什么是回调函数，引用百度百科的解释：回调函数就是一个通过函数指针调用的函数。如果你把函数的指针（地址）作为参数传递给另一个函数，当这个指针被用来调用其所指向的函数时，我们就说这是回调函数。回调函数不是由该函数的实现方直接调用，而是在特定的事件或条件发生时由另外的一方调用的，用于对该事件或条件进行响应[2].\n\n不好意思，上述解释我看了好几遍，也没理解其中深刻奥秘，相信一些读者你也一样。光说不练假把式，咱们还是以实战理解脉络。\n\n### 实例一 ： 同步调用\n\n本文以底层服务BottomService和上层服务UpperService为示例，利用上层服务调用底层服务，整体执行过程如下：\n\n第一步: 执行UpperService.callBottomService();\n\n第二步: 执行BottomService.bottom();\n\n第三步:执行UpperService.upperTaskAfterCallBottomService()\n\n#### 1.1 同步调用代码\n\n同步调用时序图：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230403210703.png)\n\n\n**1.1.1 底层服务类:BottomService.java**\n\n````\npackage synchronization.demo;\n\n/**\n\n* Created by lance on 2017/1/19.\n\n*/\n\npublic class BottomService {\n\npublic String bottom(String param) {\n\ntry { //  模拟底层处理耗时，上层服务需要等待\n\nThread.sleep(3000);\n\n} catch (InterruptedException e) {\n\ne.printStackTrace();\n\n}\n\nreturn param +\" BottomService.bottom() execute -->\";\n\n}\n\n}\n\n\n\n**1.1.2 上层服务接口: UpperService.java**\n\n\npackage synchronization.demo;\n\n/**\n\n* Created by lance on 2017/1/19.\n\n*/\n\npublic interface UpperService {\n\npublic void upperTaskAfterCallBottomService(String upperParam);\n\npublic String callBottomService(final String param);\n\n}\n\n\n\n**1.1.3 上层服务接口实现类:UpperServiceImpl.java**\n\n\npackage synchronization.demo;\n\n/**\n\n* Created by lance on 2017/1/19.\n\n*/\n\npublic class UpperServiceImpl implements UpperService {\n\nprivate BottomService bottomService;\n\n@Override\n\npublic void upperTaskAfterCallBottomService(String upperParam) {\n\nSystem.out.println(upperParam + \" upperTaskAfterCallBottomService() execute.\");\n\n}\n\npublic UpperServiceImpl(BottomService bottomService) {\n\nthis.bottomService = bottomService;\n\n}\n\n@Override\n\npublic String callBottomService(final String param) {\n\nreturn bottomService.bottom(param + \" callBottomService.bottom() execute --> \");\n\n}\n\n}\n\n\n\n**1.1.4 Test测试类:Test.java**\n\n\npackage synchronization.demo;\n\nimport java.util.Date;\n\n/**\n\n* Created by lance on 2017/1/19.\n\n*/\n\npublic class Test {\n\npublic static void main(String[] args) {\n\nBottomService bottomService = new BottomService();\n\nUpperService upperService = new UpperServiceImpl(bottomService);\n\nSystem.out.println(\"=============== callBottomService start ==================:\" + new Date());\n\nString result = upperService.callBottomService(\"callBottomService start --> \");\n\n//upperTaskAfterCallBottomService执行必须等待callBottomService()调用BottomService.bottom()方法返回后才能够执行\n\nupperService.upperTaskAfterCallBottomService(result);\n\nSystem.out.println(\"=============== callBottomService end ====================:\" + new Date());\n\n}\n\n}\n\n````\n\n**1.1.5 输出结果:**\n\n```\n=============== callBottomService start ==================:Thu Jan 19 14:59:58 CST 2017\n\ncallBottomService start -->  callBottomService.bottom() execute -->  BottomService.bottom() execute --> upperTaskAfterCallBottomService() execute.\n\n=============== callBottomService end ====================:Thu Jan 19 15:00:01 CST 2017\n\n```\n\n注意输出结果：\n\n是同步方式，Test调用callBottomService()等待执行结束，然后再执行下一步，即执行结束。callBottomService开始执行时间为Thu Jan 19 14:59:58 CST 2017，执行结束时间为Thu Jan 19 15:00:01 CST 2017，耗时3秒钟，与模拟的耗时时间一致，即3000毫秒。\n\n## 实例二：由浅入深\n\n前几天公司面试有问道java回调的问题，因为这方面也没有太多研究，所以回答的含糊不清，这回特意来补习一下。看了看网上的回调解释和例子，都那么的绕口，得看半天才能绕回来，其实吧，回调是个很简单的机制。在这里我用简单的语言先来解释一下：假设有两个类，分别是A和B，在A中有一个方法a()，B中有一个方法b()；在A里面调用B中的方法b()，而方法b()中调用了方法a()，这样子就同时实现了b()和a()两个方法的功能。\n\n疑惑：为啥这么麻烦，我直接在类A中的B.b()方法下调用a()方法就行了呗。\n解答：回调更像是一个约定，就是如果我调用了b()方法，那么就必须要回调，而不需要显示调用\n一、Java的回调-浅\n我们用例子来解释：小明和小李相约一起去吃早饭，但是小李起的有点晚要先洗漱，等小李洗漱完成后，通知小明再一起去吃饭。小明就是类A，小李就是类B。一起去吃饭这个事件就是方法a(),小李去洗漱就是方法b()。\n````\npublic class XiaoMing { \n   //小明和小李一起吃饭\n   public void eatFood() {\n      XiaoLi xl = new XiaoLi();\n      //A调用B的方法\n      xl.washFace();\n   }\n \n   public void eat() {\n      System.out.print(\"小明和小李一起去吃大龙虾\");\n   }\n}\n那么怎么让小李洗漱完后在通知小明一起去吃饭呢\n\npublic class XiaoMing { \n   //小明和小李一起吃饭\n   public void eatFood() {\n      XiaoLi xl = new XiaoLi();\n      //A调用B的方法\n      xl.washFace();\n      eat();\n   }\n \n   public void eat() {\n      System.out.print(\"小明和小李一起去吃大龙虾\");\n   }\n}\n````   \n不过上面已经说过了这个不是回调函数，所以不能这样子，正确的方式如下\n````\n    public class XiaoLi{//小李\n       public void washFace() {\n        System.out.print(\"小李要洗漱\");\n        XiaoMing xm = new XiaoMing();\n            //B调用A的方法\n        xm.eat();//洗漱完后，一起去吃饭\n       }\n    }\n````   \n这样子就可以实现washFace()同时也能实现eat()。小李洗漱完后，再通知小明一起去吃饭，这就是回调。\n\n二、Java的回调-中\n可是细心的伙伴可能会发现，小李的代码完全写死了，这样子的场合可能适用和小明一起去吃饭，可是假如小李洗漱完不吃饭了，想和小王上网去，这样子就不适用了。其实上面是伪代码，仅仅是帮助大家理解的，真正情况下是需要利用接口来设置回调的。现在我们继续用小明和小李去吃饭的例子来讲讲接口是如何使用的。\n\n小明和小李相约一起去吃早饭，但是小李起的有点晚要先洗漱，等小李洗漱完成后，通知小明再一起去吃饭。小明就是类A，小李就是类B。不同的是我们新建一个吃饭的接口EatRice，接口中有个抽象方法eat()。在小明中调用这个接口，并实现eat()；小李声明这个接口对象，并且调用这个接口的抽象方法。这里可能有点绕口，不过没关系，看看例子就很清楚了。\n\nEatRice接口：\n````\npublic interface EatRice {\n   public void eat(String food);\n}\n小明：\n\npublic class XiaoMing implements EatRice{//小明\n    \n   //小明和小李一起吃饭\n   public void eatFood() {\n    XiaoLi xl = new XiaoLi();\n    //A调用B的方法\n    xl.washFace(\"大龙虾\", this);//this指的是小明这个类实现的EatRice接口\n   }\n \n   @Override\n   public void eat(String food) {\n    // TODO Auto-generated method stub\n    System.out.println(\"小明和小李一起去吃\" + food);\n   }\n}\n小李:\n\npublic class XiaoLi{//小李\n   public void washFace(String food,EatRice er) {\n    System.out.println(\"小李要洗漱\");\n        //B调用了A的方法\n    er.eat(food);\n   }\n}\n测试Demo:\n\npublic class demo {\n   public static void main(String args[]) {\n    XiaoMing xm = new XiaoMing();\n    xm.eatFood();\n   }\n}\n````   \n测试结果：\n\n\n这样子就通过接口的形式实现了软编码。通过接口的形式我可以实现小李洗漱完后，和小王一起去上网。代码如下\n````\n    public class XiaoWang implements EatRice{//小王\n        \n       //小王和小李一起去上网\n       public void eatFood() {\n        XiaoLi xl = new XiaoLi();\n        //A调用B的方法\n        xl.washFace(\"轻舞飞扬上网\", this);\n       }\n     \n       @Override\n       public void eat(String bar) {\n        // TODO Auto-generated method stub\n        System.out.println(\"小王和小李一起去\" + bar);\n       }\n    }\n````\n## 实例三：Tom做题\n\n数学老师让Tom做一道题，并且Tom做题期间数学老师不用盯着Tom，而是在玩手机，等Tom把题目做完后再把答案告诉老师。\n\n> 1 数学老师需要Tom的一个引用，然后才能将题目发给Tom。\n> \n> 2 数学老师需要提供一个方法以便Tom做完题目以后能够将答案告诉他。\n> \n> 3 Tom需要数学老师的一个引用，以便Tom把答案给这位老师，而不是隔壁的体育老师。\n\n回调接口，可以理解为老师接口\n````\n//回调指的是A调用B来做一件事，B做完以后将结果告诉给A，这期间A可以做别的事情。\n//这个接口中有一个方法，意为B做完题目后告诉A时使用的方法。\n//所以我们必须提供这个接口以便让B来回调。\n//回调接口，\npublic interface CallBack {\n    void tellAnswer(int res);\n}\n````\n\n数学老师类\n````        \n    //老师类实例化回调接口，即学生写完题目之后通过老师的提供的方法进行回调。\n    //那么学生如何调用到老师的方法呢，只要在学生类的方法中传入老师的引用即可。\n    //而老师需要指定学生答题，所以也要传入学生的实例。\npublic class Teacher implements CallBack{\n    private Student student;\n\n    Teacher(Student student) {\n        this.student = student;\n    }\n\n    void askProblem (Student student, Teacher teacher) {\n        //main方法是主线程运行，为了实现异步回调，这里开启一个线程来操作\n        new Thread(new Runnable() {\n            @Override\n            public void run() {\n                student.resolveProblem(teacher);\n            }\n        }).start();\n        //老师让学生做题以后，等待学生回答的这段时间，可以做别的事，比如玩手机.\\\n        //而不需要同步等待，这就是回调的好处。\n        //当然你可以说开启一个线程让学生做题就行了，但是这样无法让学生通知老师。\n        //需要另外的机制去实现通知过程。\n        // 当然，多线程中的future和callable也可以实现数据获取的功能。\n        for (int i = 1;i < 4;i ++) {\n            System.out.println(\"等学生回答问题的时候老师玩了 \" + i + \"秒的手机\");\n        }\n    }\n\n    @Override\n    public void tellAnswer(int res) {\n        System.out.println(\"the answer is \" + res);\n    }\n}\n````\n学生接口\n````\n        //学生的接口，解决问题的方法中要传入老师的引用，否则无法完成对具体实例的回调。\n        //写为接口的好处就是，很多个学生都可以实现这个接口，并且老师在提问题时可以通过\n        //传入List<Student>来聚合学生，十分方便。\n    public interface Student {\n        void resolveProblem (Teacher teacher);\n    }\n````\n学生Tom\n\n````\n    public class Tom implements Student{\n    \n        @Override\n        public void resolveProblem(Teacher teacher) {\n            try {\n                //学生思考了3秒后得到了答案，通过老师提供的回调方法告诉老师。\n                Thread.sleep(3000);\n                System.out.println(\"work out\");\n                teacher.tellAnswer(111);\n            } catch (InterruptedException e) {\n                e.printStackTrace();\n            }\n        }\n````        \n测试类\n````\n    public class Test {\n        public static void main(String[] args) {\n            //测试\n            Student tom = new Tom();\n            Teacher lee = new Teacher(tom);\n            lee.askProblem(tom, lee);\n            //结果\n    //        等学生回答问题的时候老师玩了 1秒的手机\n    //        等学生回答问题的时候老师玩了 2秒的手机\n    //        等学生回答问题的时候老师玩了 3秒的手机\n    //        work out\n    //        the answer is 111\n        }\n    }\n````\n## 参考文章\n\nhttps://blog.csdn.net/fengye454545/article/details/80198446\nhttps://blog.csdn.net/xiaanming/article/details/8703708/\nhttps://www.cnblogs.com/prayjourney/p/9667835.html\nhttps://blog.csdn.net/qq_25652949/article/details/86572948\nhttps://my.oschina.net/u/3703858/blog/1798627\n\n"
  },
  {
    "path": "docs/Java/basic/面向对象基础.md",
    "content": "点击关注[公众号](#公众号)及时获取笔主最新更新文章，并可免费领取Java工程师必备学习资源。\n\n* [Java面向对象三大特性（基础篇）](#java面向对象三大特性（基础篇）)\n  * [对象的概念](#对象的概念)\n  * [面向对象和面向过程的区别](#面向对象和面向过程的区别)\n  * [面向对象的三大核心特性简介](#面向对象的三大核心特性简介)\n  * [面向对象编程三大特性详解](#面向对象编程三大特性详解)\n    * [一、继承](#一、继承)\n      * [1、继承的概念](#1、继承的概念)\n      * [2、继承的好处](#2、继承的好处)\n      * [3、语法规则](#3、语法规则)\n        * [A、方法的重写](#a、方法的重写)\n        * [B、继承的初始化顺序](#b、继承的初始化顺序)\n        * [C、final关键字](#c、final关键字)\n        * [D、super关键字](#d、super关键字)\n    * [二、封装](#二、封装)\n      * [1、封装的概念](#1、封装的概念)\n      * [2、封装的优点](#2、封装的优点)\n    * [三、多态](#三、多态)\n      * [1、多态的概念](#1、多态的概念)\n      * [2、多态的好处](#2、多态的好处)\n      * [3、Java中的多态](#3、java中的多态)\n        * [A、引用多态　　](#a、引用多态　　)\n        * [B、方法多态](#b、方法多态)\n        * [C、引用类型转换](#c、引用类型转换)\n  * [参考文章](#参考文章)\n\n\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章首发于我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《夯实Java基础系列博文》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n该系列博文会告诉你如何从入门到进阶，一步步地学习Java基础知识，并上手进行实战，接着了解每个Java知识点背后的实现原理，更完整地了解整个Java技术体系，形成自己的知识框架。为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n# Java面向对象三大特性（基础篇）\n\n面向对象简称 OO（Object Oriented），20 世纪 80 年代以后，有了面向对象分析（OOA）、 面向对象设计（OOD）、面向对象程序设计（OOP）等新的系统开发方式模型的研究。\n\n对语言来说，一切皆是对象。把现实世界中的对象抽象地体现在编程世界中，一个对象代表了某个具体的操作。一个个对象最终组成了完整的程序设计，这些对象可以是独立存在的，也可以是从别的对象继承过来的。对象之间通过相互作用传递信息，实现程序开发。\n\n## 对象的概念\n\nJava 是面向对象的编程语言，对象就是面向对象程序设计的核心。所谓对象就是真实世界中的实体，对象与实体是一一对应的，也就是说现实世界中每一个实体都是一个对象，它是一种具体的概念。对象有以下特点：\n\n*   对象具有属性和行为。\n*   对象具有变化的状态。\n*   对象具有唯一性。\n*   对象都是某个类别的实例。\n*   一切皆为对象，真实世界中的所有事物都可以视为对象。\n\n## 面向对象和面向过程的区别\n\n- 面向过程：\n一种较早的编程思想，顾名思义就是该思想是站着过程的角度思考问题，强调的就是功能行为，功能的执行过程，即先后顺序，而每一个功能我们都使用函数（类似于方法）把这些步骤一步一步实现。使用的时候依次调用函数就可以了。\n\n- 面向过程的设计：\n  最小的程序单元是函数，每个函数负责完成某一个功能，用于接受输入数据，函数对输入数据进行处理，然后输出结果数据，整个软件系统由一个个的函数组成，其中作为程序入口的函数称之为主函数，主函数依次调用其他函数，普通函数之间可以相互调用，从而实现整个系统功能。 \n  面向过程最大的问题在于随着系统的膨胀，面向过程将无法应付，最终导致系统的崩溃。为了解决这一种软件危机，我们提出面向对象思想。\n\n- 面向过程的缺陷：\n  是采用自顶而下的设计模式，在设计阶段就需要考虑每一个模块应该分解成哪些子模块，每一个子模块又细分为更小的子模块，如此类推，直到将模块细化为一个个函数。\n\n- 存在的问题 \n  设计不够直观，与人类的思维习惯不一致\n  系统软件适应新差，可拓展性差，维护性低\n\n- 面向对象： \n  一种基于面向过程的新编程思想，顾名思义就是该思想是站在对象的角度思考问题，我们把多个功能合理放到不同对象里，强调的是具备某些功能的对象。\n  具备某种功能的实体，称为对象。面向对象最小的程序单元是：类。面向对象更加符合常规的思维方式，稳定性好，可重用性强，易于开发大型软件产品，有良好的可维护性。\n  在软件工程上，面向对象可以使工程更加模块化，实现更低的耦合和更高的内聚。\n\n\n## 面向对象的三大核心特性简介\n\n面向对象开发模式更有利于人们开拓思维，在具体的开发过程中便于程序的划分，方便程序员分工合作，提高开发效率。\n\n该开发模式之所以使程序设计更加完善和强大，主要是因为面向对象具有继承、封装和多态 3 个核心特性。\n\n**1、继承的概念**\n\n继承是java面向对象编程技术的一块基石，因为它允许创建分等级层次的类。\n\n继承就是子类继承父类的特征和行为，使得子类对象（实例）具有父类的实例域和方法，或子类从父类继承方法，使得子类具有父类相同的行为。\n\n\n\n![img](https://www.runoob.com/wp-content/uploads/2013/12/14B0951E-FC75-47A3-B611-4E1883887339.jpg)\n\n兔子和羊属于食草动物类，狮子和豹属于食肉动物类。\n\n食草动物和食肉动物又是属于动物类。\n\n所以继承需要符合的关系是：is-a，父类更通用，子类更具体。\n\n虽然食草动物和食肉动物都是属于动物，但是两者的属性和行为上有差别，所以子类会具有父类的一般特性也会具有自身的特性。\n\n**2、Java 多态**\n\n------\n\n多态是同一个行为具有多个不同表现形式或形态的能力。\n\n多态就是同一个接口，使用不同的实例而执行不同操作，如图所示：\n\n\n\n![img](https://www.runoob.com/wp-content/uploads/2013/12/dt-java.png)\n\n多态性是对象多种表现形式的体现。\n\n> 现实中，比如我们按下 F1 键这个动作：\n>\n> - 如果当前在 Flash 界面下弹出的就是 AS 3 的帮助文档；\n> - 如果当前在 Word 下弹出的就是 Word 帮助；\n> - 在 Windows 下弹出的就是 Windows 帮助和支持。\n>\n> 同一个事件发生在不同的对象上会产生不同的结果。\n\n**3、Java 封装**\n\n------\n\n在面向对象程式设计方法中，封装（英语：Encapsulation）是指一种将抽象性函式接口的实现细节部份包装、隐藏起来的方法。\n\n封装可以被认为是一个保护屏障，防止该类的代码和数据被外部类定义的代码随机访问。\n\n要访问该类的代码和数据，必须通过严格的接口控制。\n\n封装最主要的功能在于我们能修改自己的实现代码，而不用修改那些调用我们代码的程序片段。\n\n适当的封装可以让程式码更容易理解与维护，也加强了程式码的安全性。\n\n## 面向对象编程三大特性详解\n\n面向对象编程是利用 类和对象编程的一种思想。万物可归类，类是对于世界事物的高度抽象 ，不同的事物之间有不同的关系 ，一个类自身与外界的封装关系，一个父类和子类的继承关系， 一个类和多个类的多态关系。万物皆对象，对象是具体的世界事物，面向对象的三大特征封装，继承，多态，封装，封装说明一个类行为和属性与其他类的关系，低耦合，高内聚；继承是父类和子类的关系，多态说的是类与类的关系。\n\n### 一、继承\n\n#### 1、继承的概念\n\n如同生活中的子女继承父母拥有的所有财产，程序中的继承性是指子类拥有父类[数据结构](http://c.biancheng.net/data_structure/)的方法和机制，这是类之间的一种关系；继承只能是单继承。\n\n例如定义一个语文老师类和数学老师类，如果不采用继承方式，那么两个类中需要定义的属性和方法如图 1 所示。\n\n![](http://c.biancheng.net/uploads/allimg/181017/3-1Q01G40613629.jpg)\n图1 语文老师类和数学老师类中的属性和方法\n\n从图 1 能够看出，语文老师类和数学老师类中的许多属性和方法相同，这些相同的属性和方法可以提取出来放在一个父类中，这个父类用于被语文老师类和数学老师类继承。当然父类还可以继承别的类，如图 2 所示。\n\n![](http://c.biancheng.net/uploads/allimg/181017/3-1Q01G40AR23.jpg)\n图2 父类继承示例图\n\n总结图 2 的继承关系，可以用概括的树形关系来表示，如图 3 所示。\n\n![](http://c.biancheng.net/uploads/allimg/181017/3-1Q01G40RT47.jpg)\n图3 类继承示例图\n\n从图 3 中可以看出，学校主要人员是一个大的类别，老师和学生是学校主要人员的两个子类，而老师又可以分为语文老师和数学老师两个子类，学生也可以分为班长和组长两个子类。\n\n使用这种层次形的分类方式，是为了将多个类的通用属性和方法提取出来，放在它们的父类中，然后只需要在子类中各自定义自己独有的属性和方法，并以继承的形式在父类中获取它们的通用属性和方法即可。\n\n　继承是类与类的一种关系，是一种“is a”的关系。比如“狗”继承“动物”，这里动物类是狗类的父类或者基类，狗类是动物类的子类或者派生类。\n注：java中的继承是**单继承**，即**一个类只有一个父类。**\n\n**补充：Java中的继承只能单继承，但是可以通过内部类继承其他类来实现多继承。**\n\n```\npublic class Son extends Father{\npublic void go () {\nSystem.out.println(\"son go\");\n}\npublic void eat () {\nSystem.out.println(\"son eat\");\n}\npublic void sleep() {\nSystem.out.println(\"zzzzzz\");\n}\npublic void cook() {\n//匿名内部类实现的多继承\nnew Mother().cook();\n//内部类继承第二个父类来实现多继承\nMom mom = new Mom();\nmom.cook();\n}\nprivate class Mom extends Mother {\n@Override\npublic void cook() {\nSystem.out.println(\"mom cook\");\n}\n}\n}\n```\n\n#### 2、继承的好处\n\n　子类拥有父类的所有属性和方法（除了private修饰的属性不能拥有）从而实现了实现代码的复用；　\n\n ##### A、方法的重写\n\n子类如果对继承的父类的方法不满意（不适合），可以自己编写继承的方法，这种方式就称为**方法的重写。当调用方法时会优先调用子类的方法。**\n\n**重写要注意：**\n\na、返回值类型\n\nb、方法名\n\nc、参数类型及个数\n\n都要与父类继承的方法相同，才叫方法的重写。\n\n**重载和重写的区别：**\n\n方法重载：在同一个类中处理不同数据的多个相同方法名的多态手段。\n\n方法重写：相对继承而言，子类中对父类已经存在的方法进行区别化的修改。\n\n------\n\n##### B、继承的初始化顺序\n\n1、初始化父类再初始化子类\n\n2、先执行初始化对象中属性，再执行构造方法中的初始化。\n\n基于上面两点，我们就知道实例化一个子类，java程序的执行顺序是：\n\n**父类对象属性初始化---->父类对象构造方法---->子类对象属性初始化--->子类对象构造方法**　　　\n\n------\n\n##### C、final关键字\n\n　使用final关键字做标识有“最终的”含义。\n\n　　1. final 修饰类，则该类**不允许被继承。**\n\n　　2. final 修饰方法，则该方法不允许被**覆盖(重写)**。\n\n　　3. final 修饰属性，则该类的该属性不会进行隐式的初始化，所以 该final 属性的**初始化属性必须有值**，或在**构造方法中赋值(但只能选其一，且必须选其一，因为没有默认值！)，**且初始化之后就不能改了，**只能赋值一次**。\n\n　　4. final 修饰变量，则该变量的值只能赋一次值，在声明变量的时候才能赋值，即变为**常量**。\n\n------\n\n##### D、super关键字\n\n　在对象的内部使用，可以代表父类对象。\n\n　　1、访问父类的属性：super.age\n\n　　 2、访问父类的方法：super.eat()\n\n　super的应用：\n\n　首先我们知道子类的构造的过程当中必须调用父类的构造方法。其实这个过程已经隐式地使用了我们的super关键字。\n\n　这是因为如果子类的构造方法中没有显示调用父类的构造方法，则系统默认调用父类无参的构造方法。\n\n　那么如果自己用super关键字在子类里调用父类的构造方法，则必须在子类的构造方法中的**第一行**。\n\n　**要注意的是：如果子类构造方法中既没有显示调用父类的构造方法，而父类没有无参的构造方法，则编译出错。**\n\n（补充说明，虽然没有显示声明父类的无参的构造方法，系统会自动默认生成一个无参构造方法，但是，如果你声明了一个有参的构造方法，而没有声明无参的构造方法，这时系统不会动默认生成一个无参构造方法，此时称为父类有没有无参的构造方法。）\n\n------\n\n### 二、封装\n\n#### 1、封装的概念\n\n封装是将代码及其处理的数据绑定在一起的一种编程机制，该机制保证了程序和数据都不受外部干扰且不被误用。封装的目的在于保护信息，使用它的主要优点如下。\n\n*   保护类中的信息，它可以阻止在外部定义的代码随意访问内部代码和数据。\n*   隐藏细节信息，一些不需要程序员修改和使用的信息，比如取款机中的键盘，用户只需要知道按哪个键实现什么操作就可以，至于它内部是如何运行的，用户不需要知道。\n*   有助于建立各个系统之间的松耦合关系，提高系统的独立性。当一个系统的实现方式发生变化时，只要它的接口不变，就不会影响其他系统的使用。例如 U 盘，不管里面的存储方式怎么改变，只要 U 盘上的 USB 接口不变，就不会影响用户的正常操作。\n*   提高软件的复用率，降低成本。每个系统都是一个相对独立的整体，可以在不同的环境中得到使用。例如，一个 U 盘可以在多台电脑上使用。\n\nJava 语言的基本封装单位是类。由于类的用途是封装复杂性，所以类的内部有隐藏实现复杂性的机制。Java 提供了私有和公有的访问模式，类的公有接口代表外部的用户应该知道或可以知道的每件东西，私有的方法数据只能通过该类的成员代码来访问，这就可以确保不会发生不希望的事情。\n\n#### 2、封装的优点\n\n在面向对象程式设计方法中，封装（英语：Encapsulation）是指一种将抽象性函式接口的实现细节部份包装、隐藏起来的方法。\n\n封装可以被认为是一个保护屏障，防止该类的代码和数据被外部类定义的代码随机访问。\n\n要访问该类的代码和数据，必须通过严格的接口控制。\n\n封装最主要的功能在于我们能修改自己的实现代码，而不用修改那些调用我们代码的程序片段。\n\n适当的封装可以让程式码更容易理解与维护，也加强了程式码的安全性。\n\n封装的优点\n\n1. 良好的封装能够减少耦合。\n\n2. 类内部的结构可以自由修改。\n\n3. 可以对成员变量进行更精确的控制。\n\n4. 隐藏信息，实现细节。\n\nJava 封装，说白了就是将一大坨公共通用的实现逻辑玩意，装到一个盒子里（class），出入口都在这个盒子上。你要用就将这个盒子拿来用，连接出入口，就能用了，不用就可以直接扔，对你代码没什么影响。\n\n对程序员来说，使用封装的目的：\n\n1. 偷懒，辛苦一次，后面都能少敲很多代码，增强了代码得复用性\n\n2. 简化代码，看起来更容易懂\n\n3. 隐藏核心实现逻辑代码，简化外部逻辑，并且不让其他人修改，jar 都这么干\n\n4. 一对一，一个功能就只为这个功能服务；避免头发绳子一块用，导致最后一团糟\n\n\n##### C、Java 中的内部类\n\n内部类（ Inner Class ）就是定义在另外一个类**里面**的类。与之对应，包含内部类的类被称为外部类。\n\n那么问题来了：那为什么要将一个类定义在另一个类里面呢？清清爽爽的独立的一个类多好啊！！\n\n答：内部类的主要作用如下：\n\n1. 内部类提供了**更好的封装**，可以把内部类**隐藏**在外部类之内，**不允许**同一个包中的其他类访问该类。\n\n2. 内部类的方法可以**直接访问外部类的所有数据**，包括**私有的数据**。\n\n3. 内部类所实现的功能使用外部类同样可以实现，只是有时使用内部类更方便。\n\n内部类可分为以下几种： \n\n- 成员内部类\n\n- 静态内部类\n- 方法内部类\n- 匿名内部类　　\n\n### 三、多态\n\n#### 1、多态的概念\n\n面向对象的多态性，即“一个接口，多个方法”。多态性体现在父类中定义的属性和方法被子类继承后，可以具有不同的属性或表现方式。多态性允许一个接口被多个同类使用，弥补了单继承的不足。多态概念可以用树形关系来表示，如图 4 所示。\n\n![](http://c.biancheng.net/uploads/allimg/181017/3-1Q01G4095bW.jpg)\n\n图4 多态示例图\n\n从图 4 中可以看出，老师类中的许多属性和方法可以被语文老师类和数学老师类同时使用，这样也不易出错。\n\n#### 2、多态的好处\n\n可替换性（substitutability）。多态对已存在代码具有可替换性。例如，多态对圆Circle类工作，对其他任何圆形几何体，如圆环，也同样工作。\n\n可扩充性（extensibility）。多态对代码具有可扩充性。增加新的子类不影响已存在类的多态性、继承性，以及其他特性的运行和操作。实际上新加子类更容易获得多态功能。例如，在实现了圆锥、半圆锥以及半球体的多态基础上，很容易增添球体类的多态性。\n\n接口性（interface-ability）。多态是超类通过方法签名，向子类提供了一个共同接口，由子类来完善或者覆盖它而实现的。\n\n灵活性（flexibility）。它在应用中体现了灵活多样的操作，提高了使用效率。\n\n简化性（simplicity）。多态简化对应用软件的代码编写和修改过程，尤其在处理大量对象的运算和操作时，这个特点尤为突出和重要。\n\n子代父类实例化，然后就相当于一个父亲有很多儿子，送快递的给这个父亲的儿子送东西，他只需要送到父亲的家就行了，至于具体是那个儿子的，父亲还会分不清自己的儿子么，所以你就不用操心了。\n\n**使用多态是一种好习惯**\n多态方式声明是一种好的习惯。当我们创建的类，使用时，只用到它的超类或接口定义的方法时，我们可以将其索引声明为它的超类或接口类型。\n\n它的好处是，如果某天我们对这个接口方法的实现方式变了，对这个接口又有一个新的实现类，我们的程序也需要使用最新的实现方式，此时只要将对象实现修改一下，索引无需变化。\n\n比如Map< String,String> map = new HashMap < String,String>();\n\n想换成HashTable实现，可以Map< String,String> map = new HashTable < String,String>();\n\n比如写一个方法，参数要求传递List类型，你就可以用List list = new ArrayList()中的list传递，但是你写成ArrayList list = new ArrayList()是传递不进去的。尽管方法处理时都一样。另外，方法还可以根据你传递的不同list（ArrayList或者LinkList）进行不同处理。\n\n#### 3、Java中的多态\n\njava里的多态主要表现在两个方面：\n\n##### A、引用多态　　\n\n　　父类的引用可以指向本类的对象；\n\n　　父类的引用可以指向子类的对象；\n\n　　这两句话是什么意思呢，让我们用代码来体验一下，首先我们创建一个父类Animal和一个子类Dog，在主函数里如下所示：\n\n![img](https://cdn.mianshigee.com/upload/note/thumbnails/202202211611552259.png)\n\n　　注意：我们不能使用一个子类的引用来指向父类的对象，如：\n![img](https://cdn.mianshigee.com/upload/note/thumbnails/202202211611556958.png)。\n\n　　这里我们必须深刻理解引用多态的意义，才能更好记忆这种多态的特性。为什么子类的引用不能用来指向父类的对象呢？我在这里通俗给大家讲解一下：就以上面的例子来说，我们能说“狗是一种动物”，但是不能说“动物是一种狗”，狗和动物是父类和子类的继承关系，它们的从属是不能颠倒的。当父类的引用指向子类的对象时，该对象将只是看成一种特殊的父类（里面有重写的方法和属性），反之，一个子类的引用来指向父类的对象是不可行的！！\n\n##### B、方法多态\n\n　　根据上述创建的两个对象：本类对象和子类对象，同样都是父类的引用，当我们指向不同的对象时，它们调用的方法也是多态的。\n\n　　创建本类对象时，调用的方法为本类方法；\n\n　　创建子类对象时，调用的方法为子类重写的方法或者继承的方法；\n\n　　使用多态的时候要注意：**如果我们在子类中编写一个独有的方法（没有继承父类的方法），此时就不能通过父类的引用创建的子类对象来调用该方法！！！**\n\n　　**注意： 继承是多态的基础。**\n\n------\n\n##### C、引用类型转换\n\n　了解了多态的含义后，我们在日常使用多态的特性时经常需要进行引用类型转换。\n\n　引用类型转换：\n\n　**1.向上类型转换(隐式/自动类型转换)，是小类型转换到大类型**\n\n　 就以上述的父类Animal和一个子类Dog来说明，当父类的引用可以指向子类的对象时，就是**向上类型转换**。如：\n\n   ![img](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9pbWFnZXMyMDE1LmNuYmxvZ3MuY29tL2Jsb2cvMTE4OTMxMi8yMDE3MDcvMTE4OTMxMi0yMDE3MDcwMTE2MjYzMDUwOC05NjE1MDc2NTkucG5n?x-oss-process=image/format,png)\n\n　 **2. 向下类型转换(强制类型转换)，是大类型转换到小类型(有风险,可能出现数据溢出)。**\n\n　　将上述代码再加上一行，我们再次将父类转换为子类引用，那么会出现错误，编译器不允许我们直接这么做**，**虽然我们知道这个父类引用指向的就是子类对象，但是编译器认为这种转换是存在风险的**。**如：\n\n   ![img](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9pbWFnZXMyMDE1LmNuYmxvZ3MuY29tL2Jsb2cvMTE4OTMxMi8yMDE3MDcvMTE4OTMxMi0yMDE3MDcwMTE2MjkyNjQ3Ny0zODU3OTc1LnBuZw?x-oss-process=image/format,png)\n\n　　那么我们该怎么解决这个问题呢，我们可以在animal前加上（Dog）来强制类型转换。如：![img](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9pbWFnZXMyMDE1LmNuYmxvZ3MuY29tL2Jsb2cvMTE4OTMxMi8yMDE3MDcvMTE4OTMxMi0yMDE3MDcwMTE2NDIyOTg5OS0xMDU1MTkwNzc0LnBuZw?x-oss-process=image/format,png)\n\n　　但是如果父类引用没有指向**该子类的对象**，则不能向下类型转换，虽然编译器不会报错，但是运行的时候程序会出错，如：![img](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9pbWFnZXMyMDE1LmNuYmxvZ3MuY29tL2Jsb2cvMTE4OTMxMi8yMDE3MDcvMTE4OTMxMi0yMDE3MDcwMTE2NTEzMzI4OS03MTc0MzkzNjAucG5n?x-oss-process=image/format,png)\n\n　　其实这就是上面所说的子类的引用指向父类的对象，而强制转换类型也不能转换！！\n\n　　还有一种情况是父类的引用指向**其他子类的对象**，则不能通过强制转为**该子类的对象**。如：\n\n　　![img](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9pbWFnZXMyMDE1LmNuYmxvZ3MuY29tL2Jsb2cvMTE4OTMxMi8yMDE3MDcvMTE4OTMxMi0yMDE3MDcwMTE2NTEzMzI4OS03MTc0MzkzNjAucG5n?x-oss-process=image/format,png)\n\n　　这是因为我们在编译的时候进行了强制类型转换，编译时的类型是我们强制转换的类型，所以编译器不会报错，而当我们运行的时候，程序给animal开辟的是Dog类型的内存空间，这与Cat类型内存空间不匹配，所以无法正常转换。这两种情况出错的本质是一样的，所以我们在使用强制类型转换的时候要特别注意这两种错误！！下面有个更安全的方式来实现向下类型转换。。。。\n\n　    **3. instanceof运算符，来解决引用对象的类型，避免类型转换的安全性问题。**\n\n　　**instanceof**是Java的一个二元操作符，和==，>，<是同一类东东。由于它是由字母组成的，所以也是Java的保留关键字。**它的作用是测试它左边的对象是否是它右边的类的实例**，返回boolean类型的数据。\n\n　　我们来使用instanceof运算符来规避上面的错误，代码修改如下：\n\n　　![img](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9pbWFnZXMyMDE1LmNuYmxvZ3MuY29tL2Jsb2cvMTE4OTMxMi8yMDE3MDcvMTE4OTMxMi0yMDE3MDcwMTE2NTYyNjU3MS01MDEyMjgyNTQucG5n?x-oss-process=image/format,png)\n\n　　利用if语句和instanceof运算符来判断两个对象的类型是否一致。\n\n　　**补充说明：**在比较一个对象是否和另一个对象属于同一个类实例的时候，我们通常可以采用instanceof和getClass两种方法通过两者是否相等来判断，但是两者在判断上面是有差别的。Instanceof进行类型检查规则是:**你属于该类吗？或者你属于该类的派生类吗？**而通过getClass获得类型信息采用==来进行检查是否相等的操作是**严格的判断**,**不会存在继承方面的考虑**；\n\n　　**总结：**在写程序的时候，如果要进行类型转换，我们最好使用instanceof运算符来判断它左边的对象是否是它右边的类的实例，再进行强制转换。\n\n------\n\n**D、重写和重载**　\n\n多态一般可以分为两种，一个是重写override，一个是重载overload。\n\n```\n重写是由于继承关系中的子类有一个和父类同名同参数的方法，会覆盖掉父类的方法。重载是因为一个同名方法可以传入多个参数组合。\n\n注意，同名方法如果参数相同，即使返回值不同也是不能同时存在的，编译会出错。\n\n从jvm实现的角度来看，重写又叫运行时多态，编译时看不出子类调用的是哪个方法，但是运行时操作数栈会先根据子类的引用去子类的类信息中查找方法，找不到的话再到父类的类信息中查找方法。\n\n而重载则是编译时多态，因为编译期就可以确定传入的参数组合，决定调用的具体方法是哪一个了。\n```\n\n**1. 向上转型和向下转型**\n\n```\npublic static void main(String[] args) {\n    Son son = new Son();\n    //首先先明确一点，转型指的是左侧引用的改变。\n    //father引用类型是Father，指向Son实例，就是向上转型，既可以使用子类的方法，也可以使用父类的方法。\n    //向上转型,此时运行father的方法\n    Father father = son;\n    father.smoke();\n    //不能使用子类独有的方法。\n    // father.play();编译会报错\n    father.drive();\n    //Son类型的引用指向Father的实例，所以是向下转型，不能使用子类非重写的方法，可以使用父类的方法。\n\n    //向下转型，此时运行了son的方法\n    Son son1 = (Son) father;\n    //转型后就是一个正常的Son实例\n    son1.play();\n    son1.drive();\n    son1.smoke();\n    \n    //因为向下转型之前必须先经历向上转型。\n\t//在向下转型过程中，分为两种情况：\n\n\t//情况一：如果父类引用的对象如果引用的是指向的子类对象，\n\t//那么在向下转型的过程中是安全的。也就是编译是不会出错误的。\n    //因为运行期Son实例确实有这些方法\n    Father f1 = new Son();\n    Son s1 = (Son) f1;\n    s1.smoke();\n    s1.drive();\n    s1.play();\n\n    //情况二：如果父类引用的对象是父类本身，那么在向下转型的过程中是不安全的，编译不会出错，\n    //但是运行时会出现java.lang.ClassCastException错误。它可以使用instanceof来避免出错此类错误。\n    //因为运行期Father实例并没有这些方法。\n        Father f2 = new Father();\n        Son s2 = (Son) f2;\n        s2.drive();\n        s2.smoke();\n        s2.play();\n\n    //向下转型和向上转型的应用，有些人觉得这个操作没意义，何必先向上转型再向下转型呢，不是多此一举么。其实可以用于方法参数中的类型聚合，然后具体操作再进行分解。\n    //比如add方法用List引用类型作为参数传入，传入具体类时经历了向下转型\n    add(new LinkedList());\n    add(new ArrayList());\n\n    //总结\n    //向上转型和向下转型都是针对引用的转型，是编译期进行的转型，根据引用类型来判断使用哪个方法\n    //并且在传入方法时会自动进行转型（有需要的话）。运行期将引用指向实例，如果是不安全的转型则会报错。\n    //若安全则继续执行方法。\n\n}\npublic static void add(List list) {\n    System.out.println(list);\n    //在操作具体集合时又经历了向上转型\n//        ArrayList arr = (ArrayList) list;\n//        LinkedList link = (LinkedList) list;\n}\n```\n\n总结：\n向上转型和向下转型都是针对引用的转型，是编译期进行的转型，根据引用类型来判断使用哪个方法。并且在传入方法时会自动进行转型（有需要的话）。运行期将引用指向实例，如果是不安全的转型则会报错，若安全则继续执行方法。\n\n**2. 编译期的静态分派**\n\n其实就是根据引用类型来调用对应方法。\n\n```\npublic static void main(String[] args) {\n    Father father  = new Son();\n    静态分派 a= new 静态分派();\n\n    //编译期确定引用类型为Father。\n    //所以调用的是第一个方法。\n    a.play(father);\n    //向下转型后，引用类型为Son，此时调用第二个方法。\n    //所以，编译期只确定了引用，运行期再进行实例化。\n    a.play((Son)father);\n    //当没有Son引用类型的方法时，会自动向上转型调用第一个方法。\n    a.smoke(father);\n    //\n```\n\n```\n}\npublic void smoke(Father father) {\n    System.out.println(\"father smoke\");\n}\npublic void play (Father father) {\n    System.out.println(\"father\");\n    //father.drive();\n}\npublic void play (Son son) {\n    System.out.println(\"son\");\n    //son.drive();\n}\n```\n\n**3. 方法重载优先级匹配**\n\n```\npublic static void main(String[] args) {\n    方法重载优先级匹配 a = new 方法重载优先级匹配();\n    //普通的重载一般就是同名方法不同参数。\n    //这里我们来讨论当同名方法只有一个参数时的情况。\n    //此时会调用char参数的方法。\n    //当没有char参数的方法。会调用int类型的方法，如果没有int就调用long\n    //即存在一个调用顺序char -> int -> long ->double -> ..。\n    //当没有基本类型对应的方法时，先自动装箱，调用包装类方法。\n    //如果没有包装类方法，则调用包装类实现的接口的方法。\n    //最后再调用持有多个参数的char...方法。\n    a.eat('a');\n    a.eat('a','c','b');\n}\npublic void eat(short i) {\n    System.out.println(\"short\");\n}\npublic void eat(int i) {\n    System.out.println(\"int\");\n}\npublic void eat(double i) {\n    System.out.println(\"double\");\n}\npublic void eat(long i) {\n    System.out.println(\"long\");\n}\npublic void eat(Character c) {\n    System.out.println(\"Character\");\n}\npublic void eat(Comparable c) {\n    System.out.println(\"Comparable\");\n}\npublic void eat(char ... c) {\n    System.out.println(Arrays.toString(c));\n    System.out.println(\"...\");\n}\n\n//    public void eat(char i) {\n//        System.out.println(\"char\");\n//    }\n```\n\n## 参考文章\n\n<https://segmentfault.com/a/1190000009707894>\n\n<https://www.cnblogs.com/hysum/p/7100874.html>\n\n<http://c.biancheng.net/view/939.html>\n\n<https://www.runoob.com/>\n\n<https://blog.csdn.net/android_hl/article/details/53228348/>\n\n\n\n"
  },
  {
    "path": "docs/Java/collection/Java集合类总结.md",
    "content": "# 目录\n  * [Colletion，iterator，comparable](#colletion，iterator，comparable)\n  * [List](#list)\n  * [Map](#map)\n  * [CHM](#chm)\n  * [Set](#set)\n  * [Linkedhashmap](#linkedhashmap)\n  * [collections和Arrays工具类](#collections和arrays工具类)\n  * [comparable和comparator](#comparable和comparator)\n  * [treemap和treeset](#treemap和treeset)\n\n\n这篇总结是基于之前博客内容的一个整理和回顾。\n\n这里先简单地总结一下，更多详细内容请参考我的专栏：深入浅出Java核心技术\n\nhttps://blog.csdn.net/column/details/21930.html\n\n里面有包括Java集合类在内的众多Java核心技术系列文章。\n\n\n以下总结不保证全对，如有错误，还望能够指出。谢谢\n<!-- more -->\n\n## Colletion，iterator，comparable\n\n\n一般认为Collection是最上层接口，但是hashmap实际上实现的是Map接口。iterator是迭代器，是实现iterable接口的类必须要提供的一个东西，能够使用for(i : A) 这种方式实现的类型能提供迭代器，以前有一个enumeration，现在早弃用了。\n\n\n## List\n\n\nList接口下的实现类有ArrayList，linkedlist，vector等等，一般就是用这两个，用法不多说，老生常谈。\nArrayList的扩容方式是1.5倍扩容，这样扩容避免2倍扩容可能浪费空间，是一种折中的方案。\n另外他不是线程安全，vector则是线程安全的，它是两倍扩容的。\n\nlinkedlist没啥好说的，多用于实现链表。\n\n## Map\n\n\nmap永远都是重头戏。\n\n\nhashmap是数组和链表的组合结构，数组是一个Entry数组，entry是k-V键值对类型，所以一个entry数组存着很entry节点，一个entry的位置通过key的hashcode方法，再进行hash（移位等操作），最后与表长-1进行相与操作，其实就是取hash值到的后n - 1位，n代表表长是2的n次方。\n\n\nhashmap的默认负载因子是0.75，阈值是16 * 0.75 = 12；初始长度为16；\n\n\nhashmap的增删改查方式比较简单，都是遍历，替换。有一点要注意的是key相等时，替换元素，不相等时连成链表。\n\n\n除此之外，1.8jdk改进了hashmap，当链表上的元素个数超过8个时自动转化成红黑树，节点变成树节点，以提高搜索效率和插入效率到logn。\n\n\n还有一点值得一提的是，hashmap的扩容操作，由于hashmap非线程安全，扩容时如果多线程并发进行操作，则可能有两个线程分别操作新表和旧表，导致节点成环，查询时会形成死锁。chm避免了这个问题。\n\n\n另外，扩容时会将旧表元素移到新表，原来的版本移动时会有rehash操作，每个节点都要rehash，非常不方便，而1.8改成另一种方式，对于同一个index下的链表元素，由于一个元素的hash值在扩容后只有两种情况，要么是hash值不变，要么是hash值变为原来值+2^n次方，这是因为表长翻倍，所以hash值取后n位，第一位要么是0要么是1，所以hash值也只有两种情况。这两种情况的元素分别加到两个不同的链表。这两个链表也只需要分别放到新表的两个位置即可，是不是很酷。\n\n\n最后有一个比较冷门的知识点，hashmap1.7版本链表使用的是节点的头插法，扩容时转移链表仍然使用头插法，这样的结果就是扩容后链表会倒置，而hashmap.1.8在插入时使用尾插法，扩容时使用头插法，这样可以保证顺序不变。\n\n\n## CHM\n\n\nconcurrenthashmap也稍微提一下把，chm1.7使用分段锁来控制并发，每个segment对应一个segmentmask，通过key的hash值相与这个segmentmask得到segment位置，然后在找到具体的entry数组下标。所以chm需要维护多个segment，每个segment对应一个数组。分段锁使用的是ReentrantLock可重入锁实现。查询时不加锁。\n\n\n1.8则放弃使用分段锁，改用cas+synchronized方式实现并发控制，查询时不加锁，插入时如果没有冲突直接cas到成功为止，有冲突则使用synchronized插入。\n\n\n\n\n## Set\n\n\nset就是hashmap将value固定为一个object，只存key元素包装成一个entry即可，其他不变。\n\n\n## Linkedhashmap\n\n\n在原来hashmap基础上将所有的节点依据插入的次序另外连成一个链表。用来保持顺序，可以使用它实现lru缓存，当访问命中时将节点移到队头，当插入元素超过长度时，删除队尾元素即可。\n\n\n## collections和Arrays工具类\n两个工具类分别操作集合和数组，可以进行常用的排序，合并等操作。\n\n\n## comparable和comparator\n实现comparable接口可以让一个类的实例互相使用compareTo方法进行比较大小，可以自定义比较规则，comparator则是一个通用的比较器，比较指定类型的两个元素之间的大小关系。\n\n\n## treemap和treeset\n\n主要是基于红黑树实现的两个数据结构，可以保证key序列是有序的，获取sortedset就可以顺序打印key值了。其中涉及到红黑树的插入和删除，调整等操作，比较复杂，这里就不细说了。\n\n\n"
  },
  {
    "path": "docs/Java/collection/Java集合详解：HashMap和HashTable.md",
    "content": "# 目录\n  * [HashMap](#hashmap)\n    * [定义](#定义)\n    * [构造函数](#构造函数)\n    * [数据结构](#数据结构)\n    * [存储实现：put(key,vlaue)](#存储实现：putkeyvlaue)\n    * [JDK1.8的hashmap：put方法](#jdk18的hashmap：put方法)\n    * [扩容](#扩容)\n    * [读取实现：get(key)](#读取实现：getkey)\n  * [HashTable](#hashtable)\n    * [定义](#定义-1)\n    * [构造方法](#构造方法)\n    * [主要方法](#主要方法)\n  * [HashTable与HashMap的异同点](#hashtable与hashmap的异同点)\n  * [面试题：HashMap和HashTable的区别](#面试题：hashmap和hashtable的区别)\n  * [参考文章](#参考文章)\n\n    \n本文参考http://cmsblogs.com/?p=176\n\n\n《Java集合详解系列》是我在完成夯实Java基础篇的系列博客后准备开始整理的新系列文章。\n为了更好地诠释知识点，形成体系文章，本系列文章整理了很多优质的博客内容，如有侵权请联系我，一定删除。\n\n这些文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star、fork哈\n\n本系列文章将整理于我的个人博客：\n\n> www.how2playlife.com\n\n## HashMap\n\nHashMap也是我们使用非常多的Collection，它是基于哈希表的 Map 接口的实现，以key-value的形式存在。在HashMap中，key-value总是会当做一个整体来处理，系统会根据hash算法来来计算key-value的存储位置，我们总是可以通过key快速地存、取value。下面就来分析HashMap的存取。\n\n### 定义\n\nHashMap实现了Map接口，继承AbstractMap。其中Map接口定义了键映射到值的规则，而AbstractMap类提供 Map 接口的骨干实现，以最大限度地减少实现此接口所需的工作，其实AbstractMap类已经实现了Map，这里标注Map LZ觉得应该是更加清晰吧！\n````\npublic class HashMap<K,V>\n    extends AbstractMap<K,V>\n    implements Map<K,V>, Cloneable, Serializable\n````\n### 构造函数\n\n      HashMap提供了三个构造函数：\n    \n      HashMap()：构造一个具有默认初始容量 (16) 和默认加载因子 (0.75) 的空 HashMap。\n    \n      HashMap(int initialCapacity)：构造一个带指定初始容量和默认加载因子 (0.75) 的空 HashMap。\n    \n      HashMap(int initialCapacity, float loadFactor)：构造一个带指定初始容量和加载因子的空 HashMap。\n\n  在这里提到了两个参数：初始容量，加载因子。\n\n>   这两个参数是影响HashMap性能的重要参数，其中容量表示哈希表中桶的数量，初始容量是创建哈希表时的容量，加载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度，它衡量的是一个散列表的空间的使用程度，负载因子越大表示散列表的装填程度越高，反之愈小。\n>\n>   对于使用链表法的散列表来说，查找一个元素的平均时间是O(1+a)，因此如果负载因子越大，对空间的利用更充分，然而后果是查找效率的降低；如果负载因子太小，那么散列表的数据将过于稀疏，对空间造成严重浪费。系统默认负载因子为0.75，一般情况下我们是无需修改的。\n\n  HashMap是一种支持快速存取的数据结构，要了解它的性能必须要了解它的数据结构。\n\n### 数据结构\n\n>   我们知道在Java中最常用的两种结构是数组和模拟指针(引用)，几乎所有的数据结构都可以利用这两种来组合实现，HashMap也是如此。实际上HashMap是一个“链表散列”，如下是它的数据结构：\n\nHashMap数据结构图\n\n下图的table数组的每个格子都是一个桶。负载因子就是map中的元素占用的容量百分比。比如负载因子是0.75，初始容量（桶数量）为16时，那么允许装填的元素最大个数就是16*0.75 = 12，这个最大个数也被成为阈值，就是map中定义的threshold。超过这个阈值时，map就会自动扩容。\n\n\n### 存储实现：put(key,vlaue)\n首先我们先看源码\n````    \npublic V put(K key, V value) {\n    //当key为null，调用putForNullKey方法，保存null与table第一个位置中，这是HashMap允许为null的原因\n    if (key == null)\n        return putForNullKey(value);\n    //计算key的hash值，此处对原来元素的hashcode进行了再次hash\n    int hash = hash(key.hashCode());                  ------(1)\n    //计算key hash 值在 table 数组中的位置\n    int i = indexFor(hash, table.length);             ------(2)\n    //从i出开始迭代 e,找到 key 保存的位置\n    for (Entry<K, V> e = table[i]; e != null; e = e.next) {\n        Object k;\n        //判断该条链上是否有hash值相同的(key相同)\n        //若存在相同，则直接覆盖value，返回旧value\n        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {\n            V oldValue = e.value;    //旧值 = 新值\n            e.value = value;\n            e.recordAccess(this);\n            return oldValue;     //返回旧值\n        }\n    }\n    //修改次数增加1\n    modCount++;\n    //将key、value添加至i位置处\n    addEntry(hash, key, value, i);\n    return null;\n}\n````\n  通过源码我们可以清晰看到HashMap保存数据的过程为：首先判断key是否为null，若为null，则直接调用putForNullKey方法。\n\n  若不为空则先计算key的hash值，然后根据hash值搜索在table数组中的索引位置，如果table数组在该位置处有元素，则通过比较是否存在相同的key，若存在则覆盖原来key的value，==否则将该元素保存在链头（最先保存的元素放在链尾）==。\n\n  若table在该处没有元素，则直接保存。这个过程看似比较简单，其实深有内幕。有如下几点：\n\n>   1、 先看迭代处。此处迭代原因就是为了防止存在相同的key值，若发现两个hash值（key）相同时，HashMap的处理方式是用新value替换旧value，这里并没有处理key，这就解释了HashMap中没有两个相同的key。\n>\n>   2、 在看（1）、（2）处。这里是HashMap的精华所在。首先是hash方法，该方法为一个纯粹的数学计算，就是计算h的hash值。\n````\nstatic int hash(int h) {\n        h ^= (h >>> 20) ^ (h >>> 12);\n        return h ^ (h >>> 7) ^ (h >>> 4);\n    }\n````\n  我们知道对于HashMap的table而言，数据分布需要均匀（最好每项都只有一个元素，这样就可以直接找到），不能太紧也不能太松，太紧会导致查询速度慢，太松则浪费空间。计算hash值后，怎么才能保证table元素分布均与呢？我们会想到取模，但是由于取模的消耗较大，HashMap是这样处理的：调用indexFor方法。\n````\nstatic int indexFor(int h, int length) {\n        return h & (length-1);\n    }\n````\n  HashMap的底层数组长度总是2的n次方，在构造函数中存在：capacity <<= 1;这样做总是能够保证HashMap的底层数组长度为2的n次方。当length为2的n次方时，h&(length - 1)就相当于对length取模，而且速度比直接取模快得多，这是HashMap在速度上的一个优化。至于为什么是2的n次方下面解释。\n\n ==对length取模来得到hash是常用的hash索引方法，这里采用位运算的话效率更高。==\n\n  我们回到indexFor方法，该方法仅有一条语句：h&(length - 1)，这句话除了上面的取模运算外还有一个非常重要的责任：均匀分布table数据和充分利用空间。\n\n  这里我们假设length为16(2^n)和15，h为5、6、7。\n\n  当n=15时，6和7的结果一样，这样表示他们在table存储的位置是相同的，也就是产生了碰撞，6、7就会在一个位置形成链表，这样就会导致查询速度降低。诚然这里只分析三个数字不是很多，那么我们就看0-15。\n\n>   而当length = 16时，length – 1 = 15 即1111，那么进行低位&运算时，值总是与原来hash值相同，而进行高位运算时，其值等于其低位值。所以说当length = 2^n时，不同的hash值发生碰撞的概率比较小，这样就会使得数据在table数组中分布较均匀，查询速度也较快。\n>\n>   这里我们再来复习put的流程：当我们想一个HashMap中添加一对key-value时，系统首先会计算key的hash值，然后根据hash值确认在table中存储的位置。若该位置没有元素，则直接插入。否则迭代该处元素链表并依此比较其key的hash值。\n>\n>   如果两个hash值相等且key值相等(e.hash == hash && ((k = e.key) == key || key.equals(k))),则用新的Entry的value覆盖原来节点的value。如果两个hash值相等但key值不等 ，则将该节点插入该链表的链头。具体的实现过程见addEntry方法，如下：\n````\nvoid addEntry(int hash, K key, V value, int bucketIndex) {\n        //获取bucketIndex处的Entry\n        Entry<K, V> e = table[bucketIndex];\n        //将新创建的 Entry 放入 bucketIndex 索引处，并让新的 Entry 指向原来的 Entry \n        table[bucketIndex] = new Entry<K, V>(hash, key, value, e);\n        //若HashMap中元素的个数超过极限了，则容量扩大两倍\n        if (size++ >= threshold)\n            resize(2 * table.length);\n    }\n````\n这个方法中有两点需要注意：\n\n    后面添加的entry反而会接到前面。\n\n  一、是链的产生。\n\n  这是一个非常优雅的设计。系统总是将新的Entry对象添加到bucketIndex处。如果bucketIndex处已经有了对象，那么新添加的Entry对象将指向原有的Entry对象，形成一条Entry链，但是若bucketIndex处没有Entry对象，也就是e==null,那么新添加的Entry对象指向null，也就不会产生Entry链了。\n\n  二、扩容问题。\n\n  随着HashMap中元素的数量越来越多，发生碰撞的概率就越来越大，所产生的链表长度就会越来越长，这样势必会影响HashMap的速度，为了保证HashMap的效率，系统必须要在某个临界点进行扩容处理。\n\n  该临界点在当HashMap中元素的数量等于table数组长度*加载因子。但是扩容是一个非常耗时的过程，因为它需要重新计算这些数据在新table数组中的位置并进行复制处理。所以如果我们已经预知HashMap中元素的个数，那么预设元素的个数能够有效的提高HashMap的性能。\n\n### JDK1.8的hashmap：put方法\n````\nfinal V putVal(int hash, K key, V value, boolean onlyIfAbsent,\n               boolean evict) {\n    Node<K,V>[] tab; Node<K,V> p; int n, i;\n    if ((tab = table) == null || (n = tab.length) == 0)\n        n = (tab = resize()).length;\n    if ((p = tab[i = (n - 1) & hash]) == null)\n        tab[i] = newNode(hash, key, value, null);\n    else {\n        Node<K,V> e; K k;\n        if (p.hash == hash &&\n            ((k = p.key) == key || (key != null && key.equals(k))))\n            e = p;\n            //如果p是红黑树节点，则用另外的处理方法\n        else if (p instanceof TreeNode)\n            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);\n        else {\n            for (int binCount = 0; ; ++binCount) {\n                if ((e = p.next) == null) {\n                    p.next = newNode(hash, key, value, null);\n                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st\n                    //当链表节点数超过8个，则直接进行红黑树化。\n                        treeifyBin(tab, hash);\n                    break;\n                }\n                if (e.hash == hash &&\n                    ((k = e.key) == key || (key != null && key.equals(k))))\n                    break;\n                p = e;\n            }\n        }\n        if (e != null) { // existing mapping for key\n            V oldValue = e.value;\n            if (!onlyIfAbsent || oldValue == null)\n                e.value = value;\n            afterNodeAccess(e);\n            return oldValue;\n        }\n    }\n    ++modCount;\n    if (++size > threshold)\n        resize();\n    afterNodeInsertion(evict);\n    return null;\n}\n````\nJDK1.8在链表长度超过8时会转换为红黑树。\n转换方法如下：\n````\nfinal void treeifyBin(Node<K,V>[] tab, int hash) {\n    int n, index; Node<K,V> e;\n    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)\n    //如果节点数变小小于红黑树的节点数阈值时，调整空间\n        resize();\n    else if ((e = tab[index = (n - 1) & hash]) != null) {\n        TreeNode<K,V> hd = null, tl = null;\n        do {\n        //该方法直接返回一个红黑树结点。\n            TreeNode<K,V> p = replacementTreeNode(e, null);\n            if (tl == null)\n                hd = p;\n            else {\n            //从链表头开始依次插入红黑树\n                p.prev = tl;\n                tl.next = p;\n            }\n            tl = p;\n        } while ((e = e.next) != null);\n        if ((tab[index] = hd) != null)\n            hd.treeify(tab);\n    }\n}\n    \n// For treeifyBin\nTreeNode<K,V> replacementTreeNode(Node<K,V> p, Node<K,V> next) {\n    return new TreeNode<>(p.hash, p.key, p.value, next);\n}\n````\n### 扩容\n````\nfinal Node<K,V>[] resize() {\n    Node<K,V>[] oldTab = table;\n    int oldCap = (oldTab == null) ? 0 : oldTab.length;\n    int oldThr = threshold;\n    int newCap, newThr = 0;\n    if (oldCap > 0) {\n        //如果原容量大于最大空间，则让阈值为最大值。因为不能再扩容了，最大容量就是整数最大值。\n        if (oldCap >= MAXIMUM_CAPACITY) {\n            threshold = Integer.MAX_VALUE;\n            return oldTab;\n        }\n        //两倍扩容，阈值也跟着变为两倍\n        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&\n                 oldCap >= DEFAULT_INITIAL_CAPACITY)\n            newThr = oldThr << 1; // double threshold\n    }\n    else if (oldThr > 0) // initial capacity was placed in threshold\n        newCap = oldThr;\n    else {               // zero initial threshold signifies using defaults\n        newCap = DEFAULT_INITIAL_CAPACITY;\n        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);\n    }\n    if (newThr == 0) {\n        float ft = (float)newCap * loadFactor;\n        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?\n                  (int)ft : Integer.MAX_VALUE);\n    }\n    threshold = newThr;\n    \n    @SuppressWarnings({\"rawtypes\",\"unchecked\"})\n    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];\n    \n    table = newTab;\n    \n    if (oldTab != null) {\n        for (int j = 0; j < oldCap; ++j) {\n            Node<K,V> e;\n            if ((e = oldTab[j]) != null) {\n                oldTab[j] = null;\n                if (e.next == null)\n                    //当后面没有节点时，直接插入即可 //每个元素重新计算索引位置，此处的hash值并没有变，只是改变索引值\n                    newTab[e.hash & (newCap - 1)] = e;\n                else if (e instanceof TreeNode)\n                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);\n                else { // preserve order\n                //否则，就从头到尾依次将节点进行索引然后插入新数组，这样插入后的链表顺序会和原来的顺序相反。\n                    Node<K,V> loHead = null, loTail = null;\n                    Node<K,V> hiHead = null, hiTail = null;\n                    Node<K,V> next;\n                    do {\n                        next = e.next;\n                        if ((e.hash & oldCap) == 0) {\n                            if (loTail == null)\n                                loHead = e;\n                            else\n                                loTail.next = e;\n                            loTail = e;\n                        }\n                        else {\n                            if (hiTail == null)\n                                hiHead = e;\n                            else\n                                hiTail.next = e;\n                            hiTail = e;\n                        }\n                    } while ((e = next) != null);\n                    if (loTail != null) {\n                        loTail.next = null;\n                        newTab[j] = loHead;\n                    }\n                    if (hiTail != null) {\n                        hiTail.next = null;\n                        newTab[j + oldCap] = hiHead;\n                    }\n                }\n            }\n        }\n    }\n    return newTab;\n}\n````\n### 读取实现：get(key)\n````\n相对于HashMap的存而言，取就显得比较简单了。通过key的hash值找到在table数组中的索引处的Entry，然后返回该key对应的value即可。\n\npublic V get(Object key) {\n    // 若为null，调用getForNullKey方法返回相对应的value\n    if (key == null)\n        return getForNullKey();\n    // 根据该 key 的 hashCode 值计算它的 hash 码  \n    int hash = hash(key.hashCode());\n    // 取出 table 数组中指定索引处的值\n    for (Entry<K, V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) {\n        Object k;\n        //若搜索的key与查找的key相同，则返回相对应的value\n        if (e.hash == hash && ((k = e.key) == key || key.equals(k)))\n            return e.value;\n    }\n    return null;\n}\n````\n> 在这里能够根据key快速的取到value除了和HashMap的数据结构密不可分外，还和Entry有莫大的关系，在前面就提到过，HashMap在存储过程中并没有将key，value分开来存储，而是当做一个整体key-value来处理的，这个整体就是Entry对象。\n>\n> 同时value也只相当于key的附属而已。在存储的过程中，系统根据key的hashcode来决定Entry在table数组中的存储位置，在取的过程中同样根据key的hashcode取出相对应的Entry对象。\n>\n> 在java中与有两个类都提供了一个多种用途的hashTable机制，他们都可以将可以key和value结合起来构成键值对通过put(key,value)方法保存起来，然后通过get(key)方法获取相对应的value值。\n\n## HashTable\n\n一个是前面提到的HashMap，还有一个就是马上要讲解的HashTable。对于HashTable而言，它在很大程度上和HashMap的实现差不多，如果我们对HashMap比较了解的话，对HashTable的认知会提高很大的帮助。他们两者之间只存在几点的不同，这个后面会阐述。\n\n### 定义\n\nHashTable在Java中的定义如下：\n\npublic class Hashtable<K,V>\nextends Dictionary<K,V>\nimplements Map<K,V>, Cloneable, java.io.Serializable\n从中可以看出HashTable继承Dictionary类，实现Map接口。其中Dictionary类是任何可将键映射到相应值的类（如 Hashtable）的抽象父类。每个键和每个值都是一个对象。在任何一个 Dictionary 对象中，每个键至多与一个值相关联。Map是\"key-value键值对\"接口。\n\nHashTable采用\"拉链法\"实现哈希表，它定义了几个重要的参数：table、count、threshold、loadFactor、modCount。\n\ntable：为一个Entry[]数组类型，Entry代表了“拉链”的节点，每一个Entry代表了一个键值对，哈希表的\"key-value键值对\"都是存储在Entry数组中的。\n\ncount：HashTable的大小，注意这个大小并不是HashTable的容器大小，而是他所包含Entry键值对的数量。\n\nthreshold：Hashtable的阈值，用于判断是否需要调整Hashtable的容量。threshold的值=\"容量*加载因子\"。\n\nloadFactor：加载因子。\n\nmodCount：用来实现“fail-fast”机制的（也就是快速失败）。所谓快速失败就是在并发集合中，其进行迭代操作时，若有其他线程对其进行结构性的修改，这时迭代器会立马感知到，并且立即抛出ConcurrentModificationException异常，而不是等到迭代完成之后才告诉你（你已经出错了）。\n\n### 构造方法\n````\n//在HashTabel中存在5个构造函数。通过这5个构造函数我们构建出一个我想要的HashTable。\npublic Hashtable() {\n        this(11, 0.75f);\n    }\n      默认构造函数，容量为11，加载因子为0.75。\n\npublic Hashtable(int initialCapacity) {\n        this(initialCapacity, 0.75f);\n}\n\n//用指定初始容量和默认的加载因子 (0.75) 构造一个新的空哈希表。\npublic Hashtable(int initialCapacity, float loadFactor) {\n    //验证初始容量\n    if (initialCapacity < 0)\n        throw new IllegalArgumentException(\"Illegal Capacity: \"+\n                                           initialCapacity);\n    //验证加载因子\n    if (loadFactor <= 0 || Float.isNaN(loadFactor))\n        throw new IllegalArgumentException(\"Illegal Load: \"+loadFactor);\n\n    if (initialCapacity==0)\n        initialCapacity = 1;\n\n    this.loadFactor = loadFactor;\n\n    //初始化table，获得大小为initialCapacity的table数组\n    table = new Entry[initialCapacity];\n    //计算阀值\n    threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);\n    //初始化HashSeed值\n    initHashSeedAsNeeded(initialCapacity);\n}\n\n````\n\n>  用指定初始容量和指定加载因子构造一个新的空哈希表。其中initHashSeedAsNeeded方法用于初始化hashSeed参数，其中hashSeed用于计算key的hash值，它与key的hashCode进行按位异或运算。这个hashSeed是一个与实例相关的随机值，主要用于解决hash冲突。\n\n````\n    private int hash(Object k) {\n            return hashSeed ^ k.hashCode();\n        }\n````\n\n  构造一个与给定的 Map 具有相同映射关系的新哈希表。\n\n````\n    public Hashtable(Map<? extends K, ? extends V> t) {\n            //设置table容器大小，其值==t.size * 2 + 1\n            this(Math.max(2*t.size(), 11), 0.75f);\n            putAll(t);\n        }\n````\n\n### 主要方法\n\nHashTable的API对外提供了许多方法，这些方法能够很好帮助我们操作HashTable，但是这里我只介绍两个最根本的方法：put、get。\n````\n//首先我们先看put方法：将指定 key 映射到此哈希表中的指定 value。注意这里键key和值value都不可为空。\npublic synchronized V put(K key, V value) {\n    // 确保value不为null\n    if (value == null) {\n        throw new NullPointerException();\n    }\n\n    /*\n     * 确保key在table[]是不重复的\n     * 处理过程：\n     * 1、计算key的hash值，确认在table[]中的索引位置\n     * 2、迭代index索引位置，如果该位置处的链表中存在一个一样的key，则替换其value，返回旧值\n     */\n    Entry tab[] = table;\n    int hash = hash(key);    //计算key的hash值\n    int index = (hash & 0x7FFFFFFF) % tab.length;     //确认该key的索引位置\n    //迭代，寻找该key，替换\n    for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {\n        if ((e.hash == hash) && e.key.equals(key)) {\n            V old = e.value;\n            e.value = value;\n            return old;\n        }\n    }\n\n    modCount++;\n    if (count >= threshold) {  //如果容器中的元素数量已经达到阀值，则进行扩容操作\n        rehash();\n        tab = table;\n        hash = hash(key);\n        index = (hash & 0x7FFFFFFF) % tab.length;\n    }\n\n    // 在索引位置处插入一个新的节点\n    Entry<K,V> e = tab[index];\n    tab[index] = new Entry<>(hash, key, value, e);\n    //容器中元素+1\n    count++;\n    return null;\n}\n````\n\n> put方法的整个处理流程是：计算key的hash值，根据hash值获得key在table数组中的索引位置，然后迭代该key处的Entry链表（我们暂且理解为链表），若该链表中存在一个这个的key对象，那么就直接替换其value值即可，否则在将改key-value节点插入该index索引位置处\n\n 在HashTabled的put方法中有两个地方需要注意：\n\n````\n    //1、HashTable的扩容操作，在put方法中，如果需要向table[]中添加Entry元素，会首先进行容量校验，如果容量已经达到了阀值，HashTable就会进行扩容处理rehash()，如下:\n    protected void rehash() {\n            int oldCapacity = table.length;\n            //元素\n            Entry<K,V>[] oldMap = table;\n    \n            //新容量=旧容量 * 2 + 1\n            int newCapacity = (oldCapacity << 1) + 1;\n            if (newCapacity - MAX_ARRAY_SIZE > 0) {\n                if (oldCapacity == MAX_ARRAY_SIZE)\n                    return;\n                newCapacity = MAX_ARRAY_SIZE;\n            }\n    \n            //新建一个size = newCapacity 的HashTable\n            Entry<K,V>[] newMap = new Entry[];\n    \n            modCount++;\n            //重新计算阀值\n            threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);\n            //重新计算hashSeed\n            boolean rehash = initHashSeedAsNeeded(newCapacity);\n    \n            table = newMap;\n            //将原来的元素拷贝到新的HashTable中\n            for (int i = oldCapacity ; i-- > 0 ;) {\n                for (Entry<K,V> old = oldMap[i] ; old != null ; ) {\n                    Entry<K,V> e = old;\n                    old = old.next;\n    \n                    if (rehash) {\n                        e.hash = hash(e.key);\n                    }\n                    int index = (e.hash & 0x7FFFFFFF) % newCapacity;\n                    e.next = newMap[index];\n                    newMap[index] = e;\n                }\n            }\n        }\n````\n 在这个rehash()方法中我们可以看到容量扩大两倍+1，同时需要将原来HashTable中的元素一一复制到新的HashTable中，这个过程是比较消耗时间的，同时还需要重新计算hashSeed的，毕竟容量已经变了。\n\n 这里对阀值啰嗦一下：比如初始值11、加载因子默认0.75，那么这个时候阀值threshold=8，当容器中的元素达到8时，HashTable进行一次扩容操作，容量 = 8 * 2 + 1 =17，而阀值threshold=17*0.75 = 13，当容器元素再一次达到阀值时，HashTable还会进行扩容操作，依次类推。\n\n下面是计算key的hash值，这里hashSeed发挥了作用。\n````\nprivate int hash(Object k) {\n        return hashSeed ^ k.hashCode();\n    }\n````\n 相对于put方法，get方法就会比较简单，处理过程就是计算key的hash值，判断在table数组中的索引位置，然后迭代链表，匹配直到找到相对应key的value,若没有找到返回null。\n````    \npublic synchronized V get(Object key) {\n        Entry tab[] = table;\n        int hash = hash(key);\n        int index = (hash & 0x7FFFFFFF) % tab.length;\n        for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {\n            if ((e.hash == hash) && e.key.equals(key)) {\n                return e.value;\n            }\n        }\n        return null;\n    }\n````\n## HashTable与HashMap的异同点\n\n  HashTable和HashMap存在很多的相同点，但是他们还是有几个比较重要的不同点。\n\n\n>\n> 第一：我们从他们的定义就可以看出他们的不同，HashTable基于Dictionary类，而HashMap是基于AbstractMap。Dictionary是什么？它是任何可将键映射到相应值的类的抽象父类，而AbstractMap是基于Map接口的骨干实现，它以最大限度地减少实现此接口所需的工作。\n>\n> \n>\n> 第二：HashMap可以允许存在一个为null的key和任意个为null的value，但是HashTable中的key和value都不允许为null。如下：\n>\n> \n>\n> 当HashMap遇到为null的key时，它会调用putForNullKey方法来进行处理。对于value没有进行任何处理，只要是对象都可以。\n\n    if (key == null)\n                 return putForNullKey(value);\n           而当HashTable遇到null时，他会直接抛出NullPointerException异常信息。\n\n    if (value == null) {\n        throw new NullPointerException();\n    }\n\n    第三：Hashtable的方法是同步的，而HashMap的方法不是。所以有人一般都建议如果是涉及到多线程同步时采用HashTable，没有涉及就采用HashMap，但是在Collections类中存在一个静态方法：synchronizedMap()，该方法创建了一个线程安全的Map对象，并把它作为一个封装的对象来返回，所以通过Collections类的synchronizedMap方法是可以我们你同步访问潜在的HashMap。这样君该如何选择呢？？？\n\n\n## 面试题：HashMap和HashTable的区别\n\nHashMap线程不安全，HashTable是线程安全的。HashMap内部实现没有任何线程同步相关的代码，所以相对而言性能要好一点。如果在多线程中使用HashMap需要自己管理线程同步。HashTable大部分对外接口都使用synchronized包裹，所以是线程安全的，但是性能会相对差一些。\n\n二者的基类不一样。HashMap派生于AbstractMap，HashTable派生于Dictionary。它们都实现Map, Cloneable, Serializable这些接口。AbstractMap中提供的基础方法更多，并且实现了多个通用的方法，而在Dictionary中只有少量的接口，并且都是abstract类型。\n\nkey和value的取值范围不同。HashMap的key和value都可以为null，但是HashTablekey和value都不能为null。对于HashMap如果get返回null，并不能表明HashMap不存在这个key，如果需要判断HashMap中是否包含某个key，就需要使用containsKey这个方法来判断。\n\n算法不一样。HashMap的initialCapacity为16，而HashTable的initialCapacity为11。HashMap中初始容量必须是2的幂,如果初始化传入的initialCapacity不是2的幂，将会自动调整为大于出入的initialCapacity最小的2的幂。HashMap使用自己的计算hash的方法(会依赖key的hashCode方法)，HashTable则使用key的hashCode方法得到。\n\n## 参考文章\nhttp://cmsblogs.com/?p=176\n\nhttp://mini.eastday.com/mobile/180310183019559.html#\n\nhttps://blog.csdn.net/lihua5419/article/details/87691965\n\nhttps://www.cnblogs.com/aeolian/p/8468632.html\n\n\n\n\n\n​                     \n"
  },
  {
    "path": "docs/Java/collection/Java集合详解：HashSet，TreeSet与LinkedHashSet.md",
    "content": "# 目录\n  * [HashSet](#hashset)\n    * [定义](#定义)\n    * [方法](#方法)\n  * [TreeSet](#treeset)\n    * [TreeSet定义](#treeset定义)\n    * [TreeSet主要方法](#treeset主要方法)\n  * [最后](#最后)\n  * [LinkedHashSet](#linkedhashset)\n    * [LinkedHashSet内部是如何工作的](#linkedhashset内部是如何工作的)\n    * [LinkedHashSet是如何维护插入顺序的](#linkedhashset是如何维护插入顺序的)\n  * [参考文章](#参考文章)\n\n\n本文参考 http://cmsblogs.com/?p=599\n\n《Java集合详解系列》是我在完成夯实Java基础篇的系列博客后准备开始整理的新系列文章。\n为了更好地诠释知识点，形成体系文章，本系列文章整理了很多优质的博客内容，如有侵权请联系我，一定删除。\n\n这些文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star、fork哈\n\n本系列文章将整理于我的个人博客：\n\n> www.how2playlife.com\n\n今天我们来探索一下HashSet，TreeSet与LinkedHashSet的基本原理与源码实现，由于这三个set都是基于之前文章的三个map进行实现的，所以推荐大家先看一下前面有关map的文章，结合使用味道更佳。\n\n## HashSet\n\n### 定义\n````\npublic class HashSet<E>\n    extends AbstractSet<E>\n    implements Set<E>, Cloneable, java.io.Serializable\n````\nHashSet继承AbstractSet类，实现Set、Cloneable、Serializable接口。其中AbstractSet提供 Set 接口的骨干实现，从而最大限度地减少了实现此接口所需的工作。\nSet接口是一种不包括重复元素的Collection，它维持它自己的内部排序，所以随机访问没有任何意义。\n\n本文基于1.8jdk进行源码分析。\n\n 基本属性\n\n基于HashMap实现，底层使用HashMap保存所有元素\n````\nprivate transient HashMap<E,Object> map;\n\n//定义一个Object对象作为HashMap的value\nprivate static final Object PRESENT = new Object();\n````\n构造函数\n\n````\n/**\n * 默认构造函数\n * 初始化一个空的HashMap，并使用默认初始容量为16和加载因子0.75。\n */\npublic HashSet() {\n    map = new HashMap<>();\n}\n\n/**\n * 构造一个包含指定 collection 中的元素的新 set。\n */\npublic HashSet(Collection<? extends E> c) {\n    map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));\n    addAll(c);\n}\n\n/**\n * 构造一个新的空 set，其底层 HashMap 实例具有指定的初始容量和指定的加载因子\n */\npublic HashSet(int initialCapacity, float loadFactor) {\n    map = new HashMap<>(initialCapacity, loadFactor);\n}\n\n/**\n * 构造一个新的空 set，其底层 HashMap 实例具有指定的初始容量和默认的加载因子（0.75）。\n */\npublic HashSet(int initialCapacity) {\n   map = new HashMap<>(initialCapacity);\n}\n\n/**\n * 在API中我没有看到这个构造函数，今天看源码才发现（原来访问权限为包权限，不对外公开的）\n * 以指定的initialCapacity和loadFactor构造一个新的空链接哈希集合。\n * dummy 为标识 该构造函数主要作用是对LinkedHashSet起到一个支持作用\n */\nHashSet(int initialCapacity, float loadFactor, boolean dummy) {\n   map = new LinkedHashMap<>(initialCapacity, loadFactor);\n}\n````\n从构造函数中可以看出HashSet所有的构造都是构造出一个新的HashMap，其中最后一个构造函数，为包访问权限是不对外公开，仅仅只在使用LinkedHashSet时才会发生作用。\n\n### 方法\n\n既然HashSet是基于HashMap，那么对于HashSet而言，其方法的实现过程是非常简单的。\n````\npublic Iterator<E> iterator() {\n        return map.keySet().iterator();\n}\n````\n\n> iterator()方法返回对此 set 中元素进行迭代的迭代器。返回元素的顺序并不是特定的。\n>\n> 底层调用HashMap的keySet返回所有的key，这点反应了HashSet中的所有元素都是保存在HashMap的key中，value则是使用的PRESENT对象，该对象为static final。\n````\n    public int size() {\n             return map.size();\n         }\n        size()返回此 set 中的元素的数量（set 的容量）。底层调用HashMap的size方法，返回HashMap容器的大小。\n\n    public boolean isEmpty() {\n            return map.isEmpty();\n        }\n        isEmpty()，判断HashSet()集合是否为空，为空返回 true，否则返回false。\n    \n    public boolean contains(Object o) {\n            return map.containsKey(o);\n    }\n    \n    public boolean containsKey(Object key) {\n        return getNode(hash(key), key) != null;\n    }\n    \n    //最终调用该方法进行节点查找\n    final Node<K,V> getNode(int hash, Object key) {\n        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;\n        //先检查桶的头结点是否存在\n        if ((tab = table) != null && (n = tab.length) > 0 &&\n            (first = tab[(n - 1) & hash]) != null) {\n            if (first.hash == hash && // always check first node\n                ((k = first.key) == key || (key != null && key.equals(k))))\n                return first;\n                //不是头结点，则遍历链表，如果是树节点则使用树节点的方法遍历，直到找到，或者为null\n            if ((e = first.next) != null) {\n                if (first instanceof TreeNode)\n                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);\n                do {\n                    if (e.hash == hash &&\n                        ((k = e.key) == key || (key != null && key.equals(k))))\n                        return e;\n                } while ((e = e.next) != null);\n            }\n        }\n        return null;\n    }\n````\n\ncontains()，判断某个元素是否存在于HashSet()中，存在返回true，否则返回false。更加确切的讲应该是要满足这种关系才能返回true：(o==null ? e==null : o.equals(e))。底层调用containsKey判断HashMap的key值是否为空。\n\n````\npublic boolean add(E e) {\n        return map.put(e, PRESENT)==null;\n}\n\npublic V put(K key, V value) {\n    return putVal(hash(key), key, value, false, true);\n}\n\nmap的put方法：\nfinal V putVal(int hash, K key, V value, boolean onlyIfAbsent,\n               boolean evict) {\n    Node<K,V>[] tab; Node<K,V> p; int n, i;\n    \n    //确认初始化\n    if ((tab = table) == null || (n = tab.length) == 0)\n        n = (tab = resize()).length;\n        \n    //如果桶为空，直接插入新元素，也就是entry\n    if ((p = tab[i = (n - 1) & hash]) == null)\n        tab[i] = newNode(hash, key, value, null);\n    else {\n        Node<K,V> e; K k;\n        //如果冲突，分为三种情况\n        //key相等时让旧entry等于新entry即可\n        if (p.hash == hash &&\n            ((k = p.key) == key || (key != null && key.equals(k))))\n            e = p;\n        //红黑树情况\n        else if (p instanceof TreeNode)\n            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);\n        else {\n            //如果key不相等，则连成链表\n            for (int binCount = 0; ; ++binCount) {\n                if ((e = p.next) == null) {\n                    p.next = newNode(hash, key, value, null);\n                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st\n                        treeifyBin(tab, hash);\n                    break;\n                }\n                if (e.hash == hash &&\n                    ((k = e.key) == key || (key != null && key.equals(k))))\n                    break;\n                p = e;\n            }\n        }\n        if (e != null) { // existing mapping for key\n            V oldValue = e.value;\n            if (!onlyIfAbsent || oldValue == null)\n                e.value = value;\n            afterNodeAccess(e);\n            return oldValue;\n        }\n    }\n    ++modCount;\n    if (++size > threshold)\n        resize();\n    afterNodeInsertion(evict);\n    return null;\n}\n````\n\n> 这里注意一点，hashset只是不允许重复的元素加入，而不是不允许元素连成链表，因为只要key的equals方法判断为true时它们是相等的，此时会发生value的替换，因为所有entry的value一样，所以和没有插入时一样的。\n>\n> 而当两个hashcode相同但key不相等的entry插入时，仍然会连成一个链表，长度超过8时依然会和hashmap一样扩展成红黑树，看完源码之后笔者才明白自己之前理解错了。所以看源码还是蛮有好处的。hashset基本上就是使用hashmap的方法再次实现了一遍而已，只不过value全都是同一个object，让你以为相同元素没有插入，事实上只是value替换成和原来相同的值而已。\n\n当add方法发生冲突时，如果key相同，则替换value，如果key不同，则连成链表。       \n\nadd()如果此 set 中尚未包含指定元素，则添加指定元素。如果此Set没有包含满足(e==null ? e2==null : e.equals(e2)) 的e2时，则将e2添加到Set中，否则不添加且返回false。\n\n由于底层使用HashMap的put方法将key = e，value=PRESENT构建成key-value键值对，当此e存在于HashMap的key中，则value将会覆盖原有value，但是key保持不变，所以如果将一个已经存在的e元素添加中HashSet中，新添加的元素是不会保存到HashMap中，所以这就满足了HashSet中元素不会重复的特性。\n````\npublic boolean remove(Object o) {\n    return map.remove(o)==PRESENT;\n}\n````\nremove如果指定元素存在于此 set 中，则将其移除。底层使用HashMap的remove方法删除指定的Entry。\n````\npublic void clear() {\n    map.clear();\n}\n````\nclear从此 set 中移除所有元素。底层调用HashMap的clear方法清除所有的Entry。\n````\npublic Object clone() {\n    try {\n        HashSet<E> newSet = (HashSet<E>) super.clone();\n        newSet.map = (HashMap<E, Object>) map.clone();\n        return newSet;\n    } catch (CloneNotSupportedException e) {\n        throw new InternalError();\n    }\n}\n````\nclone返回此 HashSet 实例的浅表副本：并没有复制这些元素本身。\n\n后记：\n\n> 由于HashSet底层使用了HashMap实现，使其的实现过程变得非常简单，如果你对HashMap比较了解，那么HashSet简直是小菜一碟。有两个方法对HashMap和HashSet而言是非常重要的，下篇将详细讲解hashcode和equals。\n\n\n## TreeSet\n\n与HashSet是基于HashMap实现一样，TreeSet同样是基于TreeMap实现的。在《Java提高篇（二七）-----TreeMap》中LZ详细讲解了TreeMap实现机制，如果客官详情看了这篇博文或者多TreeMap有比较详细的了解，那么TreeSet的实现对您是喝口水那么简单。\n\n### TreeSet定义\n\n我们知道TreeMap是一个有序的二叉树，那么同理TreeSet同样也是一个有序的，它的作用是提供有序的Set集合。通过源码我们知道TreeSet基础AbstractSet，实现NavigableSet、Cloneable、Serializable接口。\n\n其中AbstractSet提供 Set 接口的骨干实现，从而最大限度地减少了实现此接口所需的工作。\n\nNavigableSet是扩展的 SortedSet，具有了为给定搜索目标报告最接近匹配项的导航方法，这就意味着它支持一系列的导航方法。比如查找与指定目标最匹配项。Cloneable支持克隆，Serializable支持序列化。\n````\npublic class TreeSet<E> extends AbstractSet<E>\n    implements NavigableSet<E>, Cloneable, java.io.Serializable\n````\n同时在TreeSet中定义了如下几个变量。\n````\nprivate transient NavigableMap<E,Object> m;\n    \n//PRESENT会被当做Map的value与key构建成键值对\n private static final Object PRESENT = new Object();\n````\n其构造方法：\n\n````\n//默认构造方法，根据其元素的自然顺序进行排序\n\npublic TreeSet() {\n    this(new TreeMap<E,Object>());\n}\n\n//构造一个包含指定 collection 元素的新 TreeSet，它按照其元素的自然顺序进行排序。\npublic TreeSet(Comparator<? super E> comparator) {\n        this(new TreeMap<>(comparator));\n}\n\n//构造一个新的空 TreeSet，它根据指定比较器进行排序。\npublic TreeSet(Collection<? extends E> c) {\n    this();\n    addAll(c);\n}\n\n//构造一个与指定有序 set 具有相同映射关系和相同排序的新 TreeSet。\npublic TreeSet(SortedSet<E> s) {\n    this(s.comparator());\n    addAll(s);\n}\n\nTreeSet(NavigableMap<E,Object> m) {\n    this.m = m;\n}\n````\n### TreeSet主要方法\n\n1、add：将指定的元素添加到此 set（如果该元素尚未存在于 set 中）。\n````    \npublic boolean add(E e) {\n        return m.put(e, PRESENT)==null;\n    }\n \npublic V put(K key, V value) {\n    Entry<K,V> t = root;\n    if (t == null) {\n    //空树时，判断节点是否为空\n        compare(key, key); // type (and possibly null) check\n\n        root = new Entry<>(key, value, null);\n        size = 1;\n        modCount++;\n        return null;\n    }\n    int cmp;\n    Entry<K,V> parent;\n    // split comparator and comparable paths\n    Comparator<? super K> cpr = comparator;\n    //非空树，根据传入比较器进行节点的插入位置查找\n    if (cpr != null) {\n        do {\n            parent = t;\n            //节点比根节点小，则找左子树，否则找右子树\n            cmp = cpr.compare(key, t.key);\n            if (cmp < 0)\n                t = t.left;\n            else if (cmp > 0)\n                t = t.right;\n                //如果key的比较返回值相等，直接更新值（一般compareto相等时equals方法也相等）\n            else\n                return t.setValue(value);\n        } while (t != null);\n    }\n    else {\n    //如果没有传入比较器，则按照自然排序\n        if (key == null)\n            throw new NullPointerException();\n        @SuppressWarnings(\"unchecked\")\n            Comparable<? super K> k = (Comparable<? super K>) key;\n        do {\n            parent = t;\n            cmp = k.compareTo(t.key);\n            if (cmp < 0)\n                t = t.left;\n            else if (cmp > 0)\n                t = t.right;\n            else\n                return t.setValue(value);\n        } while (t != null);\n    }\n    //查找的节点为空，直接插入，默认为红节点\n    Entry<K,V> e = new Entry<>(key, value, parent);\n    if (cmp < 0)\n        parent.left = e;\n    else\n        parent.right = e;\n        //插入后进行红黑树调整\n    fixAfterInsertion(e);\n    size++;\n    modCount++;\n    return null;\n}    \n````\n2、get：获取元素\n````\npublic V get(Object key) {\n    Entry<K,V> p = getEntry(key);\n    return (p==null ? null : p.value);\n}\n````\n该方法与put的流程类似，只不过是把插入换成了查找\n    \n3、ceiling：返回此 set 中大于等于给定元素的最小元素；如果不存在这样的元素，则返回 null。\n````\npublic E ceiling(E e) {\n    return m.ceilingKey(e);\n}\n````\n4、clear：移除此 set 中的所有元素。\n````\npublic void clear() {\n    m.clear();\n}\n````\n5、clone：返回 TreeSet 实例的浅表副本。属于浅拷贝。\n````\npublic Object clone() {\n    TreeSet<E> clone = null;\n    try {\n        clone = (TreeSet<E>) super.clone();\n    } catch (CloneNotSupportedException e) {\n        throw new InternalError();\n    }\n\n    clone.m = new TreeMap<>(m);\n    return clone;\n}\n````\n6、comparator：返回对此 set 中的元素进行排序的比较器；如果此 set 使用其元素的自然顺序，则返回 null。\n````\npublic Comparator<? super E> comparator() {\n        return m.comparator();\n    }\n````\n7、contains：如果此 set 包含指定的元素，则返回 true。\n````\npublic boolean contains(Object o) {\n        return m.containsKey(o);\n    }\n````\n8、descendingIterator：返回在此 set 元素上按降序进行迭代的迭代器。\n````\npublic Iterator<E> descendingIterator() {\n    return m.descendingKeySet().iterator();\n}\n````\n9、descendingSet：返回此 set 中所包含元素的逆序视图。\n````\npublic NavigableSet<E> descendingSet() {\n    return new TreeSet<>(m.descendingMap());\n}\n````\n10、first：返回此 set 中当前第一个（最低）元素。\n````\npublic E first() {\n    return m.firstKey();\n}\n````\n11、floor：返回此 set 中小于等于给定元素的最大元素；如果不存在这样的元素，则返回 null。\n````    \npublic E floor(E e) {\n    return m.floorKey(e);\n}\n````\n12、headSet：返回此 set 的部分视图，其元素严格小于 toElement。\n````\npublic SortedSet<E> headSet(E toElement) {\n    return headSet(toElement, false);\n}\n````\n13、higher：返回此 set 中严格大于给定元素的最小元素；如果不存在这样的元素，则返回 null。\n````\npublic E higher(E e) {\n    return m.higherKey(e);\n}\n````\n14、isEmpty：如果此 set 不包含任何元素，则返回 true。\n````\npublic boolean isEmpty() {\n    return m.isEmpty();\n}\n````\n15、iterator：返回在此 set 中的元素上按升序进行迭代的迭代器。\n````\npublic Iterator<E> iterator() {\n    return m.navigableKeySet().iterator();\n}\n````\n16、last：返回此 set 中当前最后一个（最高）元素。\n````\npublic E last() {\n    return m.lastKey();\n}\n````\n17、lower：返回此 set 中严格小于给定元素的最大元素；如果不存在这样的元素，则返回 null。\n````    \npublic E lower(E e) {\n    return m.lowerKey(e);\n}\n````\n18、pollFirst：获取并移除第一个（最低）元素；如果此 set 为空，则返回 null。\n````\npublic E pollFirst() {\n    Map.Entry<E,?> e = m.pollFirstEntry();\n    return (e == null) ? null : e.getKey();\n}\n````\n19、pollLast：获取并移除最后一个（最高）元素；如果此 set 为空，则返回 null。\n````\npublic E pollLast() {\n    Map.Entry<E,?> e = m.pollLastEntry();\n    return (e == null) ? null : e.getKey();\n}\n````\n20、remove：将指定的元素从 set 中移除（如果该元素存在于此 set 中）。\n````\npublic boolean remove(Object o) {\n    return m.remove(o)==PRESENT;\n}\n````\n该方法与put类似，只不过把插入换成了删除，并且要进行删除后调整\n\n21、size：返回 set 中的元素数（set 的容量）。\n````\npublic int size() {\n    return m.size();\n}\n````\n22、subSet：返回此 set 的部分视图\n````\n/**\n * 返回此 set 的部分视图，其元素范围从 fromElement 到 toElement。\n */\n public NavigableSet<E> subSet(E fromElement, boolean fromInclusive,\n     E toElement,   boolean toInclusive) {\n     return new TreeSet<>(m.subMap(fromElement, fromInclusive,\n          toElement,   toInclusive));\n }\n \n /**\n  * 返回此 set 的部分视图，其元素从 fromElement（包括）到 toElement（不包括）。\n  */\n public SortedSet<E> subSet(E fromElement, E toElement) {\n     return subSet(fromElement, true, toElement, false);\n }\n````\n23、tailSet：返回此 set 的部分视图\n````\n/**\n * 返回此 set 的部分视图，其元素大于（或等于，如果 inclusive 为 true）fromElement。\n */\npublic NavigableSet<E> tailSet(E fromElement, boolean inclusive) {\n    return new TreeSet<>(m.tailMap(fromElement, inclusive));\n}\n\n/**\n * 返回此 set 的部分视图，其元素大于等于 fromElement。\n */\npublic SortedSet<E> tailSet(E fromElement) {\n    return tailSet(fromElement, true);\n}\n````\n\n## 最后\n\n由于TreeSet是基于TreeMap实现的，所以如果我们对treeMap有了一定的了解，对TreeSet那是小菜一碟，我们从TreeSet中的源码可以看出，其实现过程非常简单，几乎所有的方法实现全部都是基于TreeMap的。\n\n## LinkedHashSet\n\n### LinkedHashSet内部是如何工作的\n\nLinkedHashSet是HashSet的一个“扩展版本”，HashSet并不管什么顺序，不同的是LinkedHashSet会维护“插入顺序”。HashSet内部使用HashMap对象来存储它的元素，而LinkedHashSet内部使用LinkedHashMap对象来存储和处理它的元素。这篇文章，我们将会看到LinkedHashSet内部是如何运作的及如何维护插入顺序的。\n\n我们首先着眼LinkedHashSet的构造函数。在LinkedHashSet类中一共有4个构造函数。这些构造函数都只是简单地调用父类构造函数（如HashSet类的构造函数）。\n下面看看LinkedHashSet的构造函数是如何定义的。\n````\n//Constructor - 1\n \npublic LinkedHashSet(int initialCapacity, float loadFactor)\n{\n      super(initialCapacity, loadFactor, true);              //Calling super class constructor\n}\n \n//Constructor - 2\n \npublic LinkedHashSet(int initialCapacity)\n{\n        super(initialCapacity, .75f, true);             //Calling super class constructor\n}\n \n//Constructor - 3\n \npublic LinkedHashSet()\n{\n        super(16, .75f, true);                //Calling super class constructor\n}\n \n//Constructor - 4\n \npublic LinkedHashSet(Collection<? extends E> c)\n{\n        super(Math.max(2*c.size(), 11), .75f, true);          //Calling super class constructor\n        addAll(c);\n}\n````    \n在上面的代码片段中，你可能注意到4个构造函数调用的是同一个父类的构造函数。这个构造函数（父类的，译者注）是一个包内私有构造函数（见下面的代码，HashSet的构造函数没有使用public公开，译者注），它只能被LinkedHashSet使用。\n\n这个构造函数需要初始容量，负载因子和一个boolean类型的哑值（没有什么用处的参数，作为标记，译者注）等参数。这个哑参数只是用来区别这个构造函数与HashSet的其他拥有初始容量和负载因子参数的构造函数，下面是这个构造函数的定义，\n````\nHashSet(int initialCapacity, float loadFactor, boolean dummy)\n{\n    map = new LinkedHashMap<>(initialCapacity, loadFactor);\n}\n````\n显然，这个构造函数内部初始化了一个LinkedHashMap对象，这个对象恰好被LinkedHashSet用来存储它的元素。\n\nLinkedHashSet并没有自己的方法，所有的方法都继承自它的父类HashSet，因此，对LinkedHashSet的所有操作方式就好像对HashSet操作一样。\n\n唯一的不同是内部使用不同的对象去存储元素。在HashSet中，插入的元素是被当做HashMap的键来保存的，而在LinkedHashSet中被看作是LinkedHashMap的键。\n\n这些键对应的值都是常量PRESENT（PRESENT是HashSet的静态成员变量，译者注）。\n\n### LinkedHashSet是如何维护插入顺序的\n> LinkedHashSet使用LinkedHashMap对象来存储它的元素，插入到LinkedHashSet中的元素实际上是被当作LinkedHashMap的键保存起来的。\n>\n> LinkedHashMap的每一个键值对都是通过内部的静态类Entry<K, V>实例化的。这个 Entry<K, V>类继承了HashMap.Entry类。\n>\n> 这个静态类增加了两个成员变量，before和after来维护LinkedHasMap元素的插入顺序。这两个成员变量分别指向前一个和后一个元素，这让LinkedHashMap也有类似双向链表的表现。\n````\nprivate static class Entry<K,V> extends HashMap.Entry<K,V>\n{\n    // These fields comprise the doubly linked list used for iteration.\n    Entry<K,V> before, after;\n\n    Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {\n        super(hash, key, value, next);\n    }\n}\n````\n从上面代码看到的LinkedHashMap内部类的前面两个成员变量——before和after负责维护LinkedHashSet的插入顺序。LinkedHashMap定义的成员变量header保存的是\n这个双向链表的头节点。header的定义就像下面这样，\n\n接下来看一个例子就知道LinkedHashSet内部是如何工作的了。\n````\npublic class LinkedHashSetExample\n{\n    public static void main(String[] args)\n    {\n        //Creating LinkedHashSet\n \n        LinkedHashSet<String> set = new LinkedHashSet<String>();\n \n        //Adding elements to LinkedHashSet\n \n        set.add(\"BLUE\");\n \n        set.add(\"RED\");\n \n        set.add(\"GREEN\");    \n \n        set.add(\"BLACK\");\n    }\n}\n````\n\n如果你知道LinkedHashMap内部是如何工作的，就非常容易明白LinkedHashSet内部是如何工作的。看一遍LinkedHashSet和LinkedHashMap的源码，\n你就能够准确地理解在Java中LinkedHashSet内部是如何工作的。\n\n\n## 参考文章\nhttp://cmsblogs.com/?p=599\n\nhttps://www.cnblogs.com/one-apple-pie/p/11036309.html\n\nhttps://blog.csdn.net/learningcoding/article/details/79983248\n\n\n"
  },
  {
    "path": "docs/Java/collection/Java集合详解：Iterator，fail-fast机制与比较器.md",
    "content": "# 目录\n  * [Iterator](#iterator)\n    * [java.util.Iterator](#javautiliterator)\n    * [各个集合的Iterator的实现](#各个集合的iterator的实现)\n    * [fail-fast机制](#fail-fast机制)\n      * [fail-fast示例](#fail-fast示例)\n      * [fail-fast产生原因](#fail-fast产生原因)\n      * [fail-fast解决办法](#fail-fast解决办法)\n  * [Comparable 和 Comparator](#comparable-和-comparator)\n    * [Comparable](#comparable)\n      * [Comparator](#comparator)\n      * [Java8中使用lambda实现比较器](#java8中使用lambda实现比较器)\n    * [总结](#总结)\n    * [参考文章](#参考文章)\n\n\n本文参考 cmsblogs.com/p=1185\n\n《Java集合详解系列》是我在完成夯实Java基础篇的系列博客后准备开始整理的新系列文章。\n为了更好地诠释知识点，形成体系文章，本系列文章整理了很多优质的博客内容，如有侵权请联系我，一定删除。\n\n这些文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star、fork哈\n\n本系列文章将整理于我的个人博客：\n\n> www.how2playlife.com\n\n今天我们来探索一下LIterator，fail-fast机制与比较器的源码。\n\n## Iterator\n\n迭代对于我们搞Java的来说绝对不陌生。我们常常使用JDK提供的迭代接口进行Java集合的迭代。\n````\nIterator iterator = list.iterator();\nwhile(iterator.hasNext()){\n    String string = iterator.next();\n    do something\n}\n````\n迭代其实我们可以简单地理解为遍历，是一个标准化遍历各类容器里面的所有对象的方法类，它是一个很典型的设计模式。Iterator模式是用于遍历集合类的标准访问方法。\n\n它可以把访问逻辑从不同类型的集合类中抽象出来，从而避免向客户端暴露集合的内部结构。 在没有迭代器时我们都是这么进行处理的。如下：\n\n对于数组我们是使用下标来进行处理的\n````\nint[] arrays = new int[10];\n   for(int i = 0 ; i  arrays.length ; i++){\n       int a = arrays[i];\n       do something\n   }\n````\n对于ArrayList是这么处理的\n````\nListString list = new ArrayListString();\nfor(int i = 0 ; i  list.size() ;  i++){\n  String string = list.get(i);\n  do something\n}\n````\n对于这两种方式，我们总是都事先知道集合的内部结构，访问代码和集合本身是紧密耦合的，无法将访问逻辑从集合类和客户端代码中分离出来。同时每一种集合对应一种遍历方法，客户端代码无法复用。\n\n在实际应用中如何需要将上面将两个集合进行整合是相当麻烦的。所以为了解决以上问题，Iterator模式腾空出世，它总是用同一种逻辑来遍历集合。\n\n使得客户端自身不需要来维护集合的内部结构，所有的内部状态都由Iterator来维护。客户端从不直接和集合类打交道，它总是控制Iterator，向它发送向前，向后，取当前元素的命令，就可以间接遍历整个集合。\n\n上面只是对Iterator模式进行简单的说明，下面我们看看Java中Iterator接口，看他是如何来进行实现的。\n\n### java.util.Iterator\n\n在Java中Iterator为一个接口，它只提供了迭代了基本规则，在JDK中他是这样定义的：对 collection 进行迭代的迭代器。迭代器取代了 Java Collections Framework 中的 Enumeration。迭代器与枚举有两点不同：\n\n    1、迭代器允许调用者利用定义良好的语义在迭代期间从迭代器所指向的 collection 移除元素。\n    \n    2、方法名称得到了改进。\n\n其接口定义如下：\n````\npublic interface Iterator {\n　　boolean hasNext();\n　　Object next();\n　　void remove();\n}\n````\n其中：\n\n    Object next()：返回迭代器刚越过的元素的引用，返回值是Object，需要强制转换成自己需要的类型\n    \n    boolean hasNext()：判断容器内是否还有可供访问的元素\n    \n    void remove()：删除迭代器刚越过的元素\n\n对于我们而言，我们只一般只需使用next()、hasNext()两个方法即可完成迭代。如下：\n````\nfor(Iterator it = c.iterator(); it.hasNext(); ) {\n　　Object o = it.next();\n　　 do something\n}\n````\n==前面阐述了Iterator有一个很大的优点,就是我们不必知道集合的内部结果,集合的内部结构、状态由Iterator来维持，通过统一的方法hasNext()、next()来判断、获取下一个元素，至于具体的内部实现我们就不用关心了。==\n\n但是作为一个合格的程序员我们非常有必要来弄清楚Iterator的实现。下面就ArrayList的源码进行分析分析。\n\n### 各个集合的Iterator的实现\n\n下面就ArrayList的Iterator实现来分析，其实如果我们理解了ArrayList、Hashset、TreeSet的数据结构，内部实现，对于他们是如何实现Iterator也会胸有成竹的。因为ArrayList的内部实现采用数组，所以我们只需要记录相应位置的索引即可，其方法的实现比较简单。\n\nArrayList的Iterator实现\n\n在ArrayList内部首先是定义一个内部类Itr，该内部类实现Iterator接口，如下：\n````\nprivate class Itr implements IteratorE {\n    do something\n}\n而ArrayList的iterator()方法实现：\n\npublic IteratorE iterator() {\n        return new Itr();\n}\n````\n所以通过使用ArrayList.iterator()方法返回的是Itr()内部类，所以现在我们需要关心的就是Itr()内部类的实现：\n\n在Itr内部定义了三个int型的变量：cursor、lastRet、expectedModCount。其中cursor表示下一个元素的索引位置，lastRet表示上一个元素的索引位置\n\n    int cursor;             \n    int lastRet = -1;     \n    int expectedModCount = modCount;\n\n从cursor、lastRet定义可以看出，lastRet一直比cursor少一所以hasNext()实现方法异常简单，只需要判断cursor和lastRet是否相等即可。\n````\npublic boolean hasNext() {\n    return cursor != size;\n}\n````\n对于next()实现其实也是比较简单的，只要返回cursor索引位置处的元素即可，然后修改cursor、lastRet即可。\n````\npublic E next() {\n    checkForComodification();\n    int i = cursor;    记录索引位置\n    if (i = size)    如果获取元素大于集合元素个数，则抛出异常\n        throw new NoSuchElementException();\n    Object[] elementData = ArrayList.this.elementData;\n    if (i = elementData.length)\n        throw new ConcurrentModificationException();\n    cursor = i + 1;      cursor + 1\n    return (E) elementData[lastRet = i];  lastRet + 1 且返回cursor处元素\n}\n````\n checkForComodification()主要用来判断集合的修改次数是否合法，即用来判断遍历过程中集合是否被修改过。\n\n 。modCount用于记录ArrayList集合的修改次数，初始化为0，，每当集合被修改一次（结构上面的修改，内部update不算），如add、remove等方法，modCount + 1，所以如果modCount不变，则表示集合内容没有被修改。\n\n 该机制主要是用于实现ArrayList集合的快速失败机制，在Java的集合中，较大一部分集合是存在快速失败机制的，这里就不多说，后面会讲到。\n\n 所以要保证在遍历过程中不出错误，我们就应该保证在遍历过程中不会对集合产生结构上的修改（当然remove方法除外），出现了异常错误，我们就应该认真检查程序是否出错而不是catch后不做处理。\n````\nfinal void checkForComodification() {\n            if (modCount != expectedModCount)\n                throw new ConcurrentModificationException();\n        }\n        \n//对于remove()方法的是实现，它是调用ArrayList本身的remove()方法删除lastRet位置元素，然后修改modCount即可。\n\npublic void remove() {\n    if (lastRet  0)\n        throw new IllegalStateException();\n    checkForComodification();\n\n    try {\n        ArrayList.this.remove(lastRet);\n        cursor = lastRet;\n        lastRet = -1;\n        expectedModCount = modCount;\n    } catch (IndexOutOfBoundsException ex) {\n        throw new ConcurrentModificationException();\n    }\n}\n````\n这里就对ArrayList的Iterator实现讲解到这里，对于Hashset、TreeSet等集合的Iterator实现，各位如果感兴趣可以继续研究，个人认为在研究这些集合的源码之前，有必要对该集合的数据结构有清晰的认识，这样会达到事半功倍的效果！！！！\n\n### fail-fast机制\n这部分参考http://cmsblogs.com/p=1220\n\n在JDK的Collection中我们时常会看到类似于这样的话：\n\n例如，ArrayList\n    \n 注意，迭代器的快速失败行为无法得到保证，因为一般来说，不可能对是否出现不同步并发修改做出任何硬性保证。快速失败迭代器会尽最大努力抛出ConcurrentModificationException。\n 因此，为提高这类迭代器的正确性而编写一个依赖于此异常的程序是错误的做法：迭代器的快速失败行为应该仅用于检测 bug。\n\nHashMap中：\n\n 注意，迭代器的快速失败行为不能得到保证，一般来说，存在非同步的并发修改时，不可能作出任何坚决的保证。快速失败迭代器尽最大努力抛出 ConcurrentModificationException。因此，编写依赖于此异常的程序的做法是错误的，正确做法是：迭代器的快速失败行为应该仅用于检测程序错误。\n\n在这两段话中反复地提到”快速失败”。那么何为”快速失败”机制呢？\n\n “快速失败”也就是fail-fast，它是Java集合的一种错误检测机制。当多个线程对集合进行结构上的改变的操作时，有可能会产生fail-fast机制。\n\n 记住是有可能，而不是一定。例如：假设存在两个线程（线程1、线程2），线程1通过Iterator在遍历集合A中的元素，在某个时候线程2修改了集合A的结构（是结构上面的修改，而不是简单的修改集合元素的内容），那么这个时候程序就会抛出 ConcurrentModificationException异常，从而产生fail-fast机制。\n\n#### fail-fast示例\n````\npublic class FailFastTest {\n    private static ListInteger list = new ArrayList();    \n \n    private static class threadOne extends Thread{\n        public void run() {\n            IteratorInteger iterator = list.iterator();\n            while(iterator.hasNext()){\n                int i = iterator.next();\n                System.out.println(ThreadOne 遍历 + i);\n                try {\n                    Thread.sleep(10);\n                } catch (InterruptedException e) {\n                    e.printStackTrace();\n                }\n            }\n        }\n    }    \n````        \ndesc当i == 3时，修改list\n\n````\n        private static class threadTwo extends Thread{\n            public void run(){\n                int i = 0 ; \n                while(i  6){\n                    System.out.println(ThreadTwo run： + i);\n                    if(i == 3){\n                        list.remove(i);\n                    }\n                    i++;\n                }\n            }\n        }\n        \n        public static void main(String[] args) {\n            for(int i = 0 ; i  10;i++){\n                list.add(i);\n            }\n            new threadOne().start();\n            new threadTwo().start();\n        }\n    }\n````    \n运行结果：\n\n    ThreadOne 遍历0\n    ThreadTwo run：0\n    ThreadTwo run：1\n    ThreadTwo run：2\n    ThreadTwo run：3\n    ThreadTwo run：4\n    ThreadTwo run：5\n    Exception in thread Thread-0 java.util.ConcurrentModificationException\n        at java.util.ArrayList$Itr.checkForComodification(Unknown Source)\n        at java.util.ArrayList$Itr.next(Unknown Source)\n        at test.ArrayListTest$threadOne.run(ArrayListTest.java23)\n\n#### fail-fast产生原因\n\n通过上面的示例和讲解，我初步知道fail-fast产生的原因就在于程序在对 collection 进行迭代时，某个线程对该 collection 在结构上对其做了修改，这时迭代器就会抛出 ConcurrentModificationException 异常信息，从而产生 fail-fast。\n\n 要了解fail-fast机制，我们首先要对ConcurrentModificationException 异常有所了解。当方法检测到对象的并发修改，但不允许这种修改时就抛出该异常。同时需要注意的是，该异常不会始终指出对象已经由不同线程并发修改，如果单线程违反了规则，同样也有可能会抛出改异常。\n\n诚然，迭代器的快速失败行为无法得到保证，它不能保证一定会出现该错误，但是快速失败操作会尽最大努力抛出ConcurrentModificationException异常，所以因此，为提高此类操作的正确性而编写一个依赖于此异常的程序是错误的做法，正确做法是：ConcurrentModificationException 应该仅用于检测 bug。下面我将以ArrayList为例进一步分析fail-fast产生的原因。\n\n 从前面我们知道fail-fast是在操作迭代器时产生的。现在我们来看看ArrayList中迭代器的源代码：\n````\nprivate class Itr implements IteratorE {\n    int cursor;\n    int lastRet = -1;\n    int expectedModCount = ArrayList.this.modCount;\n\n    public boolean hasNext() {\n        return (this.cursor != ArrayList.this.size);\n    }\n\n    public E next() {\n        checkForComodification();\n         省略此处代码 \n    }\n\n    public void remove() {\n        if (this.lastRet  0)\n            throw new IllegalStateException();\n        checkForComodification();\n         省略此处代码 \n    }\n\n    final void checkForComodification() {\n        if (ArrayList.this.modCount == this.expectedModCount)\n            return;\n        throw new ConcurrentModificationException();\n    }\n}\n````\n从上面的源代码我们可以看出，迭代器在调用next()、remove()方法时都是调用checkForComodification()方法，该方法主要就是检测modCount == expectedModCount  若不等则抛出ConcurrentModificationException 异常，从而产生fail-fast机制。所以要弄清楚为什么会产生fail-fast机制我们就必须要用弄明白为什么modCount != expectedModCount ，他们的值在什么时候发生改变的。\n\nexpectedModCount 是在Itr中定义的：int expectedModCount = ArrayList.this.modCount;所以他的值是不可能会修改的，所以会变的就是modCount。modCount是在 AbstractList 中定义的，为全局变量：\n\nprotected transient int modCount = 0;\n那么他什么时候因为什么原因而发生改变呢？请看ArrayList的源码：\n````\npublic boolean add(E paramE) {\n    ensureCapacityInternal(this.size + 1);\n     省略此处代码 \n}\n\nprivate void ensureCapacityInternal(int paramInt) {\n    if (this.elementData == EMPTY_ELEMENTDATA)\n        paramInt = Math.max(10, paramInt);\n    ensureExplicitCapacity(paramInt);\n}\n\nprivate void ensureExplicitCapacity(int paramInt) {\n    this.modCount += 1;    修改modCount\n     省略此处代码 \n}\n\npublic boolean remove(Object paramObject) {\n    int i;\n    if (paramObject == null)\n        for (i = 0; i  this.size; ++i) {\n            if (this.elementData[i] != null)\n                continue;\n            fastRemove(i);\n            return true;\n        }\n    else\n        for (i = 0; i  this.size; ++i) {\n            if (!(paramObject.equals(this.elementData[i])))\n                continue;\n            fastRemove(i);\n            return true;\n        }\n    return false;\n}\n\nprivate void fastRemove(int paramInt) {\n    this.modCount += 1;   修改modCount\n     省略此处代码 \n}\n\npublic void clear() {\n    this.modCount += 1;    修改modCount\n     省略此处代码 \n}\n````\n从上面的源代码我们可以看出，ArrayList中无论add、remove、clear方法只要是涉及了改变ArrayList元素的个数的方法都会导致modCount的改变。\n\n所以我们这里可以初步判断由于expectedModCount 得值与modCount的改变不同步，导致两者之间不等从而产生fail-fast机制。知道产生fail-fast产生的根本原因了，我们可以有如下场景：\n\n有两个线程（线程A，线程B），其中线程A负责遍历list、线程B修改list。线程A在遍历list过程的某个时候（此时expectedModCount = modCount=N），线程启动，同时线程B增加一个元素，这是modCount的值发生改变（modCount + 1 = N + 1）。\n\n线程A继续遍历执行next方法时，通告checkForComodification方法发现expectedModCount  = N  ，而modCount = N + 1，两者不等，这时就抛出ConcurrentModificationException 异常，从而产生fail-fast机制。\n\n所以，直到这里我们已经完全了解了fail-fast产生的根本原因了。知道了原因就好找解决办法了。\n\n#### fail-fast解决办法\n\n通过前面的实例、源码分析，我想各位已经基本了解了fail-fast的机制，下面我就产生的原因提出解决方案。这里有两种解决方案：\n\n 方案一：在遍历过程中所有涉及到改变modCount值得地方全部加上synchronized或者直接使用Collections.synchronizedList，这样就可以解决。但是不推荐，因为增删造成的同步锁可能会阻塞遍历操作。\n\n 方案二：使用CopyOnWriteArrayList来替换ArrayList。推荐使用该方案。\n\nCopyOnWriteArrayList为何物？ArrayList 的一个线程安全的变体，其中所有可变操作（add、set 等等）都是通过对底层数组进行一次新的复制来实现的。 该类产生的开销比较大，但是在两种情况下，它非常适合使用。\n\n 1：在不能或不想进行同步遍历，但又需要从并发线程中排除冲突时。\n\n 2：当遍历操作的数量大大超过可变操作的数量时。遇到这两种情况使用CopyOnWriteArrayList来替代ArrayList再适合不过了。那么为什么CopyOnWriterArrayList可以替代ArrayList呢？\n\n\n第一、CopyOnWriterArrayList的无论是从数据结构、定义都和ArrayList一样。它和ArrayList一样，同样是实现List接口，底层使用数组实现。在方法上也包含add、remove、clear、iterator等方法。\n\n第二、CopyOnWriterArrayList根本就不会产生ConcurrentModificationException异常，也就是它使用迭代器完全不会产生fail-fast机制。请看：\n````\nprivate static class COWIteratorE implements ListIteratorE {\n    public E next() {\n        if (!(hasNext()))\n            throw new NoSuchElementException();\n        return this.snapshot[(this.cursor++)];\n    }\n}\n````\nCopyOnWriterArrayList的方法根本就没有像ArrayList中使用checkForComodification方法来判断expectedModCount 与 modCount 是否相等。它为什么会这么做，凭什么可以这么做呢？我们以add方法为例：\n````\npublic boolean add(E paramE) {\n        ReentrantLock localReentrantLock = this.lock;\n        localReentrantLock.lock();\n        try {\n            Object[] arrayOfObject1 = getArray();\n            int i = arrayOfObject1.length;\n            Object[] arrayOfObject2 = Arrays.copyOf(arrayOfObject1, i + 1);\n            arrayOfObject2[i] = paramE;\n            setArray(arrayOfObject2);\n            int j = 1;\n            return j;\n        } finally {\n            localReentrantLock.unlock();\n        }\n    }\n\n    final void setArray(Object[] paramArrayOfObject) {\n        this.array = paramArrayOfObject;\n    }\n````\nCopyOnWriterArrayList的add方法与ArrayList的add方法有一个最大的不同点就在于，下面三句代码：\n\n    Object[] arrayOfObject2 = Arrays.copyOf(arrayOfObject1, i + 1);\n    arrayOfObject2[i] = paramE;\n    setArray(arrayOfObject2);\n就是这三句代码使得CopyOnWriterArrayList不会抛ConcurrentModificationException异常。他们所展现的魅力就在于copy原来的array，再在copy数组上进行add操作，这样做就完全不会影响COWIterator中的array了。\n\n 所以CopyOnWriterArrayList所代表的核心概念就是：任何对array在结构上有所改变的操作（add、remove、clear等），CopyOnWriterArrayList都会copy现有的数据，再在copy的数据上修改，这样就不会影响COWIterator中的数据了，修改完成之后改变原有数据的引用即可。同时这样造成的代价就是产生大量的对象，同时数组的copy也是相当有损耗的。\n\n# Comparable 和 Comparator\n\nJava 中为我们提供了两种比较机制：Comparable 和 Comparator，他们之间有什么区别呢？今天来了解一下。\n\n## Comparable\n\nComparable 在 java.lang包下，是一个接口，内部只有一个方法 compareTo()：\n````\n    public interface ComparableT {\n        public int compareTo(T o);\n    }\n````\nComparable 可以让实现它的类的对象进行比较，具体的比较规则是按照 compareTo 方法中的规则进行。这种顺序称为 自然顺序。\n\ncompareTo 方法的返回值有三种情况：\n\n    e1.compareTo(e2)  0 即 e1  e2\n    e1.compareTo(e2) = 0 即 e1 = e2\n    e1.compareTo(e2)  0 即 e1  e2\n\n注意：\n\n 1.由于 null 不是一个类，也不是一个对象，因此在重写 compareTo 方法时应该注意 e.compareTo(null) 的情况，即使 e.equals(null) 返回 false，compareTo 方法也应该主动抛出一个空指针异常 NullPointerException。\n\n 2.Comparable 实现类重写 compareTo 方法时一般要求 e1.compareTo(e2) == 0 的结果要和 e1.equals(e2) 一致。这样将来使用 SortedSet 等根据类的自然排序进行排序的集合容器时可以保证保存的数据的顺序和想象中一致。\n 有人可能好奇上面的第二点如果违反了会怎样呢？\n\n举个例子，如果你往一个 SortedSet 中先后添加两个对象 a 和 b，a b 满足 (!a.equals(b) && a.compareTo(b) == 0)，同时也没有另外指定个 Comparator，那当你添加完 a 再添加 b 时会添加失败返回 false, SortedSet 的 size 也不会增加，因为在 SortedSet 看来它们是相同的，而 SortedSet 中是不允许重复的。\n\n 实际上所有实现了 Comparable 接口的 Java 核心类的结果都和 equlas 方法保持一致。 \n 实现了 Comparable 接口的 List 或则数组可以使用 Collections.sort() 或者 Arrays.sort() 方法进行排序。\n\n 实现了 Comparable 接口的对象才能够直接被用作 SortedMap (SortedSet) 的 key，要不然得在外边指定 Comparator 排序规则。\n\n因此自己定义的类如果想要使用有序的集合类，需要实现 Comparable 接口，比如：\n\n\n  description 测试用的实体类 书, 实现了 Comparable 接口，自然排序\n````    \n    public class BookBean implements Serializable, Comparable {\n        private String name;\n        private int count;\n\n\n    public BookBean(String name, int count) {\n        this.name = name;\n        this.count = count;\n    }\n    \n    public String getName() {\n        return name;\n    }\n    \n    public void setName(String name) {\n        this.name = name;\n    }\n    \n    public int getCount() {\n        return count;\n    }\n    \n    public void setCount(int count) {\n        this.count = count;\n    }\n     \n    @Override\n    public boolean equals(Object o) {\n        if (this == o) return true;\n        if (!(o instanceof BookBean)) return false;\n    \n        BookBean bean = (BookBean) o;\n    \n        if (getCount() != bean.getCount()) return false;\n        return getName().equals(bean.getName());\n    \n    }\n````\n    重写 hashCode 的计算方法\n    根据所有属性进行 迭代计算，避免重复\n    计算 hashCode 时 计算因子 31 见得很多，是一个质数，不能再被除\n\n````     \n    @Override\n    public int hashCode() {\n        调用 String 的 hashCode(), 唯一表示一个字符串内容\n        int result = getName().hashCode();\n        乘以 31, 再加上 count\n        result = 31  result + getCount();\n        return result;\n    }\n    \n    @Override\n    public String toString() {\n        return BookBean{ +\n                name=' + name + ''' +\n                , count= + count +\n                '}';\n    }\n````\n\n    当向 TreeSet 中添加 BookBean 时，会调用这个方法进行排序\n\n````\n    @Override\n    public int compareTo(Object another) {\n        if (another instanceof BookBean){\n            BookBean anotherBook = (BookBean) another;\n            int result;\n    \n            比如这里按照书价排序\n            result = getCount() - anotherBook.getCount();     \n    \n          或者按照 String 的比较顺序\n          result = getName().compareTo(anotherBook.getName());\n    \n            if (result == 0){   当书价一致时，再对比书名。 保证所有属性比较一遍\n                result = getName().compareTo(anotherBook.getName());\n            }\n            return result;\n        }\n         一样就返回 0\n        return 0;\n    }\n````\n上述代码还重写了 equlas(), hashCode() 方法，自定义的类将来可能会进行比较时，建议重写这些方法。\n\n 这里我想表达的是在有些场景下 equals 和 compareTo 结果要保持一致，这时候不重写 equals，使用 Object.equals 方法得到的结果会有问题，比如说 HashMap.put() 方法，会先调用 key 的 equals 方法进行比较，然后才调用 compareTo。\n\n 后面重写 compareTo 时，要判断某个相同时对比下一个属性，把所有属性都比较一次。\n \n### Comparator\n首先认识一下Comparator：\n\nComparator 是javase中的接口，位于java.util包下，该接口抽象度极高，有必要掌握该接口的使用\n大多数文章告诉大家Comparator是用来排序，但我想说排序是Comparator能实现的功能之一，他不仅限于排序\n\n排序例子：\n题目描述\n输入一个正整数数组，把数组里所有数字拼接起来排成一个数，打印能拼接出的所有数字中最小的一个。例如输入数组{3，32，321}，则打印出这三个数字能排成的最小数字为321323。\n\n代码实现：\n````    \nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Comparator;\n \npublic class Solution {\n    public String PrintMinNumber(int [] s) {\n        if(s==null) return null;\n        String s1=\"\";\n        ArrayList<Integer> list=new ArrayList<Integer>();\n        for(int i=0;i<s.length;i++){\n             list.add(s[i]);\n        }\n        Collections.sort(list,new Comparator<Integer>(){\n            public int compare(Integer str1,Integer str2){\n                String s1=str1+\"\"+str2;\n                String s2=str2+\"\"+str1;\n                return s1.compareTo(s2);\n            }\n        });\n         for(int j:list){\n                s1+=j;\n             }\n        return s1;\n    }\n}\n````    \n\n\n一般需要做比较的逻辑都可以使用的上Comparator，最常用的场景就是排序和分组，排序常使用Arrays和Collections的sort方法，而分组则可以使用提供的divider方法。\n\n排序和分组的区别在于:\n排序时，两个对象比较的结果有三种：大于，等于，小于。\n分组时，两个对象比较的结果只有两种：等于(两个对象属于同一组)，不等于(两个对象属于不同组)\n\n\n### Java8中使用lambda实现比较器\n\n今天先看看Lambda 表达式的简单使用：\n首先：Lambda表达式的基本语法：(parameters) -> expression或（请注意语句的花括号）\n(parameters) -> { statements; }\n\n第一感觉就是这个箭头感觉有点怪，不过多用几次习惯就好，它主要是为了把参数列表与Lambda主体分隔开，箭头左边的是参数列表，右边的是Lambda主体。注意：Lambda表达式可以包含多行语句。\n在用Lambda 之前，我们先看看之前写比较器的写法\n````\n    Comparator<Developer> byName = new Comparator<Developer>() {\n        @Override\n        public int compare(Developer o1, Developer o2) {\n            return o1.getName().compareTo(o2.getName());\n        }\n    };\n````\n感觉也不是很复杂，没几行代码，再来看看Lambda 表达式的写法：\n\n    Comparator<Developer> byName =\n        (Developer o1, Developer o2)->o1.getName().compareTo(o2.getName());\n\n比之前要简单许多有木有。\n下面再来看看排序功能示例：\n先用Collections.sort如下：\n````\n    public class TestSorting {\n        public static void main(String[] args) {\n            List<Developer> listDevs = getDevelopers();\n            System.out.println(\"Before Sort\");\n            for (Developer developer : listDevs) {\n                System.out.println(developer);\n            }\n            //安装年龄排序\n            Collections.sort(listDevs, new Comparator<Developer>() {\n                @Override\n                public int compare(Developer o1, Developer o2) {\n                    return o1.getAge() - o2.getAge();\n                }\n            });\n            System.out.println(\"After Sort\");\n            for (Developer developer : listDevs) {\n                System.out.println(developer);\n            }\n        }\n        private static List<Developer> getDevelopers() {\n            List<Developer> result = new ArrayList<Developer>();\n            result.add(new Developer(\"mkyong\", new BigDecimal(\"70000\"), 33));\n            result.add(new Developer(\"alvin\", new BigDecimal(\"80000\"), 20));\n            result.add(new Developer(\"jason\", new BigDecimal(\"100000\"), 10));\n            result.add(new Developer(\"iris\", new BigDecimal(\"170000\"), 55));\n            return result;\n        }\n    }\n````\n\n    输出结果：\n    \n    Before Sort\n    Developer [name=mkyong, salary=70000, age=33]\n    Developer [name=alvin, salary=80000, age=20]\n    Developer [name=jason, salary=100000, age=10]\n    Developer [name=iris, salary=170000, age=55]\n     \n    After Sort\n    Developer [name=jason, salary=100000, age=10]\n    Developer [name=alvin, salary=80000, age=20]\n    Developer [name=mkyong, salary=70000, age=33]\n    Developer [name=iris, salary=170000, age=55]\n\n看起来整个流程完全没毛病，下面再来看看Lambda的方式:\n````\n    public class TestSorting {\n        public static void main(String[] args) {\n            List<Developer> listDevs = getDevelopers();\n            System.out.println(\"Before Sort\");\n            for (Developer developer : listDevs) {\n                System.out.println(developer);\n            }\n            System.out.println(\"After Sort\");\n            //对比上面的代码\n            listDevs.sort((Developer o1, Developer o2)->o1.getAge()-o2.getAge());\n            //这样打印感觉也不错\n            listDevs.forEach((developer)->System.out.println(developer));\n        }\n        private static List<Developer> getDevelopers() {\n            List<Developer> result = new ArrayList<Developer>();\n            result.add(new Developer(\"mkyong\", new BigDecimal(\"70000\"), 33));\n            result.add(new Developer(\"alvin\", new BigDecimal(\"80000\"), 20));\n            result.add(new Developer(\"jason\", new BigDecimal(\"100000\"), 10));\n            result.add(new Developer(\"iris\", new BigDecimal(\"170000\"), 55));\n            return result;\n        }\n    }\n````\n输出结果：\n\n    Before Sort\n    Developer [name=mkyong, salary=70000, age=33]\n    Developer [name=alvin, salary=80000, age=20]\n    Developer [name=jason, salary=100000, age=10]\n    Developer [name=iris, salary=170000, age=55]\n    \n    After Sort\n    Developer [name=jason, salary=100000, age=10]\n    Developer [name=alvin, salary=80000, age=20]\n    Developer [name=mkyong, salary=70000, age=33]\n    Developer [name=iris, salary=170000, age=55]\n\n总体来说，写法与之前有较大的改变，写的代码更少，更简便，感觉还不错。\n后续会带来更多有关Java8相关的东西，毕竟作为一只程序狗，得不停的学习才能不被淘汰。Java语言都在不停的改进更新，我们有啥理由不跟上节奏呢？\n由于时间问题这里只是一个简单的应用，想了解更多可到官网查找相关示例。\n\n## 总结\n\nJava 中的两种排序方式：\n\n    Comparable 自然排序。（实体类实现）\n    Comparator 是定制排序。（无法修改实体类时，直接在调用方创建）\n    同时存在时采用 Comparator（定制排序）的规则进行比较。\n\n对于一些普通的数据类型（比如 String, Integer, Double…），它们默认实现了Comparable 接口，实现了 compareTo 方法，我们可以直接使用。\n\n而对于一些自定义类，它们可能在不同情况下需要实现不同的比较策略，我们可以新创建 Comparator 接口，然后使用特定的 Comparator 实现进行比较。\n\n这就是 Comparable 和 Comparator 的区别。\n\n\n## 参考文章\n\nhttps://blog.csdn.net/weixin_30363263/article/details/80867590\n\nhttps://www.cnblogs.com/shizhijie/p/7657049.html\n\nhttps://www.cnblogs.com/xiaweicn/p/8688216.html\n\nhttps://cmsblogs.com/p=1185\n\nhttps://blog.csdn.net/android_hl/article/details/53228348\n\n\n\n\n\n                     \n"
  },
  {
    "path": "docs/Java/collection/Java集合详解：Java集合类细节精讲.md",
    "content": "# 目录\n  * [初始容量](#初始容量)\n  * [asList的缺陷](#aslist的缺陷)\n    * [避免使用基本数据类型数组转换为列表](#避免使用基本数据类型数组转换为列表)\n    * [asList产生的列表不可操作](#aslist产生的列表不可操作)\n  * [subList的缺陷](#sublist的缺陷)\n    * [subList返回仅仅只是一个视图](#sublist返回仅仅只是一个视图)\n    * [subList生成子列表后，不要试图去操作原列表](#sublist生成子列表后，不要试图去操作原列表)\n    * [推荐使用subList处理局部列表](#推荐使用sublist处理局部列表)\n  * [保持compareTo和equals同步](#保持compareto和equals同步)\n  * [参考文章](#参考文章)\n\n\n本文参考多篇优质技术博客，参考文章请在文末查看\n\n\n《Java集合详解系列》是我在完成夯实Java基础篇的系列博客后准备开始整理的新系列文章。\n为了更好地诠释知识点，形成体系文章，本系列文章整理了很多优质的博客内容，如有侵权请联系我，一定删除。\n\n这些文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star、fork哈\n\n本系列文章将整理于我的个人博客：\n\n> www.how2playlife.com\n\n今天我们来探索一下Java集合类中的一些技术细节。主要是对一些比较容易被遗漏和误解的知识点做一些讲解和补充。可能不全面，还请谅解。\n\n## 初始容量\n\n集合是我们在Java编程中使用非常广泛的，它就像大海，海纳百川，像万能容器，盛装万物，而且这个大海，万能容器还可以无限变大（如果条件允许）。当这个海、容器的量变得非常大的时候，它的初始容量就会显得很重要了，因为挖海、扩容是需要消耗大量的人力物力财力的。\n\n同样的道理，Collection的初始容量也显得异常重要。所以：对于已知的情景，请为集合指定初始容量。\n\n    public static void main(String[] args) {\n        StudentVO student = null;\n        long begin1 = System.currentTimeMillis();\n        List<StudentVO> list1 = new ArrayList<>();\n        for(int i = 0 ; i < 1000000; i++){\n            student = new StudentVO(i,\"chenssy_\"+i,i);\n            list1.add(student);\n        }\n        long end1 = System.currentTimeMillis();\n        System.out.println(\"list1 time：\" + (end1 - begin1));\n        \n        long begin2 = System.currentTimeMillis();\n        List<StudentVO> list2 = new ArrayList<>(1000000);\n        for(int i = 0 ; i < 1000000; i++){\n            student = new StudentVO(i,\"chenssy_\"+i,i);\n            list2.add(student);\n        }\n        long end2 = System.currentTimeMillis();\n        System.out.println(\"list2 time：\" + (end2 - begin2));\n    }\n上面代码两个list都是插入1000000条数据，只不过list1没有没有申请初始化容量，而list2初始化容量1000000。那运行结果如下：\n\n    list1 time：1638\n    list2 time：921\n\n从上面的运行结果我们可以看出list2的速度是list1的两倍左右。在前面LZ就提过，ArrayList的扩容机制是比较消耗资源的。我们先看ArrayList的add方法：\n\n    public boolean add(E e) {  \n            ensureCapacity(size + 1);   \n            elementData[size++] = e;  \n            return true;  \n        }  \n    \n    public void ensureCapacity(int minCapacity) {  \n        modCount++;         //修改计数器\n        int oldCapacity = elementData.length;    \n        //当前需要的长度超过了数组长度，进行扩容处理\n        if (minCapacity > oldCapacity) {  \n            Object oldData[] = elementData;  \n            //新的容量 = 旧容量 * 1.5 + 1\n            int newCapacity = (oldCapacity * 3)/2 + 1;  \n                if (newCapacity < minCapacity)  \n                    newCapacity = minCapacity;  \n          //数组拷贝，生成新的数组 \n          elementData = Arrays.copyOf(elementData, newCapacity);  \n        }  \n    }\nArrayList每次新增一个元素，就会检测ArrayList的当前容量是否已经到达临界点，如果到达临界点则会扩容1.5倍。然而ArrayList的扩容以及数组的拷贝生成新的数组是相当耗资源的。所以若我们事先已知集合的使用场景，知道集合的大概范围，我们最好是指定初始化容量，这样对资源的利用会更加好，尤其是大数据量的前提下，效率的提升和资源的利用会显得更加具有优势。\n\n## asList的缺陷\n\n在实际开发过程中我们经常使用asList讲数组转换为List，这个方法使用起来非常方便，但是asList方法存在几个缺陷：\n\n### 避免使用基本数据类型数组转换为列表\n\n使用8个基本类型数组转换为列表时会存在一个比较有味的缺陷。先看如下程序：\n\n    public static void main(String[] args) {\n            int[] ints = {1,2,3,4,5};\n            List list = Arrays.asList(ints);\n            System.out.println(\"list'size：\" + list.size());\n        }\n    ------------------------------------\n    outPut：\n    list'size：1\n程序的运行结果并没有像我们预期的那样是5而是逆天的1，这是什么情况？先看源码：\n\n    public static <T> List<T> asList(T... a) {\n            return new ArrayList<>(a);\n        }\n\nasList接受的参数是一个泛型的变长参数，我们知道基本数据类型是无法发型化的，也就是说8个基本类型是无法作为asList的参数的， 要想作为泛型参数就必须使用其所对应的包装类型。但是这个这个实例中为什么没有出错呢？\n\n因为该实例是将int类型的数组当做其参数，而在Java中数组是一个对象，它是可以泛型化的。所以该例子是不会产生错误的。既然例子是将整个int类型的数组当做泛型参数，那么经过asList转换就只有一个int 的列表了。如下：\n\n    public static void main(String[] args) {\n        int[] ints = {1,2,3,4,5};\n        List list = Arrays.asList(ints);\n        System.out.println(\"list 的类型:\" + list.get(0).getClass());\n        System.out.println(\"list.get(0) == ints：\" + list.get(0).equals(ints));\n    }\n--------------------------------------------\noutPut:\nlist 的类型:class [I\nlist.get(0) == ints：true\n从这个运行结果我们可以充分证明list里面的元素就是int数组。弄清楚这点了，那么修改方法也就一目了然了：将int 改变为Integer。\n\n    public static void main(String[] args) {\n            Integer[] ints = {1,2,3,4,5};\n            List list = Arrays.asList(ints);\n            System.out.println(\"list'size：\" + list.size());\n            System.out.println(\"list.get(0) 的类型:\" + list.get(0).getClass());\n            System.out.println(\"list.get(0) == ints[0]：\" + list.get(0).equals(ints[0]));\n        }\n    ----------------------------------------\n    outPut:\n    list'size：5\n    list.get(0) 的类型:class java.lang.Integer\n    list.get(0) == ints[0]：true\n\n\n\n### asList产生的列表不可操作\n\n对于上面的实例我们再做一个小小的修改：\n\n    public static void main(String[] args) {\n            Integer[] ints = {1,2,3,4,5};\n            List list = Arrays.asList(ints);\n            list.add(6);\n        }\n\n该实例就是讲ints通过asList转换为list 类别，然后再通过add方法加一个元素，这个实例简单的不能再简单了，但是运行结果呢？打出我们所料：\n\n    Exception in thread \"main\" java.lang.UnsupportedOperationException\n        at java.util.AbstractList.add(Unknown Source)\n        at java.util.AbstractList.add(Unknown Source)\n        at com.chenssy.test.arrayList.AsListTest.main(AsListTest.java:10)\n\n运行结果尽然抛出UnsupportedOperationException异常，该异常表示list不支持add方法。这就让我们郁闷了，list怎么可能不支持add方法呢？难道jdk脑袋堵塞了？我们再看asList的源码：\n\n    public static <T> List<T> asList(T... a) {\n            return new ArrayList<>(a);\n        }\nasList接受参数后，直接new 一个ArrayList，到这里看应该是没有错误的啊？别急，再往下看:\n\n    private static class ArrayList<E> extends AbstractList<E>\n        implements RandomAccess, java.io.Serializable{\n            private static final long serialVersionUID = -2764017481108945198L;\n            private final E[] a;\n    \n            ArrayList(E[] array) {\n                if (array==null)\n                    throw new NullPointerException();\n                a = array;\n            }\n            //.................\n        }\n这是ArrayList的源码,从这里我们可以看出,此ArrayList不是java.util.ArrayList，他是Arrays的内部类。\n\n该内部类提供了size、toArray、get、set、indexOf、contains方法，而像add、remove等改变list结果的方法从AbstractList父类继承过来，同时这些方法也比较奇葩，它直接抛出UnsupportedOperationException异常：\n\n    public boolean add(E e) {\n            add(size(), e);\n            return true;\n        }\n        \n        public E set(int index, E element) {\n            throw new UnsupportedOperationException();\n        }\n        \n        public void add(int index, E element) {\n            throw new UnsupportedOperationException();\n        }\n        \n        public E remove(int index) {\n            throw new UnsupportedOperationException();\n        }\n\n通过这些代码可以看出asList返回的列表只不过是一个披着list的外衣，它并没有list的基本特性（变长）。该list是一个长度不可变的列表，传入参数的数组有多长，其返回的列表就只能是多长。所以：：不要试图改变asList返回的列表，否则你会自食苦果。\n\n## subList的缺陷\n我们经常使用subString方法来对String对象进行分割处理，同时我们也可以使用subList、subMap、subSet来对List、Map、Set进行分割处理，但是这个分割存在某些瑕疵。\n\n### subList返回仅仅只是一个视图\n\n首先我们先看如下实例：\n\npublic static void main(String[] args) {\n        List<Integer> list1 = new ArrayList<Integer>();\n        list1.add(1);\n        list1.add(2);\n        \n        //通过构造函数新建一个包含list1的列表 list2\n        List<Integer> list2 = new ArrayList<Integer>(list1);\n        \n        //通过subList生成一个与list1一样的列表 list3\n        List<Integer> list3 = list1.subList(0, list1.size());\n        \n        //修改list3\n        list3.add(3);\n        \n        System.out.println(\"list1 == list2：\" + list1.equals(list2));\n        System.out.println(\"list1 == list3：\" + list1.equals(list3));\n    }\n\n这个例子非常简单，无非就是通过构造函数、subList重新生成一个与list1一样的list，然后修改list3，最后比较list1 == list2?、list1 == list3?。\n\n按照我们常规的思路应该是这样的：因为list3通过add新增了一个元素，那么它肯定与list1不等，而list2是通过list1构造出来的，所以应该相等，所以结果应该是：\n\n    list1 == list2：true\n    list1 == list3: false\n首先我们先不论结果的正确与否，我们先看subList的源码：\n\n    public List<E> subList(int fromIndex, int toIndex) {\n            subListRangeCheck(fromIndex, toIndex, size);\n            return new SubList(this, 0, fromIndex, toIndex);\n    }\n\nsubListRangeCheck方式是判断fromIndex、toIndex是否合法，如果合法就直接返回一个subList对象，注意在产生该new该对象的时候传递了一个参数 this ，该参数非常重要，因为他代表着原始list。\n\n/**\n     * 继承AbstractList类，实现RandomAccess接口\n     */\n    private class SubList extends AbstractList<E> implements RandomAccess {\n        private final AbstractList<E> parent;    //列表\n        private final int parentOffset;   \n        private final int offset;\n        int size;\n\n        //构造函数\n        SubList(AbstractList<E> parent,\n                int offset, int fromIndex, int toIndex) {\n            this.parent = parent;\n            this.parentOffset = fromIndex;\n            this.offset = offset + fromIndex;\n            this.size = toIndex - fromIndex;\n            this.modCount = ArrayList.this.modCount;\n        }\n    \n        //set方法\n        public E set(int index, E e) {\n            rangeCheck(index);\n            checkForComodification();\n            E oldValue = ArrayList.this.elementData(offset + index);\n            ArrayList.this.elementData[offset + index] = e;\n            return oldValue;\n        }\n    \n        //get方法\n        public E get(int index) {\n            rangeCheck(index);\n            checkForComodification();\n            return ArrayList.this.elementData(offset + index);\n        }\n    \n        //add方法\n        public void add(int index, E e) {\n            rangeCheckForAdd(index);\n            checkForComodification();\n            parent.add(parentOffset + index, e);\n            this.modCount = parent.modCount;\n            this.size++;\n        }\n    \n        //remove方法\n        public E remove(int index) {\n            rangeCheck(index);\n            checkForComodification();\n            E result = parent.remove(parentOffset + index);\n            this.modCount = parent.modCount;\n            this.size--;\n            return result;\n        }\n    }\n\n该SubLsit是ArrayList的内部类，它与ArrayList一样，都是继承AbstractList和实现RandomAccess接口。同时也提供了get、set、add、remove等list常用的方法。但是它的构造函数有点特殊，在该构造函数中有两个地方需要注意：\n\n1、this.parent = parent;而parent就是在前面传递过来的list，也就是说this.parent就是原始list的引用。\n\n2、this.offset = offset + fromIndex;this.parentOffset = fromIndex;。同时在构造函数中它甚至将modCount（fail-fast机制）传递过来了。\n\n我们再看get方法，在get方法中return ArrayList.this.elementData(offset + index);\n\n这段代码可以清晰表明get所返回就是原列表offset + index位置的元素。同样的道理还有add方法里面的：\n\nparent.add(parentOffset + index, e);\nthis.modCount = parent.modCount;\nremove方法里面的\n\nE result = parent.remove(parentOffset + index);\nthis.modCount = parent.modCount;\n\n诚然，到了这里我们可以判断subList返回的SubList同样也是AbstractList的子类，同时它的方法如get、set、add、remove等都是在原列表上面做操作，它并没有像subString一样生成一个新的对象。\n\n所以subList返回的只是原列表的一个视图，它所有的操作最终都会作用在原列表上。\n\n那么从这里的分析我们可以得出上面的结果应该恰恰与我们上面的答案相反：\n\nlist1 == list2：false\nlist1 == list3：true\n\n\n\n### subList生成子列表后，不要试图去操作原列表\n\n从上面我们知道subList生成的子列表只是原列表的一个视图而已，如果我们操作子列表它产生的作用都会在原列表上面表现，但是如果我们操作原列表会产生什么情况呢？\n\npublic static void main(String[] args) {\n        List<Integer> list1 = new ArrayList<Integer>();\n        list1.add(1);\n        list1.add(2);\n        \n        //通过subList生成一个与list1一样的列表 list3\n        List<Integer> list3 = list1.subList(0, list1.size());\n        //修改list1\n        list1.add(3);\n        \n        System.out.println(\"list1'size：\" + list1.size());\n        System.out.println(\"list3'size：\" + list3.size());\n    }\n该实例如果不产生意外，那么他们两个list的大小都应该都是3，但是偏偏事与愿违，事实上我们得到的结果是这样的：\n\n    list1'size：3\n    Exception in thread \"main\" java.util.ConcurrentModificationException\n        at java.util.ArrayList$SubList.checkForComodification(Unknown Source)\n        at java.util.ArrayList$SubList.size(Unknown Source)\n        at com.chenssy.test.arrayList.SubListTest.main(SubListTest.java:17)\nlist1正常输出，但是list3就抛出ConcurrentModificationException异常，看过我另一篇博客的同仁肯定对这个异常非常，fail-fast？不错就是fail-fast机制，在fail-fast机制中，LZ花了很多力气来讲述这个异常，所以这里LZ就不对这个异常多讲了。我们再看size方法：\n\n    public int size() {\n                checkForComodification();\n                return this.size;\n            }\nsize方法首先会通过checkForComodification验证，然后再返回this.size。\n\n    private void checkForComodification() {\n                if (ArrayList.this.modCount != this.modCount)\n                    throw new ConcurrentModificationException();\n            }\n该方法表明当原列表的modCount与this.modCount不相等时就会抛出ConcurrentModificationException。\n\n同时我们知道modCount 在new的过程中 \"继承\"了原列表modCount，只有在修改该列表（子列表）时才会修改该值（先表现在原列表后作用于子列表）。\n\n而在该实例中我们是操作原列表，原列表的modCount当然不会反应在子列表的modCount上啦，所以才会抛出该异常。\n\n对于子列表视图，它是动态生成的，生成之后就不要操作原列表了，否则必然都导致视图的不稳定而抛出异常。最好的办法就是将原列表设置为只读状态，要操作就操作子列表：\n\n//通过subList生成一个与list1一样的列表 list3\n\n    List<Integer> list3 = list1.subList(0, list1.size());\n\n//对list1设置为只读状态\n\n    list1 = Collections.unmodifiableList(list1);\n\n### 推荐使用subList处理局部列表\n\n在开发过程中我们一定会遇到这样一个问题：获取一堆数据后，需要删除某段数据。例如，有一个列表存在1000条记录，我们需要删除100-200位置处的数据，可能我们会这样处理：\n\n    for(int i = 0 ; i < list1.size() ; i++){\n       if(i >= 100 && i <= 200){\n           list1.remove(i);\n           /*\n            * 当然这段代码存在问题，list remove之后后面的元素会填充上来，\n             * 所以需要对i进行简单的处理，当然这个不是这里讨论的问题。\n             */\n       }\n    }\n这个应该是我们大部分人的处理方式吧，其实还有更好的方法，利用subList。在前面LZ已经讲过，子列表的操作都会反映在原列表上。所以下面一行代码全部搞定：\n\n    list1.subList(100, 200).clear();\n简单而不失华丽！！！！！\n\n\n## 保持compareTo和equals同步\n在Java中我们常使用Comparable接口来实现排序，其中compareTo是实现该接口方法。我们知道compareTo返回0表示两个对象相等，返回正数表示大于，返回负数表示小于。同时我们也知道equals也可以判断两个对象是否相等，那么他们两者之间是否存在关联关系呢？\n\n    public class Student implements Comparable<Student>{\n        private String id;\n        private String name;\n        private int age;\n        \n        public Student(String id,String name,int age){\n            this.id = id;\n            this.name = name;\n            this.age = age;\n        }\n    \n        public boolean equals(Object obj){\n            if(obj == null){\n                return false;\n            }\n            \n            if(this == obj){\n                return true;\n            }\n            \n            if(obj.getClass() != this.getClass()){\n                return false;\n            }\n            \n            Student student = (Student)obj;\n            if(!student.getName().equals(getName())){\n                return false;\n            }\n            \n            return true;\n        }\n        \n        public int compareTo(Student student) {\n            return this.age - student.age;\n        }\n    \n        /** 省略getter、setter方法 */\n    }\nStudent类实现Comparable接口和实现equals方法，其中compareTo是根据age来比对的，equals是根据name来比对的。\n\n    public static void main(String[] args){\n            List<Student> list = new ArrayList<>();\n            list.add(new Student(\"1\", \"chenssy1\", 24));\n            list.add(new Student(\"2\", \"chenssy1\", 26));\n            \n            Collections.sort(list);   //排序\n            \n            Student student = new Student(\"2\", \"chenssy1\", 26);\n            \n            //检索student在list中的位置\n            int index1 = list.indexOf(student);\n            int index2 = Collections.binarySearch(list, student);\n            \n            System.out.println(\"index1 = \" + index1);\n            System.out.println(\"index2 = \" + index2);\n        }\n\n按照常规思路来说应该两者index是一致的，因为他们检索的是同一个对象，但是非常遗憾，其运行结果：\n\nindex1 = 0\nindex2 = 1\n\n> 为什么会产生这样不同的结果呢？这是因为indexOf和binarySearch的实现机制不同。\n>\n> indexOf是基于equals来实现的只要equals返回TRUE就认为已经找到了相同的元素。\n>\n> 而binarySearch是基于compareTo方法的，当compareTo返回0 时就认为已经找到了该元素。\n>\n> 在我们实现的Student类中我们覆写了compareTo和equals方法，但是我们的compareTo、equals的比较依据不同，一个是基于age、一个是基于name。\n\n比较依据不同那么得到的结果很有可能会不同。所以知道了原因，我们就好修改了：将两者之间的比较依据保持一致即可。\n\n对于compareTo和equals两个方法我们可以总结为：compareTo是判断元素在排序中的位置是否相等，equals是判断元素是否相等，既然一个决定排序位置，一个决定相等，所以我们非常有必要确保当排序位置相同时，其equals也应该相等。\n\n使其相等的方式就是两者应该依附于相同的条件。当compareto相等时equals也应该相等，而compareto不相等时equals不应该相等，并且compareto依据某些属性来决定排序。\n\n\n## 参考文章\nhttps://www.cnblogs.com/galibujianbusana/p/6600226.html\n\nhttp://blog.itpub.net/69906029/viewspace-2641300/\n\nhttps://www.cnblogs.com/itxiaok/p/10356553.html\n\n\n\n\n\n\n​                     \n"
  },
  {
    "path": "docs/Java/collection/Java集合详解：Queue和LinkedList.md",
    "content": "# 目录\n  * [LinkedList](#linkedlist)\n    * [概述](#概述)\n    * [源码分析](#源码分析)\n      * [定义](#定义)\n      * [属性](#属性)\n      * [构造方法](#构造方法)\n      * [增加方法](#增加方法)\n      * [移除方法](#移除方法)\n      * [查找方法](#查找方法)\n  * [Queue](#queue)\n    * [DeQueue](#dequeue)\n    * [ArrayDeque （底层使用循环数组实现双向队列）](#arraydeque-（底层使用循环数组实现双向队列）)\n      * [创建](#创建)\n      * [add操作](#add操作)\n      * [remove操作](#remove操作)\n    * [PriorityQueue（底层用数组实现堆的结构）](#priorityqueue（底层用数组实现堆的结构）)\n      * [add 添加方法](#add-添加方法)\n      * [poll，出队方法](#poll，出队方法)\n      * [remove，删除队列元素](#remove，删除队列元素)\n  * [总结和同步的问题](#总结和同步的问题)\n  * [参考文章](#参考文章)\n\n\n本文参考 http://cmsblogs.com/?p=155 \n和 \nhttps://www.jianshu.com/p/0e84b8d3606c\n\n《Java集合详解系列》是我在完成夯实Java基础篇的系列博客后准备开始整理的新系列文章。\n为了更好地诠释知识点，形成体系文章，本系列文章整理了很多优质的博客内容，如有侵权请联系我，一定删除。\n\n这些文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star、fork哈\n\n本系列文章将整理于我的个人博客：\n\n> www.how2playlife.com\n\n## LinkedList\n### 概述\n> \n>   LinkedList与ArrayList一样实现List接口，只是ArrayList是List接口的大小可变数组的实现，LinkedList是List接口链表的实现。基于链表实现的方式使得LinkedList在插入和删除时更优于ArrayList，而随机访问则比ArrayList逊色些。\n> \n>   LinkedList实现所有可选的列表操作，并允许所有的元素包括null。\n> \n>   除了实现 List 接口外，LinkedList 类还为在列表的开头及结尾 get、remove 和 insert 元素提供了统一的命名方法。这些操作允许将链接列表用作堆栈、队列或双端队列。\n> \n>   此类实现 Deque 接口，为 add、poll 提供先进先出队列操作，以及其他堆栈和双端队列操作。\n> \n>   所有操作都是按照双重链接列表的需要执行的。在列表中编索引的操作将从开头或结尾遍历列表（从靠近指定索引的一端）。\n> \n>   同时，与ArrayList一样此实现不是同步的。\n> \n>   （以上摘自JDK 6.0 API）。\n\n### 源码分析\n\n#### 定义\n\n  首先我们先看LinkedList的定义：\n````\npublic class LinkedList<E>\nextends AbstractSequentialList<E>\nimplements List<E>, Deque<E>, Cloneable, java.io.Serializable\n````\n从这段代码中我们可以清晰地看出LinkedList继承AbstractSequentialList，实现List、Deque、Cloneable、Serializable。其中AbstractSequentialList提供了 List 接口的骨干实现，从而最大限度地减少了实现受“连续访问”数据存储（如链接列表）支持的此接口所需的工作,从而以减少实现List接口的复杂度。Deque一个线性 collection，支持在两端插入和移除元素，定义了双端队列的操作。\n\n#### 属性\n\n在LinkedList中提供了两个基本属性size、header。\n````\nprivate transient Entry<E> header = new Entry<E>(null, null, null);\nprivate transient int size = 0;\n//其中size表示的LinkedList的大小，header表示链表的表头，Entry为节点对象。\n\nprivate static class Entry<E> {\n    E element;        //元素节点\n    Entry<E> next;    //下一个元素\n    Entry<E> previous;  //上一个元素\n\n    Entry(E element, Entry<E> next, Entry<E> previous) {\n        this.element = element;\n        this.next = next;\n        this.previous = previous;\n    }\n}\n````\n上面为Entry对象的源代码，Entry为LinkedList的内部类，它定义了存储的元素。该元素的前一个元素、后一个元素，这是典型的双向链表定义方式。\n\n#### 构造方法\n\nLinkedList提供了两个构造方法：LinkedList()和LinkedList(Collection<? extends E> c)。\n````\n/**\n *  构造一个空列表。\n */\npublic LinkedList() {\n    header.next = header.previous = header;\n}\n\n/**\n *  构造一个包含指定 collection 中的元素的列表，这些元素按其 collection 的迭代器返回的顺序排列。\n */\npublic LinkedList(Collection<? extends E> c) {\n    this();\n    addAll(c);\n}\n````\n  LinkedList()构造一个空列表。里面没有任何元素，仅仅只是将header节点的前一个元素、后一个元素都指向自身。\n\n  LinkedList(Collection<? extends E> c)： 构造一个包含指定 collection 中的元素的列表，这些元素按其 collection 的迭代器返回的顺序排列。该构造函数首先会调用LinkedList()，构造一个空列表，然后调用了addAll()方法将Collection中的所有元素添加到列表中。以下是addAll()的源代码：\n\n````\n/**\n     *  添加指定 collection 中的所有元素到此列表的结尾，顺序是指定 collection 的迭代器返回这些元素的顺序。\n     */\n    public boolean addAll(Collection<? extends E> c) {\n        return addAll(size, c);\n    }\n    \n/**\n * 将指定 collection 中的所有元素从指定位置开始插入此列表。其中index表示在其中插入指定collection中第一个元素的索引\n */\npublic boolean addAll(int index, Collection<? extends E> c) {\n    //若插入的位置小于0或者大于链表长度，则抛出IndexOutOfBoundsException异常\n    if (index < 0 || index > size)\n        throw new IndexOutOfBoundsException(\"Index: \" + index + \", Size: \" + size);\n    Object[] a = c.toArray();\n    int numNew = a.length;    //插入元素的个数\n    //若插入的元素为空，则返回false\n    if (numNew == 0)\n        return false;\n    //modCount:在AbstractList中定义的，表示从结构上修改列表的次数\n    modCount++;\n    //获取插入位置的节点，若插入的位置在size处，则是头节点，否则获取index位置处的节点\n    Entry<E> successor = (index == size ? header : entry(index));\n    //插入位置的前一个节点，在插入过程中需要修改该节点的next引用：指向插入的节点元素\n    Entry<E> predecessor = successor.previous;\n    //执行插入动作\n    for (int i = 0; i < numNew; i++) {\n        //构造一个节点e，这里已经执行了插入节点动作同时修改了相邻节点的指向引用\n        //\n        Entry<E> e = new Entry<E>((E) a[i], successor, predecessor);\n        //将插入位置前一个节点的下一个元素引用指向当前元素\n        predecessor.next = e;\n        //修改插入位置的前一个节点，这样做的目的是将插入位置右移一位，保证后续的元素是插在该元素的后面，确保这些元素的顺序\n        predecessor = e;\n    }\n    successor.previous = predecessor;\n    //修改容量大小\n    size += numNew;\n    return true;\n}\n````\n\n在addAll()方法中，涉及到了两个方法，一个是entry(int index)，该方法为LinkedList的私有方法，主要是用来查找index位置的节点元素。\n\n````\n/**\n * 返回指定位置(若存在)的节点元素\n */\nprivate Entry<E> entry(int index) {\n    if (index < 0 || index >= size)\n        throw new IndexOutOfBoundsException(\"Index: \" + index + \", Size: \"\n                + size);\n    //头部节点\n    Entry<E> e = header;\n    //判断遍历的方向\n    if (index < (size >> 1)) {\n        for (int i = 0; i <= index; i++)\n            e = e.next;\n    } else {\n        for (int i = size; i > index; i--)\n            e = e.previous;\n    }\n    return e;\n}\n````        \n  从该方法有两个遍历方向中我们也可以看出LinkedList是双向链表，这也是在构造方法中为什么需要将header的前、后节点均指向自己。\n\n  如果对数据结构有点了解，对上面所涉及的内容应该问题，我们只需要清楚一点：LinkedList是双向链表，其余都迎刃而解。\n\n  由于篇幅有限，下面将就LinkedList中几个常用的方法进行源码分析。\n\n#### 增加方法\n\nadd(E e): 将指定元素添加到此列表的结尾。\n\n````\npublic boolean add(E e) {\n    addBefore(e, header);\n        return true;\n}\n\n//该方法调用addBefore方法，然后直接返回true，对于addBefore()而已，它为LinkedList的私有方法。\nprivate Entry<E> addBefore(E e, Entry<E> entry) {\n    //利用Entry构造函数构建一个新节点 newEntry，\n    Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);\n    //修改newEntry的前后节点的引用，确保其链表的引用关系是正确的\n    newEntry.previous.next = newEntry;\n    newEntry.next.previous = newEntry;\n    //容量+1\n    size++;\n    //修改次数+1\n    modCount++;\n    return newEntry;\n}\n````\n\n    在addBefore方法中无非就是做了这件事：构建一个新节点newEntry，然后修改其前后的引用。\n    \n    LinkedList还提供了其他的增加方法：\n    \n    add(int index, E element)：在此列表中指定的位置插入指定的元素。\n    \n    addAll(Collection<? extends E> c)：添加指定 collection 中的所有元素到此列表的结尾，顺序是指定 collection 的迭代器返回这些元素的顺序。\n    \n    addAll(int index, Collection<? extends E> c)：将指定 collection 中的所有元素从指定位置开始插入此列表。\n    \n    AddFirst(E e): 将指定元素插入此列表的开头。\n    \n    addLast(E e): 将指定元素添加到此列表的结尾。\n\n#### 移除方法\n\nremove(Object o)：从此列表中移除首次出现的指定元素（如果存在）。该方法的源代码如下：\n\n````\n    public boolean remove(Object o) {\n            if (o==null) {\n                for (Entry<E> e = header.next; e != header; e = e.next) {\n                    if (e.element==null) {\n                        remove(e);\n                        return true;\n                    }\n                }\n            } else {\n                for (Entry<E> e = header.next; e != header; e = e.next) {\n                    if (o.equals(e.element)) {\n                        remove(e);\n                        return true;\n                    }\n                }\n            }\n            return false;\n        }\n````        \n  该方法首先会判断移除的元素是否为null，然后迭代这个链表找到该元素节点，最后调用remove(Entry<E> e)，remove(Entry<E> e)为私有方法，是LinkedList中所有移除方法的基础方法，如下：\n\n````\n    private E remove(Entry<E> e) {\n            if (e == header)\n                throw new NoSuchElementException();\n    \n            //保留被移除的元素：要返回\n            E result = e.element;\n            \n            //将该节点的前一节点的next指向该节点后节点\n            e.previous.next = e.next;\n            //将该节点的后一节点的previous指向该节点的前节点\n            //这两步就可以将该节点从链表从除去：在该链表中是无法遍历到该节点的\n            e.next.previous = e.previous;\n            //将该节点归空\n            e.next = e.previous = null;\n            e.element = null;\n            size--;\n            modCount++;\n            return result;\n        }\n````        \n其他的移除方法：\n\n      clear()： 从此列表中移除所有元素。\n\n      remove()：获取并移除此列表的头（第一个元素）。\n\n      remove(int index)：移除此列表中指定位置处的元素。\n\n      remove(Objec o)：从此列表中移除首次出现的指定元素（如果存在）。\n\n      removeFirst()：移除并返回此列表的第一个元素。\n\n      removeFirstOccurrence(Object o)：从此列表中移除第一次出现的指定元素（从头部到尾部遍历列表时）。\n\n      removeLast()：移除并返回此列表的最后一个元素。\n\n      removeLastOccurrence(Object o)：从此列表中移除最后一次出现的指定元素（从头部到尾部遍历列表时）。\n\n#### 查找方法\n\n      对于查找方法的源码就没有什么好介绍了，无非就是迭代，比对，然后就是返回当前值。\n\n      get(int index)：返回此列表中指定位置处的元素。\n\n      getFirst()：返回此列表的第一个元素。\n\n      getLast()：返回此列表的最后一个元素。\n\n      indexOf(Object o)：返回此列表中首次出现的指定元素的索引，如果此列表中不包含该元素，则返回 -1。\n\n      lastIndexOf(Object o)：返回此列表中最后出现的指定元素的索引，如果此列表中不包含该元素，则返回 -1。\n      \n## Queue\n\nQueue接口定义了队列数据结构，元素是有序的(按插入顺序)，先进先出。Queue接口相关的部分UML类图如下：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404145942.png)\n\n### DeQueue\n\n> DeQueue(Double-ended queue)为接口，继承了Queue接口，创建双向队列，灵活性更强，可以前向或后向迭代，在队头队尾均可心插入或删除元素。它的两个主要实现类是ArrayDeque和LinkedList。\n\n### ArrayDeque （底层使用循环数组实现双向队列）\n\n#### 创建\n````\n    public ArrayDeque() {\n       // 默认容量为16\n       elements = new Object[16];\n    }\n    \n    public ArrayDeque(int numElements) {\n       // 指定容量的构造函数\n       allocateElements(numElements);\n    }\n    private void allocateElements(int numElements) {\n            int initialCapacity = MIN_INITIAL_CAPACITY;// 最小容量为8\n            // Find the best power of two to hold elements.\n            // Tests \"<=\" because arrays aren't kept full.\n            // 如果要分配的容量大于等于8，扩大成2的幂（是为了维护头、尾下标值）；否则使用最小容量8\n            if (numElements >= initialCapacity) {\n                initialCapacity = numElements;\n                initialCapacity |= (initialCapacity >>>  1);\n                initialCapacity |= (initialCapacity >>>  2);\n                initialCapacity |= (initialCapacity >>>  4);\n                initialCapacity |= (initialCapacity >>>  8);\n                initialCapacity |= (initialCapacity >>> 16);\n                initialCapacity++;\n                if (initialCapacity < 0)   // Too many elements, must back off\n                    initialCapacity >>>= 1;// Good luck allocating 2 ^ 30 elements\n            }\n            elements = new Object[initialCapacity];\n        }\n````\n\n#### add操作\n````\n    add(E e) 调用 addLast(E e) 方法：\n    public void addLast(E e) {\n       if (e == null)\n          throw new NullPointerException(\"e == null\");\n       elements[tail] = e; // 根据尾索引，添加到尾端\n       // 尾索引+1，并与数组（length - 1）进行取‘&’运算，因为length是2的幂，所以（length-1）转换为2进制全是1，\n       // 所以如果尾索引值 tail 小于等于（length - 1），那么‘&’运算后仍为 tail 本身；如果刚好比（length - 1）大1时，\n       // ‘&’运算后 tail 便为0（即回到了数组初始位置）。正是通过与（length - 1）进行取‘&’运算来实现数组的双向循环。\n       // 如果尾索引和头索引重合了，说明数组满了，进行扩容。\n       if ((tail = (tail + 1) & (elements.length - 1)) == head)\n          doubleCapacity();// 扩容为原来的2倍\n    }\n\n\n    addFirst(E e) 的实现：\n    public void addFirst(E e) {\n       if (e == null)\n          throw new NullPointerException(\"e == null\");\n       // 此处如果head为0，则-1（1111 1111 1111 1111 1111 1111 1111 1111）与（length - 1）进行取‘&’运算，结果必然是（length - 1），即回到了数组的尾部。\n       elements[head = (head - 1) & (elements.length - 1)] = e;\n       // 如果尾索引和头索引重合了，说明数组满了，进行扩容\n       if (head == tail)\n          doubleCapacity();\n    }\n\n````\n#### remove操作\n````\n    remove()方法最终都会调对应的poll()方法：\n        public E poll() {\n            return pollFirst();\n        }\n        public E pollFirst() {\n            int h = head;\n            @SuppressWarnings(\"unchecked\") E result = (E) elements[h];\n            // Element is null if deque empty\n            if (result == null)\n                return null;\n            elements[h] = null;     // Must null out slot\n            // 头索引 + 1\n            head = (h + 1) & (elements.length - 1);\n            return result;\n        }\n        public E pollLast() {\n            // 尾索引 - 1\n            int t = (tail - 1) & (elements.length - 1);\n            @SuppressWarnings(\"unchecked\") E result = (E) elements[t];\n            if (result == null)\n                return null;\n            elements[t] = null;\n            tail = t;\n            return result;\n        }\n````\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404150054.png)\n\n### PriorityQueue（底层用数组实现堆的结构）\n> \n> 优先队列跟普通的队列不一样，普通队列是一种遵循FIFO规则的队列，拿数据的时候按照加入队列的顺序拿取。 而优先队列每次拿数据的时候都会拿出优先级最高的数据。\n> \n> 优先队列内部维护着一个堆，每次取数据的时候都从堆顶拿数据（堆顶的优先级最高），这就是优先队列的原理。\n> \n\n#### add 添加方法\n````\n    public boolean add(E e) {\n        return offer(e); // add方法内部调用offer方法\n    }\n    public boolean offer(E e) {\n        if (e == null) // 元素为空的话，抛出NullPointerException异常\n            throw new NullPointerException();\n        modCount++;\n        int i = size;\n        if (i >= queue.length) // 如果当前用堆表示的数组已经满了，调用grow方法扩容\n            grow(i + 1); // 扩容\n        size = i + 1; // 元素个数+1\n        if (i == 0) // 堆还没有元素的情况\n            queue[0] = e; // 直接给堆顶赋值元素\n        else // 堆中已有元素的情况\n            siftUp(i, e); // 重新调整堆，从下往上调整，因为新增元素是加到最后一个叶子节点\n        return true;\n    }\n    private void siftUp(int k, E x) {\n        if (comparator != null)  // 比较器存在的情况下\n            siftUpUsingComparator(k, x); // 使用比较器调整\n        else // 比较器不存在的情况下\n            siftUpComparable(k, x); // 使用元素自身的比较器调整\n    }\n    private void siftUpUsingComparator(int k, E x) {\n        while (k > 0) { // 一直循环直到父节点还存在\n            int parent = (k - 1) >>> 1; // 找到父节点索引，等同于（k - 1）/ 2\n            Object e = queue[parent]; // 获得父节点元素\n            // 新元素与父元素进行比较，如果满足比较器结果，直接跳出，否则进行调整\n            if (comparator.compare(x, (E) e) >= 0) \n                break;\n            queue[k] = e; // 进行调整，新位置的元素变成了父元素\n            k = parent; // 新位置索引变成父元素索引，进行递归操作\n        }\n        queue[k] = x; // 新添加的元素添加到堆中\n    }\n````\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404150149.png)\n\n#### poll，出队方法\n````\n    public E poll() {\n        if (size == 0)\n            return null;\n        int s = --size; // 元素个数-1\n        modCount++;\n        E result = (E) queue[0]; // 得到堆顶元素\n        E x = (E) queue[s]; // 最后一个叶子节点\n        queue[s] = null; // 最后1个叶子节点置空\n        if (s != 0)\n            siftDown(0, x); // 从上往下调整，因为删除元素是删除堆顶的元素\n        return result;\n    }\n    private void siftDown(int k, E x) {\n        if (comparator != null) // 比较器存在的情况下\n            siftDownUsingComparator(k, x); // 使用比较器调整\n        else // 比较器不存在的情况下\n            siftDownComparable(k, x); // 使用元素自身的比较器调整\n    }\n    private void siftDownUsingComparator(int k, E x) {\n        int half = size >>> 1; // 只需循环节点个数的一般即可\n        while (k < half) {\n            int child = (k << 1) + 1; // 得到父节点的左子节点索引，即（k * 2）+ 1\n            Object c = queue[child]; // 得到左子元素\n            int right = child + 1; // 得到父节点的右子节点索引\n            if (right < size &&\n                comparator.compare((E) c, (E) queue[right]) > 0) // 左子节点跟右子节点比较，取更大的值\n                c = queue[child = right];\n            if (comparator.compare(x, (E) c) <= 0)  // 然后这个更大的值跟最后一个叶子节点比较\n                break;\n            queue[k] = c; // 新位置使用更大的值\n            k = child; // 新位置索引变成子元素索引，进行递归操作\n        }\n        queue[k] = x; // 最后一个叶子节点添加到合适的位置\n    }\n````\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404150228.png)#### remove，删除队列元素\n````\n    public boolean remove(Object o) {\n        int i = indexOf(o); // 找到数据对应的索引\n        if (i == -1) // 不存在的话返回false\n            return false;\n        else { // 存在的话调用removeAt方法，返回true\n            removeAt(i);\n            return true;\n        }\n    }\n    private E removeAt(int i) {\n        modCount++;\n        int s = --size; // 元素个数-1\n        if (s == i) // 如果是删除最后一个叶子节点\n            queue[i] = null; // 直接置空，删除即可，堆还是保持特质，不需要调整\n        else { // 如果是删除的不是最后一个叶子节点\n            E moved = (E) queue[s]; // 获得最后1个叶子节点元素\n            queue[s] = null; // 最后1个叶子节点置空\n            siftDown(i, moved); // 从上往下调整\n            if (queue[i] == moved) { // 如果从上往下调整完毕之后发现元素位置没变，从下往上调整\n                siftUp(i, moved); // 从下往上调整\n                if (queue[i] != moved)\n                    return moved;\n            }\n        }\n        return null;\n    }\n````\n\n先执行 siftDown() 下滤过程：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404150304.png)\n\n再执行 siftUp() 上滤过程：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404150353.png)\n## 总结和同步的问题\n\n1、jdk内置的优先队列PriorityQueue内部使用一个堆维护数据，每当有数据add进来或者poll出去的时候会对堆做从下往上的调整和从上往下的调整。\n\n2、PriorityQueue不是一个线程安全的类，如果要在多线程环境下使用，可以使用 PriorityBlockingQueue 这个优先阻塞队列。其中add、poll、remove方法都使用 ReentrantLock 锁来保持同步，take() 方法中如果元素为空，则会一直保持阻塞。\n\n## 参考文章\n\nhttp://cmsblogs.com/?p=155 \n\nhttps://www.jianshu.com/p/0e84b8d3606c\n\nhttps://blog.csdn.net/Faker_Wang/article/details/80923155\n\nhttps://blog.csdn.net/m0_37869177/article/details/88847569\n\nhttps://www.iteye.com/blog/shmilyaw-hotmail-com-1825171\n\nhttps://blog.csdn.net/weixin_36378917/article/details/81812210\n\n\n"
  },
  {
    "path": "docs/Java/collection/Java集合详解：TreeMap和红黑树.md",
    "content": "# 目录\n  * [什么是红黑树](#什么是红黑树)\n    * [定义](#定义)\n    * [实践](#实践)\n      * [红黑树操作](#红黑树操作)\n        * [插入操作](#插入操作)\n        * [删除操作](#删除操作)\n      * [红黑树实现](#红黑树实现)\n        * [插入](#插入)\n        * [删除节点](#删除节点)\n    * [3.总结](#3总结)\n  * [参考文章](#参考文章)\n\n\n本文参考多篇优质技术博客，参考文章请在文末查看\n\n《Java集合详解系列》是我在完成夯实Java基础篇的系列博客后准备开始整理的新系列文章。\n为了更好地诠释知识点，形成体系文章，本系列文章整理了很多优质的博客内容，如有侵权请联系我，一定删除。\n\n这些文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star、fork哈\n\n本系列文章将整理于我的个人博客：\n\n> www.how2playlife.com\n\n## 什么是红黑树\n\n首先，什么是红黑树呢？ 红黑树是一种“平衡的”二叉查找树，它是一种经典高效的算法，能够保证在最坏的情况下动态集合操作的时间为O（lgn）。红黑树每个节点包含5个域，分别为color,key,left,right和p。 color是在每个节点上增加的一个存储位表示节点的颜色，可以是RED或者BLACK。key为结点中的value值，left,right为该结点的左右孩子指针，没有的话为NIL，p是一个指针，是指向该节的父节点。如下图（来自维基百科）表示就是一颗红黑树，NIL为指向外结点的指针。（外结点视为没有key的结点）\n\n 红黑树有什么性质呢？一般称为红黑性质，有以下五点：\n\n 1）每个结点或者是红的或者是黑的；\n\n 2）根结点是黑的；\n\n 3）每个叶结点（NIL）是黑的；\n\n 4）如果一个结点是红的，则它的两个孩子都是黑的；\n\n 5）对每个结点，从该结点到其他其子孙结点的所有路径上包含相同数目的黑结点。\n\n为了后面的分析，我们还得知道以下知识点。\n\n（1）黑高度：从某个结点x出发（不包括该结点）到达一个叶结点的任意一条路径上，黑色结点的个数称为该结点x的黑高度。\n\n（2）一颗有n个内结点的红黑树的高度至多为2lg(n+1)。 （内结点视为红黑树中带关键字的结点）\n\n （3）包含n个内部节点的红黑树的高度是 O(log(n))。\n\n### 定义\n\n红黑树是特殊的二叉查找树，又名R-B树(RED-BLACK-TREE)，由于红黑树是特殊的二叉查找树，即红黑树具有了二叉查找树的特性，而且红黑树还具有以下特性：\n\n*   **1.每个节点要么是黑色要么是红色**\n\n*   **2.根节点是黑色**\n\n*   **3.每个叶子节点是黑色，并且为空节点(还有另外一种说法就是，每个叶子结点都带有两个空的黑色结点（被称为黑哨兵），如果一个结点n的只有一个左孩子，那么n的右孩子是一个黑哨兵；如果结点n只有一个右孩子，那么n的左孩子是一个黑哨兵。)**\n\n*   **4.如果一个节点是红色，则它的子节点必须是黑色**\n\n*   **5.从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。**\n\n有几点需要注意的是：\n\n1.特性3中指定红黑树的每个叶子节点都是空节点，但是在Java实现中红黑树将使用null代表空节点，因此遍历红黑树时看不到黑色的叶子节点，反而见到的叶子节点是红色的\n\n2.特性4保证了从根节点到叶子节点的最长路径的长度不会超过任何其他路径的两倍，例如黑色高度为3的红黑树，其最短路径(路径指的是根节点到叶子节点)是2(黑节点-黑节点-黑节点)，其最长路径为4(黑节点-红节点-黑节点-红节点-黑节点)。\n\n### 实践\n\n#### 红黑树操作\n\n##### 插入操作\n\n首先红黑树在插入节点的时，我们设定插入节点的颜色为**红色**,如果插入的是黑色节点，必然会违背特性5，即改变了红黑树的黑高度，如下插入红色结点又存在着几种情况：\n\n1.**黑父**\n\n如图所示，这种情况不会破坏红黑树的特性，即不需要任何处理\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404155235.png)\n\n2.**红父**\n\n当其父亲为红色时又会存在以下的情况\n\n**红叔**\n\n红叔的情况，其实相对来说比较简单的，如下图所示，只需要通过修改父、叔的颜色为黑色，祖的颜色为红色，而且回去递归的检查祖节点即可\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404155342.png)\n**黑叔**\n\n黑叔的情况有如下几种，这几种情况下是不能够通过修改颜色达到平衡的效果，因此会通过旋转的操作，红黑树种有两种旋转操作，左旋和右旋(现在存在的疑问，什么时候使用到左旋，什么时候使用到右旋)\n\n*   Case 1:[先右旋，在改变颜色(根节点必须为黑色，其两个子节点为红色，叔节点不用改变)],如下图所示，注意省略黑哨兵节点\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404155411.png)\n\n*   Case 2:[先左旋变成Case1中的情况，再右旋，最后改变颜色(根节点必须为黑色，其两个子节点为红色，叔节点不用改变)],如下图所示，注意省略黑哨兵节点\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404155517.png)\n*   Case 3:[先左旋，最后改变颜色(根节点必须为黑色，其两个子节点为红色，叔节点不用改变)],如下图所示，注意省略黑哨兵节点\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404155545.png)\n\n*   Case 4:[先右旋变成Case 3的情况，再左旋，最后改变颜色(根节点必须为黑色，其两个子节点为红色，叔节点不用改变)],如下图所示，注意省略黑哨兵节点\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404155622.png)\n\n以上就是红黑树新增节点所有可能的操作，下面会介绍红黑树中的删除操作\n\n##### 删除操作\n\n删除操作相比于插入操作情况更加复杂，删除一个节点可以大致分为三种情况：\n\n*   1.删除的节点没有孩子节点，即当前节点为叶子节点，这种可以直接删除\n\n*   2.删除的节点有一个孩子节点，这种需要删除当前节点，并使用其孩子节点顶替上来\n\n*   3.删除的节点有两个孩子节点，这种需要先找到其后继节点(树中大于节点的最小的元素);然后将其后继节点的内容复制到该节点上，其后继节点就相当于该节点的替身， 需要注意的是其后继节点一定不会有两个孩子节点(这点应该很好理解，如果后继节点有左孩子节点，那么当前的后继节点肯定不是最小的，说明后继节点只能存在没有孩子节点或者只有一个右孩子节点)，即这样就将问题转换成为1,2中的方式。\n\n在讲述修复操作之前，首先需要明白几点，\n\n1.对于红黑树而言，单支节点的情况只有如下图所示的一种情况，即为当前节点为黑色，其孩子节点为红色,(1.假设当前节点为红色，其两个孩子节点必须为黑色，2.若有孙子节点，则必为黑色，导致黑子数量不等，而红黑树不平衡)\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404155656.png)\n\n2.由于红黑树是特殊的二叉查找树，它的删除和二叉查找树类型，真正的删除点即为删除点A的中序遍历的后继(前继也可以)，通过红黑树的特性可知这个后继必然最多只能有一个孩子，其这个孩子节点必然是右孩子节点，从而为单支情况(即这个后继节点只能有一个红色孩子或没有孩子)\n\n下面将详细介绍，在执行删除节点操作之后，将通过修复操作使得红黑树达到平衡的情况。\n\n*   Case 1:被删除的节点为红色，则这节点必定为叶子节点(首先这里的被删除的节点指的是真正删除的节点，通过上文得知的真正删除的节点要么是节点本身，要么是其后继节点，若是节点本身则必须为叶子节点，不为叶子节点的话其会有左右孩子，则真正删除的是其右孩子树上的最小值，若是后继节点，也必须为叶子节点，若不是则其也会有左右孩子，从而和2中相违背)，这种情况下删除红色叶节点就可以了，不用进行其他的操作了。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404155743.png)\n\n*   Case 2:被删除的节点是黑色，其子节点是红色，将其子节点顶替上来并改变其颜色为黑色，如下图所示\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404155811.png)\n\n*   Case 3:被删除的节点是黑色，其子节点也是黑色，将其子节点顶替上来，变成了双黑的问题，此时有以下情况\n\n*   Case 1:新节点的兄弟节点为**红色**，此时若新节点在左边则做左旋操作，否则做右旋操作，之后再将其父节点颜色改变为红色，兄弟节点\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404155834.png)\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404155859.png)\n\n从图中可以看出，操作之后红黑树并未达到平衡状态，而是变成的**黑兄**的情况\n\n*   Case 2:新节点的兄弟节点为**黑色**,此时可能有如下情况\n\n*   红父二黑侄：将父节点变成黑色，兄弟节点变成红色，新节点变成黑色即可,如下图所示\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404155933.png)\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404155959.png)\n*   黑父二黑侄：将父节点变成新节点的颜色，新节点变成黑色，兄弟节点染成红色，还需要继续以父节点为判定点继续判断,如下图所示\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404160015.png)\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404160051.png)\n\n*   红侄：\n\n情况一:新节点在右子树，红侄在兄弟节点左子树，此时的操作为右旋，并将兄弟节点变为父亲的颜色，父亲节点变为黑色，侄节点变为黑色，如下图所示\n\n![](https://upload-images.jianshu.io/upload_images/4761309-62ecd431cc7a3e64.png?imageMogr2/auto-orient/strip|imageView2/2/w/895/format/webp)\n\n情况二:新节点在右子树，红侄在兄弟节点右子树，此时的操作为先左旋，后右旋并将侄节点变为父亲的颜色，父节点变为黑色，如下图所示\n\n![](https://upload-images.jianshu.io/upload_images/4761309-b5e0ada3d870dcf6.png?imageMogr2/auto-orient/strip|imageView2/2/w/879/format/webp)\n\n情况三：新节点在左子树，红侄在兄弟节点左子树,此时的操作为先右旋在左旋并将侄节点变为父亲的颜色，父亲节点变为黑色，如下图所示\n\n![](https://upload-images.jianshu.io/upload_images/4761309-bd3e2c1efdc7a147.png?imageMogr2/auto-orient/strip|imageView2/2/w/885/format/webp)\n\n情况四：新节点在右子树，红侄在兄弟节点右子树,此时的操作为左旋，并将兄弟节点变为父节点的颜色，父亲节点变为黑色，侄节点变为黑色，如下图所示\n\n![](https://upload-images.jianshu.io/upload_images/4761309-9f34c34f7d02da29.png?imageMogr2/auto-orient/strip|imageView2/2/w/879/format/webp)\n\n#### 红黑树实现\n\n如下是使用JAVA代码实现红黑树的过程，主要包括了插入、删除、左旋、右旋、遍历等操作\n\n##### 插入\n\n````\n/* 插入一个节点\n * @param node\n */\nprivate void insert(RBTreeNode<T> node){\n    int cmp;\n    RBTreeNode<T> root = this.rootNode;\n    RBTreeNode<T> parent = null;\n\n    //定位节点添加到哪个父节点下\n    while(null != root){\n        parent = root;\n        cmp = node.key.compareTo(root.key);\n        if (cmp < 0){\n            root = root.left;\n        } else {\n            root = root.right;\n        }\n    }\n\n    node.parent = parent;\n    //表示当前没一个节点，那么就当新增的节点为根节点\n    if (null == parent){\n        this.rootNode = node;\n    } else {\n        //找出在当前父节点下新增节点的位置\n        cmp = node.key.compareTo(parent.key);\n        if (cmp < 0){\n            parent.left = node;\n        } else {\n            parent.right = node;\n        }\n    }\n\n    //设置插入节点的颜色为红色\n    node.color = COLOR_RED;\n\n    //修正为红黑树\n    insertFixUp(node);\n}\n\n/**\n * 红黑树插入修正\n * @param node\n */\nprivate void insertFixUp(RBTreeNode<T> node){\n    RBTreeNode<T> parent,gparent;\n    //节点的父节点存在并且为红色\n    while( ((parent = getParent(node)) != null) && isRed(parent)){\n        gparent = getParent(parent);\n\n        //如果其祖父节点是空怎么处理\n        // 若父节点是祖父节点的左孩子\n        if(parent == gparent.left){\n            RBTreeNode<T> uncle = gparent.right;\n            if ((null != uncle) && isRed(uncle)){\n                setColorBlack(uncle);\n                setColorBlack(parent);\n                setColorRed(gparent);\n                node = gparent;\n                continue;\n            }\n\n            if (parent.right == node){\n                RBTreeNode<T> tmp;\n                leftRotate(parent);\n                tmp = parent;\n                parent = node;\n                node = tmp;\n            }\n\n            setColorBlack(parent);\n            setColorRed(gparent);\n            rightRotate(gparent);\n        } else {\n            RBTreeNode<T> uncle = gparent.left;\n            if ((null != uncle) && isRed(uncle)){\n                setColorBlack(uncle);\n                setColorBlack(parent);\n                setColorRed(gparent);\n                node = gparent;\n                continue;\n            }\n\n            if (parent.left == node){\n                RBTreeNode<T> tmp;\n                rightRotate(parent);\n                tmp = parent;\n                parent = node;\n                node = tmp;\n            }\n\n            setColorBlack(parent);\n            setColorRed(gparent);\n            leftRotate(gparent);\n        }\n    }\n    setColorBlack(this.rootNode);\n}\n\n````\n\n插入节点的操作主要分为以下几步：\n\n*   1.定位：即遍历整理红黑树，确定添加的位置，如上代码中insert方法中就是在找到添加的位置\n\n*   2.修复：这也就是前面介绍的，添加元素后可能会使得红黑树不在满足其特性，这时候需要通过变色、旋转来调整红黑树，也就是如上代码中insertFixUp方法\n\n##### 删除节点\n\n如下为删除节点的代码\n\n````\nprivate void remove(RBTreeNode<T> node){\n    RBTreeNode<T> child,parent;\n    boolean color;\n    //被删除节点左右孩子都不为空的情况\n    if ((null != node.left) && (null != node.right)){\n\n        //获取到被删除节点的后继节点\n        RBTreeNode<T> replace = node;\n\n        replace = replace.right;\n        while(null != replace.left){\n            replace = replace.left;\n        }\n\n        //node节点不是根节点\n        if (null != getParent(node)){\n            //node是左节点\n            if (getParent(node).left == node){\n                getParent(node).left = replace;\n            } else {\n                getParent(node).right = replace;\n            }\n        } else {\n            this.rootNode = replace;\n        }\n\n        child = replace.right;\n        parent = getParent(replace);\n        color = getColor(replace);\n\n        if (parent == node){\n            parent = replace;\n        } else {\n            if (null != child){\n                setParent(child,parent);\n            }\n            parent.left = child;\n\n            replace.right = node.right;\n            setParent(node.right, replace);\n        }\n\n        replace.parent = node.parent;\n        replace.color = node.color;\n        replace.left = node.left;\n        node.left.parent = replace;\n        if (color == COLOR_BLACK){\n            removeFixUp(child,parent);\n        }\n\n        node = null;\n        return;\n    }\n\n    if (null != node.left){\n        child = node.left;\n    } else {\n        child = node.right;\n    }\n\n    parent = node.parent;\n    color = node.color;\n    if (null != child){\n        child.parent = parent;\n    }\n\n    if (null != parent){\n        if (parent.left == node){\n            parent.left = child;\n        } else {\n            parent.right = child;\n        }\n    } else {\n        this.rootNode = child;\n    }\n\n    if (color == COLOR_BLACK){\n        removeFixUp(child, parent);\n    }\n    node = null;\n}\n\n/**\n * 删除修复\n * @param node\n * @param parent\n */\nprivate void removeFixUp(RBTreeNode<T> node, RBTreeNode<T> parent){\n    RBTreeNode<T> other;\n    //node不为空且为黑色，并且不为根节点\n    while ((null == node || isBlack(node)) && (node != this.rootNode) ){\n        //node是父节点的左孩子\n        if (node == parent.left){\n            //获取到其右孩子\n            other = parent.right;\n            //node节点的兄弟节点是红色\n            if (isRed(other)){\n                setColorBlack(other);\n                setColorRed(parent);\n                leftRotate(parent);\n                other = parent.right;\n            }\n\n            //node节点的兄弟节点是黑色，且兄弟节点的两个孩子节点也是黑色\n            if ((other.left == null || isBlack(other.left)) &&\n                    (other.right == null || isBlack(other.right))){\n                setColorRed(other);\n                node = parent;\n                parent = getParent(node);\n            } else {\n                //node节点的兄弟节点是黑色，且兄弟节点的右孩子是红色\n                if (null == other.right || isBlack(other.right)){\n                    setColorBlack(other.left);\n                    setColorRed(other);\n                    rightRotate(other);\n                    other = parent.right;\n                }\n                //node节点的兄弟节点是黑色，且兄弟节点的右孩子是红色，左孩子是任意颜色\n                setColor(other, getColor(parent));\n                setColorBlack(parent);\n                setColorBlack(other.right);\n                leftRotate(parent);\n                node = this.rootNode;\n                break;\n            }\n        } else {\n            other = parent.left;\n            if (isRed(other)){\n                setColorBlack(other);\n                setColorRed(parent);\n                rightRotate(parent);\n                other = parent.left;\n            }\n\n            if ((null == other.left || isBlack(other.left)) &&\n                    (null == other.right || isBlack(other.right))){\n                setColorRed(other);\n                node = parent;\n                parent = getParent(node);\n            } else {\n                if (null == other.left || isBlack(other.left)){\n                    setColorBlack(other.right);\n                    setColorRed(other);\n                    leftRotate(other);\n                    other = parent.left;\n                }\n\n                setColor(other,getColor(parent));\n                setColorBlack(parent);\n                setColorBlack(other.left);\n                rightRotate(parent);\n                node = this.rootNode;\n                break;\n            }\n        }\n    }\n    if (node!=null)\n        setColorBlack(node);\n}\n\n````\n\n删除节点主要分为几种情况去做对应的处理：\n\n*   1.删除节点,按照如下三种情况去删除节点\n    *   1.真正删除的节点没有子节点\n    *   2.真正删除的节点有一个子节点\n    *   3.正在删除的节点有两个子节点\n*   2.修复红黑树的特性，如代码中调用removeFixUp方法修复红黑树的特性。\n\n### 3.总结\n\n以上主要介绍了红黑树的一些特性，包括一些操作详细的解析了里面的过程，写的时间比较长，感觉确实比较难理清楚。后面会持续的理解更深入，若有存在问题的地方，请指正。\n\n## 参考文章\n\n[红黑树(五)之 Java的实现](https://link.jianshu.com/?t=http://www.cnblogs.com/skywang12345/p/3624343.html/)\n\n[通过分析 JDK 源代码研究 TreeMap 红黑树算法实现](https://link.jianshu.com/?t=https://www.ibm.com/developerworks/cn/java/j-lo-tree/index.html?ca=drs-)\n\n[红黑树](https://link.jianshu.com/?t=http://blog.csdn.net/eric491179912/article/details/6179908)\n\n[（图解）红黑树的插入和删除](https://link.jianshu.com/?t=http://www.cnblogs.com/deliver/p/5392768.html)\n\n[红黑树深入剖析及Java实现](https://link.jianshu.com/?t=https://zhuanlan.zhihu.com/p/24367771)\n\n\n"
  },
  {
    "path": "docs/Java/collection/Java集合详解：一文读懂ArrayList,Vector与Stack使用方法和实现原理.md",
    "content": "# 目录\n  * [ArrayList](#arraylist)\n    * [ArrayList概述](#arraylist概述)\n    * [ArrayList的继承关系](#arraylist的继承关系)\n    * [底层数据结构](#底层数据结构)\n    * [增删改查](#增删改查)\n    * [modCount](#modcount)\n    * [初始容量和扩容方式](#初始容量和扩容方式)\n    * [线程安全](#线程安全)\n  * [Vector](#vector)\n    * [Vector简介](#vector简介)\n    * [增删改查](#增删改查-1)\n    * [初始容量和扩容](#初始容量和扩容)\n    * [线程安全](#线程安全-1)\n  * [Stack](#stack)\n    * [三个集合类之间的区别](#三个集合类之间的区别)\n    * [参考文章](#参考文章)\n\n\n本文参考多篇优质技术博客，参考文章请在文末查看\n\n《Java集合详解系列》是我在完成夯实Java基础篇的系列博客后准备开始整理的新系列文章。\n为了更好地诠释知识点，形成体系文章，本系列文章整理了很多优质的博客内容，如有侵权请联系我，一定删除。\n\n这些文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star、fork哈\n\n本系列文章将整理于我的个人博客：\n\n> www.how2playlife.com\n\n\n    //一般讨论集合类无非就是。这里的两种数组类型更是如此\n    // 1底层数据结构\n    // 2增删改查方式\n    // 3初始容量，扩容方式，扩容时机。\n    // 4线程安全与否\n    // 5是否允许空，是否允许重复，是否有序 \n\n## ArrayList\n\n### ArrayList概述\n\n  ArrayList是实现List接口的动态数组，所谓动态就是它的大小是可变的。实现了所有可选列表操作，并允许包括 null 在内的所有元素。除了实现 List 接口外，此类还提供一些方法来操作内部用来存储列表的数组的大小。\n\n  每个ArrayList实例都有一个容量，该容量是指用来存储列表元素的数组的大小。默认初始容量为10。随着ArrayList中元素的增加，它的容量也会不断的自动增长。\n\n  在每次添加新的元素时，ArrayList都会检查是否需要进行扩容操作，扩容操作带来数据向新数组的重新拷贝，所以如果我们知道具体业务数据量，在构造ArrayList时可以给ArrayList指定一个初始容量，这样就会减少扩容时数据的拷贝问题。当然在添加大量元素前，应用程序也可以使用ensureCapacity操作来增加ArrayList实例的容量，这可以减少递增式再分配的数量。\n\n  注意，ArrayList实现不是同步的。如果多个线程同时访问一个ArrayList实例，而其中至少一个线程从结构上修改了列表，那么它必须保持外部同步。所以为了保证同步，最好的办法是在创建时完成，以防止意外对列表进行不同步的访问：\n\n        List list = Collections.synchronizedList(new ArrayList(...)); \n        \n### ArrayList的继承关系\n\nArrayList继承AbstractList抽象父类，实现了List接口（规定了List的操作规范）、RandomAccess（可随机访问）、Cloneable（可拷贝）、Serializable（可序列化）。\n\n![](https://img-blog.csdn.net/2018081819553095?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8zNjM3ODkxNw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)\n\n ### 底层数据结构\n\n ArrayList的底层是一个object数组，并且由trasient修饰。\n\n    //transient Object[] elementData; //\n\n\nnon-private to simplify nested class access\n//ArrayList底层数组不会参与序列化，而是使用另外的序列化方式。\n\n//使用writeobject方法进行序列化,具体为什么这么做欢迎查看我之前的关于序列化的文章\n\n//总结一下就是只复制数组中有值的位置，其他未赋值的位置不进行序列化，可以节省空间。\n\n````\n    //        private void writeObject(java.io.ObjectOutputStream s)\n    //        throws java.io.IOException{\n    //            // Write out element count, and any hidden stuff\n    //            int expectedModCount = modCount;\n    //            s.defaultWriteObject();\n    //\n    //            // Write out size as capacity for behavioural compatibility with clone()\n    //            s.writeInt(size);\n    //\n    //            // Write out all elements in the proper order.\n    //            for (int i=0; i<size; i++) {\n    //                s.writeObject(elementData[i]);\n    //            }\n    //\n    //            if (modCount != expectedModCount) {\n    //                throw new ConcurrentModificationException();\n    //            }\n    //        }\n\n````\n### 增删改查\n\n    //增删改查\n\n添加元素时，首先判断索引是否合法，然后检测是否需要扩容，最后使用System.arraycopy方法来完成数组的复制。\n\n这个方法无非就是使用System.arraycopy()方法将C集合(先准换为数组)里面的数据复制到elementData数组中。这里就稍微介绍下System.arraycopy()，因为下面还将大量用到该方法\n\n。该方法的原型为：\n````\n    public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)。\n````\n它的根本目的就是进行数组元素的复制。即从指定源数组中复制一个数组，复制从指定的位置开始，到目标数组的指定位置结束。\n\n将源数组src从srcPos位置开始复制到dest数组中，复制长度为length，数据从dest的destPos位置开始粘贴。\n````\n    //        public void add(int index, E element) {\n    //            rangeCheckForAdd(index);\n    //\n    //            ensureCapacityInternal(size + 1);  // Increments modCount!!\n    //            System.arraycopy(elementData, index, elementData, index + 1,\n    //                    size - index);\n    //            elementData[index] = element;\n    //            size++;\n    //        }\n    //\n````\n删除元素时，同样判断索引是否和法，删除的方式是把被删除元素右边的元素左移，方法同样是使用System.arraycopy进行拷贝。\n````\n    //        public E remove(int index) {\n    //            rangeCheck(index);\n    //\n    //            modCount++;\n    //            E oldValue = elementData(index);\n    //\n    //            int numMoved = size - index - 1;\n    //            if (numMoved > 0)\n    //                System.arraycopy(elementData, index+1, elementData, index,\n    //                        numMoved);\n    //            elementData[--size] = null; // clear to let GC do its work\n    //\n    //            return oldValue;\n    //        }\n````\nArrayList提供一个清空数组的办法，方法是将所有元素置为null，这样就可以让GC自动回收掉没有被引用的元素了。\n````\n    //\n    //        /**\n    //         * Removes all of the elements from this list.  The list will\n    //         * be empty after this call returns.\n    //         */\n    //        public void clear() {\n    //            modCount++;\n    //\n    //            // clear to let GC do its work\n    //            for (int i = 0; i < size; i++)\n    //                elementData[i] = null;\n    //\n    //            size = 0;\n    //        }\n````\n修改元素时，只需要检查下标即可进行修改操作。\n````\n    //        public E set(int index, E element) {\n    //            rangeCheck(index);\n    //\n    //            E oldValue = elementData(index);\n    //            elementData[index] = element;\n    //            return oldValue;\n    //        }\n    //\n    //        public E get(int index) {\n    //            rangeCheck(index);\n    //\n    //            return elementData(index);\n    //        }\n    //\n````\n上述方法都使用了rangeCheck方法，其实就是简单地检查下标而已。\n````\n    //        private void rangeCheck(int index) {\n    //            if (index >= size)\n    //                throw new IndexOutOfBoundsException(outOfBoundsMsg(index));\n    //        }\n````\n### modCount\n````\n    //        protected transient int modCount = 0;\n````\n由以上代码可以看出，在一个迭代器初始的时候会赋予它调用这个迭代器的对象的mCount，如何在迭代器遍历的过程中，一旦发现这个对象的mcount和迭代器中存储的mcount不一样那就抛异常 \n\n> 好的，下面是这个的完整解释 \n> Fail-Fast 机制 \n> 我们知道 java.util.ArrayList 不是线程安全的，ArrayList，那么将抛出ConcurrentModificationException，这就是所谓fail-fast策略。\n>\n> 这一策略在源码中的实现是通过 modCount 域，modCount 顾名思义就是修改次数，对ArrayList 内容的修改都将增加这个值，那么在迭代器初始化过程中会将这个值赋给迭代器的 expectedModCount。\n>\n> 在迭代过程中，判断 modCount 跟 expectedModCount 是否相等，如果不相等就表示已经有其他线程修改了 ArrayList。\n>\n> 所以在这里和大家建议，当大家遍历那些非线程安全的数据结构时，尽量使用迭代器\n\n### 初始容量和扩容方式\n\n初始容量是10，下面是扩容方法。\n首先先取\n````\n    //private static final int DEFAULT_CAPACITY = 10;\n    \n    // 扩容发生在add元素时，传入当前元素容量加一\n       public boolean add(E e) {\n        ensureCapacityInternal(size + 1);  // Increments modCount!!\n        elementData[size++] = e;\n        return true;\n    }\n\n\n    这里给出初始化时的数组\n    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};\n    \n    这说明：如果数组还是初始数组，那么最小的扩容大小就是size+1和初始容量中较大的一个，初始容量为10。\n    因为addall方法也会调用该函数，所以此时需要做判断。\n    private void ensureCapacityInternal(int minCapacity) {\n        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {\n            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);\n        }\n    \n        ensureExplicitCapacity(minCapacity);\n    }\n    \n    //开始精确地扩容\n    private void ensureExplicitCapacity(int minCapacity) {\n        modCount++;\n    \n        // overflow-conscious code\n            如果此时扩容容量大于数组长度吗，执行grow，否则不执行。\n        if (minCapacity - elementData.length > 0)\n            grow(minCapacity);\n    }\n````\n真正执行扩容的方法grow\n               \n扩容方式是让新容量等于旧容量的1.5被。\n\n当新容量大于最大数组容量时，执行大数扩容\n````\n    //        private void grow(int minCapacity) {\n    //            // overflow-conscious code\n    //            int oldCapacity = elementData.length;\n    //            int newCapacity = oldCapacity + (oldCapacity >> 1);\n    //            if (newCapacity - minCapacity < 0)\n    //                newCapacity = minCapacity;\n    //            if (newCapacity - MAX_ARRAY_SIZE > 0)\n    //                newCapacity = hugeCapacity(minCapacity);\n    //            // minCapacity is usually close to size, so this is a win:\n    //            elementData = Arrays.copyOf(elementData, newCapacity);\n    //        }\n````\n当新容量大于最大数组长度，有两种情况，一种是溢出，抛异常，一种是没溢出，返回整数的最大值。\n````\n    private static int hugeCapacity(int minCapacity) {\n        if (minCapacity < 0) // overflow\n            throw new OutOfMemoryError();\n        return (minCapacity > MAX_ARRAY_SIZE) ?\n            Integer.MAX_VALUE :\n            MAX_ARRAY_SIZE;\n    }\n````\n\n在这里有一个疑问，为什么每次扩容处理会是1.5倍，而不是2.5、3、4倍呢？通过google查找，发现1.5倍的扩容是最好的倍数。因为一次性扩容太大(例如2.5倍)可能会浪费更多的内存(1.5倍最多浪费33%，而2.5被最多会浪费60%，3.5倍则会浪费71%……)。但是一次性扩容太小，需要多次对数组重新分配内存，对性能消耗比较严重。所以1.5倍刚刚好，既能满足性能需求，也不会造成很大的内存消耗。\n\n  处理这个ensureCapacity()这个扩容数组外，ArrayList还给我们提供了将底层数组的容量调整为当前列表保存的实际元素的大小的功能。它可以通过trimToSize()方法来实现。该方法可以最小化ArrayList实例的存储量。\n````\n    public void trimToSize() {\n        modCount++;\n        int oldCapacity = elementData.length;\n        if (size < oldCapacity) {\n            elementData = Arrays.copyOf(elementData, size);\n        }\n    }\n````\n### 线程安全\n\nArrayList是线程不安全的。在其迭代器iteator中，如果有多线程操作导致modcount改变，会执行fastfail。抛出异常。\n````\n        final void checkForComodification() {\n            if (modCount != expectedModCount)\n                throw new ConcurrentModificationException();\n        }\n````\n## Vector\n\n### Vector简介\n\nVector可以实现可增长的对象数组。与数组一样，它包含可以使用整数索引进行访问的组件。不过，Vector的大小是可以增加或者减小的，以便适应创建Vector后进行添加或者删除操作。\n\nVector实现List接口，继承AbstractList类，所以我们可以将其看做队列，支持相关的添加、删除、修改、遍历等功能。\n\nVector实现RandmoAccess接口，即提供了随机访问功能，提供提供快速访问功能。在Vector我们可以直接访问元素。\n\nVector 实现了Cloneable接口，支持clone()方法，可以被克隆。\n\nvector底层数组不加transient，序列化时会全部复制\n````\n     protected Object[] elementData;\n\n\n \n    //        private void writeObject(java.io.ObjectOutputStream s)\n    //            throws java.io.IOException {\n    //            final java.io.ObjectOutputStream.PutField fields = s.putFields();\n    //            final Object[] data;\n    //            synchronized (this) {\n    //                fields.put(\"capacityIncrement\", capacityIncrement);\n    //                fields.put(\"elementCount\", elementCount);\n    //                data = elementData.clone();\n    //            }\n    //            fields.put(\"elementData\", data);\n    //            s.writeFields();\n    //        }\n````\nVector除了iterator外还提供Enumeration枚举方法，不过现在比较过时。\n````\n    //        public Enumeration<E> elements() {\n    //            return new Enumeration<E>() {\n    //                int count = 0;\n    //\n    //                public boolean hasMoreElements() {\n    //                    return count < elementCount;\n    //                }\n    //\n    //                public E nextElement() {\n    //                    synchronized (Vector.this) {\n    //                        if (count < elementCount) {\n    //                            return elementData(count++);\n    //                        }\n    //                    }\n    //                    throw new NoSuchElementException(\"Vector Enumeration\");\n    //                }\n    //            };\n    //        }\n    //\n````\n\n### 增删改查\n\nvector的增删改查既提供了自己的实现，也继承了abstractList抽象类的部分方法。\n下面的方法是vector自己实现的。\n````\n    //\n    //    public synchronized E elementAt(int index) {\n    //        if (index >= elementCount) {\n    //            throw new ArrayIndexOutOfBoundsException(index + \" >= \" + elementCount);\n    //        }\n    //\n    //        return elementData(index);\n    //    }\n    //\n    //\n    \n    //    public synchronized void setElementAt(E obj, int index) {\n    //        if (index >= elementCount) {\n    //            throw new ArrayIndexOutOfBoundsException(index + \" >= \" +\n    //                    elementCount);\n    //        }\n    //        elementData[index] = obj;\n    //    }\n    //\n\n \n    //    public synchronized void removeElementAt(int index) {\n    //        modCount++;\n    //        if (index >= elementCount) {\n    //            throw new ArrayIndexOutOfBoundsException(index + \" >= \" +\n    //                    elementCount);\n    //        }\n    //        else if (index < 0) {\n    //            throw new ArrayIndexOutOfBoundsException(index);\n    //        }\n    //        int j = elementCount - index - 1;\n    //        if (j > 0) {\n    //            System.arraycopy(elementData, index + 1, elementData, index, j);\n    //        }\n    //        elementCount--;\n    //        elementData[elementCount] = null; /* to let gc do its work */\n    //    }\n\n   \n    //    public synchronized void insertElementAt(E obj, int index) {\n    //        modCount++;\n    //        if (index > elementCount) {\n    //            throw new ArrayIndexOutOfBoundsException(index\n    //                    + \" > \" + elementCount);\n    //        }\n    //        ensureCapacityHelper(elementCount + 1);\n    //        System.arraycopy(elementData, index, elementData, index + 1, elementCount - index);\n    //        elementData[index] = obj;\n    //        elementCount++;\n    //    }\n    //\n    \n    //    public synchronized void addElement(E obj) {\n    //        modCount++;\n    //        ensureCapacityHelper(elementCount + 1);\n    //        elementData[elementCount++] = obj;\n    //    }\n````\n### 初始容量和扩容  \n扩容方式与ArrayList基本一样，但是扩容时不是1.5倍扩容，而是有一个扩容增量。\n\n````\n    //    protected int elementCount;\n    \n    //    protected int capacityIncrement;\n    //\n    //\n    //    }\n    //    public Vector() {\n    //        this(10);\n    //    }\n````\ncapacityIncrement：向量的大小大于其容量时，容量自动增加的量。如果在创建Vector时，指定了capacityIncrement的大小；则，每次当Vector中动态数组容量增加时>，增加的大小都是capacityIncrement。如果容量的增量小于等于零，则每次需要增大容量时，向量的容量将增大一倍。\n\n\n````\n    //        public synchronized void ensureCapacity(int minCapacity) {\n    //            if (minCapacity > 0) {\n    //                modCount++;\n    //                ensureCapacityHelper(minCapacity);\n    //            }\n    //        }\n    //        private void ensureCapacityHelper(int minCapacity) {\n    //            // overflow-conscious code\n    //            if (minCapacity - elementData.length > 0)\n    //                grow(minCapacity);\n    //        }\n    //\n    //        private void grow(int minCapacity) {\n    //            // overflow-conscious code\n    //            int oldCapacity = elementData.length;\n    //            int newCapacity = oldCapacity + ((capacityIncrement > 0) ?\n    //                    capacityIncrement : oldCapacity);\n    //            if (newCapacity - minCapacity < 0)\n    //                newCapacity = minCapacity;\n    //            if (newCapacity - MAX_ARRAY_SIZE > 0)\n    //                newCapacity = hugeCapacity(minCapacity);\n    //            elementData = Arrays.copyOf(elementData, newCapacity);\n    //        }\n\n````\n下面是扩容过程示意图\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404145205.png)\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404145237.png)\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404145305.png)\n\n### 线程安全\n\nvector大部分方法都使用了synchronized修饰符，所以他是线层安全的集合类。\n\n## Stack\n我们最常用的数据结构之一大概就是stack了。在实际的程序执行，方法调用的过程中都离不开stack。那么，在一个成熟的类库里面，它的实现是怎么样的呢？也许平时我们实践的时候也会尝试着去写一个stack的实现玩玩。这里，我们就仔细的分析一下jdk里的详细实现。\n\n# Stack\n\n  如果我们去查jdk的文档，我们会发现stack是在java.util这个包里。它对应的一个大致的类关系图如下：\n\n![](http://dl.iteye.com/upload/attachment/0081/2496/006da63f-388e-3669-b57f-1cdd1909d5f8.jpg)\n\n  通过继承Vector类，Stack类可以很容易的实现他本身的功能。因为大部分的功能在Vector里面已经提供支持了。\n在Java中Stack类表示后进先出（LIFO）的对象堆栈。栈是一种非常常见的数据结构，它采用典型的先进后出的操作方式完成的。\n\n\nStack通过五个操作对Vector进行扩展，允许将向量视为堆栈。这个五个操作如下：\n\n> empty()\n>\n> 测试堆栈是否为空。\n>\n> peek()\n>\n> 查看堆栈顶部的对象，但不从堆栈中移除它。\n>\n> pop()\n>\n> 移除堆栈顶部的对象，并作为此函数的值返回该对象。\n>\n> push(E item)\n>\n> 把项压入堆栈顶部。\n>\n> search(Object o)\n>\n> 返回对象在堆栈中的位置，以 1 为基数。\n\nStack继承Vector，他对Vector进行了简单的扩展：\n````\npublic class Stack<E> extends Vector<E>\n````\n\n  Stack的实现非常简单，仅有一个构造方法，五个实现方法（从Vector继承而来的方法不算与其中），同时其实现的源码非常简单\n````\n    /**\n     * 构造函数\n     */\n    public Stack() {\n    }\n    \n    /**\n     *  push函数：将元素存入栈顶\n     */\n    public E push(E item) {\n        // 将元素存入栈顶。\n        // addElement()的实现在Vector.java中\n        addElement(item);\n    \n        return item;\n    }\n    \n    /**\n     * pop函数：返回栈顶元素，并将其从栈中删除\n     */\n    public synchronized E pop() {\n        E    obj;\n        int    len = size();\n    \n        obj = peek();\n        // 删除栈顶元素，removeElementAt()的实现在Vector.java中\n        removeElementAt(len - 1);\n    \n        return obj;\n    }\n    \n    /**\n     * peek函数：返回栈顶元素，不执行删除操作\n     */\n    public synchronized E peek() {\n        int    len = size();\n    \n        if (len == 0)\n            throw new EmptyStackException();\n        // 返回栈顶元素，elementAt()具体实现在Vector.java中\n        return elementAt(len - 1);\n    }\n    \n    /**\n     * 栈是否为空\n     */\n    public boolean empty() {\n        return size() == 0;\n    }\n    \n    /**\n     *  查找“元素o”在栈中的位置：由栈底向栈顶方向数\n     */\n    public synchronized int search(Object o) {\n        // 获取元素索引，elementAt()具体实现在Vector.java中\n        int i = lastIndexOf(o);\n    \n        if (i >= 0) {\n            return size() - i;\n        }\n        return -1;\n    }\n````\nStack的源码很多都是基于Vector，所以这里不再累述\n\n## 三个集合类之间的区别\n\nArrayList的优缺点\n\n从上面的几个过程总结一下ArrayList的优缺点。ArrayList的优点如下：\n>\n> 1、ArrayList底层以数组实现，是一种随机访问模式，再加上它实现了RandomAccess接口，因此查找也就是get的时候非常快\n>\n> 2、ArrayList在顺序添加一个元素的时候非常方便，只是往数组里面添加了一个元素而已\n\n不过ArrayList的缺点也十分明显：\n>\n> 1、删除元素的时候，涉及到一次元素复制，如果要复制的元素很多，那么就会比较耗费性能\n>\n> 2、插入元素的时候，涉及到一次元素复制，如果要复制的元素很多，那么就会比较耗费性能\n>\n> 因此，ArrayList比较适合顺序添加、随机访问的场景。\n\n \n\nArrayList和Vector的区别\n\n> ArrayList是线程非安全的，这很明显，因为ArrayList中所有的方法都不是同步的，在并发下一定会出现线程安全问题。那么我们想要使用ArrayList并且让它线程安全怎么办？一个方法是用Collections.synchronizedList方法把你的ArrayList变成一个线程安全的List，比如：\n````\n     List<String> synchronizedList = Collections.synchronizedList(list);\n     synchronizedList.add(\"aaa\");\n     synchronizedList.add(\"bbb\");\n     for (int i = 0; i < synchronizedList.size(); i++)\n     {\n         System.out.println(synchronizedList.get(i));\n     }\n````\n另一个方法就是Vector，它是ArrayList的线程安全版本，其实现90%和ArrayList都完全一样，区别在于：\n\n> 1、Vector是线程安全的，ArrayList是线程非安全的\n>\n> 2、Vector可以指定增长因子，如果该增长因子指定了，那么扩容的时候会每次新的数组大小会在原数组的大小基础上加上增长因子；如果不指定增长因子，那么就给原数组大小*2，源代码是这样的：\n````\n    int newCapacity = oldCapacity + ((capacityIncrement > 0) ?\n                                     capacityIncrement : oldCapacity);\n````\n\n## 参考文章\n\nhttps://www.cnblogs.com/williamjie/p/11158523.html\n\nhttps://www.cnblogs.com/shenzhichipingguo/p/10075212.html\n\nhttps://www.cnblogs.com/rnmb/p/6553711.html\n\nhttps://blog.csdn.net/u011419651/article/details/83831156\n\nhttps://www.jianshu.com/p/c4027084ac43\n\n\n"
  },
  {
    "path": "docs/Java/collection/Java集合详解：深入理解LinkedHashMap和LRU缓存.md",
    "content": "# 目录\n  * [LinkedHashMap 概述](#linkedhashmap-概述)\n  * [LinkedHashMap 在 JDK 中的定义](#linkedhashmap-在-jdk-中的定义)\n    * [类结构定义](#类结构定义)\n    * [成员变量定义](#成员变量定义)\n    * [成员方法定义](#成员方法定义)\n    * [基本元素 Entry](#基本元素-entry)\n  * [LinkedHashMap 的构造函数](#linkedhashmap-的构造函数)\n    * [LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)](#linkedhashmapint-initialcapacity-float-loadfactor-boolean-accessorder)\n    * [LinkedHashMap(Map<? extends K, ? extends V> m)](#linkedhashmapmap-extends-k--extends-v-m)\n    * [init 方法](#init-方法)\n  * [LinkedHashMap 的数据结构](#linkedhashmap-的数据结构)\n  * [LinkedHashMap 的快速存取](#linkedhashmap-的快速存取)\n    * [LinkedHashMap 的存储实现 : put(key, vlaue)](#linkedhashmap-的存储实现--putkey-vlaue)\n    * [LinkedHashMap 的扩容操作 : resize()](#linkedhashmap-的扩容操作--resize)\n    * [LinkedHashMap 的读取实现 ：get(Object key)](#linkedhashmap-的读取实现-：getobject-key)\n    * [LinkedHashMap 存取小结](#linkedhashmap-存取小结)\n  * [LinkedHashMap 与 LRU(Least recently used，最近最少使用)算法](#linkedhashmap-与-lruleast-recently-used，最近最少使用算法)\n    * [put操作与标志位accessOrder](#put操作与标志位accessorder)\n    * [get操作与标志位accessOrder](#get操作与标志位accessorder)\n    * [LinkedListMap与LRU小结](#linkedlistmap与lru小结)\n  * [使用LinkedHashMap实现LRU算法](#使用linkedhashmap实现lru算法)\n  * [LinkedHashMap 有序性原理分析](#linkedhashmap-有序性原理分析)\n  * [JDK1.8的改动](#jdk18的改动)\n  * [总结](#总结)\n\n\n本文参考多篇优质技术博客，参考文章请在文末查看\n\n《Java集合详解系列》是我在完成夯实Java基础篇的系列博客后准备开始整理的新系列文章。\n为了更好地诠释知识点，形成体系文章，本系列文章整理了很多优质的博客内容，如有侵权请联系我，一定删除。\n\n这些文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star、fork哈\n\n本系列文章将整理于我的个人博客：\n\n> www.how2playlife.com\n\n\n摘要：\n\n>HashMap和双向链表合二为一即是LinkedHashMap。所谓LinkedHashMap，其落脚点在HashMap，因此更准确地说，它是一个将所有Entry节点链入一个双向链表的HashMap。\n\n >由于LinkedHashMap是HashMap的子类，所以LinkedHashMap自然会拥有HashMap的所有特性。比如，LinkedHashMap的元素存取过程基本与HashMap基本类似，只是在细节实现上稍有不同。当然，这是由LinkedHashMap本身的特性所决定的，因为它额外维护了一个双向链表用于保持迭代顺序。\n\n>此外，LinkedHashMap可以很好的支持LRU算法，笔者在第七节便在LinkedHashMap的基础上实现了一个能够很好支持LRU的结构。\n\n友情提示：\n\n> 　　本文所有关于 LinkedHashMap 的源码都是基于 JDK 1.6 的，不同 JDK 版本之间也许会有些许差异，但不影响我们对 LinkedHashMap 的数据结构、原理等整体的把握和了解。后面会讲解1.8对于LinkedHashMap的改动。\n>\n> 　　由于 LinkedHashMap 是 HashMap 的子类，所以其具有HashMap的所有特性，这一点在源码共用上体现的尤为突出。因此，读者在阅读本文之前，最好对 HashMap 有一个较为深入的了解和回顾，否则很可能会导致事倍功半。可以参考我之前关于hashmap的文章。\n\n## LinkedHashMap 概述\n>\n> 　　笔者曾提到，HashMap 是 Java Collection Framework 的重要成员，也是Map族(如下图所示)中我们最为常用的一种。不过遗憾的是，HashMap是无序的，也就是说，迭代HashMap所得到的元素顺序并不是它们最初放置到HashMap的顺序。\n\n>\n> 　　HashMap的这一缺点往往会造成诸多不便，因为在有些场景中，我们确需要用到一个可以保持插入顺序的Map。庆幸的是，JDK为我们解决了这个问题，它为HashMap提供了一个子类 —— LinkedHashMap。虽然LinkedHashMap增加了时间和空间上的开销，但是它通过维护一个额外的双向链表保证了迭代顺序。\n> 　　\n\n> 　　特别地，该迭代顺序可以是插入顺序，也可以是访问顺序。因此，根据链表中元素的顺序可以将LinkedHashMap分为：保持插入顺序的LinkedHashMap和保持访问顺序的LinkedHashMap，其中LinkedHashMap的默认实现是按插入顺序排序的。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404153000.png)\n\n> 　 本质上，HashMap和双向链表合二为一即是LinkedHashMap。所谓LinkedHashMap，其落脚点在HashMap，因此更准确地说，它是一个将所有Entry节点链入一个双向链表双向链表的HashMap。\n\n> 　 在LinkedHashMapMap中，所有put进来的Entry都保存在如下面第一个图所示的哈希表中，但由于它又额外定义了一个以head为头结点的双向链表(如下面第二个图所示)，因此对于每次put进来Entry，除了将其保存到哈希表中对应的位置上之外，还会将其插入到双向链表的尾部。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404153032.png)\n\n　　更直观地，下图很好地还原了LinkedHashMap的原貌：HashMap和双向链表的密切配合和分工合作造就了LinkedHashMap。特别需要注意的是，next用于维护HashMap各个桶中的Entry链，before、after用于维护LinkedHashMap的双向链表，虽然它们的作用对象都是Entry，但是各自分离，是两码事儿。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404153119.png)\n　　 \n　　其中，HashMap与LinkedHashMap的Entry结构示意图如下图所示：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404153144.png)\n\n　　特别地，由于LinkedHashMap是HashMap的子类，所以LinkedHashMap自然会拥有HashMap的所有特性。比如，==LinkedHashMap也最多只允许一条Entry的键为Null(多条会覆盖)，但允许多条Entry的值为Null。==\n　　\n　　此外，LinkedHashMap 也是 Map 的一个非同步的实现。此外，LinkedHashMap还可以用来实现LRU (Least recently used, 最近最少使用)算法，这个问题会在下文的特别谈到。\n\n## LinkedHashMap 在 JDK 中的定义\n\n### 类结构定义\n\nLinkedHashMap继承于HashMap，其在JDK中的定义为：\n````\npublic class LinkedHashMap<K,V> extends HashMap<K,V>\n    implements Map<K,V> {\n\n    ...\n}\n````\n### 成员变量定义\n\n　　与HashMap相比，LinkedHashMap增加了两个属性用于保证迭代顺序，分别是 双向链表头结点header 和 标志位accessOrder (值为true时，表示按照访问顺序迭代；值为false时，表示按照插入顺序迭代)。\n````\n/**\n * The head of the doubly linked list.\n */\nprivate transient Entry<K,V> header;  // 双向链表的表头元素\n\n/**\n * The iteration ordering method for this linked hash map: <tt>true</tt>\n * for access-order, <tt>false</tt> for insertion-order.\n *\n * @serial\n */\nprivate final boolean accessOrder;  //true表示按照访问顺序迭代，false时表示按照插入顺序 \n````\n### 成员方法定义\n\n从下图我们可以看出，LinkedHashMap中并增加没有额外方法。也就是说，LinkedHashMap与HashMap在操作上大致相同，只是在实现细节上略有不同罢了。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404153251.png)\n### 基本元素 Entry\n\n　　LinkedHashMap采用的hash算法和HashMap相同，但是它重新定义了Entry。LinkedHashMap中的Entry增加了两个指针 before 和 after，它们分别用于维护双向链接列表。特别需要注意的是，next用于维护HashMap各个桶中Entry的连接顺序，before、after用于维护Entry插入的先后顺序的，源代码如下：\n````\nprivate static class Entry<K,V> extends HashMap.Entry<K,V> {\n\n    // These fields comprise the doubly linked list used for iteration.\n    Entry<K,V> before, after;\n\n    Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {\n        super(hash, key, value, next);\n    }\n    ...\n}\n````\n形象地，HashMap与LinkedHashMap的Entry结构示意图如下图所示：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404153428.png)\n\n## LinkedHashMap 的构造函数\nLinkedHashMap 一共提供了五个构造函数，它们都是在HashMap的构造函数的基础上实现的，除了默认空参数构造方法，下面这个构造函数包含了大部分其他构造方法使用的参数，就不一一列举了。\n\n### LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)\n\n该构造函数意在构造一个指定初始容量和指定负载因子的具有指定迭代顺序的LinkedHashMap，其源码如下：\n````\n/**\n     * Constructs an empty <tt>LinkedHashMap</tt> instance with the\n     * specified initial capacity, load factor and ordering mode.\n     *\n     * @param  initialCapacity the initial capacity\n     * @param  loadFactor      the load factor\n     * @param  accessOrder     the ordering mode - <tt>true</tt> for\n     *         access-order, <tt>false</tt> for insertion-order\n     * @throws IllegalArgumentException if the initial capacity is negative\n     *         or the load factor is nonpositive\n     */\n    public LinkedHashMap(int initialCapacity,\n             float loadFactor,\n                         boolean accessOrder) {\n        super(initialCapacity, loadFactor);   // 调用HashMap对应的构造函数\n        this.accessOrder = accessOrder;    // 迭代顺序的默认值\n    }\n````\n初始容量 和负载因子是影响HashMap性能的两个重要参数。同样地，它们也是影响LinkedHashMap性能的两个重要参数。此外，LinkedHashMap 增加了双向链表头结点 header和标志位 accessOrder两个属性用于保证迭代顺序。\n　　\n### LinkedHashMap(Map<? extends K, ? extends V> m)\n\n　　该构造函数意在构造一个与指定 Map 具有相同映射的 LinkedHashMap，其 初始容量不小于 16 (具体依赖于指定Map的大小)，负载因子是 0.75，是 Java Collection Framework 规范推荐提供的，其源码如下：\n````\n    /**\n     * Constructs an insertion-ordered <tt>LinkedHashMap</tt> instance with\n     * the same mappings as the specified map.  The <tt>LinkedHashMap</tt>\n     * instance is created with a default load factor (0.75) and an initial\n     * capacity sufficient to hold the mappings in the specified map.\n     *\n     * @param  m the map whose mappings are to be placed in this map\n     * @throws NullPointerException if the specified map is null\n     */\n    public LinkedHashMap(Map<? extends K, ? extends V> m) {\n        super(m);       // 调用HashMap对应的构造函数\n        accessOrder = false;    // 迭代顺序的默认值\n    }\n````\n### init 方法\n\n从上面的五种构造函数我们可以看出，无论采用何种方式创建LinkedHashMap，其都会调用HashMap相应的构造函数。事实上，不管调用HashMap的哪个构造函数，HashMap的构造函数都会在最后调用一个init()方法进行初始化，只不过这个方法在HashMap中是一个空实现，而在LinkedHashMap中重写了它用于初始化它所维护的双向链表。例如，HashMap的参数为空的构造函数以及init方法的源码如下：\n\n````\n    /**\n     * Constructs an empty <tt>HashMap</tt> with the default initial capacity\n     * (16) and the default load factor (0.75).\n     */\n    public HashMap() {\n        this.loadFactor = DEFAULT_LOAD_FACTOR;\n        threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);\n        table = new Entry[DEFAULT_INITIAL_CAPACITY];\n        init();\n    }\n\n   /**\n     * Initialization hook for subclasses. This method is called\n     * in all constructors and pseudo-constructors (clone, readObject)\n     * after HashMap has been initialized but before any entries have\n     * been inserted.  (In the absence of this method, readObject would\n     * require explicit knowledge of subclasses.)\n     */\n    void init() {\n    }\n````\n\n在LinkedHashMap中，它重写了init方法以便初始化双向列表，源码如下：\n\n````\n /**\n     * Called by superclass constructors and pseudoconstructors (clone,\n     * readObject) before any entries are inserted into the map.  Initializes\n     * the chain.\n     */\n    void init() {\n        header = new Entry<K,V>(-1, null, null, null);\n        header.before = header.after = header;\n    }\n````\n\n因此，我们在创建LinkedHashMap的同时就会不知不觉地对双向链表进行初始化。\n\n## LinkedHashMap 的数据结构\n\n> 本质上，LinkedHashMap = HashMap + 双向链表，也就是说，HashMap和双向链表合二为一即是LinkedHashMap。\n\n> 也可以这样理解，LinkedHashMap 在不对HashMap做任何改变的基础上，给HashMap的任意两个节点间加了两条连线(before指针和after指针)，使这些节点形成一个双向链表。\n\n> 在LinkedHashMapMap中，所有put进来的Entry都保存在HashMap中，但由于它又额外定义了一个以head为头结点的空的双向链表，因此对于每次put进来Entry还会将其插入到双向链表的尾部。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404153615.png)\n\n## LinkedHashMap 的快速存取\n\n　　我们知道，在HashMap中最常用的两个操作就是：put(Key,Value) 和 get(Key)。同样地，在 LinkedHashMap 中最常用的也是这两个操作。\n\n对于put(Key,Value)方法而言，LinkedHashMap完全继承了HashMap的 put(Key,Value) 方法，只是对put(Key,Value)方法所调用的recordAccess方法和addEntry方法进行了重写；对于get(Key)方法而言，LinkedHashMap则直接对它进行了重写。\n\n下面我们结合JDK源码看 LinkedHashMap 的存取实现。\n\n### LinkedHashMap 的存储实现 : put(key, vlaue)\n\n上面谈到，LinkedHashMap没有对 put(key,vlaue) 方法进行任何直接的修改，完全继承了HashMap的 put(Key,Value) 方法，其源码如下：\n\n````\npublic V put(K key, V value) {\n\n    //当key为null时，调用putForNullKey方法，并将该键值对保存到table的第一个位置 \n    if (key == null)\n        return putForNullKey(value); \n\n    //根据key的hashCode计算hash值\n    int hash = hash(key.hashCode());           \n\n    //计算该键值对在数组中的存储位置（哪个桶）\n    int i = indexFor(hash, table.length);              \n\n    //在table的第i个桶上进行迭代，寻找 key 保存的位置\n    for (Entry<K,V> e = table[i]; e != null; e = e.next) {      \n        Object k;\n        //判断该条链上是否存在hash值相同且key值相等的映射，若存在，则直接覆盖 value，并返回旧value\n        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {\n            V oldValue = e.value;\n            e.value = value;\n            e.recordAccess(this); // LinkedHashMap重写了Entry中的recordAccess方法--- (1)    \n            return oldValue;    // 返回旧值\n        }\n    }\n\n    modCount++; //修改次数增加1，快速失败机制\n\n    //原Map中无该映射，将该添加至该链的链头\n    addEntry(hash, key, value, i);  // LinkedHashMap重写了HashMap中的createEntry方法 ---- (2)    \n    return null;\n}\n````\n　　上述源码反映了LinkedHashMap与HashMap保存数据的过程。特别地，在LinkedHashMap中，它对addEntry方法和Entry的recordAccess方法进行了重写。下面我们对比地看一下LinkedHashMap 和HashMap的addEntry方法的具体实现：\n````\n/**\n * This override alters behavior of superclass put method. It causes newly\n * allocated entry to get inserted at the end of the linked list and\n * removes the eldest entry if appropriate.\n *\n * LinkedHashMap中的addEntry方法\n */\nvoid addEntry(int hash, K key, V value, int bucketIndex) {   \n\n    //创建新的Entry，并插入到LinkedHashMap中  \n    createEntry(hash, key, value, bucketIndex);  // 重写了HashMap中的createEntry方法\n\n    //双向链表的第一个有效节点（header后的那个节点）为最近最少使用的节点，这是用来支持LRU算法的\n    Entry<K,V> eldest = header.after;  \n    //如果有必要，则删除掉该近期最少使用的节点，  \n    //这要看对removeEldestEntry的覆写,由于默认为false，因此默认是不做任何处理的。  \n    if (removeEldestEntry(eldest)) {  \n        removeEntryForKey(eldest.key);  \n    } else {  \n        //扩容到原来的2倍  \n        if (size >= threshold)  \n            resize(2 * table.length);  \n    }  \n} \n````    \n    -------------------------------我是分割线------------------------------------\n````    \n /**\n * Adds a new entry with the specified key, value and hash code to\n * the specified bucket.  It is the responsibility of this\n * method to resize the table if appropriate.\n *\n * Subclass overrides this to alter the behavior of put method.\n * \n * HashMap中的addEntry方法\n */\nvoid addEntry(int hash, K key, V value, int bucketIndex) {\n    //获取bucketIndex处的Entry\n    Entry<K,V> e = table[bucketIndex];\n\n    //将新创建的 Entry 放入 bucketIndex 索引处，并让新的 Entry 指向原来的 Entry \n    table[bucketIndex] = new Entry<K,V>(hash, key, value, e);\n\n    //若HashMap中元素的个数超过极限了，则容量扩大两倍\n    if (size++ >= threshold)\n        resize(2 * table.length);\n}\n````\n　　由于LinkedHashMap本身维护了插入的先后顺序，因此其可以用来做缓存，14~19行的操作就是用来支持LRU算法的，这里暂时不用去关心它。此外，在LinkedHashMap的addEntry方法中，它重写了HashMap中的createEntry方法，我们接着看一下createEntry方法：\n````\nvoid createEntry(int hash, K key, V value, int bucketIndex) { \n    // 向哈希表中插入Entry，这点与HashMap中相同 \n    //创建新的Entry并将其链入到数组对应桶的链表的头结点处， \n    HashMap.Entry<K,V> old = table[bucketIndex];  \n    Entry<K,V> e = new Entry<K,V>(hash, key, value, old);  \n    table[bucketIndex] = e;     \n\n    //在每次向哈希表插入Entry的同时，都会将其插入到双向链表的尾部，  \n    //这样就按照Entry插入LinkedHashMap的先后顺序来迭代元素(LinkedHashMap根据双向链表重写了迭代器)\n    //同时，新put进来的Entry是最近访问的Entry，把其放在链表末尾 ，也符合LRU算法的实现  \n    e.addBefore(header);  \n    size++;  \n}  \n````\n　　由以上源码我们可以知道，在LinkedHashMap中向哈希表中插入新Entry的同时，还会通过Entry的addBefore方法将其链入到双向链表中。其中，addBefore方法本质上是一个双向链表的插入操作，其源码如下：\n````\n\n//在双向链表中，将当前的Entry插入到existingEntry(header)的前面  \nprivate void addBefore(Entry<K,V> existingEntry) {  \n    after  = existingEntry;  \n    before = existingEntry.before;  \n    before.after = this;  \n    after.before = this;  \n}  \n````\n　　到此为止，我们分析了在LinkedHashMap中put一条键值对的完整过程。总的来说，相比HashMap而言，LinkedHashMap在向哈希表添加一个键值对的同时，也会将其链入到它所维护的双向链表中，以便设定迭代顺序。\n\n### LinkedHashMap 的扩容操作 : resize()\n\n在HashMap中，我们知道随着HashMap中元素的数量越来越多，发生碰撞的概率将越来越大，所产生的子链长度就会越来越长，这样势必会影响HashMap的存取速度。\n\n为了保证HashMap的效率，系统必须要在某个临界点进行扩容处理，该临界点就是HashMap中元素的数量在数值上等于threshold（table数组长度*加载因子）。\n\n但是，不得不说，扩容是一个非常耗时的过程，因为它需要重新计算这些元素在新table数组中的位置并进行复制处理。所以，如果我们能够提前预知HashMap中元素的个数，那么在构造HashMap时预设元素的个数能够有效的提高HashMap的性能。 \n\n同样的问题也存在于LinkedHashMap中，因为LinkedHashMap本来就是一个HashMap，只是它还将所有Entry节点链入到了一个双向链表中。LinkedHashMap完全继承了HashMap的resize()方法，只是对它所调用的transfer方法进行了重写。我们先看resize()方法源码：\n````\n    void resize(int newCapacity) {\n        Entry[] oldTable = table;\n        int oldCapacity = oldTable.length;\n    \n        // 若 oldCapacity 已达到最大值，直接将 threshold 设为 Integer.MAX_VALUE\n        if (oldCapacity == MAXIMUM_CAPACITY) {  \n            threshold = Integer.MAX_VALUE;\n            return;             // 直接返回\n        }\n    \n        // 否则，创建一个更大的数组\n        Entry[] newTable = new Entry[newCapacity];\n    \n        //将每条Entry重新哈希到新的数组中\n        transfer(newTable);  //LinkedHashMap对它所调用的transfer方法进行了重写\n    \n        table = newTable;\n        threshold = (int)(newCapacity * loadFactor);  // 重新设定 threshold\n    }\n````\n　　从上面代码中我们可以看出，Map扩容操作的核心在于重哈希。所谓重哈希是指重新计算原HashMap中的元素在新table数组中的位置并进行复制处理的过程。鉴于性能和LinkedHashMap自身特点的考量，LinkedHashMap对重哈希过程(transfer方法)进行了重写，源码如下：\n````\n    /**\n     * Transfers all entries to new table array.  This method is called\n     * by superclass resize.  It is overridden for performance, as it is\n     * faster to iterate using our linked list.\n     */\n    void transfer(HashMap.Entry[] newTable) {\n        int newCapacity = newTable.length;\n        // 与HashMap相比，借助于双向链表的特点进行重哈希使得代码更加简洁\n        for (Entry<K,V> e = header.after; e != header; e = e.after) {\n            int index = indexFor(e.hash, newCapacity);   // 计算每个Entry所在的桶\n            // 将其链入桶中的链表\n            e.next = newTable[index];\n            newTable[index] = e;   \n        }\n    }\n````\n　　如上述源码所示，LinkedHashMap借助于自身维护的双向链表轻松地实现了重哈希操作。 \n\n### LinkedHashMap 的读取实现 ：get(Object key)\n\n　　相对于LinkedHashMap的存储而言，读取就显得比较简单了。LinkedHashMap中重写了HashMap中的get方法，源码如下：\n\n````\npublic V get(Object key) {\n    // 根据key获取对应的Entry，若没有这样的Entry，则返回null\n    Entry<K,V> e = (Entry<K,V>)getEntry(key); \n    if (e == null)      // 若不存在这样的Entry，直接返回\n        return null;\n    e.recordAccess(this);\n    return e.value;\n}\n\n/**\n * Returns the entry associated with the specified key in the\n * HashMap.  Returns null if the HashMap contains no mapping\n * for the key.\n * \n * HashMap 中的方法\n *     \n */\nfinal Entry<K,V> getEntry(Object key) {\n    if (size == 0) {\n        return null;\n    }\n\n    int hash = (key == null) ? 0 : hash(key);\n    for (Entry<K,V> e = table[indexFor(hash, table.length)];\n         e != null;\n         e = e.next) {\n        Object k;\n        if (e.hash == hash &&\n            ((k = e.key) == key || (key != null && key.equals(k))))\n            return e;\n    }\n    return null;\n}\n````\n在LinkedHashMap的get方法中，通过HashMap中的getEntry方法获取Entry对象。注意这里的recordAccess方法，如果链表中元素的排序规则是按照插入的先后顺序排序的话，该方法什么也不做；如果链表中元素的排序规则是按照访问的先后顺序排序的话，则将e移到链表的末尾处，笔者会在后文专门阐述这个问题。\n\n另外，同样地，调用LinkedHashMap的get(Object key)方法后，若返回值是 NULL，则也存在如下两种可能：\n\n该 key 对应的值就是 null;\nHashMap 中不存在该 key。\n\n### LinkedHashMap 存取小结\n\n> LinkedHashMap的存取过程基本与HashMap基本类似，只是在细节实现上稍有不同，这是由LinkedHashMap本身的特性所决定的，因为它要额外维护一个双向链表用于保持迭代顺序。\n>\n> 在put操作上，虽然LinkedHashMap完全继承了HashMap的put操作，但是在细节上还是做了一定的调整，比如，在LinkedHashMap中向哈希表中插入新Entry的同时，还会通过Entry的addBefore方法将其链入到双向链表中。\n>\n> 在扩容操作上，虽然LinkedHashMap完全继承了HashMap的resize操作，但是鉴于性能和LinkedHashMap自身特点的考量，LinkedHashMap对其中的重哈希过程(transfer方法)进行了重写。在读取操作上，LinkedHashMap中重写了HashMap中的get方法，通过HashMap中的getEntry方法获取Entry对象。在此基础上，进一步获取指定键对应的值。\n\n## LinkedHashMap 与 LRU(Least recently used，最近最少使用)算法\n\n到此为止，我们已经分析完了LinkedHashMap的存取实现，这与HashMap大体相同。LinkedHashMap区别于HashMap最大的一个不同点是，前者是有序的，而后者是无序的。为此，LinkedHashMap增加了两个属性用于保证顺序，分别是双向链表头结点header和标志位accessOrder。\n\n我们知道，header是LinkedHashMap所维护的双向链表的头结点，而accessOrder用于决定具体的迭代顺序。实际上，accessOrder标志位的作用可不像我们描述的这样简单，我们接下来仔细分析一波~ \n\n> 我们知道，当accessOrder标志位为true时，表示双向链表中的元素按照访问的先后顺序排列，可以看到，虽然Entry插入链表的顺序依然是按照其put到LinkedHashMap中的顺序，但put和get方法均有调用recordAccess方法（put方法在key相同时会调用）。\n>\n> recordAccess方法判断accessOrder是否为true，如果是，则将当前访问的Entry（put进来的Entry或get出来的Entry）移到双向链表的尾部（key不相同时，put新Entry时，会调用addEntry，它会调用createEntry，该方法同样将新插入的元素放入到双向链表的尾部，既符合插入的先后顺序，又符合访问的先后顺序，因为这时该Entry也被访问了）；\n>\n> 当标志位accessOrder的值为false时，表示双向链表中的元素按照Entry插入LinkedHashMap到中的先后顺序排序，即每次put到LinkedHashMap中的Entry都放在双向链表的尾部，这样遍历双向链表时，Entry的输出顺序便和插入的顺序一致，这也是默认的双向链表的存储顺序。\n\n因此，当标志位accessOrder的值为false时，虽然也会调用recordAccess方法，但不做任何操作。\n\n### put操作与标志位accessOrder\n````\n/ 将key/value添加到LinkedHashMap中      \npublic V put(K key, V value) {      \n    // 若key为null，则将该键值对添加到table[0]中。      \n    if (key == null)      \n        return putForNullKey(value);      \n    // 若key不为null，则计算该key的哈希值，然后将其添加到该哈希值对应的链表中。      \n    int hash = hash(key.hashCode());      \n    int i = indexFor(hash, table.length);      \n    for (Entry<K,V> e = table[i]; e != null; e = e.next) {      \n        Object k;      \n        // 若key对已经存在，则用新的value取代旧的value     \n        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {      \n            V oldValue = e.value;      \n            e.value = value;      \n            e.recordAccess(this);      \n            return oldValue;      \n        }      \n    }      \n\n    // 若key不存在，则将key/value键值对添加到table中      \n    modCount++;    \n    //将key/value键值对添加到table[i]处    \n    addEntry(hash, key, value, i);      \n    return null;      \n}      \n````\n　　从上述源码我们可以看到，当要put进来的Entry的key在哈希表中已经在存在时，会调用Entry的recordAccess方法；当该key不存在时，则会调用addEntry方法将新的Entry插入到对应桶的单链表的头部。我们先来看recordAccess方法：\n````\n/**\n* This method is invoked by the superclass whenever the value\n* of a pre-existing entry is read by Map.get or modified by Map.set.\n* If the enclosing Map is access-ordered, it moves the entry\n* to the end of the list; otherwise, it does nothing.\n*/\nvoid recordAccess(HashMap<K,V> m) {  \n    LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;  \n    //如果链表中元素按照访问顺序排序，则将当前访问的Entry移到双向循环链表的尾部，  \n    //如果是按照插入的先后顺序排序，则不做任何事情。  \n    if (lm.accessOrder) {  \n        lm.modCount++;  \n        //移除当前访问的Entry  \n        remove();  \n        //将当前访问的Entry插入到链表的尾部  \n        addBefore(lm.header);  \n      }  \n  } \n````\n　　LinkedHashMap重写了HashMap中的recordAccess方法（HashMap中该方法为空），当调用父类的put方法时，在发现key已经存在时，会调用该方法；当调用自己的get方法时，也会调用到该方法。\n\n该方法提供了LRU算法的实现，它将最近使用的Entry放到双向循环链表的尾部。也就是说，当accessOrder为true时，get方法和put方法都会调用recordAccess方法使得最近使用的Entry移到双向链表的末尾；当accessOrder为默认值false时，从源码中可以看出recordAccess方法什么也不会做。我们反过头来，再看一下addEntry方法：\n\n````\n/**\n * This override alters behavior of superclass put method. It causes newly\n * allocated entry to get inserted at the end of the linked list and\n * removes the eldest entry if appropriate.\n *\n * LinkedHashMap中的addEntry方法\n */\nvoid addEntry(int hash, K key, V value, int bucketIndex) {   \n\n    //创建新的Entry，并插入到LinkedHashMap中  \n    createEntry(hash, key, value, bucketIndex);  // 重写了HashMap中的createEntry方法\n\n    //双向链表的第一个有效节点（header后的那个节点）为最近最少使用的节点，这是用来支持LRU算法的\n    Entry<K,V> eldest = header.after;  \n    //如果有必要，则删除掉该近期最少使用的节点，  \n    //这要看对removeEldestEntry的覆写,由于默认为false，因此默认是不做任何处理的。  \n    if (removeEldestEntry(eldest)) {  \n        removeEntryForKey(eldest.key);  \n    } else {  \n        //扩容到原来的2倍  \n        if (size >= threshold)  \n            resize(2 * table.length);  \n    }  \n} \n\nvoid createEntry(int hash, K key, V value, int bucketIndex) { \n    // 向哈希表中插入Entry，这点与HashMap中相同 \n    //创建新的Entry并将其链入到数组对应桶的链表的头结点处， \n    HashMap.Entry<K,V> old = table[bucketIndex];  \n    Entry<K,V> e = new Entry<K,V>(hash, key, value, old);  \n    table[bucketIndex] = e;     \n\n    //在每次向哈希表插入Entry的同时，都会将其插入到双向链表的尾部，  \n    //这样就按照Entry插入LinkedHashMap的先后顺序来迭代元素(LinkedHashMap根据双向链表重写了迭代器)\n    //同时，新put进来的Entry是最近访问的Entry，把其放在链表末尾 ，也符合LRU算法的实现  \n    e.addBefore(header);  \n    size++;  \n}\n````\n　　同样是将新的Entry链入到table中对应桶中的单链表中，但可以在createEntry方法中看出，同时也会把新put进来的Entry插入到了双向链表的尾部。\n　　\n从插入顺序的层面来说，新的Entry插入到双向链表的尾部可以实现按照插入的先后顺序来迭代Entry，而从访问顺序的层面来说，新put进来的Entry又是最近访问的Entry，也应该将其放在双向链表的尾部。在上面的addEntry方法中还调用了removeEldestEntry方法，该方法源码如下：\n````\n    /**\n     * Returns <tt>true</tt> if this map should remove its eldest entry.\n     * This method is invoked by <tt>put</tt> and <tt>putAll</tt> after\n     * inserting a new entry into the map.  It provides the implementor\n     * with the opportunity to remove the eldest entry each time a new one\n     * is added.  This is useful if the map represents a cache: it allows\n     * the map to reduce memory consumption by deleting stale entries.\n     *\n     * <p>Sample use: this override will allow the map to grow up to 100\n     * entries and then delete the eldest entry each time a new entry is\n     * added, maintaining a steady state of 100 entries.\n     * \n     *     private static final int MAX_ENTRIES = 100;\n     *\n     *     protected boolean removeEldestEntry(Map.Entry eldest) {\n     *        return size() > MAX_ENTRIES;\n     *     }\n     * \n     *\n     * <p>This method typically does not modify the map in any way,\n     * instead allowing the map to modify itself as directed by its\n     * return value.  It <i>is</i> permitted for this method to modify\n     * the map directly, but if it does so, it <i>must</i> return\n     * <tt>false</tt> (indicating that the map should not attempt any\n     * further modification).  The effects of returning <tt>true</tt>\n     * after modifying the map from within this method are unspecified.\n     *\n     * <p>This implementation merely returns <tt>false</tt> (so that this\n     * map acts like a normal map - the eldest element is never removed).\n     *\n     * @param    eldest The least recently inserted entry in the map, or if\n     *           this is an access-ordered map, the least recently accessed\n     *           entry.  This is the entry that will be removed it this\n     *           method returns <tt>true</tt>.  If the map was empty prior\n     *           to the <tt>put</tt> or <tt>putAll</tt> invocation resulting\n     *           in this invocation, this will be the entry that was just\n     *           inserted; in other words, if the map contains a single\n     *           entry, the eldest entry is also the newest.\n     * @return   <tt>true</tt> if the eldest entry should be removed\n     *           from the map; <tt>false</tt> if it should be retained.\n     */\n     \n    protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {\n        return false;\n    }\n}\n````\n　　该方法是用来被重写的，一般地，如果用LinkedHashmap实现LRU算法，就要重写该方法。比如可以将该方法覆写为如果设定的内存已满，则返回true，这样当再次向LinkedHashMap中putEntry时，在调用的addEntry方法中便会将近期最少使用的节点删除掉（header后的那个节点）。在第七节，笔者便重写了该方法并实现了一个名副其实的LRU结构。\n\n### get操作与标志位accessOrder\n````\n    public V get(Object key) {\n        // 根据key获取对应的Entry，若没有这样的Entry，则返回null\n        Entry<K,V> e = (Entry<K,V>)getEntry(key); \n        if (e == null)      // 若不存在这样的Entry，直接返回\n            return null;\n        e.recordAccess(this);\n        return e.value;\n    }\n````\n　　在LinkedHashMap中进行读取操作时，一样也会调用recordAccess方法。上面笔者已经表述的很清楚了，此不赘述。\n\n### LinkedListMap与LRU小结\n\n　　使用LinkedHashMap实现LRU的必要前提是将accessOrder标志位设为true以便开启按访问顺序排序的模式。我们可以看到，无论是put方法还是get方法，都会导致目标Entry成为最近访问的Entry，因此就把该Entry加入到了双向链表的末尾：get方法通过调用recordAccess方法来实现；\n\nput方法在覆盖已有key的情况下，也是通过调用recordAccess方法来实现，在插入新的Entry时，则是通过createEntry中的addBefore方法来实现。这样，我们便把最近使用的Entry放入到了双向链表的后面。多次操作后，双向链表前面的Entry便是最近没有使用的，这样当节点个数满的时候，删除最前面的Entry(head后面的那个Entry)即可，因为它就是最近最少使用的Entry。\n\n## 使用LinkedHashMap实现LRU算法\n　　如下所示，笔者使用LinkedHashMap实现一个符合LRU算法的数据结构，该结构最多可以缓存6个元素，但元素多余六个时，会自动删除最近最久没有被使用的元素，如下所示：\n\n````\npublic class LRU<K,V> extends LinkedHashMap<K, V> implements Map<K, V>{\n\n    private static final long serialVersionUID = 1L;\n\n    public LRU(int initialCapacity,\n             float loadFactor,\n                        boolean accessOrder) {\n        super(initialCapacity, loadFactor, accessOrder);\n    }\n\n    /** \n     * @description 重写LinkedHashMap中的removeEldestEntry方法，当LRU中元素多余6个时，\n     *              删除最不经常使用的元素\n     * @author rico       \n     * @created 2017年5月12日 上午11:32:51      \n     * @param eldest\n     * @return     \n     * @see java.util.LinkedHashMap#removeEldestEntry(java.util.Map.Entry)     \n     */  \n    @Override\n    protected boolean removeEldestEntry(java.util.Map.Entry<K, V> eldest) {\n        // TODO Auto-generated method stub\n        if(size() > 6){\n            return true;\n        }\n        return false;\n    }\n\n    public static void main(String[] args) {\n\n        LRU<Character, Integer> lru = new LRU<Character, Integer>(\n                16, 0.75f, true);\n\n        String s = \"abcdefghijkl\";\n        for (int i = 0; i < s.length(); i++) {\n            lru.put(s.charAt(i), i);\n        }\n        System.out.println(\"LRU中key为h的Entry的值为： \" + lru.get('h'));\n        System.out.println(\"LRU的大小 ：\" + lru.size());\n        System.out.println(\"LRU ：\" + lru);\n    }\n}\n````\n　　下图是程序的运行结果： \n![](http://static.zybuluo.com/Rico123/gjz8mjvhkkhwjlzr5o8b27yv/LRU.png)\n## LinkedHashMap 有序性原理分析\n\n如前文所述，LinkedHashMap 增加了双向链表头结点header 和 标志位accessOrder两个属性用于保证迭代顺序。但是要想真正实现其有序性，还差临门一脚，那就是重写HashMap 的迭代器，其源码实现如下：\n````\nprivate abstract class LinkedHashIterator<T> implements Iterator<T> {\n    Entry<K,V> nextEntry    = header.after;\n    Entry<K,V> lastReturned = null;\n\n    /**\n     * The modCount value that the iterator believes that the backing\n     * List should have.  If this expectation is violated, the iterator\n     * has detected concurrent modification.\n     */\n    int expectedModCount = modCount;\n\n    public boolean hasNext() {         // 根据双向列表判断 \n            return nextEntry != header;\n    }\n\n    public void remove() {\n        if (lastReturned == null)\n        throw new IllegalStateException();\n        if (modCount != expectedModCount)\n        throw new ConcurrentModificationException();\n\n            LinkedHashMap.this.remove(lastReturned.key);\n            lastReturned = null;\n            expectedModCount = modCount;\n    }\n\n    Entry<K,V> nextEntry() {        // 迭代输出双向链表各节点\n        if (modCount != expectedModCount)\n        throw new ConcurrentModificationException();\n            if (nextEntry == header)\n                throw new NoSuchElementException();\n\n            Entry<K,V> e = lastReturned = nextEntry;\n            nextEntry = e.after;\n            return e;\n    }\n}\n\n// Key 迭代器，KeySet\nprivate class KeyIterator extends LinkedHashIterator<K> {   \n    public K next() { return nextEntry().getKey(); }\n}\n\n   // Value 迭代器，Values(Collection)\nprivate class ValueIterator extends LinkedHashIterator<V> {\n    public V next() { return nextEntry().value; }\n}\n\n// Entry 迭代器，EntrySet\nprivate class EntryIterator extends LinkedHashIterator<Map.Entry<K,V>> {\n    public Map.Entry<K,V> next() { return nextEntry(); }\n}\n````\n\n　 从上述代码中我们可以知道，LinkedHashMap重写了HashMap 的迭代器，它使用其维护的双向链表进行迭代输出。\n　 \n## JDK1.8的改动\n\n原文是基于JDK1.6的实现，实际上JDK1.8对其进行了改动。\n首先它删除了addentry，createenrty等方法（事实上是hashmap的改动影响了它而已）。\n\nlinkedhashmap同样使用了大部分hashmap的增删改查方法。\n新版本linkedhashmap主要是通过对hashmap内置几个方法重写来实现lru的。\n\nhashmap不提供实现：\n\n    void afterNodeAccess(Node<K,V> p) { }\n    void afterNodeInsertion(boolean evict) { }\n    void afterNodeRemoval(Node<K,V> p) { }\n\nlinkedhashmap的实现：\n    \n处理元素被访问后的情况\n````\nvoid afterNodeAccess(Node<K,V> e) { // move node to last\n    LinkedHashMap.Entry<K,V> last;\n    if (accessOrder && (last = tail) != e) {\n        LinkedHashMap.Entry<K,V> p =\n            (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;\n        p.after = null;\n        if (b == null)\n            head = a;\n        else\n            b.after = a;\n        if (a != null)\n            a.before = b;\n        else\n            last = b;\n        if (last == null)\n            head = p;\n        else {\n            p.before = last;\n            last.after = p;\n        }\n        tail = p;\n        ++modCount;\n    }\n}\n````\n处理元素插入后的情况\n\n````\n    void afterNodeInsertion(boolean evict) { // possibly remove eldest\n    LinkedHashMap.Entry<K,V> first;\n    if (evict && (first = head) != null && removeEldestEntry(first)) {\n        K key = first.key;\n        removeNode(hash(key), key, null, false, true);\n    }\n````\n处理元素被删除后的情况\n````\nvoid afterNodeRemoval(Node<K,V> e) { // unlink\n    LinkedHashMap.Entry<K,V> p =\n        (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;\n    p.before = p.after = null;\n    if (b == null)\n        head = a;\n    else\n        b.after = a;\n    if (a == null)\n        tail = b;\n    else\n        a.before = b;\n}\n````\n另外1.8的hashmap在链表长度超过8时自动转为红黑树，会按顺序插入链表中的元素，可以自定义比较器来定义节点的插入顺序。\n\n1.8的linkedhashmap同样会使用这一特性，当变为红黑树以后，节点的先后顺序同样是插入红黑树的顺序，其双向链表的性质没有改表，只是原来hashmap的链表变成了红黑树而已，在此不要混淆。\n\n## 总结\n> 本文从linkedhashmap的数据结构，以及源码分析，到最后的LRU缓存实现，比较深入地剖析了linkedhashmap的底层原理。\n> 总结以下几点：\n> \n> 1 linkedhashmap在hashmap的数组加链表结构的基础上，将所有节点连成了一个双向链表。\n> \n> 2 当主动传入的accessOrder参数为false时, 使用put方法时，新加入元素不会被加入双向链表，get方法使用时也不会把元素放到双向链表尾部。\n> \n> 3 当主动传入的accessOrder参数为true时，使用put方法新加入的元素，如果遇到了哈希冲突，并且对key值相同的元素进行了替换，就会被放在双向链表的尾部，当元素超过上限且removeEldestEntry方法返回true时，直接删除最早元素以便新元素插入。如果没有冲突直接放入，同样加入到链表尾部。使用get方法时会把get到的元素放入双向链表尾部。\n> \n> 4 linkedhashmap的扩容比hashmap来的方便，因为hashmap需要将原来的每个链表的元素分别在新数组进行反向插入链化，而linkedhashmap的元素都连在一个链表上，可以直接迭代然后插入。\n> \n> 5 linkedhashmap的removeEldestEntry方法默认返回false，要实现lru很重要的一点就是集合满时要将最久未访问的元素删除，在linkedhashmap中这个元素就是头指针指向的元素。实现LRU可以直接实现继承linkedhashmap并重写removeEldestEntry方法来设置缓存大小。jdk中实现了LRUCache也可以直接使用。\n\n## 参考文章\nhttp://cmsblogs.com/?p=176\n\nhttps://www.jianshu.com/p/8f4f58b4b8ab\n\nhttps://blog.csdn.net/wang_8101/article/details/83067860\n\nhttps://www.cnblogs.com/create-and-orange/p/11237072.html\n\nhttps://www.cnblogs.com/ganchuanpu/p/8908093.html\n\n\n\n\n"
  },
  {
    "path": "docs/Java/concurrency/Java并发指南：AQS中的公平锁与非公平锁，Condtion.md",
    "content": "# 目录\n  * [公平锁和非公平锁](#公平锁和非公平锁)\n  * [Condition](#condition)\n    * [1\\. 将节点加入到条件队列](#1-将节点加入到条件队列)\n    * [2\\. 完全释放独占锁](#2-完全释放独占锁)\n    * [3\\. 等待进入阻塞队列](#3-等待进入阻塞队列)\n    * [4\\. signal 唤醒线程，转移到阻塞队列](#4-signal-唤醒线程，转移到阻塞队列)\n    * [5\\. 唤醒后检查中断状态](#5-唤醒后检查中断状态)\n    * [6\\. 获取独占锁](#6-获取独占锁)\n    * [7\\. 处理中断状态](#7-处理中断状态)\n    * [带超时机制的 await](#-带超时机制的-await)\n    * [不抛出 InterruptedException 的 await](#-不抛出-interruptedexception-的-await)\n  * [AbstractQueuedSynchronizer 独占锁的取消排队](#abstractqueuedsynchronizer-独占锁的取消排队)\n  * [再说 java 线程中断和 InterruptedException 异常](#再说-java-线程中断和-interruptedexception-异常)\n    * [线程中断](#线程中断)\n    * [InterruptedException 概述](#interruptedexception-概述)\n    * [处理中断](#处理中断)\n  * [总结](#总结)\n\n\n本文转自：http://hongjiev.github.io/2017/06/16/AbstractQueuedSynchronizer\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章同步发于我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《Java并发指南》其中一篇，本文大部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何全面深入地学习Java并发技术，从Java多线程基础，再到并发编程的基础知识，从Java并发包的入门和实战，再到JUC的源码剖析，一步步地学习Java并发编程，并上手进行实战，以便让你更完整地了解整个Java并发编程知识体系，形成自己的知识框架。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供一些对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n<!--more -->\n\n文章比较长，信息量比较大，建议在 pc 上阅读。文章标题是为了呼应前文，其实可以单独成文的，主要是希望读者看文章能系统看。\n\n本文关注以下几点内容：\n\n1.  深入理解 ReentrantLock 公平锁和非公平锁的区别\n2.  深入分析 AbstractQueuedSynchronizer 中的 ConditionObject\n3.  深入理解 Java 线程中断和 InterruptedException 异常\n\n基本上本文把以上几点都说清楚了，我假设读者看过[上一篇文章中对 AbstractQueuedSynchronizer 的介绍](http://hongjiev.github.io/2017/06/16/AbstractQueuedSynchronizer/)，当然如果你已经熟悉 AQS 中的独占锁了，那也可以直接看这篇。各小节之间基本上没什么关系，大家可以只关注自己感兴趣的部分。\n\n其实这篇文章的信息量很大，初学者估计**至少要 1 小时**才能看完，希望本文对得起大家的时间。\n\n## 公平锁和非公平锁\n\nReentrantLock 默认采用非公平锁，除非你在构造方法中传入参数 true 。\n\n```\npublic ReentrantLock() {\n    // 默认非公平锁\n    sync = new NonfairSync();\n}\npublic ReentrantLock(boolean fair) {\n    sync = fair ? new FairSync() : new NonfairSync();\n}\n```\n\n公平锁的 lock 方法：\n\n```\nstatic final class FairSync extends Sync {\n    final void lock() {\n        acquire(1);\n    }\n    // AbstractQueuedSynchronizer.acquire(int arg)\n    public final void acquire(int arg) {\n        if (!tryAcquire(arg) &&\n            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))\n            selfInterrupt();\n    }\n    protected final boolean tryAcquire(int acquires) {\n        final Thread current = Thread.currentThread();\n        int c = getState();\n        if (c == 0) {\n            // 1\\. 和非公平锁相比，这里多了一个判断：是否有线程在等待\n            if (!hasQueuedPredecessors() &&\n                compareAndSetState(0, acquires)) {\n                setExclusiveOwnerThread(current);\n                return true;\n            }\n        }\n        else if (current == getExclusiveOwnerThread()) {\n            int nextc = c + acquires;\n            if (nextc < 0)\n                throw new Error(\"Maximum lock count exceeded\");\n            setState(nextc);\n            return true;\n        }\n        return false;\n    }\n}\n```\n\n非公平锁的 lock 方法：\n\n```\nstatic final class NonfairSync extends Sync {\n    final void lock() {\n        // 2\\. 和公平锁相比，这里会直接先进行一次CAS，成功就返回了\n        if (compareAndSetState(0, 1))\n            setExclusiveOwnerThread(Thread.currentThread());\n        else\n            acquire(1);\n    }\n    // AbstractQueuedSynchronizer.acquire(int arg)\n    public final void acquire(int arg) {\n        if (!tryAcquire(arg) &&\n            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))\n            selfInterrupt();\n    }\n    protected final boolean tryAcquire(int acquires) {\n        return nonfairTryAcquire(acquires);\n    }\n}\n/**\n * Performs non-fair tryLock.  tryAcquire is implemented in\n * subclasses, but both need nonfair try for trylock method.\n */\nfinal boolean nonfairTryAcquire(int acquires) {\n    final Thread current = Thread.currentThread();\n    int c = getState();\n    if (c == 0) {\n        // 这里没有对阻塞队列进行判断\n        if (compareAndSetState(0, acquires)) {\n            setExclusiveOwnerThread(current);\n            return true;\n        }\n    }\n    else if (current == getExclusiveOwnerThread()) {\n        int nextc = c + acquires;\n        if (nextc < 0) // overflow\n            throw new Error(\"Maximum lock count exceeded\");\n        setState(nextc);\n        return true;\n    }\n    return false;\n}\n```\n\n总结：公平锁和非公平锁只有两处不同：\n\n1.  非公平锁在调用 lock 后，首先就会调用 CAS 进行一次抢锁，如果这个时候恰巧锁没有被占用，那么直接就获取到锁返回了。\n2.  非公平锁在 CAS 失败后，和公平锁一样都会进入到 tryAcquire 方法，在 tryAcquire 方法中，如果发现锁这个时候被释放了（state == 0），非公平锁会直接 CAS 抢锁，但是公平锁会判断等待队列是否有线程处于等待状态，如果有则不去抢锁，乖乖排到后面。\n\n公平锁和非公平锁就这两点区别，如果这两次 CAS 都不成功，那么后面非公平锁和公平锁是一样的，都要进入到阻塞队列等待唤醒。\n\n相对来说，非公平锁会有更好的性能，因为它的吞吐量比较大。当然，非公平锁让获取锁的时间变得更加不确定，可能会导致在阻塞队列中的线程长期处于饥饿状态。\n\n## Condition\n\nTips: 这里重申一下，要看懂这个，必须要先看懂上一篇关于[AbstractQueuedSynchronizer](http://hongjiev.github.io/2017/06/16/AbstractQueuedSynchronizer/)的介绍，或者你已经有相关的知识了，否则这节肯定是看不懂的。\n\n我们先来看看 Condition 的使用场景，Condition 经常可以用在**生产者-消费者**的场景中，请看 Doug Lea 给出的这个例子：\n\n```\nimport java.util.concurrent.locks.Condition;\nimport java.util.concurrent.locks.Lock;\nimport java.util.concurrent.locks.ReentrantLock;\n\nclass BoundedBuffer {\n    final Lock lock = new ReentrantLock();\n    // condition 依赖于 lock 来产生\n    final Condition notFull = lock.newCondition();\n    final Condition notEmpty = lock.newCondition();\n\n    final Object[] items = new Object[100];\n    int putptr, takeptr, count;\n\n    // 生产\n    public void put(Object x) throws InterruptedException {\n        lock.lock();\n        try {\n            while (count == items.length)\n                notFull.await();  // 队列已满，等待，直到 not full 才能继续生产\n            items[putptr] = x;\n            if (++putptr == items.length) putptr = 0;\n            ++count;\n            notEmpty.signal(); // 生产成功，队列已经 not empty 了，发个通知出去\n        } finally {\n            lock.unlock();\n        }\n    }\n\n    // 消费\n    public Object take() throws InterruptedException {\n        lock.lock();\n        try {\n            while (count == 0)\n                notEmpty.await(); // 队列为空，等待，直到队列 not empty，才能继续消费\n            Object x = items[takeptr];\n            if (++takeptr == items.length) takeptr = 0;\n            --count;\n            notFull.signal(); // 被我消费掉一个，队列 not full 了，发个通知出去\n            return x;\n        } finally {\n            lock.unlock();\n        }\n    }\n}\n```\n\n> 1、我们可以看到，在使用 condition 时，必须先持有相应的锁。这个和 Object 类中的方法有相似的语义，需要先持有某个对象的监视器锁才可以执行 wait(), notify() 或 notifyAll() 方法。\n> \n> 2、ArrayBlockingQueue 采用这种方式实现了生产者-消费者，所以请只把这个例子当做学习例子，实际生产中可以直接使用 ArrayBlockingQueue\n\n我们常用 obj.wait()，obj.notify() 或 obj.notifyAll() 来实现相似的功能，但是，它们是基于对象的监视器锁的。需要深入了解这几个方法的读者，可以参考我的另一篇文章《[深入分析 java 8 编程语言规范：Threads and Locks](http://hongjiev.github.io/2017/07/05/Threads-And-Locks-md/)》。而这里说的 Condition 是基于 ReentrantLock 实现的，而 ReentrantLock 是依赖于 AbstractQueuedSynchronizer 实现的。\n\n在往下看之前，读者心里要有一个整体的概念。condition 是依赖于 ReentrantLock 的，不管是调用 await 进入等待还是 signal 唤醒，**都必须获取到锁才能进行操作**。\n\n每个 ReentrantLock 实例可以通过调用多次 newCondition 产生多个 ConditionObject 的实例：\n\n```\nfinal ConditionObject newCondition() {\n    // 实例化一个 ConditionObject\n    return new ConditionObject();\n}\n```\n\n我们首先来看下我们关注的 Condition 的实现类`AbstractQueuedSynchronizer`类中的`ConditionObject`。\n\n```\npublic class ConditionObject implements Condition, java.io.Serializable {\n        private static final long serialVersionUID = 1173984872572414699L;\n        // 条件队列的第一个节点\n          // 不要管这里的关键字 transient，是不参与序列化的意思\n        private transient Node firstWaiter;\n        // 条件队列的最后一个节点\n        private transient Node lastWaiter;\n        ......\n```\n\n在上一篇介绍 AQS 的时候，我们有一个**阻塞队列**，用于保存等待获取锁的线程的队列。这里我们引入另一个概念，叫**条件队列**（condition queue），我画了一张简单的图用来说明这个。\n\n> 这里的阻塞队列如果叫做同步队列（sync queue）其实比较贴切，不过为了和前篇呼应，我就继续使用阻塞队列了。记住这里的两个概念，**阻塞队列**和**条件队列**。\n\n![condition-2](https://www.javadoop.com/blogimages/AbstractQueuedSynchronizer-2/aqs2-2.png)\n\n> 这里，我们简单回顾下 Node 的属性：\n> \n> ```\n> volatile int waitStatus; // 可取值 0、CANCELLED(1)、SIGNAL(-1)、CONDITION(-2)、PROPAGATE(-3)\n> volatile Node prev;\n> volatile Node next;\n> volatile Thread thread;\n> Node nextWaiter;\n> ```\n> \n> prev 和 next 用于实现阻塞队列的双向链表，这里的 nextWaiter 用于实现条件队列的单向链表\n\n基本上，把这张图看懂，你也就知道 condition 的处理流程了。所以，我先简单解释下这图，然后再具体地解释代码实现。\n\n1.  条件队列和阻塞队列的节点，都是 Node 的实例，因为条件队列的节点是需要转移到阻塞队列中去的；\n2.  我们知道一个 ReentrantLock 实例可以通过多次调用 newCondition() 来产生多个 Condition 实例，这里对应 condition1 和 condition2。注意，ConditionObject 只有两个属性 firstWaiter 和 lastWaiter；\n3.  每个 condition 有一个关联的**条件队列**，如线程 1 调用`condition1.await()`方法即可将当前线程 1 包装成 Node 后加入到条件队列中，然后阻塞在这里，不继续往下执行，条件队列是一个单向链表；\n4.  调用`condition1.signal()`触发一次唤醒，此时唤醒的是队头，会将condition1 对应的**条件队列**的 firstWaiter（队头） 移到**阻塞队列的队尾**，等待获取锁，获取锁后 await 方法才能返回，继续往下执行。\n\n上面的 2->3->4 描述了一个最简单的流程，没有考虑中断、signalAll、还有带有超时参数的 await 方法等，不过把这里弄懂是这节的主要目的。\n\n同时，从图中也可以很直观地看出，哪些操作是线程安全的，哪些操作是线程不安全的。\n\n这个图看懂后，下面的代码分析就简单了。\n\n接下来，我们一步步按照流程来走代码分析，我们先来看看 wait 方法：\n\n```\n// 首先，这个方法是可被中断的，不可被中断的是另一个方法 awaitUninterruptibly()\n// 这个方法会阻塞，直到调用 signal 方法（指 signal() 和 signalAll()，下同），或被中断\npublic final void await() throws InterruptedException {\n    // 老规矩，既然该方法要响应中断，那么在最开始就判断中断状态\n    if (Thread.interrupted())\n        throw new InterruptedException();\n\n    // 添加到 condition 的条件队列中\n    Node node = addConditionWaiter();\n\n    // 释放锁，返回值是释放锁之前的 state 值\n    // await() 之前，当前线程是必须持有锁的，这里肯定要释放掉\n    int savedState = fullyRelease(node);\n\n    int interruptMode = 0;\n    // 这里退出循环有两种情况，之后再仔细分析\n    // 1\\. isOnSyncQueue(node) 返回 true，即当前 node 已经转移到阻塞队列了\n    // 2\\. checkInterruptWhileWaiting(node) != 0 会到 break，然后退出循环，代表的是线程中断\n    while (!isOnSyncQueue(node)) {\n        LockSupport.park(this);\n        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)\n            break;\n    }\n    // 被唤醒后，将进入阻塞队列，等待获取锁\n    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)\n        interruptMode = REINTERRUPT;\n    if (node.nextWaiter != null) // clean up if cancelled\n        unlinkCancelledWaiters();\n    if (interruptMode != 0)\n        reportInterruptAfterWait(interruptMode);\n}\n```\n\n其实，我大体上也把整个 await 过程说得十之八九了，下面我们分步把上面的几个点用源码说清楚。\n\n### 1\\. 将节点加入到条件队列\n\naddConditionWaiter() 是将当前节点加入到条件队列，看图我们知道，这种条件队列内的操作是线程安全的。\n\n```\n// 将当前线程对应的节点入队，插入队尾\nprivate Node addConditionWaiter() {\n    Node t = lastWaiter;\n    // 如果条件队列的最后一个节点取消了，将其清除出去\n    // 为什么这里把 waitStatus 不等于 Node.CONDITION，就判定为该节点发生了取消排队？\n    if (t != null && t.waitStatus != Node.CONDITION) {\n        // 这个方法会遍历整个条件队列，然后会将已取消的所有节点清除出队列\n        unlinkCancelledWaiters();\n        t = lastWaiter;\n    }\n    // node 在初始化的时候，指定 waitStatus 为 Node.CONDITION\n    Node node = new Node(Thread.currentThread(), Node.CONDITION);\n\n    // t 此时是 lastWaiter，队尾\n    // 如果队列为空\n    if (t == null)\n        firstWaiter = node;\n    else\n        t.nextWaiter = node;\n    lastWaiter = node;\n    return node;\n}\n```\n\n上面的这块代码很简单，就是将当前线程进入到条件队列的队尾。\n\n在addWaiter 方法中，有一个 unlinkCancelledWaiters() 方法，该方法用于清除队列中已经取消等待的节点。\n\n当 await 的时候如果发生了取消操作（这点之后会说），或者是在节点入队的时候，发现最后一个节点是被取消的，会调用一次这个方法。\n\n```\n// 等待队列是一个单向链表，遍历链表将已经取消等待的节点清除出去\n// 纯属链表操作，很好理解，看不懂多看几遍就可以了\nprivate void unlinkCancelledWaiters() {\n    Node t = firstWaiter;\n    Node trail = null;\n    while (t != null) {\n        Node next = t.nextWaiter;\n        // 如果节点的状态不是 Node.CONDITION 的话，这个节点就是被取消的\n        if (t.waitStatus != Node.CONDITION) {\n            t.nextWaiter = null;\n            if (trail == null)\n                firstWaiter = next;\n            else\n                trail.nextWaiter = next;\n            if (next == null)\n                lastWaiter = trail;\n        }\n        else\n            trail = t;\n        t = next;\n    }\n}\n```\n\n### 2\\. 完全释放独占锁\n\n回到 wait 方法，节点入队了以后，会调用`int savedState = fullyRelease(node);`方法释放锁，注意，这里是完全释放独占锁（fully release），因为 ReentrantLock 是可以重入的。\n\n> 考虑一下这里的 savedState。如果在 condition1.await() 之前，假设线程先执行了 2 次 lock() 操作，那么 state 为 2，我们理解为该线程持有 2 把锁，这里 await() 方法必须将 state 设置为 0，然后再进入挂起状态，这样其他线程才能持有锁。当它被唤醒的时候，它需要重新持有 2 把锁，才能继续下去。\n\n```\n// 首先，我们要先观察到返回值 savedState 代表 release 之前的 state 值\n// 对于最简单的操作：先 lock.lock()，然后 condition1.await()。\n//         那么 state 经过这个方法由 1 变为 0，锁释放，此方法返回 1\n//         相应的，如果 lock 重入了 n 次，savedState == n\n// 如果这个方法失败，会将节点设置为\"取消\"状态，并抛出异常 IllegalMonitorStateException\nfinal int fullyRelease(Node node) {\n    boolean failed = true;\n    try {\n        int savedState = getState();\n        // 这里使用了当前的 state 作为 release 的参数，也就是完全释放掉锁，将 state 置为 0\n        if (release(savedState)) {\n            failed = false;\n            return savedState;\n        } else {\n            throw new IllegalMonitorStateException();\n        }\n    } finally {\n        if (failed)\n            node.waitStatus = Node.CANCELLED;\n    }\n}\n```\n\n> 考虑一下，如果一个线程在不持有 lock 的基础上，就去调用 condition1.await() 方法，它能进入条件队列，但是在上面的这个方法中，由于它不持有锁，release(savedState) 这个方法肯定要返回 false，进入到异常分支，然后进入 finally 块设置`node.waitStatus = Node.CANCELLED`，这个已经入队的节点之后会被后继的节点”请出去“。\n\n### 3\\. 等待进入阻塞队列\n\n释放掉锁以后，接下来是这段，这边会自旋，如果发现自己还没到阻塞队列，那么挂起，等待被转移到阻塞队列。\n\n```\nint interruptMode = 0;\n// 如果不在阻塞队列中，注意了，是阻塞队列\nwhile (!isOnSyncQueue(node)) {\n    // 线程挂起\n    LockSupport.park(this);\n\n    // 这里可以先不用看了，等看到它什么时候被 unpark 再说\n    if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)\n        break;\n}\n```\n\nisOnSyncQueue(Node node) 用于判断节点是否已经转移到阻塞队列了：\n\n```\n// 在节点入条件队列的时候，初始化时设置了 waitStatus = Node.CONDITION\n// 前面我提到，signal 的时候需要将节点从条件队列移到阻塞队列，\n// 这个方法就是判断 node 是否已经移动到阻塞队列了\nfinal boolean isOnSyncQueue(Node node) {\n\n    // 移动过去的时候，node 的 waitStatus 会置为 0，这个之后在说 signal 方法的时候会说到\n    // 如果 waitStatus 还是 Node.CONDITION，也就是 -2，那肯定就是还在条件队列中\n    // 如果 node 的前驱 prev 指向还是 null，说明肯定没有在 阻塞队列(prev是阻塞队列链表中使用的)\n    if (node.waitStatus == Node.CONDITION || node.prev == null)\n        return false;\n    // 如果 node 已经有后继节点 next 的时候，那肯定是在阻塞队列了\n    if (node.next != null) \n        return true;\n\n    // 下面这个方法从阻塞队列的队尾开始从后往前遍历找，如果找到相等的，说明在阻塞队列，否则就是不在阻塞队列\n\n    // 可以通过判断 node.prev() != null 来推断出 node 在阻塞队列吗？答案是：不能。\n    // 这个可以看上篇 AQS 的入队方法，首先设置的是 node.prev 指向 tail，\n    // 然后是 CAS 操作将自己设置为新的 tail，可是这次的 CAS 是可能失败的。\n\n    return findNodeFromTail(node);\n}\n\n// 从阻塞队列的队尾往前遍历，如果找到，返回 true\nprivate boolean findNodeFromTail(Node node) {\n    Node t = tail;\n    for (;;) {\n        if (t == node)\n            return true;\n        if (t == null)\n            return false;\n        t = t.prev;\n    }\n}\n```\n\n回到前面的循环，isOnSyncQueue(node) 返回 false 的话，那么进到`LockSupport.park(this);`这里线程挂起。\n\n### 4\\. signal 唤醒线程，转移到阻塞队列\n\n为了大家理解，这里我们先看唤醒操作，因为刚刚到`LockSupport.park(this);`把线程挂起了，等待唤醒。\n\n唤醒操作通常由另一个线程来操作，就像生产者-消费者模式中，如果线程因为等待消费而挂起，那么当生产者生产了一个东西后，会调用 signal 唤醒正在等待的线程来消费。\n\n```\n// 唤醒等待了最久的线程\n// 其实就是，将这个线程对应的 node 从条件队列转移到阻塞队列\npublic final void signal() {\n    // 调用 signal 方法的线程必须持有当前的独占锁\n    if (!isHeldExclusively())\n        throw new IllegalMonitorStateException();\n    Node first = firstWaiter;\n    if (first != null)\n        doSignal(first);\n}\n\n// 从条件队列队头往后遍历，找出第一个需要转移的 node\n// 因为前面我们说过，有些线程会取消排队，但是可能还在队列中\nprivate void doSignal(Node first) {\n    do {\n          // 将 firstWaiter 指向 first 节点后面的第一个，因为 first 节点马上要离开了\n        // 如果将 first 移除后，后面没有节点在等待了，那么需要将 lastWaiter 置为 null\n        if ( (firstWaiter = first.nextWaiter) == null)\n            lastWaiter = null;\n        // 因为 first 马上要被移到阻塞队列了，和条件队列的链接关系在这里断掉\n        first.nextWaiter = null;\n    } while (!transferForSignal(first) &&\n             (first = firstWaiter) != null);\n      // 这里 while 循环，如果 first 转移不成功，那么选择 first 后面的第一个节点进行转移，依此类推\n}\n\n// 将节点从条件队列转移到阻塞队列\n// true 代表成功转移\n// false 代表在 signal 之前，节点已经取消了\nfinal boolean transferForSignal(Node node) {\n\n    // CAS 如果失败，说明此 node 的 waitStatus 已不是 Node.CONDITION，说明节点已经取消，\n    // 既然已经取消，也就不需要转移了，方法返回，转移后面一个节点\n    // 否则，将 waitStatus 置为 0\n    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))\n        return false;\n\n    // enq(node): 自旋进入阻塞队列的队尾\n    // 注意，这里的返回值 p 是 node 在阻塞队列的前驱节点\n    Node p = enq(node);\n    int ws = p.waitStatus;\n    // ws > 0 说明 node 在阻塞队列中的前驱节点取消了等待锁，直接唤醒 node 对应的线程。唤醒之后会怎么样，后面再解释\n    // 如果 ws <= 0, 那么 compareAndSetWaitStatus 将会被调用，上篇介绍的时候说过，节点入队后，需要把前驱节点的状态设为 Node.SIGNAL(-1)\n    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))\n        // 如果前驱节点取消或者 CAS 失败，会进到这里唤醒线程，之后的操作看下一节\n        LockSupport.unpark(node.thread);\n    return true;\n}\n```\n\n正常情况下，`ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)`这句中，ws <= 0，而且`compareAndSetWaitStatus(p, ws, Node.SIGNAL)`会返回 true，所以一般也不会进去 if 语句块中唤醒 node 对应的线程。然后这个方法返回 true，也就意味着 signal 方法结束了，节点进入了阻塞队列。\n\n假设发生了阻塞队列中的前驱节点取消等待，或者 CAS 失败，只要唤醒线程，让其进到下一步即可。\n\n### 5\\. 唤醒后检查中断状态\n\n上一步 signal 之后，我们的线程由条件队列转移到了阻塞队列，之后就准备获取锁了。只要重新获取到锁了以后，继续往下执行。\n\n等线程从挂起中恢复过来，继续往下看\n\n```\nint interruptMode = 0;\nwhile (!isOnSyncQueue(node)) {\n    // 线程挂起\n    LockSupport.park(this);\n\n    if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)\n        break;\n}\n```\n\n先解释下 interruptMode。interruptMode 可以取值为 REINTERRUPT（1），THROW_IE（-1），0\n\n*   REINTERRUPT： 代表 await 返回的时候，需要重新设置中断状态\n*   THROW_IE： 代表 await 返回的时候，需要抛出 InterruptedException 异常\n*   0 ：说明在 await 期间，没有发生中断\n\n有以下三种情况会让 LockSupport.park(this); 这句返回继续往下执行：\n\n1.  常规路径。signal -> 转移节点到阻塞队列 -> 获取了锁（unpark）\n2.  线程中断。在 park 的时候，另外一个线程对这个线程进行了中断\n3.  signal 的时候我们说过，转移以后的前驱节点取消了，或者对前驱节点的CAS操作失败了\n4.  假唤醒。这个也是存在的，和 Object.wait() 类似，都有这个问题\n\n线程唤醒后第一步是调用 checkInterruptWhileWaiting(node) 这个方法，此方法用于判断是否在线程挂起期间发生了中断，如果发生了中断，是 signal 调用之前中断的，还是 signal 之后发生的中断。\n\n```\n// 1\\. 如果在 signal 之前已经中断，返回 THROW_IE\n// 2\\. 如果是 signal 之后中断，返回 REINTERRUPT\n// 3\\. 没有发生中断，返回 0\nprivate int checkInterruptWhileWaiting(Node node) {\n    return Thread.interrupted() ?\n        (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :\n        0;\n}\n```\n\n> Thread.interrupted()：如果当前线程已经处于中断状态，那么该方法返回 true，同时将中断状态重置为 false，所以，才有后续的`重新中断（REINTERRUPT）`的使用。\n\n看看怎么判断是 signal 之前还是之后发生的中断：\n\n```\n// 只有线程处于中断状态，才会调用此方法\n// 如果需要的话，将这个已经取消等待的节点转移到阻塞队列\n// 返回 true：如果此线程在 signal 之前被取消，\nfinal boolean transferAfterCancelledWait(Node node) {\n    // 用 CAS 将节点状态设置为 0 \n    // 如果这步 CAS 成功，说明是 signal 方法之前发生的中断，因为如果 signal 先发生的话，signal 中会将 waitStatus 设置为 0\n    if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {\n        // 将节点放入阻塞队列\n        // 这里我们看到，即使中断了，依然会转移到阻塞队列\n        enq(node);\n        return true;\n    }\n\n    // 到这里是因为 CAS 失败，肯定是因为 signal 方法已经将 waitStatus 设置为了 0\n    // signal 方法会将节点转移到阻塞队列，但是可能还没完成，这边自旋等待其完成\n    // 当然，这种事情还是比较少的吧：signal 调用之后，没完成转移之前，发生了中断\n    while (!isOnSyncQueue(node))\n        Thread.yield();\n    return false;\n}\n```\n\n> 这里再说一遍，即使发生了中断，节点依然会转移到阻塞队列。\n\n到这里，大家应该都知道这个 while 循环怎么退出了吧。要么中断，要么转移成功。\n\n这里描绘了一个场景，本来有个线程，它是排在条件队列的后面的，但是因为它被中断了，那么它会被唤醒，然后它发现自己不是被 signal 的那个，但是它会自己主动去进入到阻塞队列。\n\n### 6\\. 获取独占锁\n\nwhile 循环出来以后，下面是这段代码：\n\n```\nif (acquireQueued(node, savedState) && interruptMode != THROW_IE)\n    interruptMode = REINTERRUPT;\n```\n\n由于 while 出来后，我们确定节点已经进入了阻塞队列，准备获取锁。\n\n这里的 acquireQueued(node, savedState) 的第一个参数 node 之前已经经过 enq(node) 进入了队列，参数 savedState 是之前释放锁前的 state，这个方法返回的时候，代表当前线程获取了锁，而且 state == savedState了。\n\n注意，前面我们说过，不管有没有发生中断，都会进入到阻塞队列，而 acquireQueued(node, savedState) 的返回值就是代表线程是否被中断。如果返回 true，说明被中断了，而且 interruptMode != THROW_IE，说明在 signal 之前就发生中断了，这里将 interruptMode 设置为 REINTERRUPT，用于待会重新中断。\n\n继续往下：\n\n```\nif (node.nextWaiter != null) // clean up if cancelled\n    unlinkCancelledWaiters();\nif (interruptMode != 0)\n    reportInterruptAfterWait(interruptMode);\n```\n\n本着一丝不苟的精神，这边说说`node.nextWaiter != null`怎么满足。我前面也说了 signal 的时候会将节点转移到阻塞队列，有一步是 node.nextWaiter = null，将断开节点和条件队列的联系。\n\n可是，`在判断发生中断的情况下，是 signal 之前还是之后发生的？`这部分的时候，我也介绍了，如果 signal 之前就中断了，也需要将节点进行转移到阻塞队列，这部分转移的时候，是没有设置 node.nextWaiter = null 的。\n\n之前我们说过，如果有节点取消，也会调用 unlinkCancelledWaiters 这个方法，就是这里了。\n\n### 7\\. 处理中断状态\n\n到这里，我们终于可以好好说下这个 interruptMode 干嘛用了。\n\n*   0：什么都不做，没有被中断过；\n*   THROW_IE：await 方法抛出 InterruptedException 异常，因为它代表在 await() 期间发生了中断；\n*   REINTERRUPT：重新中断当前线程，因为它代表 await() 期间没有被中断，而是 signal() 以后发生的中断\n\n```\nprivate void reportInterruptAfterWait(int interruptMode)\n    throws InterruptedException {\n    if (interruptMode == THROW_IE)\n        throw new InterruptedException();\n    else if (interruptMode == REINTERRUPT)\n        selfInterrupt();\n}\n```\n\n> 这个中断状态这部分内容，大家应该都理解了吧，不理解的话，多看几遍就是了。\n\n### * 带超时机制的 await\n\n经过前面的 7 步，整个 ConditionObject 类基本上都分析完了，接下来简单分析下带超时机制的 await 方法。\n\n```\npublic final long awaitNanos(long nanosTimeout) \n                  throws InterruptedException\npublic final boolean awaitUntil(Date deadline)\n                throws InterruptedException\npublic final boolean await(long time, TimeUnit unit)\n                throws InterruptedException\n```\n\n这三个方法都差不多，我们就挑一个出来看看吧：\n\n```\npublic final boolean await(long time, TimeUnit unit)\n        throws InterruptedException {\n    // 等待这么多纳秒\n    long nanosTimeout = unit.toNanos(time);\n    if (Thread.interrupted())\n        throw new InterruptedException();\n    Node node = addConditionWaiter();\n    int savedState = fullyRelease(node);\n    // 当前时间 + 等待时长 = 过期时间\n    final long deadline = System.nanoTime() + nanosTimeout;\n    // 用于返回 await 是否超时\n    boolean timedout = false;\n    int interruptMode = 0;\n    while (!isOnSyncQueue(node)) {\n        // 时间到啦\n        if (nanosTimeout <= 0L) {\n            // 这里因为要 break 取消等待了。取消等待的话一定要调用 transferAfterCancelledWait(node) 这个方法\n            // 如果这个方法返回 true，在这个方法内，将节点转移到阻塞队列成功\n            // 返回 false 的话，说明 signal 已经发生，signal 方法将节点转移了。也就是说没有超时嘛\n            timedout = transferAfterCancelledWait(node);\n            break;\n        }\n        // spinForTimeoutThreshold 的值是 1000 纳秒，也就是 1 毫秒\n        // 也就是说，如果不到 1 毫秒了，那就不要选择 parkNanos 了，自旋的性能反而更好\n        if (nanosTimeout >= spinForTimeoutThreshold)\n            LockSupport.parkNanos(this, nanosTimeout);\n        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)\n            break;\n        // 得到剩余时间\n        nanosTimeout = deadline - System.nanoTime();\n    }\n    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)\n        interruptMode = REINTERRUPT;\n    if (node.nextWaiter != null)\n        unlinkCancelledWaiters();\n    if (interruptMode != 0)\n        reportInterruptAfterWait(interruptMode);\n    return !timedout;\n}\n```\n\n超时的思路还是很简单的，不带超时参数的 await 是 park，然后等待别人唤醒。而现在就是调用 parkNanos 方法来休眠指定的时间，醒来后判断是否 signal 调用了，调用了就是没有超时，否则就是超时了。超时的话，自己来进行转移到阻塞队列，然后抢锁。\n\n### * 不抛出 InterruptedException 的 await\n\n关于 Condition 最后一小节了。\n\n```\npublic final void awaitUninterruptibly() {\n    Node node = addConditionWaiter();\n    int savedState = fullyRelease(node);\n    boolean interrupted = false;\n    while (!isOnSyncQueue(node)) {\n        LockSupport.park(this);\n        if (Thread.interrupted())\n            interrupted = true;\n    }\n    if (acquireQueued(node, savedState) || interrupted)\n        selfInterrupt();\n}\n```\n\n很简单，贴一下代码大家就都懂了，我就不废话了。\n\n## AbstractQueuedSynchronizer 独占锁的取消排队\n\n这篇文章说的是 AbstractQueuedSynchronizer，只不过好像 Condition 说太多了，赶紧把思路拉回来。\n\n接下来，我想说说怎么取消对锁的竞争？\n\n上篇文章提到过，最重要的方法是这个，我们要在这里面找答案：\n\n```\nfinal boolean acquireQueued(final Node node, int arg) {\n    boolean failed = true;\n    try {\n        boolean interrupted = false;\n        for (;;) {\n            final Node p = node.predecessor();\n            if (p == head && tryAcquire(arg)) {\n                setHead(node);\n                p.next = null; // help GC\n                failed = false;\n                return interrupted;\n            }\n            if (shouldParkAfterFailedAcquire(p, node) &&\n                parkAndCheckInterrupt())\n                interrupted = true;\n        }\n    } finally {\n        if (failed)\n            cancelAcquire(node);\n    }\n}\n```\n\n首先，到这个方法的时候，节点一定是入队成功的。\n\n我把 parkAndCheckInterrupt() 代码贴过来：\n\n```\nprivate final boolean parkAndCheckInterrupt() {\n    LockSupport.park(this);\n    return Thread.interrupted();\n}\n```\n\n这两段代码联系起来看，是不是就清楚了。\n\n如果我们要取消一个线程的排队，我们需要在另外一个线程中对其进行中断。比如某线程调用 lock() 老久不返回，我想中断它。一旦对其进行中断，此线程会从`LockSupport.park(this);`中唤醒，然后`Thread.interrupted();`返回 true。\n\n我们发现一个问题，即使是中断唤醒了这个线程，也就只是设置了`interrupted = true`然后继续下一次循环。而且，由于`Thread.interrupted();`会清除中断状态，第二次进 parkAndCheckInterrupt 的时候，返回会是 false。\n\n所以，我们要看到，在这个方法中，interrupted 只是用来记录是否发生了中断，然后用于方法返回值，其他没有做任何相关事情。\n\n所以，我们看外层方法怎么处理 acquireQueued 返回 false 的情况。\n\n```\npublic final void acquire(int arg) {\n    if (!tryAcquire(arg) &&\n        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))\n        selfInterrupt();\n}\nstatic void selfInterrupt() {\n    Thread.currentThread().interrupt();\n}\n```\n\n所以说，lock() 方法处理中断的方法就是，你中断归中断，我抢锁还是照样抢锁，几乎没关系，只是我抢到锁了以后，设置线程的中断状态而已，也不抛出任何异常出来。调用者获取锁后，可以去检查是否发生过中断，也可以不理会。\n\n* * *\n\n来条分割线。有没有被骗的感觉，我说了一大堆，可是和取消没有任何关系啊。\n\n我们来看 ReentrantLock 的另一个 lock 方法：\n\n```\npublic void lockInterruptibly() throws InterruptedException {\n    sync.acquireInterruptibly(1);\n}\n```\n\n方法上多了个`throws InterruptedException`，经过前面那么多知识的铺垫，这里我就不再啰里啰嗦了。\n\n```\npublic final void acquireInterruptibly(int arg)\n        throws InterruptedException {\n    if (Thread.interrupted())\n        throw new InterruptedException();\n    if (!tryAcquire(arg))\n        doAcquireInterruptibly(arg);\n}\n```\n\n继续往里：\n\n```\nprivate void doAcquireInterruptibly(int arg) throws InterruptedException {\n    final Node node = addWaiter(Node.EXCLUSIVE);\n    boolean failed = true;\n    try {\n        for (;;) {\n            final Node p = node.predecessor();\n            if (p == head && tryAcquire(arg)) {\n                setHead(node);\n                p.next = null; // help GC\n                failed = false;\n                return;\n            }\n            if (shouldParkAfterFailedAcquire(p, node) &&\n                parkAndCheckInterrupt())\n                // 就是这里了，一旦异常，马上结束这个方法，抛出异常。\n                // 这里不再只是标记这个方法的返回值代表中断状态\n                // 而是直接抛出异常，而且外层也不捕获，一直往外抛到 lockInterruptibly\n                throw new InterruptedException();\n        }\n    } finally {\n        // 如果通过 InterruptedException 异常出去，那么 failed 就是 true 了\n        if (failed)\n            cancelAcquire(node);\n    }\n}\n```\n\n既然到这里了，顺便说说 cancelAcquire 这个方法吧：\n\n```\nprivate void cancelAcquire(Node node) {\n    // Ignore if node doesn't exist\n    if (node == null)\n        return;\n    node.thread = null;\n    // Skip cancelled predecessors\n    // 找一个合适的前驱。其实就是将它前面的队列中已经取消的节点都”请出去“\n    Node pred = node.prev;\n    while (pred.waitStatus > 0)\n        node.prev = pred = pred.prev;\n    // predNext is the apparent node to unsplice. CASes below will\n    // fail if not, in which case, we lost race vs another cancel\n    // or signal, so no further action is necessary.\n    Node predNext = pred.next;\n    // Can use unconditional write instead of CAS here.\n    // After this atomic step, other Nodes can skip past us.\n    // Before, we are free of interference from other threads.\n    node.waitStatus = Node.CANCELLED;\n    // If we are the tail, remove ourselves.\n    if (node == tail && compareAndSetTail(node, pred)) {\n        compareAndSetNext(pred, predNext, null);\n    } else {\n        // If successor needs signal, try to set pred's next-link\n        // so it will get one. Otherwise wake it up to propagate.\n        int ws;\n        if (pred != head &&\n            ((ws = pred.waitStatus) == Node.SIGNAL ||\n             (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&\n            pred.thread != null) {\n            Node next = node.next;\n            if (next != null && next.waitStatus <= 0)\n                compareAndSetNext(pred, predNext, next);\n        } else {\n            unparkSuccessor(node);\n        }\n        node.next = node; // help GC\n    }\n}\n```\n\n其实这个方法没什么好说的，一行行看下去就是了，节点取消，只要把 waitStatus 设置为 Node.CANCELLED，会有非常多的情况被从阻塞队列中请出去，主动或被动。\n\n## 再说 java 线程中断和 InterruptedException 异常\n\n在之前的文章中，我们接触了大量的中断，这边算是个总结吧。如果你完全熟悉中断了，没有必要再看这节，本节为新手而写。\n\n### 线程中断\n\n首先，我们要明白，中断不是类似 linux 里面的命令 kill -9 pid，不是说我们中断某个线程，这个线程就停止运行了。中断代表线程状态，每个线程都关联了一个中断状态，是一个 true 或 false 的 boolean 值，初始值为 false。\n\n> Java 中的中断和操作系统的中断还不一样，这里就按照**状态**来理解吧，不要和操作系统的中断联系在一起\n\n关于中断状态，我们需要重点关注 Thread 类中的以下几个方法：\n\n```\n// Thread 类中的实例方法，持有线程实例引用即可检测线程中断状态\npublic boolean isInterrupted() {}\n\n// Thread 中的静态方法，检测调用这个方法的线程是否已经中断\n// 注意：这个方法返回中断状态的同时，会将此线程的中断状态重置为 false\n// 所以，如果我们连续调用两次这个方法的话，第二次的返回值肯定就是 false 了\npublic static boolean interrupted() {}\n\n// Thread 类中的实例方法，用于设置一个线程的中断状态为 true\npublic void interrupt() {}\n```\n\n我们说中断一个线程，其实就是设置了线程的 interrupted status 为 true，至于说被中断的线程怎么处理这个状态，那是那个线程自己的事。如以下代码：\n\n```\nwhile (!Thread.interrupted()) {\n   doWork();\n   System.out.println(\"我做完一件事了，准备做下一件，如果没有其他线程中断我的话\");\n}\n```\n\n> 这种代码就是会响应中断的，它会在干活的时候先判断下中断状态，不过，除了 JDK 源码外，其他用中断的场景还是比较少的，毕竟 JDK 源码非常讲究。\n\n当然，中断除了是线程状态外，还有其他含义，否则也不需要专门搞一个这个概念出来了。\n\n如果线程处于以下三种情况，那么当线程被中断的时候，能自动感知到：\n\n1.  来自 Object 类的 wait()、wait(long)、wait(long, int)，\n\n    来自 Thread 类的 join()、join(long)、join(long, int)、sleep(long)、sleep(long, int)\n\n    > 这几个方法的相同之处是，方法上都有: throws InterruptedException\n    > \n    > 如果线程阻塞在这些方法上（我们知道，这些方法会让当前线程阻塞），这个时候如果其他线程对这个线程进行了中断，那么这个线程会从这些方法中立即返回，抛出 InterruptedException 异常，同时重置中断状态为 false。\n\n2.  实现了 InterruptibleChannel 接口的类中的一些 I/O 阻塞操作，如 DatagramChannel 中的 connect 方法和 receive 方法等\n\n    > 如果线程阻塞在这里，中断线程会导致这些方法抛出 ClosedByInterruptException 并重置中断状态。\n\n3.  Selector 中的 select 方法，参考下我写的 NIO 的文章\n\n    > 一旦中断，方法立即返回\n\n对于以上 3 种情况是最特殊的，因为他们能自动感知到中断（这里说自动，当然也是基于底层实现），**并且在做出相应的操作后都会重置中断状态为 false**。\n\n那是不是只有以上 3 种方法能自动感知到中断呢？不是的，如果线程阻塞在 LockSupport.park(Object obj) 方法，也叫挂起，这个时候的中断也会导致线程唤醒，但是唤醒后不会重置中断状态，所以唤醒后去检测中断状态将是 true。\n\n### InterruptedException 概述\n\n它是一个特殊的异常，不是说 JVM 对其有特殊的处理，而是它的使用场景比较特殊。通常，我们可以看到，像 Object 中的 wait() 方法，ReentrantLock 中的 lockInterruptibly() 方法，Thread 中的 sleep() 方法等等，这些方法都带有`throws InterruptedException`，我们通常称这些方法为阻塞方法（blocking method）。\n\n阻塞方法一个很明显的特征是，它们需要花费比较长的时间（不是绝对的，只是说明时间不可控），还有它们的方法结束返回往往依赖于外部条件，如 wait 方法依赖于其他线程的 notify，lock 方法依赖于其他线程的 unlock等等。\n\n当我们看到方法上带有`throws InterruptedException`时，我们就要知道，这个方法应该是阻塞方法，我们如果希望它能早点返回的话，我们往往可以通过中断来实现。\n\n除了几个特殊类（如 Object，Thread等）外，感知中断并提前返回是通过轮询中断状态来实现的。我们自己需要写可中断的方法的时候，就是通过在合适的时机（通常在循环的开始处）去判断线程的中断状态，然后做相应的操作（通常是方法直接返回或者抛出异常）。当然，我们也要看到，如果我们一次循环花的时间比较长的话，那么就需要比较长的时间才能**感知**到线程中断了。\n\n### 处理中断\n\n一旦中断发生，我们接收到了这个信息，然后怎么去处理中断呢？本小节将简单分析这个问题。\n\n我们经常会这么写代码：\n\n```\ntry {\n    Thread.sleep(10000);\n} catch (InterruptedException e) {\n    // ignore\n}\n// go on \n```\n\n当 sleep 结束继续往下执行的时候，我们往往都不知道这块代码是真的 sleep 了 10 秒，还是只休眠了 1 秒就被中断了。这个代码的问题在于，我们将这个异常信息吞掉了。（对于 sleep 方法，我相信大部分情况下，我们都不在意是否是中断了，这里是举例）\n\nAQS 的做法很值得我们借鉴，我们知道 ReentrantLock 有两种 lock 方法：\n\n```\npublic void lock() {\n    sync.lock();\n}\n\npublic void lockInterruptibly() throws InterruptedException {\n    sync.acquireInterruptibly(1);\n}\n```\n\n前面我们提到过，lock() 方法不响应中断。如果 thread1 调用了 lock() 方法，过了很久还没抢到锁，这个时候 thread2 对其进行了中断，thread1 是不响应这个请求的，它会继续抢锁，当然它不会把“被中断”这个信息扔掉。我们可以看以下代码：\n\n```\npublic final void acquire(int arg) {\n    if (!tryAcquire(arg) &&\n        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))\n        // 我们看到，这里也没做任何特殊处理，就是记录下来中断状态。\n        // 这样，如果外层方法需要去检测的时候，至少我们没有把这个信息丢了\n        selfInterrupt();// Thread.currentThread().interrupt();\n}\n```\n\n而对于 lockInterruptibly() 方法，因为其方法上面有`throws InterruptedException`，这个信号告诉我们，如果我们要取消线程抢锁，直接中断这个线程即可，它会立即返回，抛出 InterruptedException 异常。\n\n在并发包中，有非常多的这种处理中断的例子，提供两个方法，分别为响应中断和不响应中断，对于不响应中断的方法，记录中断而不是丢失这个信息。如 Condition 中的两个方法就是这样的：\n\n```\nvoid await() throws InterruptedException;\nvoid awaitUninterruptibly();\n```\n\n> 通常，如果方法会抛出 InterruptedException 异常，往往方法体的第一句就是：\n> \n> ```\n> public final void await() throws InterruptedException {\n>     if (Thread.interrupted())\n>         throw new InterruptedException();\n>      ...... \n> }\n> ```\n\n熟练使用中断，对于我们写出优雅的代码是有帮助的，也有助于我们分析别人的源码。\n\n## 总结\n\n这篇文章的信息量真的很大，如果你花了时间，还是没有看懂，那是我的错了。\n\n欢迎大家向我提问，我不一定能每次都及时出现，我出现也不一定能解决大家的问题，欢迎探讨。\n"
  },
  {
    "path": "docs/Java/concurrency/Java并发指南：AQS共享模式与并发工具类的实现.md",
    "content": "# 目录\n  * [前言](#前言)\n  * [CountDownLatch](#countdownlatch)\n    * [使用例子](#使用例子)\n    * [源码分析](#源码分析)\n  * [CyclicBarrier](#cyclicbarrier)\n  * [Semaphore](#semaphore)\n  * [总结](#总结)\n\n\n本文转自：https://www.javadoop.com/\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章同步发于我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《Java并发指南》其中一篇，本文大部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何全面深入地学习Java并发技术，从Java多线程基础，再到并发编程的基础知识，从Java并发包的入门和实战，再到JUC的源码剖析，一步步地学习Java并发编程，并上手进行实战，以便让你更完整地了解整个Java并发编程知识体系，形成自己的知识框架。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供一些对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n<!--more -->\n## 前言\n\n这篇文章是 AQS 系列的最后一篇，第一篇，我们通过 ReentrantLock 公平锁分析了 AQS 的核心，第二篇的重点是把 Condition 说明白，同时也说清楚了对于线程中断的使用。\n\n这篇，我们的关注点是 AQS 最后的部分，**AQS 共享模式**的使用。有前两篇文章的铺垫，剩下的源码分析将会简单很多。\n\n本文先用 CountDownLatch 将共享模式说清楚，然后顺着把其他 AQS 相关的类 CyclicBarrier、Semaphore 的源码一起过一下。\n\n相对来说，如果读者有前面两篇文章的基础，这篇文章是简单很多，不过对于初学者来说，1 小时估计也是免不了的。\n\n## CountDownLatch\n\nCountDownLatch 这个类是比较典型的 AQS 的共享模式的使用，这是一个高频使用的类。latch 的中文意思是**门栓、栅栏**，具体怎么解释我就不废话了，大家随意，看两个例子就知道在哪里用、怎么用了。\n\n### 使用例子\n\n我们看下 Doug Lea 在 java doc 中给出的例子，这个例子非常实用，我经常会写到这个代码。\n\n假设我们有 N ( N > 0 ) 个任务，那么我们会用 N 来初始化一个 CountDownLatch，然后将这个 latch 的引用传递到各个线程中，在每个线程完成了任务后，调用 latch.countDown() 代表完成了一个任务。\n\n调用 latch.await() 的方法的线程会阻塞，直到所有的任务完成。\n\n```\nclass Driver2 { // ...\n    void main() throws InterruptedException {\n        CountDownLatch doneSignal = new CountDownLatch(N);\n        Executor e = Executors.newFixedThreadPool(8);\n\n        // 创建 N 个任务，提交给线程池来执行\n        for (int i = 0; i < N; ++i) // create and start threads\n            e.execute(new WorkerRunnable(doneSignal, i));\n\n        // 等待所有的任务完成，这个方法才会返回\n        doneSignal.await();           // wait for all to finish\n    }\n}\n\nclass WorkerRunnable implements Runnable {\n    private final CountDownLatch doneSignal;\n    private final int i;\n\n    WorkerRunnable(CountDownLatch doneSignal, int i) {\n        this.doneSignal = doneSignal;\n        this.i = i;\n    }\n\n    public void run() {\n        try {\n            doWork(i);\n            // 这个线程的任务完成了，调用 countDown 方法\n            doneSignal.countDown();\n        } catch (InterruptedException ex) {\n        } // return;\n    }\n\n    void doWork() { ...}\n}\n```\n\n所以说 CountDownLatch 非常实用，我们常常会将一个比较大的任务进行拆分，然后开启多个线程来执行，等所有线程都执行完了以后，再往下执行其他操作。这里例子中，**只有 main 线程调用了 await 方法**。\n\n我们再来看另一个例子，这个例子很典型，用了两个 CountDownLatch：\n\n```\nclass Driver { // ...\n    void main() throws InterruptedException {\n        CountDownLatch startSignal = new CountDownLatch(1);\n        CountDownLatch doneSignal = new CountDownLatch(N);\n\n        for (int i = 0; i < N; ++i) // create and start threads\n            new Thread(new Worker(startSignal, doneSignal)).start();\n\n        // 这边插入一些代码，确保上面的每个线程先启动起来，才执行下面的代码。\n        doSomethingElse();            // don't let run yet\n        // 因为这里 N == 1，所以，只要调用一次，那么所有的 await 方法都可以通过\n        startSignal.countDown();      // let all threads proceed\n        doSomethingElse();\n        // 等待所有任务结束\n        doneSignal.await();           // wait for all to finish\n    }\n}\n\nclass Worker implements Runnable {\n    private final CountDownLatch startSignal;\n    private final CountDownLatch doneSignal;\n\n    Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {\n        this.startSignal = startSignal;\n        this.doneSignal = doneSignal;\n    }\n\n    public void run() {\n        try {\n            // 为了让所有线程同时开始任务，我们让所有线程先阻塞在这里\n            // 等大家都准备好了，再打开这个门栓\n            startSignal.await();\n            doWork();\n            doneSignal.countDown();\n        } catch (InterruptedException ex) {\n        } // return;\n    }\n\n    void doWork() { ...}\n}\n```\n\n这个例子中，doneSignal 同第一个例子的使用，我们说说这里的 startSignal。N 个新开启的线程都调用了startSignal.await() 进行阻塞等待，它们阻塞在**栅栏**上，只有当条件满足的时候（startSignal.countDown()），它们才能同时通过这个栅栏，目的是让所有的线程站在一个起跑线上。\n\n![5](https://www.javadoop.com/blogimages/AbstractQueuedSynchronizer-3/5.png)\n\n如果始终只有一个线程调用 await 方法等待任务完成，那么 CountDownLatch 就会简单很多，所以之后的源码分析读者一定要在脑海中构建出这么一个场景：有 m 个线程是做任务的，有 n 个线程在某个栅栏上等待这 m 个线程做完任务，直到所有 m 个任务完成后，n 个线程同时通过栅栏。\n\n### 源码分析\n\nTalk is cheap, show me the code.\n\n构造方法，需要传入一个不小于 0 的整数：\n\n```\npublic CountDownLatch(int count) {\n    if (count < 0) throw new IllegalArgumentException(\"count < 0\");\n    this.sync = new Sync(count);\n}\n// 老套路了，内部封装一个 Sync 类继承自 AQS\nprivate static final class Sync extends AbstractQueuedSynchronizer {\n    Sync(int count) {\n        // 这样就 state == count 了\n        setState(count);\n    }\n    ...\n}\n```\n\n> 代码都是套路，先分析套路：AQS 里面的 state 是一个整数值，这边用一个 int count 参数其实初始化就是设置了这个值，所有调用了 await 方法的等待线程会挂起，然后有其他一些线程会做 state = state - 1 操作，当 state 减到 0 的同时，那个将 state 减为 0 的线程会负责唤醒 所有调用了 await 方法的线程。都是套路啊，只是 Doug Lea 的套路很深，代码很巧妙，不然我们也没有要分析源码的必要。\n\n对于 CountDownLatch，我们仅仅需要关心两个方法，一个是 countDown() 方法，另一个是 await() 方法。\n\ncountDown() 方法每次调用都会将 state 减 1，直到 state 的值为 0；而 await 是一个阻塞方法，当 state 减为 0 的时候，await 方法才会返回。await 可以被多个线程调用，读者这个时候脑子里要有个图：所有调用了 await 方法的线程阻塞在 AQS 的阻塞队列中，等待条件满足（state == 0），将线程从队列中一个个唤醒过来。\n\n我们用以下程序来分析源码，t1 和 t2 负责调用 countDown() 方法，t3 和 t4 调用 await 方法阻塞：\n\n```\npublic class CountDownLatchDemo {\n\n    public static void main(String[] args) {\n\n        CountDownLatch latch = new CountDownLatch(2);\n\n        Thread t1 = new Thread(new Runnable() {\n            @Override\n            public void run() {\n                try {\n                    Thread.sleep(5000);\n                } catch (InterruptedException ignore) {\n                }\n                // 休息 5 秒后(模拟线程工作了 5 秒)，调用 countDown()\n                latch.countDown();\n            }\n        }, \"t1\");\n\n        Thread t2 = new Thread(new Runnable() {\n            @Override\n            public void run() {\n                try {\n                    Thread.sleep(10000);\n                } catch (InterruptedException ignore) {\n                }\n                // 休息 10 秒后(模拟线程工作了 10 秒)，调用 countDown()\n                latch.countDown();\n            }\n        }, \"t2\");\n\n        t1.start();\n        t2.start();\n\n        Thread t3 = new Thread(new Runnable() {\n            @Override\n            public void run() {\n                try {\n                    // 阻塞，等待 state 减为 0\n                    latch.await();\n                    System.out.println(\"线程 t3 从 await 中返回了\");\n                } catch (InterruptedException e) {\n                    System.out.println(\"线程 t3 await 被中断\");\n                    Thread.currentThread().interrupt();\n                }\n            }\n        }, \"t3\");\n        Thread t4 = new Thread(new Runnable() {\n            @Override\n            public void run() {\n                try {\n                    // 阻塞，等待 state 减为 0\n                    latch.await();\n                    System.out.println(\"线程 t4 从 await 中返回了\");\n                } catch (InterruptedException e) {\n                    System.out.println(\"线程 t4 await 被中断\");\n                    Thread.currentThread().interrupt();\n                }\n            }\n        }, \"t4\");\n\n        t3.start();\n        t4.start();\n    }\n}\n```\n\n上述程序，大概在过了 10 秒左右的时候，会输出：\n\n```\n线程 t3 从 await 中返回了\n线程 t4 从 await 中返回了\n```\n\n> 这两条输出，顺序不是绝对的\n> \n> 后面的分析，我们假设 t3 先进入阻塞队列\n\n接下来，我们按照流程一步一步走：先 await 等待，然后被唤醒，await 方法返回。\n\n首先，我们来看 await() 方法，它代表线程阻塞，等待 state 的值减为 0。\n\n```\npublic void await() throws InterruptedException {\n    sync.acquireSharedInterruptibly(1);\n}\npublic final void acquireSharedInterruptibly(int arg)\n        throws InterruptedException {\n    // 这也是老套路了，我在第二篇的中断那一节说过了\n    if (Thread.interrupted())\n        throw new InterruptedException();\n\n    // t3 和 t4 调用 await 的时候，state 都大于 0（state 此时为 2）。\n    // 也就是说，这个 if 返回 true，然后往里看\n    if (tryAcquireShared(arg) < 0)\n        doAcquireSharedInterruptibly(arg);\n}\n// 只有当 state == 0 的时候，这个方法才会返回 1\nprotected int tryAcquireShared(int acquires) {\n    return (getState() == 0) ? 1 : -1;\n}\n```\n\n从方法名我们就可以看出，这个方法是获取共享锁，并且此方法是可中断的（中断的时候抛出 InterruptedException 退出这个方法）。\n\n```\nprivate void doAcquireSharedInterruptibly(int arg)\n    throws InterruptedException {\n    // 1\\. 入队\n    final Node node = addWaiter(Node.SHARED);\n    boolean failed = true;\n    try {\n        for (;;) {\n            final Node p = node.predecessor();\n            if (p == head) {\n                // 同上，只要 state 不等于 0，那么这个方法返回 -1\n                int r = tryAcquireShared(arg);\n                if (r >= 0) {\n                    setHeadAndPropagate(node, r);\n                    p.next = null; // help GC\n                    failed = false;\n                    return;\n                }\n            }\n            // 2\n            if (shouldParkAfterFailedAcquire(p, node) &&\n                parkAndCheckInterrupt())\n                throw new InterruptedException();\n        }\n    } finally {\n        if (failed)\n            cancelAcquire(node);\n    }\n}\n```\n\n我们来仔细分析这个方法，线程 t3 经过第 1 步 addWaiter 入队以后，我们应该可以得到这个：\n\n![2](https://www.javadoop.com/blogimages/AbstractQueuedSynchronizer-3/2.png)\n\n由于 tryAcquireShared 这个方法会返回 -1，所以 if (r >= 0) 这个分支不会进去。到 shouldParkAfterFailedAcquire 的时候，t3 将 head 的 waitStatus 值设置为 -1，如下：\n\n![3](https://www.javadoop.com/blogimages/AbstractQueuedSynchronizer-3/3.png)\n\n然后进入到 parkAndCheckInterrupt 的时候，t3 挂起。\n\n我们再分析 t4 入队，t4 会将前驱节点 t3 所在节点的 waitStatus 设置为 -1，t4 入队后，应该是这样的：\n\n![4](https://www.javadoop.com/blogimages/AbstractQueuedSynchronizer-3/4.png)\n\n然后，t4 也挂起。接下来，t3 和 t4 就等待唤醒了。\n\n接下来，我们来看唤醒的流程。为了让下面的示意图更丰富些，我们假设用 10 初始化 CountDownLatch。\n\n![1](https://www.javadoop.com/blogimages/AbstractQueuedSynchronizer-3/1.png)\n\n当然，我们的例子中，其实没有 10 个线程，只有 2 个线程 t1 和 t2，只是为了让图好看些罢了。\n\n我们再一步步看具体的流程。首先，我们看 countDown() 方法:\n\n```\npublic void countDown() {\n    sync.releaseShared(1);\n}\npublic final boolean releaseShared(int arg) {\n    // 只有当 state 减为 0 的时候，tryReleaseShared 才返回 true\n    // 否则只是简单的 state = state - 1 那么 countDown() 方法就结束了\n    //    将 state 减到 0 的那个操作才是最复杂的，继续往下吧\n    if (tryReleaseShared(arg)) {\n        // 唤醒 await 的线程\n        doReleaseShared();\n        return true;\n    }\n    return false;\n}\n// 这个方法很简单，用自旋的方法实现 state 减 1\nprotected boolean tryReleaseShared(int releases) {\n    for (;;) {\n        int c = getState();\n        if (c == 0)\n            return false;\n        int nextc = c-1;\n        if (compareAndSetState(c, nextc))\n            return nextc == 0;\n    }\n}\n```\n\ncountDown 方法就是每次调用都将 state 值减 1，如果 state 减到 0 了，那么就调用下面的方法进行唤醒阻塞队列中的线程：\n\n```\n// 调用这个方法的时候，state == 0\n// 这个方法先不要看所有的代码，按照思路往下到我写注释的地方，我们先跑通一个流程，其他的之后还会仔细分析\nprivate void doReleaseShared() {\n    for (;;) {\n        Node h = head;\n        if (h != null && h != tail) {\n            int ws = h.waitStatus;\n            // t3 入队的时候，已经将头节点的 waitStatus 设置为 Node.SIGNAL（-1） 了\n            if (ws == Node.SIGNAL) {\n                // 将 head 的 waitStatue 设置为 0\n                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))\n                    continue;            // loop to recheck cases\n                // 就是这里，唤醒 head 的后继节点，也就是阻塞队列中的第一个节点\n                // 在这里，也就是唤醒 t3\n                unparkSuccessor(h);\n            }\n            else if (ws == 0 &&\n                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) // todo\n                continue;                // loop on failed CAS\n        }\n        if (h == head)                   // loop if head changed\n            break;\n    }\n}\n```\n\n一旦 t3 被唤醒后，我们继续回到 await 的这段代码，parkAndCheckInterrupt 返回，我们先不考虑中断的情况：\n\n```\nprivate void doAcquireSharedInterruptibly(int arg)\n    throws InterruptedException {\n    final Node node = addWaiter(Node.SHARED);\n    boolean failed = true;\n    try {\n        for (;;) {\n            final Node p = node.predecessor();\n            if (p == head) {\n                int r = tryAcquireShared(arg);\n                if (r >= 0) {\n                    setHeadAndPropagate(node, r); // 2\\. 这里是下一步\n                    p.next = null; // help GC\n                    failed = false;\n                    return;\n                }\n            }\n            if (shouldParkAfterFailedAcquire(p, node) &&\n                // 1\\. 唤醒后这个方法返回\n                parkAndCheckInterrupt())\n                throw new InterruptedException();\n        }\n    } finally {\n        if (failed)\n            cancelAcquire(node);\n    }\n}\n```\n\n接下来，t3 会进到 setHeadAndPropagate(node, r) 这个方法，先把 head 给占了，然后唤醒队列中其他的线程：\n\n```\nprivate void setHeadAndPropagate(Node node, int propagate) {\n    Node h = head; // Record old head for check below\n    setHead(node);\n\n    // 下面说的是，唤醒当前 node 之后的节点，即 t3 已经醒了，马上唤醒 t4\n    // 类似的，如果 t4 后面还有 t5，那么 t4 醒了以后，马上将 t5 给唤醒了\n    if (propagate > 0 || h == null || h.waitStatus < 0 ||\n        (h = head) == null || h.waitStatus < 0) {\n        Node s = node.next;\n        if (s == null || s.isShared())\n            // 又是这个方法，只是现在的 head 已经不是原来的空节点了，是 t3 的节点了\n            doReleaseShared();\n    }\n}\n```\n\n又回到这个方法了，那么接下来，我们好好分析 doReleaseShared 这个方法，我们根据流程，头节点 head 此时是 t3 节点了：\n\n```\n// 调用这个方法的时候，state == 0\nprivate void doReleaseShared() {\n    for (;;) {\n        Node h = head;\n        // 1\\. h == null: 说明阻塞队列为空\n        // 2\\. h == tail: 说明头结点可能是刚刚初始化的头节点，\n        //   或者是普通线程节点，但是此节点既然是头节点了，那么代表已经被唤醒了，阻塞队列没有其他节点了\n        // 所以这两种情况不需要进行唤醒后继节点\n        if (h != null && h != tail) {\n            int ws = h.waitStatus;\n            // t4 将头节点(此时是 t3)的 waitStatus 设置为 Node.SIGNAL（-1） 了\n            if (ws == Node.SIGNAL) {\n                // 这里 CAS 失败的场景请看下面的解读\n                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))\n                    continue;            // loop to recheck cases\n                // 就是这里，唤醒 head 的后继节点，也就是阻塞队列中的第一个节点\n                // 在这里，也就是唤醒 t4\n                unparkSuccessor(h);\n            }\n            else if (ws == 0 &&\n                     // 这个 CAS 失败的场景是：执行到这里的时候，刚好有一个节点入队，入队会将这个 ws 设置为 -1\n                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))\n                continue;                // loop on failed CAS\n        }\n        // 如果到这里的时候，前面唤醒的线程已经占领了 head，那么再循环\n        // 否则，就是 head 没变，那么退出循环，\n        // 退出循环是不是意味着阻塞队列中的其他节点就不唤醒了？当然不是，唤醒的线程之后还是会调用这个方法的\n        if (h == head)                   // loop if head changed\n            break;\n    }\n}\n```\n\n我们分析下最后一个 if 语句，然后才能解释第一个 CAS 为什么可能会失败：\n\n1.  h == head：说明头节点还没有被刚刚用 unparkSuccessor 唤醒的线程（这里可以理解为 t4）占有，此时 break 退出循环。\n2.  h != head：头节点被刚刚唤醒的线程（这里可以理解为 t4）占有，那么这里重新进入下一轮循环，唤醒下一个节点（这里是 t4 ）。我们知道，等到 t4 被唤醒后，其实是会主动唤醒 t5、t6、t7...，那为什么这里要进行下一个循环来唤醒 t5 呢？我觉得是出于吞吐量的考虑。\n\n满足上面的 2 的场景，那么我们就能知道为什么上面的 CAS 操作 compareAndSetWaitStatus(h, Node.SIGNAL, 0) 会失败了？\n\n因为当前进行 for 循环的线程到这里的时候，可能刚刚唤醒的线程 t4 也刚刚好到这里了，那么就有可能 CAS 失败了。\n\nfor 循环第一轮的时候会唤醒 t4，t4 醒后会将自己设置为头节点，如果在 t4 设置头节点后，for 循环才跑到 if (h == head)，那么此时会返回 false，for 循环会进入下一轮。t4 唤醒后也会进入到这个方法里面，那么 for 循环第二轮和 t4 就有可能在这个 CAS 相遇，那么就只会有一个成功了。\n\n## CyclicBarrier\n\n字面意思是“可重复使用的栅栏”或“周期性的栅栏”，总之不是用了一次就没用了的，CyclicBarrier 相比 CountDownLatch 来说，要简单很多，其源码没有什么高深的地方，它是 ReentrantLock 和 Condition 的组合使用。看如下示意图，CyclicBarrier 和 CountDownLatch 是不是很像，只是 CyclicBarrier 可以有不止一个栅栏，因为它的栅栏（Barrier）可以重复使用（Cyclic）。\n\n![cyclicbarrier-2](https://www.javadoop.com/blogimages/AbstractQueuedSynchronizer-3/cyclicbarrier-2.png)\n\n首先，CyclicBarrier 的源码实现和 CountDownLatch 大相径庭，CountDownLatch 基于 AQS 的共享模式的使用，而 CyclicBarrier 基于 Condition 来实现。\n\n因为 CyclicBarrier 的源码相对来说简单许多，读者只要熟悉了前面关于 Condition 的分析，那么这里的源码是毫无压力的，就是几个特殊概念罢了。\n\n先用一张图来描绘下 CyclicBarrier 里面的一些概念，和它的基本使用流程：\n\n![cyclicbarrier-3](https://www.javadoop.com/blogimages/AbstractQueuedSynchronizer-3/cyclicbarrier-3.png)\n\n> 看图我们也知道了，CyclicBarrier 的源码最重要的就是 await() 方法了。\n\n大家先把图看完，然后我们开始源码分析：\n\n```\npublic class CyclicBarrier {\n    // 我们说了，CyclicBarrier 是可以重复使用的，我们把每次从开始使用到穿过栅栏当做\"一代\"，或者\"一个周期\"\n    private static class Generation {\n        boolean broken = false;\n    }\n\n    /** The lock for guarding barrier entry */\n    private final ReentrantLock lock = new ReentrantLock();\n\n    // CyclicBarrier 是基于 Condition 的\n    // Condition 是“条件”的意思，CyclicBarrier 的等待线程通过 barrier 的“条件”是大家都到了栅栏上\n    private final Condition trip = lock.newCondition();\n\n    // 参与的线程数\n    private final int parties;\n\n    // 如果设置了这个，代表越过栅栏之前，要执行相应的操作\n    private final Runnable barrierCommand;\n\n    // 当前所处的“代”\n    private Generation generation = new Generation();\n\n    // 还没有到栅栏的线程数，这个值初始为 parties，然后递减\n    // 还没有到栅栏的线程数 = parties - 已经到栅栏的数量\n    private int count;\n\n    public CyclicBarrier(int parties, Runnable barrierAction) {\n        if (parties <= 0) throw new IllegalArgumentException();\n        this.parties = parties;\n        this.count = parties;\n        this.barrierCommand = barrierAction;\n    }\n\n    public CyclicBarrier(int parties) {\n        this(parties, null);\n    }\n```\n\n首先，先看怎么开启新的一代：\n\n```\n// 开启新的一代，当最后一个线程到达栅栏上的时候，调用这个方法来唤醒其他线程，同时初始化“下一代”\nprivate void nextGeneration() {\n    // 首先，需要唤醒所有的在栅栏上等待的线程\n    trip.signalAll();\n    // 更新 count 的值\n    count = parties;\n    // 重新生成“新一代”\n    generation = new Generation();\n}\n```\n\n> 开启新的一代，类似于重新实例化一个 CyclicBarrier 实例\n\n看看怎么打破一个栅栏：\n\n```\nprivate void breakBarrier() {\n    // 设置状态 broken 为 true\n    generation.broken = true;\n    // 重置 count 为初始值 parties\n    count = parties;\n    // 唤醒所有已经在等待的线程\n    trip.signalAll();\n}\n```\n\n这两个方法之后用得到，现在开始分析最重要的等待通过栅栏方法 await 方法：\n\n```\n// 不带超时机制\npublic int await() throws InterruptedException, BrokenBarrierException {\n    try {\n        return dowait(false, 0L);\n    } catch (TimeoutException toe) {\n        throw new Error(toe); // cannot happen\n    }\n}\n// 带超时机制，如果超时抛出 TimeoutException 异常\npublic int await(long timeout, TimeUnit unit)\n    throws InterruptedException,\n           BrokenBarrierException,\n           TimeoutException {\n    return dowait(true, unit.toNanos(timeout));\n}\n```\n\n继续往里看：\n\n```\nprivate int dowait(boolean timed, long nanos)\n        throws InterruptedException, BrokenBarrierException,\n               TimeoutException {\n    final ReentrantLock lock = this.lock;\n    // 先要获取到锁，然后在 finally 中要记得释放锁\n    // 如果记得 Condition 部分的话，我们知道 condition 的 await() 会释放锁，被 signal() 唤醒的时候需要重新获取锁\n    lock.lock();\n    try {\n        final Generation g = generation;\n        // 检查栅栏是否被打破，如果被打破，抛出 BrokenBarrierException 异常\n        if (g.broken)\n            throw new BrokenBarrierException();\n        // 检查中断状态，如果中断了，抛出 InterruptedException 异常\n        if (Thread.interrupted()) {\n            breakBarrier();\n            throw new InterruptedException();\n        }\n        // index 是这个 await 方法的返回值\n        // 注意到这里，这个是从 count 递减后得到的值\n        int index = --count;\n\n        // 如果等于 0，说明所有的线程都到栅栏上了，准备通过\n        if (index == 0) {  // tripped\n            boolean ranAction = false;\n            try {\n                // 如果在初始化的时候，指定了通过栅栏前需要执行的操作，在这里会得到执行\n                final Runnable command = barrierCommand;\n                if (command != null)\n                    command.run();\n                // 如果 ranAction 为 true，说明执行 command.run() 的时候，没有发生异常退出的情况\n                ranAction = true;\n                // 唤醒等待的线程，然后开启新的一代\n                nextGeneration();\n                return 0;\n            } finally {\n                if (!ranAction)\n                    // 进到这里，说明执行指定操作的时候，发生了异常，那么需要打破栅栏\n                    // 之前我们说了，打破栅栏意味着唤醒所有等待的线程，设置 broken 为 true，重置 count 为 parties\n                    breakBarrier();\n            }\n        }\n\n        // loop until tripped, broken, interrupted, or timed out\n        // 如果是最后一个线程调用 await，那么上面就返回了\n        // 下面的操作是给那些不是最后一个到达栅栏的线程执行的\n        for (;;) {\n            try {\n                // 如果带有超时机制，调用带超时的 Condition 的 await 方法等待，直到最后一个线程调用 await\n                if (!timed)\n                    trip.await();\n                else if (nanos > 0L)\n                    nanos = trip.awaitNanos(nanos);\n            } catch (InterruptedException ie) {\n                // 如果到这里，说明等待的线程在 await（是 Condition 的 await）的时候被中断\n                if (g == generation && ! g.broken) {\n                    // 打破栅栏\n                    breakBarrier();\n                    // 打破栅栏后，重新抛出这个 InterruptedException 异常给外层调用的方法\n                    throw ie;\n                } else {\n                    // 到这里，说明 g != generation, 说明新的一代已经产生，即最后一个线程 await 执行完成，\n                    // 那么此时没有必要再抛出 InterruptedException 异常，记录下来这个中断信息即可\n                    // 或者是栅栏已经被打破了，那么也不应该抛出 InterruptedException 异常，\n                    // 而是之后抛出 BrokenBarrierException 异常\n                    Thread.currentThread().interrupt();\n                }\n            }\n\n              // 唤醒后，检查栅栏是否是“破的”\n            if (g.broken)\n                throw new BrokenBarrierException();\n\n            // 这个 for 循环除了异常，就是要从这里退出了\n            // 我们要清楚，最后一个线程在执行完指定任务(如果有的话)，会调用 nextGeneration 来开启一个新的代\n            // 然后释放掉锁，其他线程从 Condition 的 await 方法中得到锁并返回，然后到这里的时候，其实就会满足 g != generation 的\n            // 那什么时候不满足呢？barrierCommand 执行过程中抛出了异常，那么会执行打破栅栏操作，\n            // 设置 broken 为true，然后唤醒这些线程。这些线程会从上面的 if (g.broken) 这个分支抛 BrokenBarrierException 异常返回\n            // 当然，还有最后一种可能，那就是 await 超时，此种情况不会从上面的 if 分支异常返回，也不会从这里返回，会执行后面的代码\n            if (g != generation)\n                return index;\n\n            // 如果醒来发现超时了，打破栅栏，抛出异常\n            if (timed && nanos <= 0L) {\n                breakBarrier();\n                throw new TimeoutException();\n            }\n        }\n    } finally {\n        lock.unlock();\n    }\n}\n```\n\n好了，我想我应该讲清楚了吧，我好像几乎没有漏掉任何一行代码吧？\n\n下面开始收尾工作。\n\n首先，我们看看怎么得到有多少个线程到了栅栏上，处于等待状态：\n\n```\npublic int getNumberWaiting() {\n    final ReentrantLock lock = this.lock;\n    lock.lock();\n    try {\n        return parties - count;\n    } finally {\n        lock.unlock();\n    }\n}\n```\n\n判断一个栅栏是否被打破了，这个很简单，直接看 broken 的值即可：\n\n```\npublic boolean isBroken() {\n    final ReentrantLock lock = this.lock;\n    lock.lock();\n    try {\n        return generation.broken;\n    } finally {\n        lock.unlock();\n    }\n}\n```\n\n前面我们在说 await 的时候也几乎说清楚了，什么时候栅栏会被打破，总结如下：\n\n1.  中断，我们说了，如果某个等待的线程发生了中断，那么会打破栅栏，同时抛出 InterruptedException 异常；\n2.  超时，打破栅栏，同时抛出 TimeoutException 异常；\n3.  指定执行的操作抛出了异常，这个我们前面也说过。\n\n最后，我们来看看怎么重置一个栅栏：\n\n```\npublic void reset() {\n    final ReentrantLock lock = this.lock;\n    lock.lock();\n    try {\n        breakBarrier();   // break the current generation\n        nextGeneration(); // start a new generation\n    } finally {\n        lock.unlock();\n    }\n}\n```\n\n我们设想一下，如果初始化时，指定了线程 parties = 4，前面有 3 个线程调用了 await 等待，在第 4 个线程调用 await 之前，我们调用 reset 方法，那么会发生什么？\n\n首先，打破栅栏，那意味着所有等待的线程（3个等待的线程）会唤醒，await 方法会通过抛出 BrokenBarrierException 异常返回。然后开启新的一代，重置了 count 和 generation，相当于一切归零了。\n\n怎么样，CyclicBarrier 源码很简单吧。\n\n## Semaphore\n\n有了 CountDownLatch 的基础后，分析 Semaphore 会简单很多。Semaphore 是什么呢？它类似一个资源池（读者可以类比线程池），每个线程需要调用 acquire() 方法获取资源，然后才能执行，执行完后，需要 release 资源，让给其他的线程用。\n\n大概大家也可以猜到，Semaphore 其实也是 AQS 中共享锁的使用，因为每个线程共享一个池嘛。\n\n套路解读：创建 Semaphore 实例的时候，需要一个参数 permits，这个基本上可以确定是设置给 AQS 的 state 的，然后每个线程调用 acquire 的时候，执行 state = state - 1，release 的时候执行 state = state + 1，当然，acquire 的时候，如果 state = 0，说明没有资源了，需要等待其他线程 release。\n\n构造方法：\n\n```\npublic Semaphore(int permits) {\n    sync = new NonfairSync(permits);\n}\n\npublic Semaphore(int permits, boolean fair) {\n    sync = fair ? new FairSync(permits) : new NonfairSync(permits);\n}\n```\n\n这里和 ReentrantLock 类似，用了公平策略和非公平策略。\n\n看 acquire 方法：\n\n```\npublic void acquire() throws InterruptedException {\n    sync.acquireSharedInterruptibly(1);\n}\npublic void acquireUninterruptibly() {\n    sync.acquireShared(1);\n}\npublic void acquire(int permits) throws InterruptedException {\n    if (permits < 0) throw new IllegalArgumentException();\n    sync.acquireSharedInterruptibly(permits);\n}\npublic void acquireUninterruptibly(int permits) {\n    if (permits < 0) throw new IllegalArgumentException();\n    sync.acquireShared(permits);\n}\n```\n\n这几个方法也是老套路了，大家基本都懂了吧，这边多了两个可以传参的 acquire 方法，不过大家也都懂的吧，如果我们需要一次获取超过一个的资源，会用得着这个的。\n\n我们接下来看不抛出 InterruptedException 异常的 acquireUninterruptibly() 方法吧：\n\n```\npublic void acquireUninterruptibly() {\n    sync.acquireShared(1);\n}\npublic final void acquireShared(int arg) {\n    if (tryAcquireShared(arg) < 0)\n        doAcquireShared(arg);\n}\n```\n\n前面说了，Semaphore 分公平策略和非公平策略，我们对比一下两个 tryAcquireShared 方法：\n\n```\n// 公平策略：\nprotected int tryAcquireShared(int acquires) {\n    for (;;) {\n        // 区别就在于是不是会先判断是否有线程在排队，然后才进行 CAS 减操作\n        if (hasQueuedPredecessors())\n            return -1;\n        int available = getState();\n        int remaining = available - acquires;\n        if (remaining < 0 ||\n            compareAndSetState(available, remaining))\n            return remaining;\n    }\n}\n// 非公平策略：\nprotected int tryAcquireShared(int acquires) {\n    return nonfairTryAcquireShared(acquires);\n}\nfinal int nonfairTryAcquireShared(int acquires) {\n    for (;;) {\n        int available = getState();\n        int remaining = available - acquires;\n        if (remaining < 0 ||\n            compareAndSetState(available, remaining))\n            return remaining;\n    }\n}\n```\n\n也是老套路了，所以从源码分析角度的话，我们其实不太需要关心是不是公平策略还是非公平策略，它们的区别往往就那么一两行。\n\n我们再回到 acquireShared 方法，\n\n```\npublic final void acquireShared(int arg) {\n    if (tryAcquireShared(arg) < 0)\n        doAcquireShared(arg);\n}\n```\n\n由于 tryAcquireShared(arg) 返回小于 0 的时候，说明 state 已经小于 0 了（没资源了），此时 acquire 不能立马拿到资源，需要进入到阻塞队列等待，虽然贴了很多代码，不在乎多这点了：\n\n```\nprivate void doAcquireShared(int arg) {\n    final Node node = addWaiter(Node.SHARED);\n    boolean failed = true;\n    try {\n        boolean interrupted = false;\n        for (;;) {\n            final Node p = node.predecessor();\n            if (p == head) {\n                int r = tryAcquireShared(arg);\n                if (r >= 0) {\n                    setHeadAndPropagate(node, r);\n                    p.next = null; // help GC\n                    if (interrupted)\n                        selfInterrupt();\n                    failed = false;\n                    return;\n                }\n            }\n            if (shouldParkAfterFailedAcquire(p, node) &&\n                parkAndCheckInterrupt())\n                interrupted = true;\n        }\n    } finally {\n        if (failed)\n            cancelAcquire(node);\n    }\n}\n```\n\n这个方法我就不介绍了，线程挂起后等待有资源被 release 出来。接下来，我们就要看 release 的方法了：\n\n```\n// 任务介绍，释放一个资源\npublic void release() {\n    sync.releaseShared(1);\n}\npublic final boolean releaseShared(int arg) {\n    if (tryReleaseShared(arg)) {\n        doReleaseShared();\n        return true;\n    }\n    return false;\n}\n\nprotected final boolean tryReleaseShared(int releases) {\n    for (;;) {\n        int current = getState();\n        int next = current + releases;\n        // 溢出，当然，我们一般也不会用这么大的数\n        if (next < current) // overflow\n            throw new Error(\"Maximum permit count exceeded\");\n        if (compareAndSetState(current, next))\n            return true;\n    }\n}\n```\n\ntryReleaseShared 方法总是会返回 true，然后是 doReleaseShared，这个也是我们熟悉的方法了，我就贴下代码，不分析了，这个方法用于唤醒所有的等待线程：\n\n```\nprivate void doReleaseShared() {\n    for (;;) {\n        Node h = head;\n        if (h != null && h != tail) {\n            int ws = h.waitStatus;\n            if (ws == Node.SIGNAL) {\n                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))\n                    continue;            // loop to recheck cases\n                unparkSuccessor(h);\n            }\n            else if (ws == 0 &&\n                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))\n                continue;                // loop on failed CAS\n        }\n        if (h == head)                   // loop if head changed\n            break;\n    }\n}\n```\n\nSemphore 的源码确实很简单，基本上都是分析过的老代码的组合使用了。\n\n## 总结\n\n写到这里，终于把 AbstractQueuedSynchronizer 基本上说完了，对于 Java 并发，Doug Lea 真的是神一样的存在。日后我们还会接触到很多 Doug Lea 的代码，希望我们大家都可以朝着大神的方向不断打磨自己的技术，少一些高大上的架构，多一些实实在在的优秀代码吧。\n\n（全文完）\n"
  },
  {
    "path": "docs/Java/concurrency/Java并发指南：ForkJoin并发框架与工作窃取算法剖析.md",
    "content": "# 目录\n  * [Fork/Join框架介绍](#forkjoin框架介绍)\n  * [简介](#简介)\n  * [工作窃取算法介绍](#工作窃取算法介绍)\n\n本文转自：https://www.imooc.com/article/24822\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章同步发于我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《Java并发指南》其中一篇，本文大部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何全面深入地学习Java并发技术，从Java多线程基础，再到并发编程的基础知识，从Java并发包的入门和实战，再到JUC的源码剖析，一步步地学习Java并发编程，并上手进行实战，以便让你更完整地了解整个Java并发编程知识体系，形成自己的知识框架。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供一些对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n<!--more -->\n## Fork/Join框架介绍\n\nFork/Join框架是Java7提供的一个用于并行执行任务的框架， 是一个把大任务分割成若干个小任务，最终汇总每个小任务结果后得到大任务结果的框架。使用工作窃取（work-stealing）算法，主要用于实现“分而治之”。\n\n## 简介\n\n通常，使用Java来开发一个简单的并发应用程序时，会创建一些**Runnable**对象，然后创建对应的**Thread**对象来控制程序中这些线程的创建、执行以及线程的状态。自从Java 5开始引入了**Executor**和**ExecutorService**接口以及实现这两个接口的类（比如**ThreadPoolExecutor**）之后，使得Java在并发支持上得到了进一步的提升。\n\n**执行器框架（Executor Framework）**将任务的创建和执行进行了分离，通过这个框架，只需要实现**Runnable**接口的对象和使用**Executor**对象，然后将**Runnable**对象发送给执行器。执行器再负责运行这些任务所需要的线程，包括线程的创建，线程的管理以及线程的结束。\n\n**Java 7**则又更进了一步，它包括了ExecutorService接口的另一种实现，用来解决特殊类型的问题，它就是**Fork/Join框架**，有时也称**分解/合并框架**。\n\n**Fork/Join框架**是用来解决能够通过**分治技术（Divide and Conquer Technique）**将问题拆分成小任务的问题。在一个任务中，先检查将要解决的问题的大小，如果大于一个设定的大小，那就将问题拆分成可以通过框架来执行的小任务。如果问题的大小比设定的大小要小，就可以直接在任务里解决这个问题，然后，根据需要返回任务的结果。下面的图形总结了这个原理。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404203219.png)\n\n没有固定的公式来决定问题的**参考大小（Reference Size）**，从而决定一个任务是需要进行拆分或不需要拆分，拆分与否仍是依赖于任务本身的特性。可以使用在任务中将要处理的元素的数目和任务执行所需要的时间来决定参考大小。测试不同的参考大小来决定解决问题最好的一个方案，将**ForkJoinPool**类看作一个特殊的**Executor**执行器类型。这个框架基于以下两种操作。\n\n*   **分解（Fork）**操作：当需要将一个任务拆分成更小的多个任务时，在框架中执行这些任务；\n*   **合并（Join）**操作：当一个主任务等待其创建的多个子任务的完成执行。\n\n**Fork/Join框架和执行器框架（Executor Framework）**主要的区别在于**工作窃取算法（Work-Stealing Algorithm）**。与执行器框架不同，使用Join操作让一个主任务等待它所创建的子任务的完成，执行这个任务的线程称之为**工作者线程（Worker Thread）**。工作者线程寻找其他仍未被执行的任务，然后开始执行。通过这种方式，这些线程在运行时拥有所有的优点，进而提升应用程序的性能。\n\n为了达到这个目标，通过**Fork/Join框架**执行的任务有以下限制。\n\n*   任务只能使用**fork()**和**join()**操作当作同步机制。如果使用其他的同步机制，工作者线程就不能执行其他任务，当然这些任务是在同步操作里时。比如，如果在**Fork/Join 框架**中将一个任务休眠，正在执行这个任务的工作者线程在休眠期内不能执行另一个任务。\n*   任务不能执行I/O操作，比如文件数据的读取与写入。\n*   任务不能抛出非运行时异常（Checked Exception），必须在代码中处理掉这些异常。\n\n**Fork/Join****框架**的核心是由下列两个类组成的。\n\n*   **ForkJoinPool：**这个类实现了ExecutorService接口和工作窃取算法（Work-Stealing Algorithm）。它管理工作者线程，并提供任务的状态信息，以及任务的执行信息。\n*   **ForkJoinTask：**这个类是一个将在**ForkJoinPool**中执行的任务的基类。\n\n**Fork/Join框架**提供了在一个任务里执行**fork()**和**join()**操作的机制和控制任务状态的方法。通常，为了实现**Fork/Join**任务，需要实现一个以下两个类之一的子类。\n\n*   **RecursiveAction：**用于任务没有返回结果的场景。\n*   **RecursiveTask：**用于任务有返回结果的场景。\n\n## 工作窃取算法介绍\n\n工作窃取（work-stealing）算法优点是充分利用线程进行并行计算，并减少了线程间的竞争，其缺点是在某些情况下还是存在竞争，比如双端队列里只有一个任务时。并且消耗了更多的系统资源，比如创建多个线程和多个双端队列。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404203237.png)\n\n**Fork/Join框架基础类**\n\n*   ForkJoinPool： 用来执行Task，或生成新的ForkJoinWorkerThread，执行 ForkJoinWorkerThread 间的 work-stealing 逻辑。ForkJoinPool 不是为了替代 ExecutorService，而是它的补充，在某些应用场景下性能比 ExecutorService 更好。\n*   ForkJoinTask： 执行具体的分支逻辑，声明以同步/异步方式进行执行\n*   ForkJoinWorkerThread： 是 ForkJoinPool 内的 worker thread，执行\n*   ForkJoinTask, 内部有 ForkJoinPool.WorkQueue来保存要执行的ForkJoinTask。\n*   ForkJoinPool.WorkQueue：保存要执行的ForkJoinTask。\n\n**基本思想**\n\n*   ForkJoinPool 的每个工作线程都维护着一个工作队列（WorkQueue），这是一个双端队列（Deque），里面存放的对象是任务（ForkJoinTask）。\n*   每个工作线程在运行中产生新的任务（通常是因为调用了 fork()）时，会放入工作队列的队尾，并且工作线程在处理自己的工作队列时，使用的是 LIFO 方式，也就是说每次从队尾取出任务来执行。\n*   每个工作线程在处理自己的工作队列同时，会尝试窃取一个任务（或是来自于刚刚提交到 pool 的任务，或是来自于其他工作线程的工作队列），窃取的任务位于其他线程的工作队列的队首，也就是说工作线程在窃取其他工作线程的任务时，使用的是 FIFO 方式。\n*   在遇到 join() 时，如果需要 join 的任务尚未完成，则会先处理其他任务，并等待其完成。\n*   在既没有自己的任务，也没有可以窃取的任务时，进入休眠。\n\n**代码演示**\n\n大家学习时，通常借助的例子都类似于下面这段：\n\n```\n@Slf4j\npublic class ForkJoinTaskExample extends RecursiveTask<Integer> {\n\n    public static final int threshold = 2;\n    private int start;\n    private int end;\n\n    public ForkJoinTaskExample(int start, int end) {\n        this.start = start;\n        this.end = end;\n    }\n\n    @Override\n    protected Integer compute() {\n        int sum = 0;\n\n        //如果任务足够小就计算任务\n        boolean canCompute = (end - start) <= threshold;\n        if (canCompute) {\n            for (int i = start; i <= end; i++) {\n                sum += i;\n            }\n        } else {\n            // 如果任务大于阈值，就分裂成两个子任务计算\n            int middle = (start + end) / 2;\n            ForkJoinTaskExample leftTask = new ForkJoinTaskExample(start, middle);\n            ForkJoinTaskExample rightTask = new ForkJoinTaskExample(middle + 1, end);\n\n            // 执行子任务\n            leftTask.fork();\n            rightTask.fork();\n\n            // 等待任务执行结束合并其结果\n            int leftResult = leftTask.join();\n            int rightResult = rightTask.join();\n\n            // 合并子任务\n            sum = leftResult + rightResult;\n        }\n        return sum;\n    }\n\n    public static void main(String[] args) {\n        ForkJoinPool forkjoinPool = new ForkJoinPool();\n\n        //生成一个计算任务，计算1+2+3+4\n        ForkJoinTaskExample task = new ForkJoinTaskExample(1, 100);\n\n        //执行一个任务\n        Future<Integer> result = forkjoinPool.submit(task);\n\n        try {\n            log.info(\"result:{}\", result.get());\n        } catch (Exception e) {\n            log.error(\"exception\", e);\n        }\n    }\n}\n```\n\n**重点注意**\n\n需要特别注意的是：\n\n1.  ForkJoinPool 使用submit 或 invoke 提交的区别：invoke是同步执行，调用之后需要等待任务完成，才能执行后面的代码；submit是异步执行，只有在Future调用get的时候会阻塞。\n2.  这里继承的是RecursiveTask，还可以继承RecursiveAction。前者适用于有返回值的场景，而后者适合于没有返回值的场景\n3.  这一点是最容易忽略的地方，其实这里执行子任务调用fork方法并不是最佳的选择，最佳的选择是invokeAll方法。\n\n```\nleftTask.fork();  \nrightTask.fork();\n\n替换为\n\ninvokeAll(leftTask, rightTask);\n```\n\n具体说一下原理：对于Fork/Join模式，假如Pool里面线程数量是固定的，那么调用子任务的fork方法相当于A先分工给B，然后A当监工不干活，B去完成A交代的任务。所以上面的模式相当于浪费了一个线程。那么如果使用invokeAll相当于A分工给B后，A和B都去完成工作。这样可以更好的利用线程池，缩短执行的时间。\n\n"
  },
  {
    "path": "docs/Java/concurrency/Java并发指南：JMM中的final关键字解析.md",
    "content": "# 目录\n\n  * [一、properly constructed / this对象逸出](#一、properly-constructed--this对象逸出)\n  * [二、对象的安全发布](#二、对象的安全发布)\n  * [三、 final 关键字的内存语义](#三、-final-关键字的内存语义)\n  * [四、HotSpot VM中对final内存语义的实现](#四、hotspot-vm中对final内存语义的实现)\n* [参考文献](#参考文献)\n\n\n**本文转载自互联网，侵删**\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章首发于我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《Java并发指南》其中一篇，本文大部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何全面深入地学习Java并发技术，从Java多线程基础，再到并发编程的基础知识，从Java并发包的入门和实战，再到JUC的源码剖析，一步步地学习Java并发编程，并上手进行实战，以便让你更完整地了解整个Java并发编程知识体系，形成自己的知识框架。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供一些对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n## 一、properly constructed / this对象逸出\n在开始讲之前final之前，先了解一个概念，叫做 “properly constructed”。其含义是：在构造器创建对象的过程中，正在被创建的对象的引用没有发生 “逸出(escape)” 。\n````\npublic Test {\nprivate final int x;\nprivate int y;\nprivate static Test instance;\n\n\tpublic Test(int x, int y) {\n\t\tthis.x = x;\n\t\tthis.y = y;\n\t\tinstance = this;\n\t}\n}\n````\n在上面的例子中，在构造器中把正在创建的对象赋值给了一个静态变量instance，这种行为就叫“逸出”。\n\n## 二、对象的安全发布\n所谓对象的安全发布，意思就是构建一个被完整初始化的对象，防止一个未完全初始化的对象被访问。\n\n看下面的例子：\n````\npublic class Test {\nstatic MyObj obj;\n\n\tstatic class MyObj {\n\t\tint a, b, c, d;\n\t\tMyObj() {\n\t\t\ta = 1;\n\t\t\tb = 1;\n\t\t\tc = 1;\n\t\t\td = 1;\n\t\t}\n\t}\n\t\n\t// Thread 1\n\tpublic static void init() {\n\t\tobj = new MyObj();\n\t}\n\t\n\t// Thread 2\n\tpublic static void f() {\n\t\tif (obj == null) return;\n\t\tif (obj != null) {\n\t\t\tSystem.out.println(obj.a + obj.b + obj.c + obj.d);\n\t\t}\n\t}\n\n}\n````\n\n上述代码中，Thread 2 打印出的数将会是几呢？事实上，可能是0, 1, 2, 3, 4 中的任意一个值。\n\n如果打印出的数不是4，那么就说明线程2读到了未完全初始化的MyObj对象。\n\n为什么会出现这种情况呢？是因为 obj = new MyObj() 这个操作底层实际上分为以下3个步骤：\n\n在堆上给MyObj对象分配空间，对象的字段置为默认值\n执行构造器中的初始化语句进行初始化\n将堆上的MyObj对象的引用赋值给obj\n其中，步骤2和3是可能发生重排序的 (StoreStore重排序)，因此就Thread 2就可能看到 obj != null，但是a,b,c,d还没全部赋值完成的情况，也就是 Thread 1 不安全的发布了 MyObj 对象。\n\n扩展一下，在实现单例模式时，会什么要double checked且加上volatile，正是这个原因，防止不安全的发布。\n\n笔者在 x86 平台的 HotSpot 虚拟机中使用 jcstress 工具对不安全发布现象进行了测试，发现类似于上述的代码在 x86 平台的 HotSpot 虚拟机中并不会出现为完全初始化的情况，出现这种情况就说明，x86 平台的 HotSpot 虚拟机实现中，编译器没有对上述步骤2和3进行重排序，而x86的内存模型又保证了x86不会发生StoreStore重排序，因此上述步骤2和3不会发生重排序，进而始终可以读到完全初始化过的对象。但是，在其他硬件平台或其他JVM中就未必如此了。 我们的Java代码不应该基于特定硬件平台或虚拟机实现之上，而是应该遵循JLS和JMM的规范！\n\n## 三、 final 关键字的内存语义\n\n1. 可见性\n   摘自[1]：\n\nThe values for an object’s final fields are set in its constructor. Assuming the object is constructed “correctly”, once an object is constructed, the values assigned to the final fields in the constructor will be visible to all other threads without synchronization. In addition, the visible values for any other object or array referenced by those final fields will be at least as up-to-date as the final fields.\n\n在JMM中规定，如果我们在构造器中对final字段进行初始化，并且构造器中没有发生this对象的逸出，那么无需任何同步措施，即可确保其他线程可以看到构造器中初始化给final字段的值。此外，如果这个final字段是一个引用类型，那么可以确保该引用类型对象引用到的对象或数组的内容都是至少和final字段一样新的值。\n\n这里的“至少和final字段一样新的值”，意思也就是 up to date as of the end of the object’s constructor ([1])，也就是说，对于引用类型的final变量，其他线程至少能够读到构造器结束时，这个final类型引用变量的成员的状态。\n\n实际上，final 关键字对可见性的影响在Java语言规范的 17.5.1. Semantics of final Fields 一节也作出了正式规范。针对final关键字，引入了两个偏序关系——Dereference Chain和Memory Chain，借助这两个偏序关系，可以在不同线程之间的 w ww 和 r 2 r2r2 操作之间建立 happens before 关系，如下图所示：\n\n\n而 happens before 关系又隐含着可见性，所以 w ww 写的内容对于 r 2 r2r2 是可见的。结合上图 w ww 到 r 2 r2r2 的关系链，我们也不难理解为什么之前说 “对于引用类型的final变量，其他线程至少能够读到构造器结束时，这个final类型引用变量的成员的状态” 了，借助上述关系同样可以建立修改final字段成员 和 读取final字段成员之间的happens before 关系。\n\n2. 有序性\n\n注意：重排序是针对单个线程（单个CPU）而言的。\n\n以下摘自文档 [2]：\n\nLoads and Stores of final fields act as “normal” accesses with respect to locks and volatiles, but impose two additional reordering rules:\n\n对final字段的load和store操作和对普通字段的load和store操作几乎一样，只不过针对final字段存在下面两条额外的重排序规则：\n\n规则1\n\n【① A store of a final field】 (inside a constructor) and, 【② if the field is a reference, any store that this final can reference】, cannot be reordered with 【③ a subsequent store (outside that constructor) of the reference to the object holding that field into a variable accessible to other threads】. For example, you cannot reorder\nx.finalField = v; ... ; sharedRef = x;\n\nThis comes into play for example when inlining constructors, where “...” spans the logical end of the constructor. You cannot move stores of finals within constructors down below a store outside of the constructor that might make the object visible to other threads. (As seen below, this may also require issuing a barrier). Similarly, you cannot reorder either of the first two with the third assignment in:\nv.afield = 1; x.finalField = v; ... ; sharedRef = x;\n\n直接看上面的话有点难懂，下面举一个例子：\n````\nclass Apple {\nprivate String color;\n\n\tpublic Apple(String color) { this.color = color; }\n}\n\npublic class Test {\nstatic Test instance;\nfinal int a;\nfinal Apple apple;\n\n\tpublic Test() { \n\t\ta = 10;\n\t\tapple = new Apple(\"red\"); \n\t}\n\t\n\tpublic static void init() {\n\t\tinstance = new Test();\n\t}\n}\n````\n\n其实， x.finalField = v; ... ; sharedRef = x; 中的 x 在构造器中可以理解为 this，在 init() 方法中可以理解为new Test() 返回的对象。\n\n上述英文文档中：\n\n操作①是在构造器中对final字段 (基本类型和引用类型) 的赋值操作，对应a = 10，它不能和操作③进行重排序\n操作②也是指构造器中的操作，在①的基础上，如果final字段是一个引用类型，那么所有这个final字段引用到的store操作，都不能和操作③重排序。例如上面代码中的 apple 就是一个 final 类型应用变量，那么通过apple可以引用到color，则对apple.color的赋值操作不能和③进行重排序。\n操作③是在构造器外，将包含final字段的对象赋值给一个其他线程可以访问到的变量。在上述代码中，也就对应init()方法中的instance = new Test()，因为new Test()对象包含final字段a，且字段instance可以被其他线程访问到。\n实现这一条重排序规则可以通过在构造器结束位置插入StoreStore屏障来实现。\n\n规则2\nThe initial load (i.e., the very first encounter by a thread) of a final field cannot be reordered with the initial load of the reference to the object containing the final field. This comes into play in:\nx = sharedRef; ... ; i = x.finalField;\nA compiler would never reorder these since they are dependent, but there can be consequences of this rule on some processors.\n\n再看个例子：\n````\npublic class Test {\nstatic Test instance;\nfinal int a;\n\n\tpublic Test() { a = 10; }\n\t\n\tpublic static void init() {\n\t\tinstance = new Test();\n\t}\n\t\n\tpublic static void read() {\n\t\tif (obj != null) {\n\t\t\tSystem.out.println(obj.a);\n\t\t}\n\t}\n}\n````\n这条规则的意思是，通过对象访问其final字段(obj.a)这一操作 和 该操作之前第一次访问该对象的操作 (if (obj != null)中读取obj的操作) 是不能重排序的。\n\nJava编译器不会对不会对上述两个操作进行重排序，因为对编译器来说这两个操作(load(x)和load(x.field))之间存在依赖。但是对于处理器来说，它俩都是load操作，所以在允许LoadLoad重排序的处理器上，这两个操作是可能被重排序的，此时就需要加上LoadLoad屏障。\n\n总结\nfinal字段的重排序规则总结如下：\n\n构造器内final字段的写 和 构造器外将包含该final字段的对象赋值给一个其他线程能访问到的变量 这两个操作不能重排序\n加入final字段是一个引用类型，那么构造器内对该final引用类型字段的成员的写 和 构造器外将包含该final字段的对象赋值给一个其他线程能访问到的变量 这两个操作之间不能重排序\n构造器外，对于包含final字段的对象的读 和 对final字段的成员的读 不能重排序\n\n## 四、HotSpot VM中对final内存语义的实现\n[3] 在HotSpot源码的 parse1.cpp 中：\n````\n//------------------------------do_exits---------------------------------------\nvoid Parse::do_exits() {\n// ...\nif (method()->is_initializer() &&\n(wrote_final() ||\nPPC64_ONLY(wrote_volatile() ||)\n(AlwaysSafeConstructors && wrote_fields()))) {\n_exits.insert_mem_bar(Op_MemBarRelease, alloc_with_final());\n// ...\ndo_exists() 函数在构造器退出时会执行，显然它会使用 wrote_final() 判断构造器内是不是存在final类型的写，如果是的话，则插入一个内存屏障 Op_MemBarRelease，这个屏障实际上对应 LoadStore 和 StoreStore。插入 LoadStore 屏障是为了照顾这样一种特殊情况：final 字段的值依赖于另外的字段。例如x.finalField = x.normalField + 1; ...; sharedRef = x;，这里插入LoadStore 就是为了防止对 x.normalField 的读操作和sharedRef = x 发生重排序。\n````\n\n 总结\nfinal关键字的内存语义相较于volatile等关键字还是很难理解的，以上的内容如有表述不恰当之处还请指正。\n\n总结\nfinal关键字的内存语义相较于volatile等关键字还是很难理解的，以上的内容如有表述不恰当之处还请指正。\n\n# 参考文献\nJava Concurrency in Practice\n\nJSR 133 (Java Memory Model) FAQ\n\nJava Concurrency in Practice\n\nThe JSR-133 Cookbook for Compiler Writers\nIntel® 64 and IA-32 ArchitecturesvSoftware Developer’s Manual Volume 3A: System Programming Guide, Part 1\n"
  },
  {
    "path": "docs/Java/concurrency/Java并发指南：JUC中常用的Unsafe和Locksupport.md",
    "content": "# 目录\n  * [前言](#前言)\n  * [Unsafe类是啥？](#unsafe类是啥？)\n  * [为什么叫Unsafe？](#为什么叫unsafe？)\n  * [JAVA高并发—LockSupport的学习及简单使用](#java高并发locksupport的学习及简单使用)\n\n\n本文转自网络，侵删\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章同步发于我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《Java并发指南》其中一篇，本文大部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何全面深入地学习Java并发技术，从Java多线程基础，再到并发编程的基础知识，从Java并发包的入门和实战，再到JUC的源码剖析，一步步地学习Java并发编程，并上手进行实战，以便让你更完整地了解整个Java并发编程知识体系，形成自己的知识框架。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供一些对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n<!--more -->\n## 前言\n\n最近在看Java并发包的源码，发现了神奇的Unsafe类，仔细研究了一下，在这里跟大家分享一下。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404202919.png)\n\nUnsafe类是在sun.misc包下，不属于Java标准。但是很多Java的基础类库，包括一些被广泛使用的高性能开发库都是基于Unsafe类开发的，比如Netty、Cassandra、Hadoop、Kafka等。Unsafe类在提升Java运行效率，增强Java语言底层操作能力方面起了很大的作用。\n\nUnsafe类使Java拥有了像C语言的指针一样操作内存空间的能力，同时也带来了指针的问题。过度的使用Unsafe类会使得出错的几率变大，因此Java官方并不建议使用的，官方文档也几乎没有。Oracle正在计划从Java 9中去掉Unsafe类，如果真是如此影响就太大了。\n\n通常我们最好也不要使用Unsafe类，除非有明确的目的，并且也要对它有深入的了解才行。要想使用Unsafe类需要用一些比较tricky的办法。Unsafe类使用了单例模式，需要通过一个静态方法getUnsafe()来获取。但Unsafe类做了限制，如果是普通的调用的话，它会抛出一个SecurityException异常；只有由主类加载器加载的类才能调用这个方法。其源码如下：\n\n````\n    1 public static Unsafe getUnsafe() {\n    2     Class var0 = Reflection.getCallerClass();\n    3     if(!VM.isSystemDomainLoader(var0.getClassLoader())) {\n    4         throw new SecurityException(\"Unsafe\");\n    5     } else {\n    6         return theUnsafe;\n    7     }\n    8 }\n````\n网上也有一些办法来用主类加载器加载用户代码，比如设置bootclasspath参数。但更简单方法是利用Java反射，方法如下：\n````\n    1 Field f = Unsafe.class.getDeclaredField(\"theUnsafe\");\n    2 f.setAccessible(true);\n    3 Unsafe unsafe = (Unsafe) f.get(null);\n````\n获取到Unsafe实例之后，我们就可以为所欲为了。Unsafe类提供了以下这些功能：\n\n一、内存管理。包括分配内存、释放内存等。\n\n该部分包括了allocateMemory（分配内存）、reallocateMemory（重新分配内存）、copyMemory（拷贝内存）、freeMemory（释放内存 ）、getAddress（获取内存地址）、addressSize、pageSize、getInt（获取内存地址指向的整数）、getIntVolatile（获取内存地址指向的整数，并支持volatile语义）、putInt（将整数写入指定内存地址）、putIntVolatile（将整数写入指定内存地址，并支持volatile语义）、putOrderedInt（将整数写入指定内存地址、有序或者有延迟的方法）等方法。getXXX和putXXX包含了各种基本类型的操作。\n\n利用copyMemory方法，我们可以实现一个通用的对象拷贝方法，无需再对每一个对象都实现clone方法，当然这通用的方法只能做到对象浅拷贝。\n\n二、非常规的对象实例化。\n\nallocateInstance()方法提供了另一种创建实例的途径。通常我们可以用new或者反射来实例化对象，使用allocateInstance()方法可以直接生成对象实例，且无需调用构造方法和其它初始化方法。\n\n这在对象反序列化的时候会很有用，能够重建和设置final字段，而不需要调用构造方法。\n\n三、操作类、对象、变量。\n\n这部分包括了staticFieldOffset（静态域偏移）、defineClass（定义类）、defineAnonymousClass（定义匿名类）、ensureClassInitialized（确保类初始化）、objectFieldOffset（对象域偏移）等方法。\n\n通过这些方法我们可以获取对象的指针，通过对指针进行偏移，我们不仅可以直接修改指针指向的数据（即使它们是私有的），甚至可以找到JVM已经认定为垃圾、可以进行回收的对象。\n\n四、数组操作。\n\n这部分包括了arrayBaseOffset（获取数组第一个元素的偏移地址）、arrayIndexScale（获取数组中元素的增量地址）等方法。arrayBaseOffset与arrayIndexScale配合起来使用，就可以定位数组中每个元素在内存中的位置。\n\n由于Java的数组最大值为Integer.MAX_VALUE，使用Unsafe类的内存分配方法可以实现超大数组。实际上这样的数据就可以认为是C数组，因此需要注意在合适的时间释放内存。\n\n五、多线程同步。包括锁机制、CAS操作等。\n\n这部分包括了monitorEnter、tryMonitorEnter、monitorExit、compareAndSwapInt、compareAndSwap等方法。\n\n其中monitorEnter、tryMonitorEnter、monitorExit已经被标记为deprecated，不建议使用。\n\nUnsafe类的CAS操作可能是用的最多的，它为Java的锁机制提供了一种新的解决办法，比如AtomicInteger等类都是通过该方法来实现的。compareAndSwap方法是原子的，可以避免繁重的锁机制，提高代码效率。这是一种乐观锁，通常认为在大部分情况下不出现竞态条件，如果操作失败，会不断重试直到成功。\n\n六、挂起与恢复。\n\n这部分包括了park、unpark等方法。\n\n将一个线程进行挂起是通过park方法实现的，调用 park后，线程将一直阻塞直到超时或者中断等条件出现。unpark可以终止一个挂起的线程，使其恢复正常。整个并发框架中对线程的挂起操作被封装在 LockSupport类中，LockSupport类中有各种版本pack方法，但最终都调用了Unsafe.park()方法。\n\n七、内存屏障。\n\n这部分包括了loadFence、storeFence、fullFence等方法。这是在Java 8新引入的，用于定义内存屏障，避免代码重排序。\n\nloadFence() 表示该方法之前的所有load操作在内存屏障之前完成。同理storeFence()表示该方法之前的所有store操作在内存屏障之前完成。fullFence()表示该方法之前的所有load、store操作在内存屏障之前完成。\n\n## Unsafe类是啥？\n\nJava最初被设计为一种安全的受控环境。尽管如此，Java HotSpot还是包含了一个“后门”，提供了一些可以直接操控内存和线程的低层次操作。这个后门类——sun.misc.Unsafe——被JDK广泛用于自己的包中，如java.nio和java.util.concurrent。但是丝毫不建议在生产环境中使用这个后门。因为这个API十分不安全、不轻便、而且不稳定。这个不安全的类提供了一个观察HotSpot JVM内部结构并且可以对其进行修改。有时它可以被用来在不适用C++调试的情况下学习虚拟机内部结构，有时也可以被拿来做性能监控和开发工具。\n\n## 为什么叫Unsafe？\n\nJava官方不推荐使用Unsafe类，因为官方认为，这个类别人很难正确使用，非正确使用会给JVM带来致命错误。而且未来Java可能封闭丢弃这个类。\n\n\n1、简单介绍\n LockSupport是JDK中比较底层的类，用来创建锁和其他同步工具类的基本线程阻塞原语。可以做到与join() 、wait()/notifyAll() 功能一样，使线程自由的阻塞、释放。\n Java锁和同步器框架的核心AQS(AbstractQueuedSynchronizer 抽象队列同步器)，就是通过调用LockSupport.park()和LockSupport.unpark()实现线程的阻塞和唤醒的。\n\n补充：AQS定义了一套多线程访问共享资源的同步器框架，许多同步类实现都依赖于它，\n如常用的ReentrantLock/Semaphore/CountDownLatch...。\n\n2、简单原理\nLockSupport方法底层都是调用Unsafe的方法实现。全名sun.misc.Unsafe，该类可以直接操控内存，被JDK广泛用于自己的包中，如java.nio和java.util.concurrent。但是不建议在生产环境中使用这个类。因为这个API十分不安全、不轻便、而且不稳定。\n\n LockSupport提供park()和unpark()方法实现阻塞线程和解除线程阻塞，LockSupport和每个使用它的线程都与一个许可(permit)关联。permit是相当于1，0的开关，默认是0，调用一次unpark就加1变成1，调用一次park会消费permit, 也就会将1变成0，同时park立即返回。\n\n 再次调用park会变成block（因为permit为0了，会阻塞在这里，直到permit变为1）, 这时调用unpark会把permit置为1。每个线程都有一个相关的permit, permit最多只有一个，重复调用unpark也不会积累。意思就是说unpark 之后，如果permit 已经变为1，之后，再执行unpark ,permit 依旧是1。下边有例子会说到。\n\n3、简单例子\n以下边的做饭例子，正常来说，做饭 之前，要有锅、有菜才能开始做饭 。具体如下：\n（1）先假设已经有了锅 ，那只需要买菜就可以做饭。如下，即注释掉了买锅的步骤：\n````\n      public class LockSupportTest {\n          public static void main(String[] args) throws InterruptedException {\n            //买锅\n      //      Thread t1 = new Thread(new BuyGuo(Thread.currentThread()));\n      //      t1.start();\n    \n            //买菜\n              Thread t2 = new Thread(new BuyCai(Thread.currentThread()));\n              t2.start();\n      //        LockSupport.park();\n      //        System.out.println(\"锅买回来了...\");\n              LockSupport.park();\n              System.out.println(\"菜买回来 了...\");\n              System.out.println(\"开始做饭\");\n          }\n      }\n      class BuyGuo implements Runnable{\n          private Object threadObj;\n          public BuyGuo(Object threadObj) {\n              this.threadObj = threadObj;\n          }\n    \n          @Override\n          public void run() {\n              System.out.println(\"去买锅...\");\n              LockSupport.unpark((Thread)threadObj);\n    \n          }\n      }\n      class BuyCai implements Runnable{\n          private Object threadObj;\n          public BuyCai(Object threadObj) {\n              this.threadObj = threadObj;\n          }\n    \n          @Override\n          public void run() {\n              System.out.println(\"买菜去...\");\n              LockSupport.unpark((Thread)threadObj);\n          }\n      }\n````  \n\n执行后，可出现下面的结果：\n\n买菜去...\n菜买回来了...\n开始做饭\n\n 如上所述，可以达到阻塞主线程等到买完菜之后才开始做饭。这即是park()、unpark() 的用法。简单解释一下上述的步骤：\n\nmain 方法启动后，主线程 和 买菜线程 同时开始执行。\n因为两者同时进行，当主线程 走到park() 时，发现permit 还为0 ，即会等待在这里。\n当买菜线程执行进去后，走到unpark() 会将permit 变为1 。\n主线程 park() 处发现permit 已经变成1 ，就可以继续往下执行了，同时消费掉permit ，重新变成0 。\n 以上permit 只是park/unpark 执行的一种逻辑开关，执行的步骤大致如此。\n\n4、注意点及思考\n（1）必须将park()与uppark() 配对使用才更高效。\n如果上边也把买锅的线程放开，main 方法改为如下：\n````\n       //买锅\n      Thread t1 = new Thread(new BuyGuo(Thread.currentThread()));\n       t1.start();\n      //买菜\n        Thread t2 = new Thread(new BuyCai(Thread.currentThread()));\n        t2.start();\n        LockSupport.park();\n        System.out.println(\"锅买回来了...\");\n        LockSupport.park();\n        System.out.println(\"菜买回来了...\");\n        System.out.println(\"开始做饭\");\n````\n即调用了两次park() 和unpark() ，发现有时候可以，有时候会使线程卡在那里，然后我又换了下顺序，如下：\n````\n       //买锅\n      Thread t1 = new Thread(new BuyGuo(Thread.currentThread()));\n       t1.start();\n          LockSupport.park();\n        System.out.println(\"锅买回来了...\");\n      //买菜\n        Thread t2 = new Thread(new BuyCai(Thread.currentThread()));\n        t2.start();\n        LockSupport.park();\n        System.out.println(\"菜买回来了...\");\n        System.out.println(\"开始做饭\");\n````\n原理没有详细去研究,不过想了想，上边两种其实并无区别，只是执行顺序有了影响，park() 和unpark() 既然是成对配合使用，通过标识permit 来控制，如果像前边那个例子那样，出现阻塞的情况原因，我分析可能是这么个原因：\n\n当买锅的时候，通过unpark()将permit 置为1，但是还没等到外边的main方法执行第一个park() ,买菜的线程又调了一次unpark(),但是这时候permit 还是从1变成了1，等回到主线程调用park()的时候，因为还有两个park()需要执行，也就是需要两个消费permit ,因为permit 只有1个，所以，可能会剩下一个park()卡在那里了。\n\n（2）使用park(Object blocker) 方法更能明确问题\n其实park() 有个重载方法park(Object blocker) ,这俩方法效果差不多，但是有blocker的可以传递给开发人员更多的现场信息，可以查看到当前线程的阻塞对象，方便定位问题。所以java6新增加带blocker入参的系列park方法，替代原有的park方法。\n\n5、与wait()/notifyAll() 的比较\nLockSupport 的 park/unpark 方法，虽然与平时Object 中wait/notify 同样达到阻塞线程的效果。但是它们之间还是有区别的。\n\n面向的对象主体不同。LockSupport() 操作的是线程对象，直接传入的就是Thread ,而wait() 属于具体对象，notifyAll() 也是针对所有线程进行唤醒。\nwait/notify 需要获取对象的监视器，即synchronized修饰，而park/unpark 不需要获取对象的监视器。\n实现的机制不同，因此两者没有交集。也就是说 LockSupport 阻塞的线程，notify/notifyAll 没法唤醒。但是 park 之后，同样可以被中断(interrupt()) !\n\n\n## JAVA高并发—LockSupport的学习及简单使用\n1、简单介绍\n LockSupport是JDK中比较底层的类，用来创建锁和其他同步工具类的基本线程阻塞原语。可以做到与join() 、wait()/notifyAll() 功能一样，使线程自由的阻塞、释放。\n \n\n Java锁和同步器框架的核心AQS(AbstractQueuedSynchronizer 抽象队列同步器)，就是通过调用LockSupport.park()和LockSupport.unpark()实现线程的阻塞和唤醒的。\n\n补充：AQS定义了一套多线程访问共享资源的同步器框架，许多同步类实现都依赖于它，\n如常用的ReentrantLock/Semaphore/CountDownLatch...。\n\n2、简单原理\nLockSupport方法底层都是调用Unsafe的方法实现。全名sun.misc.Unsafe，该类可以直接操控内存，被JDK广泛用于自己的包中，如java.nio和java.util.concurrent。但是不建议在生产环境中使用这个类。因为这个API十分不安全、不轻便、而且不稳定。\n\n LockSupport提供park()和unpark()方法实现阻塞线程和解除线程阻塞，LockSupport和每个使用它的线程都与一个许可(permit)关联。\n\npermit是相当于1，0的开关，默认是0，调用一次unpark就加1变成1，调用一次park会消费permit, 也就会将1变成0，同时park立即返回。再次调用park会变成block（因为permit为0了，会阻塞在这里，直到permit变为1）, 这时调用unpark会把permit置为1。\n\n 每个线程都有一个相关的permit, permit最多只有一个，重复调用unpark也不会积累。意思就是说unpark 之后，如果permit 已经变为1，之后，再执行unpark ,permit 依旧是1。下边有例子会说到。\n\n3、简单例子\n以下边的做饭例子，正常来说，做饭 之前，要有锅、有菜才能开始做饭 。具体如下：\n（1）先假设已经有了锅 ，那只需要买菜就可以做饭。如下，即注释掉了买锅的步骤：\n````    \n    public class LockSupportTest {\n        public static void main(String[] args) throws InterruptedException {\n          //买锅\n    //      Thread t1 = new Thread(new BuyGuo(Thread.currentThread()));\n    //      t1.start();\n    \n          //买菜\n            Thread t2 = new Thread(new BuyCai(Thread.currentThread()));\n            t2.start();\n    //        LockSupport.park();\n    //        System.out.println(\"锅买回来了...\");\n            LockSupport.park();\n            System.out.println(\"菜买回来 了...\");\n            System.out.println(\"开始做饭\");\n        }\n    }\n    class BuyGuo implements Runnable{\n        private Object threadObj;\n        public BuyGuo(Object threadObj) {\n            this.threadObj = threadObj;\n        }\n    \n        @Override\n        public void run() {\n            System.out.println(\"去买锅...\");\n            LockSupport.unpark((Thread)threadObj);\n    \n        }\n    }\n    class BuyCai implements Runnable{\n        private Object threadObj;\n        public BuyCai(Object threadObj) {\n            this.threadObj = threadObj;\n        }\n    \n        @Override\n        public void run() {\n            System.out.println(\"买菜去...\");\n            LockSupport.unpark((Thread)threadObj);\n        }\n    }\n````    \n执行后，可出现下面的结果：\n\n买菜去...\n菜买回来了...\n开始做饭\n\n 如上所述，可以达到阻塞主线程等到买完菜之后才开始做饭。这即是park()、unpark() 的用法。简单解释一下上述的步骤：\n\nmain 方法启动后，主线程 和 买菜线程 同时开始执行。\n因为两者同时进行，当主线程 走到park() 时，发现permit 还为0 ，即会等待在这里。\n当买菜线程执行进去后，走到unpark() 会将permit 变为1 。\n主线程 park() 处发现permit 已经变成1 ，就可以继续往下执行了，同时消费掉permit ，重新变成0 。\n 以上permit 只是park/unpark 执行的一种逻辑开关，执行的步骤大致如此。\n\n4、注意点及思考\n（1）必须将park()与uppark() 配对使用才更高效。\n如果上边也把买锅的线程放开，main 方法改为如下：\n````\n   //买锅\n  Thread t1 = new Thread(new BuyGuo(Thread.currentThread()));\n   t1.start();\n  //买菜\n    Thread t2 = new Thread(new BuyCai(Thread.currentThread()));\n    t2.start();\n    LockSupport.park();\n    System.out.println(\"锅买回来了...\");\n    LockSupport.park();\n    System.out.println(\"菜买回来了...\");\n    System.out.println(\"开始做饭\");\n````\n即调用了两次park() 和unpark() ，发现有时候可以，有时候会使线程卡在那里，然后我又换了下顺序，如下：\n````\n   //买锅\n  Thread t1 = new Thread(new BuyGuo(Thread.currentThread()));\n   t1.start();\n      LockSupport.park();\n    System.out.println(\"锅买回来了...\");\n  //买菜\n    Thread t2 = new Thread(new BuyCai(Thread.currentThread()));\n    t2.start();\n    LockSupport.park();\n    System.out.println(\"菜买回来了...\");\n    System.out.println(\"开始做饭\");\n````\n原理没有详细去研究,不过想了想，上边两种其实并无区别，只是执行顺序有了影响，park() 和unpark() 既然是成对配合使用，通过标识permit 来控制，如果像前边那个例子那样，出现阻塞的情况原因，我分析可能是这么个原因：\n\n当买锅的时候，通过unpark()将permit 置为1，但是还没等到外边的main方法执行第一个park() ,买菜的线程又调了一次unpark(),但是这时候permit 还是从1变成了1，等回到主线程调用park()的时候，因为还有两个park()需要执行，也就是需要两个消费permit ,因为permit 只有1个，所以，可能会剩下一个park()卡在那里了。\n\n（2）使用park(Object blocker) 方法更能明确问题\n其实park() 有个重载方法park(Object blocker) ,这俩方法效果差不多，但是有blocker的可以传递给开发人员更多的现场信息，可以查看到当前线程的阻塞对象，方便定位问题。所以java6新增加带blocker入参的系列park方法，替代原有的park方法。\n\n5、与wait()/notifyAll() 的比较\nLockSupport 的 park/unpark 方法，虽然与平时Object 中wait/notify 同样达到阻塞线程的效果。但是它们之间还是有区别的。\n\n面向的对象主体不同。LockSupport() 操作的是线程对象，直接传入的就是Thread ,而wait() 属于具体对象，notifyAll() 也是针对所有线程进行唤醒。\nwait/notify 需要获取对象的监视器，即synchronized修饰，而park/unpark 不需要获取对象的监视器。\n实现的机制不同，因此两者没有交集。也就是说 LockSupport 阻塞的线程，notify/notifyAll 没法唤醒。但是 park 之后，同样可以被中断(interrupt()) !\n"
  },
  {
    "path": "docs/Java/concurrency/Java并发指南：JUC的核心类AQS详解.md",
    "content": "# 目录\n  * [简介](#简介)\n  * [AQS 结构](#aqs-结构)\n  * [线程抢锁](#线程抢锁)\n  * [解锁操作](#解锁操作)\n  * [总结](#总结)\n  * [示例图解析](#示例图解析)\n\n本文转自：https://www.javadoop.com/post/AbstractQueuedSynchronizer#toc4\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章同步发于我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《Java并发指南》其中一篇，本文大部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何全面深入地学习Java并发技术，从Java多线程基础，再到并发编程的基础知识，从Java并发包的入门和实战，再到JUC的源码剖析，一步步地学习Java并发编程，并上手进行实战，以便让你更完整地了解整个Java并发编程知识体系，形成自己的知识框架。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供一些对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n<!--more -->\n\n## 简介\n\n在分析 Java 并发包 java.util.concurrent 源码的时候，少不了需要了解 AbstractQueuedSynchronizer（以下简写AQS）这个抽象类，因为它是 Java 并发包的基础工具类，是实现 ReentrantLock、CountDownLatch、Semaphore、FutureTask 等类的基础。\n\nGoogle 一下 AbstractQueuedSynchronizer，我们可以找到很多关于 AQS 的介绍，但是很多都没有介绍清楚，因为大部分文章没有把其中的一些关键的细节说清楚。\n\n本文将从 ReentrantLock 的公平锁源码出发，分析下 AbstractQueuedSynchronizer 这个类是怎么工作的，希望能给大家提供一些简单的帮助。\n\n申明以下几点：\n\n1.  本文有点长，但还是挺简单，主要面向读者对象为并发编程的初学者，或者想要阅读 Java 并发包源码的开发者。对于新手来说，可能需要花好几个小时才能完全看懂，但是这时间肯定是值得的。\n2.  源码环境 JDK1.7（1.8没啥变化），看到不懂或有疑惑的部分，最好能自己打开源码看看。Doug Lea 大神的代码写得真心不错。\n3.  本文不分析共享模式，这样可以给读者减少很多负担，[第三篇文章](https://www.javadoop.com/post/AbstractQueuedSynchronizer-3)对共享模式进行了分析。而且也不分析 condition 部分，所以应该说很容易就可以看懂了。\n4.  本文大量使用我们平时用得最多的 ReentrantLock 的概念，本质上来说是不正确的，读者应该清楚，AQS 不仅仅用来实现可重入锁，只是希望读者可以用锁来联想 AQS 的使用场景，降低阅读压力。\n5.  ReentrantLock 的公平锁和非公平锁只有一点点区别，[第二篇文章](https://www.javadoop.com/post/AbstractQueuedSynchronizer-2)做了介绍。\n6.  评论区有读者反馈本文直接用代码说不友好，应该多配点流程图，这篇文章确实有这个问题。但是作为过来人，我想告诉大家，对于 AQS 来说，形式真的不重要，重要的是把细节说清楚。\n\n## AQS 结构\n\n先来看看 AQS 有哪些属性，搞清楚这些基本就知道 AQS 是什么套路了，毕竟可以猜嘛！\n\n```\n// 头结点，你直接把它当做 当前持有锁的线程 可能是最好理解的\nprivate transient volatile Node head;\n\n// 阻塞的尾节点，每个新的节点进来，都插入到最后，也就形成了一个链表\nprivate transient volatile Node tail;\n\n// 这个是最重要的，代表当前锁的状态，0代表没有被占用，大于 0 代表有线程持有当前锁\n// 这个值可以大于 1，是因为锁可以重入，每次重入都加上 1\nprivate volatile int state;\n\n// 代表当前持有独占锁的线程，举个最重要的使用例子，因为锁可以重入\n// reentrantLock.lock()可以嵌套调用多次，所以每次用这个来判断当前线程是否已经拥有了锁\n// if (currentThread == getExclusiveOwnerThread()) {state++}\nprivate transient Thread exclusiveOwnerThread; //继承自AbstractOwnableSynchronizer\n```\n\n怎么样，看样子应该是很简单的吧，毕竟也就四个属性啊。\n\nAbstractQueuedSynchronizer 的等待队列示意如下所示，注意了，之后分析过程中所说的 queue，也就是阻塞队列**不包含 head，不包含 head，不包含 head**。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404200412.png)\n\n等待队列中每个线程被包装成一个 Node 实例，数据结构是链表，一起看看源码吧：\n\n```\nstatic final class Node {\n    // 标识节点当前在共享模式下\n    static final Node SHARED = new Node();\n    // 标识节点当前在独占模式下\n    static final Node EXCLUSIVE = null;\n\n    // ======== 下面的几个int常量是给waitStatus用的 ===========\n    /** waitStatus value to indicate thread has cancelled */\n    // 代码此线程取消了争抢这个锁\n    static final int CANCELLED =  1;\n    /** waitStatus value to indicate successor's thread needs unparking */\n    // 官方的描述是，其表示当前node的后继节点对应的线程需要被唤醒\n    static final int SIGNAL    = -1;\n    /** waitStatus value to indicate thread is waiting on condition */\n    // 本文不分析condition，所以略过吧，下一篇文章会介绍这个\n    static final int CONDITION = -2;\n    /**\n     * waitStatus value to indicate the next acquireShared should\n     * unconditionally propagate\n     */\n    // 同样的不分析，略过吧\n    static final int PROPAGATE = -3;\n    // =====================================================\n\n    // 取值为上面的1、-1、-2、-3，或者0(以后会讲到)\n    // 这么理解，暂时只需要知道如果这个值 大于0 代表此线程取消了等待，\n    //    ps: 半天抢不到锁，不抢了，ReentrantLock是可以指定timeouot的。。。\n    volatile int waitStatus;\n    // 前驱节点的引用\n    volatile Node prev;\n    // 后继节点的引用\n    volatile Node next;\n    // 这个就是线程本尊\n    volatile Thread thread;\n\n}\n```\n\nNode 的数据结构其实也挺简单的，就是 thread + waitStatus + pre + next 四个属性而已，大家先要有这个概念在心里。\n\n上面的是基础知识，后面会多次用到，心里要时刻记着它们，心里想着这个结构图就可以了。下面，我们开始说 ReentrantLock 的公平锁。再次强调，我说的阻塞队列不包含 head 节点。\n\n![aqs-0](https://www.javadoop.com/blogimages/AbstractQueuedSynchronizer/aqs-0.png)\n\n首先，我们先看下 ReentrantLock 的使用方式。\n\n```\n// 我用个web开发中的service概念吧\npublic class OrderService {\n    // 使用static，这样每个线程拿到的是同一把锁，当然，spring mvc中service默认就是单例，别纠结这个\n    private static ReentrantLock reentrantLock = new ReentrantLock(true);\n\n    public void createOrder() {\n        // 比如我们同一时间，只允许一个线程创建订单\n        reentrantLock.lock();\n        // 通常，lock 之后紧跟着 try 语句\n        try {\n            // 这块代码同一时间只能有一个线程进来(获取到锁的线程)，\n            // 其他的线程在lock()方法上阻塞，等待获取到锁，再进来\n            // 执行代码...\n            // 执行代码...\n            // 执行代码...\n        } finally {\n            // 释放锁\n            reentrantLock.unlock();\n        }\n    }\n}\n```\n\nReentrantLock 在内部用了内部类 Sync 来管理锁，所以真正的获取锁和释放锁是由 Sync 的实现类来控制的。\n\n```\nabstract static class Sync extends AbstractQueuedSynchronizer {\n}\n```\n\nSync 有两个实现，分别为 NonfairSync（非公平锁）和 FairSync（公平锁），我们看 FairSync 部分。\n\n```\npublic ReentrantLock(boolean fair) {\n    sync = fair ? new FairSync() : new NonfairSync();\n}\n```\n\n## 线程抢锁\n\n很多人肯定开始嫌弃上面废话太多了，下面跟着代码走，我就不废话了。\n\n```\nstatic final class FairSync extends Sync {\n    private static final long serialVersionUID = -3000897897090466540L;\n      // 争锁\n    final void lock() {\n        acquire(1);\n    }\n      // 来自父类AQS，我直接贴过来这边，下面分析的时候同样会这样做，不会给读者带来阅读压力\n    // 我们看到，这个方法，如果tryAcquire(arg) 返回true, 也就结束了。\n    // 否则，acquireQueued方法会将线程压到队列中\n    public final void acquire(int arg) { // 此时 arg == 1\n        // 首先调用tryAcquire(1)一下，名字上就知道，这个只是试一试\n        // 因为有可能直接就成功了呢，也就不需要进队列排队了，\n        // 对于公平锁的语义就是：本来就没人持有锁，根本没必要进队列等待(又是挂起，又是等待被唤醒的)\n        if (!tryAcquire(arg) &&\n            // tryAcquire(arg)没有成功，这个时候需要把当前线程挂起，放到阻塞队列中。\n            acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {\n              selfInterrupt();\n        }\n    }\n\n    /**\n     * Fair version of tryAcquire.  Don't grant access unless\n     * recursive call or no waiters or is first.\n     */\n    // 尝试直接获取锁，返回值是boolean，代表是否获取到锁\n    // 返回true：1.没有线程在等待锁；2.重入锁，线程本来就持有锁，也就可以理所当然可以直接获取\n    protected final boolean tryAcquire(int acquires) {\n        final Thread current = Thread.currentThread();\n        int c = getState();\n        // state == 0 此时此刻没有线程持有锁\n        if (c == 0) {\n            // 虽然此时此刻锁是可以用的，但是这是公平锁，既然是公平，就得讲究先来后到，\n            // 看看有没有别人在队列中等了半天了\n            if (!hasQueuedPredecessors() &&\n                // 如果没有线程在等待，那就用CAS尝试一下，成功了就获取到锁了，\n                // 不成功的话，只能说明一个问题，就在刚刚几乎同一时刻有个线程抢先了 =_=\n                // 因为刚刚还没人的，我判断过了\n                compareAndSetState(0, acquires)) {\n\n                // 到这里就是获取到锁了，标记一下，告诉大家，现在是我占用了锁\n                setExclusiveOwnerThread(current);\n                return true;\n            }\n        }\n          // 会进入这个else if分支，说明是重入了，需要操作：state=state+1\n        // 这里不存在并发问题\n        else if (current == getExclusiveOwnerThread()) {\n            int nextc = c + acquires;\n            if (nextc < 0)\n                throw new Error(\"Maximum lock count exceeded\");\n            setState(nextc);\n            return true;\n        }\n        // 如果到这里，说明前面的if和else if都没有返回true，说明没有获取到锁\n        // 回到上面一个外层调用方法继续看:\n        // if (!tryAcquire(arg) \n        //        && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) \n        //     selfInterrupt();\n        return false;\n    }\n\n    // 假设tryAcquire(arg) 返回false，那么代码将执行：\n      //        acquireQueued(addWaiter(Node.EXCLUSIVE), arg)，\n    // 这个方法，首先需要执行：addWaiter(Node.EXCLUSIVE)\n\n    /**\n     * Creates and enqueues node for current thread and given mode.\n     *\n     * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared\n     * @return the new node\n     */\n    // 此方法的作用是把线程包装成node，同时进入到队列中\n    // 参数mode此时是Node.EXCLUSIVE，代表独占模式\n    private Node addWaiter(Node mode) {\n        Node node = new Node(Thread.currentThread(), mode);\n        // Try the fast path of enq; backup to full enq on failure\n        // 以下几行代码想把当前node加到链表的最后面去，也就是进到阻塞队列的最后\n        Node pred = tail;\n\n        // tail!=null => 队列不为空(tail==head的时候，其实队列是空的，不过不管这个吧)\n        if (pred != null) { \n            // 将当前的队尾节点，设置为自己的前驱 \n            node.prev = pred; \n            // 用CAS把自己设置为队尾, 如果成功后，tail == node 了，这个节点成为阻塞队列新的尾巴\n            if (compareAndSetTail(pred, node)) { \n                // 进到这里说明设置成功，当前node==tail, 将自己与之前的队尾相连，\n                // 上面已经有 node.prev = pred，加上下面这句，也就实现了和之前的尾节点双向连接了\n                pred.next = node;\n                // 线程入队了，可以返回了\n                return node;\n            }\n        }\n        // 仔细看看上面的代码，如果会到这里，\n        // 说明 pred==null(队列是空的) 或者 CAS失败(有线程在竞争入队)\n        // 读者一定要跟上思路，如果没有跟上，建议先不要往下读了，往回仔细看，否则会浪费时间的\n        enq(node);\n        return node;\n    }\n\n    /**\n     * Inserts node into queue, initializing if necessary. See picture above.\n     * @param node the node to insert\n     * @return node's predecessor\n     */\n    // 采用自旋的方式入队\n    // 之前说过，到这个方法只有两种可能：等待队列为空，或者有线程竞争入队，\n    // 自旋在这边的语义是：CAS设置tail过程中，竞争一次竞争不到，我就多次竞争，总会排到的\n    private Node enq(final Node node) {\n        for (;;) {\n            Node t = tail;\n            // 之前说过，队列为空也会进来这里\n            if (t == null) { // Must initialize\n                // 初始化head节点\n                // 细心的读者会知道原来 head 和 tail 初始化的时候都是 null 的\n                // 还是一步CAS，你懂的，现在可能是很多线程同时进来呢\n                if (compareAndSetHead(new Node()))\n                    // 给后面用：这个时候head节点的waitStatus==0, 看new Node()构造方法就知道了\n\n                    // 这个时候有了head，但是tail还是null，设置一下，\n                    // 把tail指向head，放心，马上就有线程要来了，到时候tail就要被抢了\n                    // 注意：这里只是设置了tail=head，这里可没return哦，没有return，没有return\n                    // 所以，设置完了以后，继续for循环，下次就到下面的else分支了\n                    tail = head;\n            } else {\n                // 下面几行，和上一个方法 addWaiter 是一样的，\n                // 只是这个套在无限循环里，反正就是将当前线程排到队尾，有线程竞争的话排不上重复排\n                node.prev = t;\n                if (compareAndSetTail(t, node)) {\n                    t.next = node;\n                    return t;\n                }\n            }\n        }\n    }\n\n    // 现在，又回到这段代码了\n    // if (!tryAcquire(arg) \n    //        && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) \n    //     selfInterrupt();\n\n    // 下面这个方法，参数node，经过addWaiter(Node.EXCLUSIVE)，此时已经进入阻塞队列\n    // 注意一下：如果acquireQueued(addWaiter(Node.EXCLUSIVE), arg))返回true的话，\n    // 意味着上面这段代码将进入selfInterrupt()，所以正常情况下，下面应该返回false\n    // 这个方法非常重要，应该说真正的线程挂起，然后被唤醒后去获取锁，都在这个方法里了\n    final boolean acquireQueued(final Node node, int arg) {\n        boolean failed = true;\n        try {\n            boolean interrupted = false;\n            for (;;) {\n                final Node p = node.predecessor();\n                // p == head 说明当前节点虽然进到了阻塞队列，但是是阻塞队列的第一个，因为它的前驱是head\n                // 注意，阻塞队列不包含head节点，head一般指的是占有锁的线程，head后面的才称为阻塞队列\n                // 所以当前节点可以去试抢一下锁\n                // 这里我们说一下，为什么可以去试试：\n                // 首先，它是队头，这个是第一个条件，其次，当前的head有可能是刚刚初始化的node，\n                // enq(node) 方法里面有提到，head是延时初始化的，而且new Node()的时候没有设置任何线程\n                // 也就是说，当前的head不属于任何一个线程，所以作为队头，可以去试一试，\n                // tryAcquire已经分析过了, 忘记了请往前看一下，就是简单用CAS试操作一下state\n                if (p == head && tryAcquire(arg)) {\n                    setHead(node);\n                    p.next = null; // help GC\n                    failed = false;\n                    return interrupted;\n                }\n                // 到这里，说明上面的if分支没有成功，要么当前node本来就不是队头，\n                // 要么就是tryAcquire(arg)没有抢赢别人，继续往下看\n                if (shouldParkAfterFailedAcquire(p, node) &&\n                    parkAndCheckInterrupt())\n                    interrupted = true;\n            }\n        } finally {\n            // 什么时候 failed 会为 true???\n            // tryAcquire() 方法抛异常的情况\n            if (failed)\n                cancelAcquire(node);\n        }\n    }\n\n    /**\n     * Checks and updates status for a node that failed to acquire.\n     * Returns true if thread should block. This is the main signal\n     * control in all acquire loops.  Requires that pred == node.prev\n     *\n     * @param pred node's predecessor holding status\n     * @param node the node\n     * @return {@code true} if thread should block\n     */\n    // 刚刚说过，会到这里就是没有抢到锁呗，这个方法说的是：\"当前线程没有抢到锁，是否需要挂起当前线程？\"\n    // 第一个参数是前驱节点，第二个参数才是代表当前线程的节点\n    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {\n        int ws = pred.waitStatus;\n        // 前驱节点的 waitStatus == -1 ，说明前驱节点状态正常，当前线程需要挂起，直接可以返回true\n        if (ws == Node.SIGNAL)\n            /*\n             * This node has already set status asking a release\n             * to signal it, so it can safely park.\n             */\n            return true;\n\n        // 前驱节点 waitStatus大于0 ，之前说过，大于0 说明前驱节点取消了排队。\n        // 这里需要知道这点：进入阻塞队列排队的线程会被挂起，而唤醒的操作是由前驱节点完成的。\n        // 所以下面这块代码说的是将当前节点的prev指向waitStatus<=0的节点，\n        // 简单说，就是为了找个好爹，因为你还得依赖它来唤醒呢，如果前驱节点取消了排队，\n        // 找前驱节点的前驱节点做爹，往前遍历总能找到一个好爹的\n        if (ws > 0) {\n            /*\n             * Predecessor was cancelled. Skip over predecessors and\n             * indicate retry.\n             */\n            do {\n                node.prev = pred = pred.prev;\n            } while (pred.waitStatus > 0);\n            pred.next = node;\n        } else {\n            /*\n             * waitStatus must be 0 or PROPAGATE.  Indicate that we\n             * need a signal, but don't park yet.  Caller will need to\n             * retry to make sure it cannot acquire before parking.\n             */\n            // 仔细想想，如果进入到这个分支意味着什么\n            // 前驱节点的waitStatus不等于-1和1，那也就是只可能是0，-2，-3\n            // 在我们前面的源码中，都没有看到有设置waitStatus的，所以每个新的node入队时，waitStatu都是0\n            // 正常情况下，前驱节点是之前的 tail，那么它的 waitStatus 应该是 0\n            // 用CAS将前驱节点的waitStatus设置为Node.SIGNAL(也就是-1)\n            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);\n        }\n        // 这个方法返回 false，那么会再走一次 for 循序，\n        //     然后再次进来此方法，此时会从第一个分支返回 true\n        return false;\n    }\n\n    // private static boolean shouldParkAfterFailedAcquire(Node pred, Node node)\n    // 这个方法结束根据返回值我们简单分析下：\n    // 如果返回true, 说明前驱节点的waitStatus==-1，是正常情况，那么当前线程需要被挂起，等待以后被唤醒\n    //        我们也说过，以后是被前驱节点唤醒，就等着前驱节点拿到锁，然后释放锁的时候叫你好了\n    // 如果返回false, 说明当前不需要被挂起，为什么呢？往后看\n\n    // 跳回到前面是这个方法\n    // if (shouldParkAfterFailedAcquire(p, node) &&\n    //                parkAndCheckInterrupt())\n    //                interrupted = true;\n\n    // 1\\. 如果shouldParkAfterFailedAcquire(p, node)返回true，\n    // 那么需要执行parkAndCheckInterrupt():\n\n    // 这个方法很简单，因为前面返回true，所以需要挂起线程，这个方法就是负责挂起线程的\n    // 这里用了LockSupport.park(this)来挂起线程，然后就停在这里了，等待被唤醒=======\n    private final boolean parkAndCheckInterrupt() {\n        LockSupport.park(this);\n        return Thread.interrupted();\n    }\n\n    // 2\\. 接下来说说如果shouldParkAfterFailedAcquire(p, node)返回false的情况\n\n   // 仔细看shouldParkAfterFailedAcquire(p, node)，我们可以发现，其实第一次进来的时候，一般都不会返回true的，原因很简单，前驱节点的waitStatus=-1是依赖于后继节点设置的。也就是说，我都还没给前驱设置-1呢，怎么可能是true呢，但是要看到，这个方法是套在循环里的，所以第二次进来的时候状态就是-1了。\n\n    // 解释下为什么shouldParkAfterFailedAcquire(p, node)返回false的时候不直接挂起线程：\n    // => 是为了应对在经过这个方法后，node已经是head的直接后继节点了。剩下的读者自己想想吧。\n}\n```\n\n说到这里，也就明白了，多看几遍`final boolean acquireQueued(final Node node, int arg)`这个方法吧。自己推演下各个分支怎么走，哪种情况下会发生什么，走到哪里。\n\n## 解锁操作\n\n最后，就是还需要介绍下唤醒的动作了。我们知道，正常情况下，如果线程没获取到锁，线程会被`LockSupport.park(this);`挂起停止，等待被唤醒。\n\n```\n// 唤醒的代码还是比较简单的，你如果上面加锁的都看懂了，下面都不需要看就知道怎么回事了\npublic void unlock() {\n    sync.release(1);\n}\n\npublic final boolean release(int arg) {\n    // 往后看吧\n    if (tryRelease(arg)) {\n        Node h = head;\n        if (h != null && h.waitStatus != 0)\n            unparkSuccessor(h);\n        return true;\n    }\n    return false;\n}\n\n// 回到ReentrantLock看tryRelease方法\nprotected final boolean tryRelease(int releases) {\n    int c = getState() - releases;\n    if (Thread.currentThread() != getExclusiveOwnerThread())\n        throw new IllegalMonitorStateException();\n    // 是否完全释放锁\n    boolean free = false;\n    // 其实就是重入的问题，如果c==0，也就是说没有嵌套锁了，可以释放了，否则还不能释放掉\n    if (c == 0) {\n        free = true;\n        setExclusiveOwnerThread(null);\n    }\n    setState(c);\n    return free;\n}\n\n/**\n * Wakes up node's successor, if one exists.\n *\n * @param node the node\n */\n// 唤醒后继节点\n// 从上面调用处知道，参数node是head头结点\nprivate void unparkSuccessor(Node node) {\n    /*\n     * If status is negative (i.e., possibly needing signal) try\n     * to clear in anticipation of signalling.  It is OK if this\n     * fails or if status is changed by waiting thread.\n     */\n    int ws = node.waitStatus;\n    // 如果head节点当前waitStatus<0, 将其修改为0\n    if (ws < 0)\n        compareAndSetWaitStatus(node, ws, 0);\n    /*\n     * Thread to unpark is held in successor, which is normally\n     * just the next node.  But if cancelled or apparently null,\n     * traverse backwards from tail to find the actual\n     * non-cancelled successor.\n     */\n    // 下面的代码就是唤醒后继节点，但是有可能后继节点取消了等待（waitStatus==1）\n    // 从队尾往前找，找到waitStatus<=0的所有节点中排在最前面的\n    Node s = node.next;\n    if (s == null || s.waitStatus > 0) {\n        s = null;\n        // 从后往前找，仔细看代码，不必担心中间有节点取消(waitStatus==1)的情况\n        for (Node t = tail; t != null && t != node; t = t.prev)\n            if (t.waitStatus <= 0)\n                s = t;\n    }\n    if (s != null)\n        // 唤醒线程\n        LockSupport.unpark(s.thread);\n}\n```\n\n唤醒线程以后，被唤醒的线程将从以下代码中继续往前走：\n\n```\nprivate final boolean parkAndCheckInterrupt() {\n    LockSupport.park(this); // 刚刚线程被挂起在这里了\n    return Thread.interrupted();\n}\n// 又回到这个方法了：acquireQueued(final Node node, int arg)，这个时候，node的前驱是head了\n```\n\n好了，后面就不分析源码了，剩下的还有问题自己去仔细看看代码吧。\n\n## 总结\n\n总结一下吧。\n\n在并发环境下，加锁和解锁需要以下三个部件的协调：\n\n1.  锁状态。我们要知道锁是不是被别的线程占有了，这个就是 state 的作用，它为 0 的时候代表没有线程占有锁，可以去争抢这个锁，用 CAS 将 state 设为 1，如果 CAS 成功，说明抢到了锁，这样其他线程就抢不到了，如果锁重入的话，state进行 +1 就可以，解锁就是减 1，直到 state 又变为 0，代表释放锁，所以 lock() 和 unlock() 必须要配对啊。然后唤醒等待队列中的第一个线程，让其来占有锁。\n2.  线程的阻塞和解除阻塞。AQS 中采用了 LockSupport.park(thread) 来挂起线程，用 unpark 来唤醒线程。\n3.  阻塞队列。因为争抢锁的线程可能很多，但是只能有一个线程拿到锁，其他的线程都必须等待，这个时候就需要一个 queue 来管理这些线程，AQS 用的是一个 FIFO 的队列，就是一个链表，每个 node 都持有后继节点的引用。AQS 采用了 CLH 锁的变体来实现，感兴趣的读者可以参考这篇文章[关于CLH的介绍](http://coderbee.net/index.php/concurrent/20131115/577)，写得简单明了。\n\n## 示例图解析\n\n下面属于回顾环节，用简单的示例来说一遍，如果上面的有些东西没看懂，这里还有一次帮助你理解的机会。\n\n首先，第一个线程调用 reentrantLock.lock()，翻到最前面可以发现，tryAcquire(1) 直接就返回 true 了，结束。只是设置了 state=1，连 head 都没有初始化，更谈不上什么阻塞队列了。要是线程 1 调用 unlock() 了，才有线程 2 来，那世界就太太太平了，完全没有交集嘛，那我还要 AQS 干嘛。\n\n如果线程 1 没有调用 unlock() 之前，线程 2 调用了 lock(), 想想会发生什么？\n\n线程 2 会初始化 head【new Node()】，同时线程 2 也会插入到阻塞队列并挂起 (注意看这里是一个 for 循环，而且设置 head 和 tail 的部分是不 return 的，只有入队成功才会跳出循环)\n\n```\nprivate Node enq(final Node node) {\n    for (;;) {\n        Node t = tail;\n        if (t == null) { // Must initialize\n            if (compareAndSetHead(new Node()))\n                tail = head;\n        } else {\n            node.prev = t;\n            if (compareAndSetTail(t, node)) {\n                t.next = node;\n                return t;\n            }\n        }\n    }\n}\n```\n\n首先，是线程 2 初始化 head 节点，此时 head==tail, waitStatus==0\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404200724.png)\n\n然后线程 2 入队：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404200745.png)\n\n同时我们也要看此时节点的 waitStatus，我们知道 head 节点是线程 2 初始化的，此时的 waitStatus 没有设置， java 默认会设置为 0，但是到 shouldParkAfterFailedAcquire 这个方法的时候，线程 2 会把前驱节点，也就是 head 的waitStatus设置为 -1。\n\n那线程 2 节点此时的 waitStatus 是多少呢，由于没有设置，所以是 0；\n\n如果线程 3 此时再进来，直接插到线程 2 的后面就可以了，此时线程 3 的 waitStatus 是 0，到 shouldParkAfterFailedAcquire 方法的时候把前驱节点线程 2 的 waitStatus 设置为 -1。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404200816.png)\n这里可以简单说下 waitStatus 中 SIGNAL(-1) 状态的意思，Doug Lea 注释的是：代表后继节点需要被唤醒。也就是说这个 waitStatus 其实代表的不是自己的状态，而是后继节点的状态，我们知道，每个 node 在入队的时候，都会把前驱节点的状态改为 SIGNAL，然后阻塞，等待被前驱唤醒。这里涉及的是两个问题：有线程取消了排队、唤醒操作。其实本质是一样的，读者也可以顺着 “waitStatus代表后继节点的状态” 这种思路去看一遍源码。\n"
  },
  {
    "path": "docs/Java/concurrency/Java并发指南：Java中的HashMap和ConcurrentHashMap全解析.md",
    "content": "# 目录\n  * [前言](#前言)\n  * [Java7 HashMap](#java7-hashmap)\n    * [put 过程分析](#put-过程分析)\n      * [数组初始化](#数组初始化)\n      * [计算具体数组位置](#计算具体数组位置)\n      * [添加节点到链表中](#添加节点到链表中)\n      * [数组扩容](#数组扩容)\n    * [get 过程分析](#get-过程分析)\n  * [Java7 ConcurrentHashMap](#java7-concurrenthashmap)\n    * [初始化](#初始化)\n    * [put 过程分析](#put-过程分析-1)\n      * [初始化槽: ensureSegment](#初始化槽-ensuresegment)\n      * [获取写入锁: scanAndLockForPut](#获取写入锁-scanandlockforput)\n      * [扩容: rehash](#扩容-rehash)\n    * [get 过程分析](#get-过程分析-1)\n    * [并发问题分析](#并发问题分析)\n  * [Java8 HashMap](#java8-hashmap)\n    * [put 过程分析](#put-过程分析-2)\n      * [数组扩容](#数组扩容-1)\n    * [get 过程分析](#get-过程分析-2)\n  * [Java8 ConcurrentHashMap](#java8-concurrenthashmap)\n    * [初始化](#初始化-1)\n    * [put 过程分析](#put-过程分析-3)\n      * [初始化数组：initTable](#初始化数组：inittable)\n      * [链表转红黑树: treeifyBin](#链表转红黑树-treeifybin)\n    * [扩容：tryPresize](#扩容：trypresize)\n      * [数据迁移：transfer](#数据迁移：transfer)\n    * [get 过程分析](#get-过程分析-3)\n  * [总结](#总结)\n\n\n本文转自：https://www.javadoop.com/\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章同步发于我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《Java并发指南》其中一篇，本文大部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何全面深入地学习Java并发技术，从Java多线程基础，再到并发编程的基础知识，从Java并发包的入门和实战，再到JUC的源码剖析，一步步地学习Java并发编程，并上手进行实战，以便让你更完整地了解整个Java并发编程知识体系，形成自己的知识框架。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供一些对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n<!--more -->\n\n## 前言\n\n今天发一篇\"水文\"，可能很多读者都会表示不理解，不过我想把它作为并发序列文章中不可缺少的一块来介绍。本来以为花不了多少时间的，不过最终还是投入了挺多时间来完成这篇文章的。\n\n网上关于 HashMap 和 ConcurrentHashMap 的文章确实不少，不过缺斤少两的文章比较多，所以才想自己也写一篇，把细节说清楚说透，尤其像 Java8 中的 ConcurrentHashMap，大部分文章都说不清楚。终归是希望能降低大家学习的成本，不希望大家到处找各种不是很靠谱的文章，看完一篇又一篇，可是还是模模糊糊。\n\n阅读建议：四节基本上可以进行独立阅读，建议初学者可按照 Java7 HashMap -> Java7 ConcurrentHashMap -> Java8 HashMap -> Java8 ConcurrentHashMap 顺序进行阅读，可适当降低阅读门槛。\n\n阅读前提：本文分析的是源码，所以至少读者要熟悉它们的接口使用，同时，对于并发，读者至少要知道 CAS、ReentrantLock、UNSAFE 操作这几个基本的知识，文中不会对这些知识进行介绍。Java8 用到了红黑树，不过本文不会进行展开，感兴趣的读者请自行查找相关资料。\n\n## Java7 HashMap\n\nHashMap 是最简单的，一来我们非常熟悉，二来就是它不支持并发操作，所以源码也非常简单。\n\n首先，我们用下面这张图来介绍 HashMap 的结构。\n\n![1](https://www.javadoop.com/blogimages/map/1.png)\n\n> 这个仅仅是示意图，因为没有考虑到数组要扩容的情况，具体的后面再说。\n\n大方向上，HashMap 里面是一个**数组**，然后数组中每个元素是一个**单向链表**。\n\n上图中，每个绿色的实体是嵌套类 Entry 的实例，Entry 包含四个属性：key, value, hash 值和用于单向链表的 next。\n\ncapacity：当前数组容量，始终保持 2^n，可以扩容，扩容后数组大小为当前的 2 倍。\n\nloadFactor：负载因子，默认为 0.75。\n\nthreshold：扩容的阈值，等于 capacity * loadFactor\n\n### put 过程分析\n\n还是比较简单的，跟着代码走一遍吧。\n\n```\npublic V put(K key, V value) {\n    // 当插入第一个元素的时候，需要先初始化数组大小\n    if (table == EMPTY_TABLE) {\n        inflateTable(threshold);\n    }\n    // 如果 key 为 null，感兴趣的可以往里看，最终会将这个 entry 放到 table[0] 中\n    if (key == null)\n        return putForNullKey(value);\n    // 1\\. 求 key 的 hash 值\n    int hash = hash(key);\n    // 2\\. 找到对应的数组下标\n    int i = indexFor(hash, table.length);\n    // 3\\. 遍历一下对应下标处的链表，看是否有重复的 key 已经存在，\n    //    如果有，直接覆盖，put 方法返回旧值就结束了\n    for (Entry<K,V> e = table[i]; e != null; e = e.next) {\n        Object k;\n        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {\n            V oldValue = e.value;\n            e.value = value;\n            e.recordAccess(this);\n            return oldValue;\n        }\n    }\n\n    modCount++;\n    // 4\\. 不存在重复的 key，将此 entry 添加到链表中，细节后面说\n    addEntry(hash, key, value, i);\n    return null;\n}\n```\n\n#### 数组初始化\n\n在第一个元素插入 HashMap 的时候做一次数组的初始化，就是先确定初始的数组大小，并计算数组扩容的阈值。\n\n```\nprivate void inflateTable(int toSize) {\n    // 保证数组大小一定是 2 的 n 次方。\n    // 比如这样初始化：new HashMap(20)，那么处理成初始数组大小是 32\n    int capacity = roundUpToPowerOf2(toSize);\n    // 计算扩容阈值：capacity * loadFactor\n    threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);\n    // 算是初始化数组吧\n    table = new Entry[capacity];\n    initHashSeedAsNeeded(capacity); //ignore\n}\n```\n\n这里有一个将数组大小保持为 2 的 n 次方的做法，Java7 和 Java8 的 HashMap 和 ConcurrentHashMap 都有相应的要求，只不过实现的代码稍微有些不同，后面再看到的时候就知道了。\n\n#### 计算具体数组位置\n\n这个简单，我们自己也能 YY 一个：使用 key 的 hash 值对数组长度进行取模就可以了。\n\n```\nstatic int indexFor(int hash, int length) {\n    // assert Integer.bitCount(length) == 1 : \"length must be a non-zero power of 2\";\n    return hash & (length-1);\n}\n```\n\n这个方法很简单，简单说就是取 hash 值的低 n 位。如在数组长度为 32 的时候，其实取的就是 key 的 hash 值的低 5 位，作为它在数组中的下标位置。\n\n#### 添加节点到链表中\n\n找到数组下标后，会先进行 key 判重，如果没有重复，就准备将新值放入到链表的**表头**。\n\n```\nvoid addEntry(int hash, K key, V value, int bucketIndex) {\n    // 如果当前 HashMap 大小已经达到了阈值，并且新值要插入的数组位置已经有元素了，那么要扩容\n    if ((size >= threshold) && (null != table[bucketIndex])) {\n        // 扩容，后面会介绍一下\n        resize(2 * table.length);\n        // 扩容以后，重新计算 hash 值\n        hash = (null != key) ? hash(key) : 0;\n        // 重新计算扩容后的新的下标\n        bucketIndex = indexFor(hash, table.length);\n    }\n    // 往下看\n    createEntry(hash, key, value, bucketIndex);\n}\n// 这个很简单，其实就是将新值放到链表的表头，然后 size++\nvoid createEntry(int hash, K key, V value, int bucketIndex) {\n    Entry<K,V> e = table[bucketIndex];\n    table[bucketIndex] = new Entry<>(hash, key, value, e);\n    size++;\n}\n```\n\n这个方法的主要逻辑就是先判断是否需要扩容，需要的话先扩容，然后再将这个新的数据插入到扩容后的数组的相应位置处的链表的表头。\n\n#### 数组扩容\n\n前面我们看到，在插入新值的时候，如果**当前的 size 已经达到了阈值，并且要插入的数组位置上已经有元素**，那么就会触发扩容，扩容后，数组大小为原来的 2 倍。\n\n```\nvoid resize(int newCapacity) {\n    Entry[] oldTable = table;\n    int oldCapacity = oldTable.length;\n    if (oldCapacity == MAXIMUM_CAPACITY) {\n        threshold = Integer.MAX_VALUE;\n        return;\n    }\n    // 新的数组\n    Entry[] newTable = new Entry[newCapacity];\n    // 将原来数组中的值迁移到新的更大的数组中\n    transfer(newTable, initHashSeedAsNeeded(newCapacity));\n    table = newTable;\n    threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);\n}\n```\n\n扩容就是用一个新的大数组替换原来的小数组，并将原来数组中的值迁移到新的数组中。\n\n由于是双倍扩容，迁移过程中，会将原来 table[i] 中的链表的所有节点，分拆到新的数组的 newTable[i] 和 newTable[i + oldLength] 位置上。如原来数组长度是 16，那么扩容后，原来 table[0] 处的链表中的所有元素会被分配到新数组中 newTable[0] 和 newTable[16] 这两个位置。代码比较简单，这里就不展开了。\n\n### get 过程分析\n\n相对于 put 过程，get 过程是非常简单的。\n\n1.  根据 key 计算 hash 值。\n2.  找到相应的数组下标：hash & (length - 1)。\n3.  遍历该数组位置处的链表，直到找到相等(==或equals)的 key。\n\n```\npublic V get(Object key) {\n    // 之前说过，key 为 null 的话，会被放到 table[0]，所以只要遍历下 table[0] 处的链表就可以了\n    if (key == null)\n        return getForNullKey();\n    // \n    Entry<K,V> entry = getEntry(key);\n\n    return null == entry ? null : entry.getValue();\n}\n```\n\ngetEntry(key):\n\n```\nfinal Entry<K,V> getEntry(Object key) {\n    if (size == 0) {\n        return null;\n    }\n\n    int hash = (key == null) ? 0 : hash(key);\n    // 确定数组下标，然后从头开始遍历链表，直到找到为止\n    for (Entry<K,V> e = table[indexFor(hash, table.length)];\n         e != null;\n         e = e.next) {\n        Object k;\n        if (e.hash == hash &&\n            ((k = e.key) == key || (key != null && key.equals(k))))\n            return e;\n    }\n    return null;\n}\n```\n\n## Java7 ConcurrentHashMap\n\nConcurrentHashMap 和 HashMap 思路是差不多的，但是因为它支持并发操作，所以要复杂一些。\n\n整个 ConcurrentHashMap 由一个个 Segment 组成，Segment 代表”部分“或”一段“的意思，所以很多地方都会将其描述为**分段锁**。注意，行文中，我很多地方用了“**槽**”来代表一个 segment。\n\n简单理解就是，ConcurrentHashMap 是一个 Segment 数组，Segment 通过继承 ReentrantLock 来进行加锁，所以每次需要加锁的操作锁住的是一个 segment，这样只要保证每个 Segment 是线程安全的，也就实现了全局的线程安全。\n\n![3](https://www.javadoop.com/blogimages/map/3.png)\n\n**concurrencyLevel**：并行级别、并发数、Segment 数，怎么翻译不重要，理解它。默认是 16，也就是说 ConcurrentHashMap 有 16 个 Segments，所以理论上，这个时候，最多可以同时支持 16 个线程并发写，只要它们的操作分别分布在不同的 Segment 上。这个值可以在初始化的时候设置为其他值，但是一旦初始化以后，它是不可以扩容的。\n\n再具体到每个 Segment 内部，其实每个 Segment 很像之前介绍的 HashMap，不过它要保证线程安全，所以处理起来要麻烦些。\n\n### 初始化\n\ninitialCapacity：初始容量，这个值指的是整个 ConcurrentHashMap 的初始容量，实际操作的时候需要平均分给每个 Segment。\n\nloadFactor：负载因子，之前我们说了，Segment 数组不可以扩容，所以这个负载因子是给每个 Segment 内部使用的。\n\n```\npublic ConcurrentHashMap(int initialCapacity,\n                         float loadFactor, int concurrencyLevel) {\n    if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)\n        throw new IllegalArgumentException();\n    if (concurrencyLevel > MAX_SEGMENTS)\n        concurrencyLevel = MAX_SEGMENTS;\n    // Find power-of-two sizes best matching arguments\n    int sshift = 0;\n    int ssize = 1;\n    // 计算并行级别 ssize，因为要保持并行级别是 2 的 n 次方\n    while (ssize < concurrencyLevel) {\n        ++sshift;\n        ssize <<= 1;\n    }\n    // 我们这里先不要那么烧脑，用默认值，concurrencyLevel 为 16，sshift 为 4\n    // 那么计算出 segmentShift 为 28，segmentMask 为 15，后面会用到这两个值\n    this.segmentShift = 32 - sshift;\n    this.segmentMask = ssize - 1;\n\n    if (initialCapacity > MAXIMUM_CAPACITY)\n        initialCapacity = MAXIMUM_CAPACITY;\n\n    // initialCapacity 是设置整个 map 初始的大小，\n    // 这里根据 initialCapacity 计算 Segment 数组中每个位置可以分到的大小\n    // 如 initialCapacity 为 64，那么每个 Segment 或称之为\"槽\"可以分到 4 个\n    int c = initialCapacity / ssize;\n    if (c * ssize < initialCapacity)\n        ++c;\n    // 默认 MIN_SEGMENT_TABLE_CAPACITY 是 2，这个值也是有讲究的，因为这样的话，对于具体的槽上，\n    // 插入一个元素不至于扩容，插入第二个的时候才会扩容\n    int cap = MIN_SEGMENT_TABLE_CAPACITY; \n    while (cap < c)\n        cap <<= 1;\n\n    // 创建 Segment 数组，\n    // 并创建数组的第一个元素 segment[0]\n    Segment<K,V> s0 =\n        new Segment<K,V>(loadFactor, (int)(cap * loadFactor),\n                         (HashEntry<K,V>[])new HashEntry[cap]);\n    Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize];\n    // 往数组写入 segment[0]\n    UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]\n    this.segments = ss;\n}\n```\n\n初始化完成，我们得到了一个 Segment 数组。\n\n我们就当是用 new ConcurrentHashMap() 无参构造函数进行初始化的，那么初始化完成后：\n\n*   Segment 数组长度为 16，不可以扩容\n*   Segment[i] 的默认大小为 2，负载因子是 0.75，得出初始阈值为 1.5，也就是以后插入第一个元素不会触发扩容，插入第二个会进行第一次扩容\n*   这里初始化了 segment[0]，其他位置还是 null，至于为什么要初始化 segment[0]，后面的代码会介绍\n*   当前 segmentShift 的值为 32 - 4 = 28，segmentMask 为 16 - 1 = 15，姑且把它们简单翻译为**移位数**和**掩码**，这两个值马上就会用到\n\n### put 过程分析\n\n我们先看 put 的主流程，对于其中的一些关键细节操作，后面会进行详细介绍。\n\n```\npublic V put(K key, V value) {\n    Segment<K,V> s;\n    if (value == null)\n        throw new NullPointerException();\n    // 1\\. 计算 key 的 hash 值\n    int hash = hash(key);\n    // 2\\. 根据 hash 值找到 Segment 数组中的位置 j\n    //    hash 是 32 位，无符号右移 segmentShift(28) 位，剩下高 4 位，\n    //    然后和 segmentMask(15) 做一次与操作，也就是说 j 是 hash 值的高 4 位，也就是槽的数组下标\n    int j = (hash >>> segmentShift) & segmentMask;\n    // 刚刚说了，初始化的时候初始化了 segment[0]，但是其他位置还是 null，\n    // ensureSegment(j) 对 segment[j] 进行初始化\n    if ((s = (Segment<K,V>)UNSAFE.getObject          // nonvolatile; recheck\n         (segments, (j << SSHIFT) + SBASE)) == null) //  in ensureSegment\n        s = ensureSegment(j);\n    // 3\\. 插入新值到 槽 s 中\n    return s.put(key, hash, value, false);\n}\n```\n\n第一层皮很简单，根据 hash 值很快就能找到相应的 Segment，之后就是 Segment 内部的 put 操作了。\n\nSegment 内部是由**数组+链表**组成的。\n\n```\nfinal V put(K key, int hash, V value, boolean onlyIfAbsent) {\n    // 在往该 segment 写入前，需要先获取该 segment 的独占锁\n    //    先看主流程，后面还会具体介绍这部分内容\n    HashEntry<K,V> node = tryLock() ? null :\n        scanAndLockForPut(key, hash, value);\n    V oldValue;\n    try {\n        // 这个是 segment 内部的数组\n        HashEntry<K,V>[] tab = table;\n        // 再利用 hash 值，求应该放置的数组下标\n        int index = (tab.length - 1) & hash;\n        // first 是数组该位置处的链表的表头\n        HashEntry<K,V> first = entryAt(tab, index);\n\n        // 下面这串 for 循环虽然很长，不过也很好理解，想想该位置没有任何元素和已经存在一个链表这两种情况\n        for (HashEntry<K,V> e = first;;) {\n            if (e != null) {\n                K k;\n                if ((k = e.key) == key ||\n                    (e.hash == hash && key.equals(k))) {\n                    oldValue = e.value;\n                    if (!onlyIfAbsent) {\n                        // 覆盖旧值\n                        e.value = value;\n                        ++modCount;\n                    }\n                    break;\n                }\n                // 继续顺着链表走\n                e = e.next;\n            }\n            else {\n                // node 到底是不是 null，这个要看获取锁的过程，不过和这里都没有关系。\n                // 如果不为 null，那就直接将它设置为链表表头；如果是null，初始化并设置为链表表头。\n                if (node != null)\n                    node.setNext(first);\n                else\n                    node = new HashEntry<K,V>(hash, key, value, first);\n\n                int c = count + 1;\n                // 如果超过了该 segment 的阈值，这个 segment 需要扩容\n                if (c > threshold && tab.length < MAXIMUM_CAPACITY)\n                    rehash(node); // 扩容后面也会具体分析\n                else\n                    // 没有达到阈值，将 node 放到数组 tab 的 index 位置，\n                    // 其实就是将新的节点设置成原链表的表头\n                    setEntryAt(tab, index, node);\n                ++modCount;\n                count = c;\n                oldValue = null;\n                break;\n            }\n        }\n    } finally {\n        // 解锁\n        unlock();\n    }\n    return oldValue;\n}\n```\n\n整体流程还是比较简单的，由于有独占锁的保护，所以 segment 内部的操作并不复杂。至于这里面的并发问题，我们稍后再进行介绍。\n\n到这里 put 操作就结束了，接下来，我们说一说其中几步关键的操作。\n\n#### 初始化槽: ensureSegment\n\nConcurrentHashMap 初始化的时候会初始化第一个槽 segment[0]，对于其他槽来说，在插入第一个值的时候进行初始化。\n\n这里需要考虑并发，因为很可能会有多个线程同时进来初始化同一个槽 segment[k]，不过只要有一个成功了就可以。\n\n```\nprivate Segment<K,V> ensureSegment(int k) {\n    final Segment<K,V>[] ss = this.segments;\n    long u = (k << SSHIFT) + SBASE; // raw offset\n    Segment<K,V> seg;\n    if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) {\n        // 这里看到为什么之前要初始化 segment[0] 了，\n        // 使用当前 segment[0] 处的数组长度和负载因子来初始化 segment[k]\n        // 为什么要用“当前”，因为 segment[0] 可能早就扩容过了\n        Segment<K,V> proto = ss[0];\n        int cap = proto.table.length;\n        float lf = proto.loadFactor;\n        int threshold = (int)(cap * lf);\n\n        // 初始化 segment[k] 内部的数组\n        HashEntry<K,V>[] tab = (HashEntry<K,V>[])new HashEntry[cap];\n        if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))\n            == null) { // 再次检查一遍该槽是否被其他线程初始化了。\n\n            Segment<K,V> s = new Segment<K,V>(lf, threshold, tab);\n            // 使用 while 循环，内部用 CAS，当前线程成功设值或其他线程成功设值后，退出\n            while ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))\n                   == null) {\n                if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s))\n                    break;\n            }\n        }\n    }\n    return seg;\n}\n```\n\n总的来说，ensureSegment(int k) 比较简单，对于并发操作使用 CAS 进行控制。\n\n> 我没搞懂这里为什么要搞一个 while 循环，CAS 失败不就代表有其他线程成功了吗，为什么要再进行判断？\n> \n> 感谢评论区的**李子木**，如果当前线程 CAS 失败，这里的 while 循环是为了将 seg 赋值返回。\n\n#### 获取写入锁: scanAndLockForPut\n\n前面我们看到，在往某个 segment 中 put 的时候，首先会调用 node = tryLock() ? null : scanAndLockForPut(key, hash, value)，也就是说先进行一次 tryLock() 快速获取该 segment 的独占锁，如果失败，那么进入到 scanAndLockForPut 这个方法来获取锁。\n\n下面我们来具体分析这个方法中是怎么控制加锁的。\n\n```\nprivate HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) {\n    HashEntry<K,V> first = entryForHash(this, hash);\n    HashEntry<K,V> e = first;\n    HashEntry<K,V> node = null;\n    int retries = -1; // negative while locating node\n\n    // 循环获取锁\n    while (!tryLock()) {\n        HashEntry<K,V> f; // to recheck first below\n        if (retries < 0) {\n            if (e == null) {\n                if (node == null) // speculatively create node\n                    // 进到这里说明数组该位置的链表是空的，没有任何元素\n                    // 当然，进到这里的另一个原因是 tryLock() 失败，所以该槽存在并发，不一定是该位置\n                    node = new HashEntry<K,V>(hash, key, value, null);\n                retries = 0;\n            }\n            else if (key.equals(e.key))\n                retries = 0;\n            else\n                // 顺着链表往下走\n                e = e.next;\n        }\n        // 重试次数如果超过 MAX_SCAN_RETRIES（单核1多核64），那么不抢了，进入到阻塞队列等待锁\n        //    lock() 是阻塞方法，直到获取锁后返回\n        else if (++retries > MAX_SCAN_RETRIES) {\n            lock();\n            break;\n        }\n        else if ((retries & 1) == 0 &&\n                 // 这个时候是有大问题了，那就是有新的元素进到了链表，成为了新的表头\n                 //     所以这边的策略是，相当于重新走一遍这个 scanAndLockForPut 方法\n                 (f = entryForHash(this, hash)) != first) {\n            e = first = f; // re-traverse if entry changed\n            retries = -1;\n        }\n    }\n    return node;\n}\n```\n\n这个方法有两个出口，一个是 tryLock() 成功了，循环终止，另一个就是重试次数超过了 MAX_SCAN_RETRIES，进到 lock() 方法，此方法会阻塞等待，直到成功拿到独占锁。\n\n这个方法就是看似复杂，但是其实就是做了一件事，那就是**获取该 segment 的独占锁**，如果需要的话顺便实例化了一下 node。\n\n#### 扩容: rehash\n\n重复一下，segment 数组不能扩容，扩容是 segment 数组某个位置内部的数组 HashEntry<K,V>[] 进行扩容，扩容后，容量为原来的 2 倍。\n\n首先，我们要回顾一下触发扩容的地方，put 的时候，如果判断该值的插入会导致该 segment 的元素个数超过阈值，那么先进行扩容，再插值，读者这个时候可以回去 put 方法看一眼。\n\n该方法不需要考虑并发，因为到这里的时候，是持有该 segment 的独占锁的。\n\n```\n// 方法参数上的 node 是这次扩容后，需要添加到新的数组中的数据。\nprivate void rehash(HashEntry<K,V> node) {\n    HashEntry<K,V>[] oldTable = table;\n    int oldCapacity = oldTable.length;\n    // 2 倍\n    int newCapacity = oldCapacity << 1;\n    threshold = (int)(newCapacity * loadFactor);\n    // 创建新数组\n    HashEntry<K,V>[] newTable =\n        (HashEntry<K,V>[]) new HashEntry[newCapacity];\n    // 新的掩码，如从 16 扩容到 32，那么 sizeMask 为 31，对应二进制 ‘000...00011111’\n    int sizeMask = newCapacity - 1;\n\n    // 遍历原数组，老套路，将原数组位置 i 处的链表拆分到 新数组位置 i 和 i+oldCap 两个位置\n    for (int i = 0; i < oldCapacity ; i++) {\n        // e 是链表的第一个元素\n        HashEntry<K,V> e = oldTable[i];\n        if (e != null) {\n            HashEntry<K,V> next = e.next;\n            // 计算应该放置在新数组中的位置，\n            // 假设原数组长度为 16，e 在 oldTable[3] 处，那么 idx 只可能是 3 或者是 3 + 16 = 19\n            int idx = e.hash & sizeMask;\n            if (next == null)   // 该位置处只有一个元素，那比较好办\n                newTable[idx] = e;\n            else { // Reuse consecutive sequence at same slot\n                // e 是链表表头\n                HashEntry<K,V> lastRun = e;\n                // idx 是当前链表的头结点 e 的新位置\n                int lastIdx = idx;\n\n                // 下面这个 for 循环会找到一个 lastRun 节点，这个节点之后的所有元素是将要放到一起的\n                for (HashEntry<K,V> last = next;\n                     last != null;\n                     last = last.next) {\n                    int k = last.hash & sizeMask;\n                    if (k != lastIdx) {\n                        lastIdx = k;\n                        lastRun = last;\n                    }\n                }\n                // 将 lastRun 及其之后的所有节点组成的这个链表放到 lastIdx 这个位置\n                newTable[lastIdx] = lastRun;\n                // 下面的操作是处理 lastRun 之前的节点，\n                //    这些节点可能分配在另一个链表中，也可能分配到上面的那个链表中\n                for (HashEntry<K,V> p = e; p != lastRun; p = p.next) {\n                    V v = p.value;\n                    int h = p.hash;\n                    int k = h & sizeMask;\n                    HashEntry<K,V> n = newTable[k];\n                    newTable[k] = new HashEntry<K,V>(h, p.key, v, n);\n                }\n            }\n        }\n    }\n    // 将新来的 node 放到新数组中刚刚的 两个链表之一 的 头部\n    int nodeIndex = node.hash & sizeMask; // add the new node\n    node.setNext(newTable[nodeIndex]);\n    newTable[nodeIndex] = node;\n    table = newTable;\n}\n```\n\n这里的扩容比之前的 HashMap 要复杂一些，代码难懂一点。上面有两个挨着的 for 循环，第一个 for 有什么用呢？\n\n仔细一看发现，如果没有第一个 for 循环，也是可以工作的，但是，这个 for 循环下来，如果 lastRun 的后面还有比较多的节点，那么这次就是值得的。因为我们只需要克隆 lastRun 前面的节点，后面的一串节点跟着 lastRun 走就是了，不需要做任何操作。\n\n我觉得 Doug Lea 的这个想法也是挺有意思的，不过比较坏的情况就是每次 lastRun 都是链表的最后一个元素或者很靠后的元素，那么这次遍历就有点浪费了。**不过 Doug Lea 也说了，根据统计，如果使用默认的阈值，大约只有 1/6 的节点需要克隆**。\n\n### get 过程分析\n\n相对于 put 来说，get 真的不要太简单。\n\n1.  计算 hash 值，找到 segment 数组中的具体位置，或我们前面用的“槽”\n2.  槽中也是一个数组，根据 hash 找到数组中具体的位置\n3.  到这里是链表了，顺着链表进行查找即可\n\n```\npublic V get(Object key) {\n    Segment<K,V> s; // manually integrate access methods to reduce overhead\n    HashEntry<K,V>[] tab;\n    // 1\\. hash 值\n    int h = hash(key);\n    long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;\n    // 2\\. 根据 hash 找到对应的 segment\n    if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&\n        (tab = s.table) != null) {\n        // 3\\. 找到segment 内部数组相应位置的链表，遍历\n        for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile\n                 (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);\n             e != null; e = e.next) {\n            K k;\n            if ((k = e.key) == key || (e.hash == h && key.equals(k)))\n                return e.value;\n        }\n    }\n    return null;\n}\n```\n\n### 并发问题分析\n\n现在我们已经说完了 put 过程和 get 过程，我们可以看到 get 过程中是没有加锁的，那自然我们就需要去考虑并发问题。\n\n添加节点的操作 put 和删除节点的操作 remove 都是要加 segment 上的独占锁的，所以它们之间自然不会有问题，我们需要考虑的问题就是 get 的时候在同一个 segment 中发生了 put 或 remove 操作。\n\n1.  put 操作的线程安全性。\n\n    1.  初始化槽，这个我们之前就说过了，使用了 CAS 来初始化 Segment 中的数组。\n    2.  添加节点到链表的操作是插入到表头的，所以，如果这个时候 get 操作在链表遍历的过程已经到了中间，是不会影响的。当然，另一个并发问题就是 get 操作在 put 之后，需要保证刚刚插入表头的节点被读取，这个依赖于 setEntryAt 方法中使用的 UNSAFE.putOrderedObject。\n    3.  扩容。扩容是新创建了数组，然后进行迁移数据，最后面将 newTable 设置给属性 table。所以，如果 get 操作此时也在进行，那么也没关系，如果 get 先行，那么就是在旧的 table 上做查询操作；而 put 先行，那么 put 操作的可见性保证就是 table 使用了 volatile 关键字。\n2.  remove 操作的线程安全性。\n\n    remove 操作我们没有分析源码，所以这里说的读者感兴趣的话还是需要到源码中去求实一下的。\n\n    get 操作需要遍历链表，但是 remove 操作会\"破坏\"链表。\n\n    如果 remove 破坏的节点 get 操作已经过去了，那么这里不存在任何问题。\n\n    如果 remove 先破坏了一个节点，分两种情况考虑。 1、如果此节点是头结点，那么需要将头结点的 next 设置为数组该位置的元素，table 虽然使用了 volatile 修饰，但是 volatile 并不能提供数组内部操作的可见性保证，所以源码中使用了 UNSAFE 来操作数组，请看方法 setEntryAt。2、如果要删除的节点不是头结点，它会将要删除节点的后继节点接到前驱节点中，这里的并发保证就是 next 属性是 volatile 的。\n\n## Java8 HashMap\n\nJava8 对 HashMap 进行了一些修改，最大的不同就是利用了红黑树，所以其由**数组+链表+红黑树**组成。\n\n根据 Java7 HashMap 的介绍，我们知道，查找的时候，根据 hash 值我们能够快速定位到数组的具体下标，但是之后的话，需要顺着链表一个个比较下去才能找到我们需要的，时间复杂度取决于链表的长度，为**O(n)**。\n\n为了降低这部分的开销，在 Java8 中，当链表中的元素达到了 8 个时，会将链表转换为红黑树，在这些位置进行查找的时候可以降低时间复杂度为**O(logN)**。\n\n来一张图简单示意一下吧：\n\n![2](https://www.javadoop.com/blogimages/map/2.png)\n\n> 注意，上图是示意图，主要是描述结构，不会达到这个状态的，因为这么多数据的时候早就扩容了。\n\n下面，我们还是用代码来介绍吧，个人感觉，Java8 的源码可读性要差一些，不过精简一些。\n\nJava7 中使用 Entry 来代表每个 HashMap 中的数据节点，Java8 中使用**Node**，基本没有区别，都是 key，value，hash 和 next 这四个属性，不过，Node 只能用于链表的情况，红黑树的情况需要使用**TreeNode**。\n\n我们根据数组元素中，第一个节点数据类型是 Node 还是 TreeNode 来判断该位置下是链表还是红黑树的。\n\n### put 过程分析\n\n```\npublic V put(K key, V value) {\n    return putVal(hash(key), key, value, false, true);\n}\n\n// 第三个参数 onlyIfAbsent 如果是 true，那么只有在不存在该 key 时才会进行 put 操作\n// 第四个参数 evict 我们这里不关心\nfinal V putVal(int hash, K key, V value, boolean onlyIfAbsent,\n               boolean evict) {\n    Node<K,V>[] tab; Node<K,V> p; int n, i;\n    // 第一次 put 值的时候，会触发下面的 resize()，类似 java7 的第一次 put 也要初始化数组长度\n    // 第一次 resize 和后续的扩容有些不一样，因为这次是数组从 null 初始化到默认的 16 或自定义的初始容量\n    if ((tab = table) == null || (n = tab.length) == 0)\n        n = (tab = resize()).length;\n    // 找到具体的数组下标，如果此位置没有值，那么直接初始化一下 Node 并放置在这个位置就可以了\n    if ((p = tab[i = (n - 1) & hash]) == null)\n        tab[i] = newNode(hash, key, value, null);\n\n    else {// 数组该位置有数据\n        Node<K,V> e; K k;\n        // 首先，判断该位置的第一个数据和我们要插入的数据，key 是不是\"相等\"，如果是，取出这个节点\n        if (p.hash == hash &&\n            ((k = p.key) == key || (key != null && key.equals(k))))\n            e = p;\n        // 如果该节点是代表红黑树的节点，调用红黑树的插值方法，本文不展开说红黑树\n        else if (p instanceof TreeNode)\n            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);\n        else {\n            // 到这里，说明数组该位置上是一个链表\n            for (int binCount = 0; ; ++binCount) {\n                // 插入到链表的最后面(Java7 是插入到链表的最前面)\n                if ((e = p.next) == null) {\n                    p.next = newNode(hash, key, value, null);\n                    // TREEIFY_THRESHOLD 为 8，所以，如果新插入的值是链表中的第 8 个\n                    // 会触发下面的 treeifyBin，也就是将链表转换为红黑树\n                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st\n                        treeifyBin(tab, hash);\n                    break;\n                }\n                // 如果在该链表中找到了\"相等\"的 key(== 或 equals)\n                if (e.hash == hash &&\n                    ((k = e.key) == key || (key != null && key.equals(k))))\n                    // 此时 break，那么 e 为链表中[与要插入的新值的 key \"相等\"]的 node\n                    break;\n                p = e;\n            }\n        }\n        // e!=null 说明存在旧值的key与要插入的key\"相等\"\n        // 对于我们分析的put操作，下面这个 if 其实就是进行 \"值覆盖\"，然后返回旧值\n        if (e != null) {\n            V oldValue = e.value;\n            if (!onlyIfAbsent || oldValue == null)\n                e.value = value;\n            afterNodeAccess(e);\n            return oldValue;\n        }\n    }\n    ++modCount;\n    // 如果 HashMap 由于新插入这个值导致 size 已经超过了阈值，需要进行扩容\n    if (++size > threshold)\n        resize();\n    afterNodeInsertion(evict);\n    return null;\n}\n```\n\n和 Java7 稍微有点不一样的地方就是，Java7 是先扩容后插入新值的，Java8 先插值再扩容，不过这个不重要。\n\n#### 数组扩容\n\nresize() 方法用于**初始化数组**或**数组扩容**，每次扩容后，容量为原来的 2 倍，并进行数据迁移。\n\n```\nfinal Node<K,V>[] resize() {\n    Node<K,V>[] oldTab = table;\n    int oldCap = (oldTab == null) ? 0 : oldTab.length;\n    int oldThr = threshold;\n    int newCap, newThr = 0;\n    if (oldCap > 0) { // 对应数组扩容\n        if (oldCap >= MAXIMUM_CAPACITY) {\n            threshold = Integer.MAX_VALUE;\n            return oldTab;\n        }\n        // 将数组大小扩大一倍\n        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&\n                 oldCap >= DEFAULT_INITIAL_CAPACITY)\n            // 将阈值扩大一倍\n            newThr = oldThr << 1; // double threshold\n    }\n    else if (oldThr > 0) // 对应使用 new HashMap(int initialCapacity) 初始化后，第一次 put 的时候\n        newCap = oldThr;\n    else {// 对应使用 new HashMap() 初始化后，第一次 put 的时候\n        newCap = DEFAULT_INITIAL_CAPACITY;\n        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);\n    }\n\n    if (newThr == 0) {\n        float ft = (float)newCap * loadFactor;\n        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?\n                  (int)ft : Integer.MAX_VALUE);\n    }\n    threshold = newThr;\n\n    // 用新的数组大小初始化新的数组\n    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];\n    table = newTab; // 如果是初始化数组，到这里就结束了，返回 newTab 即可\n\n    if (oldTab != null) {\n        // 开始遍历原数组，进行数据迁移。\n        for (int j = 0; j < oldCap; ++j) {\n            Node<K,V> e;\n            if ((e = oldTab[j]) != null) {\n                oldTab[j] = null;\n                // 如果该数组位置上只有单个元素，那就简单了，简单迁移这个元素就可以了\n                if (e.next == null)\n                    newTab[e.hash & (newCap - 1)] = e;\n                // 如果是红黑树，具体我们就不展开了\n                else if (e instanceof TreeNode)\n                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);\n                else { \n                    // 这块是处理链表的情况，\n                    // 需要将此链表拆成两个链表，放到新的数组中，并且保留原来的先后顺序\n                    // loHead、loTail 对应一条链表，hiHead、hiTail 对应另一条链表，代码还是比较简单的\n                    Node<K,V> loHead = null, loTail = null;\n                    Node<K,V> hiHead = null, hiTail = null;\n                    Node<K,V> next;\n                    do {\n                        next = e.next;\n                        if ((e.hash & oldCap) == 0) {\n                            if (loTail == null)\n                                loHead = e;\n                            else\n                                loTail.next = e;\n                            loTail = e;\n                        }\n                        else {\n                            if (hiTail == null)\n                                hiHead = e;\n                            else\n                                hiTail.next = e;\n                            hiTail = e;\n                        }\n                    } while ((e = next) != null);\n                    if (loTail != null) {\n                        loTail.next = null;\n                        // 第一条链表\n                        newTab[j] = loHead;\n                    }\n                    if (hiTail != null) {\n                        hiTail.next = null;\n                        // 第二条链表的新的位置是 j + oldCap，这个很好理解\n                        newTab[j + oldCap] = hiHead;\n                    }\n                }\n            }\n        }\n    }\n    return newTab;\n}\n```\n\n### get 过程分析\n\n相对于 put 来说，get 真的太简单了。\n\n1.  计算 key 的 hash 值，根据 hash 值找到对应数组下标: hash & (length-1)\n2.  判断数组该位置处的元素是否刚好就是我们要找的，如果不是，走第三步\n3.  判断该元素类型是否是 TreeNode，如果是，用红黑树的方法取数据，如果不是，走第四步\n4.  遍历链表，直到找到相等(==或equals)的 key\n\n```\npublic V get(Object key) {\n    Node<K,V> e;\n    return (e = getNode(hash(key), key)) == null ? null : e.value;\n}\n```\n\n```\nfinal Node<K,V> getNode(int hash, Object key) {\n    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;\n    if ((tab = table) != null && (n = tab.length) > 0 &&\n        (first = tab[(n - 1) & hash]) != null) {\n        // 判断第一个节点是不是就是需要的\n        if (first.hash == hash && // always check first node\n            ((k = first.key) == key || (key != null && key.equals(k))))\n            return first;\n        if ((e = first.next) != null) {\n            // 判断是否是红黑树\n            if (first instanceof TreeNode)\n                return ((TreeNode<K,V>)first).getTreeNode(hash, key);\n\n            // 链表遍历\n            do {\n                if (e.hash == hash &&\n                    ((k = e.key) == key || (key != null && key.equals(k))))\n                    return e;\n            } while ((e = e.next) != null);\n        }\n    }\n    return null;\n}\n```\n\n## Java8 ConcurrentHashMap\n\nJava7 中实现的 ConcurrentHashMap 说实话还是比较复杂的，Java8 对 ConcurrentHashMap 进行了比较大的改动。建议读者可以参考 Java8 中 HashMap 相对于 Java7 HashMap 的改动，对于 ConcurrentHashMap，Java8 也引入了红黑树。\n\n**说实话，Java8 ConcurrentHashMap 源码真心不简单，最难的在于扩容，数据迁移操作不容易看懂。**\n\n我们先用一个示意图来描述下其结构：\n\n![4](https://www.javadoop.com/blogimages/map/4.png)\n\n结构上和 Java8 的 HashMap 基本上一样，不过它要保证线程安全性，所以在源码上确实要复杂一些。\n\n### 初始化\n\n```\n// 这构造函数里，什么都不干\npublic ConcurrentHashMap() {\n}\n```\n\n```\npublic ConcurrentHashMap(int initialCapacity) {\n    if (initialCapacity < 0)\n        throw new IllegalArgumentException();\n    int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?\n               MAXIMUM_CAPACITY :\n               tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));\n    this.sizeCtl = cap;\n}\n```\n\n这个初始化方法有点意思，通过提供初始容量，计算了 sizeCtl，sizeCtl = 【 (1.5 * initialCapacity + 1)，然后向上取最近的 2 的 n 次方】。如 initialCapacity 为 10，那么得到 sizeCtl 为 16，如果 initialCapacity 为 11，得到 sizeCtl 为 32。\n\nsizeCtl 这个属性使用的场景很多，不过只要跟着文章的思路来，就不会被它搞晕了。\n\n如果你爱折腾，也可以看下另一个有三个参数的构造方法，这里我就不说了，大部分时候，我们会使用无参构造函数进行实例化，我们也按照这个思路来进行源码分析吧。\n\n### put 过程分析\n\n仔细地一行一行代码看下去：\n\n```\npublic V put(K key, V value) {\n    return putVal(key, value, false);\n}\n```\n\n```\nfinal V putVal(K key, V value, boolean onlyIfAbsent) {\n    if (key == null || value == null) throw new NullPointerException();\n    // 得到 hash 值\n    int hash = spread(key.hashCode());\n    // 用于记录相应链表的长度\n    int binCount = 0;\n    for (Node<K,V>[] tab = table;;) {\n        Node<K,V> f; int n, i, fh;\n        // 如果数组\"空\"，进行数组初始化\n        if (tab == null || (n = tab.length) == 0)\n            // 初始化数组，后面会详细介绍\n            tab = initTable();\n\n        // 找该 hash 值对应的数组下标，得到第一个节点 f\n        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {\n            // 如果数组该位置为空，\n            //    用一次 CAS 操作将这个新值放入其中即可，这个 put 操作差不多就结束了，可以拉到最后面了\n            //          如果 CAS 失败，那就是有并发操作，进到下一个循环就好了\n            if (casTabAt(tab, i, null,\n                         new Node<K,V>(hash, key, value, null)))\n                break;                   // no lock when adding to empty bin\n        }\n        // hash 居然可以等于 MOVED，这个需要到后面才能看明白，不过从名字上也能猜到，肯定是因为在扩容\n        else if ((fh = f.hash) == MOVED)\n            // 帮助数据迁移，这个等到看完数据迁移部分的介绍后，再理解这个就很简单了\n            tab = helpTransfer(tab, f);\n\n        else { // 到这里就是说，f 是该位置的头结点，而且不为空\n\n            V oldVal = null;\n            // 获取数组该位置的头结点的监视器锁\n            synchronized (f) {\n                if (tabAt(tab, i) == f) {\n                    if (fh >= 0) { // 头结点的 hash 值大于 0，说明是链表\n                        // 用于累加，记录链表的长度\n                        binCount = 1;\n                        // 遍历链表\n                        for (Node<K,V> e = f;; ++binCount) {\n                            K ek;\n                            // 如果发现了\"相等\"的 key，判断是否要进行值覆盖，然后也就可以 break 了\n                            if (e.hash == hash &&\n                                ((ek = e.key) == key ||\n                                 (ek != null && key.equals(ek)))) {\n                                oldVal = e.val;\n                                if (!onlyIfAbsent)\n                                    e.val = value;\n                                break;\n                            }\n                            // 到了链表的最末端，将这个新值放到链表的最后面\n                            Node<K,V> pred = e;\n                            if ((e = e.next) == null) {\n                                pred.next = new Node<K,V>(hash, key,\n                                                          value, null);\n                                break;\n                            }\n                        }\n                    }\n                    else if (f instanceof TreeBin) { // 红黑树\n                        Node<K,V> p;\n                        binCount = 2;\n                        // 调用红黑树的插值方法插入新节点\n                        if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,\n                                                       value)) != null) {\n                            oldVal = p.val;\n                            if (!onlyIfAbsent)\n                                p.val = value;\n                        }\n                    }\n                }\n            }\n\n            if (binCount != 0) {\n                // 判断是否要将链表转换为红黑树，临界值和 HashMap 一样，也是 8\n                if (binCount >= TREEIFY_THRESHOLD)\n                    // 这个方法和 HashMap 中稍微有一点点不同，那就是它不是一定会进行红黑树转换，\n                    // 如果当前数组的长度小于 64，那么会选择进行数组扩容，而不是转换为红黑树\n                    //    具体源码我们就不看了，扩容部分后面说\n                    treeifyBin(tab, i);\n                if (oldVal != null)\n                    return oldVal;\n                break;\n            }\n        }\n    }\n    // \n    addCount(1L, binCount);\n    return null;\n}\n```\n\nput 的主流程看完了，但是至少留下了几个问题，第一个是初始化，第二个是扩容，第三个是帮助数据迁移，这些我们都会在后面进行一一介绍。\n\n#### 初始化数组：initTable\n\n这个比较简单，主要就是初始化一个**合适大小**的数组，然后会设置 sizeCtl。\n\n初始化方法中的并发问题是通过对 sizeCtl 进行一个 CAS 操作来控制的。\n\n```\nprivate final Node<K,V>[] initTable() {\n    Node<K,V>[] tab; int sc;\n    while ((tab = table) == null || tab.length == 0) {\n        // 初始化的\"功劳\"被其他线程\"抢去\"了\n        if ((sc = sizeCtl) < 0)\n            Thread.yield(); // lost initialization race; just spin\n        // CAS 一下，将 sizeCtl 设置为 -1，代表抢到了锁\n        else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {\n            try {\n                if ((tab = table) == null || tab.length == 0) {\n                    // DEFAULT_CAPACITY 默认初始容量是 16\n                    int n = (sc > 0) ? sc : DEFAULT_CAPACITY;\n                    // 初始化数组，长度为 16 或初始化时提供的长度\n                    Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];\n                    // 将这个数组赋值给 table，table 是 volatile 的\n                    table = tab = nt;\n                    // 如果 n 为 16 的话，那么这里 sc = 12\n                    // 其实就是 0.75 * n\n                    sc = n - (n >>> 2);\n                }\n            } finally {\n                // 设置 sizeCtl 为 sc，我们就当是 12 吧\n                sizeCtl = sc;\n            }\n            break;\n        }\n    }\n    return tab;\n}\n```\n\n#### 链表转红黑树: treeifyBin\n\n前面我们在 put 源码分析也说过，treeifyBin 不一定就会进行红黑树转换，也可能是仅仅做数组扩容。我们还是进行源码分析吧。\n\n```\nprivate final void treeifyBin(Node<K,V>[] tab, int index) {\n    Node<K,V> b; int n, sc;\n    if (tab != null) {\n        // MIN_TREEIFY_CAPACITY 为 64\n        // 所以，如果数组长度小于 64 的时候，其实也就是 32 或者 16 或者更小的时候，会进行数组扩容\n        if ((n = tab.length) < MIN_TREEIFY_CAPACITY)\n            // 后面我们再详细分析这个方法\n            tryPresize(n << 1);\n        // b 是头结点\n        else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {\n            // 加锁\n            synchronized (b) {\n\n                if (tabAt(tab, index) == b) {\n                    // 下面就是遍历链表，建立一颗红黑树\n                    TreeNode<K,V> hd = null, tl = null;\n                    for (Node<K,V> e = b; e != null; e = e.next) {\n                        TreeNode<K,V> p =\n                            new TreeNode<K,V>(e.hash, e.key, e.val,\n                                              null, null);\n                        if ((p.prev = tl) == null)\n                            hd = p;\n                        else\n                            tl.next = p;\n                        tl = p;\n                    }\n                    // 将红黑树设置到数组相应位置中\n                    setTabAt(tab, index, new TreeBin<K,V>(hd));\n                }\n            }\n        }\n    }\n}\n```\n\n### 扩容：tryPresize\n\n如果说 Java8 ConcurrentHashMap 的源码不简单，那么说的就是扩容操作和迁移操作。\n\n这个方法要完完全全看懂还需要看之后的 transfer 方法，读者应该提前知道这点。\n\n这里的扩容也是做翻倍扩容的，扩容后数组容量为原来的 2 倍。\n\n```\n// 首先要说明的是，方法参数 size 传进来的时候就已经翻了倍了\nprivate final void tryPresize(int size) {\n    // c：size 的 1.5 倍，再加 1，再往上取最近的 2 的 n 次方。\n    int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY :\n        tableSizeFor(size + (size >>> 1) + 1);\n    int sc;\n    while ((sc = sizeCtl) >= 0) {\n        Node<K,V>[] tab = table; int n;\n\n        // 这个 if 分支和之前说的初始化数组的代码基本上是一样的，在这里，我们可以不用管这块代码\n        if (tab == null || (n = tab.length) == 0) {\n            n = (sc > c) ? sc : c;\n            if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {\n                try {\n                    if (table == tab) {\n                        @SuppressWarnings(\"unchecked\")\n                        Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];\n                        table = nt;\n                        sc = n - (n >>> 2); // 0.75 * n\n                    }\n                } finally {\n                    sizeCtl = sc;\n                }\n            }\n        }\n        else if (c <= sc || n >= MAXIMUM_CAPACITY)\n            break;\n        else if (tab == table) {\n            // 我没看懂 rs 的真正含义是什么，不过也关系不大\n            int rs = resizeStamp(n);\n\n            if (sc < 0) {\n                Node<K,V>[] nt;\n                if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||\n                    sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||\n                    transferIndex <= 0)\n                    break;\n                // 2\\. 用 CAS 将 sizeCtl 加 1，然后执行 transfer 方法\n                //    此时 nextTab 不为 null\n                if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))\n                    transfer(tab, nt);\n            }\n            // 1\\. 将 sizeCtl 设置为 (rs << RESIZE_STAMP_SHIFT) + 2)\n            //     我是没看懂这个值真正的意义是什么？不过可以计算出来的是，结果是一个比较大的负数\n            //  调用 transfer 方法，此时 nextTab 参数为 null\n            else if (U.compareAndSwapInt(this, SIZECTL, sc,\n                                         (rs << RESIZE_STAMP_SHIFT) + 2))\n                transfer(tab, null);\n        }\n    }\n}\n```\n\n这个方法的核心在于 sizeCtl 值的操作，首先将其设置为一个负数，然后执行 transfer(tab, null)，再下一个循环将 sizeCtl 加 1，并执行 transfer(tab, nt)，之后可能是继续 sizeCtl 加 1，并执行 transfer(tab, nt)。\n\n所以，可能的操作就是执行**1 次 transfer(tab, null) + 多次 transfer(tab, nt)**，这里怎么结束循环的需要看完 transfer 源码才清楚。\n\n#### 数据迁移：transfer\n\n下面这个方法有点长，将原来的 tab 数组的元素迁移到新的 nextTab 数组中。\n\n虽然我们之前说的 tryPresize 方法中多次调用 transfer 不涉及多线程，但是这个 transfer 方法可以在其他地方被调用，典型地，我们之前在说 put 方法的时候就说过了，请往上看 put 方法，是不是有个地方调用了 helpTransfer 方法，helpTransfer 方法会调用 transfer 方法的。\n\n此方法支持多线程执行，外围调用此方法的时候，会保证第一个发起数据迁移的线程，nextTab 参数为 null，之后再调用此方法的时候，nextTab 不会为 null。\n\n阅读源码之前，先要理解并发操作的机制。原数组长度为 n，所以我们有 n 个迁移任务，让每个线程每次负责一个小任务是最简单的，每做完一个任务再检测是否有其他没做完的任务，帮助迁移就可以了，而 Doug Lea 使用了一个 stride，简单理解就是**步长**，每个线程每次负责迁移其中的一部分，如每次迁移 16 个小任务。所以，我们就需要一个全局的调度者来安排哪个线程执行哪几个任务，这个就是属性 transferIndex 的作用。\n\n第一个发起数据迁移的线程会将 transferIndex 指向原数组最后的位置，然后**从后往前**的 stride 个任务属于第一个线程，然后将 transferIndex 指向新的位置，再往前的 stride 个任务属于第二个线程，依此类推。当然，这里说的第二个线程不是真的一定指代了第二个线程，也可以是同一个线程，这个读者应该能理解吧。其实就是将一个大的迁移任务分为了一个个任务包。\n\n```\nprivate final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {\n    int n = tab.length, stride;\n\n    // stride 在单核下直接等于 n，多核模式下为 (n>>>3)/NCPU，最小值是 16\n    // stride 可以理解为”步长“，有 n 个位置是需要进行迁移的，\n    //   将这 n 个任务分为多个任务包，每个任务包有 stride 个任务\n    if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)\n        stride = MIN_TRANSFER_STRIDE; // subdivide range\n\n    // 如果 nextTab 为 null，先进行一次初始化\n    //    前面我们说了，外围会保证第一个发起迁移的线程调用此方法时，参数 nextTab 为 null\n    //       之后参与迁移的线程调用此方法时，nextTab 不会为 null\n    if (nextTab == null) {\n        try {\n            // 容量翻倍\n            Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];\n            nextTab = nt;\n        } catch (Throwable ex) {      // try to cope with OOME\n            sizeCtl = Integer.MAX_VALUE;\n            return;\n        }\n        // nextTable 是 ConcurrentHashMap 中的属性\n        nextTable = nextTab;\n        // transferIndex 也是 ConcurrentHashMap 的属性，用于控制迁移的位置\n        transferIndex = n;\n    }\n\n    int nextn = nextTab.length;\n\n    // ForwardingNode 翻译过来就是正在被迁移的 Node\n    // 这个构造方法会生成一个Node，key、value 和 next 都为 null，关键是 hash 为 MOVED\n    // 后面我们会看到，原数组中位置 i 处的节点完成迁移工作后，\n    //    就会将位置 i 处设置为这个 ForwardingNode，用来告诉其他线程该位置已经处理过了\n    //    所以它其实相当于是一个标志。\n    ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);\n\n    // advance 指的是做完了一个位置的迁移工作，可以准备做下一个位置的了\n    boolean advance = true;\n    boolean finishing = false; // to ensure sweep before committing nextTab\n\n    /*\n     * 下面这个 for 循环，最难理解的在前面，而要看懂它们，应该先看懂后面的，然后再倒回来看\n     * \n     */\n\n    // i 是位置索引，bound 是边界，注意是从后往前\n    for (int i = 0, bound = 0;;) {\n        Node<K,V> f; int fh;\n\n        // 下面这个 while 真的是不好理解\n        // advance 为 true 表示可以进行下一个位置的迁移了\n        //   简单理解结局：i 指向了 transferIndex，bound 指向了 transferIndex-stride\n        while (advance) {\n            int nextIndex, nextBound;\n            if (--i >= bound || finishing)\n                advance = false;\n\n            // 将 transferIndex 值赋给 nextIndex\n            // 这里 transferIndex 一旦小于等于 0，说明原数组的所有位置都有相应的线程去处理了\n            else if ((nextIndex = transferIndex) <= 0) {\n                i = -1;\n                advance = false;\n            }\n            else if (U.compareAndSwapInt\n                     (this, TRANSFERINDEX, nextIndex,\n                      nextBound = (nextIndex > stride ?\n                                   nextIndex - stride : 0))) {\n                // 看括号中的代码，nextBound 是这次迁移任务的边界，注意，是从后往前\n                bound = nextBound;\n                i = nextIndex - 1;\n                advance = false;\n            }\n        }\n        if (i < 0 || i >= n || i + n >= nextn) {\n            int sc;\n            if (finishing) {\n                // 所有的迁移操作已经完成\n                nextTable = null;\n                // 将新的 nextTab 赋值给 table 属性，完成迁移\n                table = nextTab;\n                // 重新计算 sizeCtl：n 是原数组长度，所以 sizeCtl 得出的值将是新数组长度的 0.75 倍\n                sizeCtl = (n << 1) - (n >>> 1);\n                return;\n            }\n\n            // 之前我们说过，sizeCtl 在迁移前会设置为 (rs << RESIZE_STAMP_SHIFT) + 2\n            // 然后，每有一个线程参与迁移就会将 sizeCtl 加 1，\n            // 这里使用 CAS 操作对 sizeCtl 进行减 1，代表做完了属于自己的任务\n            if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {\n                // 任务结束，方法退出\n                if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)\n                    return;\n\n                // 到这里，说明 (sc - 2) == resizeStamp(n) << RESIZE_STAMP_SHIFT，\n                // 也就是说，所有的迁移任务都做完了，也就会进入到上面的 if(finishing){} 分支了\n                finishing = advance = true;\n                i = n; // recheck before commit\n            }\n        }\n        // 如果位置 i 处是空的，没有任何节点，那么放入刚刚初始化的 ForwardingNode ”空节点“\n        else if ((f = tabAt(tab, i)) == null)\n            advance = casTabAt(tab, i, null, fwd);\n        // 该位置处是一个 ForwardingNode，代表该位置已经迁移过了\n        else if ((fh = f.hash) == MOVED)\n            advance = true; // already processed\n        else {\n            // 对数组该位置处的结点加锁，开始处理数组该位置处的迁移工作\n            synchronized (f) {\n                if (tabAt(tab, i) == f) {\n                    Node<K,V> ln, hn;\n                    // 头结点的 hash 大于 0，说明是链表的 Node 节点\n                    if (fh >= 0) {\n                        // 下面这一块和 Java7 中的 ConcurrentHashMap 迁移是差不多的，\n                        // 需要将链表一分为二，\n                        //   找到原链表中的 lastRun，然后 lastRun 及其之后的节点是一起进行迁移的\n                        //   lastRun 之前的节点需要进行克隆，然后分到两个链表中\n                        int runBit = fh & n;\n                        Node<K,V> lastRun = f;\n                        for (Node<K,V> p = f.next; p != null; p = p.next) {\n                            int b = p.hash & n;\n                            if (b != runBit) {\n                                runBit = b;\n                                lastRun = p;\n                            }\n                        }\n                        if (runBit == 0) {\n                            ln = lastRun;\n                            hn = null;\n                        }\n                        else {\n                            hn = lastRun;\n                            ln = null;\n                        }\n                        for (Node<K,V> p = f; p != lastRun; p = p.next) {\n                            int ph = p.hash; K pk = p.key; V pv = p.val;\n                            if ((ph & n) == 0)\n                                ln = new Node<K,V>(ph, pk, pv, ln);\n                            else\n                                hn = new Node<K,V>(ph, pk, pv, hn);\n                        }\n                        // 其中的一个链表放在新数组的位置 i\n                        setTabAt(nextTab, i, ln);\n                        // 另一个链表放在新数组的位置 i+n\n                        setTabAt(nextTab, i + n, hn);\n                        // 将原数组该位置处设置为 fwd，代表该位置已经处理完毕，\n                        //    其他线程一旦看到该位置的 hash 值为 MOVED，就不会进行迁移了\n                        setTabAt(tab, i, fwd);\n                        // advance 设置为 true，代表该位置已经迁移完毕\n                        advance = true;\n                    }\n                    else if (f instanceof TreeBin) {\n                        // 红黑树的迁移\n                        TreeBin<K,V> t = (TreeBin<K,V>)f;\n                        TreeNode<K,V> lo = null, loTail = null;\n                        TreeNode<K,V> hi = null, hiTail = null;\n                        int lc = 0, hc = 0;\n                        for (Node<K,V> e = t.first; e != null; e = e.next) {\n                            int h = e.hash;\n                            TreeNode<K,V> p = new TreeNode<K,V>\n                                (h, e.key, e.val, null, null);\n                            if ((h & n) == 0) {\n                                if ((p.prev = loTail) == null)\n                                    lo = p;\n                                else\n                                    loTail.next = p;\n                                loTail = p;\n                                ++lc;\n                            }\n                            else {\n                                if ((p.prev = hiTail) == null)\n                                    hi = p;\n                                else\n                                    hiTail.next = p;\n                                hiTail = p;\n                                ++hc;\n                            }\n                        }\n                        // 如果一分为二后，节点数少于 8，那么将红黑树转换回链表\n                        ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :\n                            (hc != 0) ? new TreeBin<K,V>(lo) : t;\n                        hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :\n                            (lc != 0) ? new TreeBin<K,V>(hi) : t;\n\n                        // 将 ln 放置在新数组的位置 i\n                        setTabAt(nextTab, i, ln);\n                        // 将 hn 放置在新数组的位置 i+n\n                        setTabAt(nextTab, i + n, hn);\n                        // 将原数组该位置处设置为 fwd，代表该位置已经处理完毕，\n                        //    其他线程一旦看到该位置的 hash 值为 MOVED，就不会进行迁移了\n                        setTabAt(tab, i, fwd);\n                        // advance 设置为 true，代表该位置已经迁移完毕\n                        advance = true;\n                    }\n                }\n            }\n        }\n    }\n}\n```\n\n说到底，transfer 这个方法并没有实现所有的迁移任务，每次调用这个方法只实现了 transferIndex 往前 stride 个位置的迁移工作，其他的需要由外围来控制。\n\n这个时候，再回去仔细看 tryPresize 方法可能就会更加清晰一些了。\n\n### get 过程分析\n\nget 方法从来都是最简单的，这里也不例外：\n\n1.  计算 hash 值\n2.  根据 hash 值找到数组对应位置: (n - 1) & h\n3.  根据该位置处结点性质进行相应查找\n    *   如果该位置为 null，那么直接返回 null 就可以了\n    *   如果该位置处的节点刚好就是我们需要的，返回该节点的值即可\n    *   如果该位置节点的 hash 值小于 0，说明正在扩容，或者是红黑树，后面我们再介绍 find 方法\n    *   如果以上 3 条都不满足，那就是链表，进行遍历比对即可\n\n```\npublic V get(Object key) {\n    Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;\n    int h = spread(key.hashCode());\n    if ((tab = table) != null && (n = tab.length) > 0 &&\n        (e = tabAt(tab, (n - 1) & h)) != null) {\n        // 判断头结点是否就是我们需要的节点\n        if ((eh = e.hash) == h) {\n            if ((ek = e.key) == key || (ek != null && key.equals(ek)))\n                return e.val;\n        }\n        // 如果头结点的 hash 小于 0，说明 正在扩容，或者该位置是红黑树\n        else if (eh < 0)\n            // 参考 ForwardingNode.find(int h, Object k) 和 TreeBin.find(int h, Object k)\n            return (p = e.find(h, key)) != null ? p.val : null;\n\n        // 遍历链表\n        while ((e = e.next) != null) {\n            if (e.hash == h &&\n                ((ek = e.key) == key || (ek != null && key.equals(ek))))\n                return e.val;\n        }\n    }\n    return null;\n}\n```\n\n简单说一句，此方法的大部分内容都很简单，只有正好碰到扩容的情况，ForwardingNode.find(int h, Object k) 稍微复杂一些，不过在了解了数据迁移的过程后，这个也就不难了，所以限于篇幅这里也不展开说了。\n\n## 总结\n\n其实也不是很难嘛，虽然没有像之前的 AQS 和线程池一样一行一行源码进行分析，但还是把所有初学者可能会糊涂的地方都进行了深入的介绍，只要是稍微有点基础的读者，应该是很容易就能看懂 HashMap 和 ConcurrentHashMap 源码了。\n\n看源码不算是目的吧，深入地了解 Doug Lea 的设计思路，我觉得还挺有趣的，大师就是大师，代码写得真的是好啊。\n\n我发现很多人都以为我写博客主要是源码分析，说真的，我对于源码分析没有那么大热情，主要都是为了用源码说事罢了，可能之后的文章还是会有比较多的源码分析成分。\n\n不要脸地自以为本文的质量还是挺高的，信息量比较大，如果你觉得有写得不好的地方，或者说看完本文你还是没看懂它们，那么请提出来~~~~~\n\n（全文完）\n"
  },
  {
    "path": "docs/Java/concurrency/Java并发指南：Java中的锁Lock和synchronized.md",
    "content": "# 目录\n  * [Java中的锁机制及Lock类](#java中的锁机制及lock类)\n    * [锁的释放-获取建立的happens before 关系](#锁的释放-获取建立的happens-before-关系)\n    * [锁释放和获取的内存语义](#锁释放和获取的内存语义)\n    * [锁内存语义的实现](#锁内存语义的实现)\n  * [concurrent包的实现](#concurrent包的实现)\n  * [synchronized实现原理](#synchronized实现原理)\n    * [****1、实现原理****](#1、实现原理)\n    * [**2、Java对象头**](#2、java对象头)\n    * [**3、Monitor**](#3、monitor)\n    * [**4、锁优化**](#4、锁优化)\n    * [**5、自旋锁**](#5、自旋锁)\n    * [**6、适应自旋锁**](#6、适应自旋锁)\n    * [**7、锁消除**](#7、锁消除)\n    * [**8、锁粗化**](#8、锁粗化)\n    * [**9、轻量级锁**](#9、轻量级锁)\n    * [**10、偏向锁**](#10、偏向锁)\n  * [**11、重量级锁**](#11、重量级锁)\n  * [参考资料](#参考资料)\n\n**本文转载自并发编程网，侵删**\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章同步发于我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《Java并发指南》其中一篇，本文大部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何全面深入地学习Java并发技术，从Java多线程基础，再到并发编程的基础知识，从Java并发包的入门和实战，再到JUC的源码剖析，一步步地学习Java并发编程，并上手进行实战，以便让你更完整地了解整个Java并发编程知识体系，形成自己的知识框架。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供一些对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n<!--more -->\n\n## Java中的锁机制及Lock类\n\n### 锁的释放-获取建立的happens before 关系\n\n锁是java并发编程中最重要的同步机制。锁除了让临界区互斥执行外，还可以让释放锁的线程向获取同一个锁的线程发送消息。\n\n下面是锁释放-获取的示例代码：\n````\n    class MonitorExample {\n        int a = 0;\n     \n        public synchronized void writer() {  //1\n            a++;                             //2\n        }                                    //3\n     \n        public synchronized void reader() {  //4\n            int i = a;                       //5\n            ……\n        }                                    //6\n    }\n````\n根据程序次序规则，1 happens before 2, 2 happens before 3; 4 happens before 5, 5 happens before 6。假设线程A执行writer()方法，随后线程B执行reader()方法。根据happens before规则，这个过程包含的happens before 关系可以分为两类：\n\n1.  根据监视器锁规则，3 happens before 4。\n2.  根据happens before 的传递性，2 happens before 5。\n\n上述happens before 关系的图形化表现形式如下：\n\n![](http://blog.itpub.net/ueditor/php/upload/image/20190811/1565506398748401.png)\n\n在上图中，每一个箭头链接的两个节点，代表了一个happens before 关系。黑色箭头表示程序顺序规则；橙色箭头表示监视器锁规则；蓝色箭头表示组合这些规则后提供的happens before保证。\n\n上图表示在线程A释放了锁之后，随后线程B获取同一个锁。在上图中，2 happens before 5。因此，线程A在释放锁之前所有可见的共享变量，在线程B获取同一个锁之后，将立刻变得对B线程可见。\n\n### 锁释放和获取的内存语义\n\n当线程释放锁时，JMM会把该线程对应的本地内存中的共享变量刷新到主内存中。以上面的MonitorExample程序为例，A线程释放锁后，共享数据的状态示意图如下：\n\n![](http://blog.itpub.net/ueditor/php/upload/image/20190811/1565506402638665.png)\n\n当线程获取锁时，JMM会把该线程对应的本地内存置为无效。从而使得被监视器保护的临界区代码必须要从主内存中去读取共享变量。下面是锁获取的状态示意图：\n\n![](http://blog.itpub.net/ueditor/php/upload/image/20190811/1565506405253375.png)\n\n对比锁释放-获取的内存语义与volatile写-读的内存语义，可以看出：锁释放与volatile写有相同的内存语义；锁获取与volatile读有相同的内存语义。\n\n下面对锁释放和锁获取的内存语义做个总结：\n\n*   线程A释放一个锁，实质上是线程A向接下来将要获取这个锁的某个线程发出了（线程A对共享变量所做修改的）消息。\n*   线程B获取一个锁，实质上是线程B接收了之前某个线程发出的（在释放这个锁之前对共享变量所做修改的）消息。\n*   线程A释放锁，随后线程B获取这个锁，这个过程实质上是线程A通过主内存向线程B发送消息。\n\n### 锁内存语义的实现\n\n本文将借助ReentrantLock的源代码，来分析锁内存语义的具体实现机制。\n\n请看下面的示例代码：\n````\n    class ReentrantLockExample {\n    int a = 0;\n    ReentrantLock lock = new ReentrantLock();\n     \n    public void writer() {\n        lock.lock();         //获取锁\n        try {\n            a++;\n        } finally {\n            lock.unlock();  //释放锁\n        }\n    }\n     \n    public void reader () {\n        lock.lock();        //获取锁\n        try {\n            int i = a;\n            ……\n        } finally {\n            lock.unlock();  //释放锁\n        }\n    }\n    }\n````    \n在ReentrantLock中，调用lock()方法获取锁；调用unlock()方法释放锁。\n\nReentrantLock的实现依赖于java同步器框架AbstractQueuedSynchronizer（本文简称之为AQS）。AQS使用一个整型的volatile变量（命名为state）来维护同步状态，马上我们会看到，这个volatile变量是ReentrantLock内存语义实现的关键。 下面是ReentrantLock的类图（仅画出与本文相关的部分）：\n\n![](http://blog.itpub.net/ueditor/php/upload/image/20190811/1565506414141304.png)\n\nReentrantLock分为公平锁和非公平锁，我们首先分析公平锁。\n\n使用公平锁时，加锁方法lock()的方法调用轨迹如下：\n\n1.  ReentrantLock : lock()\n2.  FairSync : lock()\n3.  AbstractQueuedSynchronizer : acquire(int arg)\n4.  ReentrantLock : tryAcquire(int acquires)\n\n在第4步真正开始加锁，下面是该方法的源代码：\n\n````\nprotected final boolean tryAcquire(int acquires) {\n    final Thread current = Thread.currentThread();\n    int c = getState();   //获取锁的开始，首先读volatile变量state\n    if (c == 0) {\n        if (isFirst(current) &&\n            compareAndSetState(0, acquires)) {\n            setExclusiveOwnerThread(current);\n            return true;\n        }\n    }\n    else if (current == getExclusiveOwnerThread()) {\n        int nextc = c + acquires;\n        if (nextc < 0)  \n            throw new Error(\"Maximum lock count exceeded\");\n        setState(nextc);\n        return true;\n    }\n    return false;\n}\n````\n\n从上面源代码中我们可以看出，加锁方法首先读volatile变量state。\n\n在使用公平锁时，解锁方法unlock()的方法调用轨迹如下：\n\n1.  ReentrantLock : unlock()\n2.  AbstractQueuedSynchronizer : release(int arg)\n3.  Sync : tryRelease(int releases)\n\n在第3步真正开始释放锁，下面是该方法的源代码：\n\n````\nprotected final boolean tryRelease(int releases) {\n    int c = getState() - releases;\n    if (Thread.currentThread() != getExclusiveOwnerThread())\n        throw new IllegalMonitorStateException();\n    boolean free = false;\n    if (c == 0) {\n        free = true;\n        setExclusiveOwnerThread(null);\n    }\n    setState(c);           //释放锁的最后，写volatile变量state\n    return free;\n}\n````\n\n从上面的源代码我们可以看出，在释放锁的最后写volatile变量state。\n\n公平锁在释放锁的最后写volatile变量state；在获取锁时首先读这个volatile变量。根据volatile的happens-before规则，释放锁的线程在写volatile变量之前可见的共享变量，在获取锁的线程读取同一个volatile变量后将立即变的对获取锁的线程可见。\n\n现在我们分析非公平锁的内存语义的实现。\n\n非公平锁的释放和公平锁完全一样，所以这里仅仅分析非公平锁的获取。\n\n使用公平锁时，加锁方法lock()的方法调用轨迹如下：\n\n1.  ReentrantLock : lock()\n2.  NonfairSync : lock()\n3.  AbstractQueuedSynchronizer : compareAndSetState(int expect, int update)\n\n在第3步真正开始加锁，下面是该方法的源代码：\n````\nprotected final boolean compareAndSetState(int expect, int update){\n    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);\n}\n````\n该方法以原子操作的方式更新state变量，本文把java的compareAndSet()方法调用简称为CAS。JDK文档对该方法的说明如下：如果当前状态值等于预期值，则以原子方式将同步状态设置为给定的更新值。此操作具有 volatile 读和写的内存语义。\n\n这里我们分别从编译器和处理器的角度来分析,CAS如何同时具有volatile读和volatile写的内存语义。\n\n前文我们提到过，编译器不会对volatile读与volatile读后面的任意内存操作重排序；编译器不会对volatile写与volatile写前面的任意内存操作重排序。组合这两个条件，意味着为了同时实现volatile读和volatile写的内存语义，编译器不能对CAS与CAS前面和后面的任意内存操作重排序。\n\n下面我们来分析在常见的intel x86处理器中，CAS是如何同时具有volatile读和volatile写的内存语义的。\n\n下面是sun.misc.Unsafe类的compareAndSwapInt()方法的源代码：\n````\n    protected final boolean compareAndSetState(int expect, int update) {\n        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);\n    }\n````\n可以看到这是个本地方法调用。这个本地方法在openjdk中依次调用的c++代码为：unsafe.cpp，atomic.cpp和atomicwindowsx86.inline.hpp。这个本地方法的最终实现在openjdk的如下位置：openjdk-7-fcs-src-b147-27jun2011\\openjdk\\hotspot\\src\\oscpu\\windowsx86\\vm\\ atomicwindowsx86.inline.hpp（对应于windows操作系统，X86处理器）。下面是对应于intel x86处理器的源代码的片段：\n````\n    // Adding a lock prefix to an instruction on MP machine\n    // VC++ doesn't like the lock prefix to be on a single line\n    // so we can't insert a label after the lock prefix.\n    // By emitting a lock prefix, we can define a label after it.\n    #define LOCK_IF_MP(mp) __asm cmp mp, 0  \\\n                           __asm je L0      \\\n                           __asm _emit 0xF0 \\\n                           __asm L0:\n     \n    inline jint     Atomic::cmpxchg    (jint     exchange_value, volatile jint*     dest, jint     compare_value) {\n      // alternative for InterlockedCompareExchange\n      int mp = os::is_MP();\n      __asm {\n        mov edx, dest\n        mov ecx, exchange_value\n        mov eax, compare_value\n        LOCK_IF_MP(mp)\n        cmpxchg dword ptr [edx], ecx\n      }\n    }\n````\n\n如上面源代码所示，程序会根据当前处理器的类型来决定是否为cmpxchg指令添加lock前缀。如果程序是在多处理器上运行，就为cmpxchg指令加上lock前缀（lock cmpxchg）。反之，如果程序是在单处理器上运行，就省略lock前缀（单处理器自身会维护单处理器内的顺序一致性，不需要lock前缀提供的内存屏障效果）。\n\nintel的手册对lock前缀的说明如下：\n\n1.  确保对内存的读-改-写操作原子执行。在Pentium及Pentium之前的处理器中，带有lock前缀的指令在执行期间会锁住总线，使得其他处理器暂时无法通过总线访问内存。很显然，这会带来昂贵的开销。\n \n2.  从Pentium 4，Intel Xeon及P6处理器开始，intel在原有总线锁的基础上做了一个很有意义的优化：如果要访问的内存区域（area of memory）在lock前缀指令执行期间已经在处理器内部的缓存中被锁定（即包含该内存区域的缓存行当前处于独占或以修改状态），并且该内存区域被完全包含在单个缓存行（cache line）中，那么处理器将直接执行该指令。\n\n3.  由于在指令执行期间该缓存行会一直被锁定，其它处理器无法读/写该指令要访问的内存区域，因此能保证指令执行的原子性。这个操作过程叫做缓存锁定（cache locking），缓存锁定将大大降低lock前缀指令的执行开销，但是当多处理器之间的竞争程度很高或者指令访问的内存地址未对齐时，仍然会锁住总线。\n\n4.  禁止该指令与之前和之后的读和写指令重排序。\n  \n5.  把写缓冲区中的所有数据刷新到内存中。\n\n上面的第2点和第3点所具有的内存屏障效果，足以同时实现volatile读和volatile写的内存语义。\n\n经过上面的这些分析，现在我们终于能明白为什么JDK文档说CAS同时具有volatile读和volatile写的内存语义了。\n\n现在对公平锁和非公平锁的内存语义做个总结：\n\n*   公平锁和非公平锁释放时，最后都要写一个volatile变量state。\n*   公平锁获取时，首先会去读这个volatile变量。\n*   非公平锁获取时，首先会用CAS更新这个volatile变量,这个操作同时具有volatile读和volatile写的内存语义。\n\n从本文对ReentrantLock的分析可以看出，锁释放-获取的内存语义的实现至少有下面两种方式：\n\n1.  利用volatile变量的写-读所具有的内存语义。\n2.  利用CAS所附带的volatile读和volatile写的内存语义。\n\n## concurrent包的实现\n\n由于java的CAS同时具有 volatile 读和volatile写的内存语义，因此Java线程之间的通信现在有了下面四种方式：\n\n1.  A线程写volatile变量，随后B线程读这个volatile变量。\n2.  A线程写volatile变量，随后B线程用CAS更新这个volatile变量。\n3.  A线程用CAS更新一个volatile变量，随后B线程用CAS更新这个volatile变量。\n4.  A线程用CAS更新一个volatile变量，随后B线程读这个volatile变量。\n\nJava的CAS会使用现代处理器上提供的高效机器级别原子指令，这些原子指令以原子方式对内存执行读-改-写操作，这是在多处理器中实现同步的关键（从本质上来说，能够支持原子性读-改-写指令的计算机器，是顺序计算图灵机的异步等价机器，因此任何现代的多处理器都会去支持某种能对内存执行原子性读-改-写操作的原子指令）。同时，volatile变量的读/写和CAS可以实现线程之间的通信。把这些特性整合在一起，就形成了整个concurrent包得以实现的基石。如果我们仔细分析concurrent包的源代码实现，会发现一个通用化的实现模式：\n\n1.  首先，声明共享变量为volatile；\n2.  然后，使用CAS的原子条件更新来实现线程之间的同步；\n3.  同时，配合以volatile的读/写和CAS所具有的volatile读和写的内存语义来实现线程之间的通信。\n\nAQS，非阻塞数据结构和原子变量类（java.util.concurrent.atomic包中的类），这些concurrent包中的基础类都是使用这种模式来实现的，而concurrent包中的高层类又是依赖于这些基础类来实现的。从整体来看，concurrent包的实现示意图如下：\n\n![](http://blog.itpub.net/ueditor/php/upload/image/20190811/1565506416227137.png)\n\n## synchronized实现原理\n\n转自：https://blog.csdn.net/chenssy/article/details/54883355\n\n\n记得刚刚开始学习Java的时候，一遇到多线程情况就是synchronized。对于当时的我们来说，synchronized是如此的神奇且强大。我们赋予它一个名字“同步”，也成为我们解决多线程情况的良药，百试不爽。但是，随着学习的深入，我们知道synchronized是一个重量级锁，相对于Lock，它会显得那么笨重，以至于我们认为它不是那么的高效，并慢慢抛弃它。\n\n诚然，随着Javs SE 1.6对synchronized进行各种优化后，synchronized不会显得那么重。\n\n下面跟随LZ一起来探索**synchronized的实现机制、Java是如何对它进行了优化、锁优化机制、锁的存储结构和升级过程。**\n\n### ****1、实现原理****\n\nsynchronized可以保证方法或者代码块在运行时，同一时刻只有一个方法可以进入到临界区，同时它还可以保证共享变量的内存可见性。\n\nJava中每一个对象都可以作为锁，这是synchronized实现同步的基础：\n\n1.  **普通同步方法，锁是当前实例对象；**\n\n2.  **静态同步方法，锁是当前类的class对象；**\n\n3.  **同步方法块，锁是括号里面的对象。**\n\n当一个线程访问同步代码块时，它首先是需要得到锁才能执行同步代码，**当退出或者抛出异常时必须要释放锁，那么它是如何来实现这个机制的呢？**\n\n我们先看一段简单的代码：\n````\npublic class SynchronizedTest{ public synchronized void test1(){\n\n　　} public void test2(){\n　　　　synchronized(this){\n\n       }\n    }\n}\n\n````\n\n**利用Javap工具查看生成的class文件信息来分析Synchronize的实现：**\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404192352.png)\n\n　　从上面可以看出，同步代码块是使用monitorenter和monitorexit指令实现的，同步方法（在这看不出来需要看JVM底层实现）依靠的是方法修饰符上的ACCSYNCHRONIZED实现。\n\n**同步代码块：**\n\n　　monitorenter指令插入到同步代码块的开始位置，monitorexit指令插入到同步代码块的结束位置，JVM需要保证每一个monitorenter都有一个monitorexit与之相对应。任何对象都有一个monitor与之相关联，当且一个monitor被持有之后，他将处于锁定状态。线程执行到monitorenter指令时，将会尝试获取对象所对应的monitor所有权，即尝试获取对象的锁；\n\n**同步方法**\n\n　　synchronized方法则会被翻译成普通的方法调用和返回指令如:invokevirtual、areturn指令，在VM字节码层面并没有任何特别的指令来实现被synchronized修饰的方法，而是在Class文件的方法表中将该方法的**accessflags字段中的synchronized标志位置1**，表示该方法是同步方法并使用调用该方法的对象或该方法所属的Class在JVM的内部对象表示Klass做为锁对象。\n\n(摘自：http://www.cnblogs.com/javaminer/p/3889023.html)\n\n下面我们来继续分析，但是在深入之前我们需要了解两个重要的概念：**Java对象头、Monitor。**\n\n**Java对象头、monitor：Java对象头和monitor是实现synchronized的基础！下面就这两个概念来做详细介绍。**\n\n### **2、Java对象头**\n\nsynchronized用的锁是存在Java对象头里的，那么什么是Java对象头呢？\n\nHotspot虚拟机的对象头主要包括两部分数据：**Mark Word（标记字段）、Klass Pointer（类型指针）**。其中**Klass Point是是对象指向它的类元数据的指针**，虚拟机通过这个指针来确定这个对象是哪个类的实例，**Mark Word用于存储对象自身的运行时数据，它是实现轻量级锁和偏向锁的关键。**\n\n所以下面将重点阐述。\n\n**Mark Word**\n\n    Mark Word用于存储对象自身的运行时数据，如哈希码（HashCode）、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等等。Java对象头一般占有两个机器码（在32位虚拟机中，1个机器码等于4字节，也就是32bit），但是如果对象是数组类型，则需要三个机器码，因为JVM虚拟机可以通过Java对象的元数据信息确定Java对象的大小，但是无法从数组的元数据来确认数组的大小，所以用一块来记录数组长度。\n\n下图是Java对象头的存储结构（32位虚拟机）：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404192459.png)\n\n对象头信息是与对象自身定义的数据无关的额外存储成本，但是考虑到虚拟机的空间效率，Mark Word被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据，它会根据对象的状态复用自己的存储空间，也就是说，Mark Word会随着程序的运行发生变化，变化状态如下（32位虚拟机）：\n\n![](https://images2018.cnblogs.com/blog/1169376/201807/1169376-20180726111757632-1279497345.png)\n\n简单介绍了Java对象头，我们下面再看Monitor。\n\n### **3、Monitor**\n\n什么是Monitor？\n\n我们可以把它理解为一个同步工具，也可以描述为一种同步机制，它通常被描述为一个对象。\n\n与一切皆对象一样，所有的Java对象是天生的Monitor，**每一个Java对象都有成为Monitor的潜质**，因为在Java的设计中 ，每一个Java对象自打娘胎里出来**就带了一把看不见的锁，它叫做内部锁或者Monitor锁**。\n\nMonitor 是线程私有的数据结构，每一个线程都有一个可用monitor record列表，同时还有一个全局的可用列表。每一个被锁住的对象都会和一个monitor关联（对象头的MarkWord中的LockWord指向monitor的起始地址），**同时monitor中有一个Owner字段存放拥有该锁的线程的唯一标识，表示该锁被这个线程占用**。　　\n\n其结构如下：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404192639.png)\n\n*   Owner：初始时为NULL表示当前没有任何线程拥有该monitor record，当线程成功拥有该锁后保存线程唯一标识，当锁被释放时又设置为NULL。\n\n*   EntryQ：关联一个系统互斥锁（semaphore），阻塞所有试图锁住monitor record失败的线程。\n\n*   RcThis：表示blocked或waiting在该monitor record上的所有线程的个数。\n\n*   Nest：用来实现重入锁的计数。HashCode:保存从对象头拷贝过来的HashCode值（可能还包含GC age）。\n\n*   Candidate：用来避免不必要的阻塞或等待线程唤醒**，因为每一次只有一个线程能够成功拥有锁，如果每次前一个释放锁的线程唤醒所有正在阻塞或等待的线程，会引起不必要的上下文切换（从阻塞到就绪然后因为竞争锁失败又被阻塞）从而导致性能严重下降。\n\n    Candidate只有两种可能的值0表示没有需要唤醒的线程1表示要唤醒一个继任线程来竞争锁。\n\n我们知道synchronized是重量级锁，效率不怎么滴，同时这个观念也一直存在我们脑海里，不过在**JDK 1.6中对synchronize的实现进行了各种优化，使得它显得不是那么重了，那么JVM采用了那些优化手段呢？**\n\n### **4、锁优化**\n\n　　JDK1.6对锁的实现引入了大量的优化，如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。\n\n**　　锁主要存在四中状态，依次是：无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态。**他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级，这种策略是为了提高获得锁和释放锁的效率。\n\n### **5、自旋锁**\n\n　　线程的阻塞和唤醒需要CPU从用户态转为核心态，频繁的阻塞和唤醒对CPU来说是一件负担很重的工作，势必会给系统的并发性能带来很大的压力。同时我们发现在许多应用上面，**对象锁的锁状态只会持续很短一段时间**，**为了这一段很短的时间频繁地阻塞和唤醒线程**是非常不值得的。\n\n所以引入自旋锁。\n\n何谓自旋锁？\n\n**　　所谓自旋锁，就是让该线程等待一段时间，不会被立即挂起（就是不让前来获取该锁（已被占用）的线程立即阻塞），看持有锁的线程是否会很快释放锁。**\n\n**怎么等待呢？**\n\n执行一段无意义的循环即可（自旋）。\n\n　　自旋等待不能替代阻塞，先不说对处理器数量的要求（多核，貌似现在没有单核的处理器了），虽然它可以避免线程切换带来的开销，但是它占用了处理器的时间。如果持有锁的线程很快就释放了锁，那么自旋的效率就非常好；反之，自旋的线程就会白白消耗掉处理的资源，它不会做任何有意义的工作，典型的占着茅坑不拉屎，这样反而会带来性能上的浪费。\n\n　　所以说，自旋等待的时间（自旋的次数）必须要有一个限度，如果自旋超过了定义的时间仍然没有获取到锁，则应该被挂起。自旋锁在JDK 1.4.2中引入，默认关闭，但是可以使用-XX:+UseSpinning开开启，在JDK1.6中默认开启。同时自旋的默认次数为10次，可以通过参数-XX:PreBlockSpin来调整。\n\n　　如果通过参数-XX:preBlockSpin来调整自旋锁的自旋次数，会带来诸多不便。假如我将参数调整为10，但是系统很多线程都是等你刚刚退出的时候就释放了锁（假如你多自旋一两次就可以获取锁），你是不是很尴尬？于是JDK1.6引入自适应的自旋锁，让虚拟机会变得越来越聪明。\n\n### **6、适应自旋锁**\n\n　　JDK 1.6引入了更加聪明的自旋锁，即自适应自旋锁。所谓自适应就意味着自旋的次数不再是固定的，它是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。\n\n　　它怎么做呢？\n\n　　线程如果自旋成功了，那么下次自旋的次数会更加多，因为虚拟机认为既然上次成功了，那么此次自旋也很有可能会再次成功，那么它就会允许自旋等待持续的次数更多。反之，如果对于某个锁，很少有自旋能够成功的，那么在以后要或者这个锁的时候自旋的次数会减少甚至省略掉自旋过程，以免浪费处理器资源。**有了自适应自旋锁，随着程序运行和性能监控信息的不断完善，虚拟机对程序锁的状况预测会越来越准确，虚拟机会变得越来越聪明。**\n\n### **7、锁消除**\n\n　　为了保证数据的完整性，我们在进行操作时需要对这部分操作进行同步控制，但是在有些情况下，JVM检测到不可能存在共享数据竞争，这是JVM会对这些同步锁进行锁消除。锁消除的依据是逃逸分析的数据支持。\n\n**　　如果不存在竞争，为什么还需要加锁呢？**\n\n　　所以锁消除可以节省毫无意义的请求锁的时间。变量是否逃逸，对于虚拟机来说需要使用数据流分析来确定，但是对于我们程序员来说这还不清楚么？我们会在明明知道不存在数据竞争的代码块前加上同步吗？但是有时候程序并不是我们所想的那样？\n\n　　我们虽然没有显示使用锁，但是我们在使用一些JDK的内置API时，如StringBuffer、Vector、HashTable等，这个时候会存在隐形的加锁操作。\n\n**　　比如StringBuffer的append()方法，Vector的add()方法：**\n\n````\npublic void vectorTest(){\n    Vector<String> vector = new Vector<String>(); for(int i = 0 ; i < 10 ; i++){\n        vector.add(i + \"\");\n     } \n\n    System.out.println(vector);\n}\n````\n\n在运行这段代码时，JVM可以明显检测到变量vector没有逃逸出方法vectorTest()之外，所以JVM可以大胆地将vector内部的加锁操作消除。\n\n### **8、锁粗化**\n\n　　我们知道在使用同步锁的时候，需要让同步块的作用范围尽可能小，仅在共享数据的实际作用域中才进行同步。这样做的目的是为了使需要同步的操作数量尽可能缩小，如果存在锁竞争，那么等待锁的线程也能尽快拿到锁。\n\n　　在大多数的情况下，上述观点是正确的，LZ也一直坚持着这个观点。但是如果一系列的连续加锁解锁操作，可能会导致不必要的性能损耗，所以引入锁粗化的概念。\n\n　　**那什么是锁粗化？**\n\n**就是将多个连续的加锁、解锁操作连接在一起，扩展成一个范围更大的锁。**\n\n　　**如上面实例：vector每次add的时候都需要加锁操作，JVM检测到对同一个对象（vector）连续加锁、解锁操作，会合并一个更大范围的加锁、解锁操作，即加锁解锁操作会移到for循环之外。**\n\n### **9、轻量级锁**\n\n　　引入轻量级锁的主要目的是在多没有多线程竞争的前提下，**减少传统的重量级锁使用操作系统互斥量产生的性能消耗**。\n\n**当关闭偏向锁功能或者多个线程竞争偏向锁导致偏向锁升级为轻量级锁，则会尝试获取轻量级锁，其步骤如下：****获取锁。**\n\n1.  判断当前对象是否处于无锁状态（hashcode、0、01），若是，则JVM首先将在当前线程的栈帧中建立一个名为锁记录（Lock Record）的空间，用于存储锁对象目前的Mark Word的拷贝（官方把这份拷贝加了一个Displaced前缀，即Displaced Mark Word）；否则执行步骤（3）；\n\n2.  JVM利用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指正，如果成功表示竞争到锁，则将锁标志位变成00（表示此对象处于轻量级锁状态），执行同步操作；如果失败则执行步骤（3）；\n\n3.  判断当前对象的Mark Word是否指向当前线程的栈帧，如果是则表示当前线程已经持有当前对象的锁，则直接执行同步代码块；否则只能说明该锁对象已经被其他线程抢占了，这时轻量级锁需要膨胀为重量级锁，锁标志位变成10，后面等待的线程将会进入阻塞状态；\n\n**释放锁轻量级锁的释放也是通过CAS操作来进行的，主要步骤如下：**\n\n1.  取出在获取轻量级锁保存在Displaced Mark Word中的数据；\n\n2.  用CAS操作将取出的数据替换当前对象的Mark Word中，如果成功，则说明释放锁成功，否则执行（3）；\n\n3.  如果CAS操作替换失败，说明有其他线程尝试获取该锁，则需要在释放锁的同时需要唤醒被挂起的线程。\n\n　　轻量级锁能提升程序同步性能的依据是“对于绝大部分的锁，在整个同步周期内都是不存在竞争的”，这是一个经验数据。轻量级锁在当前线程的栈帧中建立一个名为锁记录的空间，用于存储锁对象目前的指向和状态。如果没有竞争，轻量级锁使用CAS操作避免了使用互斥量的开销，但如果存在锁竞争，除了互斥量的开销外，还额外发生了CAS操作，因此**在有竞争的情况下，轻量级锁会比传统的重量级锁更慢。**\n\n**什么是CAS操作？**\n\ncompare and swap,CAS操作需要输入两个数值，一个旧值（期望操作前的值）和一个新值，在操作期间先比较旧值有没有发生变化，如果没有发生变化，才交换成新值，发生了变化则不交换。\n\nCAS详解：https://mp.weixin.qq.com/s__biz=MzIxMjE5MTE1Nw==&mid=2653192625&idx=1&sn=cbabbd806e4874e8793332724ca9d454&chksm=8c99f36bbbee7a7d169581dedbe09658d0b0edb62d2cbc9ba4c40f706cb678c7d8c768afb666&scene=21#wechat_redirect\n\nhttps://blog.csdn.net/qq_35357656/article/details/78657373\n\n下图是轻量级锁的获取和释放过程：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404192732.png)\n\n### **10、偏向锁**\n\n　　引入偏向锁主要目的是：为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径。上面提到了轻量级锁的加锁解锁操作是需要依赖多次CAS原子指令的。**那么偏向锁是如何来减少不必要的CAS操作呢**？我们可以查看Mark work的结构就明白了。\n\n**只需要检查是否为偏向锁、锁标识为以及ThreadID即可，处理流程如下：****获取锁。**\n\n1.  检测Mark Word是否为可偏向状态，即是否为偏向锁1，锁标识位为01；\n\n2.  若为可偏向状态，则测试线程ID是否为当前线程ID，如果是，则执行步骤（5），否则执行步骤（3）；\n\n3.  如果线程ID不为当前线程ID，则通过CAS操作竞争锁，竞争成功，则将Mark Word的线程ID替换为当前线程ID，否则执行线程（4）；\n\n4.  通过CAS竞争锁失败，证明当前存在多线程竞争情况，当到达全局安全点，获得偏向锁的线程被挂起，偏向锁升级为轻量级锁，然后被阻塞在安全点的线程继续往下执行同步代码块；\n\n5.  执行同步代码块。\n\n释放锁偏向锁的释放采用了一种只有竞争才会释放锁的机制，线程是不会主动去释放偏向锁，需要等待其他线程来竞争。偏向锁的撤销需要等待全局安全点（这个时间点是上没有正在执行的代码）。\n\n其步骤如下：\n\n1.  **暂停拥有偏向锁的线程，判断锁对象石是否还处于被锁定状态；**\n\n2.  **撤销偏向苏，恢复到无锁状态（01）或者轻量级锁的状态。**\n\n下图是偏向锁的获取和释放流程：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404192911.png)\n\n## **11、重量级锁**\n\n　　重量级锁通过对象内部的监视器（monitor）实现，其中monitor的本质是依赖于底层操作系统的Mutex Lock实现，操作系统实现线程之间的切换需要从用户态到内核态的切换，切换成本非常高。\n\n## 参考资料\n\n1.  周志明：《深入理解Java虚拟机》\n2.  方腾飞：《Java并发编程的艺术》\n3.  Java中synchronized的实现原理与应用\n"
  },
  {
    "path": "docs/Java/concurrency/Java并发指南：Java内存模型JMM总结.md",
    "content": "# 目录\n  * [简介](#简介)\n    * [一、Java内存区域（JVM内存区域）](#一、java内存区域（jvm内存区域）)\n    * [二、Java内存模型](#二、java内存模型)\n  * [三、as-if-serial语义、happens-before原则](#三、as-if-serial语义、happens-before原则)\n    * [3.1 as-if-serial语义](#31-as-if-serial语义)\n    * [3.2 happens-before原则](#32-happens-before原则)\n    * [3.3 happens-before定义](#33-happens-before定义)\n    * [3.3 happens-before对比as-if-serial](#33-happens-before对比as-if-serial)\n    * [3.4 happens-before具体规则](#34-happens-before具体规则)\n    * [3.5 happens-before与JMM的关系图](#35-happens-before与jmm的关系图)\n  * [四、volatile、锁的内存语义](#四、volatile、锁的内存语义)\n    * [4.1 volatile的内存语义](#41-volatile的内存语义)\n    * [4.2 volatile内存语义的实现](#42-volatile内存语义的实现)\n    * [4.3 锁的内存语义](#43-锁的内存语义)\n    * [4.4 final域的内存语义](#44-final域的内存语义)\n  * [五、JMM是如何处理并发过程中的三大特性](#五、jmm是如何处理并发过程中的三大特性)\n  * [参考链接：](#参考链接：)\n\n本文转自 https://www.cnblogs.com/kukri/p/9109639.html\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章同步发于我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《Java并发指南》其中一篇，本文大部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何全面深入地学习Java并发技术，从Java多线程基础，再到并发编程的基础知识，从Java并发包的入门和实战，再到JUC的源码剖析，一步步地学习Java并发编程，并上手进行实战，以便让你更完整地了解整个Java并发编程知识体系，形成自己的知识框架。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供一些对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n<!--more -->\n\n## 简介\n\n　　首先介绍两个名词：1）可见性:一个线程对共享变量值的修改,能够及时地被其他线程看到。2）共享变量:如果一个变量在多个线程的工作内存中都存在副本,那么这个变量就是这几个线程的共享变量　　\n\n　　Java线程之间的通信对程序员完全透明，在并发编程中，需要处理两个关键问题：线程之间如何通信及线程之间如何同步。\n\n　　通信：通信是指线程之间以何种机制来交换信息。在命令式编程中，线程之间的通信机制有两种：共享内存和消息传递。在共享内存的并发模型里，线程之间共享程序的公共状态，通过写-读内存中的公共状态来进行隐式通信。在消息传递的并发模型里，线程之间没有公共状态，线程之间必须通过发送消息来进行显示通信。\n\n　　同步：同步是指程序中用于控制不同线程间操作发生相对顺序的机制。在共享内存并发模型里，同步是显示进行的，程序员必须显示指定某个方法或某段代码需要在线程之间互斥执行。在消息传递的并发模型里，由于消息的发送必须在消息的接收之前，因此同步是隐式进行的。\n\n　　Java并发采用的是共享内存模型。\n\n### 一、Java内存区域（JVM内存区域）\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404195526.png)\n\nJava虚拟机在运行程序时会把其自动管理的内存划分为以上几个区域，每个区域都有的用途以及创建销毁的时机，其中蓝色部分代表的是所有线程共享的数据区域，而绿色部分代表的是每个线程的私有数据区域。\n\n*   方法区（Method Area）：\n\n    方法区属于线程共享的内存区域，又称Non-Heap（非堆），主要用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据，根据Java 虚拟机规范的规定，当方法区无法满足内存分配需求时，将抛出OutOfMemoryError 异常。值得注意的是在方法区中存在一个叫运行时常量池(Runtime Constant Pool）的区域，它主要用于存放编译器生成的各种字面量和符号引用，这些内容将在类加载后存放到运行时常量池中，以便后续使用。\n\n*   JVM堆（Java Heap）：\n\n    Java 堆也是属于线程共享的内存区域，它在虚拟机启动时创建，是Java 虚拟机所管理的内存中最大的一块，主要用于存放对象实例，几乎所有的对象实例都在这里分配内存，注意Java 堆是垃圾收集器管理的主要区域，因此很多时候也被称做GC 堆，如果在堆中没有内存完成实例分配，并且堆也无法再扩展时，将会抛出OutOfMemoryError 异常。\n\n*   程序计数器(Program Counter Register)：\n\n    属于线程私有的数据区域，是一小块内存空间，主要代表当前线程所执行的字节码行号指示器。字节码解释器工作时，通过改变这个计数器的值来选取下一条需要执行的字节码指令，分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。\n\n*   虚拟机栈(Java Virtual Machine Stacks)：\n\n    属于线程私有的数据区域，与线程同时创建，总数与线程关联，代表Java方法执行的内存模型。栈中只保存基础数据类型和自定义对象的引用(不是对象)，对象都存放在堆区中。每个方法执行时都会创建一个栈桢来存储方法的的变量表、操作数栈、动态链接方法、返回值、返回地址等信息。每个方法从调用直结束就对于一个栈桢在虚拟机栈中的入栈和出栈过程，如下（图有误，应该为栈桢）：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404195609.png)\n\n*   本地方法栈(Native Method Stacks)：\n\n    本地方法栈属于线程私有的数据区域，这部分主要与虚拟机用到的 Native 方法相关，一般情况下，我们无需关心此区域。\n\n### 二、Java内存模型\n\nJava内存模型(即Java Memory Model，简称JMM)本身是一种抽象的概念，并不真实存在。Java线程之间的通信由JMM控制，JMM决定一个线程对共享变量的写入何时对另一个线程可见。从抽象的角度来看，JMM定义了线程和主内存之间的抽象关系。\n\n　\n由于JVM运行程序的实体是线程，而每个线程创建时JVM都会为其创建一个工作内存(有些地方称为栈空间)，用于存储线程私有的数据，而Java内存模型中规定所有变量都存储在主内存，主内存是共享内存区域，所有线程都可以访问，但线程对变量的操作(读取赋值等)必须在工作内存中进行。\n\n　　\n首先要将变量从主内存拷贝的自己的工作内存空间，然后对变量进行操作，操作完成后再将变量写回主内存，不能直接操作主内存中的变量，工作内存中存储着主内存中的变量副本拷贝，前面说过，工作内存是每个线程的私有数据区域，因此不同的线程间无法访问对方的工作内存，线程间的通信(传值)必须通过主内存来完成，其简要访问过程如下图\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404195704.png)\n\n图3\n\n　　需要注意的是，JMM与Java内存区域的划分是不同的概念层次，更恰当说JMM描述的是一组规则，通过这组规则控制程序中各个变量在共享数据区域和私有数据区域的访问方式，JMM是围绕原子性，有序性、可见性展开的(稍后会分析)。\n　　\n　　JMM与Java内存区域唯一相似点，都存在共享数据区域和私有数据区域，在JMM中主内存属于共享数据区域，从某个程度上讲应该包括了堆和方法区，而工作内存数据线程私有数据区域，从某个程度上讲则应该包括程序计数器、虚拟机栈以及本地方法栈。或许在某些地方，我们可能会看见主内存被描述为堆内存，工作内存被称为线程栈，实际上他们表达的都是同一个含义。关于JMM中的主内存和工作内存说明如下\n\n*   主内存\n\n    主要存储的是Java实例对象以及线程之间的共享变量，所有线程创建的实例对象都存放在主内存中，不管该实例对象是成员变量还是方法中的本地变量(也称局部变量)，当然也包括了共享的类信息、常量、静态变量。由于是共享数据区域，多条线程对同一个变量进行访问可能会发现线程安全问题。\n\n*   工作内存\n\n    有的书籍中也称为本地内存，主要存储当前方法的所有本地变量信息(工作内存中存储着主内存中的变量副本拷贝)，每个线程只能访问自己的工作内存，即线程中的本地变量对其它线程是不可见的，就算是两个线程执行的是同一段代码，它们也会各自在自己的工作内存中创建属于当前线程的本地变量，当然也包括了字节码行号指示器、相关Native方法的信息。\n    \n    注意由于工作内存是每个线程的私有数据，线程间无法相互访问工作内存，因此存储在工作内存的数据不存在线程安全问题。注意，工作内存是JMM的一个抽象概念，并不真实存在。\n\n弄清楚主内存和工作内存后，接了解一下主内存与工作内存的数据存储类型以及操作方式，根据虚拟机规范，对于一个实例对象中的成员方法而言，如果方法中包含本地变量是基本数据类型（boolean,byte,short,char,int,long,float,double），将直接存储在工作内存的帧栈结构中，但倘若本地变量是引用类型，那么该变量的引用会存储在功能内存的帧栈中，而对象实例将存储在主内存(共享数据区域，堆)中。\n　　\n但对于实例对象的成员变量，不管它是基本数据类型或者包装类型(Integer、Double等)还是引用类型，都会被存储到堆区。至于static变量以及类本身相关信息将会存储在主内存中。需要注意的是，在主内存中的实例对象可以被多线程共享，倘若两个线程同时调用了同一个对象的同一个方法，那么两条线程会将要操作的数据拷贝一份到自己的工作内存中，执行完成操作后才刷新到主内存，简单示意图如下所示：\n\n![](https://images2018.cnblogs.com/blog/1332556/201805/1332556-20180530111627434-363867932.png)\n\n图4\n\n从图3来看，如果线程A与线程B之间要通信的话，必须经历下面两个步骤：\n\n1）线程A把本地内存A中更新过的共享变量刷新到主内存中去\n\n2）线程B到主内存中去读取线程A之前已更新过的共享变量\n\n从以上两个步骤来看，共享内存模型完成了“隐式通信”的过程。\n\nJMM也主要是通过控制主内存与每个线程的工作内存之间的交互，来为Java程序员提供内存可见性的保证。\n\n## 三、as-if-serial语义、happens-before原则\n\n### 3.1 as-if-serial语义\n\n重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段。as-if-serial语义的意思是：不管怎么重排序（编译器和处理器为了提高并行度），（单线程）程序的执行结果不能被改变。编译器、runtime和处理器都必须遵守as-if-serial语义。为了遵守as-if-serial语义，编译器和处理器不会对存在数据依赖关系的操作做重排序，因为这种重排序会改变执行结果。\n\n　　但是，如果操作之间不存在数据依赖关系，这些操作就可能被编译器和处理器重排序。\n\n### 3.2 happens-before原则\n\n　　happens-before是JMM最核心的概念。对应Java程序来说，理解happens-before是理解JMM的关键。\n\n　　设计JMM时，需要考虑两个关键因素：\n\n*   *   程序员对内存模型的使用。程序员希望内存模型易于理解、易于编程。程序员希望基于一个强内存模型来编写代码。\n    *   编译器和处理器对内存模型的实现。编译器和处理器希望内存模型对它们的束缚越少越好，这样它们就可以做尽可能多的优化来提高性能。编译器和处理器希望实现弱内存模型。\n\n但以上两点相互矛盾，所以JSR-133专家组在设计JMM时的核心膜表就是找到一个好的平衡点：一方面，为程序员提高足够强的内存可见性保证；另一方面，对编译器和处理器的限制尽可能地放松。\n\n另外还要一个特别有意思的事情就是关于重排序问题，更简单的说，重排序可以分为两类：1)会改变程序执行结果的重排序。 2)不会改变程序执行结果的重排序。\n\nJMM对这两种不同性质的重排序，采取了不同的策略，如下：\n\n*   对于会改变程序执行结果的重排序，JMM要求编译器和处理器必须禁止这种重排序。\n*   对于不会改变程序执行结果的重排序，JMM对编译器和处理器不做要求（JMM允许这种 重排序）\n\nJMM的设计图为：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404195805.png)\n\nJMM设计示意图 从图可以看出：\n\n*   JMM向程序员提供的happens-before规则能满足程序员的需求。JMM的happens-before规则不但简单易懂，而且也向程序员提供了足够强的内存可见性保证（有些内存可见性保证其实并不一定真实存在，比如上面的A happens-before B）。\n*   JMM对编译器和处理器的束缚已经尽可能少。从上面的分析可以看出，JMM其实是在遵循一个基本原则：只要不改变程序的执行结果（指的是单线程程序和正确同步的多线程程序），编译器和处理器怎么优化都行。例如，如果编译器经过细致的分析后，认定一个锁只会被单个线程访问，那么这个锁可以被消除。再如，如果编译器经过细致的分析后，认定一个volatile变量只会被单个线程访问，那么编译器可以把这个volatile变量当作一个普通变量来对待。这些优化既不会改变程序的执行结果，又能提高程序的执行效率。\n\n### 3.3 happens-before定义\n\nhappens-before的概念最初由Leslie Lamport在其一篇影响深远的论文（《Time，Clocks and the Ordering of Events in a Distributed System》）中提出。JSR-133使用happens-before的概念来指定两个操作之间的执行顺序。由于这两个操作可以在一个线程之内，也可以是在不同线程之间。因此，JMM可以通过happens-before关系向程序员提供跨线程的内存可见性保证（如果A线程的写操作a与B线程的读操作b之间存在happens-before关系，尽管a操作和b操作在不同的线程中执行，但JMM向程序员保证a操作将对b操作可见）。具体的定义为：\n\n1）如果一个操作happens-before另一个操作，那么第一个操作的执行结果将对第二个操作可见，而且第一个操作的执行顺序排在第二个操作之前。\n\n2）两个操作之间存在happens-before关系，并不意味着Java平台的具体实现必须要按照happens-before关系指定的顺序来执行。如果重排序之后的执行结果，与按happens-before关系来执行的结果一致，那么这种重排序并不非法（也就是说，JMM允许这种重排序）。\n\n上面的1）是JMM对程序员的承诺。从程序员的角度来说，可以这样理解happens-before关系：如果A happens-before B，那么Java内存模型将向程序员保证——A操作的结果将对B可见，且A的执行顺序排在B之前。注意，这只是Java内存模型向程序员做出的保证！\n\n上面的2）是JMM对编译器和处理器重排序的约束原则。正如前面所言，JMM其实是在遵循一个基本原则：只要不改变程序的执行结果（指的是单线程程序和正确同步的多线程程序），编译器和处理器怎么优化都行。JMM这么做的原因是：程序员对于这两个操作是否真的被重排序并不关心，程序员关心的是程序执行时的语义不能被改变（即执行结果不能被改变）。因此，happens-before关系本质上和as-if-serial语义是一回事。\n\n### 3.3 happens-before对比as-if-serial\n\n*   as-if-serial语义保证单线程内程序的执行结果不被改变，happens-before关系保证正确同步的多线程程序的执行结果不被改变。\n*   as-if-serial语义给编写单线程程序的程序员创造了一个幻境：单线程程序是按程序的顺序来执行的。happens-before关系给编写正确同步的多线程程序的程序员创造了一个幻境：正确同步的多线程程序是按happens-before指定的顺序来执行的。\n*   as-if-serial语义和happens-before这么做的目的，都是为了在不改变程序执行结果的前提下，尽可能地提高程序执行的并行度。\n\n### 3.4 happens-before具体规则\n\n*   程序顺序规则：一个线程中的每个操作，happens-before于该线程中的任意后续操作。\n*   监视器锁规则：对一个锁的解锁，happens-before于随后对这个锁的加锁。\n*   volatile变量规则：对一个volatile域的写，happens-before于任意后续对这个volatile域的读。\n*   传递性：如果A happens-before B，且B happens-before C，那么A happens-before C。\n*   start()规则：如果线程A执行操作ThreadB.start()（启动线程B），那么A线程的ThreadB.start()操作happens-before于线程B中的任意操作。\n*   join()规则：如果线程A执行操作ThreadB.join()并成功返回，那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回。\n\n### 3.5 happens-before与JMM的关系图\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404195827.png)\n\n一个happens-before规则对应于一个或多个编译器和处理器重排序规则。对于Java程序员来说，happens-before规则简单易懂，它避免Java程序员为了理解JMM提供的内存可见性保证而去学习复杂的重排序规则以及这些规则的具体实现方法\n\n## 四、volatile、锁的内存语义\n\n### 4.1 volatile的内存语义\n\n当声明共享变量为volatile后，对这个变量的读/写会很特别。一个volatile变量的单个读/写操作，与一个普通变量的读/写操作都是使用同一个锁来同步，它们之间的执行效果相同。\n\n锁的happens-before规则保证释放锁和获取锁的两个线程之间的内存可见性，这意味着对一个volatile变量的读，总是能看到（任意线程）对这个volatile变量最后的写入。\n\n锁的语义决定了临界区代码的执行具有原子性。这意味着，即使是64位的long型和double型变量，只要是volatile变量，对该变量的读/写就具有原子性。如果是多个volatile操作或类似于volatile++这种复合操作，这些操作整体上不具有原子性。\n\n简而言之，一旦一个共享变量（类的成员变量、类的静态成员变量）被volatile修饰之后，那么就具备了两层语义：\n\n　　1）保证了不同线程对这个变量进行操作时的可见性，即一个线程修改了某个变量的值，这新值对其他线程来说是立即可见的。\n\n　　2）禁止进行指令重排序。\n\n*   可见性。对一个volatiole变量的读，总是能看到（任意线程）对这个volatile变量最后的写入。\n*   有序性。volatile关键字能禁止指令重排序，所以volatile能在一定程度上保证有序性。\n\n    　　volatile关键字禁止指令重排序有两层意思：\n\n    　　1）当程序执行到volatile变量的读操作或者写操作时，在其前面的操作的更改肯定全部已经进行，且结果已经对后面的操作可见；在其后面的操作肯定还没有进行；\n\n    　　2）在进行指令优化时，不能将在对volatile变量访问的语句放在其后面执行，也不能把volatile变量后面的语句放到其前面执行。\n\n    　　可能上面说的比较绕，举个简单的例子：\n````\n//x、y为非volatile变量 //flag为volatile变量\n x = 2;        //语句1\ny = 0;        //语句2\nflag = true;  //语句3\nx = 4;         //语句4\ny = -1;       //语句5\n````\n由于flag变量为volatile变量，那么在进行指令重排序的过程的时候，不会将语句3放到语句1、语句2前面，也不会讲语句3放到语句4、语句5后面。但是要注意语句1和语句2的顺序、语句4和语句5的顺序是不作任何保证的。\n\n*   原子性。对任意单个volatile变量的读、写具有原子性，但类似于volatile++这种复合操作不具有原子性。\n\nvolatile写的内存语义：当写一个volatile变量时，JMM会把该线程对应的本地内存中的共享变量值刷新到主内存。\n\nvolatile读的内存语义：当读一个volatile变量时，JMM会把该线程对应的本地内存置位无效。线程接下来将从主内存中读取共享变量。（强制从主内存读取共享变量，把本地内存与主内存的共享变量的值变成一致）。\n\nvolatile写和读的内存语义总结总结：\n\n*   线程A写一个volatile变量，实质上是线程A向接下来将要读这个volatile变量的某个线程发出了（其对变量所做修改的）消息。\n*   线程B读一个volatile变量，实质上是线程B接收了之前某个线程发出的消息。\n*   线程A写一个volatile变量，随后线程B读这个volatile变量，这个过程实质上是线程A通过主内存向线程B发送消息。(隐式通信)\n\n### 4.2 volatile内存语义的实现\n\n前面提到过编译器重排序和处理器重排序。为了实现volatile内存语义，JMM分别限制了这两种类型的重排序类型。\n\n*   当第二个操作是volatile写时，不管第一个操作是什么，都不能重排序。这个规则确保volatile写之前的操作不会被编译器重排序到volatile写之后。\n*   当第一个操作是volatile读时，不管第二个操作是什么，都不能重排序。这个规则确保volatile读之后的操作不会被编译器重排序到volatile读之前。\n*   当第一个操作是volatile写时，第二个操作是volatile读时，不能重排序。\n\n为了实现volatile的内存语义，编译器在生成字节码时，会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。对于编译器来说，发现一个最优布置来最小化插入屏障的总数几乎不可能。为此，JMM采取保守策略。下面是基于保守策略的JMM内存屏障插入策略：\n\n*   在每个volatile写操作的前面插入一个StoreStore屏障。\n*   在每个volatile写操作的后面插入一个StoreLoad屏障。\n*   在每个volatile读操作的后面插入一个LoadLoad屏障。\n*   在每个volatile读操作的后面插入一个LoadStore屏障。\n\n上述内存屏障插入策略非常保守，但它可以保证在任意处理器平台，任意的程序中都能得到正确的volatile内存语义。\n\n下面是保守策略下，volatile写插入内存屏障后生成的指令序列示意图：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404195915.png)\n\n上图中的StoreStore屏障可以保证在volatile写之前，其前面的所有普通写操作已经对任意处理器可见了。这是因为StoreStore屏障将保障上面所有的普通写在volatile写之前刷新到主内存。\n\n这里比较有意思的是volatile写后面的StoreLoad屏障。这个屏障的作用是避免volatile写与后面可能有的volatile读/写操作重排序。因为编译器常常无法准确判断在一个volatile写的后面，是否需要插入一个StoreLoad屏障（比如，一个volatile写之后方法立即return）。为了保证能正确实现volatile的内存语义，JMM在这里采取了保守策略：在每个volatile写的后面或在每个volatile读的前面插入一个StoreLoad屏障。从整体执行效率的角度考虑，JMM选择了在每个volatile写的后面插入一个StoreLoad屏障。因为volatile写-读内存语义的常见使用模式是：一个写线程写volatile变量，多个读线程读同一个volatile变量。当读线程的数量大大超过写线程时，选择在volatile写之后插入StoreLoad屏障将带来可观的执行效率的提升。从这里我们可以看到JMM在实现上的一个特点：首先确保正确性，然后再去追求执行效率。下面是在保守策略下，volatile读插入内存屏障后生成的指令序列示意图：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404195934.png)\n上图中的LoadLoad屏障用来禁止处理器把上面的volatile读与下面的普通读重排序。LoadStore屏障用来禁止处理器把上面的volatile读与下面的普通写重排序。\n\n上述volatile写和volatile读的内存屏障插入策略非常保守。在实际执行时，只要不改变volatile写-读的内存语义，编译器可以根据具体情况省略不必要的屏障。下面我们通过具体的示例代码来说明：\n\n\n\n````\nclass VolatileBarrierExample { int a; volatile int v1 = 1; volatile int v2 = 2; void readAndWrite() { int i = v1;      //第一个volatile读\n    int j = v2;      // 第二个volatile读\n    a = i + j;      //普通写\n    v1 = i + 1;     // 第一个volatile写\n    v2 = j * 2;     //第二个 volatile写\n }\n\n  … //其他方法\n}\n````\n针对readAndWrite()方法，编译器在生成字节码时可以做如下的优化：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404195956.png)\n\n注意，最后的StoreLoad屏障不能省略。因为第二个volatile写之后，方法立即return。此时编译器可能无法准确断定后面是否会有volatile读或写，为了安全起见，编译器常常会在这里插入一个StoreLoad屏障。\n\n上面的优化是针对任意处理器平台，由于不同的处理器有不同“松紧度”的处理器内存模型，内存屏障的插入还可以根据具体的处理器内存模型继续优化。以x86处理器为例，上图中除最后的StoreLoad屏障外，其它的屏障都会被省略。\n\n为了提供一种比锁更轻量级的线程之间通信的机制，JSR-133专家组决定增强volatile的内存语义：严格限制编译器和处理器对volatile变量与普通变量的重排序，确保volatile的写-读和锁的释放-获取具有相同的内存语义。\n\n由于volatile仅仅保证对单个volatile变量的读/写具有原子性，而锁的互斥执行的特性可以确保对整个临界区代码的执行具有原子性。在功能上，锁比volatile更强大；在可伸缩性和执行性能上，volatile更有优势。\n\n当一个变量被定义为volatile之后，就可以保证此变量对所有线程的可见性，即当一个线程修改了此变量的值的时候，变量新的值对于其他线程来说是可以立即得知的。可以理解成：对volatile变量所有的写操作都能立刻被其他线程得知。但是这并不代表基于volatile变量的运算在并发下是安全的，因为volatile只能保证内存可见性，却没有保证对变量操作的原子性。比如下面的代码：\n\n\n````\n\n/ * \n * 发起20个线程，每个线程对race变量进行10000次自增操作，如果代码能够正确并发，\n * 则最终race的结果应为200000，但实际的运行结果却小于200000。\n * \n * @author Colin Wang */\n\npublic class Test { public static volatile int race = 0; public static void increase() {\n        race++;\n    } private static final int THREADS_COUNT = 20; public static void main(String[] args) {\n\n        Thread[] threads = new Thread[THREADS_COUNT]; for (int i = 0; i < THREADS_COUNT; i++) {\n\n            threads[i] = new Thread(new Runnable() {\n\n                @Override public void run() { for (int i = 0; i < 10000; i++) {\n                        increase();\n                    }\n                }\n            });\n\n            threads[i].start();\n        } while (Thread.activeCount() > 1)\n            Thread.yield();\n\n        System.out.println(race);\n\n    }\n\n}\n\n````\n\n\n按道理来说结果是10000，但是运行下很可能是个小于10000的值。有人可能会说volatile不是保证了可见性啊，一个线程对race的修改，另外一个线程应该立刻看到啊！可是这里的操作race++是个复合操作啊，包括读取race的值，对其自增，然后再写回主存。\n\n假设线程A，读取了race的值为10，这时候被阻塞了，因为没有对变量进行修改，触发不了volatile规则。\n\n线程B此时也读读race的值，主存里race的值依旧为10，做自增，然后立刻就被写回主存了，为11。\n\n此时又轮到线程A执行，由于工作内存里保存的是10，所以继续做自增，再写回主存，11又被写了一遍。所以虽然两个线程执行了两次increase()，结果却只加了一次。\n\n有人说，volatile不是会使缓存行无效的吗？但是这里线程A读取到线程B也进行操作之前，并没有修改inc值，所以线程B读取的时候，还是读的10。\n\n又有人说，线程B将11写回主存，不会把线程A的缓存行设为无效吗？但是线程A的读取操作已经做过了啊，只有在做读取操作时，发现自己缓存行无效，才会去读主存的值，所以这里线程A只能继续做自增了。\n\n综上所述，在这种复合操作的情景下，原子性的功能是维持不了了。但是volatile在上面那种设置flag值的例子里，由于对flag的读/写操作都是单步的，所以还是能保证原子性的。\n\n要想保证原子性，只能借助于synchronized,Lock以及并发包下的atomic的原子操作类了，即对基本数据类型的 自增（加1操作），自减（减1操作）、以及加法操作（加一个数），减法操作（减一个数）进行了封装，保证这些操作是原子性操作。\n\n[Java 理论与实践: 正确使用 Volatile 变量](http://www.ibm.com/developerworks/cn/java/j-jtp06197.html)总结了volatile关键的使用场景，\n\n只能在有限的一些情形下使用 volatile 变量替代锁。要使 volatile 变量提供理想的线程安全，必须同时满足下面两个条件：\n\n\n\n*   对变量的写操作不依赖于当前值。\n*   该变量没有包含在具有其他变量的不变式中。\n\n实际上，这些条件表明，可以被写入 volatile 变量的这些有效值独立于任何程序的状态，包括变量的当前状态。\n\n第一个条件的限制使 volatile 变量不能用作线程安全计数器。虽然增量操作（`x++`）看上去类似一个单独操作，实际上它是一个由读取－修改－写入操作序列组成的组合操作，必须以原子方式执行，而 volatile 不能提供必须的原子特性。实现正确的操作需要使`x`的值在操作期间保持不变，而 volatile 变量无法实现这点。（然而，如果将值调整为只从单个线程写入，那么可以忽略第一个条件。）\n\nvolatile一个使用场景是状态位；还有只有一个线程写，其余线程读的场景\n\n\n\n### 4.3 锁的内存语义\n\n锁可以让临界区互斥执行。锁的释放-获取的内存语义与volatile变量写-读的内存语义很像。\n\n当线程释放锁时，JMM会把该线程对应的本地内存中的共享变量刷新到主内存中。\n\n当线程获取锁时，JMM会把该线程对应的本地内存置位无效，从而使得被监视器保护的临界区代码必须从主内存中读取共享变量。\n\n不难发现：锁释放与volatile写有相同的内存语音；锁获取与volatile读有相同的内存语义。\n\n下面对锁释放和锁获取的内存语义做个总结。\n\n*   线程A释放一个锁，实质上是线程A向接下来将要获取这个锁的某个线程发出了（线程A对共享变量所做修改的）消息。\n*   线程B获取一个锁，实质上是线程B接收了之前某个线程发出的（在释放这个锁之前对共享变量所做修改）的消息。\n*   线程A释放锁，随后线程B获取这个锁，这个过程实质上是线程A通过主内存向线程B发送消息。\n\n### 4.4 final域的内存语义\n\n与前面介绍的锁和volatile想比，对final域的读和写更像是普通的变量访问。\n\n对于final域，编译器和处理器要遵循两个重排序规则：\n\n1.在构造函数内对一个final域的写入，与随后把这个被构造对象的引用赋值给一个引用变量，这两个操作之间不能重排序。\n\n2.初次读一个包含final域的对象的应用，与随后初次读这个final域，这两个操作之间不能重排序\n\n下面通过一个示例来分别说明这两个规则：\n\n\n\n````\npublic class FinalTest { int i;//普通变量\n    final int j; static FinalExample obj; public FinalExample(){\n        i = 1;\n        j = 2;\n    } public static void writer(){\n        obj = new FinalExample();\n    } public static void reader(){\n        FinalExample object = obj;//读对象引用\n        int a = object.i; int b = object.j;\n    }\n}\n\n````\n\n\n这里假设一个线程A执行writer()方法，随后另一个线程B执行reader()方法。下面我们通过这两个线程的交互来说明这两个规则。\n\n写final域的重排序规则禁止把final域的写重排序到构造函数之外。这个规则的实现包含下面两个方面。\n\n1）JMM禁止编译器把final域的写重排序到构造函数之外。\n\n2）编译器会在final域的写之后，构造函数return之前，插入一个StoreStore屏障。这个屏障禁止处理器把final域的写重排序到构造函数之外。\n\n现在让我们分析writer方法，writer方法只包含一行代码obj =newFinalTest();这行代码包含两个步骤：\n\n1）构造一个FinalTest类型的对象\n\n2）把这个对象的引用赋值给obj\n\n假设线程B的读对象引用与读对象的成员域之间没有重排序，下图是一种可能的执行时序\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404200259.png)\n\n在上图中，写普通域的操作被编译器重排序到了构造函数之外，读线程B错误的读取到了普通变量i初始化之前的值。而写final域的操作被写final域重排序的规则限定在了构造函数之内，读线程B正确的读取到了final变量初始化之后的值。\n\n写final域的重排序规则可以确保：在对象引用为任意线程可见之前，对象的final域已经被初始化了，而普通变量不具有这个保证。以上图为例，读线程B看到对象obj的时候，很可能obj对象还没有构造完成（对普通域i的写操作被重排序到构造函数外，此时初始值1还没有写入普通域i）\n\n读final域的重排序规则是：在一个线程中，初次读对象的引用与初次读这个对象包含的final域，JMM禁止重排序这两个操作(该规则仅仅针对处理器)。编译器会在读final域的操作前面加一个LoadLoad屏障。\n\n初次读对象引用与初次读该对象包含的final域，这两个操作之间存在间接依赖关系。由于编译器遵守间接依赖关系，因此编译器不会重排序这两个操作。大多数处理器也会遵守间接依赖，也不会重排序这两个操作。但有少数处理器允许对存在间接依赖关系的操作做重排序（比如alpha处理器），这个规则就是专门用来针对这种处理器的。\n\n上面的例子中，reader方法包含三个操作\n\n1）初次读引用变量obj\n\n2）初次读引用变量指向对象的普通域\n\n3）初次读引用变量指向对象的final域\n\n现在假设写线程A没有发生任何重排序，同时程序在不遵守间接依赖的处理器上执行，下图是一种可能的执行时序：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404200319.png)\n\n在上图中，读对象的普通域操作被处理器重排序到读对象引用之前。在读普通域时，该域还没有被写线程写入，这是一个错误的读取操作，而读final域的重排序规则会把读对象final域的操作“限定”在读对象引用之后，此时该final域已经被A线程初始化过了，这是一个正确的读取操作。\n\n读final域的重排序规则可以确保：在读一个对象的final域之前，一定会先读包含这个final域的对象的引用。在这个示例程序中，如果该引用不为null，那么引用对象的final域一定已经被A线程初始化过了。\n\nfinal域为引用类型，上面我们看到的final域是基础的数据类型，如果final域是引用类型呢？\n\n\n````\n\npublic class FinalReferenceTest { final int[] arrs;//final引用\n\n    static FinalReferenceTest obj; public FinalReferenceTest(){\n        arrs = new int[1];//1\n        arrs[0] = 1;//2\n } public static void write0(){//A线程\n        obj = new FinalReferenceTest();//3\n } public static void write1(){//线程B\n        obj.arrs[0] = 2;//4\n } public static void reader(){//C线程\n        if(obj!=null){//5\n            int temp =obj.arrs[0];//6\n }\n    }\n}\n\n````\n\n\nJMM可以确保读线程C至少能看到写线程A在构造函数中对final引用对象的成员域的写入。即C至少能看到数组下标0的值为1。而写线程B对数组元素的写入，读线程C可能看得到，也可能看不到。JMM不保证线程B的写入对读线程C可见，因为写线程B和读线程C之间存在数据竞争，此时的执行结果不可预知。\n\n如果想要确保读线程C看到写线程B对数组元素的写入，写线程B和读线程C之间需要使用同步原语（lock或volatile）来确保内存可见性。\n\n前面我们提到过，写final域的重排序规则可以确保：在引用变量为任意线程可见之前，该引用变量指向的对象的final域已经在构造函数中被正确初始化过了。其实，要得到这个效果，还需要一个保证：在构造函数内部，不能让这个被构造对象的引用为其他线程所见，也就是对象引用不能在构造函数中“逸出”。\n\n\n\n````\npublic class FinalReferenceEscapeExample {final int i;static FinalReferenceEscapeExample obj;public FinalReferenceEscapeExample () {\n　　　　i = 1; // 1写final域\n　　　　obj = this; // 2 this引用在此\"逸出\"\n　　}\n　　public static void writer() {new FinalReferenceEscapeExample ();\n　　}public static void reader() {if (obj != null) { // 3\n　　　　　　int temp = obj.i; // 4\n　　　　}\n　　}\n}\n\n````\n\n\n假设一个线程A执行writer()方法，另一个线程B执行reader()方法。这里的操作2使得对象还未完成构造前就为线程B可见。即使这里的操作2是构造函数的最后一步，且在程序中操作2排在操作1后面，执行read()方法的线程仍然可能无法看到final域被初始化后的值，因为这里的操作1和操作2之间可能被重排序。\n\nJSR-133为什么要增强final的语义：\n\n通过为final域增加写和读重排序规则，可以为Java程序员提供初始化安全保证：只要对象是正确构造的（被构造对象的引用在构造函数中没有“逸出”），那么不需要使用同步（指lock和volatile的使用）就可以保证任意线程都能看到这个final域在构造函数中被初始化之后的值。\n\n## 五、JMM是如何处理并发过程中的三大特性\n\nJMM是围绕这在并发过程中如何处理原子性、可见性和有序性这3个特性来建立的。\n\n*   原子性：\n\n    Java中，对基本数据类型的读取和赋值操作是原子性操作，所谓原子性操作就是指这些操作是不可中断的，要做一定做完，要么就没有执行。比如：\n\n    i = 2;j = i;i++;i = i + 1；\n\n    上面4个操作中，i=2是读取操作，必定是原子性操作，j=i你以为是原子性操作，其实吧，分为两步，一是读取i的值，然后再赋值给j,这就是2步操作了，称不上原子操作，i++和i = i + 1其实是等效的，读取i的值，加1，再写回主存，那就是3步操作了。所以上面的举例中，最后的值可能出现多种情况，就是因为满足不了原子性。\n    \n    JMM只能保证对单个volatile变量的读/写具有原子性，但类似于volatile++这种符合操作不具有原子性，这时候就必须借助于synchronized和Lock来保证整块代码的原子性了。线程在释放锁之前，必然会把i的值刷回到主存的。\n\n*   可见性：可见性指当一个线程修改了共享变量的值，其他线程能够立即得知这个修改。Java内存模型是通过在变量修改后将新值同步回主内存，在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方式来实现可见性。\n  \n*   无论是普通变量还是volatile变量，它们的区别是：volatile的特殊规则保证了新值能立即同步到主内存，以及每次使用前立即从主内存刷新。因为，可以说volatile保证了多线程操作时变量的可见性，而普通变量不能保证这一点。\n\n*   除了volatile之外，java中还有2个关键字能实现可见性，即synchronized和final（final修饰的变量，线程安全级别最高）。同步块的可见性是由“对一个变量执行unlock操作之前，必须先把此变量同步回主内存中（执行store，write操作）”这条规则获得；而final关键字的可见性是指：被final修饰的字段在构造器中一旦初始化完成，并且构造器没有把“this”的引用传递出去（this引用逃逸是一件很危险的事，其他线程有可能通过这个引用访问到“初始化了一半”的对象），那么在其他线程中就能看到final字段的值。\n\n*   有序性：JMM的有序性在讲解volatile时详细的讨论过，java程序中天然的有序性可以总结为一句话：如果在本线程内观察，所有操作都是有序的；如果在一个线程中观察另一个线程，所有操作都是无序的。前半句是指“线程内表现为串行的语义”，后半句指的是“指令重排”现象和“工作内存与主内存同步延迟”现象。\n\n*   前半句可以用JMM规定的as-if-serial语义来解决，后半句可以用JMM规定的happens-before原则来解决。Java语义提供了volatile和synchronized两个关键字来保证线程之间操作的有序性，volatile关键字本身就包含了禁止指令重排的语义，而synchronized则是由“一个变量在同一个时刻只允许一条线程对其进行lock操作”这条规则获取的。这个规则决定了持有同一个锁的两个同步块只能串行的进入。\n\n## 参考链接：\n\n[https://blog.csdn.net/javazejian/article/details/72772461#volatile%E7%A6%81%E6%AD%A2%E9%87%8D%E6%8E%92%E4%BC%98%E5%8C%96](https://blog.csdn.net/javazejian/article/details/72772461#volatile%E7%A6%81%E6%AD%A2%E9%87%8D%E6%8E%92%E4%BC%98%E5%8C%96)\n\n[https://www.cnblogs.com/_popc/p/6096517.html](https://www.cnblogs.com/_popc/p/6096517.html)\n\n[https://blog.csdn.net/liu_dong_liang/article/details/80391040](https://blog.csdn.net/liu_dong_liang/article/details/80391040)\n\n[https://www.jb51.net/article/76006.htm](https://www.jb51.net/article/76006.htm)\n\n[https://blog.csdn.net/x_i_y_u_e/article/details/50728602](https://blog.csdn.net/x_i_y_u_e/article/details/50728602)\n\n[https://blog.csdn.net/FYGu18/article/details/79001688](https://blog.csdn.net/FYGu18/article/details/79001688)\n\n[https://blog.csdn.net/soongp/article/details/80292796](https://blog.csdn.net/soongp/article/details/80292796)\n\n《Java 并发编程的艺术》 方腾飞 魏鹏 程晓明 著\n\n\n\n"
  },
  {
    "path": "docs/Java/concurrency/Java并发指南：Java读写锁ReentrantReadWriteLock源码分析.md",
    "content": "# 目录\n  * [使用示例](#使用示例)\n  * [ReentrantReadWriteLock 总览](#reentrantreadwritelock-总览)\n  * [源码分析](#源码分析)\n    * [读锁获取](#读锁获取)\n    * [读锁释放](#读锁释放)\n    * [写锁获取](#写锁获取)\n    * [写锁释放](#写锁释放)\n  * [锁降级](#锁降级)\n  * [总结](#总结)\n\n本文转自：https://www.javadoop.com/\n\n**本文转载自互联网，侵删**\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n文章同步发于我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《Java并发指南》其中一篇，本文大部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何全面深入地学习Java并发技术，从Java多线程基础，再到并发编程的基础知识，从Java并发包的入门和实战，再到JUC的源码剖析，一步步地学习Java并发编程，并上手进行实战，以便让你更完整地了解整个Java并发编程知识体系，形成自己的知识框架。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供一些对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n<!--more -->\n\n本文内容：读写锁 ReentrantReadWriteLock 的源码分析，基于 Java7/Java8。\n\n阅读建议：虽然我这里会介绍一些 AQS 的知识，不过如果你完全不了解 AQS，看本文就有点吃力了。\n\n## 使用示例\n\n下面这个例子非常实用，我是 javadoc 的搬运工：\n\n```\n// 这是一个关于缓存操作的故事\nclass CachedData {\n    Object data;\n    volatile boolean cacheValid;\n    // 读写锁实例\n    final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();\n\n    void processCachedData() {\n        // 获取读锁\n        rwl.readLock().lock();\n        if (!cacheValid) { // 如果缓存过期了，或者为 null\n            // 释放掉读锁，然后获取写锁 (后面会看到，没释放掉读锁就获取写锁，会发生死锁情况)\n            rwl.readLock().unlock();\n            rwl.writeLock().lock();\n\n            try {\n                if (!cacheValid) { // 重新判断，因为在等待写锁的过程中，可能前面有其他写线程执行过了\n                    data = ...\n                    cacheValid = true;\n                }\n                // 获取读锁 (持有写锁的情况下，是允许获取读锁的，称为 “锁降级”，反之不行。)\n                rwl.readLock().lock();\n            } finally {\n                // 释放写锁，此时还剩一个读锁\n                rwl.writeLock().unlock(); // Unlock write, still hold read\n            }\n        }\n\n        try {\n            use(data);\n        } finally {\n            // 释放读锁\n            rwl.readLock().unlock();\n        }\n    }\n}\n```\n\nReentrantReadWriteLock 分为读锁和写锁两个实例，读锁是共享锁，可被多个线程同时使用，写锁是独占锁。持有写锁的线程可以继续获取读锁，反之不行。\n\n## ReentrantReadWriteLock 总览\n\n这一节比较重要，我们要先看清楚 ReentrantReadWriteLock 的大框架，然后再到源码细节。\n\n首先，我们来看下 ReentrantReadWriteLock 的结构，它有好些嵌套类：\n\n![11](https://www.javadoop.com/blogimages/reentrant-read-write-lock/11.png)\n\n大家先仔细看看这张图中的信息。然后我们把 ReadLock 和 WriteLock 的代码提出来一起看，清晰一些：\n\n![12](https://www.javadoop.com/blogimages/reentrant-read-write-lock/12.png)\n\n很清楚了，ReadLock 和 WriteLock 中的方法都是通过 Sync 这个类来实现的。Sync 是 AQS 的子类，然后再派生了公平模式和不公平模式。\n\n从它们调用的 Sync 方法，我们可以看到：**ReadLock 使用了共享模式，WriteLock 使用了独占模式**。\n\n等等，**同一个 AQS 实例怎么可以同时使用共享模式和独占模式**？？？\n\n这里给大家回顾下 AQS，我们横向对比下 AQS 的共享模式和独占模式：\n\n![13](https://www.javadoop.com/blogimages/reentrant-read-write-lock/13.png)\n\nAQS 的精髓在于内部的属性**state**：\n\n1.  对于独占模式来说，通常就是 0 代表可获取锁，1 代表锁被别人获取了，重入例外\n2.  而共享模式下，每个线程都可以对 state 进行加减操作\n\n也就是说，独占模式和共享模式对于 state 的操作完全不一样，那读写锁 ReentrantReadWriteLock 中是怎么使用 state 的呢？答案是**将 state 这个 32 位的 int 值分为高 16 位和低 16位，分别用于共享模式和独占模式**。\n\n## 源码分析\n\n有了前面的概念，大家心里应该都有数了吧，下面就不再那么啰嗦了，直接代码分析。\n\n源代码加注释 1500 行，并不算难，我们要看的代码量不大。如果你前面一节都理解了，那么直接从头开始一行一行往下看就是了，还是比较简单的。\n\nReentrantReadWriteLock 的前面几行很简单，我们往下滑到 Sync 类，先来看下它的所有的属性：\n\n```\nabstract static class Sync extends AbstractQueuedSynchronizer {\n    // 下面这块说的就是将 state 一分为二，高 16 位用于共享模式，低16位用于独占模式\n    static final int SHARED_SHIFT   = 16;\n    static final int SHARED_UNIT    = (1 << SHARED_SHIFT);\n    static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;\n    static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;\n    // 取 c 的高 16 位值，代表读锁的获取次数(包括重入)\n    static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }\n    // 取 c 的低 16 位值，代表写锁的重入次数，因为写锁是独占模式\n    static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }\n\n    // 这个嵌套类的实例用来记录每个线程持有的读锁数量(读锁重入)\n    static final class HoldCounter {\n        // 持有的读锁数\n        int count = 0;\n        // 线程 id\n        final long tid = getThreadId(Thread.currentThread());\n    }\n\n    // ThreadLocal 的子类\n    static final class ThreadLocalHoldCounter\n        extends ThreadLocal<HoldCounter> {\n        public HoldCounter initialValue() {\n            return new HoldCounter();\n        }\n    }\n    /**\n      * 组合使用上面两个类，用一个 ThreadLocal 来记录当前线程持有的读锁数量\n      */ \n    private transient ThreadLocalHoldCounter readHolds;\n\n    // 用于缓存，记录\"最后一个获取读锁的线程\"的读锁重入次数，\n    // 所以不管哪个线程获取到读锁后，就把这个值占为已用，这样就不用到 ThreadLocal 中查询 map 了\n    // 算不上理论的依据：通常读锁的获取很快就会伴随着释放，\n    //   显然，在 获取->释放 读锁这段时间，如果没有其他线程获取读锁的话，此缓存就能帮助提高性能\n    private transient HoldCounter cachedHoldCounter;\n\n    // 第一个获取读锁的线程(并且其未释放读锁)，以及它持有的读锁数量\n    private transient Thread firstReader = null;\n    private transient int firstReaderHoldCount;\n\n    Sync() {\n        // 初始化 readHolds 这个 ThreadLocal 属性\n        readHolds = new ThreadLocalHoldCounter();\n        // 为了保证 readHolds 的内存可见性\n        setState(getState()); // ensures visibility of readHolds\n    }\n    ...\n}\n```\n\n1.  state 的高 16 位代表读锁的获取次数，包括重入次数，获取到读锁一次加 1，释放掉读锁一次减 1\n2.  state 的低 16 位代表写锁的获取次数，因为写锁是独占锁，同时只能被一个线程获得，所以它代表重入次数\n3.  每个线程都需要维护自己的 HoldCounter，记录该线程获取的读锁次数，这样才能知道到底是不是读锁重入，用 ThreadLocal 属性**readHolds**维护\n4.  **cachedHoldCounter**有什么用？其实没什么用，但能提示性能。将最后一次获取读锁的线程的 HoldCounter 缓存到这里，这样比使用 ThreadLocal 性能要好一些，因为 ThreadLocal 内部是基于 map 来查询的。但是 cachedHoldCounter 这一个属性毕竟只能缓存一个线程，所以它要起提升性能作用的依据就是：通常读锁的获取紧随着就是该读锁的释放。我这里可能表达不太好，但是大家应该是懂的吧。\n5.  **firstReader**和**firstReaderHoldCount**有什么用？其实也没什么用，但是它也能提示性能。将\"第一个\"获取读锁的线程记录在 firstReader 属性中，这里的**第一个**不是全局的概念，等这个 firstReader 当前代表的线程释放掉读锁以后，会有后来的线程占用这个属性的。**firstReader 和 firstReaderHoldCount 使得在读锁不产生竞争的情况下，记录读锁重入次数非常方便快速**\n6.  如果一个线程使用了 firstReader，那么它就不需要占用 cachedHoldCounter\n7.  个人认为，读写锁源码中最让初学者头疼的就是这几个用于提升性能的属性了，使得大家看得云里雾里的。主要是因为 ThreadLocal 内部是通过一个 ThreadLocalMap 来操作的，会增加检索时间。而很多场景下，执行 unlock 的线程往往就是刚刚最后一次执行 lock 的线程，中间可能没有其他线程进行 lock。还有就是很多不怎么会发生读锁竞争的场景。\n\n上面说了这么多，是希望能帮大家降低后面阅读源码的压力，大家也可以先看看后面的，然后再慢慢体会。\n\n前面我们好像都只说读锁，完全没提到写锁，主要是因为写锁真的是简单很多，我也特地将写锁的源码放到了后面，我们先啃下最难的读锁先。\n\n### 读锁获取\n\n下面我就不一行一行按源码顺序说了，我们按照使用来说。\n\n我们来看下读锁 ReadLock 的 lock 流程：\n\n```\n// ReadLock\npublic void lock() {\n    sync.acquireShared(1);\n}\n// AQS\npublic final void acquireShared(int arg) {\n    if (tryAcquireShared(arg) < 0)\n        doAcquireShared(arg);\n}\n```\n\n然后我们就会进到 Sync 类的 tryAcquireShared 方法：\n\n> 在 AQS 中，如果 tryAcquireShared(arg) 方法返回值小于 0 代表没有获取到共享锁(读锁)，大于 0 代表获取到\n> \n> 回顾 AQS 共享模式：tryAcquireShared 方法不仅仅在 acquireShared 的最开始被使用，这里是 try，也就可能会失败，如果失败的话，执行后面的 doAcquireShared，进入到阻塞队列，然后等待前驱节点唤醒。唤醒以后，还是会调用 tryAcquireShared 进行获取共享锁的。当然，唤醒以后再 try 是很容易获得锁的，因为这个节点已经排了很久的队了，组织是会照顾它的。\n> \n> 所以，你在看下面这段代码的时候，要想象到两种获取读锁的场景，一种是新来的，一种是排队排到它的。\n\n```\nprotected final int tryAcquireShared(int unused) {\n\n    Thread current = Thread.currentThread();\n    int c = getState();\n\n    // exclusiveCount(c) 不等于 0，说明有线程持有写锁，\n    //    而且不是当前线程持有写锁，那么当前线程获取读锁失败\n    //         （另，如果持有写锁的是当前线程，是可以继续获取读锁的）\n    if (exclusiveCount(c) != 0 &&\n        getExclusiveOwnerThread() != current)\n        return -1;\n\n    // 读锁的获取次数\n    int r = sharedCount(c);\n\n    // 读锁获取是否需要被阻塞，稍后细说。为了进去下面的分支，假设这里不阻塞就好了\n    if (!readerShouldBlock() &&\n        // 判断是否会溢出 (2^16-1，没那么容易溢出的)\n        r < MAX_COUNT &&\n        // 下面这行 CAS 是将 state 属性的高 16 位加 1，低 16 位不变，如果成功就代表获取到了读锁\n        compareAndSetState(c, c + SHARED_UNIT)) {\n\n        // =======================\n        //   进到这里就是获取到了读锁\n        // =======================\n\n        if (r == 0) {\n            // r == 0 说明此线程是第一个获取读锁的，或者说在它前面获取读锁的都走光光了，它也算是第一个吧\n            //  记录 firstReader 为当前线程，及其持有的读锁数量：1\n            firstReader = current;\n            firstReaderHoldCount = 1;\n        } else if (firstReader == current) {\n            // 进来这里，说明是 firstReader 重入获取读锁（这非常简单，count 加 1 结束）\n            firstReaderHoldCount++;\n        } else {\n            // 前面我们说了 cachedHoldCounter 用于缓存最后一个获取读锁的线程\n            // 如果 cachedHoldCounter 缓存的不是当前线程，设置为缓存当前线程的 HoldCounter\n            HoldCounter rh = cachedHoldCounter;\n            if (rh == null || rh.tid != getThreadId(current))\n                cachedHoldCounter = rh = readHolds.get();\n            else if (rh.count == 0) \n                // 到这里，那么就是 cachedHoldCounter 缓存的是当前线程，但是 count 为 0，\n                // 大家可以思考一下：这里为什么要 set ThreadLocal 呢？(当然，答案肯定不在这块代码中)\n                //   既然 cachedHoldCounter 缓存的是当前线程，\n                //   当前线程肯定调用过 readHolds.get() 进行初始化 ThreadLocal\n                readHolds.set(rh);\n\n            // count 加 1\n            rh.count++;\n        }\n        // return 大于 0 的数，代表获取到了共享锁\n        return 1;\n    }\n    // 往下看\n    return fullTryAcquireShared(current);\n}\n```\n\n上面的代码中，要进入 if 分支，需要满足：readerShouldBlock() 返回 false，并且 CAS 要成功（我们先不要纠结 MAX_COUNT 溢出）。\n\n那我们反向推，怎么样进入到最后的 fullTryAcquireShared：\n\n*   readerShouldBlock() 返回 true，2 种情况：\n\n    *   在 FairSync 中说的是 hasQueuedPredecessors()，即阻塞队列中有其他元素在等待锁。\n\n        > 也就是说，公平模式下，有人在排队呢，你新来的不能直接获取锁\n\n    *   在 NonFairSync 中说的是 apparentlyFirstQueuedIsExclusive()，即判断阻塞队列中 head 的第一个后继节点是否是来获取写锁的，如果是的话，让这个写锁先来，避免写锁饥饿。\n\n        > 作者给写锁定义了更高的优先级，所以如果碰上获取写锁的线程**马上**就要获取到锁了，获取读锁的线程不应该和它抢。\n        > \n        > 如果 head.next 不是来获取写锁的，那么可以随便抢，因为是非公平模式，大家比比 CAS 速度\n\n*   compareAndSetState(c, c + SHARED_UNIT) 这里 CAS 失败，存在竞争。可能是和另一个读锁获取竞争，当然也可能是和另一个写锁获取操作竞争。\n\n然后就会来到 fullTryAcquireShared 中再次尝试：\n\n```\n/**\n * 1\\. 刚刚我们说了可能是因为 CAS 失败，如果就此返回，那么就要进入到阻塞队列了，\n *    想想有点不甘心，因为都已经满足了 !readerShouldBlock()，也就是说本来可以不用到阻塞队列的，\n *    所以进到这个方法其实是增加 CAS 成功的机会\n * 2\\. 在 NonFairSync 情况下，虽然 head.next 是获取写锁的，我知道它等待很久了，我没想和它抢，\n *    可是如果我是来重入读锁的，那么只能表示对不起了\n */\nfinal int fullTryAcquireShared(Thread current) {\n    HoldCounter rh = null;\n    // 别忘了这外层有个 for 循环\n    for (;;) {\n        int c = getState();\n        // 如果其他线程持有了写锁，自然这次是获取不到读锁了，乖乖到阻塞队列排队吧\n        if (exclusiveCount(c) != 0) {\n            if (getExclusiveOwnerThread() != current)\n                return -1;\n            // else we hold the exclusive lock; blocking here\n            // would cause deadlock.\n        } else if (readerShouldBlock()) {\n            /**\n              * 进来这里，说明：\n              *  1\\. exclusiveCount(c) == 0：写锁没有被占用\n              *  2\\. readerShouldBlock() 为 true，说明阻塞队列中有其他线程在等待\n              *\n              * 既然 should block，那进来这里是干什么的呢？\n              * 答案：是进来处理读锁重入的！\n              * \n              */\n\n            // firstReader 线程重入读锁，直接到下面的 CAS\n            if (firstReader == current) {\n                // assert firstReaderHoldCount > 0;\n            } else {\n                if (rh == null) {\n                    rh = cachedHoldCounter;\n                    if (rh == null || rh.tid != getThreadId(current)) {\n                        // cachedHoldCounter 缓存的不是当前线程\n                        // 那么到 ThreadLocal 中获取当前线程的 HoldCounter\n                        // 如果当前线程从来没有初始化过 ThreadLocal 中的值，get() 会执行初始化\n                        rh = readHolds.get();\n                        // 如果发现 count == 0，也就是说，纯属上一行代码初始化的，那么执行 remove\n                        // 然后往下两三行，乖乖排队去\n                        if (rh.count == 0)\n                            readHolds.remove();\n                    }\n                }\n                if (rh.count == 0)\n                    // 排队去。\n                    return -1;\n            }\n            /**\n              * 这块代码我看了蛮久才把握好它是干嘛的，原来只需要知道，它是处理重入的就可以了。\n              * 就是为了确保读锁重入操作能成功，而不是被塞到阻塞队列中等待\n              *\n              * 另一个信息就是，这里对于 ThreadLocal 变量 readHolds 的处理：\n              *    如果 get() 后发现 count == 0，居然会做 remove() 操作，\n              *    这行代码对于理解其他代码是有帮助的\n              */\n        }\n\n        if (sharedCount(c) == MAX_COUNT)\n            throw new Error(\"Maximum lock count exceeded\");\n\n        if (compareAndSetState(c, c + SHARED_UNIT)) {\n            // 这里 CAS 成功，那么就意味着成功获取读锁了\n            // 下面需要做的是设置 firstReader 或 cachedHoldCounter\n\n            if (sharedCount(c) == 0) {\n                // 如果发现 sharedCount(c) 等于 0，就将当前线程设置为 firstReader\n                firstReader = current;\n                firstReaderHoldCount = 1;\n            } else if (firstReader == current) {\n                firstReaderHoldCount++;\n            } else {\n                // 下面这几行，就是将 cachedHoldCounter 设置为当前线程\n                if (rh == null)\n                    rh = cachedHoldCounter;\n                if (rh == null || rh.tid != getThreadId(current))\n                    rh = readHolds.get();\n                else if (rh.count == 0)\n                    readHolds.set(rh);\n                rh.count++;\n                cachedHoldCounter = rh;\n            }\n            // 返回大于 0 的数，代表获取到了读锁\n            return 1;\n        }\n    }\n}\n```\n\n> firstReader 是每次将**读锁获取次数**从 0 变为 1 的那个线程。\n> \n> 能缓存到 firstReader 中就不要缓存到 cachedHoldCounter 中。\n\n上面的源码分析应该说得非常详细了，如果到这里你不太能看懂上面的有些地方的注释，那么可以先往后看，然后再多看几遍。\n\n### 读锁释放\n\n下面我们看看读锁释放的流程：\n\n```\n// ReadLock\npublic void unlock() {\n    sync.releaseShared(1);\n}\n```\n\n```\n// Sync\npublic final boolean releaseShared(int arg) {\n    if (tryReleaseShared(arg)) {\n        doReleaseShared(); // 这句代码其实唤醒 获取写锁的线程，往下看就知道了\n        return true;\n    }\n    return false;\n}\n\n// Sync\nprotected final boolean tryReleaseShared(int unused) {\n    Thread current = Thread.currentThread();\n    if (firstReader == current) {\n        if (firstReaderHoldCount == 1)\n            // 如果等于 1，那么这次解锁后就不再持有锁了，把 firstReader 置为 null，给后来的线程用\n            // 为什么不顺便设置 firstReaderHoldCount = 0？因为没必要，其他线程使用的时候自己会设值\n            firstReader = null;\n        else\n            firstReaderHoldCount--;\n    } else {\n        // 判断 cachedHoldCounter 是否缓存的是当前线程，不是的话要到 ThreadLocal 中取\n        HoldCounter rh = cachedHoldCounter;\n        if (rh == null || rh.tid != getThreadId(current))\n            rh = readHolds.get();\n\n        int count = rh.count;\n        if (count <= 1) {\n\n            // 这一步将 ThreadLocal remove 掉，防止内存泄漏。因为已经不再持有读锁了\n            readHolds.remove();\n\n            if (count <= 0)\n                // 就是那种，lock() 一次，unlock() 好几次的逗比\n                throw unmatchedUnlockException();\n        }\n        // count 减 1\n        --rh.count;\n    }\n\n    for (;;) {\n        int c = getState();\n        // nextc 是 state 高 16 位减 1 后的值\n        int nextc = c - SHARED_UNIT;\n        if (compareAndSetState(c, nextc))\n            // 如果 nextc == 0，那就是 state 全部 32 位都为 0，也就是读锁和写锁都空了\n            // 此时这里返回 true 的话，其实是帮助唤醒后继节点中的获取写锁的线程\n            return nextc == 0;\n    }\n}\n```\n\n读锁释放的过程还是比较简单的，主要就是将 hold count 减 1，如果减到 0 的话，还要将 ThreadLocal 中的 remove 掉。\n\n然后是在 for 循环中将 state 的高 16 位减 1，如果发现读锁和写锁都释放光了，那么唤醒后继的获取写锁的线程。\n\n### 写锁获取\n\n1.  写锁是独占锁。\n2.  如果有读锁被占用，写锁获取是要进入到阻塞队列中等待的。\n\n```\n// WriteLock\npublic void lock() {\n    sync.acquire(1);\n}\n// AQS\npublic final void acquire(int arg) {\n    if (!tryAcquire(arg) &&\n        // 如果 tryAcquire 失败，那么进入到阻塞队列等待\n        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))\n        selfInterrupt();\n}\n\n// Sync\nprotected final boolean tryAcquire(int acquires) {\n\n    Thread current = Thread.currentThread();\n    int c = getState();\n    int w = exclusiveCount(c);\n    if (c != 0) {\n\n        // 看下这里返回 false 的情况：\n        //   c != 0 && w == 0: 写锁可用，但是有线程持有读锁(也可能是自己持有)\n        //   c != 0 && w !=0 && current != getExclusiveOwnerThread(): 其他线程持有写锁\n        //   也就是说，只要有读锁或写锁被占用，这次就不能获取到写锁\n        if (w == 0 || current != getExclusiveOwnerThread())\n            return false;\n\n        if (w + exclusiveCount(acquires) > MAX_COUNT)\n            throw new Error(\"Maximum lock count exceeded\");\n\n        // 这里不需要 CAS，仔细看就知道了，能到这里的，只可能是写锁重入，不然在上面的 if 就拦截了\n        setState(c + acquires);\n        return true;\n    }\n\n    // 如果写锁获取不需要 block，那么进行 CAS，成功就代表获取到了写锁\n    if (writerShouldBlock() ||\n        !compareAndSetState(c, c + acquires))\n        return false;\n    setExclusiveOwnerThread(current);\n    return true;\n}\n```\n\n下面看一眼**writerShouldBlock()**的判定，然后你再回去看一篇写锁获取过程。\n\n```\nstatic final class NonfairSync extends Sync {\n    // 如果是非公平模式，那么 lock 的时候就可以直接用 CAS 去抢锁，抢不到再排队\n    final boolean writerShouldBlock() {\n        return false; // writers can always barge\n    }\n    ...\n}\nstatic final class FairSync extends Sync {\n    final boolean writerShouldBlock() {\n        // 如果是公平模式，那么如果阻塞队列有线程等待的话，就乖乖去排队\n        return hasQueuedPredecessors();\n    }\n    ...\n}\n```\n\n### 写锁释放\n\n```\n// WriteLock\npublic void unlock() {\n    sync.release(1);\n}\n\n// AQS\npublic final boolean release(int arg) {\n    // 1\\. 释放锁\n    if (tryRelease(arg)) {\n        // 2\\. 如果独占锁释放\"完全\"，唤醒后继节点\n        Node h = head;\n        if (h != null && h.waitStatus != 0)\n            unparkSuccessor(h);\n        return true;\n    }\n    return false;\n}\n\n// Sync \n// 释放锁，是线程安全的，因为写锁是独占锁，具有排他性\n// 实现很简单，state 减 1 就是了\nprotected final boolean tryRelease(int releases) {\n    if (!isHeldExclusively())\n        throw new IllegalMonitorStateException();\n    int nextc = getState() - releases;\n    boolean free = exclusiveCount(nextc) == 0;\n    if (free)\n        setExclusiveOwnerThread(null);\n    setState(nextc);\n    // 如果 exclusiveCount(nextc) == 0，也就是说包括重入的，所有的写锁都释放了，\n    // 那么返回 true，这样会进行唤醒后继节点的操作。\n    return free;\n}\n```\n\n看到这里，是不是发现写锁相对于读锁来说要简单很多。\n\n## 锁降级\n\nDoug Lea 没有说写锁更**高级**，如果有线程持有读锁，那么写锁获取也需要等待。\n\n不过从源码中也可以看出，确实会给写锁一些特殊照顾，如非公平模式下，为了提高吞吐量，lock 的时候会先 CAS 竞争一下，能成功就代表读锁获取成功了，但是如果发现 head.next 是获取写锁的线程，就不会去做 CAS 操作。\n\nDoug Lea 将持有写锁的线程，去获取读锁的过程称为**锁降级（Lock downgrading）**。这样，此线程就既持有写锁又持有读锁。\n\n但是，**锁升级**是不可以的。线程持有读锁的话，在没释放的情况下不能去获取写锁，因为会发生**死锁**。\n\n回去看下写锁获取的源码：\n\n```\nprotected final boolean tryAcquire(int acquires) {\n\n    Thread current = Thread.currentThread();\n    int c = getState();\n    int w = exclusiveCount(c);\n    if (c != 0) {\n        // 看下这里返回 false 的情况：\n        //   c != 0 && w == 0: 写锁可用，但是有线程持有读锁(也可能是自己持有)\n        //   c != 0 && w !=0 && current != getExclusiveOwnerThread(): 其他线程持有写锁\n        //   也就是说，只要有读锁或写锁被占用，这次就不能获取到写锁\n        if (w == 0 || current != getExclusiveOwnerThread())\n            return false;\n        ...\n    }\n    ...\n}\n```\n\n仔细想想，如果线程 a 先获取了读锁，然后获取写锁，那么线程 a 就到阻塞队列休眠了，自己把自己弄休眠了，而且可能之后就没人去唤醒它了。\n\n## 总结\n\n![14](https://www.javadoop.com/blogimages/reentrant-read-write-lock/14.png)\n\n（全文完）\n"
  },
  {
    "path": "docs/Java/concurrency/Java并发指南：并发三大问题与volatile关键字，CAS操作.md",
    "content": "# 目录\n  * [序言](#序言)\n  * [原子性](#原子性)\n  * [可见性](#可见性)\n  * [有序性](#有序性)\n  * [volatile关键字详解：在JMM中volatile的内存语义是锁](#volatile关键字详解：在jmm中volatile的内存语义是锁)\n    * [volatile的特性](#volatile的特性)\n  * [volatile写-读建立的happens before关系](#volatile写-读建立的happens-before关系)\n    * [volatile写-读的内存语义](#volatile写-读的内存语义)\n    * [volatile内存语义的实现](#volatile内存语义的实现)\n    * [JSR-133为什么要增强volatile的内存语义](#jsr-133为什么要增强volatile的内存语义)\n    * [引言](#引言)\n    * [术语定义](#术语定义)\n    * [3 处理器如何实现原子操作](#3-处理器如何实现原子操作)\n      * [3.1 处理器自动保证基本内存操作的原子性](#31-处理器自动保证基本内存操作的原子性)\n      * [3.2 使用总线锁保证原子性](#32-使用总线锁保证原子性)\n      * [3.3 使用缓存锁保证原子性](#33-使用缓存锁保证原子性)\n    * [4 JAVA如何实现原子操作](#4-java如何实现原子操作)\n  * [4.1 使用循环CAS实现原子操作](#41-使用循环cas实现原子操作)\n      * [4.2 使用锁机制实现原子操作](#42-使用锁机制实现原子操作)\n  * [5 参考资料](#5-参考资料)\n\n\n**本文转载自互联网，侵删**\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章同步发于我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《Java并发指南》其中一篇，本文大部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何全面深入地学习Java并发技术，从Java多线程基础，再到并发编程的基础知识，从Java并发包的入门和实战，再到JUC的源码剖析，一步步地学习Java并发编程，并上手进行实战，以便让你更完整地了解整个Java并发编程知识体系，形成自己的知识框架。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供一些对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n<!--more -->\n\n## 序言\n\n先来看如下这个简单的Java类，该类中并没有使用任何的同步。\n\n````\nfinal class SetCheck {\n    private int a = 0;\n    private long b = 0;\n \n    void set() {\n        a = 1;\n        b = -1;\n    }\n \n    boolean check() {\n        return ((b == 0) || (b == -1 && a == 1));\n    }\n}\n    \n````     \n\n如果是在一个串行执行的语言中，执行SetCheck类中的check方法永远不会返回false，即使编译器，运行时和计算机硬件并没有按照你所期望的逻辑来处理这段程序，该方法依然不会返回false。在程序执行过程中，下面这些你所不能预料的行为都是可能发生的：\n\n*   编译器可能会进行指令重排序，所以b变量的赋值操作可能先于a变量。如果是一个内联方法，编译器可能更甚一步将该方法的指令与其他语句进行重排序。\n*   处理器可能会对语句所对应的机器指令进行重排序之后再执行，甚至并发地去执行。\n*   内存系统（由高速缓存控制单元组成）可能会对变量所对应的内存单元的写操作指令进行重排序。重排之后的写操作可能会对其他的计算/内存操作造成覆盖。\n*   编译器，处理器以及内存系统可能会让两条语句的机器指令交错。比如在32位机器上，b变量的高位字节先被写入，然后是a变量，紧接着才会是b变量的低位字节。\n*   编译器，处理器以及内存系统可能会导致代表两个变量的内存单元在（如果有的话）连续的check调用（如果有的话）之后的某个时刻才更新，而以这种方式保存相应的值（如在CPU寄存器中）仍会得到预期的结果（check永远不会返回false）。\n\n在串行执行的语言中，只要程序执行遵循类似串行的语义，如上几种行为就不会有任何的影响。在一段简单的代码块中，串行执行程序不会依赖于代码的内部执行细节，因此如上的几种行为可以随意控制代码。\n\n这样就为编译器和计算机硬件提供了基本的灵活性。基于此，在过去的数十年内很多技术（CPU的流水线操作，多级缓存，读写平衡，寄存器分配等等）应运而生，为计算机处理速度的大幅提升奠定了基础。这些操作的类似串行执行的特性可以让开发人员无须知道其内部发生了什么。对于开发人员来说，如果不创建自己的线程，那么这些行为也不会对其产生任何的影响。\n\n然而这些情况在并发编程中就完全不一样了，上面的代码在并发过程中，当一个线程调用check方法的时候完全有可能另一个线程正在执行set方法，这种情况下check方法就会将上面提到的优化操作过程暴露出来。\n\n如果上述任意一个操作发生，那么check方法就有可能返回false。例如，check方法读取long类型的变量b的时候可能得到的既不是0也不是-1.而是一个被写入一半的值。另一种情况，set方法中的语句的乱序执行有可能导致check方法读取变量b的值的时候是-1，然而读取变量a时却依然是0。\n\n换句话说，不仅是并发执行会导致问题，而且在一些优化操作（比如指令重排序）进行之后也会导致代码执行结果和源代码中的逻辑有所出入。由于编译器和运行时技术的日趋成熟以及多处理器的逐渐普及，这种现象就变得越来越普遍。\n\n对于那些一直从事串行编程背景的开发人员（其实，基本上所有的程序员）来说，这可能会导致令人诧异的结果，而这些结果可能从没在串行编程中出现过。这可能就是那些微妙难解的并发编程错误的根本源头吧。\n\n在绝大部分的情况下，有一个很简单易行的方法来避免那些在复杂的并发程序中因代码执行优化导致的问题：使用同步。例如，如果SetCheck类中所有的方法都被声明为synchronized,那么你就可以确保那么内部处理细节都不会影响代码预期的结果了。\n\n但是在有些情况下你却不能或者不想去使用同步，抑或着你需要推断别人未使用同步的代码。在这些情况下你只能依赖Java内存模型所阐述的结果语义所提供的最小保证。Java内存模型允许上面提到的所有操作，但是限制了它们在执行语义上潜在的结果，此外还提出了一些技术让程序员可以用来控制这些语义的某些方面。\n\nJava内存模型是Java语言规范的一部分，主要在JLS的第17章节介绍。这里，我们只是讨论一些基本的动机，属性以及模型的程序一致性。这里对JLS第一版中所缺少的部分进行了澄清。\n\n我们假设Java内存模型可以被看作在1.2.4中描述的那种标准的SMP机器的理想化模型。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404190605.png)\n\n（1.2.4）\n\n在这个模型中，每一个线程都可以被看作为运行在不同的CPU上，然而即使是在多处理器上，这种情况也是很罕见的。但是实际上，通过模型所具备的某些特性，这种CPU和线程单一映射能够通过一些合理的方法去实现。例如，因为CPU的寄存器不能被另一个CPU直接访问，这种模型必须考虑到某个线程无法得知被另一个线程操作变量的值的情况。这种情况不仅仅存在于多处理器环境上，在单核CPU环境里，因为编译器和处理器的不可预测的行为也可能导致同样的情况。\n\nJava内存模型没有具体讲述前面讨论的执行策略是由编译器，CPU，缓存控制器还是其它机制促成的。甚至没有用开发人员所熟悉的类，对象及方法来讨论。取而代之，Java内存模型中仅仅定义了线程和内存之间那种抽象的关系。众所周知，每个线程都拥有自己的工作存储单元（缓存和寄存器的抽象）来存储线程当前使用的变量的值。Java内存模型仅仅保证了代码指令与变量操作的有序性，大多数规则都只是指出什么时候变量值应该在内存和线程工作内存之间传输。这些规则主要是为了解决如下三个相互牵连的问题：\n\n1.  原子性：哪些指令必须是不可分割的。在Java内存模型中，这些规则需声明仅适用于-—实例变量和静态变量，也包括数组元素，但不包括方法中的局部变量-—的内存单元的简单读写操作。\n2.  可见性：在哪些情况下，一个线程执行的结果对另一个线程是可见的。这里需要关心的结果有，写入的字段以及读取这个字段所看到的值。\n3.  有序性：在什么情况下，某个线程的操作结果对其它线程来看是无序的。最主要的乱序执行问题主要表现在读写操作和赋值语句的相互执行顺序上。\n\n## 原子性\n\n当正确的使用了同步，上面属性都会具有一个简单的特性：一个同步方法或者代码块中所做的修改对于使用了同一个锁的同步方法或代码块都具有原子性和可见性。同步方法或代码块之间的执行过程都会和代码指定的执行顺序保持一致。即使代码块内部指令也许是乱序执行的，也不会对使用了同步的其它线程造成任何影响。\n\n当没有使用同步或者使用的不一致的时候，情况就会变得复杂。Java内存模型所提供的保障要比大多数开发人员所期望的弱，也远不及目前业界所实现的任意一款Java虚拟机。这样，开发人员就必须负起额外的义务去保证对象的一致性关系：对象间若有能被多个线程看到的某种恒定关系，所有依赖这种关系的线程就必须一直维持这种关系，而不仅仅由执行状态修改的线程来维持。\n\n除了long型字段和double型字段外，java内存模型确保访问任意类型字段所对应的内存单元都是原子的。这包括引用其它对象的引用类型的字段。此外，volatile long 和volatile double也具有原子性 。（虽然java内存模型不保证non-volatile long 和 non-volatile double的原子性，当然它们在某些场合也具有原子性。）（译注：non-volatile long在64位JVM，OS，CPU下具有原子性）\n\n当在一个表达式中使用一个non-long或者non-double型字段时，原子性可以确保你将获得这个字段的初始值或者某个线程对这个字段写入之后的值；但不会是两个或更多线程在同一时间对这个字段写入之后产生混乱的结果值（即原子性可以确保，获取到的结果值所对应的所有bit位，全部都是由单个线程写入的）。但是，如下面（译注：指可见性章节）将要看到的，原子性不能确保你获得的是任意线程写入之后的最新值。 因此，原子性保证通常对并发程序设计的影响很小。\n\n## 可见性\n\n只有在下列情况时，一个线程对字段的修改才能确保对另一个线程可见：\n\n一个写线程释放一个锁之后，另一个读线程随后获取了同一个锁。本质上，线程释放锁时会将强制刷新工作内存中的脏数据到主内存中，获取一个锁将强制线程装载（或重新装载）字段的值。锁提供对一个同步方法或块的互斥性执行，线程执行获取锁和释放锁时，所有对字段的访问的内存效果都是已定义的。\n\n注意同步的双重含义：锁提供高级同步协议，同时在线程执行同步方法或块时，内存系统（有时通过内存屏障指令）保证值的一致性。这说明，与顺序程序设计相比较，并发程序设计与分布式程序设计更加类似。同步的第二个特性可以视为一种机制：一个线程在运行已同步方法时，它将发送和/或接收其他线程在同步方法中对变量所做的修改。从这一点来说，使用锁和发送消息仅仅是语法不同而已。\n\n如果把一个字段声明为volatile型，线程对这个字段写入后，在执行后续的内存访问之前，线程必须刷新这个字段且让这个字段对其他线程可见（即该字段立即刷新）。每次对volatile字段的读访问，都要重新装载字段的值。\n\n一个线程首次访问一个对象的字段，它将读到这个字段的初始值或被某个线程写入后的值。\n此外，把还未构造完成的对象的引用暴露给某个线程，这是一个错误的做法，在构造函数内部开始一个新线程也是危险的，特别是这个类可能被子类化时。Thread.start有如下的内存效果：调用start方法的线程释放了锁，随后开始执行的新线程获取了这个锁。\n\n如果在子类构造函数执行之前，可运行的超类调用了new Thread(this).start()，当run方法执行时，对象很可能还没有完全初始化。同样，如果你创建且开始一个新线程T，这个线程使用了在执行start之后才创建的一个对象X。你不能确信X的字段值将能对线程T可见。除非你把所有用到X的引用的方法都同步。如果可行的话，你可以在开始T线程之前创建X。\n\n线程终止时，所有写过的变量值都要刷新到主内存中。比如，一个线程使用Thread.join来终止另一个线程，那么第一个线程肯定能看到第二个线程对变量值得修改。\n\n注意，在同一个线程的不同方法之间传递对象的引用，永远也不会出现内存可见性问题。\n\n内存模型确保上述操作最终会发生，一个线程对一个特定字段的特定更新，最终将会对其他线程可见，但这个“最终”可能是很长一段时间。线程之间没有同步时，很难保证对字段的值能在多线程之间保持一致（指写线程对字段的写入立即能对读线程可见）。\n\n特别是，如果字段不是volatile或没有通过同步来访问这个字段，在一个循环中等待其他线程对这个字段的写入，这种情况总是错误的。\n\n在缺乏同步的情况下，模型还允许不一致的可见性。比如，得到一个对象的一个字段的最新值，同时得到这个对象的其他字段的过期的值。同样，可能读到一个引用变量的最新值，但读取到这个引用变量引用的对象的字段的过期值。\n不管怎样，线程之间的可见性并不总是失效（指线程即使没有使用同步，仍然有可能读取到字段的最新值），内存模型仅仅是允许这种失效发生而已。因此，即使多个线程之间没有使用同步，也不保证一定会发生内存可见性问题（指线程读取到过期的值），java内存模型仅仅是允许内存可见性问题发生而已。\n\n在很多当前的JVM实现和java执行平台中，甚至是在那些使用多处理器的JVM和平台中，也很少出现内存可见性问题。共享同一个CPU的多个线程使用公共的缓存，缺少强大的编译器优化，以及存在强缓存一致性的硬件，这些都会使线程更新后的值能够立即在多线程之间传递。\n\n这使得测试基于内存可见性的错误是不切实际的，因为这样的错误极难发生。或者这种错误仅仅在某个你没有使用过的平台上发生，或仅在未来的某个平台上发生。这些类似的解释对于多线程之间的内存可见性问题来说非常普遍。没有同步的并发程序会出现很多问题，包括内存一致性问题。\n\n## 有序性\n\n有序性规则表现在以下两种场景:线程内和线程间\n\n*   从某个线程的角度看方法的执行，指令会按照一种叫“串行”（as-if-serial）的方式执行，此种方式已经应用于顺序编程语言。\n*   这个线程“观察”到其他线程并发地执行非同步的代码时，任何代码都有可能交叉执行。唯一起作用的约束是：对于同步方法，同步块以及volatile字段的操作仍维持相对有序。\n\n再次提醒，这些仅是最小特性的规则。具体到任何一个程序或平台上，可能存在更严格的有序性规则。所以你不能依赖它们，因为即使你的代码遵循了这些更严格的规则，仍可能在不同特性的JVM上运行失败，而且测试非常困难。\n\n需要注意的是，线程内部的观察视角被JLS_[1]_中其他的语义的讨论所采用。例如，算术表达式的计算在线程内看来是从左到右地执行操作（JLS15.6章节），而这种执行效果是没有必要被其他线程观察到的。\n\n仅当某一时刻只有一个线程操作变量时，线程内的执行表现为串行。出现上述情景，可能是因为使用了同步，互斥体_[2]_或者纯属巧合。当多线程同时运行在非同步的代码里进行公用字段的读写时，会形成一种执行模式。在这种模式下，代码会任意交叉执行，原子性和可见性会失效，以及产生竞态条件。这时线程执行不再表现为串行。\n\n尽管JLS列出了一些特定的合法和非法的重排序，如果碰到所列范围之外的问题，会降低以下这条实践保证：运行结果反映了几乎所有的重排序产生的代码交叉执行的情况。所以，没必要去探究这些代码的有序性。\n\n## volatile关键字详解：在JMM中volatile的内存语义是锁\n\n### volatile的特性\n\n当我们声明共享变量为volatile后，对这个变量的读/写将会很特别。理解volatile特性的一个好方法是：把对volatile变量的单个读/写，看成是使用同一个监视器锁对这些单个读/写操作做了同步。下面我们通过具体的示例来说明，请看下面的示例代码：\n````\nclass VolatileFeaturesExample {\n    volatile long vl = 0L; // 使用volatile声明64位的long型变量\n \n    public void set(long l) {\n        vl = l; // 单个volatile变量的写\n    }\n \n    public void getAndIncrement() {\n        vl++; // 复合（多个）volatile变量的读/写\n    }\n \n    public long get() {\n        return vl; // 单个volatile变量的读\n    }\n}\n````\n\n    \n\n假设有多个线程分别调用上面程序的三个方法，这个程序在语意上和下面程序等价：\n````\nclass VolatileFeaturesExample {\n    long vl = 0L;               // 64位的long型普通变量\n \n    public synchronized void set(long l) {     //对单个的普通 变量的写用同一个监视器同步\n        vl = l;\n    }\n \n    public void getAndIncrement () { //普通方法调用\n        long temp = get();           //调用已同步的读方法\n        temp += 1L;                  //普通写操作\n        set(temp);                   //调用已同步的写方法\n    }\n    public synchronized long get() { \n    //对单个的普通变量的读用同一个监视器同步\n        return vl;\n    }\n}\n````\n\n    \n\n如上面示例程序所示，对一个volatile变量的单个读/写操作，与对一个普通变量的读/写操作使用同一个监视器锁来同步，它们之间的执行效果相同。\n\n监视器锁的happens-before规则保证释放监视器和获取监视器的两个线程之间的内存可见性，这意味着对一个volatile变量的读，总是能看到（任意线程）对这个volatile变量最后的写入。\n\n简而言之，volatile变量自身具有下列特性：监视器锁的语义决定了临界区代码的执行具有原子性。这意味着即使是64位的long型和double型变量，只要它是volatile变量，对该变量的读写就将具有原子性。如果是多个volatile操作或类似于volatile++这种复合操作，这些操作整体上不具有原子性。\n\n*   可见性。对一个volatile变量的读，总是能看到（任意线程）对这个volatile变量最后的写入。\n*   原子性：对任意单个volatile变量的读/写具有原子性，但类似于volatile++这种复合操作不具有原子性。\n\n## volatile写-读建立的happens before关系\n\n上面讲的是volatile变量自身的特性，对程序员来说，volatile对线程的内存可见性的影响比volatile自身的特性更为重要，也更需要我们去关注。\n\n从JSR-133开始，volatile变量的写-读可以实现线程之间的通信。\n\n从内存语义的角度来说，volatile与监视器锁有相同的效果：volatile写和监视器的释放有相同的内存语义；volatile读与监视器的获取有相同的内存语义。\n\n请看下面使用volatile变量的示例代码：\n````\nclass VolatileExample {\n\tint a = 0;\n\tvolatile boolean flag = false;\n \n\tpublic void writer() {\n\t\ta = 1; // 1\n\t\tflag = true; // 2\n\t}\n\tpublic void reader() {\n        if (flag) {                //3\n            int i =  a;           //4\n            ……\n        }\n    }\n}\n````\n\n假设线程A执行writer()方法之后，线程B执行reader()方法。根据happens before规则，这个过程建立的happens before 关系可以分为两类：\n\n1.  根据程序次序规则，1 happens before 2; 3 happens before 4。\n2.  根据volatile规则，2 happens before 3。\n3.  根据happens before 的传递性规则，1 happens before 4。\n\n这里A线程写一个volatile变量后，B线程读同一个volatile变量。A线程在写volatile变量之前所有可见的共享变量，在B线程读同一个volatile变量后，将立即变得对B线程可见。\n\n### volatile写-读的内存语义\n\nvolatile写的内存语义如下：\n\n当写一个volatile变量时，JMM会把该线程对应的本地内存中的共享变量刷新到主内存。\n\n以上面示例程序VolatileExample为例，假设线程A首先执行writer()方法，随后线程B执行reader()方法，初始时两个线程的本地内存中的flag和a都是初始状态。下图是线程A执行volatile写后，共享变量的状态示意图：\n\n![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9UTEgzQ2ljUFZpYnJlMXJ3emFJWjJkT3VVdDVjekVBdVQ3dmRtZ0NFNjBKeTlRODRsZ0VwdzNqaWFCREJmSjE2TkxZUEQ2a1dNeHBnOTNad0tUOVY2Ym1Ydy82NDA?x-oss-process=image/format,png)\n\n如上图所示，线程A在写flag变量后，本地内存A中被线程A更新过的两个共享变量的值被刷新到主内存中。此时，本地内存A和主内存中的共享变量的值是一致的。\n\nvolatile读的内存语义如下：\n\n*   当读一个volatile变量时，JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。\n\n下面是线程B读同一个volatile变量后，共享变量的状态示意图：\n\n![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9UTEgzQ2ljUFZpYnJlMXJ3emFJWjJkT3VVdDVjekVBdVQ3WWcwMEt1bGVicDAza0MyU2ZHWmI3Tm5vaDcxaG5JS3dpYW5qcnJsUzBUN3dNZWljWUJySmY4YVEvNjQw?x-oss-process=image/format,png)\n\n如上图所示，在读flag变量后，本地内存B已经被置为无效。此时，线程B必须从主内存中读取共享变量。线程B的读取操作将导致本地内存B与主内存中的共享变量的值也变成一致的了。\n\n如果我们把volatile写和volatile读这两个步骤综合起来看的话，在读线程B读一个volatile变量后，写线程A在写这个volatile变量之前所有可见的共享变量的值都将立即变得对读线程B可见。\n\n下面对volatile写和volatile读的内存语义做个总结：\n\n*   线程A写一个volatile变量，实质上是线程A向接下来将要读这个volatile变量的某个线程发出了（其对共享变量所在修改的）消息。\n*   线程B读一个volatile变量，实质上是线程B接收了之前某个线程发出的（在写这个volatile变量之前对共享变量所做修改的）消息。\n*   线程A写一个volatile变量，随后线程B读这个volatile变量，这个过程实质上是线程A通过主内存向线程B发送消息。\n\n### volatile内存语义的实现\n\n下面，让我们来看看JMM如何实现volatile写/读的内存语义。\n\n前文我们提到过重排序分为编译器重排序和处理器重排序。为了实现volatile内存语义，JMM会分别限制这两种类型的重排序类型。下面是JMM针对编译器制定的volatile重排序规则表：\n\n\n\n| 是否能重排序 | 第二个操作 |\n| --- | --- |\n| 第一个操作 | 普通读/写 | volatile读 | volatile写 |\n| 普通读/写 |  |  | NO |\n| volatile读 | NO | NO | NO |\n| volatile写 |  | NO | NO |\n\n\n\n举例来说，第三行最后一个单元格的意思是：在程序顺序中，当第一个操作为普通变量的读或写时，如果第二个操作为volatile写，则编译器不能重排序这两个操作。\n\n从上表我们可以看出：\n\n*   当第二个操作是volatile写时，不管第一个操作是什么，都不能重排序。这个规则确保volatile写之前的操作不会被编译器重排序到volatile写之后。\n*   当第一个操作是volatile读时，不管第二个操作是什么，都不能重排序。这个规则确保volatile读之后的操作不会被编译器重排序到volatile读之前。\n*   当第一个操作是volatile写，第二个操作是volatile读时，不能重排序。\n\n为了实现volatile的内存语义，编译器在生成字节码时，会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。对于编译器来说，发现一个最优布置来最小化插入屏障的总数几乎不可能，为此，JMM采取保守策略。下面是基于保守策略的JMM内存屏障插入策略：\n\n*   在每个volatile写操作的前面插入一个StoreStore屏障。\n*   在每个volatile写操作的后面插入一个StoreLoad屏障。\n*   在每个volatile读操作的后面插入一个LoadLoad屏障。\n*   在每个volatile读操作的后面插入一个LoadStore屏障。\n\n上述内存屏障插入策略非常保守，但它可以保证在任意处理器平台，任意的程序中都能得到正确的volatile内存语义。\n\n下面是保守策略下，volatile写插入内存屏障后生成的指令序列示意图：\n\n![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9UTEgzQ2ljUFZpYnJlMXJ3emFJWjJkT3VVdDVjekVBdVQ3U2dxcTJ0SnF1N1pwQllpYVZmZnpTd3F5NGhDY1dSWVhNVzlzM0VWSmljRFZzODY5cWc1M1A3eEEvNjQw?x-oss-process=image/format,png)\n\n上图中的StoreStore屏障可以保证在volatile写之前，其前面的所有普通写操作已经对任意处理器可见了。这是因为StoreStore屏障将保障上面所有的普通写在volatile写之前刷新到主内存。\n\n这里比较有意思的是volatile写后面的StoreLoad屏障。这个屏障的作用是避免volatile写与后面可能有的volatile读/写操作重排序。因为编译器常常无法准确判断在一个volatile写的后面，是否需要插入一个StoreLoad屏障（比如，一个volatile写之后方法立即return）。为了保证能正确实现volatile的内存语义，JMM在这里采取了保守策略：在每个volatile写的后面或在每个volatile读的前面插入一个StoreLoad屏障。从整体执行效率的角度考虑，JMM选择了在每个volatile写的后面插入一个StoreLoad屏障。因为volatile写-读内存语义的常见使用模式是：一个写线程写volatile变量，多个读线程读同一个volatile变量。当读线程的数量大大超过写线程时，选择在volatile写之后插入StoreLoad屏障将带来可观的执行效率的提升。从这里我们可以看到JMM在实现上的一个特点：首先确保正确性，然后再去追求执行效率。\n\n下面是在保守策略下，volatile读插入内存屏障后生成的指令序列示意图：\n\n![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9UTEgzQ2ljUFZpYnJlMXJ3emFJWjJkT3VVdDVjekVBdVQ3U2dxcTJ0SnF1N1pwQllpYVZmZnpTd3F5NGhDY1dSWVhNVzlzM0VWSmljRFZzODY5cWc1M1A3eEEvNjQw?x-oss-process=image/format,png)\n\n上图中的LoadLoad屏障用来禁止处理器把上面的volatile读与下面的普通读重排序。LoadStore屏障用来禁止处理器把上面的volatile读与下面的普通写重排序。\n\n上述volatile写和volatile读的内存屏障插入策略非常保守。在实际执行时，只要不改变volatile写-读的内存语义，编译器可以根据具体情况省略不必要的屏障。下面我们通过具体的示例代码来说明：\n````\nclass VolatileBarrierExample {\n    int a;\n    volatile int v1 = 1;\n    volatile int v2 = 2;\n \n    void readAndWrite() {\n        int i = v1; // 第一个volatile读\n        int j = v2; // 第二个volatile读\n        a = i + j; // 普通写\n        v1 = i + 1; // 第一个volatile写\n        v2 = j * 2; // 第二个 volatile写\n    }\n}\n````\n    \n\n针对readAndWrite()方法，编译器在生成字节码时可以做如下的优化：\n\n![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9UTEgzQ2ljUFZpYnJlMXJ3emFJWjJkT3VVdDVjekVBdVQ3RzlISFVoNTh0MHNCVUZwMEJyZVU4Njc0OGZHMFhKdmxRTXFpYXE3dUhRNklGd1Q5a1pZQktjZy82NDA?x-oss-process=image/format,png)\n\n注意，最后的StoreLoad屏障不能省略。因为第二个volatile写之后，方法立即return。此时编译器可能无法准确断定后面是否会有volatile读或写，为了安全起见，编译器常常会在这里插入一个StoreLoad屏障。\n\n上面的优化是针对任意处理器平台，由于不同的处理器有不同“松紧度”的处理器内存模型，内存屏障的插入还可以根据具体的处理器内存模型继续优化。以x86处理器为例，上图中除最后的StoreLoad屏障外，其它的屏障都会被省略。\n\n前面保守策略下的volatile读和写，在 x86处理器平台可以优化成：\n\n![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9UTEgzQ2ljUFZpYnJlMXJ3emFJWjJkT3VVdDVjekVBdVQ3aWNaU1h6dURzT2pnZ0hBTmljYnljbTNPYjkwbkVVOURCTm1JaHE1Y2NVdTZMUnZxS3V1cmlhQ29nLzY0MA?x-oss-process=image/format,png)\n\n前文提到过，x86处理器仅会对写-读操作做重排序。X86不会对读-读，读-写和写-写操作做重排序，因此在x86处理器中会省略掉这三种操作类型对应的内存屏障。在x86中，JMM仅需在volatile写后面插入一个StoreLoad屏障即可正确实现volatile写-读的内存语义。这意味着在x86处理器中，volatile写的开销比volatile读的开销会大很多（因为执行StoreLoad屏障开销会比较大）。\n\n### JSR-133为什么要增强volatile的内存语义\n\n在JSR-133之前的旧Java内存模型中，虽然不允许volatile变量之间重排序，但旧的Java内存模型允许volatile变量与普通变量之间重排序。在旧的内存模型中，VolatileExample示例程序可能被重排序成下列时序来执行：\n\n![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9UTEgzQ2ljUFZpYnJlMXJ3emFJWjJkT3VVdDVjekVBdVQ3TG9aamN2em13WUk3aGVJcksyemp2YWs5RGI5cHZKaFJ3NVR0cHFGMzNWUjFHb0tzd1lwNUdnLzY0MA?x-oss-process=image/format,png)\n\n在旧的内存模型中，当1和2之间没有数据依赖关系时，1和2之间就可能被重排序（3和4类似）。其结果就是：读线程B执行4时，不一定能看到写线程A在执行1时对共享变量的修改。\n\n因此在旧的内存模型中 ，volatile的写-读没有监视器的释放-获所具有的内存语义。为了提供一种比监视器锁更轻量级的线程之间通信的机制，JSR-133专家组决定增强volatile的内存语义：严格限制编译器和处理器对volatile变量与普通变量的重排序，确保volatile的写-读和监视器的释放-获取一样，具有相同的内存语义。从编译器重排序规则和处理器内存屏障插入策略来看，只要volatile变量与普通变量之间的重排序可能会破坏volatile的内存语意，这种重排序就会被编译器重排序规则和处理器内存屏障插入策略禁止。\n\n由于volatile仅仅保证对单个volatile变量的读/写具有原子性，而监视器锁的互斥执行的特性可以确保对整个临界区代码的执行具有原子性。在功能上，监视器锁比volatile更强大；在可伸缩性和执行性能上，volatile更有优势。如果读者想在程序中用volatile代替监视器锁，请一定谨慎。\n\n##CAS操作详解\n\n本文属于作者原创，原文发表于InfoQ：[http://www.infoq.com/cn/articles/atomic-operation](http://www.infoq.com/cn/articles/atomic-operation)\n\n### 引言\n\n原子（atom）本意是“不能被进一步分割的最小粒子”，而原子操作（atomic operation）意为”不可被中断的一个或一系列操作” 。在多处理器上实现原子操作就变得有点复杂。本文让我们一起来聊一聊在Inter处理器和Java里是如何实现原子操作的。\n\n### 术语定义\n\n| 术语名称 | 英文 | 解释 |\n| --- | --- | --- |\n| 缓存行 | Cache line | 缓存的最小操作单位 |\n| 比较并交换 | Compare and Swap | CAS操作需要输入两个数值，一个旧值（期望操作前的值）和一个新值，在操作期间先比较下在旧值有没有发生变化，如果没有发生变化，才交换成新值，发生了变化则不交换。 |\n| CPU流水线 | CPU pipeline | CPU流水线的工作方式就象工业生产上的装配流水线，在CPU中由5~6个不同功能的电路单元组成一条指令处理流水线，然后将一条X86指令分成5~6步后再由这些电路单元分别执行，这样就能实现在一个CPU时钟周期完成一条指令，因此提高CPU的运算速度。 |\n| 内存顺序冲突 | Memory order violation | 内存顺序冲突一般是由假共享引起，假共享是指多个CPU同时修改同一个缓存行的不同部分而引起其中一个CPU的操作无效，当出现这个内存顺序冲突时，CPU必须清空流水线。 |\n\n\n### 3 处理器如何实现原子操作\n\n32位IA-32处理器使用基于对缓存加锁或总线加锁的方式来实现多处理器之间的原子操作。\n\n#### 3.1 处理器自动保证基本内存操作的原子性\n\n首先处理器会自动保证基本的内存操作的原子性。处理器保证从系统内存当中读取或者写入一个字节是原子的，意思是当一个处理器读取一个字节时，其他处理器不能访问这个字节的内存地址。奔腾6和最新的处理器能自动保证单处理器对同一个缓存行里进行16/32/64位的操作是原子的，但是复杂的内存操作处理器不能自动保证其原子性，比如跨总线宽度，跨多个缓存行，跨页表的访问。但是处理器提供总线锁定和缓存锁定两个机制来保证复杂内存操作的原子性。\n\n#### 3.2 使用总线锁保证原子性\n\n第一个机制是通过总线锁保证原子性。如果多个处理器同时对共享变量进行读改写（i++就是经典的读改写操作）操作，那么共享变量就会被多个处理器同时进行操作，这样读改写操作就不是原子的，操作完之后共享变量的值会和期望的不一致，举个例子：如果i=1,我们进行两次i++操作，我们期望的结果是3，但是有可能结果是2。如下图\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404191936.png)\n（例1）\n\n原因是有可能多个处理器同时从各自的缓存中读取变量i，分别进行加一操作，然后分别写入系统内存当中。那么想要保证读改写共享变量的操作是原子的，就必须保证CPU1读改写共享变量的时候，CPU2不能操作缓存了该共享变量内存地址的缓存。\n\n处理器使用总线锁就是来解决这个问题的。所谓总线锁就是使用处理器提供的一个LOCK＃信号，当一个处理器在总线上输出此信号时，其他处理器的请求将被阻塞住,那么该处理器可以独占使用共享内存。\n\n#### 3.3 使用缓存锁保证原子性\n\n第二个机制是通过缓存锁定保证原子性。在同一时刻我们只需保证对某个内存地址的操作是原子性即可，但总线锁定把CPU和内存之间通信锁住了，这使得锁定期间，其他处理器不能操作其他内存地址的数据，所以总线锁定的开销比较大，最近的处理器在某些场合下使用缓存锁定代替总线锁定来进行优化。\n\n频繁使用的内存会缓存在处理器的L1，L2和L3高速缓存里，那么原子操作就可以直接在处理器内部缓存中进行，并不需要声明总线锁，在奔腾6和最近的处理器中可以使用“缓存锁定”的方式来实现复杂的原子性。所谓“缓存锁定”就是如果缓存在处理器缓存行中内存区域在LOCK操作期间被锁定，当它执行锁操作回写内存时，处理器不在总线上声言LOCK＃信号，而是修改内部的内存地址，并允许它的缓存一致性机制来保证操作的原子性，因为缓存一致性机制会阻止同时修改被两个以上处理器缓存的内存区域数据，当其他处理器回写已被锁定的缓存行的数据时会起缓存行无效，在例1中，当CPU1修改缓存行中的i时使用缓存锁定，那么CPU2就不能同时缓存了i的缓存行。\n\n但是有两种情况下处理器不会使用缓存锁定。第一种情况是：当操作的数据不能被缓存在处理器内部，或操作的数据跨多个缓存行（cache line），则处理器会调用总线锁定。第二种情况是：有些处理器不支持缓存锁定。对于Inter486和奔腾处理器,就算锁定的内存区域在处理器的缓存行中也会调用总线锁定。\n\n以上两个机制我们可以通过Inter处理器提供了很多LOCK前缀的指令来实现。比如位测试和修改指令BTS，BTR，BTC，交换指令XADD，CMPXCHG和其他一些操作数和逻辑指令，比如ADD（加），OR（或）等，被这些指令操作的内存区域就会加锁，导致其他处理器不能同时访问它。\n\n### 4 JAVA如何实现原子操作\n\n在java中可以通过锁和循环CAS的方式来实现原子操作。\n\n## 4.1 使用循环CAS实现原子操作\n\nJVM中的CAS操作正是利用了上一节中提到的处理器提供的CMPXCHG指令实现的。自旋CAS实现的基本思路就是循环进行CAS操作直到成功为止，以下代码实现了一个基于CAS线程安全的计数器方法safeCount和一个非线程安全的计数器count。\n\n````\npackage Test;\n \nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.atomic.AtomicInteger;\n \npublic class Counter {\n \n    private AtomicInteger atomicI = new AtomicInteger();\n \n    private int i = 0;\n \n    public static void main(String[] args) {\n \n        final Counter cas = new Counter();\n \n        List<Thread> ts = new ArrayList<Thread>();\n \n        long start = System.currentTimeMillis();\n \n        for (int j = 0; j < 100; j++) {\n \n            Thread t = new Thread(new Runnable() {\n                @Override\n                public void run() {\n                    for (int i = 0; i < 10000; i++) {\n                        cas.count();\n                        cas.safeCount();\n                    }\n \n                }\n \n            });\n            ts.add(t);\n        }\n \n        for (Thread t : ts) {\n            t.start();\n        }\n \n        // 等待所有线程执行完成\n \n        for (Thread t : ts) {\n            try {\n \n                t.join();\n \n            } catch (InterruptedException e) {\n \n                e.printStackTrace();\n \n            }\n \n        }\n \n        System.out.println(cas.i);\n \n        System.out.println(cas.atomicI.get());\n \n        System.out.println(System.currentTimeMillis() - start);\n \n    }\n \n    /**\n     * \n     * 使用CAS实现线程安全计数器\n     * \n     */\n \n    private void safeCount() {\n        for (;;) {\n            int i = atomicI.get();\n            boolean suc = atomicI.compareAndSet(i, ++i);\n            if (suc) {\n                break;\n            }\n        }\n    }\n \n    /**\n     * \n     * 非线程安全计数器\n     * \n     */\n \n    private void count() {\n        i++;\n    }\n}\n````\n\n    结果\n    992362\n    1000000\n    75\n\n从Java1.5开始JDK的并发包里提供了一些类来支持原子操作，如[AtomicBoolean](http://www.cjsdn.net/doc/jdk60/java/util/concurrent/atomic/AtomicBoolean.html)（用原子方式更新的 boolean 值），[AtomicInteger](http://www.cjsdn.net/doc/jdk60/java/util/concurrent/atomic/AtomicInteger.html)（用原子方式更新的 int 值），[AtomicLong](http://www.cjsdn.net/doc/jdk60/java/util/concurrent/atomic/AtomicLong.html)（用原子方式更新的 long 值），这些原子包装类还提供了有用的工具方法，比如以原子的方式将当前值自增1和自减1。\n\n在Java并发包中有一些并发框架也使用了自旋CAS的方式来实现原子操作，比如LinkedTransferQueue类的Xfer方法。CAS虽然很高效的解决原子操作，但是CAS仍然存在三大问题。ABA问题，循环时间长开销大和只能保证一个共享变量的原子操作。\n\n1.  ABA问题。因为CAS需要在操作值的时候检查下值有没有发生变化，如果没有发生变化则更新，但是如果一个值原来是A，变成了B，又变成了A，那么使用CAS进行检查时会发现它的值没有发生变化，但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号，每次变量更新的时候把版本号加一，那么A－B－A 就会变成1A-2B－3A。\n\n从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用，并且当前标志是否等于预期标志，如果全部相等，则以原子方式将该引用和该标志的值设置为给定的更新值。\n\n````    \npublic boolean compareAndSet(\n   V      expectedReference,//预期引用\n \n   V      newReference,//更新后的引用\n \n  int    expectedStamp, //预期标志\n \n  int    newStamp //更新后的标志\n)\n````\n1.  循环时间长开销大。自旋CAS如果长时间不成功，会给CPU带来非常大的执行开销。如果JVM能支持处理器提供的pause指令那么效率会有一定的提升，pause指令有两个作用，第一它可以延迟流水线执行指令（de-pipeline）,使CPU不会消耗过多的执行资源，延迟的时间取决于具体实现的版本，在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突（memory order violation）而引起CPU流水线被清空（CPU pipeline flush），从而提高CPU的执行效率。\n\n\n2.  只能保证一个共享变量的原子操作。当对一个共享变量执行操作时，我们可以使用循环CAS的方式来保证原子操作，但是对多个共享变量操作时，循环CAS就无法保证操作的原子性，这个时候就可以用锁，或者有一个取巧的办法，就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i＝2,j=a，合并一下ij=2a，然后用CAS来操作ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性，你可以把多个变量放在一个对象里来进行CAS操作。\n\n#### 4.2 使用锁机制实现原子操作\n\n锁机制保证了只有获得锁的线程能够操作锁定的内存区域。JVM内部实现了很多种锁机制，有偏向锁，轻量级锁和互斥锁，有意思的是除了偏向锁，JVM实现锁的方式都用到的循环CAS，当一个线程想进入同步块的时候使用循环CAS的方式来获取锁，当它退出同步块的时候使用循环CAS释放锁。详细说明可以参见文章[Java SE1.6中的Synchronized](http://www.infoq.com/cn/articles/java-se-16-synchronized)。\n\n## 5 参考资料\n\n1.  [Java SE1.6中的Synchronized](http://www.infoq.com/cn/articles/java-se-16-synchronized)\n2.  [Intel 64和IA-32架构软件开发人员手册](http://www.intel.com/products/processor/manuals/)\n3.  [深入分析Volatile的实现原理](http://www.infoq.com/cn/articles/ftf-java-volatile)\n"
  },
  {
    "path": "docs/Java/concurrency/Java并发指南：并发基础与Java多线程.md",
    "content": "# 目录\n  * [1多线程的优点](#1多线程的优点)\n    * [1.1资源利用率更好案例](#11资源利用率更好案例)\n    * [1.2程序响应更快](#12程序响应更快)\n  * [2多线程的代价](#2多线程的代价)\n    * [2.1设计更复杂](#21设计更复杂)\n    * [2.2上下文切换的开销](#22上下文切换的开销)\n    * [2.3增加资源消耗](#23增加资源消耗)\n  * [3竞态条件与临界区](#3竞态条件与临界区)\n  * [**4**线程的运行与创建](#4线程的运行与创建)\n  * [5线程的状态和优先级](#5线程的状态和优先级)\n\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章同步发于我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《Java并发指南》其中一篇，本文大部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何全面深入地学习Java并发技术，从Java多线程基础，再到并发编程的基础知识，从Java并发包的入门和实战，再到JUC的源码剖析，一步步地学习Java并发编程，并上手进行实战，以便让你更完整地了解整个Java并发编程知识体系，形成自己的知识框架。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供一些对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n<!--more -->\n\n## 1多线程的优点\n\n1.  - 资源利用率更好\n2.  - 程序设计在某些情况下更简单\n3.  - 程序响应更快\n\n### 1.1资源利用率更好案例\n\n**方式1**\n从磁盘读取一个文件需要5秒，处理一个文件需要2秒。处理两个文件则需要14秒\n\n```\n1 5秒读取文件A2 2秒处理文件A3 5秒读取文件B4 2秒处理文件B5 ---------------------6 总共需要14秒\n```\n\n**方式2**\n从磁盘中读取文件的时候，大部分的CPU非常的空闲。它可以做一些别的事情。通过改变操作的顺序，就能够更好的使用CPU资源。看下面的顺序：\n\n```\n1 5秒读取文件A2 5秒读取文件B + 2秒处理文件A3 2秒处理文件B4 ---------------------5 总共需要12秒\n```\n\n**总结：多线程并发效率提高2秒**\n\n\n### 1.2程序响应更快\n\n设想一个服务器应用，它在某一个端口监听进来的请求。当一个请求到来时，它把请求传递给工作者线程(worker thread)，然后立刻返回去监听。而工作者线程则能够处理这个请求并发送一个回复给客户端。\n````\nwhile(server is active){\n    listenThread for request\n   hand request to workerThread\n}\n````\n\n这种方式，服务端线程迅速地返回去监听。因此，更多的客户端能够发送请求给服务端。这个服务也变得响应更快。\n\n## 2多线程的代价\n\n### 2.1设计更复杂\n\n多线程一般都复杂。在多线程访问共享数据的时候，这部分代码需要特别的注意。线程之间的交互往往非常复杂。不正确的线程同步产生的错误非常难以被发现，并且重现以修复。\n\n### 2.2上下文切换的开销\n\n**上下文切换**当CPU从执行一个线程切换到执行另外一个线程的时候，它需要先存储当前线程的本地的数据，程序指针等，然后载入另一个线程的本地数据，程序指针等，最后才开始执行。\n\nCPU会在一个上下文中执行一个线程，然后切换到另外一个上下文中执行另外一个线程。\n\n上下文切换并不廉价。如果没有必要，应该减少上下文切换的发生。\n\n### 2.3增加资源消耗\n\n**每个线程需要消耗的资源：**\n\nCPU，内存（维持它本地的堆栈），操作系统资源（管理线程）\n\n## 3竞态条件与临界区\n\n当多个线程竞争同一资源时，如果对资源的访问顺序敏感，就称存在竞态条件。导致竞态条件发生的代码区称作临界区。\n\n**多线程同时执行下面的代码可能会出错：**\n\n````\npublic class Counter {\n    protected long count = 0;\n \n    public void add(long value) {\n        this.count = this.count + value;\n    }\n}\n````\n想象下线程A和B同时执行同一个Counter对象的add()方法，我们无法知道操作系统何时会在两个线程之间切换。JVM并不是将这段代码视为单条指令来执行的，而是按照下面的顺序\n\n\n    从内存获取 this.count 的值放到寄存器\n    将寄存器中的值增加value\n    将寄存器中的值写回内存\n     \n     \n     \n    观察线程A和B交错执行会发生什么\n     \n    \tthis.count = 0;\n       A:\t读取 this.count 到一个寄存器 (0)\n       B:\t读取 this.count 到一个寄存器 (0)\n       B: \t将寄存器的值加2\n       B:\t回写寄存器值(2)到内存. this.count 现在等于 2\n       A:\t将寄存器的值加3\n\n\n由于两个线程是交叉执行的，两个线程从内存中读出的初始值都是0。然后各自加了2和3，并分别写回内存。最终的值可能并不是期望的5，而是最后写回内存的那个线程的值，上面例子中最后写回内存可能是线程A，也可能是线程B\n \n## **4**线程的运行与创建\n\n**Java 创建线程对象有两种方法：**\n\n*   继承 Thread 类创建线程对象\n*   实现 Runnable 接口类创建线程对象\n\n**注意：**\n\n在java中，每次程序运行至少启动2个线程。一个是main线程，一个是垃圾收集线程。因为每当使用java命令执行一个类的时候，实际上都会启动一个jvm，每一个jvm实际上就是在操作系统中启动了一个进程。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404183859.png)\n## 5线程的状态和优先级\n\n**线程优先级**1 到 10 ，其中 1 是最低优先级，10 是最高优先级。\n\n**状态**\n\n*   new（新建）\n*   runnnable（可运行）\n*   blocked（阻塞）\n*   waiting（等待）\n*   time waiting （定时等待）\n*   terminated（终止）\n\n**状态转换**\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404183924.png)\n**线程状态流程如下：**\n\n*   线程创建后，进入 new 状态\n*   调用 start 或者 run 方法，进入 runnable 状态\n*   JVM 按照线程优先级及时间分片等执行 runnable 状态的线程。开始执行时，进入 running 状态\n*   如果线程执行 sleep、wait、join，或者进入 IO 阻塞等。进入 wait 或者 blocked 状态\n*   线程执行完毕后，线程被线程队列移除。最后为 terminated 状态\n\n**代码**\n\n````\n    public class MyThreadInfo extends Thread {\n     \n    \t@Override // 可以省略\n    \tpublic void run() {\n    \t\tSystem.out.println(\"run\");\n    \t\t// System.exit(1);\n    \t}\n     \n    \tpublic static void main(String[] args) {\n    \t\tMyThreadInfo thread = new MyThreadInfo();\n    \t\tthread.start();\n     \n    \t\tSystem.out.println(\"线程唯一标识符：\" + thread.getId());\n    \t\tSystem.out.println(\"线程名称：\" + thread.getName());\n    \t\tSystem.out.println(\"线程状态：\" + thread.getState());\n    \t\tSystem.out.println(\"线程优先级：\" + thread.getPriority());\n    \t}\n    }\n````     \n    结果：\n    线程唯一标识符：9\n    线程名称：Thread-0\n    run\n    线程状态：RUNNABLE\n\n\n\n\n"
  },
  {
    "path": "docs/Java/concurrency/Java并发指南：深入理解Java内存模型JMM.md",
    "content": "# 目录\n  * [一：JMM基础与happens-before](#一：jmm基础与happens-before)\n    * [并发编程模型的分类](#并发编程模型的分类)\n    * [重排序](#重排序)\n    * [处理器重排序与内存屏障指令](#处理器重排序与内存屏障指令)\n    * [happens-before](#happens-before)\n  * [二：重排序与JMM的as-if-serial](#二：重排序与jmm的as-if-serial)\n    * [数据依赖性](#数据依赖性)\n    * [as-if-serial语义](#as-if-serial语义)\n    * [程序顺序规则](#程序顺序规则)\n    * [重排序对多线程的影响](#重排序对多线程的影响)\n  * [三：顺序一致性内存模型与JMM](#三：顺序一致性内存模型与jmm)\n    * [数据竞争与顺序一致性保证](#数据竞争与顺序一致性保证)\n    * [顺序一致性内存模型](#顺序一致性内存模型)\n    * [同步程序的顺序一致性效果](#同步程序的顺序一致性效果)\n    * [未同步程序的执行特性](#未同步程序的执行特性)\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章同步发于我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《Java并发指南》其中一篇，本文大部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何全面深入地学习Java并发技术，从Java多线程基础，再到并发编程的基础知识，从Java并发包的入门和实战，再到JUC的源码剖析，一步步地学习Java并发编程，并上手进行实战，以便让你更完整地了解整个Java并发编程知识体系，形成自己的知识框架。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供一些对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n<!--more -->\n\n最近在面试的的时候，什么是Java内存模型也是常问的的一道面试题。\n\n> 网上有很多关于Java内存模型的文章，在《深入理解Java虚拟机》和《Java并发编程的艺术》等书中也都有关于这个知识点的介绍。但是，很多人读完之后还是搞不清楚，甚至有的人说自己更懵了。本文，就来整体的介绍一下Java内存模型，目的很简单，让你读完本文以后，就知道到底Java内存模型是什么，为什么要有Java内存模型，Java内存模型解决了什么问题等。\n\n## 1\\. 为什么要有内存模型？\n\n要想回答这个问题，我们需要先弄懂传统计算机硬件内存架构。好了，我要开始画图了。\n\n## 1.1\\. 硬件内存架构\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404185859.png)\n\n**1）CPU**\n\n去过机房的同学都知道，一般在大型服务器上会配置多个CPU，每个CPU还会有多个核，这就意味着多个CPU或者多个核可以同时（并发）工作。如果使用Java 起了一个多线程的任务，很有可能每个 CPU 都会跑一个线程，那么你的任务在某一刻就是真正并发执行了。\n\n**（2）CPU Register**\n\nCPU Register也就是 CPU 寄存器。CPU 寄存器是 CPU 内部集成的，在寄存器上执行操作的效率要比在主存上高出几个数量级。\n\n**（3）CPU Cache Memory**\n\nCPU Cache Memory也就是 CPU 高速缓存，相对于寄存器来说，通常也可以成为 L2 二级缓存。相对于硬盘读取速度来说内存读取的效率非常高，但是与 CPU 还是相差数量级，所以在 CPU 和主存间引入了多级缓存，目的是为了做一下缓冲。\n\n**（4）Main Memory**\n\nMain Memory 就是主存，主存比 L1、L2 缓存要大很多。\n\n注意：部分高端机器还有 L3 三级缓存。\n\n## 1.2\\. 缓存一致性问题\n\n由于主存与 CPU 处理器的运算能力之间有数量级的差距，所以在传统计算机内存架构中会引入高速缓存来作为主存和处理器之间的缓冲，CPU 将常用的数据放在高速缓存中，运算结束后 CPU 再讲运算结果同步到主存中。\n\n使用高速缓存解决了 CPU 和主存速率不匹配的问题，但同时又引入另外一个新问题：缓存一致性问题。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404190013.png)\n\n在多CPU的系统中(或者单CPU多核的系统)，每个CPU内核都有自己的高速缓存，它们共享同一主内存(Main Memory)。当多个CPU的运算任务都涉及同一块主内存区域时，CPU 会将数据读取到缓存中进行运算，这可能会导致各自的缓存数据不一致。\n\n因此需要每个 CPU 访问缓存时遵循一定的协议，在读写数据时根据协议进行操作，共同来维护缓存的一致性。这类协议有 MSI、MESI、MOSI、和 Dragon Protocol 等。\n\n## 1.3\\. 处理器优化和指令重排序\n\n为了提升性能在 CPU 和主内存之间增加了高速缓存，但在多线程并发场景可能会遇到`缓存一致性问题`。那还有没有办法进一步提升 CPU 的执行效率呢？答案是：处理器优化。\n\n> 为了使处理器内部的运算单元能够最大化被充分利用，处理器会对输入代码进行乱序执行处理，这就是处理器优化。\n\n除了处理器会对代码进行优化处理，很多现代编程语言的编译器也会做类似的优化，比如像 Java 的即时编译器（JIT）会做指令重排序。\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404190116.png)\n\n> 处理器优化其实也是重排序的一种类型，这里总结一下，重排序可以分为三种类型：\n\n*   编译器优化的重排序。编译器在不改变单线程程序语义放入前提下，可以重新安排语句的执行顺序。\n*   指令级并行的重排序。现代处理器采用了指令级并行技术来将多条指令重叠执行。如果不存在数据依赖性，处理器可以改变语句对应机器指令的执行顺序。\n*   内存系统的重排序。由于处理器使用缓存和读写缓冲区，这使得加载和存储操作看上去可能是在乱序执行。\n\n## 2\\. 并发编程的问题\n\n上面讲了一堆硬件相关的东西，有些同学可能会有点懵，绕了这么大圈，这些东西跟 Java 内存模型有啥关系吗？不要急咱们慢慢往下看。\n\n熟悉 Java 并发的同学肯定对这三个问题很熟悉：『可见性问题』、『原子性问题』、『有序性问题』。如果从更深层次看这三个问题，其实就是上面讲的『缓存一致性』、『处理器优化』、『指令重排序』造成的。\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404190045.png)\n\n\n缓存一致性问题其实就是可见性问题，处理器优化可能会造成原子性问题，指令重排序会造成有序性问题，你看是不是都联系上了。\n\n出了问题总是要解决的，那有什么办法呢？首先想到简单粗暴的办法，干掉缓存让 CPU 直接与主内存交互就解决了可见性问题，禁止处理器优化和指令重排序就解决了原子性和有序性问题，但这样一夜回到解放前了，显然不可取。\n\n所以技术前辈们想到了在物理机器上定义出一套内存模型， 规范内存的读写操作。内存模型解决并发问题主要采用两种方式：限制处理器优化和使用内存屏障。\n\n## 3\\. Java 内存模型\n\n同一套内存模型规范，不同语言在实现上可能会有些差别。接下来着重讲一下 Java 内存模型实现原理。\n\n## 3.1\\. Java 运行时内存区域与硬件内存的关系\n\n了解过 JVM 的同学都知道，JVM 运行时内存区域是分片的，分为栈、堆等，其实这些都是 JVM 定义的逻辑概念。在传统的硬件内存架构中是没有栈和堆这种概念。\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404190254.png)\n\n\n从图中可以看出栈和堆既存在于高速缓存中又存在于主内存中，所以两者并没有很直接的关系。\n\n## 3.2\\. Java 线程与主内存的关系\n\nJava 内存模型是一种规范，定义了很多东西：\n\n*   所有的变量都存储在主内存（Main Memory）中。\n*   每个线程都有一个私有的本地内存（Local Memory），本地内存中存储了该线程以读/写共享变量的拷贝副本。\n*   线程对变量的所有操作都必须在本地内存中进行，而不能直接读写主内存。\n*   不同的线程之间无法直接访问对方本地内存中的变量。\n\n看文字太枯燥了，我又画了一张图：\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404190315.png)\n\n\n## 3.3\\. 线程间通信\n\n如果两个线程都对一个共享变量进行操作，共享变量初始值为 1，每个线程都变量进行加 1，预期共享变量的值为 3。在 JMM 规范下会有一系列的操作。\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404190335.png)\n\n\n为了更好的控制主内存和本地内存的交互，Java 内存模型定义了八种操作来实现：\n\n*   lock：锁定。作用于主内存的变量，把一个变量标识为一条线程独占状态。\n*   unlock：解锁。作用于主内存变量，把一个处于锁定状态的变量释放出来，释放后的变量才可以被其他线程锁定。\n*   read：读取。作用于主内存变量，把一个变量值从主内存传输到线程的工作内存中，以便随后的load动作使用\n*   load：载入。作用于工作内存的变量，它把read操作从主内存中得到的变量值放入工作内存的变量副本中。\n*   use：使用。作用于工作内存的变量，把工作内存中的一个变量值传递给执行引擎，每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。\n*   assign：赋值。作用于工作内存的变量，它把一个从执行引擎接收到的值赋值给工作内存的变量，每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。\n*   store：存储。作用于工作内存的变量，把工作内存中的一个变量的值传送到主内存中，以便随后的write的操作。\n*   write：写入。作用于主内存的变量，它把store操作从工作内存中一个变量的值传送到主内存的变量中。\n\n> 注意：工作内存也就是本地内存的意思。\n\n## 4\\. 有态度的总结\n\n由于CPU 和主内存间存在数量级的速率差，想到了引入了多级高速缓存的传统硬件内存架构来解决，多级高速缓存作为 CPU 和主内间的缓冲提升了整体性能。解决了速率差的问题，却又带来了缓存一致性问题。\n\n数据同时存在于高速缓存和主内存中，如果不加以规范势必造成灾难，因此在传统机器上又抽象出了内存模型。\n\nJava 语言在遵循内存模型的基础上推出了 JMM 规范，目的是解决由于多线程通过共享内存进行通信时，存在的本地内存数据不一致、编译器会对代码指令重排序、处理器会对代码乱序执行等带来的问题。\n\n为了更精准控制工作内存和主内存间的交互，JMM 还定义了八种操作：lock, unlock, read, load,use,assign, store, write。\n\n前面介绍过了计算机内存模型，这是解决多线程场景下并发问题的一个重要规范。那么具体的实现是如何的呢，不同的编程语言，在实现上可能有所不同。\n\n我们知道，Java程序是需要运行在Java虚拟机上面的，**Java内存模型（Java Memory Model ,JMM）就是一种符合内存模型规范的，屏蔽了各种硬件和操作系统的访问差异的，保证了Java程序在各种平台下对内存的访问都能保证效果一致的机制及规范。**\n\n提到Java内存模型，一般指的是JDK 5 开始使用的新的内存模型，主要由<u>[JSR-133: JavaTM Memory Model and Thread Specification](http://www.cs.umd.edu/~pugh/java/memoryModel/jsr133.pdf)</u>描述。感兴趣的可以参看下这份PDF文档（[http://www.cs.umd.edu/~pugh/java/memoryModel/jsr133.pdf](http://www.cs.umd.edu/~pugh/java/memoryModel/jsr133.pdf)）\n\nJava内存模型规定了所有的变量都存储在主内存中，每条线程还有自己的工作内存，线程的工作内存中保存了该线程中是用到的变量的主内存副本拷贝，线程对变量的所有操作都必须在工作内存中进行，而不能直接读写主内存。不同的线程之间也无法直接访问对方工作内存中的变量，线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。\n\n而JMM就作用于工作内存和主存之间数据同步过程。他规定了如何做数据同步以及什么时候做数据同步。\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404190350.png)\n\n\n这里面提到的主内存和工作内存，读者可以简单的类比成计算机内存模型中的主存和缓存的概念。特别需要注意的是，主内存和工作内存与JVM内存结构中的Java堆、栈、方法区等并不是同一个层次的内存划分，无法直接类比。《深入理解Java虚拟机》中认为，如果一定要勉强对应起来的话，从变量、主内存、工作内存的定义来看，主内存主要对应于Java堆中的对象实例数据部分。工作内存则对应于虚拟机栈中的部分区域。\n\n**所以，再来总结下，JMM是一种规范，目的是解决由于多线程通过共享内存进行通信时，存在的本地内存数据不一致、编译器会对代码指令重排序、处理器会对代码乱序执行等带来的问题。**\n\n## **Java内存模型的实现**\n\n了解Java多线程的朋友都知道，在Java中提供了一系列和并发处理相关的关键字，比如`volatile`、`synchronized`、`final`、`concurren`包等。其实这些就是Java内存模型封装了底层的实现后提供给程序员使用的一些关键字。\n\n在开发多线程的代码的时候，我们可以直接使用`synchronized`等关键字来控制并发，从来就不需要关心底层的编译器优化、缓存一致性等问题。所以，**Java内存模型，除了定义了一套规范，还提供了一系列原语，封装了底层实现后，供开发者直接使用。**\n\n本文并不准备把所有的关键字逐一介绍其用法，因为关于各个关键字的用法，网上有很多资料。读者可以自行学习。本文还有一个重点要介绍的就是，我们前面提到，并发编程要解决原子性、有序性和一致性的问题，我们就再来看下，在Java中，分别使用什么方式来保证。\n\n### **原子性**\n\n在Java中，为了保证原子性，提供了两个高级的字节码指令`monitorenter`和`monitorexit`。在<u>[synchronized的实现原理](http://www.hollischuang.com/archives/1883)</u>文章中，介绍过，这两个字节码，在Java中对应的关键字就是`synchronized`。\n\n因此，在Java中可以使用`synchronized`来保证方法和代码块内的操作是原子性的。\n\n### **可见性**\n\nJava内存模型是通过在变量修改后将新值同步回主内存，在变量读取前从主内存刷新变量值的这种依赖主内存作为传递媒介的方式来实现的。\n\nJava中的`volatile`关键字提供了一个功能，那就是被其修饰的变量在被修改后可以立即同步到主内存，被其修饰的变量在每次是用之前都从主内存刷新。因此，可以使用`volatile`来保证多线程操作时变量的可见性。\n\n除了`volatile`，Java中的`synchronized`和`final`两个关键字也可以实现可见性。只不过实现方式不同，这里不再展开了。\n\n### **有序性**\n\n在Java中，可以使用`synchronized`和`volatile`来保证多线程之间操作的有序性。实现方式有所区别：\n\n`volatile`关键字会禁止指令重排。`synchronized`关键字保证同一时刻只允许一条线程操作。\n\n好了，这里简单的介绍完了Java并发编程中解决原子性、可见性以及有序性可以使用的关键字。读者可能发现了，好像`synchronized`关键字是万能的，他可以同时满足以上三种特性，这其实也是很多人滥用`synchronized`的原因。\n\n但是`synchronized`是比较影响性能的，虽然编译器提供了很多锁优化技术，但是也不建议过度使用。\n\n## **总结**\n\n在读完本文之后，相信你应该了解了什么是Java内存模型、Java内存模型的作用以及Java中内存模型做了什么事情等。关于Java中这些和内存模型有关的关键字，希望读者还可以继续深入学习，并且自己写几个例子亲自体会一下。\n\n可以参考《深入理解Java虚拟机》和《Java并发编程的艺术》两本书。\n\n参考：\n\n> [https://www.cnblogs.com/aishangJava/p/14742033.html](https://www.cnblogs.com/aishangJava/p/14742033.html)\n> [https://www.jianshu.com/p/8420ade6ff76](https://www.jianshu.com/p/8420ade6ff76)\n> httpa://[http://www.hollischuang.com/archives/2550](http://www.hollischuang.com/archives/2550)\n\n\n\n\n\n\n\n"
  },
  {
    "path": "docs/Java/concurrency/Java并发指南：深度解读Java线程池设计思想及源码实现.md",
    "content": "# 目录\n  * [前言](#前言)\n  * [总览](#总览)\n  * [Executor 接口](#executor-接口)\n  * [ExecutorService](#executorservice)\n  * [FutureTask](#futuretask)\n  * [AbstractExecutorService](#abstractexecutorservice)\n  * [ThreadPoolExecutor](#threadpoolexecutor)\n  * [Executors](#executors)\n  * [Worker获取任务](#getTask)\n  * [总结](#总结)\n\n\n本文转自：https://www.javadoop.com/\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章同步发于我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《Java并发指南》其中一篇，本文大部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何全面深入地学习Java并发技术，从Java多线程基础，再到并发编程的基础知识，从Java并发包的入门和实战，再到JUC的源码剖析，一步步地学习Java并发编程，并上手进行实战，以便让你更完整地了解整个Java并发编程知识体系，形成自己的知识框架。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供一些对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n<!--more -->\n## 前言\n\n我相信大家都看过很多的关于线程池的文章，基本上也是面试的时候必问的，如果你在看过很多文章以后，还是一知半解的，那希望这篇文章能让你真正的掌握好 Java 线程池。\n\n本文一大重点是源码解析，同时会有少量篇幅介绍线程池设计思想以及作者 Doug Lea 实现过程中的一些巧妙用法。本文还是会一行行关键代码进行分析，目的是为了让那些自己看源码不是很理解的同学可以得到参考。\n\n线程池是非常重要的工具，如果你要成为一个好的工程师，还是得比较好地掌握这个知识，很多线上问题都是因为没有用好线程池导致的。即使你为了谋生，也要知道，这基本上是面试必问的题目，而且面试官很容易从被面试者的回答中捕捉到被面试者的技术水平。\n\n本文略长，建议在 pc 上阅读，边看文章边翻源码（Java7 和 Java8 都一样），建议想好好看的读者抽出至少 30 分钟的整块时间来阅读。当然，如果读者仅为面试准备，可以直接滑到最后的**总结**部分。\n\n## 总览\n\n开篇来一些废话。下图是 java 线程池几个相关类的继承结构：\n\n![1](https://www.javadoop.com/blogimages/java-thread-pool/1.jpg)\n\n先简单说说这个继承结构，Executor 位于最顶层，也是最简单的，就一个 execute(Runnable runnable) 接口方法定义。\n\nExecutorService 也是接口，在 Executor 接口的基础上添加了很多的接口方法，所以**一般来说我们会使用这个接口**。\n\n然后再下来一层是 AbstractExecutorService，从名字我们就知道，这是抽象类，这里实现了非常有用的一些方法供子类直接使用，之后我们再细说。\n\n然后才到我们的重点部分 ThreadPoolExecutor 类，这个类提供了关于线程池所需的非常丰富的功能。\n\n另外，我们还涉及到下图中的这些类：\n\n![others](https://www.javadoop.com/blogimages/java-thread-pool/others.png)\n\n同在并发包中的 Executors 类，类名中带字母 s，我们猜到这个是工具类，里面的方法都是静态方法，如以下我们最常用的用于生成 ThreadPoolExecutor 的实例的一些方法：\n\n```\npublic static ExecutorService newCachedThreadPool() {\n    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,\n                                  60L, TimeUnit.SECONDS,\n                                  new SynchronousQueue<Runnable>());\n}\npublic static ExecutorService newFixedThreadPool(int nThreads) {\n    return new ThreadPoolExecutor(nThreads, nThreads,\n                                  0L, TimeUnit.MILLISECONDS,\n                                  new LinkedBlockingQueue<Runnable>());\n}\n```\n\n另外，由于线程池支持**获取线程执行的结果**，所以，引入了 Future 接口，RunnableFuture 继承自此接口，然后我们最需要关心的就是它的实现类 FutureTask。到这里，记住这个概念，在线程池的使用过程中，我们是往线程池提交任务（task），使用过线程池的都知道，我们提交的每个任务是实现了 Runnable 接口的，其实就是先将 Runnable 的任务包装成 FutureTask，然后再提交到线程池。这样，读者才能比较容易记住 FutureTask 这个类名：它首先是一个任务（Task），然后具有 Future 接口的语义，即可以在将来（Future）得到执行的结果。\n\n当然，线程池中的 BlockingQueue 也是非常重要的概念，如果线程数达到 corePoolSize，我们的每个任务会提交到等待队列中，等待线程池中的线程来取任务并执行。这里的 BlockingQueue 通常我们使用其实现类 LinkedBlockingQueue、ArrayBlockingQueue 和 SynchronousQueue，每个实现类都有不同的特征，使用场景之后会慢慢分析。想要详细了解各个 BlockingQueue 的读者，可以参考我的前面的一篇对 BlockingQueue 的各个实现类进行详细分析的文章。\n\n> 把事情说完整：除了上面说的这些类外，还有一个很重要的类，就是定时任务实现类 ScheduledThreadPoolExecutor，它继承自本文要重点讲解的 ThreadPoolExecutor，用于实现定时执行。不过本文不会介绍它的实现，我相信读者看完本文后可以比较容易地看懂它的源码。\n\n以上就是本文要介绍的知识，废话不多说，开始进入正文。\n\n## Executor 接口\n\n```\n/* \n * @since 1.5\n * @author Doug Lea\n */\npublic interface Executor {\n    void execute(Runnable command);\n}\n```\n\n我们可以看到 Executor 接口非常简单，就一个`void execute(Runnable command)`方法，代表提交一个任务。为了让大家理解 java 线程池的整个设计方案，我会按照 Doug Lea 的设计思路来多说一些相关的东西。\n\n我们经常这样启动一个线程：\n\n```\nnew Thread(new Runnable(){\n  // do something\n}).start();\n```\n\n用了线程池 Executor 后就可以像下面这么使用：\n\n```\nExecutor executor = anExecutor;\nexecutor.execute(new RunnableTask1());\nexecutor.execute(new RunnableTask2());\n```\n\n如果我们希望线程池同步执行每一个任务，我们可以这么实现这个接口：\n\n```\nclass DirectExecutor implements Executor {\n    public void execute(Runnable r) {\n        r.run();// 这里不是用的new Thread(r).start()，也就是说没有启动任何一个新的线程。\n    }\n}\n```\n\n我们希望每个任务提交进来后，直接启动一个新的线程来执行这个任务，我们可以这么实现：\n\n```\nclass ThreadPerTaskExecutor implements Executor {\n    public void execute(Runnable r) {\n        new Thread(r).start();  // 每个任务都用一个新的线程来执行\n    }\n}\n```\n\n我们再来看下怎么组合两个 Executor 来使用，下面这个实现是将所有的任务都加到一个 queue 中，然后从 queue 中取任务，交给真正的执行器执行，这里采用 synchronized 进行并发控制：\n\n```\nclass SerialExecutor implements Executor {\n    // 任务队列\n    final Queue<Runnable> tasks = new ArrayDeque<Runnable>();\n    // 这个才是真正的执行器\n    final Executor executor;\n    // 当前正在执行的任务\n    Runnable active;\n\n    // 初始化的时候，指定执行器\n    SerialExecutor(Executor executor) {\n        this.executor = executor;\n    }\n\n    // 添加任务到线程池: 将任务添加到任务队列，scheduleNext 触发执行器去任务队列取任务\n    public synchronized void execute(final Runnable r) {\n        tasks.offer(new Runnable() {\n            public void run() {\n                try {\n                    r.run();\n                } finally {\n                    scheduleNext();\n                }\n            }\n        });\n        if (active == null) {\n            scheduleNext();\n        }\n    }\n\n    protected synchronized void scheduleNext() {\n        if ((active = tasks.poll()) != null) {\n            // 具体的执行转给真正的执行器 executor\n            executor.execute(active);\n        }\n    }\n}\n```\n\n当然了，Executor 这个接口只有提交任务的功能，太简单了，我们想要更丰富的功能，比如我们想知道执行结果、我们想知道当前线程池有多少个线程活着、已经完成了多少任务等等，这些都是这个接口的不足的地方。接下来我们要介绍的是继承自`Executor`接口的`ExecutorService`接口，这个接口提供了比较丰富的功能，也是我们最常使用到的接口。\n\n## ExecutorService\n\n一般我们定义一个线程池的时候，往往都是使用这个接口：\n\n```\nExecutorService executor = Executors.newFixedThreadPool(args...);\nExecutorService executor = Executors.newCachedThreadPool(args...);\n```\n\n因为这个接口中定义的一系列方法大部分情况下已经可以满足我们的需要了。\n\n那么我们简单初略地来看一下这个接口中都有哪些方法：\n\n```\npublic interface ExecutorService extends Executor {\n\n    // 关闭线程池，已提交的任务继续执行，不接受继续提交新任务\n    void shutdown();\n\n    // 关闭线程池，尝试停止正在执行的所有任务，不接受继续提交新任务\n    // 它和前面的方法相比，加了一个单词“now”，区别在于它会去停止当前正在进行的任务\n    List<Runnable> shutdownNow();\n\n    // 线程池是否已关闭\n    boolean isShutdown();\n\n    // 如果调用了 shutdown() 或 shutdownNow() 方法后，所有任务结束了，那么返回true\n    // 这个方法必须在调用shutdown或shutdownNow方法之后调用才会返回true\n    boolean isTerminated();\n\n    // 等待所有任务完成，并设置超时时间\n    // 我们这么理解，实际应用中是，先调用 shutdown 或 shutdownNow，\n    // 然后再调这个方法等待所有的线程真正地完成，返回值意味着有没有超时\n    boolean awaitTermination(long timeout, TimeUnit unit)\n            throws InterruptedException;\n\n    // 提交一个 Callable 任务\n    <T> Future<T> submit(Callable<T> task);\n\n    // 提交一个 Runnable 任务，第二个参数将会放到 Future 中，作为返回值，\n    // 因为 Runnable 的 run 方法本身并不返回任何东西\n    <T> Future<T> submit(Runnable task, T result);\n\n    // 提交一个 Runnable 任务\n    Future<?> submit(Runnable task);\n\n    // 执行所有任务，返回 Future 类型的一个 list\n    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)\n            throws InterruptedException;\n\n    // 也是执行所有任务，但是这里设置了超时时间\n    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,\n                                  long timeout, TimeUnit unit)\n            throws InterruptedException;\n\n    // 只有其中的一个任务结束了，就可以返回，返回执行完的那个任务的结果\n    <T> T invokeAny(Collection<? extends Callable<T>> tasks)\n            throws InterruptedException, ExecutionException;\n\n    // 同上一个方法，只有其中的一个任务结束了，就可以返回，返回执行完的那个任务的结果，\n    // 不过这个带超时，超过指定的时间，抛出 TimeoutException 异常\n    <T> T invokeAny(Collection<? extends Callable<T>> tasks,\n                    long timeout, TimeUnit unit)\n            throws InterruptedException, ExecutionException, TimeoutException;\n}\n```\n\n这些方法都很好理解，一个简单的线程池主要就是这些功能，能提交任务，能获取结果，能关闭线程池，这也是为什么我们经常用这个接口的原因。\n\n## FutureTask\n\n在继续往下层介绍 ExecutorService 的实现类之前，我们先来说说相关的类 FutureTask。\n\n```\nFuture      Runnable\n   \\           /\n    \\         /\n   RunnableFuture\n          |\n          |\n      FutureTask\n\nFutureTask 通过 RunnableFuture 间接实现了 Runnable 接口，\n所以每个 Runnable 通常都先包装成 FutureTask，\n然后调用 executor.execute(Runnable command) 将其提交给线程池\n```\n\n我们知道，Runnable 的 void run() 方法是没有返回值的，所以，通常，如果我们需要的话，会在 submit 中指定第二个参数作为返回值：\n\n```\n<T> Future<T> submit(Runnable task, T result);\n```\n\n其实到时候会通过这两个参数，将其包装成 Callable。它和 Runnable 的区别在于 run() 没有返回值，而 Callable 的 call() 方法有返回值，同时，如果运行出现异常，call() 方法会抛出异常。\n\n```\npublic interface Callable<V> {\n\n    V call() throws Exception;\n}\n```\n\n在这里，就不展开说 FutureTask 类了，因为本文篇幅本来就够大了，这里我们需要知道怎么用就行了。\n\n下面，我们来看看`ExecutorService`的抽象实现`AbstractExecutorService`。\n\n## AbstractExecutorService\n\nAbstractExecutorService 抽象类派生自 ExecutorService 接口，然后在其基础上实现了几个实用的方法，这些方法提供给子类进行调用。\n\n这个抽象类实现了 invokeAny 方法和 invokeAll 方法，这里的两个 newTaskFor 方法也比较有用，用于将任务包装成 FutureTask。定义于最上层接口 Executor中的`void execute(Runnable command)`由于不需要获取结果，不会进行 FutureTask 的包装。\n\n> 需要获取结果（FutureTask），用 submit 方法，不需要获取结果，可以用 execute 方法。\n\n下面，我将一行一行源码地来分析这个类，跟着源码来看看其实现吧：\n\n> Tips: invokeAny 和 invokeAll 方法占了这整个类的绝大多数篇幅，读者可以选择适当跳过，因为它们可能在你的实践中使用的频次比较低，而且它们不带有承前启后的作用，不用担心会漏掉什么导致看不懂后面的代码。\n\n```\npublic abstract class AbstractExecutorService implements ExecutorService {\n\n    // RunnableFuture 是用于获取执行结果的，我们常用它的子类 FutureTask\n    // 下面两个 newTaskFor 方法用于将我们的任务包装成 FutureTask 提交到线程池中执行\n    protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {\n        return new FutureTask<T>(runnable, value);\n    }\n\n    protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {\n        return new FutureTask<T>(callable);\n    }\n\n    // 提交任务\n    public Future<?> submit(Runnable task) {\n        if (task == null) throw new NullPointerException();\n        // 1\\. 将任务包装成 FutureTask\n        RunnableFuture<Void> ftask = newTaskFor(task, null);\n        // 2\\. 交给执行器执行，execute 方法由具体的子类来实现\n        // 前面也说了，FutureTask 间接实现了Runnable 接口。\n        execute(ftask);\n        return ftask;\n    }\n\n    public <T> Future<T> submit(Runnable task, T result) {\n        if (task == null) throw new NullPointerException();\n        // 1\\. 将任务包装成 FutureTask\n        RunnableFuture<T> ftask = newTaskFor(task, result);\n        // 2\\. 交给执行器执行\n        execute(ftask);\n        return ftask;\n    }\n\n    public <T> Future<T> submit(Callable<T> task) {\n        if (task == null) throw new NullPointerException();\n        // 1\\. 将任务包装成 FutureTask\n        RunnableFuture<T> ftask = newTaskFor(task);\n        // 2\\. 交给执行器执行\n        execute(ftask);\n        return ftask;\n    }\n\n    // 此方法目的：将 tasks 集合中的任务提交到线程池执行，任意一个线程执行完后就可以结束了\n    // 第二个参数 timed 代表是否设置超时机制，超时时间为第三个参数，\n    // 如果 timed 为 true，同时超时了还没有一个线程返回结果，那么抛出 TimeoutException 异常\n    private <T> T doInvokeAny(Collection<? extends Callable<T>> tasks,\n                            boolean timed, long nanos)\n        throws InterruptedException, ExecutionException, TimeoutException {\n        if (tasks == null)\n            throw new NullPointerException();\n        // 任务数\n        int ntasks = tasks.size();\n        if (ntasks == 0)\n            throw new IllegalArgumentException();\n        // \n        List<Future<T>> futures= new ArrayList<Future<T>>(ntasks);\n\n        // ExecutorCompletionService 不是一个真正的执行器，参数 this 才是真正的执行器\n        // 它对执行器进行了包装，每个任务结束后，将结果保存到内部的一个 completionQueue 队列中\n        // 这也是为什么这个类的名字里面有个 Completion 的原因吧。\n        ExecutorCompletionService<T> ecs =\n            new ExecutorCompletionService<T>(this);\n        try {\n            // 用于保存异常信息，此方法如果没有得到任何有效的结果，那么我们可以抛出最后得到的一个异常\n            ExecutionException ee = null;\n            long lastTime = timed ? System.nanoTime() : 0;\n            Iterator<? extends Callable<T>> it = tasks.iterator();\n\n            // 首先先提交一个任务，后面的任务到下面的 for 循环一个个提交\n            futures.add(ecs.submit(it.next()));\n            // 提交了一个任务，所以任务数量减 1\n            --ntasks;\n            // 正在执行的任务数(提交的时候 +1，任务结束的时候 -1)\n            int active = 1;\n\n            for (;;) {\n                // ecs 上面说了，其内部有一个 completionQueue 用于保存执行完成的结果\n                // BlockingQueue 的 poll 方法不阻塞，返回 null 代表队列为空\n                Future<T> f = ecs.poll();\n                // 为 null，说明刚刚提交的第一个线程还没有执行完成\n                // 在前面先提交一个任务，加上这里做一次检查，也是为了提高性能\n                if (f == null) {\n                    if (ntasks > 0) {\n                        --ntasks;\n                        futures.add(ecs.submit(it.next()));\n                        ++active;\n                    }\n                    // 这里是 else if，不是 if。这里说明，没有任务了，同时 active 为 0 说明\n                    // 任务都执行完成了。其实我也没理解为什么这里做一次 break？\n                    // 因为我认为 active 为 0 的情况，必然从下面的 f.get() 返回了\n\n                    // 2018-02-23 感谢读者 newmicro 的 comment，\n                    //  这里的 active == 0，说明所有的任务都执行失败，那么这里是 for 循环出口\n                    else if (active == 0)\n                        break;\n                    // 这里也是 else if。这里说的是，没有任务了，但是设置了超时时间，这里检测是否超时\n                    else if (timed) {\n                        // 带等待的 poll 方法\n                        f = ecs.poll(nanos, TimeUnit.NANOSECONDS);\n                        // 如果已经超时，抛出 TimeoutException 异常，这整个方法就结束了\n                        if (f == null)\n                            throw new TimeoutException();\n                        long now = System.nanoTime();\n                        nanos -= now - lastTime;\n                        lastTime = now;\n                    }\n                    // 这里是 else。说明，没有任务需要提交，但是池中的任务没有完成，还没有超时(如果设置了超时)\n                    // take() 方法会阻塞，直到有元素返回，说明有任务结束了\n                    else\n                        f = ecs.take();\n                }\n                /*\n                 * 我感觉上面这一段并不是很好理解，这里简单说下。\n                 * 1\\. 首先，这在一个 for 循环中，我们设想每一个任务都没那么快结束，\n                 *     那么，每一次都会进到第一个分支，进行提交任务，直到将所有的任务都提交了\n                 * 2\\. 任务都提交完成后，如果设置了超时，那么 for 循环其实进入了“一直检测是否超时”\n                       这件事情上\n                 * 3\\. 如果没有设置超时机制，那么不必要检测超时，那就会阻塞在 ecs.take() 方法上，\n                       等待获取第一个执行结果\n                 * 4\\. 如果所有的任务都执行失败，也就是说 future 都返回了，\n                       但是 f.get() 抛出异常，那么从 active == 0 分支出去(感谢 newmicro 提出)\n                         // 当然，这个需要看下面的 if 分支。\n                 */\n\n                // 有任务结束了\n                if (f != null) {\n                    --active;\n                    try {\n                        // 返回执行结果，如果有异常，都包装成 ExecutionException\n                        return f.get();\n                    } catch (ExecutionException eex) {\n                        ee = eex;\n                    } catch (RuntimeException rex) {\n                        ee = new ExecutionException(rex);\n                    }\n                }\n            }// 注意看 for 循环的范围，一直到这里\n\n            if (ee == null)\n                ee = new ExecutionException();\n            throw ee;\n\n        } finally {\n            // 方法退出之前，取消其他的任务\n            for (Future<T> f : futures)\n                f.cancel(true);\n        }\n    }\n\n    public <T> T invokeAny(Collection<? extends Callable<T>> tasks)\n        throws InterruptedException, ExecutionException {\n        try {\n            return doInvokeAny(tasks, false, 0);\n        } catch (TimeoutException cannotHappen) {\n            assert false;\n            return null;\n        }\n    }\n\n    public <T> T invokeAny(Collection<? extends Callable<T>> tasks,\n                           long timeout, TimeUnit unit)\n        throws InterruptedException, ExecutionException, TimeoutException {\n        return doInvokeAny(tasks, true, unit.toNanos(timeout));\n    }\n\n    // 执行所有的任务，返回任务结果。\n    // 先不要看这个方法，我们先想想，其实我们自己提交任务到线程池，也是想要线程池执行所有的任务\n    // 只不过，我们是每次 submit 一个任务，这里以一个集合作为参数提交\n    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)\n        throws InterruptedException {\n        if (tasks == null)\n            throw new NullPointerException();\n        List<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());\n        boolean done = false;\n        try {\n            // 这个很简单\n            for (Callable<T> t : tasks) {\n                // 包装成 FutureTask\n                RunnableFuture<T> f = newTaskFor(t);\n                futures.add(f);\n                // 提交任务\n                execute(f);\n            }\n            for (Future<T> f : futures) {\n                if (!f.isDone()) {\n                    try {\n                        // 这是一个阻塞方法，直到获取到值，或抛出了异常\n                        // 这里有个小细节，其实 get 方法签名上是会抛出 InterruptedException 的\n                        // 可是这里没有进行处理，而是抛给外层去了。此异常发生于还没执行完的任务被取消了\n                        f.get();\n                    } catch (CancellationException ignore) {\n                    } catch (ExecutionException ignore) {\n                    }\n                }\n            }\n            done = true;\n            // 这个方法返回，不像其他的场景，返回 List<Future>，其实执行结果还没出来\n            // 这个方法返回是真正的返回，任务都结束了\n            return futures;\n        } finally {\n            // 为什么要这个？就是上面说的有异常的情况\n            if (!done)\n                for (Future<T> f : futures)\n                    f.cancel(true);\n        }\n    }\n\n    // 带超时的 invokeAll，我们找不同吧\n    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,\n                                         long timeout, TimeUnit unit)\n        throws InterruptedException {\n        if (tasks == null || unit == null)\n            throw new NullPointerException();\n        long nanos = unit.toNanos(timeout);\n        List<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());\n        boolean done = false;\n        try {\n            for (Callable<T> t : tasks)\n                futures.add(newTaskFor(t));\n\n            long lastTime = System.nanoTime();\n\n            Iterator<Future<T>> it = futures.iterator();\n            // 每提交一个任务，检测一次是否超时\n            while (it.hasNext()) {\n                execute((Runnable)(it.next()));\n                long now = System.nanoTime();\n                nanos -= now - lastTime;\n                lastTime = now;\n                // 超时\n                if (nanos <= 0)\n                    return futures;\n            }\n\n            for (Future<T> f : futures) {\n                if (!f.isDone()) {\n                    if (nanos <= 0)\n                        return futures;\n                    try {\n                        // 调用带超时的 get 方法，这里的参数 nanos 是剩余的时间，\n                        // 因为上面其实已经用掉了一些时间了\n                        f.get(nanos, TimeUnit.NANOSECONDS);\n                    } catch (CancellationException ignore) {\n                    } catch (ExecutionException ignore) {\n                    } catch (TimeoutException toe) {\n                        return futures;\n                    }\n                    long now = System.nanoTime();\n                    nanos -= now - lastTime;\n                    lastTime = now;\n                }\n            }\n            done = true;\n            return futures;\n        } finally {\n            if (!done)\n                for (Future<T> f : futures)\n                    f.cancel(true);\n        }\n    }\n\n}\n```\n\n到这里，我们发现，这个抽象类包装了一些基本的方法，可是像 submit、invokeAny、invokeAll 等方法，它们都没有真正开启线程来执行任务，它们都只是在方法内部调用了 execute 方法，所以最重要的 execute(Runnable runnable) 方法还没出现，需要等具体执行器来实现这个最重要的部分，这里我们要说的就是 ThreadPoolExecutor 类了。\n\n> 鉴于本文的篇幅，我觉得看到这里的读者应该已经不多了，大家都习惯了快餐文化。我写的每篇文章都力求让读者可以通过我的一篇文章而对相关内容有全面的了解，所以篇幅不免长了些。\n\n## ThreadPoolExecutor\n\nThreadPoolExecutor 是 JDK 中的线程池实现，这个类实现了一个线程池需要的各个方法，它实现了任务提交、线程管理、监控等等方法。\n\n我们可以基于它来进行业务上的扩展，以实现我们需要的其他功能，比如实现定时任务的类 ScheduledThreadPoolExecutor 就继承自 ThreadPoolExecutor。当然，这不是本文关注的重点，下面，还是赶紧进行源码分析吧。\n\n首先，我们来看看线程池实现中的几个概念和处理流程。\n\n我们先回顾下提交任务的几个方法：\n\n```\npublic Future<?> submit(Runnable task) {\n    if (task == null) throw new NullPointerException();\n    RunnableFuture<Void> ftask = newTaskFor(task, null);\n    execute(ftask);\n    return ftask;\n}\npublic <T> Future<T> submit(Runnable task, T result) {\n    if (task == null) throw new NullPointerException();\n    RunnableFuture<T> ftask = newTaskFor(task, result);\n    execute(ftask);\n    return ftask;\n}\npublic <T> Future<T> submit(Callable<T> task) {\n    if (task == null) throw new NullPointerException();\n    RunnableFuture<T> ftask = newTaskFor(task);\n    execute(ftask);\n    return ftask;\n}\n```\n\n一个最基本的概念是，submit 方法中，参数是 Runnable 类型（也有Callable 类型），这个参数不是用于 new Thread(**runnable**).start() 中的，此处的这个参数不是用于启动线程的，这里指的是**任务**，任务要做的事情是 run() 方法里面定义的或 Callable 中的 call() 方法里面定义的。\n\n初学者往往会搞混这个，因为 Runnable 总是在各个地方出现，经常把一个 Runnable 包到另一个 Runnable 中。请把它想象成有个 Task 接口，这个接口里面有一个 run() 方法。\n\n我们回过神来继续往下看，我画了一个简单的示意图来描述线程池中的一些主要的构件：\n\n![pool-1](https://www.javadoop.com/blogimages/java-thread-pool/pool-1.png)\n\n当然，上图没有考虑队列是否有界，提交任务时队列满了怎么办？什么情况下会创建新的线程？提交任务时线程池满了怎么办？空闲线程怎么关掉？这些问题下面我们会一一解决。\n\n我们经常会使用`Executors`这个工具类来快速构造一个线程池，对于初学者而言，这种工具类是很有用的，开发者不需要关注太多的细节，只要知道自己需要一个线程池，仅仅提供必需的参数就可以了，其他参数都采用作者提供的默认值。\n\n```\npublic static ExecutorService newFixedThreadPool(int nThreads) {\n    return new ThreadPoolExecutor(nThreads, nThreads,\n                                  0L, TimeUnit.MILLISECONDS,\n                                  new LinkedBlockingQueue<Runnable>());\n}\npublic static ExecutorService newCachedThreadPool() {\n    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,\n                                  60L, TimeUnit.SECONDS,\n                                  new SynchronousQueue<Runnable>());\n}\n```\n\n这里先不说有什么区别，它们最终都会导向这个构造方法：\n\n```\n    public ThreadPoolExecutor(int corePoolSize,\n                              int maximumPoolSize,\n                              long keepAliveTime,\n                              TimeUnit unit,\n                              BlockingQueue<Runnable> workQueue,\n                              ThreadFactory threadFactory,\n                              RejectedExecutionHandler handler) {\n        if (corePoolSize < 0 ||\n            maximumPoolSize <= 0 ||\n            maximumPoolSize < corePoolSize ||\n            keepAliveTime < 0)\n            throw new IllegalArgumentException();\n        // 这几个参数都是必须要有的\n        if (workQueue == null || threadFactory == null || handler == null)\n            throw new NullPointerException();\n\n        this.corePoolSize = corePoolSize;\n        this.maximumPoolSize = maximumPoolSize;\n        this.workQueue = workQueue;\n        this.keepAliveTime = unit.toNanos(keepAliveTime);\n        this.threadFactory = threadFactory;\n        this.handler = handler;\n    }\n```\n\n基本上，上面的构造方法中列出了我们最需要关心的几个属性了，下面逐个介绍下构造方法中出现的这几个属性：\n\n*   corePoolSize\n\n    > 核心线程数，不要抠字眼，反正先记着有这么个属性就可以了。\n\n*   maximumPoolSize\n\n    > 最大线程数，线程池允许创建的最大线程数。\n\n*   workQueue\n\n    > 任务队列，BlockingQueue 接口的某个实现（常使用 ArrayBlockingQueue 和 LinkedBlockingQueue）。\n\n*   keepAliveTime\n\n    > 空闲线程的保活时间，如果某线程的空闲时间超过这个值都没有任务给它做，那么可以被关闭了。注意这个值并不会对所有线程起作用，如果线程池中的线程数少于等于核心线程数 corePoolSize，那么这些线程不会因为空闲太长时间而被关闭，当然，也可以通过调用`allowCoreThreadTimeOut(true)`使核心线程数内的线程也可以被回收。\n\n*   threadFactory\n\n    > 用于生成线程，一般我们可以用默认的就可以了。通常，我们可以通过它将我们的线程的名字设置得比较可读一些，如 Message-Thread-1， Message-Thread-2 类似这样。\n\n*   handler：\n\n    > 当线程池已经满了，但是又有新的任务提交的时候，该采取什么策略由这个来指定。有几种方式可供选择，像抛出异常、直接拒绝然后返回等，也可以自己实现相应的接口实现自己的逻辑，这个之后再说。\n\n除了上面几个属性外，我们再看看其他重要的属性。\n\nDoug Lea 采用一个 32 位的整数来存放线程池的状态和当前池中的线程数，其中高 3 位用于存放线程池状态，低 29 位表示线程数（即使只有 29 位，也已经不小了，大概 5 亿多，现在还没有哪个机器能起这么多线程的吧）。我们知道，java 语言在整数编码上是统一的，都是采用补码的形式，下面是简单的移位操作和布尔操作，都是挺简单的。\n\n```\n\nprivate final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));\n\n// 这里 COUNT_BITS 设置为 29(32-3)，意味着前三位用于存放线程状态，后29位用于存放线程数\n// 很多初学者很喜欢在自己的代码中写很多 29 这种数字，或者某个特殊的字符串，然后分布在各个地方，这是非常糟糕的\nprivate static final int COUNT_BITS = Integer.SIZE - 3;\n\n// 000 11111111111111111111111111111\n// 这里得到的是 29 个 1，也就是说线程池的最大线程数是 2^29-1=536870911\n// 以我们现在计算机的实际情况，这个数量还是够用的\nprivate static final int CAPACITY   = (1 << COUNT_BITS) - 1;\n\n// 我们说了，线程池的状态存放在高 3 位中\n// 运算结果为 111跟29个0：111 00000000000000000000000000000\nprivate static final int RUNNING    = -1 << COUNT_BITS;\n// 000 00000000000000000000000000000\nprivate static final int SHUTDOWN   =  0 << COUNT_BITS;\n// 001 00000000000000000000000000000\nprivate static final int STOP       =  1 << COUNT_BITS;\n// 010 00000000000000000000000000000\nprivate static final int TIDYING    =  2 << COUNT_BITS;\n// 011 00000000000000000000000000000\nprivate static final int TERMINATED =  3 << COUNT_BITS;\n\n// 将整数 c 的低 29 位修改为 0，就得到了线程池的状态\nprivate static int runStateOf(int c)     { return c & ~CAPACITY; }\n// 将整数 c 的高 3 为修改为 0，就得到了线程池中的线程数\nprivate static int workerCountOf(int c)  { return c & CAPACITY; }\n\nprivate static int ctlOf(int rs, int wc) { return rs | wc; }\n\n/*\n * Bit field accessors that don't require unpacking ctl.\n * These depend on the bit layout and on workerCount being never negative.\n */\n\nprivate static boolean runStateLessThan(int c, int s) {\n    return c < s;\n}\n\nprivate static boolean runStateAtLeast(int c, int s) {\n    return c >= s;\n}\n\nprivate static boolean isRunning(int c) {\n    return c < SHUTDOWN;\n}\n```\n\n上面就是对一个整数的简单的位操作，几个操作方法将会在后面的源码中一直出现，所以读者最好把方法名字和其代表的功能记住，看源码的时候也就不需要来来回回翻了。\n\n在这里，介绍下线程池中的各个状态和状态变化的转换过程：\n\n*   RUNNING：这个没什么好说的，这是最正常的状态：接受新的任务，处理等待队列中的任务\n*   SHUTDOWN：不接受新的任务提交，但是会继续处理等待队列中的任务\n*   STOP：不接受新的任务提交，不再处理等待队列中的任务，中断正在执行任务的线程\n*   TIDYING：所有的任务都销毁了，workCount 为 0。线程池的状态在转换为 TIDYING 状态时，会执行钩子方法 terminated()\n*   TERMINATED：terminated() 方法结束后，线程池的状态就会变成这个\n\n> RUNNING 定义为 -1，SHUTDOWN 定义为 0，其他的都比 0 大，所以等于 0 的时候不能提交任务，大于 0 的话，连正在执行的任务也需要中断。\n\n看了这几种状态的介绍，读者大体也可以猜到十之八九的状态转换了，各个状态的转换过程有以下几种：\n\n*   RUNNING -> SHUTDOWN：当调用了 shutdown() 后，会发生这个状态转换，这也是最重要的\n*   (RUNNING or SHUTDOWN) -> STOP：当调用 shutdownNow() 后，会发生这个状态转换，这下要清楚 shutDown() 和 shutDownNow() 的区别了\n*   SHUTDOWN -> TIDYING：当任务队列和线程池都清空后，会由 SHUTDOWN 转换为 TIDYING\n*   STOP -> TIDYING：当任务队列清空后，发生这个转换\n*   TIDYING -> TERMINATED：这个前面说了，当 terminated() 方法结束后\n\n上面的几个记住核心的就可以了，尤其第一个和第二个。\n\n另外，我们还要看看一个内部类 Worker，因为 Doug Lea 把线程池中的线程包装成了一个个 Worker，翻译成工人，就是线程池中做任务的线程。所以到这里，我们知道**任务是 Runnable（内部变量名叫 task 或 command），线程是 Worker**。\n\nWorker 这里又用到了抽象类 AbstractQueuedSynchronizer。题外话，AQS 在并发中真的是到处出现，而且非常容易使用，写少量的代码就能实现自己需要的同步方式（对 AQS 源码感兴趣的读者请参看我之前写的几篇文章）。\n\n```\nprivate final class Worker\n    extends AbstractQueuedSynchronizer\n    implements Runnable\n{\n    private static final long serialVersionUID = 6138294804551838833L;\n\n    // 这个是真正的线程，任务靠你啦\n    final Thread thread;\n\n    // 前面说了，这里的 Runnable 是任务。为什么叫 firstTask？因为在创建线程的时候，如果同时指定了\n    // 这个线程起来以后需要执行的第一个任务，那么第一个任务就是存放在这里的(线程可不止执行这一个任务)\n    // 当然了，也可以为 null，这样线程起来了，自己到任务队列（BlockingQueue）中取任务（getTask 方法）就行了\n    Runnable firstTask;\n\n    // 用于存放此线程完成的任务数，注意了，这里用了 volatile，保证可见性\n    volatile long completedTasks;\n\n    // Worker 只有这一个构造方法，传入 firstTask，也可以传 null\n    Worker(Runnable firstTask) {\n        setState(-1); // inhibit interrupts until runWorker\n        this.firstTask = firstTask;\n        // 调用 ThreadFactory 来创建一个新的线程\n        this.thread = getThreadFactory().newThread(this);\n    }\n\n    // 这里调用了外部类的 runWorker 方法\n    public void run() {\n        runWorker(this);\n    }\n\n    ...// 其他几个方法没什么好看的，就是用 AQS 操作，来获取这个线程的执行权，用了独占锁\n}\n```\n\n前面虽然啰嗦，但是简单。有了上面的这些基础后，我们终于可以看看 ThreadPoolExecutor 的 execute 方法了，前面源码分析的时候也说了，各种方法都最终依赖于 execute 方法：\n\n```\npublic void execute(Runnable command) {\n    if (command == null)\n        throw new NullPointerException();\n\n    // 前面说的那个表示 “线程池状态” 和 “线程数” 的整数\n    int c = ctl.get();\n\n    // 如果当前线程数少于核心线程数，那么直接添加一个 worker 来执行任务，\n    // 创建一个新的线程，并把当前任务 command 作为这个线程的第一个任务(firstTask)\n    if (workerCountOf(c) < corePoolSize) {\n        // 添加任务成功，那么就结束了。提交任务嘛，线程池已经接受了这个任务，这个方法也就可以返回了\n        // 至于执行的结果，到时候会包装到 FutureTask 中。\n        // 返回 false 代表线程池不允许提交任务\n        if (addWorker(command, true))\n            return;\n        c = ctl.get();\n    }\n    // 到这里说明，要么当前线程数大于等于核心线程数，要么刚刚 addWorker 失败了\n\n    // 如果线程池处于 RUNNING 状态，把这个任务添加到任务队列 workQueue 中\n    if (isRunning(c) && workQueue.offer(command)) {\n        /* 这里面说的是，如果任务进入了 workQueue，我们是否需要开启新的线程\n         * 因为线程数在 [0, corePoolSize) 是无条件开启新的线程\n         * 如果线程数已经大于等于 corePoolSize，那么将任务添加到队列中，然后进到这里\n         */\n        int recheck = ctl.get();\n        // 如果线程池已不处于 RUNNING 状态，那么移除已经入队的这个任务，并且执行拒绝策略\n        if (! isRunning(recheck) && remove(command))\n            reject(command);\n        // 如果线程池还是 RUNNING 的，并且线程数为 0，那么开启新的线程\n        // 到这里，我们知道了，这块代码的真正意图是：担心任务提交到队列中了，但是线程都关闭了\n        else if (workerCountOf(recheck) == 0)\n            addWorker(null, false);\n    }\n    // 如果 workQueue 队列满了，那么进入到这个分支\n    // 以 maximumPoolSize 为界创建新的 worker，\n    // 如果失败，说明当前线程数已经达到 maximumPoolSize，执行拒绝策略\n    else if (!addWorker(command, false))\n        reject(command);\n}\n```\n\n> 对创建线程的错误理解：如果线程数少于 corePoolSize，创建一个线程，如果线程数在 [corePoolSize, maximumPoolSize] 之间那么可以创建线程或复用空闲线程，keepAliveTime 对这个区间的线程有效。\n> \n> 从上面的几个分支，我们就可以看出，上面的这段话是错误的。\n\n上面这些一时半会也不可能全部消化搞定，我们先继续往下吧，到时候再回头看几遍。\n\n这个方法非常重要 addWorker(Runnable firstTask, boolean core) 方法，我们看看它是怎么创建新的线程的：\n\n```\n// 第一个参数是准备提交给这个线程执行的任务，之前说了，可以为 null\n// 第二个参数为 true 代表使用核心线程数 corePoolSize 作为创建线程的界限，也就说创建这个线程的时候，\n//         如果线程池中的线程总数已经达到 corePoolSize，那么不能响应这次创建线程的请求\n//         如果是 false，代表使用最大线程数 maximumPoolSize 作为界限\nprivate boolean addWorker(Runnable firstTask, boolean core) {\n    retry:\n    for (;;) {\n        int c = ctl.get();\n        int rs = runStateOf(c);\n\n        // 这个非常不好理解\n        // 如果线程池已关闭，并满足以下条件之一，那么不创建新的 worker：\n        // 1\\. 线程池状态大于 SHUTDOWN，其实也就是 STOP, TIDYING, 或 TERMINATED\n        // 2\\. firstTask != null\n        // 3\\. workQueue.isEmpty()\n        // 简单分析下：\n        // 还是状态控制的问题，当线程池处于 SHUTDOWN 的时候，不允许提交任务，但是已有的任务继续执行\n        // 当状态大于 SHUTDOWN 时，不允许提交任务，且中断正在执行的任务\n        // 多说一句：如果线程池处于 SHUTDOWN，但是 firstTask 为 null，且 workQueue 非空，那么是允许创建 worker 的\n        // 这是因为 SHUTDOWN 的语义：不允许提交新的任务，但是要把已经进入到 workQueue 的任务执行完，所以在满足条件的基础上，是允许创建新的 Worker 的\n        if (rs >= SHUTDOWN &&\n            ! (rs == SHUTDOWN &&\n               firstTask == null &&\n               ! workQueue.isEmpty()))\n            return false;\n\n        for (;;) {\n            int wc = workerCountOf(c);\n            if (wc >= CAPACITY ||\n                wc >= (core ? corePoolSize : maximumPoolSize))\n                return false;\n            // 如果成功，那么就是所有创建线程前的条件校验都满足了，准备创建线程执行任务了\n            // 这里失败的话，说明有其他线程也在尝试往线程池中创建线程\n            if (compareAndIncrementWorkerCount(c))\n                break retry;\n            // 由于有并发，重新再读取一下 ctl\n            c = ctl.get();\n            // 正常如果是 CAS 失败的话，进到下一个里层的for循环就可以了\n            // 可是如果是因为其他线程的操作，导致线程池的状态发生了变更，如有其他线程关闭了这个线程池\n            // 那么需要回到外层的for循环\n            if (runStateOf(c) != rs)\n                continue retry;\n            // else CAS failed due to workerCount change; retry inner loop\n        }\n    }\n\n    /* \n     * 到这里，我们认为在当前这个时刻，可以开始创建线程来执行任务了，\n     * 因为该校验的都校验了，至于以后会发生什么，那是以后的事，至少当前是满足条件的\n     */\n\n    // worker 是否已经启动\n    boolean workerStarted = false;\n    // 是否已将这个 worker 添加到 workers 这个 HashSet 中\n    boolean workerAdded = false;\n    Worker w = null;\n    try {\n        final ReentrantLock mainLock = this.mainLock;\n        // 把 firstTask 传给 worker 的构造方法\n        w = new Worker(firstTask);\n        // 取 worker 中的线程对象，之前说了，Worker的构造方法会调用 ThreadFactory 来创建一个新的线程\n        final Thread t = w.thread;\n        if (t != null) {\n            // 这个是整个线程池的全局锁，持有这个锁才能让下面的操作“顺理成章”，\n            // 因为关闭一个线程池需要这个锁，至少我持有锁的期间，线程池不会被关闭\n            mainLock.lock();\n            try {\n\n                int c = ctl.get();\n                int rs = runStateOf(c);\n\n                // 小于 SHUTTDOWN 那就是 RUNNING，这个自不必说，是最正常的情况\n                // 如果等于 SHUTDOWN，前面说了，不接受新的任务，但是会继续执行等待队列中的任务\n                if (rs < SHUTDOWN ||\n                    (rs == SHUTDOWN && firstTask == null)) {\n                    // worker 里面的 thread 可不能是已经启动的\n                    if (t.isAlive())\n                        throw new IllegalThreadStateException();\n                    // 加到 workers 这个 HashSet 中\n                    workers.add(w);\n                    int s = workers.size();\n                    // largestPoolSize 用于记录 workers 中的个数的最大值\n                    // 因为 workers 是不断增加减少的，通过这个值可以知道线程池的大小曾经达到的最大值\n                    if (s > largestPoolSize)\n                        largestPoolSize = s;\n                    workerAdded = true;\n                }\n            } finally {\n                mainLock.unlock();\n            }\n            // 添加成功的话，启动这个线程\n            if (workerAdded) {\n                // 启动线程\n                t.start();\n                workerStarted = true;\n            }\n        }\n    } finally {\n        // 如果线程没有启动，需要做一些清理工作，如前面 workCount 加了 1，将其减掉\n        if (! workerStarted)\n            addWorkerFailed(w);\n    }\n    // 返回线程是否启动成功\n    return workerStarted;\n}\n```\n\n简单看下 addWorkFailed 的处理：\n\n```\n// workers 中删除掉相应的 worker\n// workCount 减 1\nprivate void addWorkerFailed(Worker w) {\n    final ReentrantLock mainLock = this.mainLock;\n    mainLock.lock();\n    try {\n        if (w != null)\n            workers.remove(w);\n        decrementWorkerCount();\n        // rechecks for termination, in case the existence of this worker was holding up termination\n        tryTerminate();\n    } finally {\n        mainLock.unlock();\n    }\n}\n```\n\n回过头来，继续往下走。我们知道，worker 中的线程 start 后，其 run 方法会调用 runWorker 方法：\n\n```\n// Worker 类的 run() 方法\npublic void run() {\n    runWorker(this);\n}\n```\n\n继续往下看 runWorker 方法：\n\n```\n// 此方法由 worker 线程启动后调用，这里用一个 while 循环来不断地从等待队列中获取任务并执行\n// 前面说了，worker 在初始化的时候，可以指定 firstTask，那么第一个任务也就可以不需要从队列中获取\nfinal void runWorker(Worker w) {\n    // \n    Thread wt = Thread.currentThread();\n    // 该线程的第一个任务(如果有的话)\n    Runnable task = w.firstTask;\n    w.firstTask = null;\n    w.unlock(); // allow interrupts\n    boolean completedAbruptly = true;\n    try {\n        // 循环调用 getTask 获取任务\n        while (task != null || (task = getTask()) != null) {\n            w.lock();          \n            // 如果线程池状态大于等于 STOP，那么意味着该线程也要中断\n            if ((runStateAtLeast(ctl.get(), STOP) ||\n                 (Thread.interrupted() &&\n                  runStateAtLeast(ctl.get(), STOP))) &&\n                !wt.isInterrupted())\n                wt.interrupt();\n            try {\n                // 这是一个钩子方法，留给需要的子类实现\n                beforeExecute(wt, task);\n                Throwable thrown = null;\n                try {\n                    // 到这里终于可以执行任务了\n                    task.run();\n                } catch (RuntimeException x) {\n                    thrown = x; throw x;\n                } catch (Error x) {\n                    thrown = x; throw x;\n                } catch (Throwable x) {\n                    // 这里不允许抛出 Throwable，所以转换为 Error\n                    thrown = x; throw new Error(x);\n                } finally {\n                    // 也是一个钩子方法，将 task 和异常作为参数，留给需要的子类实现\n                    afterExecute(task, thrown);\n                }\n            } finally {\n                // 置空 task，准备 getTask 获取下一个任务\n                task = null;\n                // 累加完成的任务数\n                w.completedTasks++;\n                // 释放掉 worker 的独占锁\n                w.unlock();\n            }\n        }\n        completedAbruptly = false;\n    } finally {\n        // 如果到这里，需要执行线程关闭：\n        // 1\\. 说明 getTask 返回 null，也就是说，队列中已经没有任务需要执行了，执行关闭\n        // 2\\. 任务执行过程中发生了异常\n        // 第一种情况，已经在代码处理了将 workCount 减 1，这个在 getTask 方法分析中会说\n        // 第二种情况，workCount 没有进行处理，所以需要在 processWorkerExit 中处理\n        // 限于篇幅，我不准备分析这个方法了，感兴趣的读者请自行分析源码\n        processWorkerExit(w, completedAbruptly);\n    }\n}\n```\n\n我们看看 getTask() 是怎么获取任务的，这个方法写得真的很好，每一行都很简单，组合起来却所有的情况都想好了：\n\n```\n// 此方法有三种可能：\n// 1\\. 阻塞直到获取到任务返回。我们知道，默认 corePoolSize 之内的线程是不会被回收的，\n//      它们会一直等待任务\n// 2\\. 超时退出。keepAliveTime 起作用的时候，也就是如果这么多时间内都没有任务，那么应该执行关闭\n// 3\\. 如果发生了以下条件，此方法必须返回 null:\n//    - 池中有大于 maximumPoolSize 个 workers 存在(通过调用 setMaximumPoolSize 进行设置)\n//    - 线程池处于 SHUTDOWN，而且 workQueue 是空的，前面说了，这种不再接受新的任务\n//    - 线程池处于 STOP，不仅不接受新的线程，连 workQueue 中的线程也不再执行\nprivate Runnable getTask() {\n    boolean timedOut = false; // Did the last poll() time out?\n\n    retry:\n    for (;;) {\n        int c = ctl.get();\n        int rs = runStateOf(c);\n        // 两种可能\n        // 1\\. rs == SHUTDOWN && workQueue.isEmpty()\n        // 2\\. rs >= STOP\n        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {\n            // CAS 操作，减少工作线程数\n            decrementWorkerCount();\n            return null;\n        }\n\n        boolean timed;      // Are workers subject to culling?\n        for (;;) {\n            int wc = workerCountOf(c);\n            // 允许核心线程数内的线程回收，或当前线程数超过了核心线程数，那么有可能发生超时关闭\n            timed = allowCoreThreadTimeOut || wc > corePoolSize;\n\n            // 这里 break，是为了不往下执行后一个 if (compareAndDecrementWorkerCount(c))\n            // 两个 if 一起看：如果当前线程数 wc > maximumPoolSize，或者超时，都返回 null\n            // 那这里的问题来了，wc > maximumPoolSize 的情况，为什么要返回 null？\n            //    换句话说，返回 null 意味着关闭线程。\n            // 那是因为有可能开发者调用了 setMaximumPoolSize() 将线程池的 maximumPoolSize 调小了，那么多余的 Worker 就需要被关闭\n            if (wc <= maximumPoolSize && ! (timedOut && timed))\n                break;\n            if (compareAndDecrementWorkerCount(c))\n                return null;\n            c = ctl.get();  // Re-read ctl\n            // compareAndDecrementWorkerCount(c) 失败，线程池中的线程数发生了改变\n            if (runStateOf(c) != rs)\n                continue retry;\n            // else CAS failed due to workerCount change; retry inner loop\n        }\n        // wc <= maximumPoolSize 同时没有超时\n        try {\n            // 到 workQueue 中获取任务\n            Runnable r = timed ?\n                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :\n                workQueue.take();\n            if (r != null)\n                return r;\n            timedOut = true;\n        } catch (InterruptedException retry) {\n            // 如果此 worker 发生了中断，采取的方案是重试\n            // 解释下为什么会发生中断，这个读者要去看 setMaximumPoolSize 方法。\n\n            // 如果开发者将 maximumPoolSize 调小了，导致其小于当前的 workers 数量，\n            // 那么意味着超出的部分线程要被关闭。重新进入 for 循环，自然会有部分线程会返回 null\n            timedOut = false;\n        }\n    }\n}\n```\n\n到这里，基本上也说完了整个流程，读者这个时候应该回到 execute(Runnable command) 方法，看看各个分支，我把代码贴过来一下：\n\n```\npublic void execute(Runnable command) {\n    if (command == null)\n        throw new NullPointerException();\n\n    // 前面说的那个表示 “线程池状态” 和 “线程数” 的整数\n    int c = ctl.get();\n\n    // 如果当前线程数少于核心线程数，那么直接添加一个 worker 来执行任务，\n    // 创建一个新的线程，并把当前任务 command 作为这个线程的第一个任务(firstTask)\n    if (workerCountOf(c) < corePoolSize) {\n        // 添加任务成功，那么就结束了。提交任务嘛，线程池已经接受了这个任务，这个方法也就可以返回了\n        // 至于执行的结果，到时候会包装到 FutureTask 中。\n        // 返回 false 代表线程池不允许提交任务\n        if (addWorker(command, true))\n            return;\n        c = ctl.get();\n    }\n    // 到这里说明，要么当前线程数大于等于核心线程数，要么刚刚 addWorker 失败了\n\n    // 如果线程池处于 RUNNING 状态，把这个任务添加到任务队列 workQueue 中\n    if (isRunning(c) && workQueue.offer(command)) {\n        /* 这里面说的是，如果任务进入了 workQueue，我们是否需要开启新的线程\n         * 因为线程数在 [0, corePoolSize) 是无条件开启新的线程\n         * 如果线程数已经大于等于 corePoolSize，那么将任务添加到队列中，然后进到这里\n         */\n        int recheck = ctl.get();\n        // 如果线程池已不处于 RUNNING 状态，那么移除已经入队的这个任务，并且执行拒绝策略\n        if (! isRunning(recheck) && remove(command))\n            reject(command);\n        // 如果线程池还是 RUNNING 的，并且线程数为 0，那么开启新的线程\n        // 到这里，我们知道了，这块代码的真正意图是：担心任务提交到队列中了，但是线程都关闭了\n        else if (workerCountOf(recheck) == 0)\n            addWorker(null, false);\n    }\n    // 如果 workQueue 队列满了，那么进入到这个分支\n    // 以 maximumPoolSize 为界创建新的 worker，\n    // 如果失败，说明当前线程数已经达到 maximumPoolSize，执行拒绝策略\n    else if (!addWorker(command, false))\n        reject(command);\n}\n```\n\n上面各个分支中，有两种情况会调用 reject(command) 来处理任务，因为按照正常的流程，线程池此时不能接受这个任务，所以需要执行我们的拒绝策略。接下来，我们说一说 ThreadPoolExecutor 中的拒绝策略。\n\n```\nfinal void reject(Runnable command) {\n    // 执行拒绝策略\n    handler.rejectedExecution(command, this);\n}\n```\n\n此处的 handler 我们需要在构造线程池的时候就传入这个参数，它是 RejectedExecutionHandler 的实例。\n\nRejectedExecutionHandler 在 ThreadPoolExecutor 中有四个已经定义好的实现类可供我们直接使用，当然，我们也可以实现自己的策略，不过一般也没有必要。\n\n```\n// 只要线程池没有被关闭，那么由提交任务的线程自己来执行这个任务。\npublic static class CallerRunsPolicy implements RejectedExecutionHandler {\n    public CallerRunsPolicy() { }\n    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {\n        if (!e.isShutdown()) {\n            r.run();\n        }\n    }\n}\n\n// 不管怎样，直接抛出 RejectedExecutionException 异常\n// 这个是默认的策略，如果我们构造线程池的时候不传相应的 handler 的话，那就会指定使用这个\npublic static class AbortPolicy implements RejectedExecutionHandler {\n    public AbortPolicy() { }\n    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {\n        throw new RejectedExecutionException(\"Task \" + r.toString() +\n                                             \" rejected from \" +\n                                             e.toString());\n    }\n}\n\n// 不做任何处理，直接忽略掉这个任务\npublic static class DiscardPolicy implements RejectedExecutionHandler {\n    public DiscardPolicy() { }\n    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {\n    }\n}\n\n// 这个相对霸道一点，如果线程池没有被关闭的话，\n// 把队列队头的任务(也就是等待了最长时间的)直接扔掉，然后提交这个任务到等待队列中\npublic static class DiscardOldestPolicy implements RejectedExecutionHandler {\n    public DiscardOldestPolicy() { }\n    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {\n        if (!e.isShutdown()) {\n            e.getQueue().poll();\n            e.execute(r);\n        }\n    }\n}\n```\n\n到这里，ThreadPoolExecutor 的源码算是分析结束了。单纯从源码的难易程度来说，ThreadPoolExecutor 的源码还算是比较简单的，只是需要我们静下心来好好看看罢了。\n\n## Executors\n\n这节其实也不是分析 Executors 这个类，因为它仅仅是工具类，它的所有方法都是 static 的。\n\n*   生成一个固定大小的线程池：\n\n```\npublic static ExecutorService newFixedThreadPool(int nThreads) {\n    return new ThreadPoolExecutor(nThreads, nThreads,\n                                  0L, TimeUnit.MILLISECONDS,\n                                  new LinkedBlockingQueue<Runnable>());\n}\n```\n\n最大线程数设置为与核心线程数相等，此时 keepAliveTime 设置为 0（因为这里它是没用的，即使不为 0，线程池默认也不会回收 corePoolSize 内的线程），任务队列采用 LinkedBlockingQueue，无界队列。\n\n过程分析：刚开始，每提交一个任务都创建一个 worker，当 worker 的数量达到 nThreads 后，不再创建新的线程，而是把任务提交到 LinkedBlockingQueue 中，而且之后线程数始终为 nThreads。\n\n*   生成只有**一个线程**的固定线程池，这个更简单，和上面的一样，只要设置线程数为 1 就可以了：\n\n```\npublic static ExecutorService newSingleThreadExecutor() {\n    return new FinalizableDelegatedExecutorService\n        (new ThreadPoolExecutor(1, 1,\n                                0L, TimeUnit.MILLISECONDS,\n                                new LinkedBlockingQueue<Runnable>()));\n}\n```\n\n*   生成一个需要的时候就创建新的线程，同时可以复用之前创建的线程（如果这个线程当前没有任务）的线程池：\n\n```\npublic static ExecutorService newCachedThreadPool() {\n    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,\n                                  60L, TimeUnit.SECONDS,\n                                  new SynchronousQueue<Runnable>());\n}\n```\n\n核心线程数为 0，最大线程数为 Integer.MAX_VALUE，keepAliveTime 为 60 秒，任务队列采用 SynchronousQueue。\n\n这种线程池对于任务可以比较快速地完成的情况有比较好的性能。如果线程空闲了 60 秒都没有任务，那么将关闭此线程并从线程池中移除。所以如果线程池空闲了很长时间也不会有问题，因为随着所有的线程都会被关闭，整个线程池不会占用任何的系统资源。\n\n过程分析：我把 execute 方法的主体黏贴过来，让大家看得明白些。鉴于 corePoolSize 是 0，那么提交任务的时候，直接将任务提交到队列中，由于采用了 SynchronousQueue，所以如果是第一个任务提交的时候，offer 方法肯定会返回 false，因为此时没有任何 worker 对这个任务进行接收，那么将进入到最后一个分支来创建第一个 worker。之后再提交任务的话，取决于是否有空闲下来的线程对任务进行接收，如果有，会进入到第二个 if 语句块中，否则就是和第一个任务一样，进到最后的 else if 分支创建新线程。\n\n```\nint c = ctl.get();\n// corePoolSize 为 0，所以不会进到这个 if 分支\nif (workerCountOf(c) < corePoolSize) {\n    if (addWorker(command, true))\n        return;\n    c = ctl.get();\n}\n// offer 如果有空闲线程刚好可以接收此任务，那么返回 true，否则返回 false\nif (isRunning(c) && workQueue.offer(command)) {\n    int recheck = ctl.get();\n    if (! isRunning(recheck) && remove(command))\n        reject(command);\n    else if (workerCountOf(recheck) == 0)\n        addWorker(null, false);\n}\nelse if (!addWorker(command, false))\n    reject(command);\n```\n\n> SynchronousQueue 是一个比较特殊的 BlockingQueue，其本身不储存任何元素，它有一个虚拟队列（或虚拟栈），不管读操作还是写操作，如果当前队列中存储的是与当前操作相同模式的线程，那么当前操作也进入队列中等待；如果是相反模式，则配对成功，从当前队列中取队头节点。具体的信息，可以看我的另一篇关于 BlockingQueue 的文章。\n\n## getTask\n  前文已经分析了`runWorker`方法，我们可以看到该方法中有一行这样的代码\n  ```java\n  //...\n   while (task != null || (task = getTask()) != null) {\n  //...\n   ```\n  这一行代码如果`task!=null`，即前一个条件为`true`时意味着传入的一个非`null`的`task`，同时代码会优化使得第二个条件不去执行；那么前一个条件为`false`时，就会尝试调用`getTask`方法。在这一个方法中，`worker`会尝试从工作队列中取出任务来进行执行。\n  ```java\n   private Runnable getTask() {\n        boolean timedOut = false;\n        //不断尝试获得任务\n        for (;;) {\n            int c = ctl.get();\n            int rs = runStateOf(c);\n            // 前文已有相似的状态，这里不再赘述\n            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {\n                decrementWorkerCount();\n                return null;\n            }\n            int wc = workerCountOf(c);\n            //是否设置了允许核心线程超时（设置了之后核心线程在超时后会销毁），或者当前worker数量比核心池大\n            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;\n          \n            if ((wc > maximumPoolSize || (timed && timedOut))&& (wc > 1 || workQueue.isEmpty())) {\n                if (compareAndDecrementWorkerCount(c))\n                    return null;\n                continue;\n            }\n            try {\n                //是否设置了允许核心线程超时（设置了之后核心线程在超时后会销毁），或者当前worker数量比核心池大\n                //如果是调用queue的poll(int,TimeUnit)方法，否则直接调用take方法\n                Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take();\n                //能获取到非空的任务就返回\n                //对于不设置允许核心线程超时的情况，核心线程就一直在getTask的这个循环中\n                //一直等待有新的任务来执行\n                if (r != null)\n                    return r;\n                timedOut = true;\n            } catch (InterruptedException retry) {\n                timedOut = false;\n            }\n        }\n    }\n    ```\n## 总结\n\n我一向不喜欢写总结，因为我把所有需要表达的都写在正文中了，写小篇幅的总结并不能真正将话说清楚，本文的总结部分为准备面试的读者而写，希望能帮到面试者或者没有足够的时间看完全文的读者。\n\n1.  java 线程池有哪些关键属性？\n\n    > corePoolSize，maximumPoolSize，workQueue，keepAliveTime，rejectedExecutionHandler\n    > \n    > corePoolSize 到 maximumPoolSize 之间的线程会被回收，当然 corePoolSize 的线程也可以通过设置而得到回收（allowCoreThreadTimeOut(true)）。\n    > \n    > workQueue 用于存放任务，添加任务的时候，如果当前线程数超过了 corePoolSize，那么往该队列中插入任务，线程池中的线程会负责到队列中拉取任务。\n    > \n    > keepAliveTime 用于设置空闲时间，如果线程数超出了 corePoolSize，并且有些线程的空闲时间超过了这个值，会执行关闭这些线程的操作\n    > \n    > rejectedExecutionHandler 用于处理当线程池不能执行此任务时的情况，默认有**抛出 RejectedExecutionException 异常**、**忽略任务**、**使用提交任务的线程来执行此任务**和**将队列中等待最久的任务删除，然后提交此任务**这四种策略，默认为抛出异常。\n\n2.  说说线程池中的线程创建时机？\n\n    > 1.  如果当前线程数少于 corePoolSize，那么提交任务的时候创建一个新的线程，并由这个线程执行这个任务；\n    > 2.  如果当前线程数已经达到 corePoolSize，那么将提交的任务添加到队列中，等待线程池中的线程去队列中取任务；\n    > 3.  如果队列已满，那么创建新的线程来执行任务，需要保证池中的线程数不会超过 maximumPoolSize，如果此时线程数超过了 maximumPoolSize，那么执行拒绝策略。\n\n    * 注意：如果将队列设置为无界队列，那么线程数达到 corePoolSize 后，其实线程数就不会再增长了。因为后面的任务直接往队列塞就行了，此时 maximumPoolSize 参数就没有什么意义。\n\n3.  Executors.newFixedThreadPool(…) 和 Executors.newCachedThreadPool() 构造出来的线程池有什么差别？\n\n    > 细说太长，往上滑一点点，在 Executors 的小节进行了详尽的描述。\n\n4.  任务执行过程中发生异常怎么处理？\n\n    > 如果某个任务执行出现异常，那么执行任务的线程会被关闭，而不是继续接收其他任务。然后会启动一个新的线程来代替它。\n\n5.  什么时候会执行拒绝策略？\n\n    > 1.  workers 的数量达到了 corePoolSize（任务此时需要进入任务队列），任务入队成功，与此同时线程池被关闭了，而且关闭线程池并没有将这个任务出队，那么执行拒绝策略。这里说的是非常边界的问题，入队和关闭线程池并发执行，读者仔细看看 execute 方法是怎么进到第一个 reject(command) 里面的。\n    > 2.  workers 的数量大于等于 corePoolSize，将任务加入到任务队列，可是队列满了，任务入队失败，那么准备开启新的线程，可是线程数已经达到 maximumPoolSize，那么执行拒绝策略。\n\n因为本文实在太长了，所以我没有说执行结果是怎么获取的，也没有说关闭线程池相关的部分，这个就留给读者吧。\n\n本文篇幅是有点长，如果读者发现什么不对的地方，或者有需要补充的地方，请不吝提出，谢谢。\n\n（全文完）\n"
  },
  {
    "path": "docs/Java/concurrency/Java并发指南：解读Java阻塞队列BlockingQueue.md",
    "content": "# 目录\n  * [前言](#前言)\n  * [BlockingQueue](#blockingqueue)\n  * [BlockingQueue 实现之 ArrayBlockingQueue](#blockingqueue-实现之-arrayblockingqueue)\n  * [BlockingQueue 实现之 LinkedBlockingQueue](#blockingqueue-实现之-linkedblockingqueue)\n  * [BlockingQueue 实现之 SynchronousQueue](#blockingqueue-实现之-synchronousqueue)\n  * [BlockingQueue 实现之 PriorityBlockingQueue](#blockingqueue-实现之-priorityblockingqueue)\n  * [总结](#总结)\n\n本文转自：https://www.javadoop.com/\n\n**本文转载自互联网，侵删**\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章同步发于我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《Java并发指南》其中一篇，本文大部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何全面深入地学习Java并发技术，从Java多线程基础，再到并发编程的基础知识，从Java并发包的入门和实战，再到JUC的源码剖析，一步步地学习Java并发编程，并上手进行实战，以便让你更完整地了解整个Java并发编程知识体系，形成自己的知识框架。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供一些对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n<!--more -->\n## 前言\n\n最近得空，想写篇文章好好说说 java 线程池问题，我相信很多人都一知半解的，包括我自己在仔仔细细看源码之前，也有许多的不解，甚至有些地方我一直都没有理解到位。\n\n说到线程池实现，那么就不得不涉及到各种 BlockingQueue 的实现，那么我想就 BlockingQueue 的问题和大家分享分享我了解的一些知识。\n\n本文没有像之前分析 AQS 那样一行一行源码分析了，不过还是把其中最重要和最难理解的代码说了一遍，所以不免篇幅略长。本文涉及到比较多的 Doug Lea 对 BlockingQueue 的设计思想，希望有心的读者真的可以有一些收获，我觉得自己还是写了一些干货的。\n\n本文直接参考 Doug Lea 写的 Java doc 和注释，这也是我们在学习 java 并发包时最好的材料了。希望大家能有所思、有所悟，学习 Doug Lea 的代码风格，并将其优雅、严谨的作风应用到我们写的每一行代码中。\n\n**目录：**\n\n## BlockingQueue\n\n> 开篇先介绍下 BlockingQueue 这个接口的规则，后面再看其实现。\n\n首先，最基本的来说， BlockingQueue 是一个**先进先出**的队列（Queue），为什么说是阻塞（Blocking）的呢？是因为 BlockingQueue 支持当获取队列元素但是队列为空时，会阻塞等待队列中有元素再返回；也支持添加元素时，如果队列已满，那么等到队列可以放入新元素时再放入。\n\nBlockingQueue 是一个接口，继承自 Queue，所以其实现类也可以作为 Queue 的实现来使用，而 Queue 又继承自 Collection 接口。\n\nBlockingQueue 对插入操作、移除操作、获取元素操作提供了四种不同的方法用于不同的场景中使用：1、抛出异常；2、返回特殊值（null 或 true/false，取决于具体的操作）；3、阻塞等待此操作，直到这个操作成功；4、阻塞等待此操作，直到成功或者超时指定时间。总结如下：\n\n|  | _Throws exception_ | _Special value_ | _Blocks_ | _Times out_ |\n| --- | --- | --- | --- | --- |\n| **Insert** | add(e) | offer(e) | **put(e)** | offer(e, time, unit) |\n| **Remove** | remove() | poll() | **take()** | poll(time, unit) |\n| **Examine** | element() | peek() | _not applicable_ | _not applicable_ |\n\nBlockingQueue 的各个实现都遵循了这些规则，当然我们也不用死记这个表格，知道有这么回事，然后写代码的时候根据自己的需要去看方法的注释来选取合适的方法即可。\n\n> 对于 BlockingQueue，我们的关注点应该在 put(e) 和 take() 这两个方法，因为这两个方法是带阻塞的。\n\nBlockingQueue 不接受 null 值的插入，相应的方法在碰到 null 的插入时会抛出 NullPointerException 异常。null 值在这里通常用于作为特殊值返回（表格中的第三列），代表 poll 失败。所以，如果允许插入 null 值的话，那获取的时候，就不能很好地用 null 来判断到底是代表失败，还是获取的值就是 null 值。\n\n一个 BlockingQueue 可能是有界的，如果在插入的时候，发现队列满了，那么 put 操作将会阻塞。通常，在这里我们说的无界队列也不是说真正的无界，而是它的容量是 Integer.MAX_VALUE（21亿多）。\n\nBlockingQueue 是设计用来实现生产者-消费者队列的，当然，你也可以将它当做普通的 Collection 来用，前面说了，它实现了 java.util.Collection 接口。例如，我们可以用 remove(x) 来删除任意一个元素，但是，这类操作通常并不高效，所以尽量只在少数的场合使用，比如一条消息已经入队，但是需要做取消操作的时候。\n\nBlockingQueue 的实现都是线程安全的，但是批量的集合操作如`addAll`,`containsAll`,`retainAll`和`removeAll`不一定是原子操作。如 addAll(c) 有可能在添加了一些元素后中途抛出异常，此时 BlockingQueue 中已经添加了部分元素，这个是允许的，取决于具体的实现。\n\nBlockingQueue 不支持 close 或 shutdown 等**关闭**操作，因为开发者可能希望不会有新的元素添加进去，此特性取决于具体的实现，不做强制约束。\n\n最后，BlockingQueue 在生产者-消费者的场景中，是支持多消费者和多生产者的，说的其实就是线程安全问题。\n\n相信上面说的每一句都很清楚了，BlockingQueue 是一个比较简单的线程安全容器，下面我会分析其具体的在 JDK 中的实现，这里又到了 Doug Lea 表演时间了。\n\n## BlockingQueue 实现之 ArrayBlockingQueue\n\nArrayBlockingQueue 是 BlockingQueue 接口的有界队列实现类，底层采用数组来实现。\n\n其并发控制采用可重入锁来控制，不管是插入操作还是读取操作，都需要获取到锁才能进行操作。\n\n如果读者看过我之前写的《[一行一行源码分析清楚 AbstractQueuedSynchronizer（二）](https://www.javadoop.com/post/AbstractQueuedSynchronizer-2)》 的关于 Condition 的文章的话，那么你一定能很容易看懂 ArrayBlockingQueue 的源码，它采用一个 ReentrantLock 和相应的两个 Condition 来实现。\n\nArrayBlockingQueue 共有以下几个属性：\n\n```\n// 用于存放元素的数组\nfinal Object[] items;\n// 下一次读取操作的位置\nint takeIndex;\n// 下一次写入操作的位置\nint putIndex;\n// 队列中的元素数量\nint count;\n\n// 以下几个就是控制并发用的同步器\nfinal ReentrantLock lock;\nprivate final Condition notEmpty;\nprivate final Condition notFull;\n```\n\n我们用个示意图来描述其同步机制：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404201844.png)\nArrayBlockingQueue 实现并发同步的原理就是，读操作和写操作都需要获取到 AQS 独占锁才能进行操作。如果队列为空，这个时候读操作的线程进入到**读线程队列**排队，等待写线程写入新的元素，然后唤醒读线程队列的第一个等待线程。如果队列已满，这个时候写操作的线程进入到**写线程队列**排队，等待读线程将队列元素移除腾出空间，然后唤醒写线程队列的第一个等待线程。\n\n对于 ArrayBlockingQueue，我们可以在构造的时候指定以下三个参数：\n\n1.  队列容量，其限制了队列中最多允许的元素个数；\n2.  指定独占锁是公平锁还是非公平锁。非公平锁的吞吐量比较高，公平锁可以保证每次都是等待最久的线程获取到锁；\n3.  可以指定用一个集合来初始化，将此集合中的元素在构造方法期间就先添加到队列中。\n\n更具体的源码我就不进行分析了，因为它就是 AbstractQueuedSynchronizer 中 Condition 的使用，感兴趣的读者请看我写的《[一行一行源码分析清楚 AbstractQueuedSynchronizer（二）](https://www.javadoop.com/post/AbstractQueuedSynchronizer-2/)》，因为只要看懂了那篇文章，ArrayBlockingQueue 的代码就没有分析的必要了，当然，如果你完全不懂 Condition，那么基本上也就可以说看不懂 ArrayBlockingQueue 的源码了。\n\n## BlockingQueue 实现之 LinkedBlockingQueue\n\n底层基于单向链表实现的阻塞队列，可以当做无界队列也可以当做有界队列来使用。看构造方法：\n\n```\n// 传说中的无界队列\npublic LinkedBlockingQueue() {\n    this(Integer.MAX_VALUE);\n}\n```\n\n```\n// 传说中的有界队列\npublic LinkedBlockingQueue(int capacity) {\n    if (capacity <= 0) throw new IllegalArgumentException();\n    this.capacity = capacity;\n    last = head = new Node<E>(null);\n}\n```\n\n我们看看这个类有哪些属性：\n\n```\n// 队列容量\nprivate final int capacity;\n\n// 队列中的元素数量\nprivate final AtomicInteger count = new AtomicInteger(0);\n\n// 队头\nprivate transient Node<E> head;\n\n// 队尾\nprivate transient Node<E> last;\n\n// take, poll, peek 等读操作的方法需要获取到这个锁\nprivate final ReentrantLock takeLock = new ReentrantLock();\n\n// 如果读操作的时候队列是空的，那么等待 notEmpty 条件\nprivate final Condition notEmpty = takeLock.newCondition();\n\n// put, offer 等写操作的方法需要获取到这个锁\nprivate final ReentrantLock putLock = new ReentrantLock();\n\n// 如果写操作的时候队列是满的，那么等待 notFull 条件\nprivate final Condition notFull = putLock.newCondition();\n```\n\n这里用了两个锁，两个 Condition，简单介绍如下：\n\n**takeLock 和 notEmpty 怎么搭配：**如果要获取（take）一个元素，需要获取 takeLock 锁，但是获取了锁还不够，如果队列此时为空，还需要队列不为空（notEmpty）这个条件（Condition）。\n\n**putLock 需要和 notFull 搭配：**如果要插入（put）一个元素，需要获取 putLock 锁，但是获取了锁还不够，如果队列此时已满，还需要队列不是满的（notFull）这个条件（Condition）。\n\n首先，这里用一个示意图来看看 LinkedBlockingQueue 的并发读写控制，然后再开始分析源码：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404201927.png)\n看懂这个示意图，源码也就简单了，读操作是排好队的，写操作也是排好队的，唯一的并发问题在于一个写操作和一个读操作同时进行，只要控制好这个就可以了。\n\n先上构造方法：\n\n```\npublic LinkedBlockingQueue(int capacity) {\n    if (capacity <= 0) throw new IllegalArgumentException();\n    this.capacity = capacity;\n    last = head = new Node<E>(null);\n}\n```\n\n注意，这里会初始化一个空的头结点，那么第一个元素入队的时候，队列中就会有两个元素。读取元素时，也总是获取头节点后面的一个节点。count 的计数值不包括这个头节点。\n\n我们来看下 put 方法是怎么将元素插入到队尾的：\n\n```\npublic void put(E e) throws InterruptedException {\n    if (e == null) throw new NullPointerException();\n    // 如果你纠结这里为什么是 -1，可以看看 offer 方法。这就是个标识成功、失败的标志而已。\n    int c = -1;\n    Node<E> node = new Node(e);\n    final ReentrantLock putLock = this.putLock;\n    final AtomicInteger count = this.count;\n    // 必须要获取到 putLock 才可以进行插入操作\n    putLock.lockInterruptibly();\n    try {\n        // 如果队列满，等待 notFull 的条件满足。\n        while (count.get() == capacity) {\n            notFull.await();\n        }\n        // 入队\n        enqueue(node);\n        // count 原子加 1，c 还是加 1 前的值\n        c = count.getAndIncrement();\n        // 如果这个元素入队后，还有至少一个槽可以使用，调用 notFull.signal() 唤醒等待线程。\n        // 哪些线程会等待在 notFull 这个 Condition 上呢？\n        if (c + 1 < capacity)\n            notFull.signal();\n    } finally {\n        // 入队后，释放掉 putLock\n        putLock.unlock();\n    }\n    // 如果 c == 0，那么代表队列在这个元素入队前是空的（不包括head空节点），\n    // 那么所有的读线程都在等待 notEmpty 这个条件，等待唤醒，这里做一次唤醒操作\n    if (c == 0)\n        signalNotEmpty();\n}\n\n// 入队的代码非常简单，就是将 last 属性指向这个新元素，并且让原队尾的 next 指向这个元素\n// 这里入队没有并发问题，因为只有获取到 putLock 独占锁以后，才可以进行此操作\nprivate void enqueue(Node<E> node) {\n    // assert putLock.isHeldByCurrentThread();\n    // assert last.next == null;\n    last = last.next = node;\n}\n\n// 元素入队后，如果需要，调用这个方法唤醒读线程来读\nprivate void signalNotEmpty() {\n    final ReentrantLock takeLock = this.takeLock;\n    takeLock.lock();\n    try {\n        notEmpty.signal();\n    } finally {\n        takeLock.unlock();\n    }\n}\n```\n\n我们再看看 take 方法：\n\n```\npublic E take() throws InterruptedException {\n    E x;\n    int c = -1;\n    final AtomicInteger count = this.count;\n    final ReentrantLock takeLock = this.takeLock;\n    // 首先，需要获取到 takeLock 才能进行出队操作\n    takeLock.lockInterruptibly();\n    try {\n        // 如果队列为空，等待 notEmpty 这个条件满足再继续执行\n        while (count.get() == 0) {\n            notEmpty.await();\n        }\n        // 出队\n        x = dequeue();\n        // count 进行原子减 1\n        c = count.getAndDecrement();\n        // 如果这次出队后，队列中至少还有一个元素，那么调用 notEmpty.signal() 唤醒其他的读线程\n        if (c > 1)\n            notEmpty.signal();\n    } finally {\n        // 出队后释放掉 takeLock\n        takeLock.unlock();\n    }\n    // 如果 c == capacity，那么说明在这个 take 方法发生的时候，队列是满的\n    // 既然出队了一个，那么意味着队列不满了，唤醒写线程去写\n    if (c == capacity)\n        signalNotFull();\n    return x;\n}\n// 取队头，出队\nprivate E dequeue() {\n    // assert takeLock.isHeldByCurrentThread();\n    // assert head.item == null;\n    // 之前说了，头结点是空的\n    Node<E> h = head;\n    Node<E> first = h.next;\n    h.next = h; // help GC\n    // 设置这个为新的头结点\n    head = first;\n    E x = first.item;\n    first.item = null;\n    return x;\n}\n// 元素出队后，如果需要，调用这个方法唤醒写线程来写\nprivate void signalNotFull() {\n    final ReentrantLock putLock = this.putLock;\n    putLock.lock();\n    try {\n        notFull.signal();\n    } finally {\n        putLock.unlock();\n    }\n}\n```\n\n源码分析就到这里结束了吧，毕竟还是比较简单的源码，基本上只要读者认真点都看得懂。\n\n## BlockingQueue 实现之 SynchronousQueue\n\n它是一个特殊的队列，它的名字其实就蕴含了它的特征 - - 同步的队列。为什么说是同步的呢？这里说的并不是多线程的并发问题，而是因为当一个线程往队列中写入一个元素时，写入操作不会立即返回，需要等待另一个线程来将这个元素拿走；同理，当一个读线程做读操作的时候，同样需要一个相匹配的写线程的写操作。这里的 Synchronous 指的就是读线程和写线程需要同步，一个读线程匹配一个写线程。\n\n我们比较少使用到 SynchronousQueue 这个类，不过它在线程池的实现类 ThreadPoolExecutor 中得到了应用，感兴趣的读者可以在看完这个后去看看相应的使用。\n\n虽然上面我说了队列，但是 SynchronousQueue 的队列其实是虚的，其不提供任何空间（一个都没有）来存储元素。数据必须从某个写线程交给某个读线程，而不是写到某个队列中等待被消费。\n\n你不能在 SynchronousQueue 中使用 peek 方法（在这里这个方法直接返回 null），peek 方法的语义是只读取不移除，显然，这个方法的语义是不符合 SynchronousQueue 的特征的。SynchronousQueue 也不能被迭代，因为根本就没有元素可以拿来迭代的。虽然 SynchronousQueue 间接地实现了 Collection 接口，但是如果你将其当做 Collection 来用的话，那么集合是空的。当然，这个类也是不允许传递 null 值的（并发包中的容器类好像都不支持插入 null 值，因为 null 值往往用作其他用途，比如用于方法的返回值代表操作失败）。\n\n接下来，我们来看看具体的源码实现吧，它的源码不是很简单的那种，我们需要先搞清楚它的设计思想。\n\n源码加注释大概有 1200 行，我们先看大框架：\n\n```\n// 构造时，我们可以指定公平模式还是非公平模式，区别之后再说\npublic SynchronousQueue(boolean fair) {\n    transferer = fair ? new TransferQueue() : new TransferStack();\n}\nabstract static class Transferer {\n    // 从方法名上大概就知道，这个方法用于转移元素，从生产者手上转到消费者手上\n    // 也可以被动地，消费者调用这个方法来从生产者手上取元素\n    // 第一个参数 e 如果不是 null，代表场景为：将元素从生产者转移给消费者\n    // 如果是 null，代表消费者等待生产者提供元素，然后返回值就是相应的生产者提供的元素\n    // 第二个参数代表是否设置超时，如果设置超时，超时时间是第三个参数的值\n    // 返回值如果是 null，代表超时，或者中断。具体是哪个，可以通过检测中断状态得到。\n    abstract Object transfer(Object e, boolean timed, long nanos);\n}\n```\n\nTransferer 有两个内部实现类，是因为构造 SynchronousQueue 的时候，我们可以指定公平策略。公平模式意味着，所有的读写线程都遵守先来后到，FIFO 嘛，对应 TransferQueue。而非公平模式则对应 TransferStack。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404202000.png)\n\n我们先采用公平模式分析源码，然后再说说公平模式和非公平模式的区别。\n\n接下来，我们看看 put 方法和 take 方法：\n\n```\n// 写入值\npublic void put(E o) throws InterruptedException {\n    if (o == null) throw new NullPointerException();\n    if (transferer.transfer(o, false, 0) == null) { // 1\n        Thread.interrupted();\n        throw new InterruptedException();\n    }\n}\n// 读取值并移除\npublic E take() throws InterruptedException {\n    Object e = transferer.transfer(null, false, 0); // 2\n    if (e != null)\n        return (E)e;\n    Thread.interrupted();\n    throw new InterruptedException();\n}\n```\n\n我们看到，写操作 put(E o) 和读操作 take() 都是调用 Transferer.transfer(…) 方法，区别在于第一个参数是否为 null 值。\n\n我们来看看 transfer 的设计思路，其基本算法如下：\n\n1.  当调用这个方法时，如果队列是空的，或者队列中的节点和当前的线程操作类型一致（如当前操作是 put 操作，而队列中的元素也都是写线程）。这种情况下，将当前线程加入到等待队列即可。\n2.  如果队列中有等待节点，而且与当前操作可以匹配（如队列中都是读操作线程，当前线程是写操作线程，反之亦然）。这种情况下，匹配等待队列的队头，出队，返回相应数据。\n\n其实这里有个隐含的条件被满足了，队列如果不为空，肯定都是同种类型的节点，要么都是读操作，要么都是写操作。这个就要看到底是读线程积压了，还是写线程积压了。\n\n我们可以假设出一个男女配对的场景：一个男的过来，如果一个人都没有，那么他需要等待；如果发现有一堆男的在等待，那么他需要排到队列后面；如果发现是一堆女的在排队，那么他直接牵走队头的那个女的。\n\n既然这里说到了等待队列，我们先看看其实现，也就是 QNode:\n\n```\nstatic final class QNode {\n    volatile QNode next;          // 可以看出来，等待队列是单向链表\n    volatile Object item;         // CAS'ed to or from null\n    volatile Thread waiter;       // 将线程对象保存在这里，用于挂起和唤醒\n    final boolean isData;         // 用于判断是写线程节点(isData == true)，还是读线程节点\n\n    QNode(Object item, boolean isData) {\n        this.item = item;\n        this.isData = isData;\n    }\n  ......\n```\n\n相信说了这么多以后，我们再来看 transfer 方法的代码就轻松多了。\n\n```\n/**\n * Puts or takes an item.\n */\nObject transfer(Object e, boolean timed, long nanos) {\n\n    QNode s = null; // constructed/reused as needed\n    boolean isData = (e != null);\n\n    for (;;) {\n        QNode t = tail;\n        QNode h = head;\n        if (t == null || h == null)         // saw uninitialized value\n            continue;                       // spin\n\n        // 队列空，或队列中节点类型和当前节点一致，\n        // 即我们说的第一种情况，将节点入队即可。读者要想着这块 if 里面方法其实就是入队\n        if (h == t || t.isData == isData) { // empty or same-mode\n            QNode tn = t.next;\n            // t != tail 说明刚刚有节点入队，continue 即可\n            if (t != tail)                  // inconsistent read\n                continue;\n            // 有其他节点入队，但是 tail 还是指向原来的，此时设置 tail 即可\n            if (tn != null) {               // lagging tail\n                // 这个方法就是：如果 tail 此时为 t 的话，设置为 tn\n                advanceTail(t, tn);\n                continue;\n            }\n            // \n            if (timed && nanos <= 0)        // can't wait\n                return null;\n            if (s == null)\n                s = new QNode(e, isData);\n            // 将当前节点，插入到 tail 的后面\n            if (!t.casNext(null, s))        // failed to link in\n                continue;\n\n            // 将当前节点设置为新的 tail\n            advanceTail(t, s);              // swing tail and wait\n            // 看到这里，请读者先往下滑到这个方法，看完了以后再回来这里，思路也就不会断了\n            Object x = awaitFulfill(s, e, timed, nanos);\n            // 到这里，说明之前入队的线程被唤醒了，准备往下执行\n            if (x == s) {                   // wait was cancelled\n                clean(t, s);\n                return null;\n            }\n\n            if (!s.isOffList()) {           // not already unlinked\n                advanceHead(t, s);          // unlink if head\n                if (x != null)              // and forget fields\n                    s.item = s;\n                s.waiter = null;\n            }\n            return (x != null) ? x : e;\n\n        // 这里的 else 分支就是上面说的第二种情况，有相应的读或写相匹配的情况\n        } else {                            // complementary-mode\n            QNode m = h.next;               // node to fulfill\n            if (t != tail || m == null || h != head)\n                continue;                   // inconsistent read\n\n            Object x = m.item;\n            if (isData == (x != null) ||    // m already fulfilled\n                x == m ||                   // m cancelled\n                !m.casItem(x, e)) {         // lost CAS\n                advanceHead(h, m);          // dequeue and retry\n                continue;\n            }\n\n            advanceHead(h, m);              // successfully fulfilled\n            LockSupport.unpark(m.waiter);\n            return (x != null) ? x : e;\n        }\n    }\n}\n\nvoid advanceTail(QNode t, QNode nt) {\n    if (tail == t)\n        UNSAFE.compareAndSwapObject(this, tailOffset, t, nt);\n}\n```\n\n```\n// 自旋或阻塞，直到满足条件，这个方法返回\nObject awaitFulfill(QNode s, Object e, boolean timed, long nanos) {\n\n    long lastTime = timed ? System.nanoTime() : 0;\n    Thread w = Thread.currentThread();\n    // 判断需要自旋的次数，\n    int spins = ((head.next == s) ?\n                 (timed ? maxTimedSpins : maxUntimedSpins) : 0);\n    for (;;) {\n        // 如果被中断了，那么取消这个节点\n        if (w.isInterrupted())\n            // 就是将当前节点 s 中的 item 属性设置为 this\n            s.tryCancel(e);\n        Object x = s.item;\n        // 这里是这个方法的唯一的出口\n        if (x != e)\n            return x;\n        // 如果需要，检测是否超时\n        if (timed) {\n            long now = System.nanoTime();\n            nanos -= now - lastTime;\n            lastTime = now;\n            if (nanos <= 0) {\n                s.tryCancel(e);\n                continue;\n            }\n        }\n        if (spins > 0)\n            --spins;\n        // 如果自旋达到了最大的次数，那么检测\n        else if (s.waiter == null)\n            s.waiter = w;\n        // 如果自旋到了最大的次数，那么线程挂起，等待唤醒\n        else if (!timed)\n            LockSupport.park(this);\n        // spinForTimeoutThreshold 这个之前讲 AQS 的时候其实也说过，剩余时间小于这个阈值的时候，就\n        // 不要进行挂起了，自旋的性能会比较好\n        else if (nanos > spinForTimeoutThreshold)\n            LockSupport.parkNanos(this, nanos);\n    }\n}\n```\n\nDoug Lea 的巧妙之处在于，将各个代码凑在了一起，使得代码非常简洁，当然也同时增加了我们的阅读负担，看代码的时候，还是得仔细想想各种可能的情况。\n\n下面，再说说前面说的公平模式和非公平模式的区别。\n\n相信大家心里面已经有了公平模式的工作流程的概念了，我就简单说说 TransferStack 的算法，就不分析源码了。\n\n1.  当调用这个方法时，如果队列是空的，或者队列中的节点和当前的线程操作类型一致（如当前操作是 put 操作，而栈中的元素也都是写线程）。这种情况下，将当前线程加入到等待栈中，等待配对。然后返回相应的元素，或者如果被取消了的话，返回 null。\n2.  如果栈中有等待节点，而且与当前操作可以匹配（如栈里面都是读操作线程，当前线程是写操作线程，反之亦然）。将当前节点压入栈顶，和栈中的节点进行匹配，然后将这两个节点出栈。配对和出栈的动作其实也不是必须的，因为下面的一条会执行同样的事情。\n3.  如果栈顶是进行匹配而入栈的节点，帮助其进行匹配并出栈，然后再继续操作。\n\n应该说，TransferStack 的源码要比 TransferQueue 的复杂一些，如果读者感兴趣，请自行进行源码阅读。\n\n## BlockingQueue 实现之 PriorityBlockingQueue\n\n带排序的 BlockingQueue 实现，其并发控制采用的是 ReentrantLock，队列为无界队列（ArrayBlockingQueue 是有界队列，LinkedBlockingQueue 也可以通过在构造函数中传入 capacity 指定队列最大的容量，但是 PriorityBlockingQueue 只能指定初始的队列大小，后面插入元素的时候，如果空间不够的话会自动扩容）。\n\n简单地说，它就是 PriorityQueue 的线程安全版本。不可以插入 null 值，同时，插入队列的对象必须是可比较大小的（comparable），否则报 ClassCastException 异常。它的插入操作 put 方法不会 block，因为它是无界队列（take 方法在队列为空的时候会阻塞）。\n\n它的源码相对比较简单，本节将介绍其核心源码部分。\n\n我们来看看它有哪些属性：\n\n```\n// 构造方法中，如果不指定大小的话，默认大小为 11\nprivate static final int DEFAULT_INITIAL_CAPACITY = 11;\n// 数组的最大容量\nprivate static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;\n\n// 这个就是存放数据的数组\nprivate transient Object[] queue;\n\n// 队列当前大小\nprivate transient int size;\n\n// 大小比较器，如果按照自然序排序，那么此属性可设置为 null\nprivate transient Comparator<? super E> comparator;\n\n// 并发控制所用的锁，所有的 public 且涉及到线程安全的方法，都必须先获取到这个锁\nprivate final ReentrantLock lock;\n\n// 这个很好理解，其实例由上面的 lock 属性创建\nprivate final Condition notEmpty;\n\n// 这个也是用于锁，用于数组扩容的时候，需要先获取到这个锁，才能进行扩容操作\n// 其使用 CAS 操作\nprivate transient volatile int allocationSpinLock;\n\n// 用于序列化和反序列化的时候用，对于 PriorityBlockingQueue 我们应该比较少使用到序列化\nprivate PriorityQueue q;\n```\n\n此类实现了 Collection 和 Iterator 接口中的所有接口方法，对其对象进行迭代并遍历时，不能保证有序性。如果你想要实现有序遍历，建议采用 Arrays.sort(queue.toArray()) 进行处理。PriorityBlockingQueue 提供了 drainTo 方法用于将部分或全部元素有序地填充（准确说是转移，会删除原队列中的元素）到另一个集合中。还有一个需要说明的是，如果两个对象的优先级相同（compare 方法返回 0），此队列并不保证它们之间的顺序。\n\nPriorityBlockingQueue 使用了基于数组的**二叉堆**来存放元素，所有的 public 方法采用同一个 lock 进行并发控制。\n\n二叉堆：一颗完全二叉树，它非常适合用数组进行存储，对于数组中的元素`a[i]`，其左子节点为`a[2*i+1]`，其右子节点为`a[2*i + 2]`，其父节点为`a[(i-1)/2]`，其堆序性质为，每个节点的值都小于其左右子节点的值。二叉堆中最小的值就是根节点，但是删除根节点是比较麻烦的，因为需要调整树。\n\n简单用个图解释一下二叉堆，我就不说太多专业的严谨的术语了，这种数据结构的优点是一目了然的，最小的元素一定是根元素，它是一棵满的树，除了最后一层，最后一层的节点从左到右紧密排列。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404202057.png)\n下面开始 PriorityBlockingQueue 的源码分析，首先我们来看看构造方法:\n\n```\n// 默认构造方法，采用默认值(11)来进行初始化\npublic PriorityBlockingQueue() {\n    this(DEFAULT_INITIAL_CAPACITY, null);\n}\n// 指定数组的初始大小\npublic PriorityBlockingQueue(int initialCapacity) {\n    this(initialCapacity, null);\n}\n// 指定比较器\npublic PriorityBlockingQueue(int initialCapacity,\n                             Comparator<? super E> comparator) {\n    if (initialCapacity < 1)\n        throw new IllegalArgumentException();\n    this.lock = new ReentrantLock();\n    this.notEmpty = lock.newCondition();\n    this.comparator = comparator;\n    this.queue = new Object[initialCapacity];\n}\n// 在构造方法中就先填充指定的集合中的元素\npublic PriorityBlockingQueue(Collection<? extends E> c) {\n    this.lock = new ReentrantLock();\n    this.notEmpty = lock.newCondition();\n    // \n    boolean heapify = true; // true if not known to be in heap order\n    boolean screen = true;  // true if must screen for nulls\n    if (c instanceof SortedSet<?>) {\n        SortedSet<? extends E> ss = (SortedSet<? extends E>) c;\n        this.comparator = (Comparator<? super E>) ss.comparator();\n        heapify = false;\n    }\n    else if (c instanceof PriorityBlockingQueue<?>) {\n        PriorityBlockingQueue<? extends E> pq =\n            (PriorityBlockingQueue<? extends E>) c;\n        this.comparator = (Comparator<? super E>) pq.comparator();\n        screen = false;\n        if (pq.getClass() == PriorityBlockingQueue.class) // exact match\n            heapify = false;\n    }\n    Object[] a = c.toArray();\n    int n = a.length;\n    // If c.toArray incorrectly doesn't return Object[], copy it.\n    if (a.getClass() != Object[].class)\n        a = Arrays.copyOf(a, n, Object[].class);\n    if (screen && (n == 1 || this.comparator != null)) {\n        for (int i = 0; i < n; ++i)\n            if (a[i] == null)\n                throw new NullPointerException();\n    }\n    this.queue = a;\n    this.size = n;\n    if (heapify)\n        heapify();\n}\n```\n\n接下来，我们来看看其内部的自动扩容实现：\n\n```\nprivate void tryGrow(Object[] array, int oldCap) {\n    // 这边做了释放锁的操作\n    lock.unlock(); // must release and then re-acquire main lock\n    Object[] newArray = null;\n    // 用 CAS 操作将 allocationSpinLock 由 0 变为 1，也算是获取锁\n    if (allocationSpinLock == 0 &&\n        UNSAFE.compareAndSwapInt(this, allocationSpinLockOffset,\n                                 0, 1)) {\n        try {\n            // 如果节点个数小于 64，那么增加的 oldCap + 2 的容量\n            // 如果节点数大于等于 64，那么增加 oldCap 的一半\n            // 所以节点数较小时，增长得快一些\n            int newCap = oldCap + ((oldCap < 64) ?\n                                   (oldCap + 2) :\n                                   (oldCap >> 1));\n            // 这里有可能溢出\n            if (newCap - MAX_ARRAY_SIZE > 0) {    // possible overflow\n                int minCap = oldCap + 1;\n                if (minCap < 0 || minCap > MAX_ARRAY_SIZE)\n                    throw new OutOfMemoryError();\n                newCap = MAX_ARRAY_SIZE;\n            }\n            // 如果 queue != array，那么说明有其他线程给 queue 分配了其他的空间\n            if (newCap > oldCap && queue == array)\n                // 分配一个新的大数组\n                newArray = new Object[newCap];\n        } finally {\n            // 重置，也就是释放锁\n            allocationSpinLock = 0;\n        }\n    }\n    // 如果有其他的线程也在做扩容的操作\n    if (newArray == null) // back off if another thread is allocating\n        Thread.yield();\n    // 重新获取锁\n    lock.lock();\n    // 将原来数组中的元素复制到新分配的大数组中\n    if (newArray != null && queue == array) {\n        queue = newArray;\n        System.arraycopy(array, 0, newArray, 0, oldCap);\n    }\n}\n```\n\n扩容方法对并发的控制也非常的巧妙，释放了原来的独占锁 lock，这样的话，扩容操作和读操作可以同时进行，提高吞吐量。\n\n下面，我们来分析下写操作 put 方法和读操作 take 方法。\n\n```\npublic void put(E e) {\n    // 直接调用 offer 方法，因为前面我们也说了，在这里，put 方法不会阻塞\n    offer(e); \n}\npublic boolean offer(E e) {\n    if (e == null)\n        throw new NullPointerException();\n    final ReentrantLock lock = this.lock;\n    // 首先获取到独占锁\n    lock.lock();\n    int n, cap;\n    Object[] array;\n    // 如果当前队列中的元素个数 >= 数组的大小，那么需要扩容了\n    while ((n = size) >= (cap = (array = queue).length))\n        tryGrow(array, cap);\n    try {\n        Comparator<? super E> cmp = comparator;\n        // 节点添加到二叉堆中\n        if (cmp == null)\n            siftUpComparable(n, e, array);\n        else\n            siftUpUsingComparator(n, e, array, cmp);\n        // 更新 size\n        size = n + 1;\n        // 唤醒等待的读线程\n        notEmpty.signal();\n    } finally {\n        lock.unlock();\n    }\n    return true;\n}\n```\n\n对于二叉堆而言，插入一个节点是简单的，插入的节点如果比父节点小，交换它们，然后继续和父节点比较。\n\n```\n// 这个方法就是将数据 x 插入到数组 array 的位置 k 处，然后再调整树\nprivate static <T> void siftUpComparable(int k, T x, Object[] array) {\n    Comparable<? super T> key = (Comparable<? super T>) x;\n    while (k > 0) {\n        // 二叉堆中 a[k] 节点的父节点位置\n        int parent = (k - 1) >>> 1;\n        Object e = array[parent];\n        if (key.compareTo((T) e) >= 0)\n            break;\n        array[k] = e;\n        k = parent;\n    }\n    array[k] = key;\n}\n```\n\n我们用图来示意一下，我们接下来要将**11**插入到队列中，看看 siftUp 是怎么操作的。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404202125.png)\n\n我们再看看 take 方法：\n\n```\npublic E take() throws InterruptedException {\n    final ReentrantLock lock = this.lock;\n    // 独占锁\n    lock.lockInterruptibly();\n    E result;\n    try {\n        // dequeue 出队\n        while ( (result = dequeue()) == null)\n            notEmpty.await();\n    } finally {\n        lock.unlock();\n    }\n    return result;\n}\n```\n\n```\nprivate E dequeue() {\n    int n = size - 1;\n    if (n < 0)\n        return null;\n    else {\n        Object[] array = queue;\n        // 队头，用于返回\n        E result = (E) array[0];\n        // 队尾元素先取出\n        E x = (E) array[n];\n        // 队尾置空\n        array[n] = null;\n        Comparator<? super E> cmp = comparator;\n        if (cmp == null)\n            siftDownComparable(0, x, array, n);\n        else\n            siftDownUsingComparator(0, x, array, n, cmp);\n        size = n;\n        return result;\n    }\n}\n```\n\ndequeue 方法返回队头，并调整二叉堆的树，调用这个方法必须先获取独占锁。\n\n废话不多说，出队是非常简单的，因为队头就是最小的元素，对应的是数组的第一个元素。难点是队头出队后，需要调整树。\n\n```\nprivate static <T> void siftDownComparable(int k, T x, Object[] array,\n                                           int n) {\n    if (n > 0) {\n        Comparable<? super T> key = (Comparable<? super T>)x;\n        // 这里得到的 half 肯定是非叶节点\n        // a[n] 是最后一个元素，其父节点是 a[(n-1)/2]。所以 n >>> 1 代表的节点肯定不是叶子节点\n        // 下面，我们结合图来一行行分析，这样比较直观简单\n        // 此时 k 为 0, x 为 17，n 为 9\n        int half = n >>> 1; // 得到 half = 4\n        while (k < half) {\n            // 先取左子节点\n            int child = (k << 1) + 1; // 得到 child = 1\n            Object c = array[child];  // c = 12\n            int right = child + 1;  // right = 2\n            // 如果右子节点存在，而且比左子节点小\n            // 此时 array[right] = 20，所以条件不满足\n            if (right < n &&\n                ((Comparable<? super T>) c).compareTo((T) array[right]) > 0)\n                c = array[child = right];\n            // key = 17, c = 12，所以条件不满足\n            if (key.compareTo((T) c) <= 0)\n                break;\n            // 把 12 填充到根节点\n            array[k] = c;\n            // k 赋值后为 1\n            k = child;\n            // 一轮过后，我们发现，12 左边的子树和刚刚的差不多，都是缺少根节点，接下来处理就简单了\n        }\n        array[k] = key;\n    }\n}\n```\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404202147.png)\n\n记住二叉堆是一棵完全二叉树，那么根节点 10 拿掉后，最后面的元素 17 必须找到合适的地方放置。首先，17 和 10 不能直接交换，那么先将根节点 10 的左右子节点中较小的节点往上滑，即 12 往上滑，然后原来 12 留下了一个空节点，然后再把这个空节点的较小的子节点往上滑，即 13 往上滑，最后，留出了位子，17 补上即可。\n\n我稍微调整下这个树，以便读者能更明白：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404202208.png)\n好了， PriorityBlockingQueue 我们也说完了。\n\n## 总结\n\n我知道本文过长，相信一字不漏看完的读者肯定是少数。\n\nArrayBlockingQueue 底层是数组，有界队列，如果我们要使用生产者-消费者模式，这是非常好的选择。\n\nLinkedBlockingQueue 底层是链表，可以当做无界和有界队列来使用，所以大家不要以为它就是无界队列。\n\nSynchronousQueue 本身不带有空间来存储任何元素，使用上可以选择公平模式和非公平模式。\n\nPriorityBlockingQueue 是无界队列，基于数组，数据结构为二叉堆，数组第一个也是树的根节点总是最小值。\n\n（全文完）\n"
  },
  {
    "path": "docs/Java/concurrency/Java并发编程学习总结.md",
    "content": "# 目录\n  * [线程安全](#线程安全)\n  * [互斥和同步](#互斥和同步)\n  * [JMM内存模型](#jmm内存模型)\n  * [as-if-Serial，happens-before](#as-if-serial，happens-before)\n  * [volatile](#volatile)\n  * [synchronized和锁优化](#synchronized和锁优化)\n  * [CAS操作](#cas操作)\n  * [Lock类](#lock类)\n    * [AQS](#aqs)\n    * [锁Lock和Conditon](#锁lock和conditon)\n  * [并发工具类](#并发工具类)\n  * [原子数据类型](#原子数据类型)\n  * [同步容器](#同步容器)\n  * [阻塞队列](#阻塞队列)\n  * [线程池](#线程池)\n    * [类图](#类图)\n    * [常用线程池](#常用线程池)\n  * [Fork/Join框架](#forkjoin框架)\n\n\n这篇总结主要是基于我Java并发技术系列的文章而形成的的。主要是把重要的知识点用自己的话说了一遍，可能会有一些错误，还望见谅和指点。谢谢\n\n更多详细内容可以查看我的专栏文章：Java并发技术指南\n\nhttps://blog.csdn.net/column/details/21961.html\n<!-- more -->\n\n## 线程安全\n\n线程安全一般指多线程之间的操作结果不会因为线程调度的顺序不同而发生改变。\n\n## 互斥和同步\n\n    互斥一般指资源的独占访问，同步则要求同步代码中的代码顺序执行，并且也是单线程独占的。\n\n## JMM内存模型\n\n    JVM中的内存分区包括堆，栈，方法区等区域，这些内存都是抽象出来的，实际上，系统中只有一个主内存，但是为了方便Java多线程语义的实现，以及降低程序员编写并发程序的难度，Java提出了JMM内存模型，将内存分为主内存和工作内存，工作内存是线程独占的，实际上它是一系列寄存器，编译器优化后的结果。\n\n## as-if-Serial，happens-before\n\n    as if serial语义提供单线程代码的顺序执行保证，虽然他允许指令重排序，但是前提是指令重排序不会改变执行结果。\n\n## volatile\n\n    volatile语义实际上是在代码中插入一个内存屏障，内存屏障分为读写，写读，读读，写写四种，可以用来避免volatile变量的读写操作发生重排序，从而保证了volatile的语义，实际上，volatile修饰的变量强制要求线程写时将数据从缓存刷入主内存，读时强制要求线程从主内存中读取，因此保证了它的可见性。\n    \n    而对于volatile修饰的64位类型数据，可以保证其原子性，不会因为指令重排序导致一个64位数据被分割成两个32位数据来读取。\n\n## synchronized和锁优化\n\n    synchronized是Java提供的同步标识，底层是操作系统的mutex lock调用，需要进行用户态到内核态的切换，开销比较大。\n    synchronized经过编译后的汇编代码会有monitor in和monitor out的字样，用于标识进入监视器模块和退出监视器模块，\n    监视器模块watcher会监控同步代码块中的线程号，只允线程号正确的线程进入。\n    \n    Java在synchronized关键字中进行了多次优化。\n    \n    比如轻量级锁优化，使用锁对象的对象头做文章，当一个线程需要获得该对象锁时，线程有一段空间叫做lock record，用于存储对象头的mask word，然后通过cas操作将对象头的mask word改成指向线程中的lockrecord。\n    如果成功了就是获取到了锁，否则就是发生了互斥。需要锁粗化，膨胀为互斥锁。\n    \n    偏向锁，去掉了更多的同步措施，检查mask word是否是可偏向状态，然后检查mask word中的线程id是否是自己的id，如果是则执行同步代码，如果不是则cas修改其id，如果修改失败，则出现锁争用，偏向锁失效，膨胀为轻量级锁。\n    \n    自旋锁，每个线程会被分配一段时间片，并且听候cpu调度，如果发生线程阻塞需要切换的开销，于是使用自旋锁不需要阻塞，而是忙等循环，一获取时间片就开始忙等，这样的锁就是自旋锁，一般用于并发量比较小，又担心切换开销的场景。\n\n## CAS操作\n    CAS操作是通过硬件实现的原子操作，通过一条指令完成比较和赋值的操作，防止发生因指令重排导致的非原子操作，在Java中通过unsafe包可以直接使用，在Java原子类中使用cas操作来完成一系列原子数据类型的构建，保证自加自减等依赖原值的操作不会出现并发问题。\n    \n    cas操作也广泛用在其他并发类中，通过循环cas操作可以完成线程安全的并发赋值，也可以通过一次cas操作来避免使用互斥锁。\n\n## Lock类\n\n### AQS\n\nAQS是Lock类的基石，他是一个抽象类，通过操作一个变量state来判断线程锁争用的情况，通过一系列方法实现对该变量的修改。一般可以分为独占锁和互斥锁。\n\nAQS维护着一个CLH阻塞队列，这个队列主要用来存放阻塞等待锁的线程节点。可以看做一个链表。\n\n一：独占锁\n独占锁的state只有0和1两种情况（如果是可重入锁也可以把state一直往上加，这里不讨论），state = 1时说明已经有线程争用到锁。线程获取锁时一般是通过aqs的lock方法，如果state为0，首先尝试cas修改state=1，成功返回，失败时则加入阻塞队列。非公共锁使用时，线程节点加入阻塞队列时依然会尝试cas获取锁，最后如果还是失败再老老实实阻塞在队列中。\n\n独占锁还可以分为公平锁和非公平锁，公平锁要求锁节点依据顺序加入阻塞队列，通过判断前置节点的状态来改变后置节点的状态，比如前置节点获取锁后，释放锁时会通知后置节点。\n\n非公平锁则不一定会按照队列的节点顺序来获取锁，如上面所说，会先尝试cas操作，失败再进入阻塞队列。\n\n二：共享锁\n共享锁的state状态可以是0到n。共享锁维护的阻塞队列和互斥锁不太一样，互斥锁的节点释放锁后只会通知后置节点，而共享锁获取锁后会通知所有的共享类型节点，让他们都来获取锁。共享锁用于countdownlatch工具类与cyliderbarrier等，可以很好地完成多线程的协调工作\n\n### 锁Lock和Conditon\n\nLock 锁维护这两个内部类fairsync和unfairsync，都继承自aqs，重写了部分方法，实际上大部分方法还是aqs中的，Lock只是重新把AQS做了封装，让程序员更方便地使用Lock锁。\n\n和Lock锁搭配使用的还有condition，由于Lock锁只维护着一个阻塞队列，有时候想分不同情况进行锁阻塞和锁通知怎么办，原来我们一般会使用多个锁对象，现在可以使用condition来完成这件事，比如线程A和线程B分别等待事件A和事件B，可以使用两个condition分别维护两个队列，A放在A队列，B放在B队列，由于Lock和condition是绑定使用的，当事件A触发，线程A被唤醒，此时他会加入Lock自己的CLH队列中进行锁争用，当然也分为公平锁和非公平锁两种，和上面的描述一样。\n\nLock和condtion的组合广泛用于JUC包中，比如生产者和消费者模型，再比如cyliderbarrier。\n\n###读写锁\n\n读写锁也是Lock的一个子类，它在一个阻塞队列中同时存储读线程节点和写线程节点，读写锁采用state的高16位和低16位分别代表独占锁和共享锁的状态，如果共享锁的state > 0可以继续获取读锁，并且state-1，如果=0,则加入到阻塞队列中，写锁节点和独占锁的处理一样，因此一个队列中会有两种类型的节点，唤醒读锁节点时不会唤醒写锁节点，唤醒写锁节点时，则会唤醒后续的节点。\n\n因此读写锁一般用于读多写少的场景，写锁可以降级为读锁，就是在获取到写锁的情况下可以再获取读锁。\n\n## 并发工具类\n\ncountdownlatch主要通过AQS的共享模式实现，初始时设置state为N，N是countdownlatch初始化使用的size，每当有一个线程执行countdown，则state-1，state = 0之前所有线程阻塞在队列中，当state=0时唤醒队头节点，队头节点依次通知所有共享类型的节点，唤醒这些线程并执行后面的代码。\n\ncycliderbarrier主要通过lock和condition结合实现，首先设置state为屏障等待的线程数，在某个节点设置一个屏障，所有线程运行到此处会阻塞等待，其实就是等待在一个condition的队列中，并且每当有一个线程到达，state -=1 则当所有线程到达时,state = 0，则唤醒condition队列的所有结点，去执行后面的代码。\n\nsamphere也是使用AQS的共享模式实现的，与countlatch大同小异，不再赘述。\n\nexchanger就比较复杂了。使用exchanger时会开辟一段空间用来让两个线程进行交互操作，这个空间一般是一个栈或队列，一个线程进来时先把数据放到这个格子里，然后阻塞等待其他线程跟他交换，如果另一个线程也进来了，就会读取这个数据，并把自己的数据放到对方线程的格子里，然后双双离开。当然使用栈和队列的交互是不同的，使用栈的话匹配的是最晚进来的一个线程，队列则相反。\n\n## 原子数据类型\n\n原子数据类型基本都是通过cas操作实现的，避免并发操作时出现的安全问题。\n\n## 同步容器\n\n同步容器主要就是concurrenthashmap了，在集合类中我已经讲了chm了，所以在这里简单带过，chm1.7通过分段锁来实现锁粗化，使用的死LLock锁，而1.8则改用synchronized和cas的结合，性能更好一些。\n\n还有就是concurrentlinkedlist，ConcurrentSkipListMap与CopyOnWriteArrayList。\n\n第一个链表也是通过cas和synchronized实现。\n\n而concurrentskiplistmap则是一个跳表，跳表分为很多层，每层都是一个链表，每个节点可以有向下和向右两个指针，先通过向右指针进行索引，再通过向下指针细化搜索，这个的搜索效率是很高的，可以达到logn，并且它的实现难度也比较低。通过跳表存map就是把entry节点放在链表中了。查询时按照跳表的查询规则即可。\n\nCopyOnWriteArrayList是一个写时复制链表，查询时不加锁，而修改时则会复制一个新list进行操作，然后再赋值给原list即可。\n适合读多写少的场景。\n\n## 阻塞队列\n\n        BlockingQueue 实现之 ArrayBlockingQueue\n    \n    ArrayBlockingQueue其实就是数组实现的阻塞队列，该阻塞队列通过一个lock和两个condition实现，一个condition负责从队头插入节点，一个condition负责队尾读取节点，通过这样的方式可以实现生产者消费者模型。   \n        \n        BlockingQueue 实现之 LinkedBlockingQueue\n    \n    LinkedBlockingQueue是用链表实现的阻塞队列，和arrayblockqueue有所区别，它支持实现为无界队列，并且它使用两个lock和对应的condition搭配使用，这是因为链表可以同时对头部和尾部进行操作，而数组进行操作后可能还要执行移位和扩容等操作。\n    所以链表实现更灵活，读写分别用两把锁，效率更高。\n        \n        BlockingQueue 实现之 SynchronousQueue\n     \n     SynchronousQueue实现是一个不存储数据的队列，只会保留一个队列用于保存线程节点。详细请参加上面的exchanger实现类，它就是基于SynchronousQueue设计出来的工具类。\n        \n        BlockingQueue 实现之 PriorityBlockingQueue\n        \n        PriorityBlockingQueue\n        \n        PriorityBlockingQueue是一个支持优先级的无界队列。默认情况下元素采取自然顺序排列，也可以通过比较器comparator来指定元素的排序规则。元素按照升序排列。\n        \n        DelayQueue\n        \n        DelayQueue是一个支持延时获取元素的无界阻塞队列。队列使用PriorityQueue来实现。队列中的元素必须实现Delayed接口，在创建元素时可以指定多久才能从队列中获取当前元素。只有在延迟期满时才能从队列中提取元素。我们可以将DelayQueue运用在以下应用场景：\n        \n        缓存系统的设计：可以用DelayQueue保存缓存元素的有效期，使用一个线程循环查询DelayQueue，一旦能从DelayQueue中获取元素时，表示缓存有效期到了。\n        定时任务调度。使用DelayQueue保存当天将会执行的任务和执行时间，一旦从DelayQueue中获取到任务就开始执行，从比如TimerQueue就是使用DelayQueue实现的。\n\n## 线程池\n\n### 类图\n\n首先看看executor接口，只提供一个run方法，而他的一个子接口executorservice则提供了更多方法，比如提交任务，结束线程池等。\n\n然后抽象类abstractexecutorservice提供了更多的实现了，最后我们最常使用的类ThreadPoolExecutor就是继承它来的。\n\n\nThreadPoolExecutor可以传入多种参数来自定义实现线程池。\n\n而我们也可以使用Executors中的工厂方法来实例化常用的线程池。\n\n### 常用线程池\n\n比如newFixedThreadPool\n\nnewSingleThreadExecutor newCachedThreadPool\n\nnewScheduledThreadPool等等，这些线程池即可以使用submit提交有返回结果的callable和futuretask任务，通过一个future来接收结果，或者通过callable中的回调函数call来回写执行结果。也可以用execute执行无返回值的runable任务。\n\n在探讨这些线程池的区别之前，先看看线程池的几个核心概念。\n\n任务队列：线程池中维护了一个任务队列，每当向线程池提交任务时，任务加入队列。\n\n工作线程：也叫worker，从线程池中获取任务并执行，执行后被回收或者保留，因情况而定。\n\n核心线程数和最大线程数，核心线程数是线程池需要保持存活的线程数量，以便接收任务，最大线程数是能创建的线程数上限。\n\nnewFixedThreadPool可以设置固定的核心线程数和最大线程数，一个任务进来以后，就会开启一个线程去执行，并且这部分线程不会被回收，当开启的线程达到核心线程数时，则把任务先放进任务队列。当任务队列已满时，才会继续开启线程去处理，如果线程总数打到最大线程数限制，任务队列又是满的时候，会执行对应的拒绝策略。\n\n拒绝策略一般有几种常用的，比如丢弃任务，丢弃队尾任务，回退给调用者执行，或者抛出异常，也可以使用自定义的拒绝策略。\n\nnewSingleThreadExecutor是一个单线程执行的线程池，只会维护一个线程，他也有任务队列，当任务队列已满并且线程数已经是1个的时候，再提交任务就会执行拒绝策略。\n\nnewCachedThreadPool比较特别，第一个任务进来时会开启一个线程，而后如果线程还没执行完前面的任务又有新任务进来，就会再创建一个线程，这个线程池使用的是无容量的SynchronousQueue队列，要求请求线程和接受线程匹配时才会完成任务执行。\n所以如果一直提交任务，而接受线程来不及处理的话，就会导致线程池不断创建线程，导致cpu消耗很大。\n\nScheduledThreadPoolExecutor内部使用的是delayqueue队列，内部是一个优先级队列priorityqueue，也就是一个堆。通过这个delayqueue可以知道线程调度的先后顺序和执行时间点。\n\n\n## Fork/Join框架\n\n又称工作窃取线程池。\n\n我们在大学算法课本上，学过的一种基本算法就是：分治。其基本思路就是：把一个大的任务分成若干个子任务，这些子任务分别计算，最后再Merge出最终结果。这个过程通常都会用到递归。\n\n而Fork/Join其实就是一种利用多线程来实现“分治算法”的并行框架。\n\n另外一方面，可以把Fori/Join看作一个单机版的Map/Reduce，只不过这里的并行不是多台机器并行计算，而是多个线程并行计算。\n\n与ThreadPool的区别\n通过上面例子，我们可以看出，它在使用上，和ThreadPool有共同的地方，也有区别点： \n（1） ThreadPool只有“外部任务”，也就是调用者放到队列里的任务。 ForkJoinPool有“外部任务”，还有“内部任务”，也就是任务自身在执行过程中，分裂出”子任务“，递归，再次放入队列。 \n（2）ForkJoinPool里面的任务通常有2类，RecusiveAction/RecusiveTask，这2个都是继承自FutureTask。在使用的时候，重写其compute算法。\n\n工作窃取算法\n上面提到，ForkJoinPool里有”外部任务“，也有“内部任务”。其中外部任务，是放在ForkJoinPool的全局队列里面，而每个Worker线程，也有一个自己的队列，用于存放内部任务。\n\n窃取的基本思路就是：当worker自己的任务队列里面没有任务时，就去scan别的线程的队列，把别人的任务拿过来执行\n\n\n"
  },
  {
    "path": "docs/Java/design-parttern/初探Java设计模式：JDK中的设计模式.md",
    "content": "# 目录\n\n  * [**一，结构型模式**](#一，结构型模式)\n    * [**1，适配器模式**](#1，适配器模式)\n    * [**2，桥接模式**](#2，桥接模式)\n    * [**3，组合模式**](#3，组合模式)\n    * [**4，装饰者模式**](#4，装饰者模式)\n    * [**5，门面模式**](#5，门面模式)\n    * [**6，享元模式**](#6，享元模式)\n    * [**7，代理模式**](#7，代理模式)\n  * [**二，创建模式**](#二，创建模式)\n    * [**1，抽象工厂模式**](#1，抽象工厂模式)\n    * [**2，建造模式(Builder)**](#2，建造模式builder)\n    * [**3，工厂方法**](#3，工厂方法)\n    * [**4，原型模式**](#4，原型模式)\n    * [**5，单例模式**](#5，单例模式)\n  * [**三，行为模式**](#三，行为模式)\n    * [**1，责任链模式**](#1，责任链模式)\n    * [**2，命令模式**](#2，命令模式)\n    * [**3，解释器模式**](#3，解释器模式)\n    * [**4，迭代器模式**](#4，迭代器模式)\n    * [**5，中介者模式**](#5，中介者模式)\n    * [**6，备忘录模式**](#6，备忘录模式)\n    * [**7，空对象模式**](#7，空对象模式)\n    * [**8，观察者模式**](#8，观察者模式)\n    * [**9，状态模式**](#9，状态模式)\n    * [**10，策略模式**](#10，策略模式)\n    * [**11，模板方法模式**](#11，模板方法模式)\n    * [**12，访问者模式**](#12，访问者模式)\n\n\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star、fork哈\n\n文章也将发表在我的个人博客，阅读体验更佳：\n\n> www.how2playlife.com\n\n本文转自https://www.cnblogs.com/LinkinPark/p/5233075.html\n<!-- more -->\n\n## **一，结构型模式**\n\n### **1，适配器模式**\n\n**用来把一个接口转化成另一个接口**\n\n> **java.util.Arrays#asList()**\n>\n> **javax.swing.JTable(TableModel)**\n>\n> **java.io.InputStreamReader(InputStream)**\n>\n> **java.io.OutputStreamWriter(OutputStream)**\n>\n> **javax.xml.bind.annotation.adapters.XmlAdapter#marshal()**\n>\n> **javax.xml.bind.annotation.adapters.XmlAdapter#unmarshal()**\n\n### **2，桥接模式**\n\n**这个模式将抽象和抽象操作的实现进行了解耦，这样使得抽象和实现可以独立地变化**\n\n> **AWT (It provides an abstraction layer which maps onto the native OS the windowing support.)**\n>\n> **JDBC**\n\n### **3，组合模式**\n\n**使得客户端看来单个对象和对象的组合是同等的。换句话说，某个类型的方法同时也接受自身类型作为参数**\n\n> **javax.swing.JComponent#add(Component)**\n>\n> **java.awt.Container#add(Component)**\n>\n> **java.util.Map#putAll(Map)**\n>\n> **java.util.List#addAll(Collection)**\n>\n> **java.util.Set#addAll(Collection)**\n\n### **4，装饰者模式**\n\n**动态的给一个对象附加额外的功能，这也是子类的一种替代方式。可以看到，在创建一个类型的时候，同时也传入同一类型的对象。这在JDK里随处可见，你会发现它无处不在，所以下面这个列表只是一小部分**\n\n> **java.io.BufferedInputStream(InputStream)**\n>\n> **java.io.DataInputStream(InputStream)**\n>\n> **java.io.BufferedOutputStream(OutputStream)**\n>\n> **java.util.zip.ZipOutputStream(OutputStream)**\n>\n> **java.util.Collections#checked<list |sortedmap |sortedset |set |map>**\n\n### **5，门面模式**\n\n**给一组组件，接口，抽象，或者子系统提供一个简单的接口**\n\n> **java.lang.Class**\n>\n> **javax.faces.webapp.FacesServlet**\n\n### **6，享元模式**\n\n**使用缓存来加速大量小对象的访问时间**\n\n> **java.lang.Integer#valueOf(int)**\n>\n> **java.lang.Boolean#valueOf(boolean)**\n>\n> **java.lang.Byte#valueOf(byte)**\n>\n> **java.lang.Character#valueOf(char)**\n\n### **7，代理模式**\n\n**代理模式是用一个简单的对象来代替一个复杂的或者创建耗时的对象。**\n\n> **java.lang.reflect.Proxy**\n>\n> **RMI**\n\n****\n\n## **二，创建模式**\n\n### **1，抽象工厂模式**\n\n**抽象工厂模式提供了一个协议来生成一系列的相关或者独立的对象，而不用指定具体对象的类型。它使得应用程序能够和使用的框架的具体实现进行解耦。这在JDK或者许多框架比如Spring中都随处可见。它们也很容易识别，一个创建新对象的方法，返回的却是接口或者抽象类的，就是抽象工厂模式了**\n\n> **java.util.Calendar#getInstance()**\n>\n> **java.util.Arrays#asList()**\n>\n> **java.util.ResourceBundle#getBundle()**\n>\n> **java.sql.DriverManager#getConnection()**\n>\n> **java.sql.Connection#createStatement()**\n>\n> **java.sql.Statement#executeQuery()**\n>\n> **java.text.NumberFormat#getInstance()**\n>\n> **javax.xml.transform.TransformerFactory#newInstance()**\n\n### **2，建造模式(Builder)**\n\n**定义了一个新的类来构建另一个类的实例，以简化复杂对象的创建。建造模式通常也使用方法链接来实现**\n\n> **java.lang.StringBuilder#append()**\n>\n> **java.lang.StringBuffer#append()**\n>\n> **java.sql.PreparedStatement**\n>\n> **javax.swing.GroupLayout.Group#addComponent()**\n\n### **3，工厂方法**\n\n**就是一个返回具体对象的方法**\n\n> **java.lang.Proxy#newProxyInstance()**\n>\n> **java.lang.Object#toString()**\n>\n> **java.lang.Class#newInstance()**\n>\n> **java.lang.reflect.Array#newInstance()**\n>\n> **java.lang.reflect.Constructor#newInstance()**\n>\n> **java.lang.Boolean#valueOf(String)**\n>\n> **java.lang.Class#forName()**\n\n### **4，原型模式**\n\n**使得类的实例能够生成自身的拷贝。如果创建一个对象的实例非常复杂且耗时时，就可以使用这种模式，而不重新创建一个新的实例，你可以拷贝一个对象并直接修改它**\n\n> **java.lang.Object#clone()**\n>\n> **java.lang.Cloneable**\n\n### **5，单例模式**\n\n**用来确保类只有一个实例。Joshua Bloch在Effetive Java中建议到，还有一种方法就是使用枚举**\n\n> **java.lang.Runtime#getRuntime()**\n>\n> **java.awt.Toolkit#getDefaultToolkit()**\n>\n> **java.awt.GraphicsEnvironment#getLocalGraphicsEnvironment()**\n>\n> **java.awt.Desktop#getDesktop()**\n\n****\n\n## **三，行为模式**\n\n### **1，责任链模式**\n\n**通过把请求从一个对象传递到链条中下一个对象的方式，直到请求被处理完毕，以实现对象间的解耦**\n\n> **java.util.logging.Logger#log()**\n>\n> **javax.servlet.Filter#doFilter()**\n\n### **2，命令模式**\n\n**将操作封装到对象内，以便存储，传递和返回**\n\n> **java.lang.Runnable**\n>\n> **javax.swing.Action**\n\n### **3，解释器模式**\n\n**这个模式通常定义了一个语言的语法，然后解析相应语法的语句**\n\n> **java.util.Pattern**\n>\n> **java.text.Normalizer**\n>\n> **java.text.Format**\n\n### **4，迭代器模式**\n\n**提供一个一致的方法来顺序访问集合中的对象，这个方法与底层的集合的具体实现无关**\n\n> **java.util.Iterator**\n>\n> **java.util.Enumeration**\n\n### **5，中介者模式**\n\n**通过使用一个中间对象来进行消息分发以及减少类之间的直接依赖**\n\n> **java.util.Timer**\n>\n> **java.util.concurrent.Executor#execute()**\n>\n> **java.util.concurrent.ExecutorService#submit()**\n>\n> **java.lang.reflect.Method#invoke()**\n\n### **6，备忘录模式**\n\n**生成对象状态的一个快照，以便对象可以恢复原始状态而不用暴露自身的内容。Date对象通过自身内部的一个long值来实现备忘录模式**\n\n> **java.util.Date**\n>\n> **java.io.Serializable**\n\n### **7，空对象模式**\n\n**这个模式通过一个无意义的对象来代替没有对象这个状态。它使得你不用额外对空对象进行处理**\n\n> **java.util.Collections#emptyList()**\n>\n> **java.util.Collections#emptyMap()**\n>\n> **java.util.Collections#emptySet()**\n\n### **8，观察者模式**\n\n**它使得一个对象可以灵活的将消息发送给感兴趣的对象**\n\n> **java.util.EventListener**\n>\n> **javax.servlet.http.HttpSessionBindingListener**\n>\n> **javax.servlet.http.HttpSessionAttributeListener**\n>\n> **javax.faces.event.PhaseListener**\n\n### **9，状态模式**\n\n**通过改变对象内部的状态，使得你可以在运行时动态改变一个对象的行为**\n\n> **java.util.Iterator**\n>\n> **javax.faces.lifecycle.LifeCycle#execute()**\n\n### **10，策略模式**\n\n**使用这个模式来将一组算法封装成一系列对象。通过传递这些对象可以灵活的改变程序的功能**\n\n> **java.util.Comparator#compare()**\n>\n> **javax.servlet.http.HttpServlet**\n>\n> **javax.servlet.Filter#doFilter()**\n\n### **11，模板方法模式**\n\n**让子类可以重写方法的一部分，而不是整个重写，你可以控制子类需要重写那些操作**\n\n> **java.util.Collections#sort()**\n>\n> **java.io.InputStream#skip()**\n>\n> **java.io.InputStream#read()**\n>\n> **java.util.AbstractList#indexOf()**\n\n### **12，访问者模式**\n\n**提供一个方便的可维护的方式来操作一组对象。它使得你在不改变操作的对象前提下，可以修改或者扩展对象的行为**\n\n> **javax.lang.model.element.Element and javax.lang.model.element.ElementVisitor**\n>\n> **javax.lang.model.type.TypeMirror and javax.lang.model.type.TypeVisitor**\n\n**译者注：很多地方可能会存在争议，是否是某种模式其实并不是特别重要，重要的是它们的设计能为改善我们的代码提供一些经验**\n\n\n"
  },
  {
    "path": "docs/Java/design-parttern/初探Java设计模式：Spring涉及到的种设计模式.md",
    "content": "# 目录\n  * [结构型模式](#结构型模式)\n    * [代理模式](#代理模式)\n    * [适配器模式](#适配器模式)\n    * [桥梁模式](#桥梁模式)\n    * [装饰模式](#装饰模式)\n    * [门面模式](#门面模式)\n    * [组合模式](#组合模式)\n    * [享元模式](#享元模式)\n    * [结构型模式总结](#结构型模式总结)\n  * [参考文章](#参考文章)\n\n\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star、fork哈\n\n文章也将发表在我的个人博客，阅读体验更佳：\n\n> www.how2playlife.com\n<!-- more -->\n\n**设计模式**作为工作学习中的枕边书，却时常处于勤说不用的尴尬境地，也不是我们时常忘记，只是一直没有记忆。\n\n今天，螃蟹在IT学习者网站就设计模式的内在价值做一番探讨，并以spring为例进行讲解，只有领略了其设计的思想理念，才能在工作学习中运用到“无形”。\n\nSpring作为业界的经典框架，无论是在架构设计方面，还是在代码编写方面，都堪称行内典范。好了，话不多说，开始今天的内容。\n\nspring中常用的设计模式达到九种，我们举例说明：\n\n**第一种：简单工厂**\n\n又叫做静态工厂方法（StaticFactory Method）模式，但不属于23种GOF设计模式之一。\n简单工厂模式的实质是由一个工厂类根据传入的参数，动态决定应该创建哪一个产品类。\nspring中的BeanFactory就是简单工厂模式的体现，根据传入一个唯一的标识来获得bean对象，但是否是在传入参数后创建还是传入参数前创建这个要根据具体情况来定。如下配置，就是在 HelloItxxz 类中创建一个 itxxzBean。\n\n````\n    <beans>\n    \n      <bean id=\"singletonBean\" >\n    \n        <constructor-arg>\n    \n          <value>Hello! 这是singletonBean!value>\n    \n        </constructor-arg>\n    \n     </ bean>\n    \n      <bean id=\"itxxzBean\"\n    \n        singleton=\"false\">\n    \n        <constructor-arg>\n    \n          <value>Hello! 这是itxxzBean! value>\n    \n        </constructor-arg>\n    \n      </bean>\n    \n    </beans>\n````\n\n**第二种：工厂方法（Factory Method）**\n\n通常由应用程序直接使用new创建新的对象，为了将对象的创建和使用相分离，采用工厂模式,即应用程序将对象的创建及初始化职责交给工厂对象。\n\n一般情况下,应用程序有自己的工厂对象来创建bean.如果将应用程序自己的工厂对象交给Spring管理,那么Spring管理的就不是普通的bean,而是工厂Bean。\n\n螃蟹就以工厂方法中的静态方法为例讲解一下：\n````   \n    import java.util.Random;\n    \n    public class StaticFactoryBean{\n    \n       public static Integer createRandom() {\n    \n         return new Integer(new Random().nextInt());\n    \n       }\n    \n    }\n````\n建一个config.xm配置文件，将其纳入Spring容器来管理,需要通过factory-method指定静态方法名称\n````\n    <bean id=\"random\"\n    \n    factory-method=\"createRandom\"//createRandom方法必须是static的,才能找到 scope=\"prototype\"\n    \n    />\n````\n测试:\n````\n    public static void main(String[] args) {\n      //调用getBean()时,返回随机数.如果没有指定factory-method,会返回StaticFactoryBean的实例,即返回工厂Bean的实例   XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource(\"config.xml\"));   System.out.println(\"我是IT学习者创建的实例:\"+factory.getBean(\"random\").toString());\n    \n    }\n````\n**第三种：单例模式（Singleton）**\n\n保证一个类仅有一个实例，并提供一个访问它的全局访问点。\nspring中的单例模式完成了后半句话，即提供了全局的访问点BeanFactory。但没有从构造器级别去控制单例，这是因为spring管理的是是任意的java对象。\n核心提示点：Spring下默认的bean均为singleton，可以通过singleton=“true|false” 或者 scope=“？”来指定\n\n**第四种：适配器（Adapter）**\n\n在Spring的Aop中，使用的Advice（通知）来增强被代理类的功能。Spring实现这一AOP功能的原理就使用代理模式（1、JDK动态代理。2、CGLib字节码生成技术代理。）对类进行方法级别的切面增强，即，生成被代理类的代理类， 并在代理类的方法前，设置拦截器，通过执行拦截器重的内容增强了代理方法的功能，实现的面向切面编程。\n\n**Adapter类接口**：\n````\n    public interface AdvisorAdapter {\n    \n    boolean supportsAdvice(Advice advice);\n    \n      MethodInterceptor getInterceptor(Advisor advisor);\n    \n    }**MethodBeforeAdviceAdapter类**，Adapter\n    \n    class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {\n    \n      public boolean supportsAdvice(Advice advice) {\n    \n        return (advice instanceof MethodBeforeAdvice);\n    \n      }\n    \n      public MethodInterceptor getInterceptor(Advisor advisor) {\n    \n        MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();\n    \n      return new MethodBeforeAdviceInterceptor(advice);\n    \n      }\n    \n    }\n````\n**第五种：包装器（Decorator）**\n\n在我们的项目中遇到这样一个问题：我们的项目需要连接多个数据库，而且不同的客户在每次访问中根据需要会去访问不同的数据库。我们以往在spring和hibernate框架中总是配置一个数据源，因而sessionFactory的dataSource属性总是指向这个数据源并且恒定不变，所有DAO在使用sessionFactory的时候都是通过这个数据源访问数据库。\n\n但是现在，由于项目的需要，我们的DAO在访问sessionFactory的时候都不得不在多个数据源中不断切换，问题就出现了：如何让sessionFactory在执行数据持久化的时候，根据客户的需求能够动态切换不同的数据源？我们能不能在spring的框架下通过少量修改得到解决？是否有什么设计模式可以利用呢？\n\n\n首先想到在spring的applicationContext中配置所有的dataSource。这些dataSource可能是各种不同类型的，比如不同的数据库：Oracle、SQL Server、MySQL等，也可能是不同的数据源：比如apache提供的org.apache.commons.dbcp.BasicDataSource、spring提供的org.springframework.jndi.JndiObjectFactoryBean等。然后sessionFactory根据客户的每次请求，将dataSource属性设置成不同的数据源，以到达切换数据源的目的。\n\nspring中用到的包装器模式在类名上有两种表现：一种是类名中含有Wrapper，另一种是类名中含有Decorator。基本上都是动态地给一个对象添加一些额外的职责。\n\n**第六种：代理（Proxy）**\n\n为其他对象提供一种代理以控制对这个对象的访问。 从结构上来看和Decorator模式类似，但Proxy是控制，更像是一种对功能的限制，而Decorator是增加职责。\nspring的Proxy模式在aop中有体现，比如JdkDynamicAopProxy和Cglib2AopProxy。\n\n**第七种：观察者（Observer）**\n\n定义对象间的一种一对多的依赖关系，当一个对象的状态发生改变时，所有依赖于它的对象都得到通知并被自动更新。\nspring中Observer模式常用的地方是listener的实现。如ApplicationListener。\n\n**第八种：策略（Strategy）**\n\n定义一系列的算法，把它们一个个封装起来，并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。\nspring中在实例化对象的时候用到Strategy模式\n在SimpleInstantiationStrategy中有如下代码说明了策略模式的使用情况：\n\n**第九种：模板方法（Template Method）**\n\n定义一个操作中的算法的骨架，而将一些步骤延迟到子类中。Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。\nTemplate Method模式一般是需要继承的。这里想要探讨另一种对Template Method的理解。\n\nspring中的JdbcTemplate，在用这个类时并不想去继承这个类，因为这个类的方法太多，但是我们还是想用到JdbcTemplate已有的稳定的、公用的数据库连接，那么我们怎么办呢？我们可以把变化的东西抽出来作为一个参数传入JdbcTemplate的方法中。但是变化的东西是一段代码，而且这段代码会用到JdbcTemplate中的变量。\n\n怎么办？那我们就用回调对象吧。在这个回调对象中定义一个操纵JdbcTemplate中变量的方法，我们去实现这个方法，就把变化的东西集中到这里了。然后我们再传入这个回调对象到JdbcTemplate，从而完成了调用。这可能是Template Method不需要继承的另一种实现方式吧。\n\n\n\n"
  },
  {
    "path": "docs/Java/design-parttern/初探Java设计模式：创建型模式（工厂，单例等）.md",
    "content": "# 目录\n  * [创建型模式](#创建型模式)\n    * [简单工厂模式](#简单工厂模式)\n    * [工厂模式](#工厂模式)\n    * [抽象工厂模式](#抽象工厂模式)\n    * [单例模式](#单例模式)\n    * [建造者模式](#建造者模式)\n    * [原型模式](#原型模式)\n    * [创建型模式总结](#创建型模式总结)\n  * [参考文章](#参考文章)\n\n\n\nJava 设计模式\n\n转自https://javadoop.com/post/design-pattern\n\n系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star、fork哈\n\n文章也将发表在我的个人博客，阅读体验更佳：\n\n> www.how2playlife.com\n\n一直想写一篇介绍设计模式的文章，让读者可以很快看完，而且一看就懂，看懂就会用，同时不会将各个模式搞混。自认为本文还是写得不错的，花了不少心思来写这文章和做图，力求让读者真的能看着简单同时有所收获。\n\n设计模式是对大家实际工作中写的各种代码进行高层次抽象的总结，其中最出名的当属_Gang of Four_(_GoF_) 的分类了，他们将设计模式分类为 23 种经典的模式，根据用途我们又可以分为三大类，分别为创建型模式、结构型模式和行为型模式。是的，我不善于扯这些有的没的，还是少点废话吧~~~\n\n有一些重要的设计原则在开篇和大家分享下，这些原则将贯通全文：\n\n1.  面向接口编程，而不是面向实现。这个很重要，也是优雅的、可扩展的代码的第一步，这就不需要多说了吧。\n2.  职责单一原则。每个类都应该只有一个单一的功能，并且该功能应该由这个类完全封装起来。\n3.  对修改关闭，对扩展开放。对修改关闭是说，我们辛辛苦苦加班写出来的代码，该实现的功能和该修复的 bug 都完成了，别人可不能说改就改；对扩展开放就比较好理解了，也就是说在我们写好的代码基础上，很容易实现扩展。\n\n**目录**\n\n*   [创建型模式](https://javadoop.com/post/design-pattern#%E5%88%9B%E5%BB%BA%E5%9E%8B%E6%A8%A1%E5%BC%8F)\n    *   [简单工厂模式](https://javadoop.com/post/design-pattern#%E7%AE%80%E5%8D%95%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F)\n    *   [工厂模式](https://javadoop.com/post/design-pattern#%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F)\n    *   [抽象工厂模式](https://javadoop.com/post/design-pattern#%E6%8A%BD%E8%B1%A1%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F)\n    *   [单例模式](https://javadoop.com/post/design-pattern#%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F)\n    *   [建造者模式](https://javadoop.com/post/design-pattern#%E5%BB%BA%E9%80%A0%E8%80%85%E6%A8%A1%E5%BC%8F)\n    *   [原型模式](https://javadoop.com/post/design-pattern#%E5%8E%9F%E5%9E%8B%E6%A8%A1%E5%BC%8F)\n    *   [创建型模式总结](https://javadoop.com/post/design-pattern#%E5%88%9B%E5%BB%BA%E5%9E%8B%E6%A8%A1%E5%BC%8F%E6%80%BB%E7%BB%93)\n\n## 创建型模式\n\n创建型模式的作用就是创建对象，说到创建一个对象，最熟悉的就是 new 一个对象，然后 set 相关属性。但是，在很多场景下，我们需要给客户端提供更加友好的创建对象的方式，尤其是那种我们定义了类，但是需要提供给其他开发者用的时候。\n\n### 简单工厂模式\n\n和名字一样简单，非常简单，直接上代码吧：\n\n```\npublic class FoodFactory {\n\n    public static Food makeFood(String name) {\n        if (name.equals(\"noodle\")) {\n            Food noodle = new LanZhouNoodle();\n            noodle.addSpicy(\"more\");\n            return noodle;\n        } else if (name.equals(\"chicken\")) {\n            Food chicken = new HuangMenChicken();\n            chicken.addCondiment(\"potato\");\n            return chicken;\n        } else {\n            return null;\n        }\n    }\n}\n\n```\n\n_其中，LanZhouNoodle 和 HuangMenChicken 都继承自 Food。_\n\n简单地说，简单工厂模式通常就是这样，一个工厂类 XxxFactory，里面有一个静态方法，根据我们不同的参数，返回不同的派生自同一个父类（或实现同一接口）的实例对象。\n\n> 我们强调**职责单一**原则，一个类只提供一种功能，FoodFactory 的功能就是只要负责生产各种 Food。\n\n### 工厂模式\n\n简单工厂模式很简单，如果它能满足我们的需要，我觉得就不要折腾了。之所以需要引入工厂模式，是因为我们往往需要使用两个或两个以上的工厂。\n\n```\npublic interface FoodFactory {\n    Food makeFood(String name);\n}\npublic class ChineseFoodFactory implements FoodFactory {\n\n    @Override\n    public Food makeFood(String name) {\n        if (name.equals(\"A\")) {\n            return new ChineseFoodA();\n        } else if (name.equals(\"B\")) {\n            return new ChineseFoodB();\n        } else {\n            return null;\n        }\n    }\n}\npublic class AmericanFoodFactory implements FoodFactory {\n\n    @Override\n    public Food makeFood(String name) {\n        if (name.equals(\"A\")) {\n            return new AmericanFoodA();\n        } else if (name.equals(\"B\")) {\n            return new AmericanFoodB();\n        } else {\n            return null;\n        }\n    }\n}\n\n```\n\n其中，ChineseFoodA、ChineseFoodB、AmericanFoodA、AmericanFoodB 都派生自 Food。\n\n客户端调用：\n\n```\npublic class APP {\n    public static void main(String[] args) {\n        // 先选择一个具体的工厂\n        FoodFactory factory = new ChineseFoodFactory();\n        // 由第一步的工厂产生具体的对象，不同的工厂造出不一样的对象\n        Food food = factory.makeFood(\"A\");\n    }\n}\n\n```\n\n虽然都是调用 makeFood(\"A\") 制作 A 类食物，但是，不同的工厂生产出来的完全不一样。\n\n第一步，我们需要选取合适的工厂，然后第二步基本上和简单工厂一样。\n\n**核心在于，我们需要在第一步选好我们需要的工厂**。比如，我们有 LogFactory 接口，实现类有 FileLogFactory 和 KafkaLogFactory，分别对应将日志写入文件和写入 Kafka 中，显然，我们客户端第一步就需要决定到底要实例化 FileLogFactory 还是 KafkaLogFactory，这将决定之后的所有的操作。\n\n虽然简单，不过我也把所有的构件都画到一张图上，这样读者看着比较清晰：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404211801.png)\n### 抽象工厂模式\n\n当涉及到**产品族**的时候，就需要引入抽象工厂模式了。\n\n一个经典的例子是造一台电脑。我们先不引入抽象工厂模式，看看怎么实现。\n\n因为电脑是由许多的构件组成的，我们将 CPU 和主板进行抽象，然后 CPU 由 CPUFactory 生产，主板由 MainBoardFactory 生产，然后，我们再将 CPU 和主板搭配起来组合在一起，如下图：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404211822.png)\n\n这个时候的客户端调用是这样的：\n\n```\n// 得到 Intel 的 CPU\nCPUFactory cpuFactory = new IntelCPUFactory();\nCPU cpu = intelCPUFactory.makeCPU();\n\n// 得到 AMD 的主板\nMainBoardFactory mainBoardFactory = new AmdMainBoardFactory();\nMainBoard mainBoard = mainBoardFactory.make();\n\n// 组装 CPU 和主板\nComputer computer = new Computer(cpu, mainBoard);\n\n```\n\n单独看 CPU 工厂和主板工厂，它们分别是前面我们说的**工厂模式**。这种方式也容易扩展，因为要给电脑加硬盘的话，只需要加一个 HardDiskFactory 和相应的实现即可，不需要修改现有的工厂。\n\n但是，这种方式有一个问题，那就是如果**Intel 家产的 CPU 和 AMD 产的主板不能兼容使用**，那么这代码就容易出错，因为客户端并不知道它们不兼容，也就会错误地出现随意组合。\n\n下面就是我们要说的**产品族**的概念，它代表了组成某个产品的一系列附件的集合：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404211846.png)\n\n当涉及到这种产品族的问题的时候，就需要抽象工厂模式来支持了。我们不再定义 CPU 工厂、主板工厂、硬盘工厂、显示屏工厂等等，我们直接定义电脑工厂，每个电脑工厂负责生产所有的设备，这样能保证肯定不存在兼容问题。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404212006.png)\n\n这个时候，对于客户端来说，不再需要单独挑选 CPU厂商、主板厂商、硬盘厂商等，直接选择一家品牌工厂，品牌工厂会负责生产所有的东西，而且能保证肯定是兼容可用的。\n\n```\npublic static void main(String[] args) {\n    // 第一步就要选定一个“大厂”\n    ComputerFactory cf = new AmdFactory();\n    // 从这个大厂造 CPU\n    CPU cpu = cf.makeCPU();\n    // 从这个大厂造主板\n    MainBoard board = cf.makeMainBoard();\n      // 从这个大厂造硬盘\n      HardDisk hardDisk = cf.makeHardDisk();\n\n    // 将同一个厂子出来的 CPU、主板、硬盘组装在一起\n    Computer result = new Computer(cpu, board, hardDisk);\n}\n\n```\n\n当然，抽象工厂的问题也是显而易见的，比如我们要加个显示器，就需要修改所有的工厂，给所有的工厂都加上制造显示器的方法。这有点违反了**对修改关闭，对扩展开放**这个设计原则。\n\n### 单例模式\n\n单例模式用得最多，错得最多。\n\n饿汉模式最简单：\n\n```\npublic class Singleton {\n    // 首先，将 new Singleton() 堵死\n    private Singleton() {};\n    // 创建私有静态实例，意味着这个类第一次使用的时候就会进行创建\n    private static Singleton instance = new Singleton();\n\n    public static Singleton getInstance() {\n        return instance;\n    }\n    // 瞎写一个静态方法。这里想说的是，如果我们只是要调用 Singleton.getDate(...)，\n    // 本来是不想要生成 Singleton 实例的，不过没办法，已经生成了\n    public static Date getDate(String mode) {return new Date();}\n}\n\n```\n\n> 很多人都能说出饿汉模式的缺点，可是我觉得生产过程中，很少碰到这种情况：你定义了一个单例的类，不需要其实例，可是你却把一个或几个你会用到的静态方法塞到这个类中。\n\n饱汉模式最容易出错：\n\n```\npublic class Singleton {\n    // 首先，也是先堵死 new Singleton() 这条路\n    private Singleton() {}\n    // 和饿汉模式相比，这边不需要先实例化出来，注意这里的 volatile，它是必须的\n    private static volatile Singleton instance = null;\n\n    public static Singleton getInstance() {\n        if (instance == null) {\n            // 加锁\n            synchronized (Singleton.class) {\n                // 这一次判断也是必须的，不然会有并发问题\n                if (instance == null) {\n                    instance = new Singleton();\n                }\n            }\n        }\n        return instance;\n    }\n}\n\n```\n\n> 双重检查，指的是两次检查 instance 是否为 null。\n> \n> volatile 在这里是需要的，希望能引起读者的关注。\n> \n> 很多人不知道怎么写，直接就在 getInstance() 方法签名上加上 synchronized，这就不多说了，性能太差。\n\n嵌套类最经典，以后大家就用它吧：\n\n```\npublic class Singleton3 {\n\n    private Singleton3() {}\n    // 主要是使用了 嵌套类可以访问外部类的静态属性和静态方法 的特性\n    private static class Holder {\n        private static Singleton3 instance = new Singleton3();\n    }\n    public static Singleton3 getInstance() {\n        return Holder.instance;\n    }\n}\n\n```\n\n> 注意，很多人都会把这个**嵌套类**说成是**静态内部类**，严格地说，内部类和嵌套类是不一样的，它们能访问的外部类权限也是不一样的。\n\n最后，一定有人跳出来说用枚举实现单例，是的没错，枚举类很特殊，它在类加载的时候会初始化里面的所有的实例，而且 JVM 保证了它们不会再被实例化，所以它天生就是单例的。不说了，读者自己看着办吧，不建议使用。\n\n### 建造者模式\n\n经常碰见的 XxxBuilder 的类，通常都是建造者模式的产物。建造者模式其实有很多的变种，但是对于客户端来说，我们的使用通常都是一个模式的：\n\n```\nFood food = new FoodBuilder().a().b().c().build();\nFood food = Food.builder().a().b().c().build();\n\n```\n\n套路就是先 new 一个 Builder，然后可以链式地调用一堆方法，最后再调用一次 build() 方法，我们需要的对象就有了。\n\n来一个中规中矩的建造者模式：\n\n```\nclass User {\n    // 下面是“一堆”的属性\n    private String name;\n    private String password;\n    private String nickName;\n    private int age;\n\n    // 构造方法私有化，不然客户端就会直接调用构造方法了\n    private User(String name, String password, String nickName, int age) {\n        this.name = name;\n        this.password = password;\n        this.nickName = nickName;\n        this.age = age;\n    }\n    // 静态方法，用于生成一个 Builder，这个不一定要有，不过写这个方法是一个很好的习惯，\n    // 有些代码要求别人写 new User.UserBuilder().a()...build() 看上去就没那么好\n    public static UserBuilder builder() {\n        return new UserBuilder();\n    }\n\n    public static class UserBuilder {\n        // 下面是和 User 一模一样的一堆属性\n        private String  name;\n        private String password;\n        private String nickName;\n        private int age;\n\n        private UserBuilder() {\n        }\n\n        // 链式调用设置各个属性值，返回 this，即 UserBuilder\n        public UserBuilder name(String name) {\n            this.name = name;\n            return this;\n        }\n\n        public UserBuilder password(String password) {\n            this.password = password;\n            return this;\n        }\n\n        public UserBuilder nickName(String nickName) {\n            this.nickName = nickName;\n            return this;\n        }\n\n        public UserBuilder age(int age) {\n            this.age = age;\n            return this;\n        }\n\n        // build() 方法负责将 UserBuilder 中设置好的属性“复制”到 User 中。\n        // 当然，可以在 “复制” 之前做点检验\n        public User build() {\n            if (name == null || password == null) {\n                throw new RuntimeException(\"用户名和密码必填\");\n            }\n            if (age <= 0 || age >= 150) {\n                throw new RuntimeException(\"年龄不合法\");\n            }\n            // 还可以做赋予”默认值“的功能\n              if (nickName == null) {\n                nickName = name;\n            }\n            return new User(name, password, nickName, age);\n        }\n    }\n}\n\n```\n\n核心是：先把所有的属性都设置给 Builder，然后 build() 方法的时候，将这些属性**复制**给实际产生的对象。\n\n看看客户端的调用：\n\n```\npublic class APP {\n    public static void main(String[] args) {\n        User d = User.builder()\n                .name(\"foo\")\n                .password(\"pAss12345\")\n                .age(25)\n                .build();\n    }\n}\n\n```\n\n说实话，建造者模式的**链式**写法很吸引人，但是，多写了很多“无用”的 builder 的代码，感觉这个模式没什么用。不过，当属性很多，而且有些必填，有些选填的时候，这个模式会使代码清晰很多。我们可以在**Builder 的构造方法**中强制让调用者提供必填字段，还有，在 build() 方法中校验各个参数比在 User 的构造方法中校验，代码要优雅一些。\n\n> 题外话，强烈建议读者使用 lombok，用了 lombok 以后，上面的一大堆代码会变成如下这样:\n\n```\n@Builder\nclass User {\n    private String  name;\n    private String password;\n    private String nickName;\n    private int age;\n}\n\n```\n\n> 怎么样，省下来的时间是不是又可以干点别的了。\n\n当然，如果你只是想要链式写法，不想要建造者模式，有个很简单的办法，User 的 getter 方法不变，所有的 setter 方法都让其**return this**就可以了，然后就可以像下面这样调用：\n\n```\nUser user = new User().setName(\"\").setPassword(\"\").setAge(20);\n\n```\n\n### 原型模式\n\n这是我要说的创建型模式的最后一个设计模式了。\n\n原型模式很简单：有一个原型**实例**，基于这个原型实例产生新的实例，也就是“克隆”了。\n\nObject 类中有一个 clone() 方法，它用于生成一个新的对象，当然，如果我们要调用这个方法，java 要求我们的类必须先**实现 Cloneable 接口**，此接口没有定义任何方法，但是不这么做的话，在 clone() 的时候，会抛出 CloneNotSupportedException 异常。\n\n```\nprotected native Object clone() throws CloneNotSupportedException;\n\n```\n\n> java 的克隆是浅克隆，碰到对象引用的时候，克隆出来的对象和原对象中的引用将指向同一个对象。通常实现深克隆的方法是将对象进行序列化，然后再进行反序列化。\n\n原型模式了解到这里我觉得就够了，各种变着法子说这种代码或那种代码是原型模式，没什么意义。\n\n### 创建型模式总结\n\n创建型模式总体上比较简单，它们的作用就是为了产生实例对象，算是各种工作的第一步了，因为我们写的是**面向对象**的代码，所以我们第一步当然是需要创建一个对象了。\n\n简单工厂模式最简单；工厂模式在简单工厂模式的基础上增加了选择工厂的维度，需要第一步选择合适的工厂；抽象工厂模式有产品族的概念，如果各个产品是存在兼容性问题的，就要用抽象工厂模式。单例模式就不说了，为了保证全局使用的是同一对象，一方面是安全性考虑，一方面是为了节省资源；建造者模式专门对付属性很多的那种类，为了让代码更优美；原型模式用得最少，了解和 Object 类中的 clone() 方法相关的知识即可。\n\n\n## 参考文章\n\n转自https://javadoop.com/post/design-pattern\n\n\n\n"
  },
  {
    "path": "docs/Java/design-parttern/初探Java设计模式：结构型模式（代理模式，适配器模式等）.md",
    "content": "# 目录\n  * [结构型模式](#结构型模式)\n    * [代理模式](#代理模式)\n    * [适配器模式](#适配器模式)\n    * [桥梁模式](#桥梁模式)\n    * [装饰模式](#装饰模式)\n    * [门面模式](#门面模式)\n    * [组合模式](#组合模式)\n    * [享元模式](#享元模式)\n    * [结构型模式总结](#结构型模式总结)\n  * [参考文章](#参考文章)\n\n\n\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star、fork哈\n\n文章也将发表在我的个人博客，阅读体验更佳：\n\n> www.how2playlife.com\n<!-- more -->\n\n## 结构型模式\n\n前面创建型模式介绍了创建对象的一些设计模式，这节介绍的结构型模式旨在通过改变代码结构来达到解耦的目的，使得我们的代码容易维护和扩展。\n\n### 代理模式\n\n第一个要介绍的代理模式是最常使用的模式之一了，用一个代理来隐藏具体实现类的实现细节，通常还用于在真实的实现的前后添加一部分逻辑。\n\n既然说是**代理**，那就要对客户端隐藏真实实现，由代理来负责客户端的所有请求。当然，代理只是个代理，它不会完成实际的业务逻辑，而是一层皮而已，但是对于客户端来说，它必须表现得就是客户端需要的真实实现。\n\n> 理解**代理**这个词，这个模式其实就简单了。\n\n```\npublic interface FoodService {\n    Food makeChicken();\n    Food makeNoodle();\n}\n\npublic class FoodServiceImpl implements FoodService {\n    public Food makeChicken() {\n          Food f = new Chicken()\n        f.setChicken(\"1kg\");\n          f.setSpicy(\"1g\");\n          f.setSalt(\"3g\");\n        return f;\n    }\n    public Food makeNoodle() {\n        Food f = new Noodle();\n        f.setNoodle(\"500g\");\n        f.setSalt(\"5g\");\n        return f;\n    }\n}\n\n// 代理要表现得“就像是”真实实现类，所以需要实现 FoodService\npublic class FoodServiceProxy implements FoodService {\n\n    // 内部一定要有一个真实的实现类，当然也可以通过构造方法注入\n    private FoodService foodService = new FoodServiceImpl();\n\n    public Food makeChicken() {\n        System.out.println(\"我们马上要开始制作鸡肉了\");\n\n        // 如果我们定义这句为核心代码的话，那么，核心代码是真实实现类做的，\n        // 代理只是在核心代码前后做些“无足轻重”的事情\n        Food food = foodService.makeChicken();\n\n        System.out.println(\"鸡肉制作完成啦，加点胡椒粉\"); // 增强\n          food.addCondiment(\"pepper\");\n\n        return food;\n    }\n    public Food makeNoodle() {\n        System.out.println(\"准备制作拉面~\");\n        Food food = foodService.makeNoodle();\n        System.out.println(\"制作完成啦\")\n        return food;\n    }\n}\n\n```\n\n客户端调用，注意，我们要用代理来实例化接口：\n\n```\n// 这里用代理类来实例化\nFoodService foodService = new FoodServiceProxy();\nfoodService.makeChicken();\n\n```\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404212100.png)\n\n我们发现没有，代理模式说白了就是做**“方法包装”**或做**“方法增强”**。在面向切面编程中，算了还是不要吹捧这个名词了，在 AOP 中，其实就是动态代理的过程。比如 Spring 中，我们自己不定义代理类，但是 Spring 会帮我们动态来定义代理，然后把我们定义在 @Before、@After、@Around 中的代码逻辑动态添加到代理中。\n\n说到动态代理，又可以展开说 …… Spring 中实现动态代理有两种，一种是如果我们的类定义了接口，如 UserService 接口和 UserServiceImpl 实现，那么采用 JDK 的动态代理，感兴趣的读者可以去看看 java.lang.reflect.Proxy 类的源码；另一种是我们自己没有定义接口的，Spring 会采用 CGLIB 进行动态代理，它是一个 jar 包，性能还不错。\n\n### 适配器模式\n\n说完代理模式，说适配器模式，是因为它们很相似，这里可以做个比较。\n\n适配器模式做的就是，有一个接口需要实现，但是我们现成的对象都不满足，需要加一层适配器来进行适配。\n\n适配器模式总体来说分三种：默认适配器模式、对象适配器模式、类适配器模式。先不急着分清楚这几个，先看看例子再说。\n\n默认适配器模式\n\n首先，我们先看看最简单的适配器模式**默认适配器模式(Default Adapter)**是怎么样的。\n\n我们用 Appache commons-io 包中的 FileAlterationListener 做例子，此接口定义了很多的方法，用于对文件或文件夹进行监控，一旦发生了对应的操作，就会触发相应的方法。\n\n```\npublic interface FileAlterationListener {\n    void onStart(final FileAlterationObserver observer);\n    void onDirectoryCreate(final File directory);\n    void onDirectoryChange(final File directory);\n    void onDirectoryDelete(final File directory);\n    void onFileCreate(final File file);\n    void onFileChange(final File file);\n    void onFileDelete(final File file);\n    void onStop(final FileAlterationObserver observer);\n}\n\n```\n\n此接口的一大问题是抽象方法太多了，如果我们要用这个接口，意味着我们要实现每一个抽象方法，如果我们只是想要监控文件夹中的**文件创建**和**文件删除**事件，可是我们还是不得不实现所有的方法，很明显，这不是我们想要的。\n\n所以，我们需要下面的一个**适配器**，它用于实现上面的接口，但是**所有的方法都是空方法**，这样，我们就可以转而定义自己的类来继承下面这个类即可。\n\n```\npublic class FileAlterationListenerAdaptor implements FileAlterationListener {\n\n    public void onStart(final FileAlterationObserver observer) {\n    }\n\n    public void onDirectoryCreate(final File directory) {\n    }\n\n    public void onDirectoryChange(final File directory) {\n    }\n\n    public void onDirectoryDelete(final File directory) {\n    }\n\n    public void onFileCreate(final File file) {\n    }\n\n    public void onFileChange(final File file) {\n    }\n\n    public void onFileDelete(final File file) {\n    }\n\n    public void onStop(final FileAlterationObserver observer) {\n    }\n}\n\n```\n\n比如我们可以定义以下类，我们仅仅需要实现我们想实现的方法就可以了：\n\n```\npublic class FileMonitor extends FileAlterationListenerAdaptor {\n    public void onFileCreate(final File file) {\n        // 文件创建\n        doSomething();\n    }\n\n    public void onFileDelete(final File file) {\n        // 文件删除\n        doSomething();\n    }\n}\n\n```\n\n当然，上面说的只是适配器模式的其中一种，也是最简单的一种，无需多言。下面，再介绍**“正统的”**适配器模式。\n\n对象适配器模式\n\n来看一个《Head First 设计模式》中的一个例子，我稍微修改了一下，看看怎么将鸡适配成鸭，这样鸡也能当鸭来用。因为，现在鸭这个接口，我们没有合适的实现类可以用，所以需要适配器。\n\n```\npublic interface Duck {\n    public void quack(); // 鸭的呱呱叫\n      public void fly(); // 飞\n}\n\npublic interface Cock {\n    public void gobble(); // 鸡的咕咕叫\n      public void fly(); // 飞\n}\n\npublic class WildCock implements Cock {\n    public void gobble() {\n        System.out.println(\"咕咕叫\");\n    }\n      public void fly() {\n        System.out.println(\"鸡也会飞哦\");\n    }\n}\n\n```\n\n鸭接口有 fly() 和 quare() 两个方法，鸡 Cock 如果要冒充鸭，fly() 方法是现成的，但是鸡不会鸭的呱呱叫，没有 quack() 方法。这个时候就需要适配了：\n\n```\n// 毫无疑问，首先，这个适配器肯定需要 implements Duck，这样才能当做鸭来用\npublic class CockAdapter implements Duck {\n\n    Cock cock;\n    // 构造方法中需要一个鸡的实例，此类就是将这只鸡适配成鸭来用\n      public CockAdapter(Cock cock) {\n        this.cock = cock;\n    }\n\n    // 实现鸭的呱呱叫方法\n      @Override\n      public void quack() {\n        // 内部其实是一只鸡的咕咕叫\n        cock.gobble();\n    }\n\n      @Override\n      public void fly() {\n        cock.fly();\n    }\n}\n\n```\n\n客户端调用很简单了：\n\n```\npublic static void main(String[] args) {\n    // 有一只野鸡\n      Cock wildCock = new WildCock();\n      // 成功将野鸡适配成鸭\n      Duck duck = new CockAdapter(wildCock);\n      ...\n}\n\n```\n\n到这里，大家也就知道了适配器模式是怎么回事了。无非是我们需要一只鸭，但是我们只有一只鸡，这个时候就需要定义一个适配器，由这个适配器来充当鸭，但是适配器里面的方法还是由鸡来实现的。\n\n我们用一个图来简单说明下：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404212138.png)\n上图应该还是很容易理解的，我就不做更多的解释了。下面，我们看看类适配模式怎么样的。\n\n类适配器模式\n\n废话少说，直接上图：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404212155.png)\n\n看到这个图，大家应该很容易理解的吧，通过继承的方法，适配器自动获得了所需要的大部分方法。这个时候，客户端使用更加简单，直接`Target t = new SomeAdapter();`就可以了。\n\n适配器模式总结\n\n1.  类适配和对象适配的异同\n\n    > 一个采用继承，一个采用组合；\n    > \n    > 类适配属于静态实现，对象适配属于组合的动态实现，对象适配需要多实例化一个对象。\n    > \n    > 总体来说，对象适配用得比较多。\n\n2.  适配器模式和代理模式的异同\n\n    比较这两种模式，其实是比较对象适配器模式和代理模式，在代码结构上，它们很相似，都需要一个具体的实现类的实例。但是它们的目的不一样，代理模式做的是增强原方法的活；适配器做的是适配的活，为的是提供“把鸡包装成鸭，然后当做鸭来使用”，而鸡和鸭它们之间原本没有继承关系。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404212256.png)\n\n### 桥梁模式\n\n理解桥梁模式，其实就是理解代码抽象和解耦。\n\n我们首先需要一个桥梁，它是一个接口，定义提供的接口方法。\n\n```\npublic interface DrawAPI {\n   public void draw(int radius, int x, int y);\n}\n\n```\n\n然后是一系列实现类：\n\n```\npublic class RedPen implements DrawAPI {\n   @Override\n   public void draw(int radius, int x, int y) {\n      System.out.println(\"用红色笔画图，radius:\" + radius + \", x:\" + x + \", y:\" + y);\n   }\n}\npublic class GreenPen implements DrawAPI {\n   @Override\n   public void draw(int radius, int x, int y) {\n      System.out.println(\"用绿色笔画图，radius:\" + radius + \", x:\" + x + \", y:\" + y);\n   }\n}\npublic class BluePen implements DrawAPI {\n   @Override\n   public void draw(int radius, int x, int y) {\n      System.out.println(\"用蓝色笔画图，radius:\" + radius + \", x:\" + x + \", y:\" + y);\n   }\n}\n\n```\n\n定义一个抽象类，此类的实现类都需要使用 DrawAPI：\n\n```\npublic abstract class Shape {\n   protected DrawAPI drawAPI;\n\n   protected Shape(DrawAPI drawAPI){\n      this.drawAPI = drawAPI;\n   }\n   public abstract void draw();    \n}\n\n```\n\n定义抽象类的子类：\n\n```\n// 圆形\npublic class Circle extends Shape {\n   private int radius;\n\n   public Circle(int radius, DrawAPI drawAPI) {\n      super(drawAPI);\n      this.radius = radius;\n   }\n\n   public void draw() {\n      drawAPI.draw(radius, 0, 0);\n   }\n}\n// 长方形\npublic class Rectangle extends Shape {\n    private int x;\n      private int y;\n\n      public Rectangle(int x, int y, DrawAPI drawAPI) {\n        super(drawAPI);\n          this.x = x;\n          this.y = y;\n    }\n      public void draw() {\n      drawAPI.draw(0, x, y);\n   }\n}\n\n```\n\n最后，我们来看客户端演示：\n\n```\npublic static void main(String[] args) {\n    Shape greenCircle = new Circle(10, new GreenPen());\n      Shape redRectangle = new Rectangle(4, 8, new RedPen());\n\n      greenCircle.draw();\n      redRectangle.draw();\n}\n\n```\n\n可能大家看上面一步步还不是特别清晰，我把所有的东西整合到一张图上：\n\n![](https://javadoop.com/blogimages/design-pattern/bridge-1.png)\n\n这回大家应该就知道抽象在哪里，怎么解耦了吧。桥梁模式的优点也是显而易见的，就是非常容易进行扩展。\n\n> 本节引用了[这里](https://www.tutorialspoint.com/design_pattern/bridge_pattern.htm)的例子，并对其进行了修改。\n\n### 装饰模式\n\n要把装饰模式说清楚明白，不是件容易的事情。也许读者知道 Java IO 中的几个类是典型的装饰模式的应用，但是读者不一定清楚其中的关系，也许看完就忘了，希望看完这节后，读者可以对其有更深的感悟。\n\n首先，我们先看一个简单的图，看这个图的时候，了解下层次结构就可以了：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404212315.png)\n我们来说说装饰模式的出发点，从图中可以看到，接口`Component`其实已经有了`ConcreteComponentA`和`ConcreteComponentB`两个实现类了，但是，如果我们要**增强**这两个实现类的话，我们就可以采用装饰模式，用具体的装饰器来**装饰**实现类，以达到增强的目的。\n\n> 从名字来简单解释下装饰器。既然说是装饰，那么往往就是**添加小功能**这种，而且，我们要满足可以添加多个小功能。最简单的，代理模式就可以实现功能的增强，但是代理不容易实现多个功能的增强，当然你可以说用代理包装代理的方式，但是那样的话代码就复杂了。\n\n首先明白一些简单的概念，从图中我们看到，所有的具体装饰者们 ConcreteDecorator_都可以作为 Component 来使用，因为它们都实现了 Component 中的所有接口。它们和 Component 实现类 ConcreteComponent_的区别是，它们只是装饰者，起**装饰**作用，也就是即使它们看上去牛逼轰轰，但是它们都只是在具体的实现中**加了层皮来装饰**而已。\n\n> 注意这段话中混杂在各个名词中的 Component 和 Decorator，别搞混了。\n\n下面来看看一个例子，先把装饰模式弄清楚，然后再介绍下 java io 中的装饰模式的应用。\n\n最近大街上流行起来了“快乐柠檬”，我们把快乐柠檬的饮料分为三类：红茶、绿茶、咖啡，在这三大类的基础上，又增加了许多的口味，什么金桔柠檬红茶、金桔柠檬珍珠绿茶、芒果红茶、芒果绿茶、芒果珍珠红茶、烤珍珠红茶、烤珍珠芒果绿茶、椰香胚芽咖啡、焦糖可可咖啡等等，每家店都有很长的菜单，但是仔细看下，其实原料也没几样，但是可以搭配出很多组合，如果顾客需要，很多没出现在菜单中的饮料他们也是可以做的。\n\n在这个例子中，红茶、绿茶、咖啡是最基础的饮料，其他的像金桔柠檬、芒果、珍珠、椰果、焦糖等都属于装饰用的。当然，在开发中，我们确实可以像门店一样，开发这些类：LemonBlackTea、LemonGreenTea、MangoBlackTea、MangoLemonGreenTea......但是，很快我们就发现，这样子干肯定是不行的，这会导致我们需要组合出所有的可能，而且如果客人需要在红茶中加双份柠檬怎么办？三份柠檬怎么办？万一有个变态要四份柠檬，所以这种做法是给自己找加班的。\n\n不说废话了，上代码。\n\n首先，定义饮料抽象基类：\n\n```\npublic abstract class Beverage {\n      // 返回描述\n      public abstract String getDescription();\n      // 返回价格\n      public abstract double cost();\n}\n\n```\n\n然后是三个基础饮料实现类，红茶、绿茶和咖啡：\n\n```\npublic class BlackTea extends Beverage {\n      public String getDescription() {\n        return \"红茶\";\n    }\n      public double cost() {\n        return 10;\n    }\n}\npublic class GreenTea extends Beverage {\n    public String getDescription() {\n        return \"绿茶\";\n    }\n      public double cost() {\n        return 11;\n    }\n}\n...// 咖啡省略\n\n```\n\n定义调料，也就是装饰者的基类，此类必须继承自 Beverage：\n\n```\n// 调料\npublic abstract class Condiment extends Beverage {\n\n}\n\n```\n\n然后我们来定义柠檬、芒果等具体的调料，它们属于装饰者，毫无疑问，这些调料肯定都需要继承 Condiment 类：\n\n```\npublic class Lemon extends Condiment {\n    private Beverage bevarage;\n      // 这里很关键，需要传入具体的饮料，如需要传入没有被装饰的红茶或绿茶，\n      // 当然也可以传入已经装饰好的芒果绿茶，这样可以做芒果柠檬绿茶\n      public Lemon(Beverage bevarage) {\n        this.bevarage = bevarage;\n    }\n      public String getDescription() {\n        // 装饰\n        return bevarage.getDescription() + \", 加柠檬\";\n    }\n      public double cost() {\n          // 装饰\n        return beverage.cost() + 2; // 加柠檬需要 2 元\n    }\n}\npublic class Mango extends Condiment {\n    private Beverage bevarage;\n      public Mango(Beverage bevarage) {\n        this.bevarage = bevarage;\n    }\n      public String getDescription() {\n        return bevarage.getDescription() + \", 加芒果\";\n    }\n      public double cost() {\n        return beverage.cost() + 3; // 加芒果需要 3 元\n    }\n}\n...// 给每一种调料都加一个类\n\n```\n\n看客户端调用：\n\n```\npublic static void main(String[] args) {\n      // 首先，我们需要一个基础饮料，红茶、绿茶或咖啡\n    Beverage beverage = new GreenTea();\n      // 开始装饰\n      beverage = new Lemon(beverage); // 先加一份柠檬\n      beverage = new Mongo(beverage); // 再加一份芒果\n\n      System.out.println(beverage.getDescription() + \" 价格：￥\" + beverage.cost());\n      //\"绿茶, 加柠檬, 加芒果 价格：￥16\"\n}\n\n```\n\n如果我们需要芒果珍珠双份柠檬红茶：\n\n```\nBeverage beverage = new Mongo(new Pearl(new Lemon(new Lemon(new BlackTea()))));\n\n```\n\n是不是很变态？\n\n看看下图可能会清晰一些：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404212336.png)\n到这里，大家应该已经清楚装饰模式了吧。\n\n下面，我们再来说说 java IO 中的装饰模式。看下图 InputStream 派生出来的部分类：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404212353.png)\n\n我们知道 InputStream 代表了输入流，具体的输入来源可以是文件（FileInputStream）、管道（PipedInputStream）、数组（ByteArrayInputStream）等，这些就像前面奶茶的例子中的红茶、绿茶，属于基础输入流。\n\nFilterInputStream 承接了装饰模式的关键节点，其实现类是一系列装饰器，比如 BufferedInputStream 代表用缓冲来装饰，也就使得输入流具有了缓冲的功能，LineNumberInputStream 代表用行号来装饰，在操作的时候就可以取得行号了，DataInputStream 的装饰，使得我们可以从输入流转换为 java 中的基本类型值。\n\n当然，在 java IO 中，如果我们使用装饰器的话，就不太适合面向接口编程了，如：\n\n```\nInputStream inputStream = new LineNumberInputStream(new BufferedInputStream(new FileInputStream(\"\")));\n\n```\n\n这样的结果是，InputStream 还是不具有读取行号的功能，因为读取行号的方法定义在 LineNumberInputStream 类中。\n\n我们应该像下面这样使用：\n\n```\nDataInputStream is = new DataInputStream(\n                              new BufferedInputStream(\n                                  new FileInputStream(\"\")));\n\n```\n\n> 所以说嘛，要找到纯的严格符合设计模式的代码还是比较难的。\n\n### 门面模式\n\n门面模式（也叫外观模式，Facade Pattern）在许多源码中有使用，比如 slf4j 就可以理解为是门面模式的应用。这是一个简单的设计模式，我们直接上代码再说吧。\n\n首先，我们定义一个接口：\n\n```\npublic interface Shape {\n   void draw();\n}\n\n```\n\n定义几个实现类：\n\n```\npublic class Circle implements Shape {\n\n   @Override\n   public void draw() {\n      System.out.println(\"Circle::draw()\");\n   }\n}\n\npublic class Rectangle implements Shape {\n\n   @Override\n   public void draw() {\n      System.out.println(\"Rectangle::draw()\");\n   }\n}\n\n```\n\n客户端调用：\n\n```\npublic static void main(String[] args) {\n    // 画一个圆形\n      Shape circle = new Circle();\n      circle.draw();\n\n      // 画一个长方形\n      Shape rectangle = new Rectangle();\n      rectangle.draw();\n}\n\n```\n\n以上是我们常写的代码，我们需要画圆就要先实例化圆，画长方形就需要先实例化一个长方形，然后再调用相应的 draw() 方法。\n\n下面，我们看看怎么用门面模式来让客户端调用更加友好一些。\n\n我们先定义一个门面：\n\n```\npublic class ShapeMaker {\n   private Shape circle;\n   private Shape rectangle;\n   private Shape square;\n\n   public ShapeMaker() {\n      circle = new Circle();\n      rectangle = new Rectangle();\n      square = new Square();\n   }\n\n  /**\n   * 下面定义一堆方法，具体应该调用什么方法，由这个门面来决定\n   */\n\n   public void drawCircle(){\n      circle.draw();\n   }\n   public void drawRectangle(){\n      rectangle.draw();\n   }\n   public void drawSquare(){\n      square.draw();\n   }\n}\n\n```\n\n看看现在客户端怎么调用：\n\n```\npublic static void main(String[] args) {\n  ShapeMaker shapeMaker = new ShapeMaker();\n\n  // 客户端调用现在更加清晰了\n  shapeMaker.drawCircle();\n  shapeMaker.drawRectangle();\n  shapeMaker.drawSquare();        \n}\n\n```\n\n门面模式的优点显而易见，客户端不再需要关注实例化时应该使用哪个实现类，直接调用门面提供的方法就可以了，因为门面类提供的方法的方法名对于客户端来说已经很友好了。\n\n### 组合模式\n\n组合模式用于表示具有层次结构的数据，使得我们对单个对象和组合对象的访问具有一致性。\n\n直接看一个例子吧，每个员工都有姓名、部门、薪水这些属性，同时还有下属员工集合（虽然可能集合为空），而下属员工和自己的结构是一样的，也有姓名、部门这些属性，同时也有他们的下属员工集合。\n\n```\npublic class Employee {\n   private String name;\n   private String dept;\n   private int salary;\n   private List<Employee> subordinates; // 下属\n\n   public Employee(String name,String dept, int sal) {\n      this.name = name;\n      this.dept = dept;\n      this.salary = sal;\n      subordinates = new ArrayList<Employee>();\n   }\n\n   public void add(Employee e) {\n      subordinates.add(e);\n   }\n\n   public void remove(Employee e) {\n      subordinates.remove(e);\n   }\n\n   public List<Employee> getSubordinates(){\n     return subordinates;\n   }\n\n   public String toString(){\n      return (\"Employee :[ Name : \" + name + \", dept : \" + dept + \", salary :\" + salary+\" ]\");\n   }   \n}\n\n```\n\n通常，这种类需要定义 add(node)、remove(node)、getChildren() 这些方法。\n\n这说的其实就是组合模式，这种简单的模式我就不做过多介绍了，相信各位读者也不喜欢看我写废话。\n\n### 享元模式\n\n英文是 Flyweight Pattern，不知道是谁最先翻译的这个词，感觉这翻译真的不好理解，我们试着强行关联起来吧。Flyweight 是轻量级的意思，享元分开来说就是 共享 元器件，也就是复用已经生成的对象，这种做法当然也就是轻量级的了。\n\n复用对象最简单的方式是，用一个 HashMap 来存放每次新生成的对象。每次需要一个对象的时候，先到 HashMap 中看看有没有，如果没有，再生成新的对象，然后将这个对象放入 HashMap 中。\n\n这种简单的代码我就不演示了。\n\n### 结构型模式总结\n\n前面，我们说了代理模式、适配器模式、桥梁模式、装饰模式、门面模式、组合模式和享元模式。读者是否可以分别把这几个模式说清楚了呢？在说到这些模式的时候，心中是否有一个清晰的图或处理流程在脑海里呢？\n\n代理模式是做方法增强的，适配器模式是把鸡包装成鸭这种用来适配接口的，桥梁模式做到了很好的解耦，装饰模式从名字上就看得出来，适合于装饰类或者说是增强类的场景，门面模式的优点是客户端不需要关心实例化过程，只要调用需要的方法即可，组合模式用于描述具有层次结构的数据，享元模式是为了在特定的场景中缓存已经创建的对象，用于提高性能。\n\n## 参考文章\n\n转自https://javadoop.com/post/design-pattern\n\n\n\n"
  },
  {
    "path": "docs/Java/design-parttern/初探Java设计模式：行为型模式（策略，观察者等）.md",
    "content": "# 目录\n  * [行为型模式](#行为型模式)\n    * [策略模式](#策略模式)\n    * [观察者模式](#观察者模式)\n    * [责任链模式](#责任链模式)\n    * [模板方法模式](#模板方法模式)\n    * [状态模式](#状态模式)\n    * [行为型模式总结](#行为型模式总结)\n  * [总结](#总结)\n  * [结构型模式](#结构型模式)\n    * [代理模式](#代理模式)\n    * [适配器模式](#适配器模式)\n    * [桥梁模式](#桥梁模式)\n    * [装饰模式](#装饰模式)\n    * [门面模式](#门面模式)\n    * [组合模式](#组合模式)\n    * [享元模式](#享元模式)\n    * [结构型模式总结](#结构型模式总结)\n  * [参考文章](#参考文章)\n\n\n\n转自https://javadoop.com/post/design-pattern\n\n[行为型模式](https://javadoop.com/post/design-pattern#%E8%A1%8C%E4%B8%BA%E5%9E%8B%E6%A8%A1%E5%BC%8F)\n\n*   [策略模式](https://javadoop.com/post/design-pattern#%E7%AD%96%E7%95%A5%E6%A8%A1%E5%BC%8F)\n*   [观察者模式](https://javadoop.com/post/design-pattern#%E8%A7%82%E5%AF%9F%E8%80%85%E6%A8%A1%E5%BC%8F)\n*   [责任链模式](https://javadoop.com/post/design-pattern#%E8%B4%A3%E4%BB%BB%E9%93%BE%E6%A8%A1%E5%BC%8F)\n*   [模板方法模式](https://javadoop.com/post/design-pattern#%E6%A8%A1%E6%9D%BF%E6%96%B9%E6%B3%95%E6%A8%A1%E5%BC%8F)\n*   [状态模式](https://javadoop.com/post/design-pattern#%E7%8A%B6%E6%80%81%E6%A8%A1%E5%BC%8F)\n*   [行为型模式总结](https://javadoop.com/post/design-pattern#%E8%A1%8C%E4%B8%BA%E5%9E%8B%E6%A8%A1%E5%BC%8F%E6%80%BB%E7%BB%93)\n*   \n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star、fork哈\n\n文章也将发表在我的个人博客，阅读体验更佳：\n\n> www.how2playlife.com\n<!-- more -->\n\n## 行为型模式\n\n行为型模式关注的是各个类之间的相互作用，将职责划分清楚，使得我们的代码更加地清晰。\n\n### 策略模式\n\n策略模式太常用了，所以把它放到最前面进行介绍。它比较简单，我就不废话，直接用代码说事吧。\n\n下面设计的场景是，我们需要画一个图形，可选的策略就是用红色笔来画，还是绿色笔来画，或者蓝色笔来画。\n\n首先，先定义一个策略接口：\n\n```\npublic interface Strategy {\n   public void draw(int radius, int x, int y);\n}\n\n```\n\n然后我们定义具体的几个策略：\n\n```\npublic class RedPen implements Strategy {\n   @Override\n   public void draw(int radius, int x, int y) {\n      System.out.println(\"用红色笔画图，radius:\" + radius + \", x:\" + x + \", y:\" + y);\n   }\n}\npublic class GreenPen implements Strategy {\n   @Override\n   public void draw(int radius, int x, int y) {\n      System.out.println(\"用绿色笔画图，radius:\" + radius + \", x:\" + x + \", y:\" + y);\n   }\n}\npublic class BluePen implements Strategy {\n   @Override\n   public void draw(int radius, int x, int y) {\n      System.out.println(\"用蓝色笔画图，radius:\" + radius + \", x:\" + x + \", y:\" + y);\n   }\n}\n\n```\n\n使用策略的类：\n\n```\npublic class Context {\n   private Strategy strategy;\n\n   public Context(Strategy strategy){\n      this.strategy = strategy;\n   }\n\n   public int executeDraw(int radius, int x, int y){\n      return strategy.draw(radius, x, y);\n   }\n}\n\n```\n\n客户端演示：\n\n```\npublic static void main(String[] args) {\n    Context context = new Context(new BluePen()); // 使用绿色笔来画\n      context.executeDraw(10, 0, 0);\n}\n\n```\n\n放到一张图上，让大家看得清晰些：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404212514.png)\n这个时候，大家有没有联想到结构型模式中的桥梁模式，它们其实非常相似，我把桥梁模式的图拿过来大家对比下：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404212533.png)\n\n要我说的话，它们非常相似，桥梁模式在左侧加了一层抽象而已。桥梁模式的耦合更低，结构更复杂一些。\n\n### 观察者模式\n\n观察者模式对于我们来说，真是再简单不过了。无外乎两个操作，观察者订阅自己关心的主题和主题有数据变化后通知观察者们。\n\n首先，需要定义主题，每个主题需要持有观察者列表的引用，用于在数据变更的时候通知各个观察者：\n\n```\npublic class Subject {\n\n   private List<Observer> observers = new ArrayList<Observer>();\n   private int state;\n\n   public int getState() {\n      return state;\n   }\n\n   public void setState(int state) {\n      this.state = state;\n      // 数据已变更，通知观察者们\n      notifyAllObservers();\n   }\n\n   public void attach(Observer observer){\n      observers.add(observer);        \n   }\n\n   // 通知观察者们\n   public void notifyAllObservers(){\n      for (Observer observer : observers) {\n         observer.update();\n      }\n   }     \n}\n\n```\n\n定义观察者接口：\n\n```\npublic abstract class Observer {\n   protected Subject subject;\n   public abstract void update();\n}\n\n```\n\n其实如果只有一个观察者类的话，接口都不用定义了，不过，通常场景下，既然用到了观察者模式，我们就是希望一个事件出来了，会有多个不同的类需要处理相应的信息。比如，订单修改成功事件，我们希望发短信的类得到通知、发邮件的类得到通知、处理物流信息的类得到通知等。\n\n我们来定义具体的几个观察者类：\n\n```\npublic class BinaryObserver extends Observer {\n\n      // 在构造方法中进行订阅主题\n    public BinaryObserver(Subject subject) {\n        this.subject = subject;\n        // 通常在构造方法中将 this 发布出去的操作一定要小心\n        this.subject.attach(this);\n    }\n\n      // 该方法由主题类在数据变更的时候进行调用\n    @Override\n    public void update() {\n        String result = Integer.toBinaryString(subject.getState());\n        System.out.println(\"订阅的数据发生变化，新的数据处理为二进制值为：\" + result);\n    }\n}\n\npublic class HexaObserver extends Observer {\n\n    public HexaObserver(Subject subject) {\n        this.subject = subject;\n        this.subject.attach(this);\n    }\n\n    @Override\n    public void update() {\n          String result = Integer.toHexString(subject.getState()).toUpperCase();\n        System.out.println(\"订阅的数据发生变化，新的数据处理为十六进制值为：\" + result);\n    }\n}\n\n```\n\n客户端使用也非常简单：\n\n```\npublic static void main(String[] args) {\n    // 先定义一个主题\n      Subject subject1 = new Subject();\n      // 定义观察者\n      new BinaryObserver(subject1);\n      new HexaObserver(subject1);\n\n      // 模拟数据变更，这个时候，观察者们的 update 方法将会被调用\n      subject.setState(11);\n}\n\n```\n\noutput:\n\n\n\n```\n订阅的数据发生变化，新的数据处理为二进制值为：1011\n订阅的数据发生变化，新的数据处理为十六进制值为：B\n\n```\n\n\n当然，jdk 也提供了相似的支持，具体的大家可以参考 java.util.Observable 和 java.util.Observer 这两个类。\n\n实际生产过程中，观察者模式往往用消息中间件来实现，如果要实现单机观察者模式，笔者建议读者使用 Guava 中的 EventBus，它有同步实现也有异步实现，本文主要介绍设计模式，就不展开说了。\n\n### 责任链模式\n\n责任链通常需要先建立一个单向链表，然后调用方只需要调用头部节点就可以了，后面会自动流转下去。比如流程审批就是一个很好的例子，只要终端用户提交申请，根据申请的内容信息，自动建立一条责任链，然后就可以开始流转了。\n\n有这么一个场景，用户参加一个活动可以领取奖品，但是活动需要进行很多的规则校验然后才能放行，比如首先需要校验用户是否是新用户、今日参与人数是否有限额、全场参与人数是否有限额等等。设定的规则都通过后，才能让用户领走奖品。\n\n> 如果产品给你这个需求的话，我想大部分人一开始肯定想的就是，用一个 List 来存放所有的规则，然后 foreach 执行一下每个规则就好了。不过，读者也先别急，看看责任链模式和我们说的这个有什么不一样？\n\n首先，我们要定义流程上节点的基类：\n\n```\npublic abstract class RuleHandler {\n\n      // 后继节点\n    protected RuleHandler successor;\n\n    public abstract void apply(Context context);\n\n    public void setSuccessor(RuleHandler successor) {\n        this.successor = successor;\n    }\n    public RuleHandler getSuccessor() {\n        return successor;\n    }\n}\n\n```\n\n接下来，我们需要定义具体的每个节点了。\n\n校验用户是否是新用户：\n\n```\npublic class NewUserRuleHandler extends RuleHandler {\n\n    public void apply(Context context) {\n        if (context.isNewUser()) {\n              // 如果有后继节点的话，传递下去\n            if (this.getSuccessor() != null) {\n                this.getSuccessor().apply(context);\n            }\n        } else {\n            throw new RuntimeException(\"该活动仅限新用户参与\");\n        }\n    }\n\n}\n\n```\n\n校验用户所在地区是否可以参与：\n\n```\npublic class LocationRuleHandler extends RuleHandler {\n    public void apply(Context context) {\n        boolean allowed = activityService.isSupportedLocation(context.getLocation);\n          if (allowed) {\n            if (this.getSuccessor() != null) {\n                this.getSuccessor().apply(context);\n            }\n        } else  {\n            throw new RuntimeException(\"非常抱歉，您所在的地区无法参与本次活动\");\n        }\n    }\n}\n\n```\n\n校验奖品是否已领完：\n\n```\npublic class LimitRuleHandler extends RuleHandler {\n    public void apply(Context context) {\n          int remainedTimes = activityService.queryRemainedTimes(context); // 查询剩余奖品\n        if (remainedTimes > 0) {\n            if (this.getSuccessor() != null) {\n                this.getSuccessor().apply(userInfo);\n            }\n        } else {\n            throw new RuntimeException(\"您来得太晚了，奖品被领完了\");\n        }\n    }\n}\n\n```\n\n客户端：\n\n```\npublic static void main(String[] args) {\n    RuleHandler newUserHandler = new NewUserRuleHandler();\n      RuleHandler locationHandler = new LocationRuleHandler();\n      RuleHandler limitHandler = new LimitRuleHandler();\n\n      // 假设本次活动仅校验地区和奖品数量，不校验新老用户\n      locationHandler.setSuccessor(limitHandler);\n      locationHandler.apply(context);\n}\n\n```\n\n代码其实很简单，就是先定义好一个链表，然后在通过任意一节点后，如果此节点有后继节点，那么传递下去。\n\n至于它和我们前面说的用一个 List 存放需要执行的规则的做法有什么异同，留给读者自己琢磨吧。\n\n### 模板方法模式\n\n在含有继承结构的代码中，模板方法模式是非常常用的，这也是在开源代码中大量被使用的。\n\n通常会有一个抽象类：\n\n```\npublic abstract class AbstractTemplate {\n    // 这就是模板方法\n      public void templateMethod(){\n        init();\n        apply(); // 这个是重点\n        end(); // 可以作为钩子方法\n    }\n    protected void init() {\n        System.out.println(\"init 抽象层已经实现，子类也可以选择覆写\");\n    }\n      // 留给子类实现\n    protected abstract void apply();\n    protected void end() {\n    }\n}\n\n```\n\n模板方法中调用了 3 个方法，其中 apply() 是抽象方法，子类必须实现它，其实模板方法中有几个抽象方法完全是自由的，我们也可以将三个方法都设置为抽象方法，让子类来实现。也就是说，模板方法只负责定义第一步应该要做什么，第二步应该做什么，第三步应该做什么，至于怎么做，由子类来实现。\n\n我们写一个实现类：\n\n```\npublic class ConcreteTemplate extends AbstractTemplate {\n    public void apply() {\n        System.out.println(\"子类实现抽象方法 apply\");\n    }\n      public void end() {\n        System.out.println(\"我们可以把 method3 当做钩子方法来使用，需要的时候覆写就可以了\");\n    }\n}\n\n```\n\n客户端调用演示：\n\n```\npublic static void main(String[] args) {\n    AbstractTemplate t = new ConcreteTemplate();\n      // 调用模板方法\n      t.templateMethod();\n}\n\n```\n\n代码其实很简单，基本上看到就懂了，关键是要学会用到自己的代码中。\n\n### 状态模式\n\n\n废话我就不说了，我们说一个简单的例子。商品库存中心有个最基本的需求是减库存和补库存，我们看看怎么用状态模式来写。\n\n核心在于，我们的关注点不再是 Context 是该进行哪种操作，而是关注在这个 Context 会有哪些操作。\n\n定义状态接口：\n\n```\npublic interface State {\n   public void doAction(Context context);\n}\n\n```\n\n定义减库存的状态：\n\n```\npublic class DeductState implements State {\n\n   public void doAction(Context context) {\n      System.out.println(\"商品卖出，准备减库存\");\n      context.setState(this);\n\n      //... 执行减库存的具体操作\n   }\n\n   public String toString(){\n      return \"Deduct State\";\n   }\n}\n\n```\n\n定义补库存状态：\n\n```\npublic class RevertState implements State {\n    public void doAction(Context context) {\n        System.out.println(\"给此商品补库存\");\n          context.setState(this);\n\n          //... 执行加库存的具体操作\n    }\n      public String toString() {\n        return \"Revert State\";\n    }\n}\n\n```\n\n前面用到了 context.setState(this)，我们来看看怎么定义 Context 类：\n\n```\npublic class Context {\n    private State state;\n      private String name;\n      public Context(String name) {\n        this.name = name;\n    }\n\n      public void setState(State state) {\n        this.state = state;\n    }\n      public void getState() {\n        return this.state;\n    }\n}\n\n```\n\n我们来看下客户端调用，大家就一清二楚了：\n\n```\npublic static void main(String[] args) {\n    // 我们需要操作的是 iPhone X\n    Context context = new Context(\"iPhone X\");\n\n    // 看看怎么进行补库存操作\n      State revertState = new RevertState();\n      revertState.doAction(context);\n\n    // 同样的，减库存操作也非常简单\n      State deductState = new DeductState();\n      deductState.doAction(context);\n\n      // 如果需要我们可以获取当前的状态\n    // context.getState().toString();\n}\n\n```\n\n读者可能会发现，在上面这个例子中，如果我们不关心当前 context 处于什么状态，那么 Context 就可以不用维护 state 属性了，那样代码会简单很多。\n\n不过，商品库存这个例子毕竟只是个例，我们还有很多实例是需要知道当前 context 处于什么状态的。\n\n### 行为型模式总结\n\n行为型模式部分介绍了策略模式、观察者模式、责任链模式、模板方法模式和状态模式，其实，经典的行为型模式还包括备忘录模式、命令模式等，但是它们的使用场景比较有限，而且本文篇幅也挺大了，我就不进行介绍了。\n\n## 总结\n\n学习设计模式的目的是为了让我们的代码更加的优雅、易维护、易扩展。这次整理这篇文章，让我重新审视了一下各个设计模式，对我自己而言收获还是挺大的。我想，文章的最大收益者一般都是作者本人，为了写一篇文章，需要巩固自己的知识，需要寻找各种资料，而且，自己写过的才最容易记住，也算是我给读者的建议吧。\n\n（全文完）\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star、fork哈\n\n文章也将发表在我的个人博客，阅读体验更佳：\n\n> www.how2playlife.com\n\n## 结构型模式\n\n前面创建型模式介绍了创建对象的一些设计模式，这节介绍的结构型模式旨在通过改变代码结构来达到解耦的目的，使得我们的代码容易维护和扩展。\n\n### 代理模式\n\n第一个要介绍的代理模式是最常使用的模式之一了，用一个代理来隐藏具体实现类的实现细节，通常还用于在真实的实现的前后添加一部分逻辑。\n\n既然说是**代理**，那就要对客户端隐藏真实实现，由代理来负责客户端的所有请求。当然，代理只是个代理，它不会完成实际的业务逻辑，而是一层皮而已，但是对于客户端来说，它必须表现得就是客户端需要的真实实现。\n\n> 理解**代理**这个词，这个模式其实就简单了。\n\n```\npublic interface FoodService {\n    Food makeChicken();\n    Food makeNoodle();\n}\n\npublic class FoodServiceImpl implements FoodService {\n    public Food makeChicken() {\n          Food f = new Chicken()\n        f.setChicken(\"1kg\");\n          f.setSpicy(\"1g\");\n          f.setSalt(\"3g\");\n        return f;\n    }\n    public Food makeNoodle() {\n        Food f = new Noodle();\n        f.setNoodle(\"500g\");\n        f.setSalt(\"5g\");\n        return f;\n    }\n}\n\n// 代理要表现得“就像是”真实实现类，所以需要实现 FoodService\npublic class FoodServiceProxy implements FoodService {\n\n    // 内部一定要有一个真实的实现类，当然也可以通过构造方法注入\n    private FoodService foodService = new FoodServiceImpl();\n\n    public Food makeChicken() {\n        System.out.println(\"我们马上要开始制作鸡肉了\");\n\n        // 如果我们定义这句为核心代码的话，那么，核心代码是真实实现类做的，\n        // 代理只是在核心代码前后做些“无足轻重”的事情\n        Food food = foodService.makeChicken();\n\n        System.out.println(\"鸡肉制作完成啦，加点胡椒粉\"); // 增强\n          food.addCondiment(\"pepper\");\n\n        return food;\n    }\n    public Food makeNoodle() {\n        System.out.println(\"准备制作拉面~\");\n        Food food = foodService.makeNoodle();\n        System.out.println(\"制作完成啦\")\n        return food;\n    }\n}\n\n```\n\n客户端调用，注意，我们要用代理来实例化接口：\n\n```\n// 这里用代理类来实例化\nFoodService foodService = new FoodServiceProxy();\nfoodService.makeChicken();\n\n```\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404212608.png)\n\n我们发现没有，代理模式说白了就是做**“方法包装”**或做**“方法增强”**。在面向切面编程中，算了还是不要吹捧这个名词了，在 AOP 中，其实就是动态代理的过程。比如 Spring 中，我们自己不定义代理类，但是 Spring 会帮我们动态来定义代理，然后把我们定义在 @Before、@After、@Around 中的代码逻辑动态添加到代理中。\n\n说到动态代理，又可以展开说 …… Spring 中实现动态代理有两种，一种是如果我们的类定义了接口，如 UserService 接口和 UserServiceImpl 实现，那么采用 JDK 的动态代理，感兴趣的读者可以去看看 java.lang.reflect.Proxy 类的源码；另一种是我们自己没有定义接口的，Spring 会采用 CGLIB 进行动态代理，它是一个 jar 包，性能还不错。\n\n### 适配器模式\n\n说完代理模式，说适配器模式，是因为它们很相似，这里可以做个比较。\n\n适配器模式做的就是，有一个接口需要实现，但是我们现成的对象都不满足，需要加一层适配器来进行适配。\n\n适配器模式总体来说分三种：默认适配器模式、对象适配器模式、类适配器模式。先不急着分清楚这几个，先看看例子再说。\n\n默认适配器模式\n\n首先，我们先看看最简单的适配器模式**默认适配器模式(Default Adapter)**是怎么样的。\n\n我们用 Appache commons-io 包中的 FileAlterationListener 做例子，此接口定义了很多的方法，用于对文件或文件夹进行监控，一旦发生了对应的操作，就会触发相应的方法。\n\n```\npublic interface FileAlterationListener {\n    void onStart(final FileAlterationObserver observer);\n    void onDirectoryCreate(final File directory);\n    void onDirectoryChange(final File directory);\n    void onDirectoryDelete(final File directory);\n    void onFileCreate(final File file);\n    void onFileChange(final File file);\n    void onFileDelete(final File file);\n    void onStop(final FileAlterationObserver observer);\n}\n\n```\n\n此接口的一大问题是抽象方法太多了，如果我们要用这个接口，意味着我们要实现每一个抽象方法，如果我们只是想要监控文件夹中的**文件创建**和**文件删除**事件，可是我们还是不得不实现所有的方法，很明显，这不是我们想要的。\n\n所以，我们需要下面的一个**适配器**，它用于实现上面的接口，但是**所有的方法都是空方法**，这样，我们就可以转而定义自己的类来继承下面这个类即可。\n\n```\npublic class FileAlterationListenerAdaptor implements FileAlterationListener {\n\n    public void onStart(final FileAlterationObserver observer) {\n    }\n\n    public void onDirectoryCreate(final File directory) {\n    }\n\n    public void onDirectoryChange(final File directory) {\n    }\n\n    public void onDirectoryDelete(final File directory) {\n    }\n\n    public void onFileCreate(final File file) {\n    }\n\n    public void onFileChange(final File file) {\n    }\n\n    public void onFileDelete(final File file) {\n    }\n\n    public void onStop(final FileAlterationObserver observer) {\n    }\n}\n\n```\n\n比如我们可以定义以下类，我们仅仅需要实现我们想实现的方法就可以了：\n\n```\npublic class FileMonitor extends FileAlterationListenerAdaptor {\n    public void onFileCreate(final File file) {\n        // 文件创建\n        doSomething();\n    }\n\n    public void onFileDelete(final File file) {\n        // 文件删除\n        doSomething();\n    }\n}\n\n```\n\n当然，上面说的只是适配器模式的其中一种，也是最简单的一种，无需多言。下面，再介绍**“正统的”**适配器模式。\n\n对象适配器模式\n\n来看一个《Head First 设计模式》中的一个例子，我稍微修改了一下，看看怎么将鸡适配成鸭，这样鸡也能当鸭来用。因为，现在鸭这个接口，我们没有合适的实现类可以用，所以需要适配器。\n\n```\npublic interface Duck {\n    public void quack(); // 鸭的呱呱叫\n      public void fly(); // 飞\n}\n\npublic interface Cock {\n    public void gobble(); // 鸡的咕咕叫\n      public void fly(); // 飞\n}\n\npublic class WildCock implements Cock {\n    public void gobble() {\n        System.out.println(\"咕咕叫\");\n    }\n      public void fly() {\n        System.out.println(\"鸡也会飞哦\");\n    }\n}\n\n```\n\n鸭接口有 fly() 和 quare() 两个方法，鸡 Cock 如果要冒充鸭，fly() 方法是现成的，但是鸡不会鸭的呱呱叫，没有 quack() 方法。这个时候就需要适配了：\n\n```\n// 毫无疑问，首先，这个适配器肯定需要 implements Duck，这样才能当做鸭来用\npublic class CockAdapter implements Duck {\n\n    Cock cock;\n    // 构造方法中需要一个鸡的实例，此类就是将这只鸡适配成鸭来用\n      public CockAdapter(Cock cock) {\n        this.cock = cock;\n    }\n\n    // 实现鸭的呱呱叫方法\n      @Override\n      public void quack() {\n        // 内部其实是一只鸡的咕咕叫\n        cock.gobble();\n    }\n\n      @Override\n      public void fly() {\n        cock.fly();\n    }\n}\n\n```\n\n客户端调用很简单了：\n\n```\npublic static void main(String[] args) {\n    // 有一只野鸡\n      Cock wildCock = new WildCock();\n      // 成功将野鸡适配成鸭\n      Duck duck = new CockAdapter(wildCock);\n      ...\n}\n\n```\n\n到这里，大家也就知道了适配器模式是怎么回事了。无非是我们需要一只鸭，但是我们只有一只鸡，这个时候就需要定义一个适配器，由这个适配器来充当鸭，但是适配器里面的方法还是由鸡来实现的。\n\n我们用一个图来简单说明下：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404212635.png)\n上图应该还是很容易理解的，我就不做更多的解释了。下面，我们看看类适配模式怎么样的。\n\n类适配器模式\n\n废话少说，直接上图：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404212648.png)\n看到这个图，大家应该很容易理解的吧，通过继承的方法，适配器自动获得了所需要的大部分方法。这个时候，客户端使用更加简单，直接`Target t = new SomeAdapter();`就可以了。\n\n适配器模式总结\n\n1.  类适配和对象适配的异同\n\n    > 一个采用继承，一个采用组合；\n    > \n    > 类适配属于静态实现，对象适配属于组合的动态实现，对象适配需要多实例化一个对象。\n    > \n    > 总体来说，对象适配用得比较多。\n\n2.  适配器模式和代理模式的异同\n\n    比较这两种模式，其实是比较对象适配器模式和代理模式，在代码结构上，它们很相似，都需要一个具体的实现类的实例。但是它们的目的不一样，代理模式做的是增强原方法的活；适配器做的是适配的活，为的是提供“把鸡包装成鸭，然后当做鸭来使用”，而鸡和鸭它们之间原本没有继承关系。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404212702.png)\n### 桥梁模式\n\n理解桥梁模式，其实就是理解代码抽象和解耦。\n\n我们首先需要一个桥梁，它是一个接口，定义提供的接口方法。\n\n```\npublic interface DrawAPI {\n   public void draw(int radius, int x, int y);\n}\n\n```\n\n然后是一系列实现类：\n\n```\npublic class RedPen implements DrawAPI {\n   @Override\n   public void draw(int radius, int x, int y) {\n      System.out.println(\"用红色笔画图，radius:\" + radius + \", x:\" + x + \", y:\" + y);\n   }\n}\npublic class GreenPen implements DrawAPI {\n   @Override\n   public void draw(int radius, int x, int y) {\n      System.out.println(\"用绿色笔画图，radius:\" + radius + \", x:\" + x + \", y:\" + y);\n   }\n}\npublic class BluePen implements DrawAPI {\n   @Override\n   public void draw(int radius, int x, int y) {\n      System.out.println(\"用蓝色笔画图，radius:\" + radius + \", x:\" + x + \", y:\" + y);\n   }\n}\n\n```\n\n定义一个抽象类，此类的实现类都需要使用 DrawAPI：\n\n```\npublic abstract class Shape {\n   protected DrawAPI drawAPI;\n\n   protected Shape(DrawAPI drawAPI){\n      this.drawAPI = drawAPI;\n   }\n   public abstract void draw();    \n}\n\n```\n\n定义抽象类的子类：\n\n```\n// 圆形\npublic class Circle extends Shape {\n   private int radius;\n\n   public Circle(int radius, DrawAPI drawAPI) {\n      super(drawAPI);\n      this.radius = radius;\n   }\n\n   public void draw() {\n      drawAPI.draw(radius, 0, 0);\n   }\n}\n// 长方形\npublic class Rectangle extends Shape {\n    private int x;\n      private int y;\n\n      public Rectangle(int x, int y, DrawAPI drawAPI) {\n        super(drawAPI);\n          this.x = x;\n          this.y = y;\n    }\n      public void draw() {\n      drawAPI.draw(0, x, y);\n   }\n}\n\n```\n\n最后，我们来看客户端演示：\n\n```\npublic static void main(String[] args) {\n    Shape greenCircle = new Circle(10, new GreenPen());\n      Shape redRectangle = new Rectangle(4, 8, new RedPen());\n\n      greenCircle.draw();\n      redRectangle.draw();\n}\n\n```\n\n可能大家看上面一步步还不是特别清晰，我把所有的东西整合到一张图上：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404212721.png)\n这回大家应该就知道抽象在哪里，怎么解耦了吧。桥梁模式的优点也是显而易见的，就是非常容易进行扩展。\n\n> 本节引用了[这里](https://www.tutorialspoint.com/design_pattern/bridge_pattern.htm)的例子，并对其进行了修改。\n\n### 装饰模式\n\n要把装饰模式说清楚明白，不是件容易的事情。也许读者知道 Java IO 中的几个类是典型的装饰模式的应用，但是读者不一定清楚其中的关系，也许看完就忘了，希望看完这节后，读者可以对其有更深的感悟。\n\n首先，我们先看一个简单的图，看这个图的时候，了解下层次结构就可以了：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404212733.png)\n我们来说说装饰模式的出发点，从图中可以看到，接口`Component`其实已经有了`ConcreteComponentA`和`ConcreteComponentB`两个实现类了，但是，如果我们要**增强**这两个实现类的话，我们就可以采用装饰模式，用具体的装饰器来**装饰**实现类，以达到增强的目的。\n\n> 从名字来简单解释下装饰器。既然说是装饰，那么往往就是**添加小功能**这种，而且，我们要满足可以添加多个小功能。最简单的，代理模式就可以实现功能的增强，但是代理不容易实现多个功能的增强，当然你可以说用代理包装代理的方式，但是那样的话代码就复杂了。\n\n首先明白一些简单的概念，从图中我们看到，所有的具体装饰者们 ConcreteDecorator_都可以作为 Component 来使用，因为它们都实现了 Component 中的所有接口。它们和 Component 实现类 ConcreteComponent_的区别是，它们只是装饰者，起**装饰**作用，也就是即使它们看上去牛逼轰轰，但是它们都只是在具体的实现中**加了层皮来装饰**而已。\n\n> 注意这段话中混杂在各个名词中的 Component 和 Decorator，别搞混了。\n\n下面来看看一个例子，先把装饰模式弄清楚，然后再介绍下 java io 中的装饰模式的应用。\n\n最近大街上流行起来了“快乐柠檬”，我们把快乐柠檬的饮料分为三类：红茶、绿茶、咖啡，在这三大类的基础上，又增加了许多的口味，什么金桔柠檬红茶、金桔柠檬珍珠绿茶、芒果红茶、芒果绿茶、芒果珍珠红茶、烤珍珠红茶、烤珍珠芒果绿茶、椰香胚芽咖啡、焦糖可可咖啡等等，每家店都有很长的菜单，但是仔细看下，其实原料也没几样，但是可以搭配出很多组合，如果顾客需要，很多没出现在菜单中的饮料他们也是可以做的。\n\n在这个例子中，红茶、绿茶、咖啡是最基础的饮料，其他的像金桔柠檬、芒果、珍珠、椰果、焦糖等都属于装饰用的。当然，在开发中，我们确实可以像门店一样，开发这些类：LemonBlackTea、LemonGreenTea、MangoBlackTea、MangoLemonGreenTea......但是，很快我们就发现，这样子干肯定是不行的，这会导致我们需要组合出所有的可能，而且如果客人需要在红茶中加双份柠檬怎么办？三份柠檬怎么办？万一有个变态要四份柠檬，所以这种做法是给自己找加班的。\n\n不说废话了，上代码。\n\n首先，定义饮料抽象基类：\n\n```\npublic abstract class Beverage {\n      // 返回描述\n      public abstract String getDescription();\n      // 返回价格\n      public abstract double cost();\n}\n\n```\n\n然后是三个基础饮料实现类，红茶、绿茶和咖啡：\n\n```\npublic class BlackTea extends Beverage {\n      public String getDescription() {\n        return \"红茶\";\n    }\n      public double cost() {\n        return 10;\n    }\n}\npublic class GreenTea extends Beverage {\n    public String getDescription() {\n        return \"绿茶\";\n    }\n      public double cost() {\n        return 11;\n    }\n}\n...// 咖啡省略\n\n```\n\n定义调料，也就是装饰者的基类，此类必须继承自 Beverage：\n\n```\n// 调料\npublic abstract class Condiment extends Beverage {\n\n}\n\n```\n\n然后我们来定义柠檬、芒果等具体的调料，它们属于装饰者，毫无疑问，这些调料肯定都需要继承 Condiment 类：\n\n```\npublic class Lemon extends Condiment {\n    private Beverage bevarage;\n      // 这里很关键，需要传入具体的饮料，如需要传入没有被装饰的红茶或绿茶，\n      // 当然也可以传入已经装饰好的芒果绿茶，这样可以做芒果柠檬绿茶\n      public Lemon(Beverage bevarage) {\n        this.bevarage = bevarage;\n    }\n      public String getDescription() {\n        // 装饰\n        return bevarage.getDescription() + \", 加柠檬\";\n    }\n      public double cost() {\n          // 装饰\n        return beverage.cost() + 2; // 加柠檬需要 2 元\n    }\n}\npublic class Mango extends Condiment {\n    private Beverage bevarage;\n      public Mango(Beverage bevarage) {\n        this.bevarage = bevarage;\n    }\n      public String getDescription() {\n        return bevarage.getDescription() + \", 加芒果\";\n    }\n      public double cost() {\n        return beverage.cost() + 3; // 加芒果需要 3 元\n    }\n}\n...// 给每一种调料都加一个类\n\n```\n\n看客户端调用：\n\n```\npublic static void main(String[] args) {\n      // 首先，我们需要一个基础饮料，红茶、绿茶或咖啡\n    Beverage beverage = new GreenTea();\n      // 开始装饰\n      beverage = new Lemon(beverage); // 先加一份柠檬\n      beverage = new Mongo(beverage); // 再加一份芒果\n\n      System.out.println(beverage.getDescription() + \" 价格：￥\" + beverage.cost());\n      //\"绿茶, 加柠檬, 加芒果 价格：￥16\"\n}\n\n```\n\n如果我们需要芒果珍珠双份柠檬红茶：\n\n```\nBeverage beverage = new Mongo(new Pearl(new Lemon(new Lemon(new BlackTea()))));\n\n```\n\n是不是很变态？\n\n看看下图可能会清晰一些：\n\n![](https://javadoop.com/blogimages/design-pattern/decorator-2.png)\n\n到这里，大家应该已经清楚装饰模式了吧。\n\n下面，我们再来说说 java IO 中的装饰模式。看下图 InputStream 派生出来的部分类：\n\n![](https://javadoop.com/blogimages/design-pattern/decorator-3.png)\n\n我们知道 InputStream 代表了输入流，具体的输入来源可以是文件（FileInputStream）、管道（PipedInputStream）、数组（ByteArrayInputStream）等，这些就像前面奶茶的例子中的红茶、绿茶，属于基础输入流。\n\nFilterInputStream 承接了装饰模式的关键节点，其实现类是一系列装饰器，比如 BufferedInputStream 代表用缓冲来装饰，也就使得输入流具有了缓冲的功能，LineNumberInputStream 代表用行号来装饰，在操作的时候就可以取得行号了，DataInputStream 的装饰，使得我们可以从输入流转换为 java 中的基本类型值。\n\n当然，在 java IO 中，如果我们使用装饰器的话，就不太适合面向接口编程了，如：\n\n```\nInputStream inputStream = new LineNumberInputStream(new BufferedInputStream(new FileInputStream(\"\")));\n\n```\n\n这样的结果是，InputStream 还是不具有读取行号的功能，因为读取行号的方法定义在 LineNumberInputStream 类中。\n\n我们应该像下面这样使用：\n\n```\nDataInputStream is = new DataInputStream(\n                              new BufferedInputStream(\n                                  new FileInputStream(\"\")));\n\n```\n\n> 所以说嘛，要找到纯的严格符合设计模式的代码还是比较难的。\n\n### 门面模式\n\n门面模式（也叫外观模式，Facade Pattern）在许多源码中有使用，比如 slf4j 就可以理解为是门面模式的应用。这是一个简单的设计模式，我们直接上代码再说吧。\n\n首先，我们定义一个接口：\n\n```\npublic interface Shape {\n   void draw();\n}\n\n```\n\n定义几个实现类：\n\n```\npublic class Circle implements Shape {\n\n   @Override\n   public void draw() {\n      System.out.println(\"Circle::draw()\");\n   }\n}\n\npublic class Rectangle implements Shape {\n\n   @Override\n   public void draw() {\n      System.out.println(\"Rectangle::draw()\");\n   }\n}\n\n```\n\n客户端调用：\n\n```\npublic static void main(String[] args) {\n    // 画一个圆形\n      Shape circle = new Circle();\n      circle.draw();\n\n      // 画一个长方形\n      Shape rectangle = new Rectangle();\n      rectangle.draw();\n}\n\n```\n\n以上是我们常写的代码，我们需要画圆就要先实例化圆，画长方形就需要先实例化一个长方形，然后再调用相应的 draw() 方法。\n\n下面，我们看看怎么用门面模式来让客户端调用更加友好一些。\n\n我们先定义一个门面：\n\n```\npublic class ShapeMaker {\n   private Shape circle;\n   private Shape rectangle;\n   private Shape square;\n\n   public ShapeMaker() {\n      circle = new Circle();\n      rectangle = new Rectangle();\n      square = new Square();\n   }\n\n  /**\n   * 下面定义一堆方法，具体应该调用什么方法，由这个门面来决定\n   */\n\n   public void drawCircle(){\n      circle.draw();\n   }\n   public void drawRectangle(){\n      rectangle.draw();\n   }\n   public void drawSquare(){\n      square.draw();\n   }\n}\n\n```\n\n看看现在客户端怎么调用：\n\n```\npublic static void main(String[] args) {\n  ShapeMaker shapeMaker = new ShapeMaker();\n\n  // 客户端调用现在更加清晰了\n  shapeMaker.drawCircle();\n  shapeMaker.drawRectangle();\n  shapeMaker.drawSquare();        \n}\n\n```\n\n门面模式的优点显而易见，客户端不再需要关注实例化时应该使用哪个实现类，直接调用门面提供的方法就可以了，因为门面类提供的方法的方法名对于客户端来说已经很友好了。\n\n### 组合模式\n\n组合模式用于表示具有层次结构的数据，使得我们对单个对象和组合对象的访问具有一致性。\n\n直接看一个例子吧，每个员工都有姓名、部门、薪水这些属性，同时还有下属员工集合（虽然可能集合为空），而下属员工和自己的结构是一样的，也有姓名、部门这些属性，同时也有他们的下属员工集合。\n\n```\npublic class Employee {\n   private String name;\n   private String dept;\n   private int salary;\n   private List<Employee> subordinates; // 下属\n\n   public Employee(String name,String dept, int sal) {\n      this.name = name;\n      this.dept = dept;\n      this.salary = sal;\n      subordinates = new ArrayList<Employee>();\n   }\n\n   public void add(Employee e) {\n      subordinates.add(e);\n   }\n\n   public void remove(Employee e) {\n      subordinates.remove(e);\n   }\n\n   public List<Employee> getSubordinates(){\n     return subordinates;\n   }\n\n   public String toString(){\n      return (\"Employee :[ Name : \" + name + \", dept : \" + dept + \", salary :\" + salary+\" ]\");\n   }   \n}\n\n```\n\n通常，这种类需要定义 add(node)、remove(node)、getChildren() 这些方法。\n\n这说的其实就是组合模式，这种简单的模式我就不做过多介绍了，相信各位读者也不喜欢看我写废话。\n\n### 享元模式\n\n英文是 Flyweight Pattern，不知道是谁最先翻译的这个词，感觉这翻译真的不好理解，我们试着强行关联起来吧。Flyweight 是轻量级的意思，享元分开来说就是 共享 元器件，也就是复用已经生成的对象，这种做法当然也就是轻量级的了。\n\n复用对象最简单的方式是，用一个 HashMap 来存放每次新生成的对象。每次需要一个对象的时候，先到 HashMap 中看看有没有，如果没有，再生成新的对象，然后将这个对象放入 HashMap 中。\n\n这种简单的代码我就不演示了。\n\n### 结构型模式总结\n\n前面，我们说了代理模式、适配器模式、桥梁模式、装饰模式、门面模式、组合模式和享元模式。读者是否可以分别把这几个模式说清楚了呢？在说到这些模式的时候，心中是否有一个清晰的图或处理流程在脑海里呢？\n\n代理模式是做方法增强的，适配器模式是把鸡包装成鸭这种用来适配接口的，桥梁模式做到了很好的解耦，装饰模式从名字上就看得出来，适合于装饰类或者说是增强类的场景，门面模式的优点是客户端不需要关心实例化过程，只要调用需要的方法即可，组合模式用于描述具有层次结构的数据，享元模式是为了在特定的场景中缓存已经创建的对象，用于提高性能。\n\n## 参考文章\n\n转自https://javadoop.com/post/design-pattern\n\n\n\n"
  },
  {
    "path": "docs/Java/design-parttern/设计模式学习总结.md",
    "content": "# 目录\n* [设计模式](#设计模式)\n* [创建型模式](#创建型模式)\n  * [单例](#单例)\n  * [工厂模式](#工厂模式)\n  * [原型模式](#原型模式)\n  * [建造者模式](#建造者模式)\n* [结构型模式](#结构型模式)\n  * [桥接模式](#桥接模式)\n  * [适配器模式](#适配器模式)\n  * [享元模式](#享元模式)\n  * [代理模式](#代理模式)\n  * [外观模式](#外观模式)\n  * [组合模式](#组合模式)\n  * [装饰者模式](#装饰者模式)\n  * [装饰者](#装饰者)\n* [行为型模式](#行为型模式)\n  * [策略模式](#策略模式)\n  * [命令模式](#命令模式)\n  * [模板方法模式](#模板方法模式)\n  * [状态模式](#状态模式)\n  * [观察者模式和事件监听机制](#观察者模式和事件监听机制)\n  * [责任链模式](#责任链模式)\n\n\n  \n设计模式基础学习总结\n这篇总结主要是基于我之前设计模式基础系列文章而形成的的。主要是把重要的知识点用自己的话说了一遍，可能会有一些错误，还望见谅和指点。谢谢\n\n更多详细内容可以查看我的专栏文章：设计模式学习\nhttps://blog.csdn.net/a724888/article/category/6780980\n\n<!-- more -->\n\n# 设计模式\n\n# 创建型模式\n\n创建型模式\n创建型模式的作用就是创建对象，说到创建一个对象，最熟悉的就是 new 一个对象，然后 set 相关属性。但是，在很多场景下，我们需要给客户端提供更加友好的创建对象的方式，尤其是那种我们定义了类，但是需要提供给其他开发者用的时候。\n    \n## 单例\n\n    单例模式保证全局的单例类只有一个实例，这样的话使用的时候直接获取即可，比如数据库的一个连接，Spring里的bean，都可以是单例的。\n    \n    单例模式一般有5种写法。\n    \n    第一种是饿汉模式，先把单例进行实例化，获取的时候通过静态方法直接获取即可。缺点是类加载后就完成了类的实例化，浪费部分空间。\n    \n    第二种是饱汉模式，先把单例置为null，然后通过静态方法获取单例时再进行实例化，但是可能有多线程同时进行实例化，会出现并发问题。\n    \n    第三种是逐步改进的方法，一开始可以用synchronized关键字进行同步，但是开销太大，而后改成使用volatile修饰单例，然后通过一次检查判断单例是否已初始化，如果未初始化就使用synchronized代码块，再次检查单例防止在这期间被初始化，而后才真正进行初始化。\n    \n    第四种是使用静态内部类来实现，静态内部类只在被使用的时候才进行初始化，所以在内部类中进行单例的实例化，只有用到的时候才会运行实例化代码。然后外部类再通过静态方法返回静态内部类的单例即可。\n    \n    第五种是枚举类，枚举类的底层实现其实也是内部类。枚举类确保每个类对象在全局是唯一的。所以保证它是单例，这个方法是最简单的。\n\n## 工厂模式\n\n    简单工厂一般是用一个工厂创建多个类的实例。\n    \n    工厂模式一般是指一个工厂服务一个接口，为这个接口的实现类进行实例化\n    \n    抽象工厂模式是指一个工厂服务于一个产品族，一个产品族可能包含多个接口，接口又会包含多个实现类，通过一个工厂就可以把这些绑定在一起，非常方便。\n\n## 原型模式\n\n    一般通过一个实例进行克隆从而获得更多同一原型的实例。使用实例的clone方法即可完成。\n\n## 建造者模式\n\n    建造者模式中有一个概念叫做链式调用，链式调用为一个类的实例化提供便利，一般提供系列的方法进行实例化，实际上就是将set方法改造一下，将原本返回为空的set方法改为返回this实例，从而实现链式调用。\n    \n    建造者模式在此基础上加入了builder方法，提供给外部进行调用，同样使用链式调用来完成参数注入。\n\n# 结构型模式\n\n\n结构型模式\n前面创建型模式介绍了创建对象的一些设计模式，这节介绍的结构型模式旨在通过改变代码结构来达到解耦的目的，使得我们的代码容易维护和扩展。\n\n## 桥接模式\n\n有点复杂。建议参考原文\n\n## 适配器模式\n\n适配器模式用于将两个不同的类进行适配。\n\n适配器模式和代理模式的异同\n\n 比较这两种模式，其实是比较对象适配器模式和代理模式，在代码结构上，\n 它们很相似，都需要一个具体的实现类的实例。\n 但是它们的目的不一样，代理模式做的是增强原方法的活；\n 适配器做的是适配的活，为的是提供“把鸡包装成鸭，然后当做鸭来使用”，\n 而鸡和鸭它们之间原本没有继承关系。\n\n 适配器模式可以分为类适配器，对象适配器等。\n\n 类适配器通过继承父类就可以把自己适配成父类了。\n 而对象适配器则需要把对象传入另一个对象的构造方法中，以便进行包装。\n\n## 享元模式\n\n/ 享元模式的核心在于享元工厂类，\n// 享元工厂类的作用在于提供一个用于存储享元对象的享元池，\n// 用户需要对象时，首先从享元池中获取，\n// 如果享元池中不存在，则创建一个新的享元对象返回给用户，\n// 在享元池中保存该新增对象。\n\n//享元模式\n//        英文是 Flyweight Pattern，不知道是谁最先翻译的这个词，感觉这翻译真的不好理解，我们试着强行关联起来吧。Flyweight 是轻量级的意思，享元分开来说就是 共享 元器件，也就是复用已经生成的对象，这种做法当然也就是轻量级的了。\n//\n//        复用对象最简单的方式是，用一个 HashMap 来存放每次新生成的对象。每次需要一个对象的时候，先到 HashMap 中看看有没有，如果没有，再生成新的对象，然后将这个对象放入 HashMap 中。\n//\n//        这种简单的代码我就不演示了。\n\n## 代理模式\n\n//    我们发现没有，代理模式说白了就是做 “方法包装” 或做 “方法增强”。\n// 在面向切面编程中，算了还是不要吹捧这个名词了，在 AOP 中，\n// 其实就是动态代理的过程。比如 Spring 中，\n// 我们自己不定义代理类，但是 Spring 会帮我们动态来定义代理，\n// 然后把我们定义在 @Before、@After、@Around 中的代码逻辑动态添加到代理中。\n\n## 外观模式\n\n外观模式一般封装具体的实现细节，为用户提供一个更加简单的接口。\n\n通过一个方法调用就可以获取需要的内容。\n\n## 组合模式\n\n//组合模式用于表示具有层次结构的数据，使得我们对单个对象和组合对象的访问具有一致性。\n\n//直接看一个例子吧，每个员工都有姓名、部门、薪水这些属性，\n// 同时还有下属员工集合（虽然可能集合为空），\n// 而下属员工和自己的结构是一样的，\n// 也有姓名、部门这些属性，\n// 同时也有他们的下属员工集合。\n\n    class Employee {\n        private String name;\n        private String dept;\n        private int salary;\n        private List<Employee> subordinates; // 下属\n    }\n\n## 装饰者模式\n\n## 装饰者\n装饰者模式把每个增强类都继承最高级父类。然后需要功能增强时把类实例传入增强类即可，然后增强类在使用时就可以增强原有类的功能了。\n\n和代理模式不同的是，装饰者模式每个装饰类都继承父类，并且可以进行多级封装。\n\n\n# 行为型模式\n\n行为型模式\n行为型模式关注的是各个类之间的相互作用，将职责划分清楚，使得我们的代码更加地清晰。\n\n## 策略模式\n\n策略模式一般把一个策略作为一个类，并且在需要指定策略的时候传入实例，于是我们可以在需要使用算法的地方传入指定算法。\n\n## 命令模式\n\n命令模式一般分为命令发起者，命令以及命令接受者三个角色。\n\n命令发起者在使用时需要注入命令实例。然后执行命令调用。\n\n命令调用实际上会调用命令接收者的方法进行实际调用。\n\n比如遥控器按钮相当于一条命令，点击按钮时命令运行，自动调用电视机提供的方法即可。\n\n## 模板方法模式\n\n模板方法一般指提供了一个方法模板，并且其中有部分实现类和部分抽象类，并且规定了执行顺序。\n\n实现类是模板提供好的方法。而抽象类则需要用户自行实现。\n\n模板方法规定了一个模板中方法的执行顺序，非常适合一些开发框架，于是模板方法也广泛运用在开源框架中。\n\n## 状态模式\n\n少见。\n\n## 观察者模式和事件监听机制\n\n观察者模式一般用于订阅者和消息发布者之间的数据订阅。\n\n一般分为观察者和主题，观察者订阅主题，把实例注册到主题维护的观察者列表上。\n\n而主题更新数据时自动把数据推给观察者或者通知观察者数据已经更新。\n\n但是由于这样的方式消息推送耦合关系比较紧。并且很难在不打开数据的情况下知道数据类型是什么。\n\n知道后来为了使数据格式更加灵活，使用了事件和事件监听器的模式，事件包装的事件类型和事件数据，从主题和观察者中解耦。\n\n主题当事件发生时，触发该事件的所有监听器，把该事件通过监听器列表发给每个监听器，监听得到事件以后，首先根据自己支持处理的事件类型中找到对应的事件处理器，再用处理器处理对应事件。\n\n\n\n## 责任链模式\n\n责任链通常需要先建立一个单向链表，然后调用方只需要调用头部节点就可以了，后面会自动流转下去。比如流程审批就是一个很好的例子，只要终端用户提交申请，根据申请的内容信息，自动建立一条责任链，然后就可以开始流转了。\n\n\n"
  },
  {
    "path": "docs/Java/network/Java网络与NIO总结.md",
    "content": "# 目录\n\n* [目录](#目录)\n  * [Java IO](#java-io)\n  * [Socket编程](#socket编程)\n  * [客户端，服务端的线程模型](#客户端，服务端的线程模型)\n  * [IO模型](#io模型)\n  * [NIO](#nio)\n  * [AIO](#aio)\n  * [Tomcat中的NIO模型](#tomcat中的nio模型)\n  * [Tomcat的container](#tomcat的container)\n  * [netty](#netty)\n\n\n# 目录\n  * [Java IO](#java-io)\n  * [Socket编程](#socket编程)\n  * [客户端，服务端的线程模型](#客户端，服务端的线程模型)\n  * [IO模型](#io模型)\n  * [NIO](#nio)\n  * [AIO](#aio)\n  * [Tomcat中的NIO模型](#tomcat中的nio模型)\n  * [Tomcat的container](#tomcat的container)\n  * [netty](#netty)\n\n\n\n这篇总结主要是基于我之前Java网络编程与NIO系列文章而形成的的。主要是把重要的知识点用自己的话说了一遍，可能会有一些错误，还望见谅和指点。谢谢\n\n更多详细内容可以查看我的专栏文章：Java网络编程与NIO\n\nhttps://blog.csdn.net/column/details/21963.html\n<!-- more -->\n\n## Java IO\n\nJava IO的基础知识已在前面讲过\n\n## Socket编程\n\nsocket是操作系统提供的网络编程接口，他封装了对于TCP/IP协议栈的支持，用于进程间的通信，当有连接接入主机以后，操作系统自动为其分配一个socket套接字，套接字绑定着一个IP与端口号。通过socket接口，可以获取tcp连接的输入流和输出流，并且通过他们进行读取和写入此操作。\n\nJava提供了net包用于socket编程，同时支持像Inetaddress，URL等工具类，使用socket绑定一个endpoint（ip+端口号），可以用于客户端的请求处理和发送，使用serversocket绑定本地ip和端口号，可以用于服务端接收TCP请求。\n\n## 客户端，服务端的线程模型\n\n一般客户端使用单线程模型即可，当有数据到来时启动线程读取，需要写入数据时开启线程进行数据写入。\n\n服务端一般使用多线程模型，一个线程负责接收tcp连接请求，每当接收到请求后开启一个线程处理它的读写请求。\n\nudp的客户端和服务端就比较简单了，由于udp数据报长度是确定的，只需要写入一个固定的缓存和读取一个固定的缓存空间即可。\n\n一般通过DatagramPacket包装一个udp数据报，然后通过DatagramSocket发送\n\n## IO模型\n\n上述的socket在处理IO请求时使用的是阻塞模型。\n\n于是我们还是得来探讨一下IO模型。\n\n一般认为，应用程序处理IO请求需要将内核缓存区中的数据拷贝到用户缓冲区。这个步骤可以通过系统调用来完成，而用户程序处理IO请求的时候，需要先检查用户缓冲区是否准备好了数据，这个操作是系统调用recevfrom，如果数据没有准备好，默认会阻塞调用该方法的线程。\n\n这样就导致了线程处理IO请求需要频繁进行阻塞，特别是并发量大的时候，线程切换的开销巨大。\n\n一般认为有几种IO模型\n\n1 阻塞IO ：就是线程会阻塞在系统调用recevfrom上，并且等待数据准备就绪以后才会返回。\n\n2 非阻塞IO : 不阻塞在系统调用recevfrom，而是通过自旋忙等的方式不断询问缓冲区数据是否准备就绪，避免线程阻塞的开销。\n\n3 IO多路复用 ：使用IO多路复用器管理socket，由于每个socket是一个文件描述符，操作系统可以维护socket和它的连接状态，一般分为可连接，可读和可写等状态。\n\n每当用户程序接受到socket请求，将请求托管给多路复用器进行监控，当程序对请求感兴趣的事件发生时，多路复用器以某种方式通知或是用户程序自己轮询请求，以便获取就绪的socket，然后只需使用一个线程进行轮询，多个线程处理就绪请求即可。\n\nIO多路复用避免了每个socket请求都需要一个线程去处理，而是使用事件驱动的方式，让少数的线程去处理多数socket的IO请求。\n\nLinux操作系统对IO多路复用提供了较好的支持，select，poll，epoll是Linux提供的支持IO多路复用的API。一般用户程序基于这个API去开发自己的IO复用模型。比如NIO的非阻塞模型，就是采用了IO多路复用的方式，是基于epoll实现的。\n\n3.1 select方式主要是使用数组来存储socket描述符，系统将发生事件的描述符做标记，然后IO复用器在轮询描述符数组的时候，就可以知道哪些请求是就绪了的。缺点是数组的长度只能到1024，并且需要不断地在内核空间和用户空间之间拷贝数组。\n\n3.2 poll方式不采用数组存储描述符，而是使用独立的数据结构来描述，并且使用id来表示描述符，能支持更多的请求数量，缺点和select方式有点类似，就是轮询的效率很低，并且需要拷贝数据。\n\n当然，上述两种方法适合在请求总数较少，并且活跃请求数较多的情况，这种场景下他们的性能还是不错的。\n\n3.3 epoll\n\nepoll函数会在内核空间开辟一个特殊的数据结构，红黑树，树节点中存放的是一个socket描述符以及用户程序感兴趣的事件类型。同时epoll还会维护一个链表。用于存储已经就绪的socket描述符节点。\n\n由Linux内核完成对红黑树的维护，当事件到达时，内核将就绪的socket节点加入链表中，用户程序可以直接访问这个链表以便获取就绪的socket。\n\n当然了，这些操作都linux包装在epoll的api中了。\n\nepoll_create函数会执行红黑树的创建操作。\n\nepoll_ctl函数会将socket和感兴趣的事件注册到红黑树中。\n\nepoll_wait函数会等待内核空间发来的链表，从而执行IO请求。\n\nepoll的水平触发和边缘触发有所区别，水平触发的意思是，如果用户程序没有执行就绪链表里的任务，epoll仍会不断通知程序。\n\n而边缘触发只会通知程序一次，之后socket的状态不发生改变epoll就不会再通知程序了。\n\n4 信号驱动\n略\n\n5 异步非阻塞\n\n用户进程发起read操作之后，立刻就可以开始去做其它的事。而另一方面，从kernel的角度，当它受到一个asynchronous read之后，首先它会立刻返回，所以不会对用户进程产生任何block。然后，kernel会等待数据准备完成，然后将数据拷贝到用户内存，当这一切都完成之后，kernel会给用户进程发送一个signal，告诉它read操作完成了。\n\n事实上就是，用户提交IO请求，然后直接返回，并且内核自动完成将数据从内核缓冲区复制到用户缓冲区，完成后再通知用户。\n\n当然，内核通知我们以后我们还需要执行剩余的操作，但是我们的代码已经继续往下运行了，所以AIO采用了回调的机制，为每个socket注册一个回调事件或者是回调处理器，在处理器中完成数据的操作，也就是内核通知到用户的时候，会自动触发回调函数，完成剩余操作。\n这样的方式就是异步的网络编程。\n\n但是，想要让操作系统支持这样的功能并非易事，windows的IOCP可以支持AIO方式，但是Linux的AIO支持并不是很好\n## NIO\n\n由于Java原生的socket只支持阻塞方式处理IO\n\n所以Java后来推出了新版IO 也叫New IO = NIO\n\nNIO提出了socketChannel，serversocketchannel，bytebuffer，selector和selectedkey等概念。\n\n1 socketchannel其实就是socket的替代品，他的好处是多个socket可以复用同一个bytebuffer，因为socket是从channel里打开的，所以多个socket都可以访问channel绑定着的buffer。\n\n2 serversocketchannel顾名思义，是用在服务端的channel。\n\n3 bytebuffer以前对用户是透明的，用户直接操作io流即可，所以之前的socket io操作都是阻塞的，引入bytebuffer以后，用户可以更灵活地进行io操作。\n\nbuffer可以分为不同数据类型的buffer，但是常用的还是bytebuffer。写入数据时按顺序写入，写入完使用flip方法反转缓冲区，让接收端反向读取。这个操作比较麻烦，后来的netty对缓冲区进行了重新封装，封装了这个经常容易出错的方法。\n\n4 selector其实就是对io多路复用器的封装，一般基于linux的epoll来实现。\nsocket把感兴趣的事件和描述符注册到selector上，然后通过遍历selectedKey来获取感兴趣的请求，进行IO操作。\nselectedkey应该就是epoll中就绪链表的实现了。\n\n5 所以一般的流程是：\n新建一个serversocket，启动一个线程进行while循环，当有请求接入时，使用accept方法阻塞获取socket，然后将socket和感兴趣的事件注册到selector上。再开启一个线程轮询selectoredKey，当请求就绪时开启一个线程去处理即可。\n\n## AIO\n\n后来NIO发展到2.0，Java又推出了AIO 的API，与上面描述的异步非阻塞模型类似。\n\nAIO使用回调的方式处理IO请求，在socket上注册一个回调函数，然后提交请求后直接返回。由操作系统完成数据拷贝操作，需要操作系统对AIO的支持。\n\nAIO的具体使用方式还是比较复杂的，感兴趣的可以自己查阅资料。\n\n## Tomcat中的NIO模型\n\nTomcat作为一个应用服务器，分为connector和container两个部分，connector负责接收请求，而container负责解析请求。\n\n一般connector负责接收http请求，当然首先要建立tcp连接，所以涉及到了如何处理连接和IO请求。\n\nTomcat使用endpoint的概念来绑定一个ip+port，首先，使用acceptor循环等待连接请求。然后开启一个线程池，也叫poller池，每个请求绑定一个poller进行后续处理，poller将socket请求封装成一个事件，并且将这个事件注册到selector中。\n\npoller还需要维护一个事件列表，以便获取selector上就绪的事件。然后poller再去列表中获取就绪的请求，将其封装成processor，交给后续的worker线程池，会有worker将其提交给container流程中进行处理。\n\n当然，到达container之后还有非常复杂的处理过程，稍微提几个点。\n\n## Tomcat的container\n\ncontainer是一个多级容器，最外层到最内层依次是engine，host，context和wrapper\n\n下面是个server.xml文件实例，Tomcat根据该文件进行部署\n\n````\n<Server>                                                //顶层类元素，可以包括多个Service   \n    <Service>                                           //顶层类元素，可包含一个Engine，多个Connecter\n        <Connector>                                     //连接器类元素，代表通信接口\n                <Engine>                                //容器类元素，为特定的Service组件处理客户请求，要包含多个Host\n                        <Host>                          //容器类元素，为特定的虚拟主机组件处理客户请求，可包含多个Context\n                                <Context>               //容器类元素，为特定的Web应用处理所有的客户请求\n                                </Context>\n                        </Host>\n                </Engine>\n        </Connector>\n    </Service>\n</Server>\n````\n\n根据配置文件初始化容器信息，当请求到达时进行容器间的请求传递，事实上整个链条被称作pipeline，pipeline连接了各个容器的入口，由于每个容器和组件都实现了lifecycle接口。\n\ntomcat可以在任意流程中通过加监听器的方式监听组件的生命周期，也就能够控制整个运行的流程，通过在pipeline上增加valve可以增加一些自定义的操作。\n\n一般到wrapper层才开始真正的请求解析，因为wrapper其实就是对servlet的简单封装，此时进来的请求和响应已经是httprequest和httpresponse，很多信息已经解析完毕，只需要按照service方法执行业务逻辑即可，当然在执行service方法之前，会调用filter链先执行过滤操作。\n\n## netty\n\nnetty我也不是很在行，这里简单总结一下\n\nnetty是一个基于事件驱动的网络编程框架。\n\n因为直接基于Java NIO编程复杂度太高，而且容易出错，于是netty对NIO进行了改造和封装。形成了一个比较完整的网络框架，可以通过他实现rpc，http服务。\n\n先了解一下两种线程模型。reactor和proactor。\n\n1 reactor就是netty采用的模型，首先也是使用一个acceptor线程接收连接请求，然后开启一个线程组reactor thread pool。\n\nserver会事先在endpoint上注册一系列的回调方法，然后接收socket请求后交给底层的selector进行管理，当selector对应的事件响应以后，会通知用户进程，然后reactor工作线程会执行接下来的IO请求，执行操作是写在回调处理器中的。\n\n\n其实netty 支持三种reactor模型\n1.1.Reactor单线程模型：Reactor单线程模型，指的是所有的I/O操作都在同一个NIO线程上面完成。对于一些小容量应用场景，可以使用单线程模型。\n\n1.2.Reactor多线程模型：Rector多线程模型与单线程模型最大的区别就是有一组NIO线程处理I/O操作。主要用于高并发、大业务量场景。\n\n1.3.主从Reactor多线程模型：主从Reactor线程模型的特点是服务端用于接收客户端连接的不再是个1个单独的NIO线程，而是一个独立的NIO线程池。利用主从NIO线程模型，可以解决1个服务端监听线程无法有效处理所有客户端连接的性能不足问题\n\n2 proactor模型其实是基于异步非阻塞IO模型的，当accpetor接收到请求以后，直接提交异步的io请求给linux内核，内核完成io请求后会回写消息到proactor提供的事件队列中，此时工作线程查看到IO请求已完成，则会继续剩余的工作，也是通过回调处理器来进行的。\n\n所以两者最大的差别是，前者基于epoll的IO多路复用，后者基于AIO实现。\n\n3 netty的核心组件：\n\nbytebuf\n\nbytebuf是对NIO中Bytebuffer的优化和扩展，并且支持堆外内存分配，堆外内存避免gc，可以更好地与内核空间进行交换数据。\n\nchannel和NIO的channel类似，但是NIO的socket代码改成nio实现非常麻烦，所以netty优化了这个过程，只需替换几个类就可以实现不更新太多代码就完成旧IO和新IO的切换。\n\nchannelhandler就是任务的处理器了，使用回调函数的方式注册到channel中，更准确来说是注册到channelpipeline里。\n\nchannelpipeline是用来管理和连接多个channelhandler的容器，执行任务时，会根据channelpipeline的调用链完成处理器的顺序调用，启动服务器时只需要将需要的channelhandler注册在上面就可以了。\n    \neventloop\n在Netty的线程模型中，一个EventLoop将由一个永远不会改变的Thread驱动，而一个Channel一生只会使用一个EventLoop（但是一个EventLoop可能会被指派用于服务多个Channel），在Channel中的所有I/O操作和事件都由EventLoop中的线程处理，也就是说一个Channel的一生之中都只会使用到一个线程。\n\n\nbootstrap\n\n在深入了解地Netty的核心组件之后，发现它们的设计都很模块化，如果想要实现你自己的应用程序，就需要将这些组件组装到一起。Netty通过Bootstrap类，以对一个Netty应用程序进行配置（组装各个组件），并最终使它运行起来。\n\n对于客户端程序和服务器程序所使用到的Bootstrap类是不同的，后者需要使用ServerBootstrap，这样设计是因为，在如TCP这样有连接的协议中，服务器程序往往需要一个以上的Channel，通过父Channel来接受来自客户端的连接，然后创建子Channel用于它们之间的通信，而像UDP这样无连接的协议，它不需要每个连接都创建子Channel，只需要一个Channel即可。\n\n\n"
  },
  {
    "path": "docs/Java/network/Java网络编程与NIO详解：IO模型与Java网络编程模型.md",
    "content": "# 目录\n\n* [目录](#目录)\n  * [IO模型介绍](#io模型介绍)\n    * [阻塞 I/O（blocking IO）](#阻塞-io（blocking-io）)\n    * [非阻塞 I/O（nonblocking IO）](#非阻塞-io（nonblocking-io）)\n    * [I/O 多路复用（ IO multiplexing）](#io-多路复用（-io-multiplexing）)\n    * [异步 I/O（asynchronous IO）](#异步-io（asynchronous-io）)\n  * [阻塞IO,非阻塞IO 与 同步IO, 异步IO的区别和联系](#阻塞io非阻塞io-与-同步io-异步io的区别和联系)\n    * [IO模型的形象举例](#io模型的形象举例)\n    * [Select/Poll/Epoll 轮询机制](#selectpollepoll-轮询机制)\n  * [Java网络编程模型](#java网络编程模型)\n    * [BIO](#bio)\n    * [NIO](#nio)\n    * [AIO](#aio)\n    * [对比](#对比)\n\n\n# 目录\n  * [IO模型介绍](#io模型介绍)\n    * [阻塞 I/O（blocking IO）](#阻塞-io（blocking-io）)\n    * [非阻塞 I/O（nonblocking IO）](#非阻塞-io（nonblocking-io）)\n    * [I/O 多路复用（ IO multiplexing）](#io-多路复用（-io-multiplexing）)\n    * [异步 I/O（asynchronous IO）](#异步-io（asynchronous-io）)\n  * [阻塞IO,非阻塞IO 与 同步IO, 异步IO的区别和联系](#阻塞io非阻塞io-与-同步io-异步io的区别和联系)\n    * [IO模型的形象举例](#io模型的形象举例)\n    * [Select/Poll/Epoll 轮询机制](#selectpollepoll-轮询机制)\n  * [Java网络编程模型](#java网络编程模型)\n    * [BIO](#bio)\n    * [NIO](#nio)\n    * [AIO](#aio)\n    * [对比](#对比)\n\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章将同步到我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《不可轻视的Java网络编程》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从计算机网络的基础知识入手，一步步地学习Java网络基础，从socket到nio、bio、aio和netty等网络编程知识，并且进行实战，网络编程是每一个Java后端工程师必须要学习和理解的知识点，进一步来说，你还需要掌握Linux中的网络编程原理，包括IO模型、网络编程框架netty的进阶原理，才能更完整地了解整个Java网络编程的知识体系，形成自己的知识框架。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供部分知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n基本概念说明\n\n**用户空间与内核空间**\n\n现在操作系统都是采用虚拟存储器，那么对32位操作系统而言，它的寻址空间（虚拟存储空间）为4G（2的32次方）。操作系统的核心是内核，独立于普通的应用程序，可以访问受保护的内存空间，也有访问底层硬件设备的所有权限。为了保证用户进程不能直接操作内核（kernel），保证内核的安全，操作系统将虚拟空间划分为两部分，一部分为内核空间，一部分为用户空间。针对linux操作系统而言，将最高的1G字节（从虚拟地址0xC0000000到0xFFFFFFFF），供内核使用，称为内核空间，而将较低的3G字节（从虚拟地址0x00000000到0xBFFFFFFF），供各个进程使用，称为用户空间。\n\n**进程切换**\n\n为了控制进程的执行，内核必须有能力挂起正在CPU上运行的进程，并恢复以前挂起的某个进程的执行。这种行为被称为进程切换。因此可以说，任何进程都是在操作系统内核的支持下运行的，是与内核紧密相关的。\n\n从一个进程的运行转到另一个进程上运行，这个过程中经过下面这些变化：\n\n*   保存处理机上下文，包括程序计数器和其他寄存器。\n*   更新PCB信息。\n*   把进程的PCB移入相应的队列，如就绪、在某事件阻塞等队列。 选择另一个进程执行，并更新其PCB。\n*   更新内存管理的数据结构。\n*   恢复处理机上下文。\n\n**进程的阻塞**\n\n正在执行的进程，由于期待的某些事件未发生，如请求系统资源失败、等待某种操作的完成、新数据尚未到达或无新工作做等，则由系统自动执行阻塞原语(Block)，使自己由运行状态变为阻塞状态。可见，进程的阻塞是进程自身的一种主动行为，也因此只有处于运行态的进程（获得CPU），才可能将其转为阻塞状态。当进程进入阻塞状态，是不占用CPU资源的。\n\n**文件描述符**\n\n文件描述符（File descriptor）是计算机科学中的一个术语，是一个用于表述指向文件的引用的抽象化概念。\n\n文件描述符在形式上是一个非负整数。实际上，它是一个索引值，指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时，内核向进程返回一个文件描述符。在程序设计中，一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统。\n\n**缓存 IO**\n\n缓存 IO 又被称作标准 IO，大多数文件系统的默认 IO 操作都是缓存 IO。在 Linux 的缓存 IO 机制中，操作系统会将 IO 的数据缓存在文件系统的页缓存（ page cache ）中，也就是说，数据会先被拷贝到操作系统内核的缓冲区中，然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。\n\n缓存 IO 的缺点：\n\n数据在传输过程中需要在应用程序地址空间和内核进行多次数据拷贝操作，这些数据拷贝操作所带来的 CPU 以及内存开销是非常大的。\n\n## IO模型介绍\n\n作者：cooffeelis\n链接：https://www.jianshu.com/p/511b9cffbdac\n來源：简书\n著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。\n\n常用的5种IO模型:\nblocking IO\nnonblocking IO\nIO multiplexing\nsignal driven IO\nasynchronous IO\n\n再说一下IO发生时涉及的对象和步骤:\n\n**_对于一个network IO (这里我们以read举例)，它会涉及到两个系统对象：_**\n\n*   一个是调用这个IO的process (or thread)\n*   一个就是系统内核(kernel)\n\n**_当一个read操作发生时，它会经历两个阶段：_**\n\n*   等待数据准备,比如accept(), recv()等待数据`(Waiting for the data to be ready)`\n*   将数据从内核拷贝到进程中, 比如 accept()接受到请求,recv()接收连接发送的数据后需要复制到内核,再从内核复制到进程用户空间`(Copying the data from the kernel to the process)`\n\n**_对于socket流而言,数据的流向经历两个阶段：_**\n\n*   第一步通常涉及等待网络上的数据分组到达，然后被复制到内核的某个缓冲区。\n*   第二步把数据从内核缓冲区复制到应用进程缓冲区。\n\n记住这两点很重要，因为这些IO Model的区别就是在两个阶段上各有不同的情况。\n\n### 阻塞 I/O（blocking IO）\n\n* * *\n\n在linux中，默认情况下所有的socket都是blocking，一个典型的读操作流程大概是这样：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405094447.png)\n阻塞IO流程\n\n当用户进程调用了recvfrom这个系统调用，kernel就开始了IO的第一个阶段：准备数据（对于网络IO来说，很多时候数据在一开始还没有到达。比如，还没有收到一个完整的UDP包。这个时候kernel就要等待足够的数据到来）。这个过程需要等待，也就是说数据被拷贝到**操作系统内核的缓冲区**中是需要一个过程的。而在用户进程这边，整个进程会被阻塞（当然，是进程自己选择的阻塞）。当kernel一直等到数据准备好了，它就会**将数据从kernel中拷贝到用户内存**，然后kernel返回结果，用户进程才解除block的状态，重新运行起来。\n\n> 所以，blocking IO的特点就是在IO执行的两个阶段都被block了。\n\n### 非阻塞 I/O（nonblocking IO）\n\n* * *\n\nlinux下，可以通过设置socket使其变为non-blocking。当对一个non-blocking socket执行读操作时，流程是这个样子：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405094507.png)\n非阻塞 I/O 流程\n\n当用户进程发出read操作时，如果kernel中的数据还没有准备好，**那么它并不会block用户进程，而是立刻返回一个error**。从用户进程角度讲 ，它发起一个read操作后，并不需要等待，而是马上就得到了一个结果。用户进程判断结果是一个error时，它就知道数据还没有准备好，于是它可以再次发送read操作。一旦kernel中的数据准备好了，并且又再次收到了用户进程的system call，那么它马上就将数据拷贝到了用户内存，然后返回。\n\n> 所以，nonblocking IO的特点是用户进程需要不断的主动询问kernel数据好了没有。\n\n***值得注意的是,此时的非阻塞IO只是应用到等待数据上,当真正有数据到达执行recvfrom的时候,还是同步阻塞IO来的, 从图中的copy data from kernel to user可以看出 ***\n\n### I/O 多路复用（ IO multiplexing）\n\n* * *\n\nIO multiplexing就是我们说的select，poll，epoll，有些地方也称这种IO方式为event driven IO。select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。它的基本原理就是select，poll，epoll这个function会不断的轮询所负责的所有socket，当某个socket有数据到达了，就通知用户进程。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405094539.png)\n\nI/O 多路复用流程\n\n这个图和blocking IO的图其实并没有太大的不同，事实上，还更差一些。因为这里需要使用两个system call (select 和 recvfrom)，而blocking IO只调用了一个system call (recvfrom)。但是，用select的优势在于它可以同时处理多个connection。\n\n> 所以，如果处理的连接数不是很高的话，使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好，可能延迟还更大。select/epoll的优势并不是对于单个连接能处理得更快，而是在于能处理更多的连接。）\n\nIO复用的实现方式目前主要有select、poll和epoll。\n\nselect和poll的原理基本相同：\n\n*   注册待侦听的fd(这里的fd创建时最好使用非阻塞)\n*   每次调用都去检查这些fd的状态，当有一个或者多个fd就绪的时候返回\n*   返回结果中包括已就绪和未就绪的fd\n\n相比select，poll解决了单个进程能够打开的文件描述符数量有限制这个问题：select受限于FD_SIZE的限制，如果修改则需要修改这个宏重新编译内核；而poll通过一个pollfd数组向内核传递需要关注的事件，避开了文件描述符数量限制。\n\n此外，select和poll共同具有的一个很大的缺点就是包含大量fd的数组被整体复制于用户态和内核态地址空间之间，开销会随着fd数量增多而线性增大。\n\nselect和poll就类似于上面说的就餐方式。但当你每次都去询问时，老板会把所有你点的饭菜都轮询一遍再告诉你情况，当大量饭菜很长时间都不能准备好的情况下是很低效的。于是，老板有些不耐烦了，就让厨师每做好一个菜就通知他。这样每次你再去问的时候，他会直接把已经准备好的菜告诉你，你再去端。这就是事件驱动IO就绪通知的方式-**epoll**。\n\nepoll的出现，解决了select、poll的缺点：\n\n*   基于事件驱动的方式，避免了每次都要把所有fd都扫描一遍。\n*   epoll_wait只返回就绪的fd。\n*   epoll使用nmap内存映射技术避免了内存复制的开销。\n*   epoll的fd数量上限是操作系统的最大文件句柄数目,这个数目一般和内存有关，通常远大于1024。\n\n目前，epoll是Linux2.6下最高效的IO复用方式，也是Nginx、Node的IO实现方式。而在freeBSD下，kqueue是另一种类似于epoll的IO复用方式。\n\n此外，对于IO复用还有一个水平触发和边缘触发的概念：\n\n*   水平触发：当就绪的fd未被用户进程处理后，下一次查询依旧会返回，这是select和poll的触发方式。\n*   边缘触发：无论就绪的fd是否被处理，下一次不再返回。理论上性能更高，但是实现相当复杂，并且任何意外的丢失事件都会造成请求处理错误。epoll默认使用水平触发，通过相应选项可以使用边缘触发。\n\n> 点评：\n> **_I/O 多路复用的特点是通过一种机制一个进程能同时等待多个文件描述符，_**而这些文件描述符（套接字描述符）其中的任意一个进入读就绪状态，select()函数就可以返回。\n> **_所以, IO多路复用，本质上不会有并发的功能，因为任何时候还是只有一个进程或线程进行工作，它之所以能提高效率是因为select\\epoll 把进来的socket放到他们的 '监视' 列表里面，当任何socket有可读可写数据立马处理，那如果select\\epoll 手里同时检测着很多socket， 一有动静马上返回给进程处理，总比一个一个socket过来,阻塞等待,处理高效率。_**\n> 当然也可以多线程/多进程方式，一个连接过来开一个进程/线程处理，这样消耗的内存和进程切换页会耗掉更多的系统资源。\n> 所以我们可以结合IO多路复用和多进程/多线程 来高性能并发，IO复用负责提高接受socket的通知效率，收到请求后，交给进程池/线程池来处理逻辑。\n>\n\n### 异步 I/O（asynchronous IO）\n\n* * *\n\nlinux下的asynchronous IO其实用得很少。先看一下它的流程：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405094934.png)\n异步IO 流程\n\n用户进程发起read操作之后，立刻就可以开始去做其它的事。而另一方面，从kernel的角度，当它受到一个asynchronous read之后，首先它会立刻返回，所以不会对用户进程产生任何block。然后，kernel会等待数据准备完成，然后将数据拷贝到用户内存，当这一切都完成之后，kernel会给用户进程发送一个signal，告诉它read操作完成了。\n\n## 阻塞IO,非阻塞IO 与 同步IO, 异步IO的区别和联系\n\n阻塞IO VS 非阻塞IO：\n\n概念：\n阻塞和非阻塞关注的是**程序在等待调用结果（消息，返回值）时的状态.**\n阻塞调用是指调用结果返回之前，当前线程会被挂起。调用线程只有在得到结果之后才会返回。非阻塞调用指在不能立刻得到结果之前，该调用不会阻塞当前线程。\n\n例子：你打电话问书店老板有没有《分布式系统》这本书，你如果是阻塞式调用，你会一直把自己“挂起”，直到得到这本书有没有的结果，如果是非阻塞式调用，你不管老板有没有告诉你，你自己先一边去玩了， 当然你也要偶尔过几分钟check一下老板有没有返回结果。在这里阻塞与非阻塞与是否同步异步无关。跟老板通过什么方式回答你结果无关。\n\n* * *\n\n分析：\n阻塞IO会一直block住对应的进程直到操作完成，而非阻塞IO在kernel还准备数据的情况下会立刻返回。\n\n同步IO VS 异步IO：\n\n概念：\n同步与异步同步和异步关注的是___消息通信机制___(synchronous communication/ asynchronous communication)所谓同步，就是在发出一个_调用_时，在没有得到结果之前，该_调用_就不返回。但是一旦调用返回，就得到返回值了。换句话说，就是由_调用者_主动等待这个_调用_的结果。而异步则是相反，_调用_在发出之后，这个调用就直接返回了，所以没有返回结果。换句话说，当一个异步过程调用发出后，调用者不会立刻得到结果。而是在_调用_发出后，_被调用者_通过状态、通知来通知调用者，或通过回调函数处理这个调用。\n\n典型的异步编程模型比如Node.js举个通俗的例子：你打电话问书店老板有没有《分布式系统》这本书，如果是同步通信机制，书店老板会说，你稍等，”我查一下\"，然后开始查啊查，等查好了（可能是5秒，也可能是一天）告诉你结果（返回结果）。而异步通信机制，书店老板直接告诉你我查一下啊，查好了打电话给你，然后直接挂电话了（不返回结果）。然后查好了，他会主动打电话给你。在这里老板通过“回电”这种方式来回调。\n\n* * *\n\n分析：\n在说明同步IO和异步IO的区别之前，需要先给出两者的定义。Stevens给出的定义（其实是POSIX的定义）是这样子的：\n\n> A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes;\n> An asynchronous I/O operation does not cause the requesting process to be blocked;\n\n两者的区别就在于同步IO做”IO operation”的时候会将process阻塞。按照这个定义，之前所述的**_阻塞IO,非阻塞IO ，IO复用都属于同步IO。_**\n有人可能会说，非阻塞IO 并没有被block啊。这里有个非常“狡猾”的地方，定义中所指的”IO operation”是指真实的IO操作，就是例子中的recvfrom这个system call。非阻塞IO在执行recvfrom这个system call的时候，如果kernel的数据没有准备好，这时候不会block进程。但是，当kernel中数据准备好的时候，recvfrom会将数据从kernel拷贝到用户内存中，这个时候进程是被block了，在这段时间内，进程是被block的。\n\n而异步IO则不一样，当进程发起IO 操作之后，就直接返回再也不理睬了，直到kernel发送一个信号，告诉进程说IO完成。在这整个过程中，进程完全没有被block。\n\n### IO模型的形象举例\n\n最后，再举几个不是很恰当的例子来说明这四个IO Model:\n有A，B，C，D四个人在钓鱼：\nA用的是最老式的鱼竿，所以呢，得一直守着，等到鱼上钩了再拉杆；\nB的鱼竿有个功能，能够显示是否有鱼上钩，所以呢，B就和旁边的MM聊天，隔会再看看有没有鱼上钩，有的话就迅速拉杆；\nC用的鱼竿和B差不多，但他想了一个好办法，就是同时放好几根鱼竿，然后守在旁边，一旦有显示说鱼上钩了，它就将对应的鱼竿拉起来；\nD是个有钱人，干脆雇了一个人帮他钓鱼，一旦那个人把鱼钓上来了，就给D发个短信。\n\n### Select/Poll/Epoll 轮询机制\n\n**_select，poll，epoll本质上都是同步I/O，因为他们都需要在读写事件就绪后自己负责进行读写，也就是说这个读写过程是阻塞的_**\nSelect/Poll/Epoll 都是IO复用的实现方式， 上面说了使用IO复用，会把socket设置成non-blocking，然后放进Select/Poll/Epoll 各自的监视列表里面，那么，他们的对socket是否有数据到达的监视机制分别是怎样的？效率又如何？我们应该使用哪种方式实现IO复用比较好？下面列出他们各自的实现方式，效率，优缺点：\n\n（1）select，poll实现需要自己不断轮询所有fd集合，直到设备就绪，期间可能要睡眠和唤醒多次交替。而epoll其实也需要调用epoll_wait不断轮询就绪链表，期间也可能多次睡眠和唤醒交替，但是它是设备就绪时，调用回调函数，把就绪fd放入就绪链表中，并唤醒在epoll_wait中进入睡眠的进程。虽然都要睡眠和交替，但是select和poll在“醒着”的时候要遍历整个fd集合，而epoll在“醒着”的时候只要判断一下就绪链表是否为空就行了，这节省了大量的CPU时间。这就是回调机制带来的性能提升。\n\n（2）select，poll每次调用都要把fd集合从用户态往内核态拷贝一次，并且要把current往设备等待队列中挂一次，而epoll只要一次拷贝，而且把current往等待队列上挂也只挂一次（在epoll_wait的开始，注意这里的等待队列并不是设备等待队列，只是一个epoll内部定义的等待队列）。这也能节省不少的开销。\n\n## Java网络编程模型\n\n上文讲述了UNIX环境的五种IO模型。基于这五种模型，在Java中，随着NIO和NIO2.0(AIO)的引入，一般具有以下几种网络编程模型：\n\n*   BIO\n*   NIO\n*   AIO\n\n### BIO\n\nBIO是一个典型的网络编程模型，是通常我们实现一个服务端程序的过程，步骤如下：\n\n*   主线程accept请求阻塞\n*   请求到达，创建新的线程来处理这个套接字，完成对客户端的响应。\n*   主线程继续accept下一个请求\n\n这种模型有一个很大的问题是：当客户端连接增多时，服务端创建的线程也会暴涨，系统性能会急剧下降。因此，在此模型的基础上，类似于 tomcat的bio connector，采用的是线程池来避免对于每一个客户端都创建一个线程。有些地方把这种方式叫做伪异步IO(把请求抛到线程池中异步等待处理)。\n\n### NIO\n\nJDK1.4开始引入了NIO类库，这里的NIO指的是New IO，主要是使用Selector多路复用器来实现。Selector在Linux等主流操作系统上是通过epoll实现的。\n\nNIO的实现流程，类似于select：\n\n*   创建ServerSocketChannel监听客户端连接并绑定监听端口，设置为非阻塞模式。\n*   创建Reactor线程，创建多路复用器(Selector)并启动线程。\n*   将ServerSocketChannel注册到Reactor线程的Selector上。监听accept事件。\n*   Selector在线程run方法中无线循环轮询准备就绪的Key。\n*   Selector监听到新的客户端接入，处理新的请求，完成tcp三次握手，建立物理连接。\n*   将新的客户端连接注册到Selector上，监听读操作。读取客户端发送的网络消息。\n*   客户端发送的数据就绪则读取客户端请求，进行处理。\n\n相比BIO，NIO的编程非常复杂。\n\n### AIO\n\nJDK1.7引入NIO2.0，提供了异步文件通道和异步套接字通道的实现。其底层在windows上是通过IOCP，在Linux上是通过epoll来实现的(LinuxAsynchronousChannelProvider.java,UnixAsynchronousServerSocketChannelImpl.java)。\n\n*   创建AsynchronousServerSocketChannel，绑定监听端口\n*   调用AsynchronousServerSocketChannel的accpet方法，传入自己实现的CompletionHandler。包括上一步，都是非阻塞的\n*   连接传入，回调CompletionHandler的completed方法，在里面，调用AsynchronousSocketChannel的read方法，传入负责处理数据的CompletionHandler。\n*   数据就绪，触发负责处理数据的CompletionHandler的completed方法。继续做下一步处理即可。\n*   写入操作类似，也需要传入CompletionHandler。\n\n其编程模型相比NIO有了不少的简化。\n\n### 对比\n\n| . | 同步阻塞IO | 伪异步IO | NIO | AIO |\n| --- | --- | --- | --- | --- |\n| 客户端数目 ：IO线程 | 1 : 1 | m : n | m : 1 | m : 0 |\n| IO模型 | 同步阻塞IO | 同步阻塞IO | 同步非阻塞IO | 异步非阻塞IO |\n| 吞吐量 | 低 | 中 | 高 | 高 |\n| 编程复杂度 | 简单 | 简单 | 非常复杂 | 复杂 |\n\n"
  },
  {
    "path": "docs/Java/network/Java网络编程与NIO详解：JAVA中原生的socket通信机制.md",
    "content": "# 目录\n\n  * [当前环境](#当前环境)\n    * [处理 socket 输入输出流](#处理-socket-输入输出流)\n    * [结果展示](#结果展示)\n  * [请求模型优化](#请求模型优化)\n  * [补充1：TCP客户端与服务端](#补充1：tcp客户端与服务端)\n  * [补充2：UDP客户端和服务端](#补充2：udp客户端和服务端)\n\n\n本文转自：https://github.com/jasonGeng88/blog\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章将同步到我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《不可轻视的Java网络编程》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从计算机网络的基础知识入手，一步步地学习Java网络基础，从socket到nio、bio、aio和netty等网络编程知识，并且进行实战，网络编程是每一个Java后端工程师必须要学习和理解的知识点，进一步来说，你还需要掌握Linux中的网络编程原理，包括IO模型、网络编程框架netty的进阶原理，才能更完整地了解整个Java网络编程的知识体系，形成自己的知识框架。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供部分知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n## 当前环境\n\n1.  jdk == 1.8\n\n*   socket 的连接处理\n*   IO 输入、输出流的处理\n*   请求数据格式处理\n*   请求模型优化\n\n今天，和大家聊一下 JAVA 中的 socket 通信问题。这里采用最简单的一请求一响应模型为例，假设我们现在需要向 baidu 站点进行通信。我们用 JAVA 原生的 socket 该如何实现。\n\n首先，我们需要建立 socket 连接（核心代码）\n\n\n````\nimport java.net.InetSocketAddress;\nimport java.net.Socket;\nimport java.net.SocketAddress;\n\n// 初始化 socket\nSocket socket = new Socket();\n// 初始化远程连接地址\nSocketAddress remote = new InetSocketAddress(host, port);\n// 建立连接\nsocket.connect(remote);\n````\n\n\n\n### 处理 socket 输入输出流\n\n成功建立 socket 连接后，我们就能获得它的输入输出流，通信的本质是对输入输出流的处理。通过输入流，读取网络连接上传来的数据，通过输出流，将本地的数据传出给远端。\n\n_socket 连接实际与处理文件流有点类似，都是在进行 IO 操作。_\n\n获取输入、输出流代码如下：\n\n\n````\n// 输入流\nInputStream in = socket.getInputStream();\n// 输出流\nOutputStream out = socket.getOutputStream();\n````\n\n\n关于 IO 流的处理，我们一般会用相应的包装类来处理 IO 流，如果直接处理的话，我们需要对`byte[]`进行操作，而这是相对比较繁琐的。如果采用包装类，我们可以直接以`string`、`int`等类型进行处理，简化了 IO 字节操作。\n\n下面以`BufferedReader`与`PrintWriter`作为输入输出的包装类进行处理。\n\n\n\n// 获取 socket 输入流\nprivate BufferedReader getReader(Socket socket) throws IOException {\n    InputStream in = socket.getInputStream();\n    return new BufferedReader(new InputStreamReader(in));\n}\n\n// 获取 socket 输出流\nprivate PrintWriter getWriter(Socket socket) throws IOException {\n    OutputStream out = socket.getOutputStream();\n    return new PrintWriter(new OutputStreamWriter(out));\n}\n\n有了 socket 连接、IO 输入输出流，下面就该向发送请求数据，以及获取请求的响应结果。\n\n因为有了 IO 包装类的支持，我们可以直接以字符串的格式进行传输，由包装类帮我们将数据装换成相应的字节流。\n\n因为我们与 baidu 站点进行的是 HTTP 访问，所有我们不需要额外定义输出格式。采用标准的 HTTP 传输格式，就能进行请求响应了（_某些特定的 RPC 框架，可能会有自定义的通信格式_）。\n\n请求的数据内容处理如下：\n\npublic class HttpUtil {\n\n    public static String compositeRequest(String host){\n\n        return \"GET / HTTP/1.1\\r\\n\" +\n                \"Host: \" + host + \"\\r\\n\" +\n                \"User-Agent: curl/7.43.0\\r\\n\" +\n                \"Accept: */*\\r\\n\\r\\n\";\n    }\n\n}\n\n发送请求数据代码如下：\n\n// 发起请求\nPrintWriter writer = getWriter(socket);\nwriter.write(HttpUtil.compositeRequest(host));\nwriter.flush();\n\n接收响应数据代码如下：\n\n// 读取响应\nString msg;\nBufferedReader reader = getReader(socket);\nwhile ((msg = reader.readLine()) != null){\n    System.out.println(msg);\n}\n\n### 结果展示\n\n至此，讲完了原生 socket 下的创建连接、发送请求与接收响应的所有核心代码。\n\n完整代码如下：\n\n```\nimport java.io.*;import java.net.InetSocketAddress;import java.net.Socket;import java.net.SocketAddress;import com.test.network.util.HttpUtil; public class SocketHttpClient {     public void start(String host, int port) {         // 初始化 socket        Socket socket = new Socket();         try {            // 设置 socket 连接            SocketAddress remote = new InetSocketAddress(host, port);            socket.setSoTimeout(5000);            socket.connect(remote);             // 发起请求            PrintWriter writer = getWriter(socket);            System.out.println(HttpUtil.compositeRequest(host));            writer.write(HttpUtil.compositeRequest(host));            writer.flush();             // 读取响应            String msg;            BufferedReader reader = getReader(socket);            while ((msg = reader.readLine()) != null){                System.out.println(msg);            }         } catch (IOException e) {            e.printStackTrace();        } finally {            try {                socket.close();            } catch (IOException e) {                e.printStackTrace();            }        }     } \tprivate BufferedReader getReader(Socket socket) throws IOException {        InputStream in = socket.getInputStream();        return new BufferedReader(new InputStreamReader(in));    }     private PrintWriter getWriter(Socket socket) throws IOException {        OutputStream out = socket.getOutputStream();        return new PrintWriter(new OutputStreamWriter(out));    } }\n```\n\n下面，我们通过实例化一个客户端，来展示 socket 通信的结果。\n\n\n````\npublic class Application {\n\n    public static void main(String[] args) {\n\n        new SocketHttpClient().start(\"www.baidu.com\", 80);\n\n    }\n}\n````\n\n## 请求模型优化\n\n这种方式，虽然实现功能没什么问题。但是我们细看，发现在 IO 写入与读取过程，是发生了 IO 阻塞的情况。即：\n\n```\n// 会发生 IO 阻塞writer.write(HttpUtil.compositeRequest(host));reader.readLine();\n```\n\n所以如果要同时请求10个不同的站点，如下：\n\n\n````\npublic class SingleThreadApplication {\n\n    public static void main(String[] args) {\n\n\t\t// HttpConstant.HOSTS 为 站点集合\n        for (String host: HttpConstant.HOSTS) {\n\n            new SocketHttpClient().start(host, HttpConstant.PORT);\n\n        }\n\n    }\n}\n````\n\n\n它一定是第一个请求响应结束后，才会发起下一个站点处理。\n\n_这在服务端更明显，虽然这里的代码是客户端连接，但是具体的操作和服务端是差不多的。请求只能一个个串行处理，这在响应时间上肯定不能达标。_\n\n*   多线程处理\n\n有人觉得这根本不是问题，JAVA 是多线程的编程语言。对于这种情况，采用多线程的模型再合适不过。\n\n```\npublic class MultiThreadApplication {     public static void main(String[] args) {         for (final String host: HttpConstant.HOSTS) {             Thread t = new Thread(new Runnable() {                public void run() {                    new SocketHttpClient().start(host, HttpConstant.PORT);                }            });             t.start();         }    }}\n```\n\n这种方式起初看起来挺有用的，但并发量一大，应用会起很多的线程。都知道，在服务器上，每一个线程实际都会占据一个文件句柄。而服务器上的句柄数是有限的，而且大量的线程，造成的线程间切换的消耗也会相当的大。所以这种方式在并发量大的场景下，一定是承载不住的。\n\n*   多线程 + 线程池 处理\n\n既然线程太多不行，那我们控制一下线程创建的数目不就行了。只启动固定的线程数来进行 socket 处理，既利用了多线程的处理，又控制了系统的资源消耗。\n\n\n````\npublic class ThreadPoolApplication {\n\n    public static void main(String[] args) {\n\n        ExecutorService executorService = Executors.newFixedThreadPool(8);\n\n        for (final String host: HttpConstant.HOSTS) {\n\n            Thread t = new Thread(new Runnable() {\n                public void run() {\n                    new SocketHttpClient().start(host, HttpConstant.PORT);\n                }\n            });\n\n            executorService.submit(t);\n            new SocketHttpClient().start(host, HttpConstant.PORT);\n\n        }\n\n    }\n}\n````\n\n\n_关于启动的线程数，一般 CPU 密集型会设置在 N+1（N为CPU核数），IO 密集型设置在 2N + 1。_\n\n这种方式，看起来是最优的了。那有没有更好的呢，如果一个线程能同时处理多个 socket 连接，并且在每个 socket 输入输出数据没有准备好的情况下，不进行阻塞，那是不是更优呢。这种技术叫做“IO多路复用”。在 JAVA 的 nio 包中，提供了相应的实现。\n\n## 补充1：TCP客户端与服务端\n\n\n````\npublic class TCP客户端 {\n    public static void main(String[] args) {\n        new Thread(new Runnable() {\n            @Override\n  public void run() {\n                try {\n                    Socket s = new Socket(\"127.0.0.1\",1234);    //构建IO\n  InputStream is = s.getInputStream();\n  OutputStream os = s.getOutputStream();    BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));\n  //向服务器端发送一条消息\n  bw.write(\"测试客户端和服务器通信，服务器接收到消息返回到客户端\\n\");\n  bw.flush();    //读取服务器返回的消息\n  BufferedReader br = new BufferedReader(new InputStreamReader(is));\n  String mess = br.readLine();\n  System._out_.println(\"服务器：\"+mess);\n  } catch (UnknownHostException e) {\n                    e.printStackTrace();\n  } catch (IOException e) {\n                    e.printStackTrace();\n  }\n            }\n        }).start();\n  }\n}\n````\n\n\n\n````\npublic class TCP服务端 {\n    public static void main(String[] args) {\n        new Thread(new Runnable() {\n            @Override\n  public void run() {\n                try {\n                    ServerSocket ss = new ServerSocket(1234);\n while (true) {\n                        System._out_.println(\"启动服务器....\");\n  Socket s = ss.accept();\n  System._out_.println(\"客户端:\" + s.getInetAddress().getLocalHost() + \"已连接到服务器\");\n  BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));\n  //读取客户端发送来的消息\n  String mess = br.readLine();\n  System._out_.println(\"客户端：\" + mess);\n  BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));\n  bw.write(mess + \"\\n\");\n  bw.flush();\n  }\n                } catch (IOException e) {\n                    e.printStackTrace();\n  }\n            }\n        }).start();\n  }\n}\n````\n## 补充2：UDP客户端和服务端\n````\npublic class UDP客户端 {\n    public static void main(String[] args) {\n        new Thread(new Runnable() {\n            @Override\n  public void run() {\n                byte []arr = \"Hello Server\".getBytes();\n try {\n                    InetAddress inetAddress = InetAddress.getLocalHost();\n  DatagramSocket datagramSocket = new DatagramSocket();\n  DatagramPacket datagramPacket = new DatagramPacket(arr, arr.length, inetAddress, 1234);\n  datagramSocket.send(datagramPacket);\n  System._out_.println(\"send end\");\n  } catch (UnknownHostException e) {\n                    e.printStackTrace();\n  } catch (SocketException e) {\n                    e.printStackTrace();\n  } catch (IOException e) {\n                    e.printStackTrace();\n  }\n            }\n        }).start();\n  }\n}\n````\n````\npublic class UDP服务端 {\n    public static void main(String[] args) {\n        new Thread(new Runnable() {\n            @Override\n  public void run() {\n                try {\n                    DatagramSocket datagramSocket = new DatagramSocket(1234);\n byte[] buffer = new byte[1024];\n  DatagramPacket packet = new DatagramPacket(buffer, buffer.length);\n  datagramSocket.receive(packet);\n  System._out_.println(\"server recv\");\n  String msg = new String(packet.getData(), \"utf-8\");\n  System._out_.println(msg);\n  } catch (SocketException e) {\n                    e.printStackTrace();\n  } catch (IOException e) {\n                    e.printStackTrace();\n  }\n            }\n        }).start();\n  }\n}\n````\n\n"
  },
  {
    "path": "docs/Java/network/Java网络编程与NIO详解：JavaNIO一步步构建IO多路复用的请求模型.md",
    "content": "# 目录\n\n  * [当前环境](#当前环境)\n  * [代码地址](#代码地址)\n  * [知识点](#知识点)\n    * [获取 socket 连接](#获取-socket-连接)\n    * [完整示例](#完整示例)\n    * [处理连接就绪事件](#处理连接就绪事件)\n\n\n本文转载自：[https://github.com/jasonGeng88/blog](https://github.com/jasonGeng88/blog)\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章将同步到我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《不可轻视的Java网络编程》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从计算机网络的基础知识入手，一步步地学习Java网络基础，从socket到nio、bio、aio和netty等网络编程知识，并且进行实战，网络编程是每一个Java后端工程师必须要学习和理解的知识点，进一步来说，你还需要掌握Linux中的网络编程原理，包括IO模型、网络编程框架netty的进阶原理，才能更完整地了解整个Java网络编程的知识体系，形成自己的知识框架。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供部分知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n*   文章一：[JAVA 中原生的 socket 通信机制](https://github.com/jasonGeng88/blog/blob/master/201708/java-socket.md)\n\n## 当前环境\n\n1.  jdk == 1.8\n\n## 代码地址\n\ngit 地址：[https://github.com/jasonGeng88/java-network-programming](https://github.com/jasonGeng88/java-network-programming)\n\n## 知识点\n\n*   nio 下 I/O 阻塞与非阻塞实现\n*   SocketChannel 介绍\n*   I/O 多路复用的原理\n*   事件选择器与 SocketChannel 的关系\n*   事件监听类型\n*   字节缓冲 ByteBuffer 数据结构\n\n接着上一篇中的站点访问问题，如果我们需要并发访问10个不同的网站，我们该如何处理？\n\n在上一篇中，我们使用了`java.net.socket`类来实现了这样的需求，以一线程处理一连接的方式，并配以线程池的控制，貌似得到了当前的最优解。可是这里也存在一个问题，连接处理是同步的，也就是并发数量增大后，大量请求会在队列中等待，或直接异常抛出。\n\n为解决这问题，我们发现元凶处在“一线程一请求”上，如果一个线程能同时处理多个请求，那么在高并发下性能上会大大改善。这里就借住 JAVA 中的 nio 技术来实现这一模型。\n\n关于什么是 nio，从字面上理解为 New IO，就是为了弥补原本 I/O 上的不足，而在 JDK 1.4 中引入的一种新的 I/O 实现方式。简单理解，就是它提供了 I/O 的阻塞与非阻塞的两种实现方式（_当然，默认实现方式是阻塞的。_）。\n\n下面，我们先来看下 nio 以阻塞方式是如何处理的。\n\n有了上一篇 socket 的经验，我们的第一步一定也是建立 socket 连接。只不过，这里不是采用`new socket()`的方式，而是引入了一个新的概念`SocketChannel`。它可以看作是 socket 的一个完善类，除了提供 Socket 的相关功能外，还提供了许多其他特性，如后面要讲到的向选择器注册的功能。\n\n类图如下：\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405094035.png)\n\n建立连接代码实现：\n````\n// 初始化 socket，建立 socket 与 channel 的绑定关系\nSocketChannel socketChannel = SocketChannel.open();\n// 初始化远程连接地址\nSocketAddress remote = new InetSocketAddress(this.host, port);\n// I/O 处理设置阻塞，这也是默认的方式，可不设置\nsocketChannel.configureBlocking(true);\n// 建立连接\nsocketChannel.connect(remote);\n````\n### 获取 socket 连接\n\n因为是同样是 I/O 阻塞的实现，所以后面的关于 socket 输入输出流的处理，和上一篇的基本相同。唯一差别是，这里需要通过 channel 来获取 socket 连接。\n\n*   获取 socket 连接\n````\nSocket socket = socketChannel.socket();\n````\n*   处理输入输出流\n\n\n````\nPrintWriter pw = getWriter(socketChannel.socket());\nBufferedReader br = getReader(socketChannel.socket());\n````\n\n\n### 完整示例\n\n\n````\npackage com.jason.network.mode.nio;\n\nimport com.jason.network.constant.HttpConstant;\nimport com.jason.network.util.HttpUtil;\n\nimport java.io.*;\nimport java.net.InetSocketAddress;\nimport java.net.Socket;\nimport java.net.SocketAddress;\nimport java.nio.channels.SocketChannel;\n\npublic class NioBlockingHttpClient {\n\n    private SocketChannel socketChannel;\n    private String host;\n\n    public static void main(String[] args) throws IOException {\n\n        for (String host: HttpConstant.HOSTS) {\n\n            NioBlockingHttpClient client = new NioBlockingHttpClient(host, HttpConstant.PORT);\n            client.request();\n\n        }\n\n    }\n\n    public NioBlockingHttpClient(String host, int port) throws IOException {\n        this.host = host;\n        socketChannel = SocketChannel.open();\n        socketChannel.socket().setSoTimeout(5000);\n        SocketAddress remote = new InetSocketAddress(this.host, port);\n        this.socketChannel.connect(remote);\n    }\n\n    public void request() throws IOException {\n        PrintWriter pw = getWriter(socketChannel.socket());\n        BufferedReader br = getReader(socketChannel.socket());\n\n        pw.write(HttpUtil.compositeRequest(host));\n        pw.flush();\n        String msg;\n        while ((msg = br.readLine()) != null){\n            System.out.println(msg);\n        }\n    }\n\n    private PrintWriter getWriter(Socket socket) throws IOException {\n        OutputStream out = socket.getOutputStream();\n        return new PrintWriter(out);\n    }\n\n    private BufferedReader getReader(Socket socket) throws IOException {\n        InputStream in = socket.getInputStream();\n        return new BufferedReader(new InputStreamReader(in));\n    }\n}\n\n````\n\nnio 的阻塞实现，基本与使用原生的 socket 类似，没有什么特别大的差别。\n\n下面我们来看看它真正强大的地方。到目前为止，我们将的都是阻塞 I/O。何为阻塞 I/O，看下图：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405094141.png)\n\n_我们主要观察图中的前三种 I/O 模型，关于异步 I/O，一般需要依靠操作系统的支持，这里不讨论。_\n\n从图中可以发现，阻塞过程主要发生在两个阶段上：\n\n*   第一阶段：等待数据就绪；\n*   第二阶段：将已就绪的数据从内核缓冲区拷贝到用户空间；\n\n这里产生了一个从内核到用户空间的拷贝，主要是为了系统的性能优化考虑。假设，从网卡读到的数据直接返回给用户空间，那势必会造成频繁的系统中断，因为从网卡读到的数据不一定是完整的，可能断断续续的过来。通过内核缓冲区作为缓冲，等待缓冲区有足够的数据，或者读取完结后，进行一次的系统中断，将数据返回给用户，这样就能避免频繁的中断产生。\n\n了解了 I/O 阻塞的两个阶段，下面我们进入正题。看看一个线程是如何实现同时处理多个 I/O 调用的。从上图中的非阻塞 I/O 可以看出，仅仅只有第二阶段需要阻塞，第一阶段的数据等待过程，我们是不需要关心的。不过该模型是频繁地去检查是否就绪，造成了 CPU 无效的处理，反而效果不好。如果有一种类似的好莱坞原则— “不要给我们打电话，我们会打给你” 。这样一个线程可以同时发起多个 I/O 调用，并且不需要同步等待数据就绪。在数据就绪完成的时候，会以事件的机制，来通知我们。这样不就实现了单线程同时处理多个 IO 调用的问题了吗？即所说的“I/O 多路复用模型”。\n\n* * *\n\n废话讲了一大堆，下面就来实际操刀一下。\n\n由上面分析可以，我们得有一个选择器，它能监听所有的 I/O 操作，并且以事件的方式通知我们哪些 I/O 已经就绪了。\n\n代码如下：\n\n````\nimport java.nio.channels.Selector;\n\n...\n\nprivate static Selector selector;\nstatic {\n    try {\n        selector = Selector.open();\n    } catch (IOException e) {\n        e.printStackTrace();\n    }\n}\n````\n\n\n下面，我们来创建一个非阻塞的`SocketChannel`，代码与阻塞实现类型，唯一不同是`socketChannel.configureBlocking(false)`。\n\n_**注意：只有在`socketChannel.configureBlocking(false)`之后的代码，才是非阻塞的，如果`socketChannel.connect()`在设置非阻塞模式之前，那么连接操作依旧是阻塞调用的。**_\n\n````\nSocketChannel socketChannel = SocketChannel.open();\nSocketAddress remote = new InetSocketAddress(host, port);\n// 设置非阻塞模式\nsocketChannel.configureBlocking(false);\nsocketChannel.connect(remote);\n````\n\n选择器与 socket 都创建好了，下一步就是将两者进行关联，好让选择器和监听到 Socket 的变化。这里采用了以`SocketChannel`主动注册到选择器的方式进行关联绑定，这也就解释了，为什么不直接`new Socket()`，而是以`SocketChannel`的方式来创建 socket。\n\n代码如下：\n\n\n````\nsocketChannel.register(selector,\n                        SelectionKey.OP_CONNECT\n                        | SelectionKey.OP_READ\n                        | SelectionKey.OP_WRITE);\n````\n\n\n上面代码，我们将 socketChannel 注册到了选择器中，并且对它的连接、可读、可写事件进行了监听。\n\n具体的事件监听类型如下：\n\n| 操作类型 | 值 | 描述 | 所属对象 |\n| --- | --- | --- | --- |\n| OP_READ | 1 << 0 | 读操作 | SocketChannel |\n| OP_WRITE | 1 << 2 | 写操作 | SocketChannel |\n| OP_CONNECT | 1 << 3 | 连接socket操作 | SocketChannel |\n| OP_ACCEPT | 1 << 4 | 接受socket操作 | ServerSocketChannel |\n\n现在，选择器已经与我们关心的 socket 进行了关联。下面就是感知事件的变化，然后调用相应的处理机制。\n\n这里与 Linux 下的 selector 有点不同，nio 下的 selecotr 不会去遍历所有关联的 socket。我们在注册时设置了我们关心的事件类型，每次从选择器中获取的，只会是那些符合事件类型，并且完成就绪操作的 socket，减少了大量无效的遍历操作。\n\n```\npublic void select() throws IOException {\n\t// 获取就绪的 socket 个数\n    while (selector.select() > 0){\n\n    \t// 获取符合的 socket 在选择器中对应的事件句柄 key\n        Set keys = selector.selectedKeys();\n\n\t\t// 遍历所有的key\n        Iterator it = keys.iterator();\n        while (it.hasNext()){\n\n\t\t\t// 获取对应的 key，并从已选择的集合中移除\n            SelectionKey key = (SelectionKey)it.next();\n            it.remove();\n\n            if (key.isConnectable()){\n            \t// 进行连接操作\n                connect(key);\n            }\n            else if (key.isWritable()){\n            \t// 进行写操作\n                write(key);\n            }\n            else if (key.isReadable()){\n            \t// 进行读操作\n                receive(key);\n            }\n        }\n    }\n}\n\n```\n\n_**注意：这里的`selector.select()`是同步阻塞的，等待有事件发生后，才会被唤醒。这就防止了 CPU 空转的产生。当然，我们也可以给它设置超时时间，`selector.select(long timeout)`来结束阻塞过程。**_\n\n### 处理连接就绪事件\n\n下面，我们分别来看下，一个 socket 是如何来处理连接、写入数据和读取数据的（_这些操作都是阻塞的过程，只是我们将等待就绪的过程变成了非阻塞的了_）。\n\n处理连接代码：\n\n\n````\n// SelectionKey 代表 SocketChannel 在选择器中注册的事件句柄\nprivate void connect(SelectionKey key) throws IOException {\n\t// 获取事件句柄对应的 SocketChannel\n    SocketChannel channel = (SocketChannel) key.channel();\n\n   // 真正的完成 socket 连接\n    channel.finishConnect();\n\n   // 打印连接信息\n    InetSocketAddress remote = (InetSocketAddress) channel.socket().getRemoteSocketAddress();\n    String host = remote.getHostName();\n    int port = remote.getPort();\n    System.out.println(String.format(\"访问地址: %s:%s 连接成功!\", host, port));\n}\n````\n\n\n````\n// 字符集处理类\nprivate Charset charset = Charset.forName(\"utf8\");\n\nprivate void write(SelectionKey key) throws IOException {\n    SocketChannel channel = (SocketChannel) key.channel();\n    InetSocketAddress remote = (InetSocketAddress) channel.socket().getRemoteSocketAddress();\n    String host = remote.getHostName();\n\n\t// 获取 HTTP 请求，同上一篇\n    String request = HttpUtil.compositeRequest(host);\n\n\t// 向 SocketChannel 写入事件 \n    channel.write(charset.encode(request));\n\n    // 修改 SocketChannel 所关心的事件\n    key.interestOps(SelectionKey.OP_READ);\n}\n````\n\n\n这里有两个地方需要注意：\n\n*   第一个是使用`channel.write(charset.encode(request));`进行数据写入。有人会说，为什么不能像上面同步阻塞那样，通过`PrintWriter`包装类进行操作。因为`PrintWriter`的`write()`方法是阻塞的，也就是说要等数据真正从 socket 发送出去后才返回。\n\n这与我们这里所讲的阻塞是不一致的，这里的操作虽然也是阻塞的，但它发生的过程是在数据从用户空间到内核缓冲区拷贝过程。至于系统将缓冲区的数据通过 socket 发送出去，这不在阻塞范围内。也解释了为什么要用`Charset`对写入内容进行编码了，因为缓冲区接收的格式是`ByteBuffer`。\n\n*   第二，选择器用来监听事件变化的两个参数是`interestOps`与`readyOps`。\n\n    *   interestOps：表示`SocketChannel`所关心的事件类型，也就是告诉选择器，当有这几种事件发生时，才来通知我。这里通过`key.interestOps(SelectionKey.OP_READ);`告诉选择器，之后我只关心“读就绪”事件，其他的不用通知我了。\n\n    *   readyOps：表示`SocketChannel`当前就绪的事件类型。以`key.isReadable()`为例，判断依据就是：`return (readyOps() & OP_READ) != 0;`\n\n\n````\nprivate void receive(SelectionKey key) throws IOException {\n    SocketChannel channel = (SocketChannel) key.channel();\n    ByteBuffer buffer = ByteBuffer.allocate(1024);\n    channel.read(buffer);\n    buffer.flip();\n    String receiveData = charset.decode(buffer).toString();\n\n\t// 当再没有数据可读时，取消在选择器中的关联，并关闭 socket 连接\n    if (\"\".equals(receiveData)) {\n        key.cancel();\n        channel.close();\n        return;\n    }\n\n    System.out.println(receiveData);\n}\n````\n\n\n这里的处理基本与写入一致，唯一要注意的是，这里我们需要自行处理去缓冲区读取数据的操作。首先会分配一个固定大小的缓冲区，然后从内核缓冲区中，拷贝数据至我们刚分配固定缓冲区上。这里存在两种情况：\n\n*   我们分配的缓冲区过大，那多余的部分以0补充（_初始化时，其实会自动补0_）。\n*   我们分配的缓冲去过小，因为选择器会不停的遍历。只要`SocketChannel`处理读就绪状态，那下一次会继续读取。当然，分配过小，会增加遍历次数。\n\n最后，将一下`ByteBuffer`的结构，它主要有 position, limit,capacity 以及 mark 属性。以`buffer.flip();`为例，讲下各属性的作用（_mark 主要是用来标记之前 position 的位置，是在当前 postion 无法满足的情况下使用的，这里不作讨论_）。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405094314.png)\n从图中看出，\n\n*   容量（capacity）：表示缓冲区可以保存的数据容量；\n*   极限（limit）：表示缓冲区的当前终点，即写入、读取都不可超过该重点；\n*   位置（position）：表示缓冲区下一个读写单元的位置；\n\n````\npackage com.jason.network.mode.nio;\n\nimport com.jason.network.constant.HttpConstant;\nimport com.jason.network.util.HttpUtil;\n\nimport java.io.IOException;\nimport java.net.InetSocketAddress;\nimport java.net.SocketAddress;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.SelectionKey;\nimport java.nio.channels.Selector;\nimport java.nio.channels.SocketChannel;\nimport java.nio.charset.Charset;\nimport java.util.Iterator;\nimport java.util.Set;\n\npublic class NioNonBlockingHttpClient {\n\n    private static Selector selector;\n    private Charset charset = Charset.forName(\"utf8\");\n\n    static {\n        try {\n            selector = Selector.open();\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n    }\n\n    public static void main(String[] args) throws IOException {\n\n        NioNonBlockingHttpClient client = new NioNonBlockingHttpClient();\n\n        for (String host: HttpConstant.HOSTS) {\n\n            client.request(host, HttpConstant.PORT);\n\n        }\n\n        client.select();\n\n    }\n\n    public void request(String host, int port) throws IOException {\n        SocketChannel socketChannel = SocketChannel.open();\n        socketChannel.socket().setSoTimeout(5000);\n        SocketAddress remote = new InetSocketAddress(host, port);\n        socketChannel.configureBlocking(false);\n        socketChannel.connect(remote);\n        socketChannel.register(selector,\n                        SelectionKey.OP_CONNECT\n                        | SelectionKey.OP_READ\n                        | SelectionKey.OP_WRITE);\n    }\n\n    public void select() throws IOException {\n        while (selector.select(500) > 0){\n            Set keys = selector.selectedKeys();\n\n            Iterator it = keys.iterator();\n\n            while (it.hasNext()){\n\n                SelectionKey key = (SelectionKey)it.next();\n                it.remove();\n\n                if (key.isConnectable()){\n                    connect(key);\n                }\n                else if (key.isWritable()){\n                    write(key);\n                }\n                else if (key.isReadable()){\n                    receive(key);\n                }\n            }\n        }\n    }\n\n    private void connect(SelectionKey key) throws IOException {\n        SocketChannel channel = (SocketChannel) key.channel();\n        channel.finishConnect();\n        InetSocketAddress remote = (InetSocketAddress) channel.socket().getRemoteSocketAddress();\n        String host = remote.getHostName();\n        int port = remote.getPort();\n        System.out.println(String.format(\"访问地址: %s:%s 连接成功!\", host, port));\n    }\n\n    private void write(SelectionKey key) throws IOException {\n        SocketChannel channel = (SocketChannel) key.channel();\n        InetSocketAddress remote = (InetSocketAddress) channel.socket().getRemoteSocketAddress();\n        String host = remote.getHostName();\n\n        String request = HttpUtil.compositeRequest(host);\n        System.out.println(request);\n\n        channel.write(charset.encode(request));\n        key.interestOps(SelectionKey.OP_READ);\n    }\n\n    private void receive(SelectionKey key) throws IOException {\n        SocketChannel channel = (SocketChannel) key.channel();\n        ByteBuffer buffer = ByteBuffer.allocate(1024);\n        channel.read(buffer);\n        buffer.flip();\n        String receiveData = charset.decode(buffer).toString();\n\n        if (\"\".equals(receiveData)) {\n            key.cancel();\n            channel.close();\n            return;\n        }\n\n        System.out.println(receiveData);\n    }\n}\n````\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405094343.png)\n\n本文从 nio 的阻塞方式讲起，介绍了阻塞 I/O 与非阻塞 I/O 的区别，以及在 nio 下是如何一步步构建一个 IO 多路复用的模型的客户端。文中需要理解的内容比较多，如果有理解错误的地方，欢迎指正~\n"
  },
  {
    "path": "docs/Java/network/Java网络编程与NIO详解：Java非阻塞IO和异步IO.md",
    "content": "# 目录\n\n* [目录](#目录)\n  * [阻塞模式 IO](#阻塞模式-io)\n  * [非阻塞 IO](#非阻塞-io)\n  * [NIO.2 异步 IO](#nio2-异步-io)\n    * [1、返回 Future 实例](#1、返回-future-实例)\n    * [2、提供 CompletionHandler 回调函数](#2、提供-completionhandler-回调函数)\n    * [AsynchronousFileChannel](#asynchronousfilechannel)\n    * [AsynchronousServerSocketChannel](#asynchronousserversocketchannel)\n    * [AsynchronousSocketChannel](#asynchronoussocketchannel)\n    * [Asynchronous Channel Groups](#asynchronous-channel-groups)\n  * [小结](#小结)\n\n\n# 目录\n  * [阻塞模式 IO](#阻塞模式-io)\n  * [非阻塞 IO](#非阻塞-io)\n  * [NIO.2 异步 IO](#nio2-异步-io)\n    * [1、返回 Future 实例](#1、返回-future-实例)\n    * [2、提供 CompletionHandler 回调函数](#2、提供-completionhandler-回调函数)\n    * [AsynchronousFileChannel](#asynchronousfilechannel)\n    * [AsynchronousServerSocketChannel](#asynchronousserversocketchannel)\n    * [AsynchronousSocketChannel](#asynchronoussocketchannel)\n    * [Asynchronous Channel Groups](#asynchronous-channel-groups)\n  * [小结](#小结)\n\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章将同步到我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《不可轻视的Java网络编程》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从计算机网络的基础知识入手，一步步地学习Java网络基础，从socket到nio、bio、aio和netty等网络编程知识，并且进行实战，网络编程是每一个Java后端工程师必须要学习和理解的知识点，进一步来说，你还需要掌握Linux中的网络编程原理，包括IO模型、网络编程框架netty的进阶原理，才能更完整地了解整个Java网络编程的知识体系，形成自己的知识框架。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供部分知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n\n上一篇文章介绍了 Java NIO 中 Buffer、Channel 和 Selector 的基本操作，主要是一些接口操作，比较简单。\n\n本文将介绍**非阻塞 IO**和**异步 IO**，也就是大家耳熟能详的 NIO 和 AIO。很多初学者可能分不清楚异步和非阻塞的区别，只是在各种场合能听到**异步非阻塞**这个词。\n\n本文会先介绍并演示阻塞模式，然后引入非阻塞模式来对阻塞模式进行优化，最后再介绍 JDK7 引入的异步 IO，由于网上关于异步 IO 的介绍相对较少，所以这部分内容我会介绍得具体一些。\n\n希望看完本文，读者可以对非阻塞 IO 和异步 IO 的迷雾看得更清晰些，或者为初学者解开一丝丝疑惑也是好的。\n\n## 阻塞模式 IO\n\n我们已经介绍过使用 Java NIO 包组成一个简单的**客户端-服务端**网络通讯所需要的 ServerSocketChannel、SocketChannel 和 Buffer，我们这里整合一下它们，给出一个完整的可运行的例子：\n\n```\npublic class Server {\n\n    public static void main(String[] args) throws IOException {\n\n        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();\n\n        // 监听 8080 端口进来的 TCP 链接\n        serverSocketChannel.socket().bind(new InetSocketAddress(8080));\n\n        while (true) {\n\n            // 这里会阻塞，直到有一个请求的连接进来\n            SocketChannel socketChannel = serverSocketChannel.accept();\n\n            // 开启一个新的线程来处理这个请求，然后在 while 循环中继续监听 8080 端口\n            SocketHandler handler = new SocketHandler(socketChannel);\n            new Thread(handler).start();\n        }\n    }\n}\n```\n\n这里看一下新的线程需要做什么，SocketHandler：\n\n```\npublic class SocketHandler implements Runnable {\n\n    private SocketChannel socketChannel;\n\n    public SocketHandler(SocketChannel socketChannel) {\n        this.socketChannel = socketChannel;\n    }\n\n    @Override\n    public void run() {\n\n        ByteBuffer buffer = ByteBuffer.allocate(1024);\n        try {\n            // 将请求数据读入 Buffer 中\n            int num;\n            while ((num = socketChannel.read(buffer)) > 0) {\n                // 读取 Buffer 内容之前先 flip 一下\n                buffer.flip();\n\n                // 提取 Buffer 中的数据\n                byte[] bytes = new byte[num];\n                buffer.get(bytes);\n\n                String re = new String(bytes, \"UTF-8\");\n                System.out.println(\"收到请求：\" + re);\n\n                // 回应客户端\n                ByteBuffer writeBuffer = ByteBuffer.wrap((\"我已经收到你的请求，你的请求内容是：\" + re).getBytes());\n                socketChannel.write(writeBuffer);\n\n                buffer.clear();\n            }\n        } catch (IOException e) {\n            IOUtils.closeQuietly(socketChannel);\n        }\n    }\n}\n```\n\n最后，贴一下客户端 SocketChannel 的使用，客户端比较简单：\n\n```\npublic class SocketChannelTest {\n    public static void main(String[] args) throws IOException {\n        SocketChannel socketChannel = SocketChannel.open();\n        socketChannel.connect(new InetSocketAddress(\"localhost\", 8080));\n\n        // 发送请求\n        ByteBuffer buffer = ByteBuffer.wrap(\"1234567890\".getBytes());\n        socketChannel.write(buffer);\n\n        // 读取响应\n        ByteBuffer readBuffer = ByteBuffer.allocate(1024);\n        int num;\n        if ((num = socketChannel.read(readBuffer)) > 0) {\n            readBuffer.flip();\n\n            byte[] re = new byte[num];\n            readBuffer.get(re);\n\n            String result = new String(re, \"UTF-8\");\n            System.out.println(\"返回值: \" + result);\n        }\n    }\n}\n```\n\n上面介绍的阻塞模式的代码应该很好理解：来一个新的连接，我们就新开一个线程来处理这个连接，之后的操作全部由那个线程来完成。\n\n那么，这个模式下的性能瓶颈在哪里呢？\n\n1.  首先，每次来一个连接都开一个新的线程这肯定是不合适的。当活跃连接数在几十几百的时候当然是可以这样做的，但如果活跃连接数是几万几十万的时候，这么多线程明显就不行了。每个线程都需要一部分内存，内存会被迅速消耗，同时，线程切换的开销非常大。\n2.  其次，阻塞操作在这里也是一个问题。首先，accept() 是一个阻塞操作，当 accept() 返回的时候，代表有一个连接可以使用了，我们这里是马上就新建线程来处理这个 SocketChannel 了，但是，但是这里不代表对方就将数据传输过来了。所以，SocketChannel#read 方法将阻塞，等待数据，明显这个等待是不值得的。同理，write 方法也需要等待通道可写才能执行写入操作，这边的阻塞等待也是不值得的。\n\n## 非阻塞 IO\n\n说完了阻塞模式的使用及其缺点以后，我们这里就可以介绍非阻塞 IO 了。\n\n非阻塞 IO 的核心在于使用一个 Selector 来管理多个通道，可以是 SocketChannel，也可以是 ServerSocketChannel，将各个通道注册到 Selector 上，指定监听的事件。\n\n之后可以只用一个线程来轮询这个 Selector，看看上面是否有通道是准备好的，当通道准备好可读或可写，然后才去开始真正的读写，这样速度就很快了。我们就完全没有必要给每个通道都起一个线程。\n\nNIO 中 Selector 是对底层操作系统实现的一个抽象，管理通道状态其实都是底层系统实现的，这里简单介绍下在不同系统下的实现。\n\n**select**：上世纪 80 年代就实现了，它支持注册 FD_SETSIZE(1024) 个 socket，在那个年代肯定是够用的，不过现在嘛，肯定是不行了。\n\n**poll**：1997 年，出现了 poll 作为 select 的替代者，最大的区别就是，poll 不再限制 socket 数量。\n\nselect 和 poll 都有一个共同的问题，那就是**它们都只会告诉你有几个通道准备好了，但是不会告诉你具体是哪几个通道**。所以，一旦知道有通道准备好以后，自己还是需要进行一次扫描，显然这个不太好，通道少的时候还行，一旦通道的数量是几十万个以上的时候，扫描一次的时间都很可观了，时间复杂度 O(n)。所以，后来才催生了以下实现。\n\n**epoll**：2002 年随 Linux 内核 2.5.44 发布，epoll 能直接返回具体的准备好的通道，时间复杂度 O(1)。\n\n除了 Linux 中的 epoll，2000 年 FreeBSD 出现了**Kqueue**，还有就是，Solaris 中有**/dev/poll**。\n\n> 前面说了那么多实现，但是没有出现 Windows，Windows 平台的非阻塞 IO 使用 select，我们也不必觉得 Windows 很落后，在 Windows 中 IOCP 提供的异步 IO 是比较强大的。\n\n我们回到 Selector，毕竟 JVM 就是这么一个屏蔽底层实现的平台，**我们面向 Selector 编程就可以了**。\n\n之前在介绍 Selector 的时候已经了解过了它的基本用法，这边来一个可运行的实例代码，大家不妨看看：\n\n```\npublic class SelectorServer {\n\n    public static void main(String[] args) throws IOException {\n        Selector selector = Selector.open();\n\n        ServerSocketChannel server = ServerSocketChannel.open();\n        server.socket().bind(new InetSocketAddress(8080));\n\n        // 将其注册到 Selector 中，监听 OP_ACCEPT 事件\n        server.configureBlocking(false);\n        server.register(selector, SelectionKey.OP_ACCEPT);\n\n        while (true) {\n            int readyChannels = selector.select();\n            if (readyChannels == 0) {\n                continue;\n            }\n            Set<SelectionKey> readyKeys = selector.selectedKeys();\n            // 遍历\n            Iterator<SelectionKey> iterator = readyKeys.iterator();\n            while (iterator.hasNext()) {\n                SelectionKey key = iterator.next();\n                iterator.remove();\n\n                if (key.isAcceptable()) {\n                    // 有已经接受的新的到服务端的连接\n                    SocketChannel socketChannel = server.accept();\n\n                    // 有新的连接并不代表这个通道就有数据，\n                    // 这里将这个新的 SocketChannel 注册到 Selector，监听 OP_READ 事件，等待数据\n                    socketChannel.configureBlocking(false);\n                    socketChannel.register(selector, SelectionKey.OP_READ);\n                } else if (key.isReadable()) {\n                    // 有数据可读\n                    // 上面一个 if 分支中注册了监听 OP_READ 事件的 SocketChannel\n                    SocketChannel socketChannel = (SocketChannel) key.channel();\n                    ByteBuffer readBuffer = ByteBuffer.allocate(1024);\n                    int num = socketChannel.read(readBuffer);\n                    if (num > 0) {\n                        // 处理进来的数据...\n                        System.out.println(\"收到数据：\" + new String(readBuffer.array()).trim());\n                        ByteBuffer buffer = ByteBuffer.wrap(\"返回给客户端的数据...\".getBytes());\n                        socketChannel.write(buffer);\n                    } else if (num == -1) {\n                        // -1 代表连接已经关闭\n                        socketChannel.close();\n                    }\n                }\n            }\n        }\n    }\n}\n```\n\n至于客户端，大家可以继续使用上一节介绍阻塞模式时的客户端进行测试。\n\n## NIO.2 异步 IO\n\nMore New IO，或称 NIO.2，随 JDK 1.7 发布，包括了引入异步 IO 接口和 Paths 等文件访问接口。\n\n异步这个词，我想对于绝大多数开发者来说都很熟悉，很多场景下我们都会使用异步。\n\n通常，我们会有一个线程池用于执行异步任务，提交任务的线程将任务提交到线程池就可以立马返回，不必等到任务真正完成。如果想要知道任务的执行结果，通常是通过传递一个回调函数的方式，任务结束后去调用这个函数。\n\n同样的原理，Java 中的异步 IO 也是一样的，都是由一个线程池来负责执行任务，然后使用回调或自己去查询结果。\n\n大部分开发者都知道为什么要这么设计了，这里再啰嗦一下。异步 IO 主要是为了控制线程数量，减少过多的线程带来的内存消耗和 CPU 在线程调度上的开销。\n\n**在 Unix/Linux 等系统中，JDK 使用了并发包中的线程池来管理任务**，具体可以查看 AsynchronousChannelGroup 的源码。\n\n在 Windows 操作系统中，提供了一个叫做[I/O Completion Ports](https://msdn.microsoft.com/en-us/library/windows/desktop/aa365198.aspx)的方案，通常简称为**IOCP**，操作系统负责管理线程池，其性能非常优异，所以**在 Windows 中 JDK 直接采用了 IOCP 的支持**，使用系统支持，把更多的操作信息暴露给操作系统，也使得操作系统能够对我们的 IO 进行一定程度的优化。\n\n> 在 Linux 中其实也是有异步 IO 系统实现的，但是限制比较多，性能也一般，所以 JDK 采用了自建线程池的方式。\n\n本文还是以实用为主，想要了解更多信息请自行查找其他资料，下面对 Java 异步 IO 进行实践性的介绍。\n\n总共有三个类需要我们关注，分别是**AsynchronousSocketChannel**，**AsynchronousServerSocketChannel**和**AsynchronousFileChannel**，只不过是在之前介绍的 FileChannel、SocketChannel 和 ServerSocketChannel 的类名上加了个前缀**Asynchronous**。\n\nJava 异步 IO 提供了两种使用方式，分别是返回 Future 实例和使用回调函数。\n\n### 1、返回 Future 实例\n\n返回 java.util.concurrent.Future 实例的方式我们应该很熟悉，JDK 线程池就是这么使用的。Future 接口的几个方法语义在这里也是通用的，这里先做简单介绍。\n\n*   future.isDone();\n\n    判断操作是否已经完成，包括了**正常完成、异常抛出、取消**\n\n*   future.cancel(true);\n\n    取消操作，方式是中断。参数 true 说的是，即使这个任务正在执行，也会进行中断。\n\n*   future.isCancelled();\n\n    是否被取消，只有在任务正常结束之前被取消，这个方法才会返回 true\n\n*   future.get();\n\n    这是我们的老朋友，获取执行结果，阻塞。\n\n*   future.get(10, TimeUnit.SECONDS);\n\n    如果上面的 get() 方法的阻塞你不满意，那就设置个超时时间。\n\n### 2、提供 CompletionHandler 回调函数\n\njava.nio.channels.CompletionHandler 接口定义：\n\n```\npublic interface CompletionHandler<V,A> {\n\n    void completed(V result, A attachment);\n\n    void failed(Throwable exc, A attachment);\n}\n```\n\n> 注意，参数上有个 attachment，虽然不常用，我们可以在各个支持的方法中传递这个参数值\n\n```\nAsynchronousServerSocketChannel listener = AsynchronousServerSocketChannel.open().bind(null);\n\n// accept 方法的第一个参数可以传递 attachment\nlistener.accept(attachment, new CompletionHandler<AsynchronousSocketChannel, Object>() {\n    public void completed(\n      AsynchronousSocketChannel client, Object attachment) {\n          // \n      }\n    public void failed(Throwable exc, Object attachment) {\n          // \n      }\n});\n```\n\n### AsynchronousFileChannel\n\n网上关于 Non-Blocking IO 的介绍文章很多，但是 Asynchronous IO 的文章相对就少得多了，所以我这边会多介绍一些相关内容。\n\n首先，我们就来关注异步的文件 IO，前面我们说了，文件 IO 在所有的操作系统中都不支持非阻塞模式，但是我们可以对文件 IO 采用异步的方式来提高性能。\n\n下面，我会介绍 AsynchronousFileChannel 里面的一些重要的接口，都很简单，读者要是觉得无趣，直接滑到下一个标题就可以了。\n\n实例化：\n\n```\nAsynchronousFileChannel channel = AsynchronousFileChannel.open(Paths.get(\"/Users/hongjie/test.txt\"));\n```\n\n一旦实例化完成，我们就可以着手准备将数据读入到 Buffer 中：\n\n```\nByteBuffer buffer = ByteBuffer.allocate(1024);\nFuture<Integer> result = channel.read(buffer, 0);\n```\n\n> 异步文件通道的读操作和写操作都需要提供一个文件的开始位置，文件开始位置为 0\n\n除了使用返回 Future 实例的方式，也可以采用回调函数进行操作，接口如下：\n\n```\npublic abstract <A> void read(ByteBuffer dst,\n                              long position,\n                              A attachment,\n                              CompletionHandler<Integer,? super A> handler);\n```\n\n顺便也贴一下写操作的两个版本的接口：\n\n```\npublic abstract Future<Integer> write(ByteBuffer src, long position);\n\npublic abstract <A> void write(ByteBuffer src,\n                               long position,\n                               A attachment,\n                               CompletionHandler<Integer,? super A> handler);\n```\n\n我们可以看到，AIO 的读写主要也还是与 Buffer 打交道，这个与 NIO 是一脉相承的。\n\n另外，还提供了用于将内存中的数据刷入到磁盘的方法：\n\n```\npublic abstract void force(boolean metaData) throws IOException;\n```\n\n> 因为我们对文件的写操作，操作系统并不会直接针对文件操作，系统会缓存，然后周期性地刷入到磁盘。如果希望将数据及时写入到磁盘中，以免断电引发部分数据丢失，可以调用此方法。参数如果设置为 true，意味着同时也将文件属性信息更新到磁盘。\n\n还有，还提供了对文件的锁定功能，我们可以锁定文件的部分数据，这样可以进行排他性的操作。\n\n```\npublic abstract Future<FileLock> lock(long position, long size, boolean shared);\n```\n\n> position 是要锁定内容的开始位置，size 指示了要锁定的区域大小，shared 指示需要的是共享锁还是排他锁\n\n当然，也可以使用回调函数的版本：\n\n```\npublic abstract <A> void lock(long position,\n                              long size,\n                              boolean shared,\n                              A attachment,\n                              CompletionHandler<FileLock,? super A> handler);\n```\n\n文件锁定功能上还提供了 tryLock 方法，此方法会快速返回结果：\n\n```\npublic abstract FileLock tryLock(long position, long size, boolean shared)\n    throws IOException;\n```\n\n> 这个方法很简单，就是尝试去获取锁，如果该区域已被其他线程或其他应用锁住，那么立刻返回 null，否则返回 FileLock 对象。\n\nAsynchronousFileChannel 操作大体上也就以上介绍的这些接口，还是比较简单的，这里就少一些废话早点结束好了。\n\n### AsynchronousServerSocketChannel\n\n这个类对应的是非阻塞 IO 的 ServerSocketChannel，大家可以类比下使用方式。\n\n我们就废话少说，用代码说事吧：\n\n```\npackage com.javadoop.aio;\n\nimport java.io.IOException;\nimport java.net.InetSocketAddress;\nimport java.net.SocketAddress;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.AsynchronousServerSocketChannel;\nimport java.nio.channels.AsynchronousSocketChannel;\nimport java.nio.channels.CompletionHandler;\n\npublic class Server {\n\n    public static void main(String[] args) throws IOException {\n\n          // 实例化，并监听端口\n        AsynchronousServerSocketChannel server =\n                AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(8080));\n\n        // 自己定义一个 Attachment 类，用于传递一些信息\n        Attachment att = new Attachment();\n        att.setServer(server);\n\n        server.accept(att, new CompletionHandler<AsynchronousSocketChannel, Attachment>() {\n            @Override\n            public void completed(AsynchronousSocketChannel client, Attachment att) {\n                try {\n                    SocketAddress clientAddr = client.getRemoteAddress();\n                    System.out.println(\"收到新的连接：\" + clientAddr);\n\n                    // 收到新的连接后，server 应该重新调用 accept 方法等待新的连接进来\n                    att.getServer().accept(att, this);\n\n                    Attachment newAtt = new Attachment();\n                    newAtt.setServer(server);\n                    newAtt.setClient(client);\n                    newAtt.setReadMode(true);\n                    newAtt.setBuffer(ByteBuffer.allocate(2048));\n\n                    // 这里也可以继续使用匿名实现类，不过代码不好看，所以这里专门定义一个类\n                    client.read(newAtt.getBuffer(), newAtt, new ChannelHandler());\n                } catch (IOException ex) {\n                    ex.printStackTrace();\n                }\n            }\n\n            @Override\n            public void failed(Throwable t, Attachment att) {\n                System.out.println(\"accept failed\");\n            }\n        });\n        // 为了防止 main 线程退出\n        try {\n            Thread.currentThread().join();\n        } catch (InterruptedException e) {\n        }\n    }\n}\n```\n\n看一下 ChannelHandler 类：\n\n```\npackage com.javadoop.aio;\n\nimport java.io.IOException;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.CompletionHandler;\nimport java.nio.charset.Charset;\n\npublic class ChannelHandler implements CompletionHandler<Integer, Attachment> {\n\n    @Override\n    public void completed(Integer result, Attachment att) {\n        if (att.isReadMode()) {\n            // 读取来自客户端的数据\n            ByteBuffer buffer = att.getBuffer();\n            buffer.flip();\n            byte bytes[] = new byte[buffer.limit()];\n            buffer.get(bytes);\n            String msg = new String(buffer.array()).toString().trim();\n            System.out.println(\"收到来自客户端的数据: \" + msg);\n\n            // 响应客户端请求，返回数据\n            buffer.clear();\n            buffer.put(\"Response from server!\".getBytes(Charset.forName(\"UTF-8\")));\n            att.setReadMode(false);\n            buffer.flip();\n            // 写数据到客户端也是异步\n            att.getClient().write(buffer, att, this);\n        } else {\n            // 到这里，说明往客户端写数据也结束了，有以下两种选择:\n            // 1\\. 继续等待客户端发送新的数据过来\n//            att.setReadMode(true);\n//            att.getBuffer().clear();\n//            att.getClient().read(att.getBuffer(), att, this);\n            // 2\\. 既然服务端已经返回数据给客户端，断开这次的连接\n            try {\n                att.getClient().close();\n            } catch (IOException e) {\n            }\n        }\n    }\n\n    @Override\n    public void failed(Throwable t, Attachment att) {\n        System.out.println(\"连接断开\");\n    }\n}\n```\n\n顺便再贴一下自定义的 Attachment 类：\n\n```\npublic class Attachment {\n    private AsynchronousServerSocketChannel server;\n    private AsynchronousSocketChannel client;\n    private boolean isReadMode;\n    private ByteBuffer buffer;\n    // getter & setter\n}\n```\n\n这样，一个简单的服务端就写好了，接下来可以接收客户端请求了。上面我们用的都是回调函数的方式，读者要是感兴趣，可以试试写个使用 Future 的。\n\n### AsynchronousSocketChannel\n\n其实，说完上面的 AsynchronousServerSocketChannel，基本上读者也就知道怎么使用 AsynchronousSocketChannel 了，和非阻塞 IO 基本类似。\n\n这边做个简单演示，这样读者就可以配合之前介绍的 Server 进行测试使用了。\n\n```\npackage com.javadoop.aio;\n\nimport java.io.IOException;\nimport java.net.InetSocketAddress;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.AsynchronousSocketChannel;\nimport java.nio.charset.Charset;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.Future;\n\npublic class Client {\n\n    public static void main(String[] args) throws Exception {\n        AsynchronousSocketChannel client = AsynchronousSocketChannel.open();\n          // 来个 Future 形式的\n        Future<?> future = client.connect(new InetSocketAddress(8080));\n        // 阻塞一下，等待连接成功\n        future.get();\n\n        Attachment att = new Attachment();\n        att.setClient(client);\n        att.setReadMode(false);\n        att.setBuffer(ByteBuffer.allocate(2048));\n        byte[] data = \"I am obot!\".getBytes();\n        att.getBuffer().put(data);\n        att.getBuffer().flip();\n\n        // 异步发送数据到服务端\n        client.write(att.getBuffer(), att, new ClientChannelHandler());\n\n        // 这里休息一下再退出，给出足够的时间处理数据\n        Thread.sleep(2000);\n    }\n}\n```\n\n往里面看下 ClientChannelHandler 类：\n\n```\npackage com.javadoop.aio;\n\nimport java.io.IOException;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.CompletionHandler;\nimport java.nio.charset.Charset;\n\npublic class ClientChannelHandler implements CompletionHandler<Integer, Attachment> {\n\n    @Override\n    public void completed(Integer result, Attachment att) {\n        ByteBuffer buffer = att.getBuffer();\n        if (att.isReadMode()) {\n            // 读取来自服务端的数据\n            buffer.flip();\n            byte[] bytes = new byte[buffer.limit()];\n            buffer.get(bytes);\n            String msg = new String(bytes, Charset.forName(\"UTF-8\"));\n            System.out.println(\"收到来自服务端的响应数据: \" + msg);\n\n            // 接下来，有以下两种选择:\n            // 1\\. 向服务端发送新的数据\n//            att.setReadMode(false);\n//            buffer.clear();\n//            String newMsg = \"new message from client\";\n//            byte[] data = newMsg.getBytes(Charset.forName(\"UTF-8\"));\n//            buffer.put(data);\n//            buffer.flip();\n//            att.getClient().write(buffer, att, this);\n            // 2\\. 关闭连接\n            try {\n                att.getClient().close();\n            } catch (IOException e) {\n            }\n        } else {\n            // 写操作完成后，会进到这里\n            att.setReadMode(true);\n            buffer.clear();\n            att.getClient().read(buffer, att, this);\n        }\n    }\n\n    @Override\n    public void failed(Throwable t, Attachment att) {\n        System.out.println(\"服务器无响应\");\n    }\n}\n```\n\n以上代码都是可以运行调试的，如果读者碰到问题，请在评论区留言。\n\n### Asynchronous Channel Groups\n\n为了知识的完整性，有必要对 group 进行介绍，其实也就是介绍 AsynchronousChannelGroup 这个类。之前我们说过，异步 IO 一定存在一个线程池，这个线程池负责接收任务、处理 IO 事件、回调等。这个线程池就在 group 内部，group 一旦关闭，那么相应的线程池就会关闭。\n\nAsynchronousServerSocketChannels 和 AsynchronousSocketChannels 是属于 group 的，当我们调用 AsynchronousServerSocketChannel 或 AsynchronousSocketChannel 的 open() 方法的时候，相应的 channel 就属于默认的 group，这个 group 由 JVM 自动构造并管理。\n\n如果我们想要配置这个默认的 group，可以在 JVM 启动参数中指定以下系统变量：\n\n*   java.nio.channels.DefaultThreadPool.threadFactory\n\n    此系统变量用于设置 ThreadFactory，它应该是 java.util.concurrent.ThreadFactory 实现类的全限定类名。一旦我们指定了这个 ThreadFactory 以后，group 中的线程就会使用该类产生。\n\n*   java.nio.channels.DefaultThreadPool.initialSize\n\n    此系统变量也很好理解，用于设置线程池的初始大小。\n\n可能你会想要使用自己定义的 group，这样可以对其中的线程进行更多的控制，使用以下几个方法即可：\n\n*   AsynchronousChannelGroup.withCachedThreadPool(ExecutorService executor, int initialSize)\n*   AsynchronousChannelGroup.withFixedThreadPool(int nThreads, ThreadFactory threadFactory)\n*   AsynchronousChannelGroup.withThreadPool(ExecutorService executor)\n\n熟悉线程池的读者对这些方法应该很好理解，它们都是 AsynchronousChannelGroup 中的静态方法。\n\n至于 group 的使用就很简单了，代码一看就懂：\n\n```\nAsynchronousChannelGroup group = AsynchronousChannelGroup\n        .withFixedThreadPool(10, Executors.defaultThreadFactory());\nAsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open(group);\nAsynchronousSocketChannel client = AsynchronousSocketChannel.open(group);\n```\n\n**AsynchronousFileChannels 不属于 group**。但是它们也是关联到一个线程池的，如果不指定，会使用系统默认的线程池，如果想要使用指定的线程池，可以在实例化的时候使用以下方法：\n\n```\npublic static AsynchronousFileChannel open(Path file,\n                                           Set<? extends OpenOption> options,\n                                           ExecutorService executor,\n                                           FileAttribute<?>... attrs) {\n    ...\n}\n```\n\n到这里，异步 IO 就算介绍完成了。\n\n## 小结\n\n我想，本文应该是说清楚了非阻塞 IO 和异步 IO 了，对于异步 IO，由于网上的资料比较少，所以不免篇幅多了些。\n\n我们也要知道，看懂了这些，确实可以学到一些东西，多了解一些知识，但是我们还是很少在工作中将这些知识变成工程代码。一般而言，我们需要在网络应用中使用 NIO 或 AIO 来提升性能，但是，在工程上，绝不是了解了一些概念，知道了一些接口就可以的，需要处理的细节还非常多。\n\n这也是为什么 Netty/Mina 如此盛行的原因，因为它们帮助封装好了很多细节，提供给我们用户友好的接口，后面有时间我也会对 Netty 进行介绍。\n\n（全文完）\n"
  },
  {
    "path": "docs/Java/network/Java网络编程与NIO详解：LinuxEpoll实现原理详解.md",
    "content": "# 目录\n\n  * [为什么要 I/O 多路复用](#为什么要-io-多路复用)\n    * [select](#select)\n    * [poll](#poll)\n    * [epoll](#epoll)\n      * [epoll_create 用来创建一个 epoll 描述符：](#epoll_create-用来创建一个-epoll-描述符：)\n      * [epoll_ctl 用来增/删/改内核中的事件表：](#epoll_ctl-用来增删改内核中的事件表：)\n      * [epoll_wait 用来等待事件](#epoll_wait-用来等待事件)\n\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章将同步到我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《不可轻视的Java网络编程》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从计算机网络的基础知识入手，一步步地学习Java网络基础，从socket到nio、bio、aio和netty等网络编程知识，并且进行实战，网络编程是每一个Java后端工程师必须要学习和理解的知识点，进一步来说，你还需要掌握Linux中的网络编程原理，包括IO模型、网络编程框架netty的进阶原理，才能更完整地了解整个Java网络编程的知识体系，形成自己的知识框架。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供部分知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n## 为什么要 I/O 多路复用\n\n当需要从一个叫`r_fd`的描述符不停地读取数据，并把读到的数据写入一个叫`w_fd`的描述符时，我们可以用循环使用阻塞 I/O ：\n````\n    while((n = read(r_fd, buf, BUF_SIZE)) > 0)\n        if(write(w_fd, buf, n) != n)\n            err_sys(\"write error\");\n````\n但是，如果要从两个地方读取数据呢？这时，不能再使用会把程序阻塞住的`read`函数。因为可能在阻塞地等待`r_fd1`的数据时，来不及处理`r_fd2`，已经到达的`r_fd2`的数据可能会丢失掉。\n\n这个情况下需要使用**非阻塞 I/O**。\n\n只要做个标记，把文件描述符标记为非阻塞的，以后再对它使用`read`函数：如果它还没有数据可读，函数会立即返回并把 errorno 这个变量的值设置为 35，于是我们知道它没有数据可读，然后可以立马去对其他描述符使用`read`；如果它有数据可读，我们就读取它数据。对所有要读的描述符都调用了一遍`read`之后，我们可以等一个较长的时间（比如几秒），然后再从第一个文件描述符开始调用`read`。这种循环就叫做**轮询**（polling）。\n\n这样，不会像使用阻塞 I/O 时那样因为一个描述符`read`长时间处于等待数据而使程序阻塞。\n\n轮询的缺点是浪费太多 CPU 时间。大多数时候我们没有数据可读，但是还是用了`read`这个**系统调用**，使用系统调用时会从用户态切换到内核态。而大多数情况下我们调用`read`，然后陷入内核态，内核发现这个描述符没有准备好，然后切换回用户态并且只得到 EAGAIN （errorno 被设置为 35），做的是无用功。描述符非常多的时候，每次的切换过程就是巨大的浪费。\n\n所以，需要**I/O 多路复用**。I/O 多路复用通过使用一个系统函数，同时等待多个描述符的可读、可写状态。\n\n为了达到这个目的，我们需要做的是：建立一个描述符列表，以及我们分别关心它们的什么事件（可读还是可写还是发生例外情况）；调用一个系统函数，直到这个描述符列表里有至少一个描述符关联的事件发生时，这个函数才会返回。\n\nselect, poll, epoll 就是这样的系统函数。\n\n### select\n\n我们可以在所有 POSIX 兼容的系统里使用 select 函数来进行 I/O 多路复用。我们需要通过 select 函数的参数传递给内核的信息有：\n\n    *   我们关心哪些描述符\n    *   我们关心它们的什么事件\n    *   我们希望等待多长时间\n\nselect 的返回时，内核会告诉我们：\n    \n    *   可读的描述符的个数\n    *   哪些描述符发生了哪些事件\n````\n    #include <sys/select.h>\n    int select(int maxfdp1, fd_set* readfds,\n               fd_set* writefds, fd_set* exceptfds,\n               struct timeval* timeout);\n    \n    // 返回值: 已就绪的描述符的个数。超时时为 0 ，错误时为 -1\n````\n`maxfdp1`意思是 “max file descriptor plus 1” ，就是把你要监视的所有文件描述符里最大的那个加上 1 。（它实际上决定了内核要遍历文件描述符的次数，比如你监视了文件描述符 5 和 20 并把`maxfdp1`设置为 21 ，内核每次都会从描述符 0 依次检查到 20。）\n\n中间的三个参数是你想监视的文件描述符的集合。可以把 fd_set 类型视为 1024 位的二进制数，这意味着 select 只能监视小于 1024 的文件描述符（1024 是由 Linux 的 sys/select.h 里`FD_SETSIZE`宏设置的值）。在 select 返回后我们通过`FD_ISSET`来判断代表该位的描述符是否是已准备好的状态。\n\n最后一个参数是等待超时的时长：到达这个时长但是没有任一描述符可用时，函数会返回 0 。\n\n用一个代码片段来展示 select 的用法：\n````\n        // 这个例子要监控文件描述符 3, 4 的可读状态，以及 4, 5 的可写状态\n        \n        // 初始化两个 fd_set 以及 timeval\n        fd_set read_set, write_set;\n        FD_ZERO(read_set);\n        FD_ZERO(write_set);\n        timeval t;\n        t.tv_sec = 5;   // 超时为 5 秒\n        t.tv_usec = 0;  // 加 0 微秒\n        \n        // 设置好两个 fd_set\n        int fd1 = 3;\n        int fd2 = 4;\n        int fd3 = 5;\n        int maxfdp1 = 5 + 1;\n        FD_SET(fd1, &read_set);\n        FD_SET(fd2, &read_set);\n        FD_SET(fd2, &write_set);\n        FD_SET(fd3, &write_set);\n        \n        // 准备备用的 fd_set\n        fd_set r_temp = read_set;\n        fd_set w_temp = write_set;\n        \n        while(true){\n            // 每次都要重新设置放入 select 的 fd_set\n            read_set = r_temp;\n            write_set = w_temp;\n        \n            // 使用 select\n            int n = select(maxfdp1, &read_set, &write_set, NULL, &t);\n        \n            // 上面的 select 函数会一直阻塞，直到\n            // 3, 4 可读以及 4, 5 可写这四件事中至少一项发生\n            // 或者等待时间到达 5 秒，返回 0\n        \n            for(int i=0; i<maxfdp1 && n>0; i++){\n                if(FD_ISSET(i, &read_set)){\n                    n--;\n                    if(i==fd1)\n                        prinf(\"描述符 3 可读\");\n                    if(i==fd2)\n                        prinf(\"描述符 4 可读\");\n                }\n                if(FD_ISSET(i, &write_set)){\n                    n--;\n                    if(i==fd2)\n                        prinf(\"描述符 3 可写\");\n                    if(i==fd3)\n                        prinf(\"描述符 4 可写\");\n                }\n            }\n            // 上面的 printf 语句换成对应的 read 或者 write 函数就\n            // 可以立即读取或者写入相应的描述符而不用等待\n        }\n````\n可以看到，select 的缺点有：\n\n*   默认能监视的文件描述符不能大于 1024，也代表监视的总数不超过1024。即使你因为需要监视的描述符大于 1024 而改动内核的`FD_SETSIZE`值，但由于 select 是每次都会线性扫描整个fd_set，集合越大速度越慢，所以性能会比较差。\n*   select 函数返回时只能看见已准备好的描述符数量，至于是哪个描述符准备好了需要循环用`FD_ISSET`来检查，当未准备好的描述符很多而准备好的很少时，效率比较低。\n*   select 函数每次执行的时候，都把参数里传入的三个 fd_set 从用户空间复制到内核空间。而每次 fd_set 里要监视的描述符变化不大时，全部重新复制一遍并不划算。同样在每次都是未准备好的描述符很多而准备好的很少时，调用 select 会很频繁，用户/内核间的的数据复制就成了一个大的开销。\n\n还有一个问题是在代码的写法上给我一些困扰的，就是每次调用 select 前必须重新设置三个 fd_set。 fd_set 类型只是 1024 位的二进制数（实际上结构体里是几个 long 变量的数组；比如 64 位机器上 long 是 64 bit，那么 fd_set 里就是 16 个 long 变量的数组），由一位的 1 和 0 代表一个文件描述符的状态，但是其实调用 select 前后位的 1/0 状态意义是不一样的。\n\n先讲一下几个对 fd_set 操作的函数的作用：`FD_ZERO`把 fd_set 所有位设置为 0 ；`FD_SET`把一个位设置为 1 ；`FD_ISSET`判断一个位是否为 1 。\n\n调用 select 前：我们用`FD_ZERO`把 fd_set 先全部初始化，然后用`FD_SET`把我们关心的代表描述符的位设置为 1 。我们这时可以用`FD_ISSET`判断这个位是否被我们设置，这时的含义是**我们想要监视的描述符是否被设置为被监视的状态**。\n\n调用 select 时：内核判断 fd_set 里的位并把各个 fd_set 里所有值为 1 的位记录下来，然后把 fd_set 全部设置成 0 ；一个描述符上有对应的事件发生时，把对应 fd_set 里代表这个描述符的位设置为 1 。\n\n在 select 返回之后：我们同样用`FD_ISSET`判断各个我们关心的位是 0 还是 1 ，这时的含义是，**这个位是否是发生了我们关心的事件**。\n\n所以，在下一次调用 select 前，我们不得不把已经被内核改掉的 fd_set 全部重新设置一下。\n\nselect 在监视大量描述符尤其是更多的描述符未准备好的情况时性能很差。《Unix 高级编程》里写，用 select 的程序通常只使用 3 到 10 个描述符。\n\n### poll\n\npoll 和 select 是相似的，只是给的接口不同。\n````\n    #include <poll.h>\n    int poll(struct pollfd fdarray[], nfds_t nfds, int timeout);\n    \n    // 返回值: 已就绪的描述符的个数。超时时为 0 ，错误时为 -1\n`fdarray`是`pollfd`的数组。`pollfd`结构体是这样的：\n\n    struct pollfd {\n        int fd;         // 文件描述符\n        short events;   // 我期待的事件\n        short revents;  // 实际发生的事件：我期待的事件中发生的；或者异常情况\n    };\n````\n`nfds`是`fdarray`的长度，也就是 pollfd 的个数。\n\n`timeout`代表等待超时的毫秒数。\n\n相比 select ，poll 有这些优点：由于 poll 在 pollfd 里用`int fd`来表示文件描述符而不像 select 里用的 fd_set 来分别表示描述符，所以没有必须小于 1024 的限制，也没有数量限制；由于 poll 用`events`表示期待的事件，通过修改`revents`来表示发生的事件，所以不需要像 select 在每次调用前重新设置描述符和期待的事件。\n\n除此之外，poll 和 select 几乎相同。在 poll 返回后，需要遍历`fdarray`来检查各个`pollfd`里的`revents`是否发生了期待的事件；每次调用 poll 时，把`fdarray`复制到内核空间。在描述符太多而每次准备好的较少时，poll 有同样的性能问题。\n\n### epoll\n\nepoll 是在 Linux 2.5.44 中首度登场的。不像 select 和 poll ，它提供了三个系统函数而不是一个。\n\n#### epoll_create 用来创建一个 epoll 描述符：\n\n    #include <sys/epoll.h>\n    int epoll_create(int size);\n    \n    // 返回值：epoll 描述符\n`size`用来告诉内核你想监视的文件描述符的数目，但是它**并不是限制了能监视的描述符的最大个数**，而是给内核最初分配的空间一个建议。然后系统会在内核中分配一个空间来存放事件表，并返回一个**epoll 描述符**，用来操作这个事件表。\n\n#### epoll_ctl 用来增/删/改内核中的事件表：\n\n    int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);\n    \n    // 返回值：成功时返回 0 ，失败时返回 -1\n\n`epfd`是 epoll 描述符。\n\n`op`是操作类型（增加/删除/修改）。\n\n`fd`是希望监视的文件描述符。\n\n`event`是一个 epoll_event 结构体的指针。epoll_event 的定义是这样的：\n\n    typedef union epoll_data {\n       void        *ptr;\n       int          fd;\n       uint32_t     u32;\n       uint64_t     u64;\n    } epoll_data_t;\n    \n    struct epoll_event {\n       uint32_t     events;      // 我期待的事件\n       epoll_data_t data;        // 用户数据变量\n    };\n\n这个结构体里，除了期待的事件外，还有一个`data`，是一个 union，它是用来让我们在得到下面第三个函数的返回值以后方便的定位文件描述符的。\n\n#### epoll_wait 用来等待事件\n\n    int epoll_wait(int epfd, struct epoll_event *result_events,\n                  int maxevents, int timeout);\n    \n    // 返回值：已就绪的描述符个数。超时时为 0 ，错误时为 -1\n`epfd`是 epoll 描述符。\n\n`result_events`是 epoll_event 结构体的指针，它将指向的是所有已经准备好的事件描述符相关联的 epoll_event（在上个步骤里调用 epoll_ctl 时关联起来的）。下面的例子可以让你知道这个参数的意义。\n\n`maxevents`是返回的最大事件个数，也就是你能通过 result_events 指针遍历到的最大的次数。\n\n`timeout`是等待超时的毫秒数。\n\n用一个代码片段来展示 epoll 的用法：\n    // 这个例子要监控文件描述符 3, 4 的可读状态，以及 4, 5 的可写状态\n    \n    /* 通过 epoll_create 创建 epoll 描述符 */\n    int epfd = epoll_create(4);\n    \n    int fd1 = 3;\n    int fd2 = 4;\n    int fd3 = 5;\n    \n    /* 通过 epoll_ctl 注册好四个事件 */\n    struct epoll_event ev1;\n    ev1.events = EPOLLIN;      // 期待它的可读事件发生\n    ev1.data   = fd1;          // 我们通常就把 data 设置为 fd ，方便以后查看\n    epoll_ctl(epfd, EPOLL_CTL_ADD, fd1, &ev1);  // 添加到事件表\n    \n    struct epoll_event ev2;\n    ev2.events = EPOLLIN;\n    ev2.data   = fd2;\n    epoll_ctl(epfd, EPOLL_CTL_ADD, fd2, &ev2);\n    \n    struct epoll_event ev3;\n    ev3.events = EPOLLOUT;     // 期待它的可写事件发生\n    ev3.data   = fd2;\n    epoll_ctl(epfd, EPOLL_CTL_ADD, fd2, &ev3);\n    \n    struct epoll_event ev4;\n    ev4.events = EPOLLOUT;\n    ev4.data   = fd3;\n    epoll_ctl(epfd, EPOLL_CTL_ADD, fd3, &ev4);\n    \n    /* 通过 epoll_wait 等待事件 */\n    # DEFINE MAXEVENTS 4\n    struct epoll_event result_events[MAXEVENTS];\n    \n    while(true){\n        int n = epoll_wait(epfd, &result_events, MAXEVENTS, 5000);\n    \n        for(int i=0; i<n; n--){\n            // result_events[i] 一定是 ev1 到 ev4 中的一个\n            if(result_events[i].events&EPOLLIN)\n                printf(\"描述符 %d 可读\", result_events[i].fd);\n            else if(result_events[i].events&EPOLLOUT)\n                printf(\"描述符 %d 可写\", result_events[i].fd)\n        }\n    }\n\n所以 epoll 解决了 poll 和 select 的问题：\n\n*   只在 epoll_ctl 的时候把数据复制到内核空间，这保证了每个描述符和事件一定只会被复制到内核空间一次；每次调用 epoll_wait 都不会复制新数据到内核空间。相比之下，select 每次调用都会把三个 fd_set 复制一遍；poll 每次调用都会把`fdarray`复制一遍。\n\n*   epoll_wait 返回 n ，那么只需要做 n 次循环，可以保证遍历的每一次都是有意义的。相比之下，select 需要做至少 n 次至多`maxfdp1`次循环；poll 需要遍历完 fdarray 即做`nfds`次循环。\n\n*   在内部实现上，epoll 使用了回调的方法。调用 epoll_ctl 时，就是注册了一个事件：在集合中放入文件描述符以及事件数据，并且加上一个回调函数。一旦文件描述符上的对应事件发生，就会调用回调函数，这个函数会把这个文件描述符加入到**就绪队列**上。当你调用 epoll_wait 时，它只是在查看就绪队列上是否有内容，有的话就返回给你的程序。`select()``poll()``epoll_wait()`三个函数在操作系统看来，都是睡眠一会儿然后判断一会儿的循环，但是 select 和 poll 在醒着的时候要遍历整个文件描述符集合，而 epoll_wait 只是看看就绪队列是否为空而已。这是 epoll 高性能的理由，使得其 I/O 的效率不会像使用轮询的 select/poll 随着描述符增加而大大降低。\n\n> 注 1 ：select/poll/epoll_wait 三个函数的等待超时时间都有一样的特性：等待时间设置为 0 时函数不阻塞而是立即返回，不论是否有文件描述符已准备好；poll/epoll_wait 中的 timeout 为 -1，select 中的 timeout 为 NULL 时，则无限等待，直到有描述符已准备好才会返回。\n\n> 注 2 ：有的新手会把文件描述符是否标记为阻塞 I/O 等同于 I/O 多路复用函数是否阻塞。其实文件描述符是否标记为阻塞，决定了你`read`或`write`它时如果它未准备好是阻塞等待，还是立即返回 EAGAIN ；而 I/O 多路复用函数除非你把 timeout 设置为 0 ，否则它总是会阻塞住你的程序。\n\n> 注 3 ：上面的例子只是入门，可能是不准确或不全面的：一是数据要立即处理防止丢失；二是 EPOLLIN/EPOLLOUT 不完全等同于可读可写事件，具体要去搜索 poll/epoll 的事件具体有哪些；三是大多数实际例子里，比如一个 tcp server ，都会在运行中不断增加/删除的文件描述符而不是记住固定的 3 4 5 几个描述符（用这种例子更能看出 epoll 的优势）；四是 epoll 的优势更多的体现在处理大量闲连接的情况，如果场景是处理少量短连接，用 select 反而更好，而且用 select 的代码能运行在所有平台上。\n"
  },
  {
    "path": "docs/Java/network/Java网络编程与NIO详解：Tomcat中的Connector源码分析（NIO）.md",
    "content": "# 目录\n\n* [目录](#目录)\n  * [前言](#前言)\n  * [源码环境准备](#源码环境准备)\n  * [endpoint](#endpoint)\n  * [init 过程分析](#init-过程分析)\n  * [start 过程分析](#start-过程分析)\n  * [Acceptor](#acceptor)\n  * [Poller](#poller)\n  * [processKey](#processkey)\n  * [总结](#总结)\n\n\n# 目录\n  * [前言](#前言)\n  * [源码环境准备](#源码环境准备)\n  * [endpoint](#endpoint)\n  * [init 过程分析](#init-过程分析)\n  * [start 过程分析](#start-过程分析)\n  * [Acceptor](#acceptor)\n  * [Poller](#poller)\n  * [processKey](#processkey)\n  * [总结](#总结)\n\n\n本文转载 https://www.javadoop.com\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章将同步到我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《不可轻视的Java网络编程》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从计算机网络的基础知识入手，一步步地学习Java网络基础，从socket到nio、bio、aio和netty等网络编程知识，并且进行实战，网络编程是每一个Java后端工程师必须要学习和理解的知识点，进一步来说，你还需要掌握Linux中的网络编程原理，包括IO模型、网络编程框架netty的进阶原理，才能更完整地了解整个Java网络编程的知识体系，形成自己的知识框架。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供部分知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n## 前言\n\n之前写了两篇关于 NIO 的文章，第一篇介绍了 NIO 的 Channel、Buffer、Selector 使用，第二篇介绍了非阻塞 IO 和异步 IO，并展示了简单的用例。\n\n本文将介绍 Tomcat 中的 NIO 使用，使大家对 Java NIO 的生产使用有更加直观的认识。\n\n虽然本文的源码篇幅也不短，但是 Tomcat 的源码毕竟不像 Doug Lea 的并发源码那么“变态”，对于大部分读者来说，阅读难度比之前介绍的其他并发源码要简单一些，所以读者不要觉得有什么压力。\n\n本文基于 Tomcat 当前（2018-03-20）**最新版本 9.0.6**。\n\n先简单画一张图示意一下本文的主要内容：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405105401.png)\n\n**目录**\n\n## 源码环境准备\n\nTomcat 9.0.6 下载地址：[https://tomcat.apache.org/download-90.cgi](https://tomcat.apache.org/download-90.cgi)\n\n由于上面下载的 tomcat 的源码并没有使用 maven 进行组织，不方便我们看源码，也不方便我们进行调试。这里我们将使用 maven 仓库中的 tomcat-embed-core，自己编写代码进行启动的方式来进行调试。\n\n首先，创建一个空的 maven 工程，然后添加以下依赖。\n\n```\n<dependency>\n    <groupId>org.apache.tomcat.embed</groupId>\n    tomcat-embed-core\n    <version>9.0.6</version>\n</dependency>\n```\n\n> 上面的依赖，只会将 tomcat-embed-core-9.0.6.jar 和 tomcat-annotations-api-9.0.6.jar 两个包引进来，对于本文来说，已经足够了，如果你需要其他功能，需要额外引用其他的依赖，如 Jasper。\n\n然后，使用以下启动方法：\n\n```\npublic static void main(String[] args) throws LifecycleException {\n\n   Tomcat tomcat = new Tomcat();\n\n   Connector connector = new Connector(\"HTTP/1.1\");\n   connector.setPort(8080);\n   tomcat.setConnector(connector);\n\n   tomcat.start();\n   tomcat.getServer().await();\n}\n```\n\n经过以上的代码，我们的 Tomcat 就启动起来了。\n\n> Tomcat 中的其他接口感兴趣的读者请自行探索，如设置 webapp 目录，设置 resources 等\n\n这里，介绍第一个重要的概念：**Connector**。在 Tomcat 中，使用 Connector 来处理连接，一个 Tomcat 可以配置多个 Connector，分别用于监听不同端口，或处理不同协议。\n\n在 Connector 的构造方法中，我们可以传`HTTP/1.1`或`AJP/1.3`用于指定协议，也可以传入相应的协议处理类，毕竟协议不是重点，将不同端口进来的连接对应不同处理类才是正道。典型地，我们可以指定以下几个协议处理类：\n\n*   org.apache.coyote.http11.Http11NioProtocol：对应非阻塞 IO\n*   org.apache.coyote.http11.Http11Nio2Protocol：对应异步 IO\n*   org.apache.coyote.http2.Http2Protocol：对应 http2 协议，对 http2 感兴趣的读者，赶紧看起来吧。\n\n本文的重点当然是非阻塞 IO 了，之前已经介绍过`异步 IO`的基础知识了，读者看完本文后，如果对异步 IO 的处理流程感兴趣，可以自行去分析一遍。\n\n> 如果你使用 9.0 以前的版本，Tomcat 在启动的时候是会自动配置一个 connector 的，我们可以不用显示配置。\n> \n> 9.0 版本的 Tomcat#start() 方法：\n> \n> ```\n> public void start() throws LifecycleException {\n>     getServer();\n>     server.start();\n> }\n> ```\n> \n> 8.5 及之前版本的 Tomcat#start() 方法：\n> \n> ```\n> public void start() throws LifecycleException {\n>     getServer();\n>     // 自动配置一个使用非阻塞 IO 的 connector\n>     getConnector();\n>     server.start();\n> }\n> ```\n\n## endpoint\n\n前面我们说过一个 Connector 对应一个协议，当然这描述也不太对，NIO 和 NIO2 就都是处理 HTTP/1.1 的，只不过一个使用非阻塞，一个使用异步。进到指定 protocol 代码，我们就会发现，它们的代码及其简单，只不过是指定了特定的**endpoint**。\n\n打开`Http11NioProtocol`和`Http11Nio2Protocol`源码，我们可以看到，在构造方法中，它们分别指定了 NioEndpoint 和 Nio2Endpoint。\n\n```\n// 非阻塞模式\npublic class Http11NioProtocol extends AbstractHttp11JsseProtocol<NioChannel> {\n    public Http11NioProtocol() {\n        // NioEndpoint\n        super(new NioEndpoint());\n    }\n    ...\n}\n// 异步模式\npublic class Http11Nio2Protocol extends AbstractHttp11JsseProtocol<Nio2Channel> {\n\n    public Http11Nio2Protocol() {\n        // Nio2Endpoint\n        super(new Nio2Endpoint());\n    }\n    ...\n}\n```\n\n这里介绍第二个重要的概念：**endpoint**。Tomcat 使用不同的 endpoint 来处理不同的协议请求，今天我们的重点是**NioEndpoint**，其使用**非阻塞 IO**来进行处理 HTTP/1.1 协议的请求。\n\n**NioEndpoint**继承 =>**AbstractJsseEndpoint**继承 =>**AbstractEndpoint**。中间的 AbstractJsseEndpoint 主要是提供了一些关于`HTTPS`的方法，这块我们暂时忽略它，后面所有关于 HTTPS 的我们都直接忽略，感兴趣的读者请自行分析。\n\n## init 过程分析\n\n下面，我们看看从 tomcat.start() 一直到 NioEndpoint 的过程。\n\n**1\\. AbstractProtocol**#**init**\n\n```\n@Override\npublic void init() throws Exception {\n    ...\n    String endpointName = getName();\n    endpoint.setName(endpointName.substring(1, endpointName.length()-1));\n    endpoint.setDomain(domain);\n    // endpoint 的 name=http-nio-8089,domain=Tomcat\n    endpoint.init();\n}\n```\n\n**2\\. AbstractEndpoint**#**init**\n\n```\npublic final void init() throws Exception {\n    if (bindOnInit) {\n        bind(); // 这里对应的当然是子类 NioEndpoint 的 bind() 方法\n        bindState = BindState.BOUND_ON_INIT;\n    }\n    ...\n}\n```\n\n**3\\. NioEndpoint**#**bind**\n\n这里就到我们的 NioEndpoint 了，要使用到我们之前学习的 NIO 的知识了。\n\n```\n@Override\npublic void bind() throws Exception {\n    // initServerSocket(); 原代码是这行，我们 “内联” 过来一起说\n\n    // 开启 ServerSocketChannel\n    serverSock = ServerSocketChannel.open();\n    socketProperties.setProperties(serverSock.socket());\n\n    // getPort() 会返回我们最开始设置的 8080，得到我们的 address 是 0.0.0.0:8080\n    InetSocketAddress addr = (getAddress()!=null?new InetSocketAddress(getAddress(),getPort()):new InetSocketAddress(getPort()));\n\n    // ServerSocketChannel 绑定地址、端口，\n    // 第二个参数 backlog 默认为 100，超过 100 的时候，新连接会被拒绝(不过源码注释也说了，这个值的真实语义取决于具体实现)\n    serverSock.socket().bind(addr,getAcceptCount());\n\n    // ※※※ 设置 ServerSocketChannel 为阻塞模式 ※※※\n    serverSock.configureBlocking(true);\n\n    // 设置 acceptor 和 poller 的数量，至于它们是什么角色，待会说\n    // acceptorThreadCount 默认为 1\n    if (acceptorThreadCount == 0) {\n        // FIXME: Doesn't seem to work that well with multiple accept threads\n        // 作者想表达的意思应该是：使用多个 acceptor 线程并不见得性能会更好\n        acceptorThreadCount = 1;\n    }\n\n    // poller 线程数，默认值定义如下，所以在多核模式下，默认为 2\n    // pollerThreadCount = Math.min(2,Runtime.getRuntime().availableProcessors());\n    if (pollerThreadCount <= 0) {\n        pollerThreadCount = 1;\n    }\n\n    // \n    setStopLatch(new CountDownLatch(pollerThreadCount));\n\n    // 初始化 ssl，我们忽略 ssl\n    initialiseSsl();\n\n    // 打开 NioSelectorPool，先忽略它\n    selectorPool.open();\n}\n```\n\n1.  ServerSocketChannel 已经打开，并且绑定要了之前指定的 8080 端口，设置成了**阻塞模式**。\n2.  设置了 acceptor 的线程数为 1\n3.  设置了 poller 的线程数，单核 CPU 为 1，多核为 2\n4.  打开了一个 SelectorPool，我们先忽略这个\n\n到这里，我们还不知道 Acceptor 和 Poller 是什么东西，我们只是设置了它们的数量，我们先来看看最后面提到的 SelectorPool。\n\n## start 过程分析\n\n刚刚我们分析完了 init() 过程，下面是启动过程 start() 分析。\n\nAbstractProtocol # start\n\n```\n@Override\npublic void start() throws Exception {\n    ...\n    // 调用 endpoint 的 start 方法\n    endpoint.start();\n\n    // Start async timeout thread\n    asyncTimeout = new AsyncTimeout();\n    Thread timeoutThread = new Thread(asyncTimeout, getNameInternal() + \"-AsyncTimeout\");\n    int priority = endpoint.getThreadPriority();\n    if (priority < Thread.MIN_PRIORITY || priority > Thread.MAX_PRIORITY) {\n        priority = Thread.NORM_PRIORITY;\n    }\n    timeoutThread.setPriority(priority);\n    timeoutThread.setDaemon(true);\n    timeoutThread.start();\n}\n```\n\nAbstractEndpoint # start\n\n```\npublic final void start() throws Exception {\n    // 按照我们的流程，刚刚 init 的时候，已经把 bindState 改为 BindState.BOUND_ON_INIT 了，\n    // 所以下面的 if 分支我们就不进去了\n    if (bindState == BindState.UNBOUND) {\n        bind();\n        bindState = BindState.BOUND_ON_START;\n    }\n    // 往里看 NioEndpoint 的实现\n    startInternal();\n}\n```\n\n下面这个方法还是比较重要的，这里会创建前面说过的 acceptor 和 poller。\n\nNioEndpoint # startInternal\n\n```\n@Override\npublic void startInternal() throws Exception {\n\n    if (!running) {\n        running = true;\n        paused = false;\n\n        // 以下几个是缓存用的，之后我们也会看到很多这样的代码，为了减少 new 很多对象出来\n        processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,\n                socketProperties.getProcessorCache());\n        eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,\n                        socketProperties.getEventCache());\n        nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,\n                socketProperties.getBufferPool());\n\n        // 创建【工作线程池】，Tomcat 自己包装了一下 ThreadPoolExecutor，\n        // 1\\. 为了在创建线程池以后，先启动 corePoolSize 个线程(这个属于线程池的知识了，不熟悉的读者可以看看我之前的文章)\n        // 2\\. 自己管理线程池的增长方式（默认 corePoolSize 10, maxPoolSize 200），不是本文重点，不分析\n        if ( getExecutor() == null ) {\n            createExecutor();\n        }\n\n        // 设置一个栅栏（tomcat 自定义了类 LimitLatch），控制最大的连接数，默认是 10000\n        initializeConnectionLatch();\n\n        // 开启 poller 线程\n        // 还记得之前 init 的时候，默认地设置了 poller 的数量为 2，所以这里启动 2 个 poller 线程\n        pollers = new Poller[getPollerThreadCount()];\n        for (int i=0; i<pollers.length; i++) {\n            pollers[i] = new Poller();\n            Thread pollerThread = new Thread(pollers[i], getName() + \"-ClientPoller-\"+i);\n            pollerThread.setPriority(threadPriority);\n            pollerThread.setDaemon(true);\n            pollerThread.start();\n        }\n\n        // 开启 acceptor 线程，和开启 poller 线程组差不多。\n        // init 的时候，默认地，acceptor 的线程数是 1\n        startAcceptorThreads();\n    }\n}\n```\n\n到这里，我们启动了**工作线程池**、**poller 线程组**、**acceptor 线程组**。同时，工作线程池初始就已经启动了 10 个线程。我们用**jconsole**来看看此时的线程，请看下图：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405105500.png)\n\n从 jconsole 中，我们可以看到，此时启动了 BlockPoller、worker、poller、acceptor、AsyncTimeout，大家应该都已经清楚了每个线程是哪里启动的吧。\n\n> Tomcat 中并没有 Worker 这个类，此名字是我瞎编。\n\n此时，我们还是不知道 acceptor、poller 甚至 worker 到底是干嘛的，下面，我们从 acceptor 线程开始看起。\n\n## Acceptor\n\n它的结构非常简单，在构造函数中，已经把 endpoint 传进来了，此外就只有 threadName 和 state 两个简单的属性。\n\n```\nprivate final AbstractEndpoint<?,U> endpoint;\nprivate String threadName;\nprotected volatile AcceptorState state = AcceptorState.NEW;\n\npublic Acceptor(AbstractEndpoint<?,U> endpoint) {\n    this.endpoint = endpoint;\n}\n```\n\n**threadName**就是一个线程名字而已，Acceptor 的状态**state**主要是随着 endpoint 来的。\n\n```\npublic enum AcceptorState {\n    NEW, RUNNING, PAUSED, ENDED\n}\n```\n\n我们直接来看 acceptor 的 run 方法吧：\n\nAcceptor # run\n\n```\n@Override\npublic void run() {\n\n    int errorDelay = 0;\n\n    // 只要 endpoint 处于 running，这里就一直循环\n    while (endpoint.isRunning()) {\n\n        // 如果 endpoint 处于 pause 状态，这边 Acceptor 用一个 while 循环将自己也挂起\n        while (endpoint.isPaused() && endpoint.isRunning()) {\n            state = AcceptorState.PAUSED;\n            try {\n                Thread.sleep(50);\n            } catch (InterruptedException e) {\n                // Ignore\n            }\n        }\n        // endpoint 结束了，Acceptor 自然也要结束嘛\n        if (!endpoint.isRunning()) {\n            break;\n        }\n        state = AcceptorState.RUNNING;\n\n        try {\n            // 如果此时达到了最大连接数(之前我们说过，默认是10000)，就等待\n            endpoint.countUpOrAwaitConnection();\n\n            // Endpoint might have been paused while waiting for latch\n            // If that is the case, don't accept new connections\n            if (endpoint.isPaused()) {\n                continue;\n            }\n\n            U socket = null;\n            try {\n                // 这里就是接收下一个进来的 SocketChannel\n                // 之前我们设置了 ServerSocketChannel 为阻塞模式，所以这边的 accept 是阻塞的\n                socket = endpoint.serverSocketAccept();\n            } catch (Exception ioe) {\n                // We didn't get a socket\n                endpoint.countDownConnection();\n                if (endpoint.isRunning()) {\n                    // Introduce delay if necessary\n                    errorDelay = handleExceptionWithDelay(errorDelay);\n                    // re-throw\n                    throw ioe;\n                } else {\n                    break;\n                }\n            }\n            // accept 成功，将 errorDelay 设置为 0\n            errorDelay = 0;\n\n            if (endpoint.isRunning() && !endpoint.isPaused()) {\n                // setSocketOptions() 是这里的关键方法，也就是说前面千辛万苦都是为了能到这里进行处理\n                if (!endpoint.setSocketOptions(socket)) {\n                    // 如果上面的方法返回 false，关闭 SocketChannel\n                    endpoint.closeSocket(socket);\n                }\n            } else {\n                // 由于 endpoint 不 running 了，或者处于 pause 了，将此 SocketChannel 关闭\n                endpoint.destroySocket(socket);\n            }\n        } catch (Throwable t) {\n            ExceptionUtils.handleThrowable(t);\n            String msg = sm.getString(\"endpoint.accept.fail\");\n            // APR specific.\n            // Could push this down but not sure it is worth the trouble.\n            if (t instanceof Error) {\n                Error e = (Error) t;\n                if (e.getError() == 233) {\n                    // Not an error on HP-UX so log as a warning\n                    // so it can be filtered out on that platform\n                    // See bug 50273\n                    log.warn(msg, t);\n                } else {\n                    log.error(msg, t);\n                }\n            } else {\n                    log.error(msg, t);\n            }\n        }\n    }\n    state = AcceptorState.ENDED;\n}\n```\n\n大家应该发现了，Acceptor 绕来绕去，都是在调用 NioEndpoint 的方法，我们简单分析一下这个。\n\n在 NioEndpoint init 的时候，我们开启了一个 ServerSocketChannel，后来 start 的时候，我们开启多个 acceptor（实际上，默认是 1 个），每个 acceptor 启动以后就开始循环调用 ServerSocketChannel 的 accept() 方法获取新的连接，然后调用 endpoint.setSocketOptions(socket) 处理新的连接，之后再进入循环 accept 下一个连接。\n\n到这里，大家应该也就知道了，为什么这个叫 acceptor 了吧？接下来，我们来看看 setSocketOptions 方法到底做了什么。\n\nNioEndpoint # setSocketOptions\n\n```\n@Override\nprotected boolean setSocketOptions(SocketChannel socket) {\n    try {\n        // 设置该 SocketChannel 为非阻塞模式\n        socket.configureBlocking(false);\n        Socket sock = socket.socket();\n        // 设置 socket 的一些属性\n        socketProperties.setProperties(sock);\n\n        // 还记得 startInternal 的时候，说过了 nioChannels 是缓存用的。\n        // 限于篇幅，这里的 NioChannel 就不展开了，它包括了 socket 和 buffer\n        NioChannel channel = nioChannels.pop();\n        if (channel == null) {\n            // 主要是创建读和写的两个 buffer，默认地，读和写 buffer 都是 8192 字节，8k\n            SocketBufferHandler bufhandler = new SocketBufferHandler(\n                    socketProperties.getAppReadBufSize(),\n                    socketProperties.getAppWriteBufSize(),\n                    socketProperties.getDirectBuffer());\n            if (isSSLEnabled()) {\n                channel = new SecureNioChannel(socket, bufhandler, selectorPool, this);\n            } else {\n                channel = new NioChannel(socket, bufhandler);\n            }\n        } else {\n            channel.setIOChannel(socket);\n            channel.reset();\n        }\n\n        // getPoller0() 会选取所有 poller 中的一个 poller\n        getPoller0().register(channel);\n    } catch (Throwable t) {\n        ExceptionUtils.handleThrowable(t);\n        try {\n            log.error(\"\",t);\n        } catch (Throwable tt) {\n            ExceptionUtils.handleThrowable(tt);\n        }\n        // Tell to close the socket\n        return false;\n    }\n    return true;\n}\n```\n\n我们看到，这里又没有进行实际的处理，而是将这个 SocketChannel**注册**到了其中一个 poller 上。因为我们知道，acceptor 应该尽可能的简单，只做 accept 的工作，简单处理下就往后面扔。acceptor 还得回到之前的循环去 accept 新的连接呢。\n\n我们只需要明白，此时，往 poller 中注册了一个 NioChannel 实例，此实例包含客户端过来的 SocketChannel 和一个 SocketBufferHandler 实例。\n\n## Poller\n\n之前我们看到 acceptor 将一个 NioChannel 实例 register 到了一个 poller 中。在看 register 方法之前，我们需要先对 poller 要有个简单的认识。\n\n```\npublic class Poller implements Runnable {\n\n    public Poller() throws IOException {\n        // 每个 poller 开启一个 Selector\n        this.selector = Selector.open();\n    }\n    private Selector selector;\n    // events 队列，此类的核心\n    private final SynchronizedQueue<PollerEvent> events =\n            new SynchronizedQueue<>();\n\n    private volatile boolean close = false;\n    private long nextExpiration = 0;//optimize expiration handling\n\n    // 这个值后面有用，记住它的初始值为 0\n    private AtomicLong wakeupCounter = new AtomicLong(0);\n\n    private volatile int keyCount = 0;\n\n    ...\n}\n```\n\n> 敲重点：每个 poller 关联了一个 Selector。\n\nPoller 内部围着一个 events 队列转，来看看其 events() 方法：\n\n```\npublic boolean events() {\n    boolean result = false;\n\n    PollerEvent pe = null;\n    for (int i = 0, size = events.size(); i < size && (pe = events.poll()) != null; i++ ) {\n        result = true;\n        try {\n            // 逐个执行 event.run()\n            pe.run();\n            // 该 PollerEvent 还得给以后用，这里 reset 一下(还是之前说过的缓存)\n            pe.reset();\n            if (running && !paused) {\n                eventCache.push(pe);\n            }\n        } catch ( Throwable x ) {\n            log.error(\"\",x);\n        }\n    }\n    return result;\n}\n```\n\nevents() 方法比较简单，就是取出当前队列中的 PollerEvent 对象，逐个执行 event.run() 方法。\n\n然后，现在来看 Poller 的 run() 方法，该方法会一直循环，直到 poller.destroy() 被调用。\n\nPoller # run\n\n```\npublic void run() {\n    while (true) {\n\n        boolean hasEvents = false;\n\n        try {\n            if (!close) {\n                // 执行 events 队列中每个 event 的 run() 方法\n                hasEvents = events();\n                // wakeupCounter 的初始值为 0，这里设置为 -1\n                if (wakeupCounter.getAndSet(-1) > 0) {\n                    //if we are here, means we have other stuff to do\n                    //do a non blocking select\n                    keyCount = selector.selectNow();\n                } else {\n                    // timeout 默认值 1 秒\n                    keyCount = selector.select(selectorTimeout);\n                }\n                wakeupCounter.set(0);\n            }\n            // 篇幅所限，我们就不说 close 的情况了\n            if (close) {\n                events();\n                timeout(0, false);\n                try {\n                    selector.close();\n                } catch (IOException ioe) {\n                    log.error(sm.getString(\"endpoint.nio.selectorCloseFail\"), ioe);\n                }\n                break;\n            }\n        } catch (Throwable x) {\n            ExceptionUtils.handleThrowable(x);\n            log.error(\"\",x);\n            continue;\n        }\n        //either we timed out or we woke up, process events first\n        // 这里没什么好说的，顶多就再执行一次 events() 方法\n        if ( keyCount == 0 ) hasEvents = (hasEvents | events());\n\n        // 如果刚刚 select 有返回 ready keys，进行处理\n        Iterator<SelectionKey> iterator =\n            keyCount > 0 ? selector.selectedKeys().iterator() : null;\n        // Walk through the collection of ready keys and dispatch\n        // any active event.\n        while (iterator != null && iterator.hasNext()) {\n            SelectionKey sk = iterator.next();\n            NioSocketWrapper attachment = (NioSocketWrapper)sk.attachment();\n            // Attachment may be null if another thread has called\n            // cancelledKey()\n            if (attachment == null) {\n                iterator.remove();\n            } else {\n                iterator.remove();\n                // ※※※※※ 处理 ready key ※※※※※\n                processKey(sk, attachment);\n            }\n        }//while\n\n        //process timeouts\n        timeout(keyCount,hasEvents);\n    }//while\n\n    getStopLatch().countDown();\n}\n```\n\npoller 的 run() 方法主要做了调用 events() 方法和处理注册到 Selector 上的 ready key，这里我们暂时不展开 processKey 方法，因为此方法必定是及其复杂的。\n\n我们回过头来看之前从 acceptor 线程中调用的 register 方法。\n\nPoller # register\n\n```\npublic void register(final NioChannel socket) {\n    socket.setPoller(this);\n    NioSocketWrapper ka = new NioSocketWrapper(socket, NioEndpoint.this);\n    socket.setSocketWrapper(ka);\n    ka.setPoller(this);\n    ka.setReadTimeout(getConnectionTimeout());\n    ka.setWriteTimeout(getConnectionTimeout());\n    ka.setKeepAliveLeft(NioEndpoint.this.getMaxKeepAliveRequests());\n    ka.setSecure(isSSLEnabled());\n\n    PollerEvent r = eventCache.pop();\n    ka.interestOps(SelectionKey.OP_READ);//this is what OP_REGISTER turns into.\n\n    // 注意第三个参数值 OP_REGISTER\n    if ( r==null) r = new PollerEvent(socket,ka,OP_REGISTER);\n    else r.reset(socket,ka,OP_REGISTER);\n\n    // 添加 event 到 poller 中\n    addEvent(r);\n}\n```\n\n这里将这个 socket（包含 socket 和 buffer 的 NioChannel 实例） 包装为一个 PollerEvent，然后添加到 events 中，此时调用此方法的 acceptor 结束返回，去处理新的 accepted 连接了。\n\n接下来，我们已经知道了，poller 线程在循环过程中会不断调用 events() 方法，那么 PollerEvent 的 run() 方法很快就会被执行，我们就来看看刚刚这个新的连接被**注册**到这个 poller 后，会发生什么。\n\nPollerEvent # run\n\n```\n@Override\npublic void run() {\n    // 对于新来的连接，前面我们说过，interestOps == OP_REGISTER\n    if (interestOps == OP_REGISTER) {\n        try {\n            // 这步很关键！！！\n            // 将这个新连接 SocketChannel 注册到该 poller 的 Selector 中，\n            // 设置监听 OP_READ 事件，\n            // 将 socketWrapper 设置为 attachment 进行传递(这个对象可是什么鬼都有，往上看就知道了)\n            socket.getIOChannel().register(\n                    socket.getPoller().getSelector(), SelectionKey.OP_READ, socketWrapper);\n        } catch (Exception x) {\n            log.error(sm.getString(\"endpoint.nio.registerFail\"), x);\n        }\n    } else {\n        /* else 这块不介绍，省得大家头大 */\n\n        final SelectionKey key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());\n        try {\n            if (key == null) {\n                // The key was cancelled (e.g. due to socket closure)\n                // and removed from the selector while it was being\n                // processed. Count down the connections at this point\n                // since it won't have been counted down when the socket\n                // closed.\n                socket.socketWrapper.getEndpoint().countDownConnection();\n            } else {\n                final NioSocketWrapper socketWrapper = (NioSocketWrapper) key.attachment();\n                if (socketWrapper != null) {\n                    //we are registering the key to start with, reset the fairness counter.\n                    int ops = key.interestOps() | interestOps;\n                    socketWrapper.interestOps(ops);\n                    key.interestOps(ops);\n                } else {\n                    socket.getPoller().cancelledKey(key);\n                }\n            }\n        } catch (CancelledKeyException ckx) {\n            try {\n                socket.getPoller().cancelledKey(key);\n            } catch (Exception ignore) {}\n        }\n    }\n}\n```\n\n到这里，我们再回顾一下：刚刚在 PollerEvent 的 run() 方法中，我们看到，新的 SocketChannel 注册到了 Poller 内部的 Selector 中，监听 OP_READ 事件，然后我们再回到 Poller 的 run() 看下，一旦该 SocketChannel 是 readable 的状态，那么就会进入到 poller 的 processKey 方法。\n\n## processKey\n\nPoller # processKey\n\n```\nprotected void processKey(SelectionKey sk, NioSocketWrapper attachment) {\n    try {\n        if ( close ) {\n            cancelledKey(sk);\n        } else if ( sk.isValid() && attachment != null ) {\n            if (sk.isReadable() || sk.isWritable() ) {\n                // 忽略 sendfile\n                if ( attachment.getSendfileData() != null ) {\n                    processSendfile(sk,attachment, false);\n                } else {\n                    // unregister 相应的 interest set，\n                    // 如接下来是处理 SocketChannel 进来的数据，那么就不再监听该 channel 的 OP_READ 事件\n                    unreg(sk, attachment, sk.readyOps());\n                    boolean closeSocket = false;\n                    // Read goes before write\n                    if (sk.isReadable()) {\n                        // 处理读\n                        if (!processSocket(attachment, SocketEvent.OPEN_READ, true)) {\n                            closeSocket = true;\n                        }\n                    }\n                    if (!closeSocket && sk.isWritable()) {\n                        // 处理写\n                        if (!processSocket(attachment, SocketEvent.OPEN_WRITE, true)) {\n                            closeSocket = true;\n                        }\n                    }\n                    if (closeSocket) {\n                        cancelledKey(sk);\n                    }\n                }\n            }\n        } else {\n            //invalid key\n            cancelledKey(sk);\n        }\n    } catch ( CancelledKeyException ckx ) {\n        cancelledKey(sk);\n    } catch (Throwable t) {\n        ExceptionUtils.handleThrowable(t);\n        log.error(\"\",t);\n    }\n}\n```\n\n接下来是 processSocket 方法，注意第三个参数，上面进来的时候是 true。\n\nAbstractEndpoint # processSocket\n\n```\npublic boolean processSocket(SocketWrapperBase<S> socketWrapper,\n        SocketEvent event, boolean dispatch) {\n    try {\n        if (socketWrapper == null) {\n            return false;\n        }\n        SocketProcessorBase<S> sc = processorCache.pop();\n        if (sc == null) {\n            // 创建一个 SocketProcessor 的实例\n            sc = createSocketProcessor(socketWrapper, event);\n        } else {\n            sc.reset(socketWrapper, event);\n        }\n        Executor executor = getExecutor();\n        if (dispatch && executor != null) {\n            // 将任务放到之前建立的 worker 线程池中执行\n            executor.execute(sc);\n        } else {\n            sc.run(); // ps: 如果 dispatch 为 false，那么就当前线程自己执行\n        }\n    } catch (RejectedExecutionException ree) {\n        getLog().warn(sm.getString(\"endpoint.executor.fail\", socketWrapper) , ree);\n        return false;\n    } catch (Throwable t) {\n        ExceptionUtils.handleThrowable(t);\n        // This means we got an OOM or similar creating a thread, or that\n        // the pool and its queue are full\n        getLog().error(sm.getString(\"endpoint.process.fail\"), t);\n        return false;\n    }\n    return true;\n}\n```\n\nNioEndpoint # createSocketProcessor\n\n```\n@Override\nprotected SocketProcessorBase<NioChannel> createSocketProcessor(\n        SocketWrapperBase<NioChannel> socketWrapper, SocketEvent event) {\n    return new SocketProcessor(socketWrapper, event);\n}\n```\n\n我们看到，提交到 worker 线程池中的是 NioEndpoint.SocketProcessor 的实例，至于它的 run() 方法之后的逻辑，我们就不再继续往里分析了。\n\n## 总结\n\n最后，再祭出文章开始的那张图来总结一下：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405105530.png)\n\n这里简单梳理下前面我们说的流程，帮大家回忆一下：\n\n1.  指定 Protocol，初始化相应的 Endpoint，我们分析的是 NioEndpoint；\n2.  init 过程：在 NioEndpoint 中做 bind 操作；\n3.  start 过程：启动 worker 线程池，启动 1 个 Acceptor 和 2 个 Poller，当然它们都是默认值，可配；\n4.  Acceptor 获取到新的连接后，getPoller0() 获取其中一个 Poller，然后 register 到 Poller 中；\n5.  Poller 循环 selector.select(xxx)，如果有通道 readable，那么在 processKey 中将其放到 worker 线程池中。\n\n后续的流程，感兴趣的读者请自行分析，本文就说到这里了。\n\n（全文完）\n"
  },
  {
    "path": "docs/Java/network/Java网络编程与NIO详解：基于NIO的网络编程框架Netty.md",
    "content": "# 目录\n\n* [目录](#目录)\n  * [Netty概述](#netty概述)\n  * [Netty简介](#netty简介)\n    * [Netty都有哪些组件？](#netty都有哪些组件？)\n    * [Netty是如何处理连接请求和业务逻辑的呢？](#netty是如何处理连接请求和业务逻辑的呢？)\n    * [如何配置一个Netty应用？](#如何配置一个netty应用？)\n    * [Netty是如何处理数据的？](#netty是如何处理数据的？)\n    * [如何处理我们的业务逻辑？](#如何处理我们的业务逻辑？)\n    * [ByteBuf](#bytebuf)\n    * [Channel](#channel)\n    * [ChannelHandler](#channelhandler)\n    * [ChannelPipeline](#channelpipeline)\n    * [EventLoop](#eventloop)\n    * [Bootstrap](#bootstrap)\n    * [Echo示例](#echo示例)\n  * [参考文献](#参考文献)\n\n\n# 目录\n  * [Netty概述](#netty概述)\n  * [etty简介](#etty简介)\n    * [Netty都有哪些组件？](#netty都有哪些组件？)\n    * [Netty是如何处理连接请求和业务逻辑的呢？](#netty是如何处理连接请求和业务逻辑的呢？)\n    * [如何配置一个Netty应用？](#如何配置一个netty应用？)\n    * [Netty是如何处理数据的？](#netty是如何处理数据的？)\n    * [如何处理我们的业务逻辑？](#如何处理我们的业务逻辑？)\n    * [ByteBuf](#bytebuf)\n    * [Channel](#channel)\n    * [ChannelHandler](#channelhandler)\n    * [ChannelPipeline](#channelpipeline)\n    * [EventLoop](#eventloop)\n    * [Bootstrap](#bootstrap)\n    * [Echo示例](#echo示例)\n  * [参考文献](#参考文献)\n\n\n本文转自：https://sylvanassun.github.io/2017/11/30/2017-11-30-netty_introduction/\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章将同步到我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《不可轻视的Java网络编程》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从计算机网络的基础知识入手，一步步地学习Java网络基础，从socket到nio、bio、aio和netty等网络编程知识，并且进行实战，网络编程是每一个Java后端工程师必须要学习和理解的知识点，进一步来说，你还需要掌握Linux中的网络编程原理，包括IO模型、网络编程框架netty的进阶原理，才能更完整地了解整个Java网络编程的知识体系，形成自己的知识框架。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供部分知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n## Netty概述\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405103849.png)\n\n**Netty是一个基于异步与事件驱动的网络应用程序框架，它支持快速与简单地开发可维护的高性能的服务器与客户端。**\n\n所谓**事件驱动就是由通过各种事件响应来决定程序的流程**，在Netty中到处都充满了异步与事件驱动，这种特点使得应用程序可以以任意的顺序响应在任意的时间点产生的事件，它带来了非常高的可伸缩性，让你的应用可以在需要处理的工作不断增长时，通过某种可行的方式或者扩大它的处理能力来适应这种增长。\n\nNetty提供了高性能与易用性，它具有以下特点：\n\n*   **拥有设计良好且统一的API**，支持NIO与OIO（阻塞IO）等多种传输类型，支持真正的无连接UDP Socket。\n\n*   **简单而强大的线程模型**，可高度定制线程（池）。(定制化的Reactor模型)\n\n*   **良好的模块化与解耦**，支持可扩展和灵活的事件模型，可以很轻松地分离关注点以复用逻辑组件（可插拔的）。\n\n*   **性能高效**，拥有比Java核心API更高的吞吐量，通过zero-copy功能以实现最少的内存复制消耗。\n\n*   **内置了许多常用的协议编解码器**，如HTTP、SSL、WebScoket等常见协议可以通过Netty做到开箱即用。用户也可以利用Netty简单方便地实现自己的应用层协议。\n\n大多数人使用Netty主要还是为了**提高应用的性能**，而高性能则离不开非阻塞IO。Netty的非阻塞IO是基于Java NIO的，并且对其进行了封装（直接使用Java NIO API在高复杂度下的应用中是一项非常繁琐且容易出错的操作，而Netty帮你封装了这些复杂操作）。\n\n## Netty简介\n\n读完这一章，我们基本上可以了解到Netty所有重要的组件，对Netty有一个全面的认识，这对下一步深入学习Netty是十分重要的，而学完这一章，我们其实已经可以用Netty解决一些常规的问题了。\n\n### Netty都有哪些组件？\n\n为了更好的理解和进一步深入Netty，我们先总体认识一下Netty用到的组件及它们在整个Netty架构中是怎么协调工作的。Netty应用中必不可少的组件：\n\n*   Bootstrap or ServerBootstrap\n\n*   EventLoop\n\n*   EventLoopGroup\n\n*   ChannelPipeline\n\n*   Channel\n\n*   Future or ChannelFuture\n\n*   ChannelInitializer\n\n*   ChannelHandler\n\n*   Bootstrap，一个Netty应用通常由一个Bootstrap开始，它主要作用是配置整个Netty程序，**串联起各个组件**。\n\n    Handler，为了支持各种协议和处理数据的方式，便诞生了Handler组件。Handler主要用来**处理各种事件**，这里的事件很广泛，比如可以是**连接、数据接收、异常、数据转换**等。\n\n    **ChannelInboundHandler**，一个最常用的Handler。这个Handler的作用就是**处理接收到数据**时的事件，也就是说，我们的业务逻辑一般就是写在这个Handler里面的，ChannelInboundHandler就是用来处理我们的**核心业务逻辑**。\n\n    ChannelInitializer，当一个链接建立时，我们需要知道**怎么来接收或者发送数据**，当然，我们有各种各样的Handler实现来处理它，那么ChannelInitializer便是用来**配置这些Handler，它会提供一个ChannelPipeline，并把Handler加入到ChannelPipeline。**\n\n    ChannelPipeline，一个Netty应用基于ChannelPipeline机制，这种机制需要**依赖于EventLoop和EventLoopGroup，因为它们三个都和事件或者事件处理相关。**\n\n    EventLoops的目的是为Channel处理IO操作，一个EventLoop可以为多个Channel服务。\n\n    EventLoopGroup会包含多个EventLoop。\n\n    Channel代表了一个Socket链接，或者其它和IO操作相关的组件，它和EventLoop一起用来参与IO处理。\n\n    Future，在Netty中所有的IO操作都是**异步的**，因此，你**不能立刻得知消息是否被正确处理**，但是我们**可以过一会等它执行完成或者直接注册一个监听**，具体的实现就是通过Future和ChannelFutures,他们可以注册一个监听，当操作执行成功或失败时监听会自动触发。总之**，所有的操作都会返回一个ChannelFuture**。\n\n### Netty是如何处理连接请求和业务逻辑的呢？\n\nChannels、Events 和 IO\n\nNetty是一个**非阻塞的、事件驱动的、网络编程**框架。当然，我们很容易理解Netty会用线程来处理IO事件，对于熟悉多线程编程的人来说，你或许会想到如何同步你的代码，但是Netty不需要我们考虑这些，具体是这样：\n\n一个Channel会对应一个EventLoop，而一个**EventLoop会对应着一个线程**，也就是说，仅有一个线程在负责一个Channel的IO操作。\n\n关于这些名词之间的关系，可以见下图：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405102033.png)\n\n如图所示：当一个连接到达，Netty会注册一个channel，然后EventLoopGroup会**分配一个EventLoop绑定到这个channel**,在这个channel的整个生命周期过程中，都会由绑定的这个EventLoop来为它服务，而这个**EventLoop就是一个线程**。\n\n说到这里，那么EventLoops和EventLoopGroups关系是如何的呢？我们前面说过一个EventLoopGroup包含多个Eventloop，但是我们看一下下面这幅图，这幅图是一个继承树，从这幅图中我们可以看出，EventLoop其实继承自EventloopGroup，也就是说，在某些情况下，我们可以把一个EventLoopGroup当做一个EventLoop来用。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405102103.png)\n### 如何配置一个Netty应用？\n\nBootsStrapping\n\n我们**利用BootsStrap来配置netty 应用**，它有两种类型，一种用于Client端：BootsStrap，另一种用于Server端：ServerBootstrap，要想区别如何使用它们，你仅需要记住一个用在Client端，一个用在Server端。下面我们来详细介绍一下这两种类型的区别：\n\n1.第一个最明显的区别是，ServerBootstrap用于Server端，通过调用**bind()**方法来绑定到一个端口监听连接；Bootstrap用于Client端，需要调用**connect**()方法来连接服务器端，但我们也可以通过调用**bind()方法返回的ChannelFuture**中获取Channel从而去connect服务器端。\n\n2.**客户端**的Bootstrap一般用**一个EventLoopGroup**，而**服务器**端的ServerBootstrap会用到**两个**（这两个也可以是同一个实例）。为何服务器端要用到两个EventLoopGroup呢？这么设计有明显的好处，如果一个ServerBootstrap有两个EventLoopGroup，那么就可以把**第一个**EventLoopGroup用来**专门负责绑定到端口监听连接事件**，而把**第二个**EventLoopGroup用来**处理每个接收到的连接**，下面我们用一幅图来展现一下这种模式：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405102127.png)\nPS: 如果仅由一个EventLoopGroup处理所有请求和连接的话，在并发量很大的情况下，这个EventLoopGroup有可能会忙于处理已经接收到的连接而不能及时处理新的连接请求，**用两个的话**，会有专门的线程来处理连接请求，不会导致请求超时的情况，大大**提高了并发处理能力**。\n\n我们知道一个Channel需要由一个EventLoop来绑定，而且两者**一旦绑定就不会再改变**。一般情况下一个EventLoopGroup中的**EventLoop数量会少于Channel数量**，那么就很**有可能出现一个多个Channel公用一个EventLoop**的情况，这就意味着如果一个Channel中的**EventLoop很忙**的话，会**影响**到这个Eventloop**对其它Channel的处理**，**这也就是为什么我们不能阻塞EventLoop的原因。**\n\n当然，我们的Server也可以只用一个EventLoopGroup,由一个实例来处理连接请求和IO事件，请看下面这幅图：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405102142.png)\n\n### Netty是如何处理数据的？\n\nNetty核心ChannelHandler\n\n下面我们来看一下netty中是怎样处理数据的，回想一下我们前面讲到的Handler，对了，就是它。说到Handler我们就不得不提ChannelPipeline，ChannelPipeline负责**安排Handler的顺序及其执行**，下面我们就来详细介绍一下他们：\n\nChannelPipeline and handlers\n\n我们的应用程序中用到的最多的应该就是ChannelHandler，我们可以这么想象，数据在一个ChannelPipeline中流动，而ChannelHandler便是其中的一个个的小阀门，这些数据都会经过每一个ChannelHandler并且被它处理。这里有一个公共接口ChannelHandler:\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405102205.png)\n\n从上图中我们可以看到，ChannelHandler有两个子类ChannelInboundHandler和ChannelOutboundHandler，这两个类对应了两个数据流向，如果数据是**从外部流入**我们的应用程序，我们就看做是**inbound**，相反便是outbound。其实ChannelHandler和Servlet有些类似，一个ChannelHandler处理完接收到的数据会传给下一个Handler，或者什么不处理，直接传递给下一个。下面我们看一下ChannelPipeline是如何安排ChannelHandler的：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405102222.png)\n\n从上图中我们可以看到，**一个ChannelPipeline可以把两种Handler（ChannelInboundHandler和ChannelOutboundHandler）混合在一起**，当一个数据流进入ChannelPipeline时，它会从ChannelPipeline头部开始传给第一个ChannelInboundHandler，当第一个处理完后再传给下一个，一直传递到管道的尾部。与之相对应的是，当数据被写出时，它会从管道的尾部开始，先经过管道尾部的“最后”一个ChannelOutboundHandler，当它处理完成后会传递给前一个ChannelOutboundHandler。\n\n**数据在各个Handler之间传递**，这需要调用方法中传递的**ChanneHandlerContext**来操作， 在netty的API中提供了两个基类分**ChannelOutboundHandlerAdapter**和**ChannelInboundHandlerAdapter**，他们**仅仅实现了调用ChanneHandlerContext来把消息传递给下一个Handler**，因为我们只关心处理数据，因此我们的程序中**可以继承这两个基类来帮助我们做这些**，而**我们仅需实现处理数据**的部分即可。\n\n我们知道InboundHandler和OutboundHandler在ChannelPipeline中是**混合在一起**的，那么它们**如何区分**彼此呢？其实很容易，因为**它们各自实现的是不同的接口**，对于inbound event，Netty会自动跳过OutboundHandler,相反若是outbound event，ChannelInboundHandler会被忽略掉。\n\n当一个ChannelHandler被加入到ChannelPipeline中时，它便会获得一个ChannelHandlerContext的引用，而**ChannelHandlerContext可以用来读写Netty中的数据流**。因此，现在可以有两种方式来发送数据，一种是把数据直接写入Channel，一种是把数据写入ChannelHandlerContext，它们的区别是写入Channel的话，数据流会从Channel的头开始传递，而如果写入ChannelHandlerContext的话，数据流会流入管道中的下一个Handler。\n\n### 如何处理我们的业务逻辑？\n\nEncoders, Decoders and Domain Logic\n\nNetty中会有很多Handler，具体是哪种Handler还要看它们继承的是InboundAdapter还是OutboundAdapter。当然，Netty中还提供了一些列的**Adapter来帮助我们简化开发**，我们知道在Channelpipeline中**每一个Handler都负责把Event传递给下一个Handler**，如果有了这些辅助Adapter，这些**额外的工作都可自动完成**，我们只需覆盖实现我们真正关心的部分即可。此外，还有一些Adapter会提供一些额外的功能，比如编码和解码。那么下面我们就来看一下其中的三种常用的ChannelHandler：\n\nEncoders和Decoders\n\n因为我们在网络传输时只能传输字节流，因此，在发送数据之前，我们必须把我们的message型转换为bytes，与之对应，我们在接收数据后，必须把接收到的bytes再转换成message。我们把bytes to message这个过程称作Decode(解码成我们可以理解的)，把message to bytes这个过程成为Encode。\n\nNetty中提供了很多现成的编码/解码器，我们一般从他们的名字中便可知道他们的用途，如**ByteToMessageDecoder**、**MessageToByteEncoder**，如专门用来处理Google Protobuf协议的**ProtobufEncoder**、**ProtobufDecoder**。\n\n我们前面说过，具体是哪种Handler就要看它们继承的是InboundAdapter还是OutboundAdapter，对于**Decoders**,很容易便可以知道它是继承自**ChannelInboundHandlerAdapter**或 ChannelInboundHandler，因为解码的意思是把ChannelPipeline**传入的bytes解码成我们可以理解的message**（即Java Object），而ChannelInboundHandler正是处理Inbound Event，而Inbound Event中传入的正是字节流。Decoder会覆盖其中的“ChannelRead()”方法，在这个方法中来调用具体的decode方法解码传递过来的字节流，然后通过调用ChannelHandlerContext.fireChannelRead(decodedMessage)方法把编码好的Message传递给下一个Handler。与之类似，Encoder就不必多少了。\n\nDomain Logic\n\n其实我们最最关心的事情就是**如何处理接收到的解码后的数据**，我们真正的业务逻辑便是处理接收到的数据。Netty提供了一个最常用的基类**SimpleChannelInboundHandler**<T>，其中**T就是这个Handler处理的数据的类型**（上一个Handler已经替我们解码好了），消息到达这个Handler时，Netty会自动调用这个Handler中的**channelRead0**(ChannelHandlerContext,T)方法，T是传递过来的数据对象，在这个方法中我们便可以任意写我们的业务逻辑了。\n\nNetty从某方面来说就是一套NIO框架，在Java NIO基础上做了封装，所以要想学好Netty我建议先理解好Java NIO，\n\nNIO可以称为New IO也可以称为Non-blocking IO，它比Java旧的阻塞IO在性能上要高效许多（如果让每一个连接中的IO操作都单独创建一个线程，那么阻塞IO并不会比NIO在性能上落后，但不可能创建无限多的线程，在连接数非常多的情况下会很糟糕）。\n\n*   ByteBuffer：NIO的数据传输是基于缓冲区的，ByteBuffer正是NIO数据传输中所使用的缓冲区抽象。**ByteBuffer支持在堆外分配内存**，并且尝试避免在执行I/O操作中的多余复制。一般的I/O操作都需要进行系统调用，这样会先切换到内核态，内核态要先从文件读取数据到它的缓冲区，只有等数据准备完毕后，才会从内核态把数据写到用户态，所谓的**阻塞IO**其实就是说的**在等待数据准备好的这段时间内进行阻塞**。如果想要避免这个额外的内核操作，可以通过使用mmap（虚拟内存映射）的方式来让用户态直接操作文件。\n\n*   Channel：它类似于(fd)文件描述符，简单地来说它**代表了一个实体**（如一个硬件设备、文件、Socket或者一个能够执行一个或多个不同的I/O操作的程序组件）。你可以从一个Channel中读取数据到缓冲区，也可以将一个缓冲区中的数据写入到Channel。\n\n*   Selector：选择器是NIO实现的关键，NIO采用的是I/O多路复用的方式来实现非阻塞，Selector通过在**一个线程中监听每个Channel的IO事件来确定有哪些已经准备好进行IO操作的Channel**，因此可**以在任何时间检查任意的读操作或写操作的完成状态**。这种方式**避免了等待IO操作准备数据时的阻塞**，使用较少的线程便可以处理许多连接，减少了线程切换与维护的开销。\n\n[![](http://wx2.sinaimg.cn/large/63503acbly1flys7n7hvaj20h90doglj.jpg)](http://wx2.sinaimg.cn/large/63503acbly1flys7n7hvaj20h90doglj.jpg)\n\n了解了NIO的实现思想之后，我觉得还很有必要了解一下Unix中的I/O模型，Unix中拥有以下5种I/O模型：\n\n*   阻塞I/O（Blocking I/O）\n\n*   非阻塞I/O（Non-blocking I/O）\n\n*   I/O多路复用（I/O multiplexing (select and poll)）\n\n*   信号驱动I/O（signal driven I/O (SIGIO)）\n\n*   异步I/O（asynchronous I/O (the POSIX aio_functions)）\n\n[![](http://wx3.sinaimg.cn/large/63503acbly1flz1e7kzblj20wb0ftq3l.jpg)](http://wx3.sinaimg.cn/large/63503acbly1flz1e7kzblj20wb0ftq3l.jpg)\n\n![](file:///C:/Users/xiaok/Pictures/%E5%A4%8D%E4%B9%A0%E6%96%87%E6%A1%A3%E5%9B%BE%E7%89%87_%E5%8B%BF%E5%88%A0/%E6%AF%94%E7%89%B9%E6%88%AA%E5%9B%BE2019-01-12-16-40-18.png?lastModify=1549709160)\n\n阻塞I/O模型是最常见的I/O模型，通常我们使用的InputStream/OutputStream都是基于阻塞I/O模型。在上图中，我们使用UDP作为例子，recvfrom()函数是UDP协议用于接收数据的函数，它需要**使用系统调用并一直阻塞到内核将数据准备好**，之后再由内核缓冲区复制数据到用户态（即是recvfrom()接收到数据），所谓**阻塞就是在等待内核准备数据的这段时间内什么也不干**。\n\n举个生活中的例子，阻塞I/O就像是你去餐厅吃饭，在等待饭做好的时间段中，你只能在餐厅中坐着干等（如果你在玩手机那么这就是非阻塞I/O了）。\n\n[![](http://wx2.sinaimg.cn/large/63503acbly1flz1e8lh7rj20wb0ft0ty.jpg)](http://wx2.sinaimg.cn/large/63503acbly1flz1e8lh7rj20wb0ft0ty.jpg)\n\n> 在非阻塞I/O模型中，内核在**数据尚未准备好**的情况下回**返回一个错误码`EWOULDBLOCK`**，而**recvfrom**并没有在失败的情况下选择阻塞休眠，而是**不断地向内核询问是否已经准备完毕**，在上图中，前三次内核都返回了`EWOULDBLOCK`，直到第四次询问时，内核数据准备完毕，然后开始将内核中缓存的数据复制到用户态。这种不断询问内核以查看某种状态是否完成的方式被称为**polling（轮询）**。\n\n非阻塞I/O就像是你在点外卖，只不过你非常心急，**每隔一段时间就要打电话问外卖小哥有没有到**。\n\n[![](http://wx3.sinaimg.cn/large/63503acbly1flz1e989dfj20wh0g80tw.jpg)](http://wx3.sinaimg.cn/large/63503acbly1flz1e989dfj20wh0g80tw.jpg)\n\n\nI/O多路复用的思想跟非阻塞I/O是一样的，只不过在**非阻塞I/O**中，是在**recvfrom的用户态（或一个线程）中去轮询内核**，这种方式会**消耗大量的CPU时间**。而**I/O多路复用**则是通过select()或poll()**系统调用来负责进行轮询**，以实现监听I/O读写事件的状态。如上图中，select监听到一个datagram可读时，就交由recvfrom去发送系统调用将内核中的数据复制到用户态。\n\n这种方式的优点很明显，通过**I/O多路复用**可以**监听多个文件描述符**，且在**内核中完成监控的任务**。但缺点是至少需要两个系统调用（select()与recvfrom()）。\n\nI/O多路复用同样适用于点外卖这个例子，只不过你在等外卖的期间完全可以做自己的事情，当外卖到的时候会**通过外卖APP或者由外卖小哥打电话来通知你**(因为内核会帮你轮询)。\n\nUnix中提供了两种I/O多路复用函数，select()和poll()。select()的兼容性更好，但它在单个进程中所能监控的文件描述符是有限的，这个值与`FD_SETSIZE`相关，32位系统中默认为1024，64位系统中为2048。select()还有一个缺点就是他轮询的方式，它采取了**线性扫描的轮询方式**，每次都要遍历FD_SETSIZE个文件描述符，不管它们是否活不活跃的。poll()本质上与select()的实现**没有区别**，不过在数据结构上区别很大，用户必须分配一个pollfd结构数组，该数组维护在内核态中，正因如此，**poll()并不像select()那样拥有大小上限的限制**，但缺点同样也很明显，**大量的fd数组会在用户态与内核态之间不断复制**，不管这样的复制是否有意义。\n\n还有一种比select()与poll()更加高效的实现叫做epoll()，它是由Linux内核2.6推出的可伸缩的I/O多路复用实现，目的是为了替代select()与poll()。epoll()同样**没有文件描述符上限的限制**，它**使用一个文件描述符来管理多个文件描述符**，并**使用一个红黑树来作为存储结构**。同时它还支持边缘触发（edge-triggered）与水平触发（level-triggered）两种模式（poll()只支持水平触发），在**边缘触发模式**下，**`epoll_wait`仅会在新的事件对象首次被加入到epoll时返回**，而在**水平触发**模式下，**`epoll_wait`会在事件状态未变更前不断地触发**。也就是说，边缘触发模式**只会**在文件描述符**变为就绪状态时通知一次**，水平触发模式会**不断地通知**该文件描述符**直到被处理**。\n\n关于`epoll_wait`请参考如下epoll API。\n````\n// 创建一个epoll对象并返回它的文件描述符。\n// 参数flags允许修改epoll的行为，它只有一个有效值EPOLL_CLOEXEC。\nint epoll_create1(int flags);\n// 配置对象，该对象负责描述监控哪些文件描述符和哪些事件。\nint epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);\n// 等待与epoll_ctl注册的任何事件，直至事件发生一次或超时。\n// 返回在events中发生的事件，最多同时返回maxevents个。\nint epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);\n````\nepoll另一亮点是**采用了事件驱动的方式而不是轮询**，在**epoll_ctl**中注册的文件描述符**在事件触发的时候会通过一个回调机制**来激活该文件描述符，**`epoll_wait`便可以收到通知**。这样效率就不会与文件描述符的数量成正比\n\n在Java NIO2（从JDK1.7开始引入）中，只要Linux内核版本在2.6以上，就会采用epoll，如下源码所示（DefaultSelectorProvider.java）。\n````\npublic static SelectorProvider create() {\nString osname = AccessController.doPrivileged(\nnew GetPropertyAction(\"os.name\"));\nif (\"SunOS\".equals(osname)) {\nreturn new sun.nio.ch.DevPollSelectorProvider();\n}\n// use EPollSelectorProvider for Linux kernels >= 2.6\nif (\"Linux\".equals(osname)) {\nString osversion = AccessController.doPrivileged(\nnew GetPropertyAction(\"os.version\"));\nString[] vers = osversion.split(\"\\\\.\", 0);\nif (vers.length >= 2) {\ntry {\nint major = Integer.parseInt(vers[0]);\nint minor = Integer.parseInt(vers[1]);\nif (major > 2 || (major == 2 && minor >= 6)) {\nreturn new sun.nio.ch.EPollSelectorProvider();\n}\n} catch (NumberFormatException x) {\n// format not recognized\n}\n}\n}\nreturn new sun.nio.ch.PollSelectorProvider();\n}\n````\n\n[![](http://wx3.sinaimg.cn/large/63503acbly1flz1e9uk8aj20wb0ft3zn.jpg)](http://wx3.sinaimg.cn/large/63503acbly1flz1e9uk8aj20wb0ft3zn.jpg)\n\n信号驱动I/O模型使用到了**信号**，内核在数据**准备就绪**时会**通过信号来进行通知**。我们首先**开启**了一个信号驱动I/O套接字，并使用sigaction系统调用来安装信号处理程序，内核直接返回，不会阻塞用户态。当datagram准备好时，内核会发送SIGIN信号，recvfrom接收到信号后会发送系统调用开始进行I/O操作。\n\n这种模型的优点是**主进程（线程）不会被阻塞**，当数据准备就绪时，**通过信号处理程序**来通知主进程（线程）准备进行I/O操作与对数据的处理。\n\n[![](http://wx2.sinaimg.cn/large/63503acbly1flz1eai66rj20wb0g8aau.jpg)](http://wx2.sinaimg.cn/large/63503acbly1flz1eai66rj20wb0g8aau.jpg)\n\n我们之前讨论的各种I/O模型无论是阻塞还是非阻塞，它们所说的**阻塞都是指的数据准备阶段**。**异步I/O**模型**同样依赖**于**信号**处理程序来进行通知，但与以上I/O模型都不相同的是，异步I/O模型通知的是**I/O操作**已经完成，而不是**数据准备**完成。\n\n可以说**异步I/O模型才是真正的非阻塞**，主进程只管做自己的事情，然后在I/O操作完成时调用回调函数来完成一些对数据的处理操作即可。\n\n闲扯了这么多，想必大家已经对I/O模型有了一个深刻的认识。之后，我们将会结合部分源码（Netty4.X）来探讨Netty中的各大核心组件，以及如何使用Netty，你会发现实现一个Netty程序是多么简单（而且还伴随了高性能与可维护性）。\n\n\n![](https://netty.io/images/components.png)\n\n### ByteBuf\n\n\n\n* * *\n\n\n\n网络传输的基本单位是字节，在Java NIO中提供了ByteBuffer作为字节缓冲区容器，但该类的API使用起来不太方便，所以Netty实现了ByteBuf作为其替代品，下面是使用ByteBuf的优点：\n\n*   相比ByteBuffer使用起来**更加简单**。\n\n*   通过内置的复合缓冲区类型实现了透明的**zero-copy**。\n\n*   **容量**可以**按需增长**。\n\n*   **读和写**使用了**不同的索引指针**。\n\n*   支持**链式调用**。\n\n*   支持**引用计数与池化**。\n\n*   可以被用户**自定义的缓冲区类型**扩展。\n\n在讨论ByteBuf之前，我们先需要了解一下ByteBuffer的实现，这样才能比较深刻地明白它们之间的区别。\n\nByteBuffer继承于`abstract class Buffer`（所以还有LongBuffer、IntBuffer等其他类型的实现），本质上它只是一个有限的线性的元素序列，包含了三个重要的属性。\n\n*   Capacity：缓冲区中元素的容量大小，你只能将capacity个数量的元素写入缓冲区，一旦缓冲区已满就需要清理缓冲区才能继续写数据。\n\n*   Position：指向下一个写入数据位置的索引指针，初始位置为0，最大为capacity-1。当写模式转换为读模式时，position需要被重置为0。\n\n*   Limit：在写模式中，limit是可以写入缓冲区的最大索引，也就是说它在写模式中等价于缓冲区的容量。在读模式中，limit表示可以读取数据的最大索引。\n\n[![](http://tutorials.jenkov.com/images/java-nio/buffers-modes.png)](http://tutorials.jenkov.com/images/java-nio/buffers-modes.png)\n\n由于Buffer中只维护了position一个索引指针，所以它在读写模式之间的切换需要调用一个flip()方法来重置指针。使用Buffer的流程一般如下：\n\n*   写入数据到缓冲区。\n\n*   调用flip()方法。\n\n*   从缓冲区中读取数据\n\n*   调用buffer.clear()或者buffer.compact()清理缓冲区，以便下次写入数据。\n\nRandomAccessFile aFile = new RandomAccessFile(\"data/nio-data.txt\", \"rw\");\nFileChannel inChannel = aFile.getChannel();\n// 分配一个48字节大小的缓冲区\nByteBuffer buf = ByteBuffer.allocate(48);\nint bytesRead = inChannel.read(buf); // 读取数据到缓冲区\nwhile (bytesRead != -1) {\nbuf.flip(); // 将position重置为0\nwhile(buf.hasRemaining()){\nSystem.out.print((char) buf.get()); // 读取数据并输出到控制台\n}\nbuf.clear(); // 清理缓冲区\nbytesRead = inChannel.read(buf);\n}\naFile.close();\nBuffer中核心方法的实现也非常简单，主要就是在操作指针position。\n\nBuffer中核心方法的实现也非常简单，主要就是在操作指针position。\n\n````\n/**\n* Sets this buffer's mark at its position.\n*\n* @return This buffer\n*/\npublic final Buffer mark() {\nmark = position; // mark属性是用来标记当前索引位置的\nreturn this;\n}\n// 将当前索引位置重置为mark所标记的位置\npublic final Buffer reset() {\nint m = mark;\nif (m < 0)\nthrow new InvalidMarkException();\nposition = m;\nreturn this;\n}\n// 翻转这个Buffer，将limit设置为当前索引位置，然后再把position重置为0\npublic final Buffer flip() {\nlimit = position;\nposition = 0;\nmark = -1;\nreturn this;\n}\n// 清理缓冲区\n// 说是清理,也只是把postion与limit进行重置,之后再写入数据就会覆盖之前的数据了\npublic final Buffer clear() {\nposition = 0;\nlimit = capacity;\nmark = -1;\nreturn this;\n}\n// 返回剩余空间\npublic final int remaining() {\nreturn limit - position;\n}\n````\n\nJava NIO中的**Buffer API操作的麻烦之处就在于读写转换需要手动重置指针。而ByteBuf没有这种繁琐性，它维护了两个不同的索引，一个用于读取，一个用于写入**。当你从ByteBuf读取数据时，它的readerIndex将会被递增已经被读取的字节数，同样的，当你写入数据时，writerIndex则会递增。readerIndex的最大范围在writerIndex的所在位置，如果试图移动readerIndex超过该值则会触发异常。\n\nByteBuf中名称以read或write开头的方法将会递增它们其对应的索引，而名称以get或set开头的方法则不会。ByteBuf同样可以指定一个最大容量，试图移动writerIndex超过该值则会触发异常。\n````\npublic byte readByte() {\n this.checkReadableBytes0(1); // 检查readerIndex是否已越界\n int i = this.readerIndex;\n byte b = this._getByte(i);\n this.readerIndex = i + 1; // 递增readerIndex\n return b;\n}\nprivate void checkReadableBytes0(int minimumReadableBytes) {\n this.ensureAccessible();\n if(this.readerIndex > this.writerIndex - minimumReadableBytes) {\n throw new IndexOutOfBoundsException(String.format(\"readerIndex(%d) + length(%d) exceeds writerIndex(%d): %s\", new Object[]{Integer.valueOf(this.readerIndex), Integer.valueOf(minimumReadableBytes), Integer.valueOf(this.writerIndex), this}));\n }\n}\npublic ByteBuf writeByte(int value) {\n this.ensureAccessible();\n this.ensureWritable0(1); // 检查writerIndex是否会越过capacity\n this._setByte(this.writerIndex++, value);\n return this;\n}\nprivate void ensureWritable0(int minWritableBytes) {\n if(minWritableBytes > this.writableBytes()) {\n if(minWritableBytes > this.maxCapacity - this.writerIndex) {\n throw new IndexOutOfBoundsException(String.format(\"writerIndex(%d) + minWritableBytes(%d) exceeds maxCapacity(%d): %s\", new Object[]{Integer.valueOf(this.writerIndex), Integer.valueOf(minWritableBytes), Integer.valueOf(this.maxCapacity), this}));\n } else {\n int newCapacity = this.alloc().calculateNewCapacity(this.writerIndex + minWritableBytes, this.maxCapacity);\n this.capacity(newCapacity);\n }\n }\n}\n// get与set只对传入的索引进行了检查，然后对其位置进行get或set\npublic byte getByte(int index) {\n this.checkIndex(index);\n return this._getByte(index);\n}\npublic ByteBuf setByte(int index, int value) {\n this.checkIndex(index);\n this._setByte(index, value);\n return this;\n}\n````\nByteBuf同样支持在**堆内和堆外进行分配**。在**堆内分配**也被称为**支撑数组模式**，它能在**没有使用池化**的情况下**提供快速的分配和释放**。\n````\nByteBuf heapBuf = Unpooled.copiedBuffer(bytes);\nif (heapBuf.hasArray()) { // 判断是否有一个支撑数组\nbyte[] array = heapBuf.array();\n// 计算第一个字节的偏移量\nint offset = heapBuf.arrayOffset() + heapBuf.readerIndex();\nint length = heapBuf.readableBytes(); // 获得可读字节\nhandleArray(array,offset,length); // 调用你的处理方法\n}\n````\n\n另一种模式为**堆外分配**，Java NIO ByteBuffer类在JDK1.4时就已经允许JVM实现通过JNI调用来在堆外分配内存（调用malloc()函数在JVM堆外分配内存），这主要是**为了避免额外的缓冲区复制操作**。\n````\nByteBuf directBuf = Unpooled.directBuffer(capacity);\nif (!directBuf.hasArray()) {\nint length = directBuf.readableBytes();\nbyte[] array = new byte[length];\n// 将字节复制到数组中\ndirectBuf.getBytes(directBuf.readerIndex(),array);\nhandleArray(array,0,length);\n}\n````\n\nByteBuf还支持第三种模式，它被称为**复合缓冲区**，为多个ByteBuf**提供**了一个**聚合视图**。在这个视图中，你可以根据需要添加或者删除ByteBuf实例，ByteBuf的子类**CompositeByteBuf实现了该模式**。\n\n一个适合使用**复合缓冲区的场景是HTTP协议**，通过HTTP协议传输的消息都会被分成两部分——头部和主体，如果这两部分由应用程序的不同模块产生，将在消息发送时进行组装，并且该应用程序还会为多个消息复用相同的消息主体，这样对于每个消息都将会创建一个新的头部，产生了很多不必要的内存操作。使用CompositeByteBuf是一个很好的选择，它消除了这些额外的复制，以帮助你复用这些消息。\n````\nCompositeByteBuf messageBuf = Unpooled.compositeBuffer();\nByteBuf headerBuf = ....;\nByteBuf bodyBuf = ....;\nmessageBuf.addComponents(headerBuf,bodyBuf);\nfor (ByteBuf buf : messageBuf) {\nSystem.out.println(buf.toString());\n}\n````\n\nCompositeByteBuf透明的实现了**zero-copy**，zero-copy其实就是避免数据在两个内存区域中来回的复制。从操作系统层面上来讲，zero-copy指的是**避免在内核态与用户态之间的数据缓冲区复制（通过mmap避免）**，而Netty中的zero-copy更偏向于在用户态中的数据操作的优化，就像使用CompositeByteBuf来复用多个ByteBuf以避免额外的复制，也可以使用wrap()方法来将一个字节数组包装成ByteBuf，又或者使用ByteBuf的slice()方法把它分割为多个共享同一内存区域的ByteBuf，这些都是为了优化内存的使用率。\n\n那么如何创建ByteBuf呢？在上面的代码中使用到了**Unpooled**，它是Netty提供的一个用于创建与分配ByteBuf的工具类，建议都使用这个工具类来创建你的缓冲区，不要自己去调用构造函数。经常使用的是wrappedBuffer()与copiedBuffer()，它们一个是用于将一个字节数组或ByteBuffer包装为一个ByteBuf，一个是根据传入的字节数组与ByteBuffer/ByteBuf来复制出一个新的ByteBuf。\n````\n// 通过array.clone()来复制一个数组进行包装\npublic static ByteBuf copiedBuffer(byte[] array) {\nreturn array.length == 0?EMPTY_BUFFER:wrappedBuffer((byte[])array.clone());\n}\n// 默认是堆内分配\npublic static ByteBuf wrappedBuffer(byte[] array) {\nreturn (ByteBuf)(array.length == 0?EMPTY_BUFFER:new UnpooledHeapByteBuf(ALLOC, array, array.length));\n}\n// 也提供了堆外分配的方法\nprivate static final ByteBufAllocator ALLOC;\npublic static ByteBuf directBuffer(int initialCapacity) {\nreturn ALLOC.directBuffer(initialCapacity);\n}\n````\n\n相对底层的分配方法是使用ByteBufAllocator，Netty实现了PooledByteBufAllocator和UnpooledByteBufAllocator，前者使用了[jemalloc（一种malloc()的实现）](https://github.com/jemalloc/jemalloc)来分配内存，并且实现了对ByteBuf的池化以提高性能。后者分配的是未池化的ByteBuf，其分配方式与之前讲的一致。\n````\nChannel channel = ...;\nByteBufAllocator allocator = channel.alloc();\nByteBuf buffer = allocator.directBuffer();\ndo something.......\n````\n\n为了优化内存使用率，**Netty提供了一套手动的方式来追踪不活跃对象**，像UnpooledHeapByteBuf这种分配在堆内的对象得益于JVM的GC管理，无需额外操心，而UnpooledDirectByteBuf是在堆外分配的，它的内部基于DirectByteBuffer，DirectByteBuffer会先向Bits类申请一个额度（Bits还拥有一个全局变量totalCapacity，记录了所有DirectByteBuffer总大小），每次申请前都会查看是否已经超过-XX:MaxDirectMemorySize所设置的上限，**如果超限就会尝试调用System.gc()**，**以试图回收一部分内存，然后休眠100毫秒，如果内存还是不足，则只能抛出OOM异常**。堆外内存的回收虽然有了这么一层保障，但为了提高性能与使用率，主动回收也是很有必要的。由于Netty还实现了ByteBuf的池化，像PooledHeapByteBuf和PooledDirectByteBuf就必须**依赖于手动的方式来进行回收**（放回池中）。\n\nNetty使用了**引用计数器的方式来追踪那些不活跃的对象**。引用计数的接口为**ReferenceCounted**，它的思想很简单，只要ByteBuf对象的**引用计数大于0**，就保证该对象**不会被释放回收**，可以通过**手动调用release()与retain()**方法来操作该对象的引用计数值**递减或递增**。用户也可以通过自定义一个ReferenceCounted的实现类，以满足自定义的规则。\n````\npackage io.netty.buffer;\npublic abstract class AbstractReferenceCountedByteBuf extends AbstractByteBuf {\n// 由于ByteBuf的实例对象会非常多,所以这里没有将refCnt包装为AtomicInteger\n// 而是使用一个全局的AtomicIntegerFieldUpdater来负责操作refCnt\nprivate static final AtomicIntegerFieldUpdater<AbstractReferenceCountedByteBuf> refCntUpdater = AtomicIntegerFieldUpdater.newUpdater(AbstractReferenceCountedByteBuf.class, \"refCnt\");\n// 每个ByteBuf的初始引用值都为1\nprivate volatile int refCnt = 1;\npublic int refCnt() {\nreturn this.refCnt;\n}\nprotected final void setRefCnt(int refCnt) {\nthis.refCnt = refCnt;\n}\npublic ByteBuf retain() {\nreturn this.retain0(1);\n}\n// 引用计数值递增increment，increment必须大于0\npublic ByteBuf retain(int increment) {\nreturn this.retain0(ObjectUtil.checkPositive(increment, \"increment\"));\n}\npublic static int checkPositive(int i, String name) {\nif(i <= 0) {\nthrow new IllegalArgumentException(name + \": \" + i + \" (expected: > 0)\");\n} else {\nreturn i;\n} \n}\n// 使用CAS操作不断尝试更新值\nprivate ByteBuf retain0(int increment) {\nint refCnt;\nint nextCnt;\ndo {\nrefCnt = this.refCnt;\nnextCnt = refCnt + increment;\nif(nextCnt <= increment) {\nthrow new IllegalReferenceCountException(refCnt, increment);\n}\n} while(!refCntUpdater.compareAndSet(this, refCnt, nextCnt));\nreturn this;\n}\npublic boolean release() {\nreturn this.release0(1);\n}\npublic boolean release(int decrement) {\nreturn this.release0(ObjectUtil.checkPositive(decrement, \"decrement\"));\n}\nprivate boolean release0(int decrement) {\nint refCnt;\ndo {\nrefCnt = this.refCnt;\nif(refCnt < decrement) {\nthrow new IllegalReferenceCountException(refCnt, -decrement);\n}\n} while(!refCntUpdater.compareAndSet(this, refCnt, refCnt - decrement));\nif(refCnt == decrement) {\nthis.deallocate();\nreturn true;\n} else {\nreturn false;\n}\n}\nprotected abstract void deallocate();\n}\n````\n\n### Channel\n\n\n\n* * *\n\n\n\nNetty中的Channel与Java NIO的概念一样，都是对**一个实体或连接的抽象**，但**Netty提供了一套更加通用的API**。就以网络套接字为例，在Java中OIO与NIO是截然不同的两套API，假设你之前使用的是OIO而又想更改为NIO实现，那么几乎需要重写所有代码。而在Netty中，只需要更改短短几行代码（更改Channel与EventLoop的实现类，如把OioServerSocketChannel替换为NioServerSocketChannel），就可以完成OIO与NIO（或其他）之间的转换。\n\n[![](http://wx2.sinaimg.cn/large/63503acbly1fm103i127ej20xe0f074y.jpg)](http://wx2.sinaimg.cn/large/63503acbly1fm103i127ej20xe0f074y.jpg)\n\n每个Channel最终都会被分配一个**ChannelPipeline**和**ChannelConfig**，前者持有所有负责**处理入站与出站数据**以及**事件**的ChannelHandler，后者包含了该Channel的**所有配置设置**，并且**支持热更新**，由于不同的传输类型可能具有其特别的配置，所以该类可能会实现为ChannelConfig的不同子类。\n\n**Channel是线程安全的**（与之后要讲的线程模型有关），因此你完全可以在多个线程中复用同一个Channel，就像如下代码所示。\n````\nfinal Channel channel = ...\nfinal ByteBuf buffer = Unpooled.copiedBuffer(\"Hello,World!\", CharsetUtil.UTF_8).retain();\nRunnable writer = new Runnable() {\n@Override\npublic void run() {\nchannel.writeAndFlush(buffer.duplicate());\n}\n};\nExecutor executor = Executors.newCachedThreadPool();\nexecutor.execute(writer);\nexecutor.execute(writer);\n````\n\nNetty除了支持常见的NIO与OIO，还内置了其他的传输类型。\n\n| Nmae | Package | Description |\n| --- | --- | --- |\n| NIO | io.netty.channel.socket.nio | 以Java NIO为基础实现 |\n| OIO | io.netty.channel.socket.oio | 以java.net为基础实现，使用阻塞I/O模型 |\n| Epoll | io.netty.channel.epoll | 由JNI驱动epoll()实现的更高性能的非阻塞I/O，它**只能使用在Linux** |\n| Local | io.netty.channel.local | **本地传输**，在JVM内部通过**管道**进行通信 |\n| Embedded | io.netty.channel.embedded | 允许在不需要真实网络传输的环境下使用ChannelHandler，主要用于对ChannelHandler进行**测试** |\n\nNIO、OIO、Epoll我们应该已经很熟悉了，下面主要说说Local与Embedded。\n\nLocal传输用于在**同一个JVM中**运行的客户端和服务器程序之间的**异步通信**，与服务器Channel相关联的SocketAddress并没有绑定真正的物理网络地址，它会被存储在注册表中，并在Channel关闭时注销。因此Local传输不会接受真正的网络流量，也就是说它不能与其他传输实现进行互操作。\n\nEmbedded传输主要用于对ChannelHandler进行**单元测试**，ChannelHandler是用于**处理消息的逻辑组件**，Netty通过将入站消息与出站消息都写入到EmbeddedChannel中的方式（提供了write/readInbound()与write/readOutbound()来读写入站与出站消息）来实现对ChannelHandler的单元测试。\n\n### ChannelHandler\n\n\n\n* * *\n\n\n\nChannelHandler充当了处理**入站**和**出站**数据的应用程序**逻辑的容器**，该类是基于**事件驱动**的，它会**响应相关的事件**然后去**调用其关联的回调函数**，例如当一个新的连接**被建立**时，ChannelHandler的**channelActive**()方法将**会被调用**。\n\n关于入站消息和出站消息的数据流向定义，如果以客户端为主视角来说的话，那么从**客户端**流向**服务器**的数据被称为**出站**，反之为入站。\n\n入站事件是可能被**入站数据或者相关的状态更改而触发的事件**，包括：连接已被激活、连接失活、读取入站数据、用户事件、发生异常等。\n\n出站事件是**未来将会触发的某个动作的结果的事件**，这些动作包括：打开或关闭远程节点的连接、将数据写（或冲刷）到套接字。\n\nChannelHandler的主要用途包括：\n\n*   对**入站与出站数据**的业务**逻辑处理**\n\n*   **记录日志**\n\n*   **将数据从一种格式转换为另一种格式**，实现编解码器。以一次HTTP协议（或者其他应用层协议）的流程为例，数据在网络传输时的单位为字节，当客户端发送请求到服务器时，服务器需要通过解码器（处理入站消息）将字节解码为协议的消息内容，服务器在发送响应的时候（处理出站消息），还需要通过编码器将消息内容编码为字节。\n\n*   **捕获异常**\n\n*   **提供Channel生命周期内的通知**，如Channel活动时与非活动时\n\nNetty中到处都充满了异步与事件驱动，而**回调函数**正是用于**响应事件之后的操作**。由于异步会直接返回一个结果，所以Netty提供了ChannelFuture（实现了java.util.concurrent.Future）来作为异步调用返回的占位符，真正的结果会在未来的某个时刻完成，到时候就可以通过ChannelFuture对其进行访问，每个Netty的出站I/O操作都将会返回一个ChannelFuture。\n\nNetty还提供了**ChannelFutureListener**接口来**监听ChannelFuture**是否成功，并采取对应的操作。\n````\nChannel channel = ...\nChannelFuture future = channel.connect(new InetSocketAddress(\"192.168.0.1\",6666));\n// 注册一个监听器\nfuture.addListener(new ChannelFutureListener() {\n@Override\npublic void operationComplete(ChannelFuture future) {\nif (future.isSuccess()) {\n// do something....\n} else {\n// 输出错误信息\nThrowable cause = future.cause();\ncause.printStackTrace();\n// do something....\n}\n}\n});\n````\n\nChannelFutureListener接口中还提供了几个简单的默认实现，方便我们使用。\n````\npackage io.netty.channel;\nimport io.netty.channel.ChannelFuture;\nimport io.netty.util.concurrent.GenericFutureListener;\npublic interface ChannelFutureListener extends GenericFutureListener<ChannelFuture> {\n// 在Future完成时关闭\nChannelFutureListener CLOSE = new ChannelFutureListener() {\npublic void operationComplete(ChannelFuture future) {\nfuture.channel().close();\n}\n};\n// 如果失败则关闭\nChannelFutureListener CLOSE_ON_FAILURE = new ChannelFutureListener() {\npublic void operationComplete(ChannelFuture future) {\nif(!future.isSuccess()) {\nfuture.channel().close();\n}\n}\n};\n// 将异常信息传递给下一个ChannelHandler\nChannelFutureListener FIRE_EXCEPTION_ON_FAILURE = new ChannelFutureListener() {\npublic void operationComplete(ChannelFuture future) {\nif(!future.isSuccess()) {\nfuture.channel().pipeline().fireExceptionCaught(future.cause());\n}\n}\n};\n}\n````\n\nChannelHandler接口**定义了对它生命周期进行监听的回调函数**，在ChannelHandler被添加到ChannelPipeline或者被移除时都会调用这些函数。\n````\npackage io.netty.channel;\npublic interface ChannelHandler {\nvoid handlerAdded(ChannelHandlerContext var1) throws Exception;\nvoid handlerRemoved(ChannelHandlerContext var1) throws Exception;\n/** @deprecated */\n@Deprecated\nvoid exceptionCaught(ChannelHandlerContext var1, Throwable var2) throws Exception;\n// 该注解表明这个ChannelHandler可被其他线程复用\n@Inherited\n@Documented\n@Target({ElementType.TYPE})\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface Sharable {\n}\n}\n````\n\n\n**入站消息与出站消息**由其对应的接口**ChannelInboundHandler与ChannelOutboundHandle**r负责，这两个接口定义了监听Channel的**生命周期的状态改变事件**的回调函数。\n````\npackage io.netty.channel;\nimport io.netty.channel.ChannelHandler;\nimport io.netty.channel.ChannelHandlerContext;\npublic interface ChannelInboundHandler extends ChannelHandler {\n// 当channel被注册到EventLoop时被调用\nvoid channelRegistered(ChannelHandlerContext var1) throws Exception;\n// 当channel已经被创建，但还未注册到EventLoop（或者从EventLoop中注销）被调用\nvoid channelUnregistered(ChannelHandlerContext var1) throws Exception;\n// 当channel处于活动状态（连接到远程节点）被调用\nvoid channelActive(ChannelHandlerContext var1) throws Exception;\n// 当channel处于非活动状态（没有连接到远程节点）被调用\nvoid channelInactive(ChannelHandlerContext var1) throws Exception;\n// 当从channel读取数据时被调用\nvoid channelRead(ChannelHandlerContext var1, Object var2) throws Exception;\n// 当channel的上一个读操作完成时被调用\nvoid channelReadComplete(ChannelHandlerContext var1) throws Exception;\n// 当ChannelInboundHandler.fireUserEventTriggered()方法被调用时被调用\nvoid userEventTriggered(ChannelHandlerContext var1, Object var2) throws Exception;\n// 当channel的可写状态发生改变时被调用\nvoid channelWritabilityChanged(ChannelHandlerContext var1) throws Exception;\n// 当处理过程中发生异常时被调用\nvoid exceptionCaught(ChannelHandlerContext var1, Throwable var2) throws Exception;\n}\n````\n````\npackage io.netty.channel;\nimport io.netty.channel.ChannelHandler;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelPromise;\nimport java.net.SocketAddress;\npublic interface ChannelOutboundHandler extends ChannelHandler {\n// 当请求将Channel绑定到一个地址时被调用\n// ChannelPromise是ChannelFuture的一个子接口，定义了如setSuccess(),setFailure()等方法\nvoid bind(ChannelHandlerContext var1, SocketAddress var2, ChannelPromise var3) throws Exception;\n// 当请求将Channel连接到远程节点时被调用\nvoid connect(ChannelHandlerContext var1, SocketAddress var2, SocketAddress var3, ChannelPromise var4) throws Exception;\n// 当请求将Channel从远程节点断开时被调用\nvoid disconnect(ChannelHandlerContext var1, ChannelPromise var2) throws Exception;\n// 当请求关闭Channel时被调用\nvoid close(ChannelHandlerContext var1, ChannelPromise var2) throws Exception;\n// 当请求将Channel从它的EventLoop中注销时被调用\nvoid deregister(ChannelHandlerContext var1, ChannelPromise var2) throws Exception;\n// 当请求从Channel读取数据时被调用\nvoid read(ChannelHandlerContext var1) throws Exception;\n// 当请求通过Channel将数据写到远程节点时被调用\nvoid write(ChannelHandlerContext var1, Object var2, ChannelPromise var3) throws Exception;\n// 当请求通过Channel将缓冲中的数据冲刷到远程节点时被调用\nvoid flush(ChannelHandlerContext var1) throws Exception;\n}\n````\n\n通过实现ChannelInboundHandler或者ChannelOutboundHandler就可以完成用户自定义的应用逻辑处理程序，不过Netty已经帮你实**现了一些基本操作，用户只需要继承并扩展ChannelInboundHandlerAdapter或ChannelOutboundHandlerAdapter**来作为自定义实现的起始点。\n\nChannelInboundHandlerAdapter与ChannelOutboundHandlerAdapter都继承于ChannelHandlerAdapter，该抽象类简单实现了ChannelHandler接口。\n````\npublic abstract class ChannelHandlerAdapter implements ChannelHandler {\nboolean added;\npublic ChannelHandlerAdapter() {\n}\n// 该方法不允许将此ChannelHandler共享复用\nprotected void ensureNotSharable() {\nif(this.isSharable()) {\nthrow new IllegalStateException(\"ChannelHandler \" + this.getClass().getName() + \" is not allowed to be shared\");\n}\n}\n// 使用反射判断实现类有没有@Sharable注解，以确认该类是否为可共享复用的\npublic boolean isSharable() {\nClass clazz = this.getClass();\nMap cache = InternalThreadLocalMap.get().handlerSharableCache();\nBoolean sharable = (Boolean)cache.get(clazz);\nif(sharable == null) {\nsharable = Boolean.valueOf(clazz.isAnnotationPresent(Sharable.class));\ncache.put(clazz, sharable);\n}\nreturn sharable.booleanValue();\n}\npublic void handlerAdded(ChannelHandlerContext ctx) throws Exception {\n}\npublic void handlerRemoved(ChannelHandlerContext ctx) throws Exception {\n}\npublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {\nctx.fireExceptionCaught(cause);\n}\n}\n````\n\nChannelInboundHandlerAdapter与ChannelOutboundHandlerAdapter**默认只是简单地将请求传递给ChannelPipeline中的下一个ChannelHandler**，源码如下：\n````\npublic class ChannelInboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelInboundHandler {\npublic ChannelInboundHandlerAdapter() {\n}\npublic void channelRegistered(ChannelHandlerContext ctx) throws Exception {\nctx.fireChannelRegistered();\n}\npublic void channelUnregistered(ChannelHandlerContext ctx) throws Exception {\nctx.fireChannelUnregistered();\n}\npublic void channelActive(ChannelHandlerContext ctx) throws Exception {\nctx.fireChannelActive();\n}\npublic void channelInactive(ChannelHandlerContext ctx) throws Exception {\nctx.fireChannelInactive();\n}\npublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\nctx.fireChannelRead(msg);\n}\npublic void channelReadComplete(ChannelHandlerContext ctx) throws Exception {\nctx.fireChannelReadComplete();\n}\npublic void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {\nctx.fireUserEventTriggered(evt);\n}\npublic void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {\nctx.fireChannelWritabilityChanged();\n}\npublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {\nctx.fireExceptionCaught(cause);\n}\n}\npublic class ChannelOutboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelOutboundHandler {\npublic ChannelOutboundHandlerAdapter() {\n}\npublic void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception {\nctx.bind(localAddress, promise);\n}\npublic void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) throws Exception {\nctx.connect(remoteAddress, localAddress, promise);\n}\npublic void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {\nctx.disconnect(promise);\n}\npublic void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {\nctx.close(promise);\n}\npublic void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {\nctx.deregister(promise);\n}\npublic void read(ChannelHandlerContext ctx) throws Exception {\nctx.read();\n}\npublic void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {\nctx.write(msg, promise);\n}\npublic void flush(ChannelHandlerContext ctx) throws Exception {\nctx.flush();\n}\n}\n````\n\n对于处理入站消息，另外一种选择是**继承SimpleChannelInboundHandler**，它是Netty的一个继承于ChannelInboundHandlerAdapter的抽象类，并在其之上实现了**自动释放资源的功能**。\n\n我们在了解ByteBuf时就已经知道了**Netty使用了一套自己实现的引用计数算法来主动释放资源**，假设你的ChannelHandler继承于ChannelInboundHandlerAdapter或ChannelOutboundHandlerAdapter，那么你就有责任去管理你所分配的ByteBuf，一般来说，一个消息对象（**ByteBuf**）已经被消费（或丢弃）了，**并不会传递给ChannelHandler链中的下一个处理器**（如果该消息到达了实际的传输层，那么当它被写入或Channel关闭时，都会被自动释放），所以你就需要去手动释放它。通过一个简单的工具类**ReferenceCountUtil的release方法**，就可以做到这一点。\n````\n// 这个泛型为消息对象的类型\npublic abstract class SimpleChannelInboundHandler<I> extends ChannelInboundHandlerAdapter {\nprivate final TypeParameterMatcher matcher;\nprivate final boolean autoRelease;\nprotected SimpleChannelInboundHandler() {\n\tthis(true);\n}\n\nprotected SimpleChannelInboundHandler(boolean autoRelease) {\n        this.matcher = TypeParameterMatcher.find(this, SimpleChannelInboundHandler.class, \"I\");\n\t    this.autoRelease = autoRelease;\n}\n\nprotected SimpleChannelInboundHandler(Class<? extends I> inboundMessageType) {\n\t\tthis(inboundMessageType, true);\n}\n\nprotected SimpleChannelInboundHandler(Class<? extends I> inboundMessageType, boolean autoRelease) {\n            this.matcher = TypeParameterMatcher.get(inboundMessageType);\n            this.autoRelease = autoRelease;\n}\n\npublic boolean acceptInboundMessage(Object msg) throws Exception {\n\t\treturn this.matcher.match(msg);\n}\n\n// SimpleChannelInboundHandler只是替你做了ReferenceCountUtil.release()\npublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\nboolean release = true;\ntry {\n    if(this.acceptInboundMessage(msg)) {\n  \t\t   this.channelRead0(ctx, msg);\n    } else {\n            release = false;\n            ctx.fireChannelRead(msg);\n    }\n} finally {\n        if(this.autoRelease && release) {\n            //ByteBuf的释放\n        \tReferenceCountUtil.release(msg);\n\t\t}\n\t}\n}\n// 这个方法才是我们需要实现的方法\nprotected abstract void channelRead0(ChannelHandlerContext var1, I var2) throws Exception;\n}\n// ReferenceCountUtil中的源码，release方法对消息对象的类型进行判断然后调用它的release()方法\npublic static boolean release(Object msg) {\nreturn msg instanceof ReferenceCounted?((ReferenceCounted)msg).release():false;\n}\n````\n\n### ChannelPipeline\n\n\n\n* * *\n\n\n\n为了**模块化与解耦合**，不可能由一个ChannelHandler来完成所有应用逻辑，所以Netty采用了拦截器链的设计。ChannelPipeline就是用来**管理ChannelHandler实例链的容器**，它的职责就是**保证实例链的流动**。\n\n每一个新创建的Channel都将会被分配一个新的ChannelPipeline，这种**关联关系是永久**性的，一个Channel一生只能对应一个ChannelPipeline。\n\n[![](http://wx3.sinaimg.cn/large/63503acbly1fm1er9l4jfj213h0fcq3d.jpg)](http://wx3.sinaimg.cn/large/63503acbly1fm1er9l4jfj213h0fcq3d.jpg)\n\n一个入站事件被触发时，它会先从ChannelPipeline的最左端（头部）开始一直传播到ChannelPipeline的最右端（尾部），而出站事件正好与入站事件顺序相反（从最右端一直传播到最左端）。这个**顺序是定死**的，Netty总是将ChannelPipeline的**入站口作为头部**，而将**出站口作为尾部**。在事件传播的过程中，ChannelPipeline会判断下一个ChannelHandler的类型是否和事件的运动方向相匹配，如果不匹配，就跳过该ChannelHandler并继续检查下一个（保证入站事件只会被ChannelInboundHandler处理），**一个**ChannelHandler也可以**同时实现**ChannelInboundHandler与ChannelOutboundHandler，它在**入站事件与出站事件中都会被调用。**\n\n在阅读ChannelHandler的源码时，发现很多方法需要一个ChannelHandlerContext类型的参数，该接口是ChannelPipeline与ChannelHandler之间相关联的关键。ChannelHandlerContext可以通知ChannelPipeline中的当前ChannelHandler的下一个ChannelHandler，还可以动态地改变当前ChannelHandler在ChannelPipeline中的位置（通过调用ChannelPipeline中的各种方法来修改）。\n\nChannelHandlerContext负责了在同一个ChannelPipeline中的ChannelHandler与其他ChannelHandler之间的交互，每个ChannelHandlerContext都对应了一个ChannelHandler。在DefaultChannelPipeline的源码中，已经表现的很明显了。\n````\npublic class DefaultChannelPipeline implements ChannelPipeline {\n.........\n// 头部节点和尾部节点的引用变量\n// ChannelHandlerContext在ChannelPipeline中是以链表的形式组织的\nfinal AbstractChannelHandlerContext head;\nfinal AbstractChannelHandlerContext tail;\n.........\n// 添加一个ChannelHandler到链表尾部\npublic final ChannelPipeline addLast(String name, ChannelHandler handler) {\nreturn this.addLast((EventExecutorGroup)null, name, handler);\n}\npublic final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {\nfinal AbstractChannelHandlerContext newCtx;\nsynchronized(this) {\n// 检查ChannelHandler是否为一个共享对象(@Sharable)\n// 如果该ChannelHandler没有@Sharable注解，并且是已被添加过的那么就抛出异常\ncheckMultiplicity(handler);\n// 返回一个DefaultChannelHandlerContext，注意该对象持有了传入的ChannelHandler\nnewCtx = this.newContext(group, this.filterName(name, handler), handler);\nthis.addLast0(newCtx);\n// 如果当前ChannelPipeline没有被注册，那么就先加到未决链表中\nif(!this.registered) {\nnewCtx.setAddPending();\nthis.callHandlerCallbackLater(newCtx, true);\nreturn this;\n}\n// 否则就调用ChannelHandler中的handlerAdded()\nEventExecutor executor = newCtx.executor();\nif(!executor.inEventLoop()) {\nnewCtx.setAddPending();\nexecutor.execute(new Runnable() {\npublic void run() {\nDefaultChannelPipeline.this.callHandlerAdded0(newCtx);\n}\n});\nreturn this;\n}\n}\nthis.callHandlerAdded0(newCtx);\nreturn this;\n}\n// 将新的ChannelHandlerContext插入到尾部与尾部之前的节点之间\nprivate void addLast0(AbstractChannelHandlerContext newCtx) {\nAbstractChannelHandlerContext prev = this.tail.prev;\nnewCtx.prev = prev;\nnewCtx.next = this.tail;\nprev.next = newCtx;\nthis.tail.prev = newCtx;\n}\n.....\n}\n````\n\nChannelHandlerContext还定义了许多与Channel和ChannelPipeline重合的方法（像read()、write()、connect()这些用于出站的方法或者如fireChannelXXXX()这样用于**入站的方法**），不同之处在于**调用Channel或者ChannelPipeline上的这些方法，它们将会从头沿着整个ChannelHandler实例链进行传播，而调用位于ChannelHandlerContext上的相同方法，则会从当前所关联的ChannelHandler开始，且只会传播给实例链中的下一个ChannelHandler**。而且，**事件之间的移动**（从一个ChannelHandler到下一个ChannelHandler）也是**通过ChannelHandlerContext中的方法调用完成**的。\n````\npublic class DefaultChannelPipeline implements ChannelPipeline {\npublic final ChannelPipeline fireChannelRead(Object msg) {\n// 注意这里将头节点传入了进去\nAbstractChannelHandlerContext.invokeChannelRead(this.head, msg);\nreturn this;\n}\n}\n````\n````\nabstract class AbstractChannelHandlerContext extends DefaultAttributeMap implements ChannelHandlerContext, ResourceLeakHint {\nstatic void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {\nfinal Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, \"msg\"), next);\nEventExecutor executor = next.executor();\nif(executor.inEventLoop()) {\nnext.invokeChannelRead(m);\n} else {\nexecutor.execute(new Runnable() {\npublic void run() {\nnext.invokeChannelRead(m);\n}\n});\n}\n}\nprivate void invokeChannelRead(Object msg) {\nif(this.invokeHandler()) {\ntry {\n((ChannelInboundHandler)this.handler()).channelRead(this, msg);\n} catch (Throwable var3) {\nthis.notifyHandlerException(var3);\n}\n} else {\n// 寻找下一个ChannelHandler\nthis.fireChannelRead(msg);\n}\n}\npublic ChannelHandlerContext fireChannelRead(Object msg) {\ninvokeChannelRead(this.findContextInbound(), msg);\nreturn this;\n}\nprivate AbstractChannelHandlerContext findContextInbound() {\nAbstractChannelHandlerContext ctx = this;\ndo {\nctx = ctx.next;\n} while(!ctx.inbound); // 直到找到一个ChannelInboundHandler\nreturn ctx;\n}\n}\n````\n\n### EventLoop\n\n\n\n* * *\n\n\n\n为了最大限度地提供高性能和可维护性，Netty设计了一套强大又易用的线程模型。在一个网络框架中，最重要的能力是能够快速高效地**处理在连接的生命周期内发生的各种事件**，与之相匹配的程序构造被称为**事件循环**，Netty定义了接口EventLoop来负责这项工作。\n\n如果是经常用Java进行多线程开发的童鞋想必经常会使用到线程池，也就是Executor这套API。Netty就是从Executor（java.util.concurrent）之上扩展了自己的EventExecutorGroup（io.netty.util.concurrent），同时为了与Channel的事件进行交互，还扩展了EventLoopGroup接口（io.netty.channel）。在io.netty.util.concurrent包下的EventExecutorXXX负责实现**线程并发**相关的工作，而在io.netty.channel包下的EventLoopXXX负责**实现网络编程**相关的工作（处理Channel中的事件）。\n\n[![](http://wx3.sinaimg.cn/large/63503acbly1fm296hz0p9j20ff0kc3z2.jpg)](http://wx3.sinaimg.cn/large/63503acbly1fm296hz0p9j20ff0kc3z2.jpg)\n\n在Netty的线程模型中，一个EventLoop将由一个永远不会改变的Thread驱动，而一个Channel一生只会使用一个EventLoop（但是一个EventLoop可能会被指派用于服务多个Channel），在Channel中的所有I/O操作和事件都由EventLoop中的线程处理，也就是说**一个Channel的一生之中都只会使用到一个线程**。不过在Netty3，只有入站事件会被EventLoop处理，所有出站事件都会由调用线程处理，这种设计导致了ChannelHandler的线程安全问题。Netty4简化了线程模型，通过在同一个线程处理所有事件，既解决了这个问题，还提供了一个更加简单的架构。\n````\npackage io.netty.channel;\npublic abstract class SingleThreadEventLoop extends SingleThreadEventExecutor implements EventLoop {\nprotected static final int DEFAULT_MAX_PENDING_TASKS = Math.max(16, SystemPropertyUtil.getInt(\"io.netty.eventLoop.maxPendingTasks\", 2147483647));\n    //内部队列\nprivate final Queue<Runnable> tailTasks;\nprotected SingleThreadEventLoop(EventLoopGroup parent, ThreadFactory threadFactory, boolean addTaskWakesUp) {\nthis(parent, threadFactory, addTaskWakesUp, DEFAULT_MAX_PENDING_TASKS, RejectedExecutionHandlers.reject());\n}\nprotected SingleThreadEventLoop(EventLoopGroup parent, Executor executor, boolean addTaskWakesUp) {\nthis(parent, executor, addTaskWakesUp, DEFAULT_MAX_PENDING_TASKS, RejectedExecutionHandlers.reject());\n}\nprotected SingleThreadEventLoop(EventLoopGroup parent, ThreadFactory threadFactory, boolean addTaskWakesUp, int maxPendingTasks, RejectedExecutionHandler rejectedExecutionHandler) {\nsuper(parent, threadFactory, addTaskWakesUp, maxPendingTasks, rejectedExecutionHandler);\nthis.tailTasks = this.newTaskQueue(maxPendingTasks);\n}\nprotected SingleThreadEventLoop(EventLoopGroup parent, Executor executor, boolean addTaskWakesUp, int maxPendingTasks, RejectedExecutionHandler rejectedExecutionHandler) {\nsuper(parent, executor, addTaskWakesUp, maxPendingTasks, rejectedExecutionHandler);\nthis.tailTasks = this.newTaskQueue(maxPendingTasks);\n}\n// 返回它所在的EventLoopGroup\npublic EventLoopGroup parent() {\nreturn (EventLoopGroup)super.parent();\n}\npublic EventLoop next() {\nreturn (EventLoop)super.next();\n}\n// 注册Channel,这里ChannelPromise和Channel关联到了一起\npublic ChannelFuture register(Channel channel) {\nreturn this.register((ChannelPromise)(new DefaultChannelPromise(channel, this)));\n}\npublic ChannelFuture register(ChannelPromise promise) {\nObjectUtil.checkNotNull(promise, \"promise\");\npromise.channel().unsafe().register(this, promise);\nreturn promise;\n}\n// 剩下这些函数都是用于调度任务\npublic final void executeAfterEventLoopIteration(Runnable task) {\nObjectUtil.checkNotNull(task, \"task\");\nif(this.isShutdown()) {\nreject();\n}\nif(!this.tailTasks.offer(task)) {\nthis.reject(task);\n}\nif(this.wakesUpForTask(task)) {\nthis.wakeup(this.inEventLoop());\n}\n}\nfinal boolean removeAfterEventLoopIterationTask(Runnable task) {\nreturn this.tailTasks.remove(ObjectUtil.checkNotNull(task, \"task\"));\n}\nprotected boolean wakesUpForTask(Runnable task) {\nreturn !(task instanceof SingleThreadEventLoop.NonWakeupRunnable);\n}\nprotected void afterRunningAllTasks() {\nthis.runAllTasksFrom(this.tailTasks);\n}\nprotected boolean hasTasks() {\nreturn super.hasTasks() || !this.tailTasks.isEmpty();\n}\npublic int pendingTasks() {\nreturn super.pendingTasks() + this.tailTasks.size();\n}\ninterface NonWakeupRunnable extends Runnable {\n}\n}\n````\n\n为了确保一个Channel的整个生命周期中的I/O事件会被一个EventLoop负责，Netty通过**inEventLoop()**方法来**判断当前执行的线程的身份**，确定它是否是分配给当前Channel以及它的EventLoop的那一个线程。\n\n如果当前（调用）线程正是EventLoop中的线程，那么所提交的任务将会被**(true)直接执行**，否则，EventLoop将调度该任务以便**(false)稍后执行**，并将它**放入内部的任务队列**（每个EventLoop都有它自己的任务队列，SingleThreadEventLoop的源码就能发现很多用于调度内部任务队列的方法），在下次处理它的事件时，将会执行队列中的那些任务。这种设计可以让任何线程与Channel直接交互，而无需在ChannelHandler中进行额外的同步。\n\n从性能上来考虑，千万**不要将一个需要长时间来运行的任务放入到任务队列中**，它会影响到该队列中的其他任务的执行。**解决方案**是**使用一个专门的EventExecutor来执行它**（ChannelPipeline提供了带有EventExecutorGroup参数的addXXX()方法，该方法可以**将传入的ChannelHandler绑定到你传入的EventExecutor之中**），这样它就会在另一条线程中执行，与其他任务隔离。\n````\npublic abstract class SingleThreadEventExecutor extends AbstractScheduledEventExecutor implements OrderedEventExecutor {\n.....\npublic void execute(Runnable task) {\nif(task == null) {\nthrow new NullPointerException(\"task\");\n} else {\nboolean inEventLoop = this.inEventLoop();\nif(inEventLoop) {\nthis.addTask(task);\n} else {\nthis.startThread();\nthis.addTask(task);\nif(this.isShutdown() && this.removeTask(task)) {\nreject();\n}\n}\nif(!this.addTaskWakesUp && this.wakesUpForTask(task)) {\nthis.wakeup(inEventLoop);\n}\n}\n}\npublic boolean inEventLoop(Thread thread) {\nreturn thread == this.thread;\n}\n.....\n}\n\n````\nEventLoopGroup**负责管理和分配EventLoop（创建EventLoop和为每个新创建的Channel分配EventLoop），根据不同的传输类型，EventLoop的创建和分配方式也不同**。例如，使用NIO传输类型，EventLoopGroup就会只使用较少的EventLoop（一个EventLoop服务于多个Channel），这是因为NIO基于I/O多路复用，一个线程可以处理多个连接，而如果使用的是OIO，那么新创建一个Channel（连接）就需要分配一个EventLoop（线程）。\n\n### Bootstrap\n\n\n\n* * *\n\n\n\n在深入了解地Netty的核心组件之后，发现它们的**设计**都很**模块化**，如果想要实现你自己的应用程序，就需要**将这些组件组装到一起**。Netty通过Bootstrap类，以对一个Netty应用程序进行配置（**组装各个组件**），并最终使它运行起来。对于客户端程序和服务器程序所使用到的Bootstrap类是不同的，后者需要使用ServerBootstrap，这样设计是因为，在如TCP这样有连接的协议中，服务器程序往往需要一个以上的Channel，通过父Channel来接受来自客户端的连接，然后创建子Channel用于它们之间的通信，而像UDP这样无连接的协议，它不需要每个连接都创建子Channel，只需要一个Channel即可。\n\n一个比较明显的差异就是Bootstrap与ServerBootstrap的group()方法，后者提供了一个接收2个EventLoopGroup的版本。\n````\n// 该方法在Bootstrap的父类AbstractBootstrap中，泛型B为它当前子类的类型（为了链式调用）\npublic B group(EventLoopGroup group) {\nif(group == null) {\nthrow new NullPointerException(\"group\");\n} else if(this.group != null) {\nthrow new IllegalStateException(\"group set already\");\n} else {\nthis.group = group;\nreturn this;\n}\n}\n// ServerBootstrap中的实现，它也支持只用一个EventLoopGroup\npublic ServerBootstrap group(EventLoopGroup group) {\nreturn this.group(group, group);\n}\npublic ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {\nsuper.group(parentGroup);\nif(childGroup == null) {\nthrow new NullPointerException(\"childGroup\");\n} else if(this.childGroup != null) {\nthrow new IllegalStateException(\"childGroup set already\");\n} else {\nthis.childGroup = childGroup;\nreturn this;\n}\n}\n````\n\nBootstrap其实没有什么可以好说的，它就只是一个**装配工**，将各个组件拼装组合到一起，然后进行一些配置，有关它的详细API请参考[Netty JavaDoc](http://netty.io/4.1/api/index.html)。\n\n### Echo示例\n\n下面我们将通过一个经典的Echo客户端与服务器的例子，来梳理一遍创建Netty应用的流程。\n\n首先实现的是服务器，我们先实现一个EchoServerInboundHandler，处理入站消息。\n````\npublic class EchoServerInboundHandler extends ChannelInboundHandlerAdapter {\n@Override\npublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\nByteBuf in = (ByteBuf) msg;\nSystem.out.printf(\"Server received: %s \\n\", in.toString(CharsetUtil.UTF_8));\n// 由于读事件不是一次性就能把完整消息发送过来的，这里并没有调用writeAndFlush\nctx.write(in); // 直接把消息写回给客户端(会被出站消息处理器处理,不过我们的应用没有实现任何出站消息处理器)\n}\n@Override\npublic void channelReadComplete(ChannelHandlerContext ctx) throws Exception {\n// 等读事件已经完成时,冲刷之前写数据的缓冲区\n// 然后添加了一个监听器，它会在Future完成时进行关闭该Channel.\nctx.writeAndFlush(Unpooled.EMPTY_BUFFER)\n.addListener(ChannelFutureListener.CLOSE);\n}\n// 处理异常，输出异常信息，然后关闭Channel\n@Override\npublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {\ncause.printStackTrace();\nctx.close();\n}\n}\n\n````\n服务器的应用逻辑只有这么多，剩下就是用ServerBootstrap进行配置了。\n````\npublic class EchoServer {\nprivate final int port;\npublic EchoServer(int port) {\nthis.port = port;\n}\npublic void start() throws Exception {\nfinal EchoServerInboundHandler serverHandler = new EchoServerInboundHandler();\nEventLoopGroup group = new NioEventLoopGroup(); // 传输类型使用NIO\ntry {\nServerBootstrap b = new ServerBootstrap();\nb.group(group) // 配置EventLoopGroup\n.channel(NioServerSocketChannel.class) // 配置Channel的类型\n.localAddress(new InetSocketAddress(port)) // 配置端口号\n.childHandler(new ChannelInitializer<SocketChannel>() {\n// 实现一个ChannelInitializer，它可以方便地添加多个ChannelHandler\n@Override\nprotected void initChannel(SocketChannel socketChannel) throws Exception {\nsocketChannel.pipeline().addLast(serverHandler);\n}\n});\n// 绑定地址，同步等待它完成\nChannelFuture f = b.bind().sync();\n// 关闭这个Future\nf.channel().closeFuture().sync();\n} finally {\n// 关闭应用程序，一般来说Netty应用只需要调用这个方法就够了\ngroup.shutdownGracefully().sync();\n}\n}\npublic static void main(String[] args) throws Exception {\nif (args.length != 1) {\nSystem.err.printf(\n\"Usage: %s <port> \\n\",\nEchoServer.class.getSimpleName()\n);\nreturn;\n}\nint port = Integer.parseInt(args[0]);\nnew EchoServer(port).start();\n}\n}\n````\n\n接下来实现客户端，同样需要先实现一个入站消息处理器。\n````\npublic class EchoClientInboundHandler extends SimpleChannelInboundHandler<ByteBuf> {\n/**\n* 我们在Channel连接到远程节点直接发送一条消息给服务器\n*/\n@Override\npublic void channelActive(ChannelHandlerContext ctx) throws Exception {\nctx.writeAndFlush(Unpooled.copiedBuffer(\"Hello, Netty!\", CharsetUtil.UTF_8));\n}\n@Override\nprotected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {\n// 输出从服务器Echo的消息\nSystem.out.printf(\"Client received: %s \\n\", byteBuf.toString(CharsetUtil.UTF_8));\n}\n@Override\npublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {\ncause.printStackTrace();\nctx.close();\n}\n}\n````\n\n然后配置客户端。\n````\npublic class EchoClient {\nprivate final String host;\nprivate final int port;\npublic EchoClient(String host, int port) {\nthis.host = host;\nthis.port = port;\n}\npublic void start() throws Exception {\nEventLoopGroup group = new NioEventLoopGroup();\ntry {\nBootstrap b = new Bootstrap();\nb.group(group)\n.channel(NioSocketChannel.class)\n.remoteAddress(new InetSocketAddress(host, port)) // 服务器的地址\n.handler(new ChannelInitializer<SocketChannel>() {\n@Override\nprotected void initChannel(SocketChannel socketChannel) throws Exception {\nsocketChannel.pipeline().addLast(new EchoClientInboundHandler());\n}\n});\nChannelFuture f = b.connect().sync(); // 连接到服务器\nf.channel().closeFuture().sync();\n} finally {\ngroup.shutdownGracefully().sync();\n}\n}\npublic static void main(String[] args) throws Exception {\nif (args.length != 2) {\nSystem.err.printf(\"Usage: %s <host> <port> \\n\", EchoClient.class.getSimpleName());\nreturn;\n}\nString host = args[0];\nint port = Integer.parseInt(args[1]);\nnew EchoClient(host, port).start();\n}\n}\n````\n\n实现一个Netty应用程序就是如此简单，用户大多数都是在编写各种应用逻辑的ChannelHandler（或者使用Netty内置的各种实用ChannelHandler），然后只需要将它们全部添加到ChannelPipeline即可。\n\n## 参考文献\n\n* * *\n\n*   [Netty: Home](https://netty.io/)\n\n*   [Chapter 6\\. I/O Multiplexing: The select and poll Functions - Shichao’s Notes](https://notes.shichao.io/unp/ch6/#io-multiplexing-model)\n\n*   [epoll(7) - Linux manual page](http://man7.org/linux/man-pages/man7/epoll.7.html)\n\n*   [Java NIO](http://tutorials.jenkov.com/java-nio/)\n\n*   [Netty: Home](https://netty.io/)\n\n*   [Chapter 6\\. I/O Multiplexing: The select and poll Functions - Shichao’s Notes](https://notes.shichao.io/unp/ch6/#io-multiplexing-model)\n\n*   [epoll(7) - Linux manual page](http://man7.org/linux/man-pages/man7/epoll.7.html)\n\n*   [Java NIO](http://tutorials.jenkov.com/java-nio/)\n"
  },
  {
    "path": "docs/Java/network/Java网络编程与NIO详解：浅析NIO包中的Buffer、Channel和Selector.md",
    "content": "# 目录\n\n* [目录](#目录)\n  * [Buffer](#buffer)\n    * [position、limit、capacity](#position、limit、capacity)\n    * [初始化 Buffer](#初始化-buffer)\n    * [填充 Buffer](#填充-buffer)\n    * [提取 Buffer 中的值](#提取-buffer-中的值)\n    * [mark() & reset()](#mark--reset)\n    * [rewind() & clear() & compact()](#rewind--clear--compact)\n  * [Channel](#channel)\n    * [FileChannel](#filechannel)\n    * [SocketChannel](#socketchannel)\n    * [ServerSocketChannel](#serversocketchannel)\n    * [DatagramChannel](#datagramchannel)\n  * [Selector](#selector)\n  * [小结](#小结)\n\n\n# 目录\n  * [Buffer](#buffer)\n    * [position、limit、capacity](#position、limit、capacity)\n    * [初始化 Buffer](#初始化-buffer)\n    * [填充 Buffer](#填充-buffer)\n    * [提取 Buffer 中的值](#提取-buffer-中的值)\n    * [mark() & reset()](#mark--reset)\n    * [rewind() & clear() & compact()](#rewind--clear--compact)\n  * [Channel](#channel)\n    * [FileChannel](#filechannel)\n    * [SocketChannel](#socketchannel)\n    * [ServerSocketChannel](#serversocketchannel)\n    * [DatagramChannel](#datagramchannel)\n  * [Selector](#selector)\n  * [小结](#小结)\n\n\n本文转载自互联网\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章将同步到我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《不可轻视的Java网络编程》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从计算机网络的基础知识入手，一步步地学习Java网络基础，从socket到nio、bio、aio和netty等网络编程知识，并且进行实战，网络编程是每一个Java后端工程师必须要学习和理解的知识点，进一步来说，你还需要掌握Linux中的网络编程原理，包括IO模型、网络编程框架netty的进阶原理，才能更完整地了解整个Java网络编程的知识体系，形成自己的知识框架。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供部分知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n本文将介绍 Java NIO 中三大组件**Buffer、Channel、Selector**的使用。\n\n本来要一起介绍**非阻塞 IO**和 JDK7 的**异步 IO**的，不过因为之前的文章真的太长了，有点影响读者阅读，所以这里将它们放到另一篇文章中进行介绍。\n\n## Buffer\n\n一个 Buffer 本质上是内存中的一块，我们可以将数据写入这块内存，之后从这块内存获取数据。\n\njava.nio 定义了以下几个 Buffer 的实现，这个图读者应该也在不少地方见过了吧。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405095127.png)\n\n其实核心是最后的**ByteBuffer**，前面的一大串类只是包装了一下它而已，我们使用最多的通常也是 ByteBuffer。\n\n我们应该将 Buffer 理解为一个数组，IntBuffer、CharBuffer、DoubleBuffer 等分别对应 int[]、char[]、double[] 等。\n\nMappedByteBuffer 用于实现内存映射文件，也不是本文关注的重点。\n\n我觉得操作 Buffer 和操作数组、类集差不多，只不过大部分时候我们都把它放到了 NIO 的场景里面来使用而已。下面介绍 Buffer 中的几个重要属性和几个重要方法。\n\n### position、limit、capacity\n\n就像数组有数组容量，每次访问元素要指定下标，Buffer 中也有几个重要属性：position、limit、capacity。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405095151.png)\n\n最好理解的当然是 capacity，它代表这个缓冲区的容量，一旦设定就不可以更改。比如 capacity 为 1024 的 IntBuffer，代表其一次可以存放 1024 个 int 类型的值。一旦 Buffer 的容量达到 capacity，需要清空 Buffer，才能重新写入值。\n\nposition 和 limit 是变化的，我们分别看下读和写操作下，它们是如何变化的。\n\n**position**的初始值是 0，每往 Buffer 中写入一个值，position 就自动加 1，代表下一次的写入位置。读操作的时候也是类似的，每读一个值，position 就自动加 1。\n\n从写操作模式到读操作模式切换的时候（**flip**），position 都会归零，这样就可以从头开始读写了。\n\n**Limit**：写操作模式下，limit 代表的是最大能写入的数据，这个时候 limit 等于 capacity。写结束后，切换到读模式，此时的 limit 等于 Buffer 中实际的数据大小，因为 Buffer 不一定被写满了。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405095206.png)\n\n### 初始化 Buffer\n\n每个 Buffer 实现类都提供了一个静态方法`allocate(int capacity)`帮助我们快速实例化一个 Buffer。如：\n\n```\nByteBuffer byteBuf = ByteBuffer.allocate(1024);\nIntBuffer intBuf = IntBuffer.allocate(1024);\nLongBuffer longBuf = LongBuffer.allocate(1024);\n// ...\n```\n\n另外，我们经常使用 wrap 方法来初始化一个 Buffer。\n\n```\npublic static ByteBuffer wrap(byte[] array) {\n    ...\n}\n```\n\n### 填充 Buffer\n\n各个 Buffer 类都提供了一些 put 方法用于将数据填充到 Buffer 中，如 ByteBuffer 中的几个 put 方法：\n\n```\n// 填充一个 byte 值\npublic abstract ByteBuffer put(byte b);\n// 在指定位置填充一个 int 值\npublic abstract ByteBuffer put(int index, byte b);\n// 将一个数组中的值填充进去\npublic final ByteBuffer put(byte[] src) {...}\npublic ByteBuffer put(byte[] src, int offset, int length) {...}\n```\n\n上述这些方法需要自己控制 Buffer 大小，不能超过 capacity，超过会抛 java.nio.BufferOverflowException 异常。\n\n对于 Buffer 来说，另一个常见的操作中就是，我们要将来自 Channel 的数据填充到 Buffer 中，在系统层面上，这个操作我们称为**读操作**，因为数据是从外部（文件或网络等）读到内存中。\n\n```\nint num = channel.read(buf);\n```\n\n上述方法会返回从 Channel 中读入到 Buffer 的数据大小。\n\n### 提取 Buffer 中的值\n\n前面介绍了写操作，每写入一个值，position 的值都需要加 1，所以 position 最后会指向最后一次写入的位置的后面一个，如果 Buffer 写满了，那么 position 等于 capacity（position 从 0 开始）。\n\n如果要读 Buffer 中的值，需要切换模式，从写入模式切换到读出模式。注意，通常在说 NIO 的读操作的时候，我们说的是从 Channel 中读数据到 Buffer 中，对应的是对 Buffer 的写入操作，初学者需要理清楚这个。\n\n调用 Buffer 的**flip()**方法，可以从写入模式切换到读取模式。其实这个方法也就是设置了一下 position 和 limit 值罢了。\n\n```\npublic final Buffer flip() {\n    limit = position; // 将 limit 设置为实际写入的数据数量\n    position = 0; // 重置 position 为 0\n    mark = -1; // mark 之后再说\n    return this;\n}\n```\n\n对应写入操作的一系列 put 方法，读操作提供了一系列的 get 方法：\n\n```\n// 根据 position 来获取数据\npublic abstract byte get();\n// 获取指定位置的数据\npublic abstract byte get(int index);\n// 将 Buffer 中的数据写入到数组中\npublic ByteBuffer get(byte[] dst)\n```\n\n附一个经常使用的方法：\n\n```\nnew String(buffer.array()).trim();\n```\n\n当然了，除了将数据从 Buffer 取出来使用，更常见的操作是将我们写入的数据传输到 Channel 中，如通过 FileChannel 将数据写入到文件中，通过 SocketChannel 将数据写入网络发送到远程机器等。对应的，这种操作，我们称之为**写操作**。\n\n```\nint num = channel.write(buf);\n```\n\n### mark() & reset()\n\n除了 position、limit、capacity 这三个基本的属性外，还有一个常用的属性就是 mark。\n\nmark 用于临时保存 position 的值，每次调用 mark() 方法都会将 mark 设值为当前的 position，便于后续需要的时候使用。\n\n```\npublic final Buffer mark() {\n    mark = position;\n    return this;\n}\n```\n\n那到底什么时候用呢？考虑以下场景，我们在 position 为 5 的时候，先 mark() 一下，然后继续往下读，读到第 10 的时候，我想重新回到 position 为 5 的地方重新来一遍，那只要调一下 reset() 方法，position 就回到 5 了。\n\n```\npublic final Buffer reset() {\n    int m = mark;\n    if (m < 0)\n        throw new InvalidMarkException();\n    position = m;\n    return this;\n}\n```\n\n### rewind() & clear() & compact()\n\n**rewind()**：会重置 position 为 0，通常用于重新从头读写 Buffer。\n\n```\npublic final Buffer rewind() {\n    position = 0;\n    mark = -1;\n    return this;\n}\n```\n\n**clear()**：有点重置 Buffer 的意思，相当于重新实例化了一样。\n\n通常，我们会先填充 Buffer，然后从 Buffer 读取数据，之后我们再重新往里填充新的数据，我们一般在重新填充之前先调用 clear()。\n\n```\npublic final Buffer clear() {\n    position = 0;\n    limit = capacity;\n    mark = -1;\n    return this;\n}\n```\n\n**compact()**：和 clear() 一样的是，它们都是在准备往 Buffer 填充新的数据之前调用。\n\n前面说的 clear() 方法会重置几个属性，但是我们要看到，clear() 方法并不会将 Buffer 中的数据清空，只不过后续的写入会覆盖掉原来的数据，也就相当于清空了数据了。\n\n而 compact() 方法有点不一样，调用这个方法以后，会先处理还没有读取的数据，也就是 position 到 limit 之间的数据（还没有读过的数据），先将这些数据移到左边，然后在这个基础上再开始写入。很明显，此时 limit 还是等于 capacity，position 指向原来数据的右边。\n\n## Channel\n\n所有的 NIO 操作始于通道，通道是数据来源或数据写入的目的地，主要地，我们将关心 java.nio 包中实现的以下几个 Channel：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405095238.png)\n\n*   FileChannel：文件通道，用于文件的读和写\n*   DatagramChannel：用于 UDP 连接的接收和发送\n*   SocketChannel：把它理解为 TCP 连接通道，简单理解就是 TCP 客户端\n*   ServerSocketChannel：TCP 对应的服务端，用于监听某个端口进来的请求\n\n**这里不是很理解这些也没关系，后面介绍了代码之后就清晰了。还有，我们最应该关注，也是后面将会重点介绍的是 SocketChannel 和 ServerSocketChannel。**\n\nChannel 经常翻译为通道，类似 IO 中的流，用于读取和写入。它与前面介绍的 Buffer 打交道，读操作的时候将 Channel 中的数据填充到 Buffer 中，而写操作时将 Buffer 中的数据写入到 Channel 中。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405095252.png)\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405095318.png)\n至少读者应该记住一点，这两个方法都是 channel 实例的方法。\n\n### FileChannel\n\n我想文件操作对于大家来说应该是最熟悉的，不过我们在说 NIO 的时候，其实 FileChannel 并不是关注的重点。而且后面我们说非阻塞的时候会看到，FileChannel 是不支持非阻塞的。\n\n**这里算是简单介绍下常用的操作吧，感兴趣的读者瞄一眼就是了。**\n\n**初始化：**\n\n```\nFileInputStream inputStream = new FileInputStream(new File(\"/data.txt\"));\nFileChannel fileChannel = inputStream.getChannel();\n```\n\n当然了，我们也可以从 RandomAccessFile#getChannel 来得到 FileChannel。\n\n**读取文件内容：**\n\n```\nByteBuffer buffer = ByteBuffer.allocate(1024);\n\nint num = fileChannel.read(buffer);\n```\n\n前面我们也说了，所有的 Channel 都是和 Buffer 打交道的。\n\n**写入文件内容：**\n\n```\nByteBuffer buffer = ByteBuffer.allocate(1024);\nbuffer.put(\"随机写入一些内容到 Buffer 中\".getBytes());\n// Buffer 切换为读模式\nbuffer.flip();\nwhile(buffer.hasRemaining()) {\n    // 将 Buffer 中的内容写入文件\n    fileChannel.write(buffer);\n}\n```\n\n### SocketChannel\n\n我们前面说了，我们可以将 SocketChannel 理解成一个 TCP 客户端。虽然这么理解有点狭隘，因为我们在介绍 ServerSocketChannel 的时候会看到另一种使用方式。\n\n打开一个 TCP 连接：\n\n```\nSocketChannel socketChannel = SocketChannel.open(new InetSocketAddress(\"https://www.javadoop.com\", 80));\n```\n\n当然了，上面的这行代码等价于下面的两行：\n\n```\n// 打开一个通道\nSocketChannel socketChannel = SocketChannel.open();\n// 发起连接\nsocketChannel.connect(new InetSocketAddress(\"https://www.javadoop.com\", 80));\n```\n\nSocketChannel 的读写和 FileChannel 没什么区别，就是操作缓冲区。\n\n```\n// 读取数据\nsocketChannel.read(buffer);\n\n// 写入数据到网络连接中\nwhile(buffer.hasRemaining()) {\n    socketChannel.write(buffer);   \n}\n```\n\n不要在这里停留太久，先继续往下走。\n\n### ServerSocketChannel\n\n之前说 SocketChannel 是 TCP 客户端，这里说的 ServerSocketChannel 就是对应的服务端。\n\nServerSocketChannel 用于监听机器端口，管理从这个端口进来的 TCP 连接。\n\n```\n// 实例化\nServerSocketChannel serverSocketChannel = ServerSocketChannel.open();\n// 监听 8080 端口\nserverSocketChannel.socket().bind(new InetSocketAddress(8080));\n\nwhile (true) {\n    // 一旦有一个 TCP 连接进来，就对应创建一个 SocketChannel 进行处理\n    SocketChannel socketChannel = serverSocketChannel.accept();\n}\n```\n\n> 这里我们可以看到 SocketChannel 的第二个实例化方式\n\n到这里，我们应该能理解 SocketChannel 了，它不仅仅是 TCP 客户端，它代表的是一个网络通道，可读可写。\n\nServerSocketChannel 不和 Buffer 打交道了，因为它并不实际处理数据，它一旦接收到请求后，实例化 SocketChannel，之后在这个连接通道上的数据传递它就不管了，因为它需要继续监听端口，等待下一个连接。\n\n### DatagramChannel\n\nUDP 和 TCP 不一样，DatagramChannel 一个类处理了服务端和客户端。\n\n> 科普一下，UDP 是面向无连接的，不需要和对方握手，不需要通知对方，就可以直接将数据包投出去，至于能不能送达，它是不知道的\n\n**监听端口：**\n\n```\nDatagramChannel channel = DatagramChannel.open();\nchannel.socket().bind(new InetSocketAddress(9090));\n```\n\n```\nByteBuffer buf = ByteBuffer.allocate(48);\n\nchannel.receive(buf);\n```\n\n**发送数据：**\n\n```\nString newData = \"New String to write to file...\"\n                    + System.currentTimeMillis();\n\nByteBuffer buf = ByteBuffer.allocate(48);\nbuf.put(newData.getBytes());\nbuf.flip();\n\nint bytesSent = channel.send(buf, new InetSocketAddress(\"jenkov.com\", 80));\n```\n\n## Selector\n\nNIO 三大组件就剩 Selector 了，Selector 建立在非阻塞的基础之上，大家经常听到的**多路复用**在 Java 世界中指的就是它，用于实现一个线程管理多个 Channel。\n\n读者在这一节不能消化 Selector 也没关系，因为后续在介绍非阻塞 IO 的时候还得说到这个，这里先介绍一些基本的接口操作。\n\n1.  首先，我们开启一个 Selector。你们爱翻译成**选择器**也好，**多路复用器**也好。\n\n    ```\n    Selector selector = Selector.open();\n    ```\n\n2.  将 Channel 注册到 Selector 上。前面我们说了，Selector 建立在非阻塞模式之上，所以注册到 Selector 的 Channel 必须要支持非阻塞模式，**FileChannel 不支持非阻塞**，我们这里讨论最常见的 SocketChannel 和 ServerSocketChannel。\n\n    ```\n    // 将通道设置为非阻塞模式，因为默认都是阻塞模式的\n    channel.configureBlocking(false);\n    // 注册\n    SelectionKey key = channel.register(selector, SelectionKey.OP_READ);\n    ```\n\n    register 方法的第二个 int 型参数（使用二进制的标记位）用于表明需要监听哪些感兴趣的事件，共以下四种事件：\n\n    *   SelectionKey.OP_READ\n\n        > 对应 00000001，通道中有数据可以进行读取\n\n    *   SelectionKey.OP_WRITE\n\n        > 对应 00000100，可以往通道中写入数据\n\n    *   SelectionKey.OP_CONNECT\n\n        > 对应 00001000，成功建立 TCP 连接\n\n    *   SelectionKey.OP_ACCEPT\n\n        > 对应 00010000，接受 TCP 连接\n\n    我们可以同时监听一个 Channel 中的发生的多个事件，比如我们要监听 ACCEPT 和 READ 事件，那么指定参数为二进制的 000**1**000**1**即十进制数值 17 即可。\n\n    注册方法返回值是**SelectionKey**实例，它包含了 Channel 和 Selector 信息，也包括了一个叫做 Interest Set 的信息，即我们设置的我们感兴趣的正在监听的事件集合。\n\n3.  调用 select() 方法获取通道信息。用于判断是否有我们感兴趣的事件已经发生了。\n\nSelector 的操作就是以上 3 步，这里来一个简单的示例，大家看一下就好了。之后在介绍非阻塞 IO 的时候，会演示一份可执行的示例代码。\n\n```\nSelector selector = Selector.open();\n\nchannel.configureBlocking(false);\n\nSelectionKey key = channel.register(selector, SelectionKey.OP_READ);\n\nwhile(true) {\n  // 判断是否有事件准备好\n  int readyChannels = selector.select();\n  if(readyChannels == 0) continue;\n\n  // 遍历\n  Set<SelectionKey> selectedKeys = selector.selectedKeys();\n  Iterator<SelectionKey> keyIterator = selectedKeys.iterator();\n  while(keyIterator.hasNext()) {\n    SelectionKey key = keyIterator.next();\n\n    if(key.isAcceptable()) {\n        // a connection was accepted by a ServerSocketChannel.\n\n    } else if (key.isConnectable()) {\n        // a connection was established with a remote server.\n\n    } else if (key.isReadable()) {\n        // a channel is ready for reading\n\n    } else if (key.isWritable()) {\n        // a channel is ready for writing\n    }\n\n    keyIterator.remove();\n  }\n}\n```\n\n对于 Selector，我们还需要非常熟悉以下几个方法：\n\n1.  **select()**\n\n    调用此方法，会将**上次 select 之后的**准备好的 channel 对应的 SelectionKey 复制到 selected set 中。如果没有任何通道准备好，这个方法会阻塞，直到至少有一个通道准备好。\n\n2.  **selectNow()**\n\n    功能和 select 一样，区别在于如果没有准备好的通道，那么此方法会立即返回 0。\n\n3.  **select(long timeout)**\n\n    看了前面两个，这个应该很好理解了，如果没有通道准备好，此方法会等待一会\n\n4.  **wakeup()**\n\n    这个方法是用来唤醒等待在 select() 和 select(timeout) 上的线程的。如果 wakeup() 先被调用，此时没有线程在 select 上阻塞，那么之后的一个 select() 或 select(timeout) 会立即返回，而不会阻塞，当然，它只会作用一次。\n\n## 小结\n\n到此为止，介绍了 Buffer、Channel 和 Selector 的常见接口。\n\nBuffer 和数组差不多，它有 position、limit、capacity 几个重要属性。put() 一下数据、flip() 切换到读模式、然后用 get() 获取数据、clear() 一下清空数据、重新回到 put() 写入数据。\n\nChannel 基本上只和 Buffer 打交道，最重要的接口就是 channel.read(buffer) 和 channel.write(buffer)。\n\nSelector 用于实现非阻塞 IO，这里仅仅介绍接口使用，后续请关注非阻塞 IO 的介绍。\n\n（全文完）\n"
  },
  {
    "path": "docs/Java/network/Java网络编程与NIO详解：浅析mmap和DirectBuffer.md",
    "content": "# 目录\n\n* [目录](#目录)\n* [mmap基础概念](#mmap基础概念)\n* [mmap内存映射原理](#mmap内存映射原理)\n* [mmap和常规文件操作的区别](#mmap和常规文件操作的区别)\n* [mmap优点总结](#mmap优点总结)\n* [mmap使用细节](#mmap使用细节)\n    * [堆外内存](#堆外内存)\n    * [在讲解DirectByteBuffer之前，需要先简单了解两个知识点](#在讲解directbytebuffer之前，需要先简单了解两个知识点)\n      * [java引用类型，因为DirectByteBuffer是通过虚引用(Phantom Reference)来实现堆外内存的释放的。](#java引用类型，因为directbytebuffer是通过虚引用phantom-reference来实现堆外内存的释放的。)\n      * [关于linux的内核态和用户态](#关于linux的内核态和用户态)\n    * [DirectByteBuffer ———— 直接缓冲](#directbytebuffer--直接缓冲)\n    * [DirectByteBuffer堆外内存的创建和回收的源码解读](#directbytebuffer堆外内存的创建和回收的源码解读)\n      * [堆外内存分配](#堆外内存分配)\n      * [Bits.reserveMemory(size, cap) 方法](#bitsreservememorysize-cap-方法)\n      * [堆外内存回收](#堆外内存回收)\n      * [通过配置参数的方式来回收堆外内存](#通过配置参数的方式来回收堆外内存)\n    * [堆外内存那些事](#堆外内存那些事)\n      * [使用堆外内存的原因](#使用堆外内存的原因)\n      * [什么情况下使用堆外内存](#什么情况下使用堆外内存)\n      * [堆外内存 VS 内存池](#堆外内存-vs-内存池)\n      * [堆外内存的特点](#堆外内存的特点)\n      * [堆外内存的一些问题](#堆外内存的一些问题)\n  * [参考文章](#参考文章)\n\n\n# 目录\n* [mmap基础概念](#mmap基础概念)\n* [mmap内存映射原理](#mmap内存映射原理)\n* [mmap和常规文件操作的区别](#mmap和常规文件操作的区别)\n* [mmap优点总结](#mmap优点总结)\n* [mmap使用细节](#mmap使用细节)\n    * [堆外内存](#堆外内存)\n    * [在讲解DirectByteBuffer之前，需要先简单了解两个知识点](#在讲解directbytebuffer之前，需要先简单了解两个知识点)\n      * [java引用类型，因为DirectByteBuffer是通过虚引用(Phantom Reference)来实现堆外内存的释放的。](#java引用类型，因为directbytebuffer是通过虚引用phantom-reference来实现堆外内存的释放的。)\n      * [关于linux的内核态和用户态](#关于linux的内核态和用户态)\n    * [DirectByteBuffer ———— 直接缓冲](#directbytebuffer--直接缓冲)\n    * [DirectByteBuffer堆外内存的创建和回收的源码解读](#directbytebuffer堆外内存的创建和回收的源码解读)\n      * [堆外内存分配](#堆外内存分配)\n      * [Bits.reserveMemory(size, cap) 方法](#bitsreservememorysize-cap-方法)\n      * [堆外内存回收](#堆外内存回收)\n      * [通过配置参数的方式来回收堆外内存](#通过配置参数的方式来回收堆外内存)\n    * [堆外内存那些事](#堆外内存那些事)\n      * [使用堆外内存的原因](#使用堆外内存的原因)\n      * [什么情况下使用堆外内存](#什么情况下使用堆外内存)\n      * [堆外内存 VS 内存池](#堆外内存-vs-内存池)\n      * [堆外内存的特点](#堆外内存的特点)\n      * [堆外内存的一些问题](#堆外内存的一些问题)\n  * [参考文章](#参考文章)\n\n\n本文转自：https://www.cnblogs.com/huxiao-tee/p/4660352.html\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章将同步到我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《不可轻视的Java网络编程》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从计算机网络的基础知识入手，一步步地学习Java网络基础，从socket到nio、bio、aio和netty等网络编程知识，并且进行实战，网络编程是每一个Java后端工程师必须要学习和理解的知识点，进一步来说，你还需要掌握Linux中的网络编程原理，包括IO模型、网络编程框架netty的进阶原理，才能更完整地了解整个Java网络编程的知识体系，形成自己的知识框架。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供部分知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n**阅读目录**\n\n*   [mmap基础概念](https://www.cnblogs.com/huxiao-tee/p/4660352.html#_label0)\n*   [mmap内存映射原理](https://www.cnblogs.com/huxiao-tee/p/4660352.html#_label1)\n*   [mmap和常规文件操作的区别](https://www.cnblogs.com/huxiao-tee/p/4660352.html#_label2)\n*   [mmap优点总结](https://www.cnblogs.com/huxiao-tee/p/4660352.html#_label3)\n*   [mmap相关函数](https://www.cnblogs.com/huxiao-tee/p/4660352.html#_label4)\n*   [mmap使用细节](https://www.cnblogs.com/huxiao-tee/p/4660352.html#_label5)\n\n# mmap基础概念\n\nmmap是一种内存映射文件的方法，即将一个文件或者其它对象映射到进程的地址空间，实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后，进程就可以采用指针的方式读写操作这一段内存，而系统会自动回写脏页面到对应的文件磁盘上，即完成了对文件的操作而不必再调用read,write等系统调用函数。相反，内核空间对这段区域的修改也直接反映用户空间，从而可以实现不同进程间的文件共享。如下图所示：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405100810.png)\n\n由上图可以看出，进程的虚拟地址空间，由多个虚拟内存区域构成。虚拟内存区域是进程的虚拟地址空间中的一个同质区间，即具有同样特性的连续地址范围。上图中所示的text数据段（代码段）、初始数据段、BSS数据段、堆、栈和内存映射，都是一个独立的虚拟内存区域。而为内存映射服务的地址空间处在堆栈之间的空余部分。\n\nlinux内核使用vm_area_struct结构来表示一个独立的虚拟内存区域，由于每个不同质的虚拟内存区域功能和内部机制都不同，因此一个进程使用多个vm_area_struct结构来分别表示不同类型的虚拟内存区域。各个vm_area_struct结构使用链表或者树形结构链接，方便进程快速访问，如下图所示：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405100838.png)\n\nvm_area_struct结构中包含区域起始和终止地址以及其他相关信息，同时也包含一个vm_ops指针，其内部可引出所有针对这个区域可以使用的系统调用函数。这样，进程对某一虚拟内存区域的任何操作需要用要的信息，都可以从vm_area_struct中获得。mmap函数就是要创建一个新的vm_area_struct结构，并将其与文件的物理磁盘地址相连。具体步骤请看下一节。\n\n# mmap内存映射原理\n\nmmap内存映射的实现过程，总的来说可以分为三个阶段：\n\n**（一）进程启动映射过程，并在虚拟地址空间中为映射创建虚拟映射区域**\n\n1、进程在用户空间调用库函数mmap，原型：void*mmap(void*start,size_tlength,intprot,intflags, intfd,off_toffset);\n\n2、在当前进程的虚拟地址空间中，寻找一段空闲的满足要求的连续的虚拟地址\n\n3、为此虚拟区分配一个vm_area_struct结构，接着对这个结构的各个域进行了初始化\n\n4、将新建的虚拟区结构（vm_area_struct）插入进程的虚拟地址区域链表或树中\n\n**（二）调用内核空间的系统调用函数mmap（不同于用户空间函数），实现文件物理地址和进程虚拟地址的一一映射关系**\n\n5、为映射分配了新的虚拟地址区域后，通过待映射的文件指针，在文件描述符表中找到对应的文件描述符，通过文件描述符，链接到内核“已打开文件集”中该文件的文件结构体（struct file），每个文件结构体维护着和这个已打开文件相关各项信息。\n\n6、通过该文件的文件结构体，链接到file_operations模块，调用内核函数mmap，其原型为：int mmap(structfile*filp,structvm_area_struct*vma)，不同于用户空间库函数。\n\n7、内核mmap函数通过虚拟文件系统inode模块定位到文件磁盘物理地址。\n\n8、通过remap_pfn_range函数建立页表，即实现了文件地址和虚拟地址区域的映射关系。此时，这片虚拟地址并没有任何数据关联到主存中。\n\n**（三）进程发起对这片映射空间的访问，引发缺页异常，实现文件内容到物理内存（主存）的拷贝**\n\n注：前两个阶段仅在于创建虚拟区间并完成地址映射，但是并没有将任何文件数据的拷贝至主存。真正的文件读取是当进程发起读或写操作时。\n\n9、进程的读或写操作访问虚拟地址空间这一段映射地址，通过查询页表，发现这一段地址并不在物理页面上。因为目前只建立了地址映射，真正的硬盘数据还没有拷贝到内存中，因此引发缺页异常。\n\n10、缺页异常进行一系列判断，确定无非法操作后，内核发起请求调页过程。\n\n11、调页过程先在交换缓存空间（swapcache）中寻找需要访问的内存页，如果没有则调用nopage函数把所缺的页从磁盘装入到主存中。\n\n12、之后进程即可对这片主存进行读或者写的操作，如果写操作改变了其内容，一定时间后系统会自动回写脏页面到对应磁盘地址，也即完成了写入到文件的过程。\n\n注：修改过的脏页面并不会立即更新回文件中，而是有一段时间的延迟，可以调用msync()来强制同步, 这样所写的内容就能立即保存到文件里了。\n\n# mmap和常规文件操作的区别\n\n对linux文件系统不了解的朋友，请参阅我之前写的博文《[从内核文件系统看文件读写过程](http://www.cnblogs.com/huxiao-tee/p/4657851.html)》，我们首先简单的回顾一下常规文件系统操作（调用read/fread等类函数）中，函数的调用过程：\n\n1、进程发起读文件请求。\n\n2、内核通过查找进程文件符表，定位到内核已打开文件集上的文件信息，从而找到此文件的inode。\n\n3、inode在address_space上查找要请求的文件页是否已经缓存在页缓存中。如果存在，则直接返回这片文件页的内容。\n\n4、如果不存在，则通过inode定位到文件磁盘地址，将数据从磁盘复制到页缓存。之后再次发起读页面过程，进而将页缓存中的数据发给用户进程。\n\n总结来说，常规文件操作为了提高读写效率和保护磁盘，使用了页缓存机制。这样造成读文件时需要先将文件页从磁盘拷贝到页缓存中，由于页缓存处在内核空间，不能被用户进程直接寻址，所以还需要将页缓存中数据页再次拷贝到内存对应的用户空间中。这样，通过了两次数据拷贝过程，才能完成进程对文件内容的获取任务。写操作也是一样，待写入的buffer在内核空间不能直接访问，必须要先拷贝至内核空间对应的主存，再写回磁盘中（延迟写回），也是需要两次数据拷贝。\n\n而使用mmap操作文件中，创建新的虚拟内存区域和建立文件磁盘地址和虚拟内存区域映射这两步，没有任何文件拷贝操作。而之后访问数据时发现内存中并无数据而发起的缺页异常过程，可以通过已经建立好的映射关系，只使用一次数据拷贝，就从磁盘中将数据传入内存的用户空间中，供进程使用。\n\n**总而言之，常规文件操作需要从磁盘到页缓存再到用户主存的两次数据拷贝。而mmap操控文件，只需要从磁盘到用户主存的一次数据拷贝过程。**说白了，mmap的关键点是实现了用户空间和内核空间的数据直接交互而省去了空间不同数据不通的繁琐过程。因此mmap效率更高。\n\n# mmap优点总结\n\n由上文讨论可知，mmap优点共有一下几点：\n\n1、对文件的读取操作跨过了页缓存，减少了数据的拷贝次数，用内存读写取代I/O读写，提高了文件读取效率。\n\n2、实现了用户空间和内核空间的高效交互方式。两空间的各自修改操作可以直接反映在映射的区域内，从而被对方空间及时捕捉。\n\n3、提供进程间共享内存及相互通信的方式。不管是父子进程还是无亲缘关系的进程，都可以将自身用户空间映射到同一个文件或匿名映射到同一片区域。从而通过各自对映射区域的改动，达到进程间通信和进程间共享的目的。\n\n  同时，如果进程A和进程B都映射了区域C，当A第一次读取C时通过缺页从磁盘复制文件页到内存中；但当B再读C的相同页面时，虽然也会产生缺页异常，但是不再需要从磁盘中复制文件过来，而可直接使用已经保存在内存中的文件数据。\n\n4、可用于实现高效的大规模数据传输。内存空间不足，是制约大数据操作的一个方面，解决方案往往是借助硬盘空间协助操作，补充内存的不足。但是进一步会造成大量的文件I/O操作，极大影响效率。这个问题可以通过mmap映射很好的解决。换句话说，但凡是需要用磁盘空间代替内存的时候，mmap都可以发挥其功效。\n\n# mmap使用细节\n\n1、使用mmap需要注意的一个关键点是，mmap映射区域大小必须是物理页大小(page_size)的整倍数（32位系统中通常是4k字节）。原因是，内存的最小粒度是页，而进程虚拟地址空间和内存的映射也是以页为单位。为了匹配内存的操作，mmap从磁盘到虚拟地址空间的映射也必须是页。\n\n2、内核可以跟踪被内存映射的底层对象（文件）的大小，进程可以合法的访问在当前文件大小以内又在内存映射区以内的那些字节。也就是说，如果文件的大小一直在扩张，只要在映射区域范围内的数据，进程都可以合法得到，这和映射建立时文件的大小无关。具体情形参见“情形三”。\n\n3、映射建立之后，即使文件关闭，映射依然存在。因为映射的是磁盘的地址，不是文件本身，和文件句柄无关。同时可用于进程间通信的有效地址空间不完全受限于被映射文件的大小，因为是按页映射。\n\n在上面的知识前提下，我们下面看看如果大小不是页的整倍数的具体情况：\n\n**情形一：一个文件的大小是5000字节，mmap函数从一个文件的起始位置开始，映射5000字节到虚拟内存中。**\n\n分析：因为单位物理页面的大小是4096字节，虽然被映射的文件只有5000字节，但是对应到进程虚拟地址区域的大小需要满足整页大小，因此mmap函数执行后，实际映射到虚拟内存区域8192个 字节，5000~8191的字节部分用零填充。映射后的对应关系如下图所示：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405100908.png)\n此时：\n\n（1）读/写前5000个字节（0~4999），会返回操作文件内容。\n\n（2）读字节5000~8191时，结果全为0。写5000~8191时，进程不会报错，但是所写的内容不会写入原文件中 。\n\n（3）读/写8192以外的磁盘部分，会返回一个SIGSECV错误。\n\n**情形二：一个文件的大小是5000字节，mmap函数从一个文件的起始位置开始，映射15000字节到虚拟内存中，即映射大小超过了原始文件的大小。**\n\n分析：由于文件的大小是5000字节，和情形一一样，其对应的两个物理页。那么这两个物理页都是合法可以读写的，只是超出5000的部分不会体现在原文件中。由于程序要求映射15000字节，而文件只占两个物理页，因此8192字节~15000字节都不能读写，操作时会返回异常。如下图所示：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405100917.png)\n\n此时：\n\n（1）进程可以正常读/写被映射的前5000字节(0~4999)，写操作的改动会在一定时间后反映在原文件中。\n\n（2）对于5000~8191字节，进程可以进行读写过程，不会报错。但是内容在写入前均为0，另外，写入后不会反映在文件中。\n\n（3）对于8192~14999字节，进程不能对其进行读写，会报SIGBUS错误。\n\n（4）对于15000以外的字节，进程不能对其读写，会引发SIGSEGV错误。\n\n**情形三：一个文件初始大小为0，使用mmap操作映射了1000*4K的大小，即1000个物理页大约4M字节空间，mmap返回指针ptr。**\n\n分析：如果在映射建立之初，就对文件进行读写操作，由于文件大小为0，并没有合法的物理页对应，如同情形二一样，会返回SIGBUS错误。\n\n但是如果，每次操作ptr读写前，先增加文件的大小，那么ptr在文件大小内部的操作就是合法的。例如，文件扩充4096字节，ptr就能操作ptr ~ [ (char)ptr + 4095]的空间。只要文件扩充的范围在1000个物理页（映射范围）内，ptr都可以对应操作相同的大小。\n\n这样，方便随时扩充文件空间，随时写入文件，不造成空间浪费。\n\n本文转自：https://www.jianshu.com/p/007052ee3773\n\n### 堆外内存\n\n堆外内存是相对于堆内内存的一个概念。堆内内存是由JVM所管控的Java进程内存，我们平时在Java中创建的对象都处于堆内内存中，并且它们遵循JVM的内存管理机制，JVM会采用垃圾回收机制统一管理它们的内存。那么堆外内存就是存在于JVM管控之外的一块内存区域，因此它是不受JVM的管控。\n\n### 在讲解DirectByteBuffer之前，需要先简单了解两个知识点\n\n#### java引用类型，因为DirectByteBuffer是通过虚引用(Phantom Reference)来实现堆外内存的释放的。\n\nPhantomReference 是所有“弱引用”中最弱的引用类型。不同于软引用和弱引用，虚引用无法通过 get() 方法来取得目标对象的强引用从而使用目标对象，观察源码可以发现 get() 被重写为永远返回 null。\n那虚引用到底有什么作用？其实虚引用主要被用来 跟踪对象被垃圾回收的状态，通过查看引用队列中是否包含对象所对应的虚引用来判断它是否 即将被垃圾回收，从而采取行动。它并不被期待用来取得目标对象的引用，而目标对象被回收前，它的引用会被放入一个 ReferenceQueue 对象中，从而达到跟踪对象垃圾回收的作用。\n关于java引用类型的实现和原理可以阅读之前的文章[Reference 、ReferenceQueue 详解](https://www.jianshu.com/p/f86d3a43eec5)和[Java 引用类型简述](https://www.jianshu.com/p/9a089a37f78d)\n\n#### 关于linux的内核态和用户态\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405100942.png)\n\n*   内核态：控制计算机的硬件资源，并提供上层应用程序运行的环境。比如socket I/0操作或者文件的读写操作等\n*   用户态：上层应用程序的活动空间，应用程序的执行必须依托于内核提供的资源。\n*   系统调用：为了使上层应用能够访问到这些资源，内核为上层应用提供访问的接口。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405100954.png)\n\n因此我们可以得知当我们通过JNI调用的native方法实际上就是从用户态切换到了内核态的一种方式。并且通过该系统调用使用操作系统所提供的功能。\n\nQ：为什么需要用户进程(位于用户态中)要通过系统调用(Java中即使JNI)来调用内核态中的资源，或者说调用操作系统的服务了？\nA：intel cpu提供Ring0-Ring3四种级别的运行模式，Ring0级别最高，Ring3最低。Linux使用了Ring3级别运行用户态，Ring0作为内核态。Ring3状态不能访问Ring0的地址空间，包括代码和数据。因此用户态是没有权限去操作内核态的资源的，它只能通过系统调用外完成用户态到内核态的切换，然后在完成相关操作后再有内核态切换回用户态。\n\n### DirectByteBuffer ———— 直接缓冲\n\nDirectByteBuffer是Java用于实现堆外内存的一个重要类，我们可以通过该类实现堆外内存的创建、使用和销毁。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405101025.png)\n\nDirectByteBuffer该类本身还是位于Java内存模型的堆中。堆内内存是JVM可以直接管控、操纵。\n而DirectByteBuffer中的unsafe.allocateMemory(size);是个一个native方法，这个方法分配的是堆外内存，通过C的malloc来进行分配的。分配的内存是系统本地的内存，并不在Java的内存中，也不属于JVM管控范围，所以在DirectByteBuffer一定会存在某种方式来操纵堆外内存。\n在DirectByteBuffer的父类Buffer中有个address属性：\n\n```\n    // Used only by direct buffers\n    // NOTE: hoisted here for speed in JNI GetDirectBufferAddress\n    long address;\n\n```\n\naddress只会被直接缓存给使用到。之所以将address属性升级放在Buffer中，是为了在JNI调用GetDirectBufferAddress时提升它调用的速率。\naddress表示分配的堆外内存的地址。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405101047.png)\n\nunsafe.allocateMemory(size);分配完堆外内存后就会返回分配的堆外内存基地址，并将这个地址赋值给了address属性。这样我们后面通过JNI对这个堆外内存操作时都是通过这个address来实现的了。\n\n在前面我们说过，在linux中内核态的权限是最高的，那么在内核态的场景下，操作系统是可以访问任何一个内存区域的，所以操作系统是可以访问到Java堆的这个内存区域的。\nQ：那为什么操作系统不直接访问Java堆内的内存区域了？\nA：这是因为JNI方法访问的内存区域是一个已经确定了的内存区域地质，那么该内存地址指向的是Java堆内内存的话，那么如果在操作系统正在访问这个内存地址的时候，Java在这个时候进行了GC操作，而GC操作会涉及到数据的移动操作[GC经常会进行先标志在压缩的操作。即，将可回收的空间做标志，然后清空标志位置的内存，然后会进行一个压缩，压缩就会涉及到对象的移动，移动的目的是为了腾出一块更加完整、连续的内存空间，以容纳更大的新对象]，数据的移动会使JNI调用的数据错乱。所以JNI调用的内存是不能进行GC操作的。\n\nQ：如上面所说，JNI调用的内存是不能进行GC操作的，那该如何解决了？\nA：①堆内内存与堆外内存之间数据拷贝的方式(并且在将堆内内存拷贝到堆外内存的过程JVM会保证不会进行GC操作)：比如我们要完成一个从文件中读数据到堆内内存的操作，即FileChannelImpl.read(HeapByteBuffer)。这里实际上File I/O会将数据读到堆外内存中，然后堆外内存再讲数据拷贝到堆内内存，这样我们就读到了文件中的内存。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405101116.png)\n```\nstatic int read(FileDescriptor var0, ByteBuffer var1, long var2, NativeDispatcher var4) throws IOException {\n    if (var1.isReadOnly()) {\n        throw new IllegalArgumentException(\"Read-only buffer\");\n    } else if (var1 instanceof DirectBuffer) {\n        return readIntoNativeBuffer(var0, var1, var2, var4);\n    } else {\n        // 分配临时的堆外内存\n        ByteBuffer var5 = Util.getTemporaryDirectBuffer(var1.remaining());\n\n        int var7;\n        try {\n            // File I/O 操作会将数据读入到堆外内存中\n            int var6 = readIntoNativeBuffer(var0, var5, var2, var4);\n            var5.flip();\n            if (var6 > 0) {\n                // 将堆外内存的数据拷贝到堆外内存中\n                var1.put(var5);\n            }\n\n            var7 = var6;\n        } finally {\n            // 里面会调用DirectBuffer.cleaner().clean()来释放临时的堆外内存\n            Util.offerFirstTemporaryDirectBuffer(var5);\n        }\n\n        return var7;\n    }\n}\n\n```\n\n而写操作则反之，我们会将堆内内存的数据线写到对堆外内存中，然后操作系统会将堆外内存的数据写入到文件中。\n② 直接使用堆外内存，如DirectByteBuffer：这种方式是直接在堆外分配一个内存(即，native memory)来存储数据，程序通过JNI直接将数据读/写到堆外内存中。因为数据直接写入到了堆外内存中，所以这种方式就不会再在JVM管控的堆内再分配内存来存储数据了，也就不存在堆内内存和堆外内存数据拷贝的操作了。这样在进行I/O操作时，只需要将这个堆外内存地址传给JNI的I/O的函数就好了。\n\n### DirectByteBuffer堆外内存的创建和回收的源码解读\n\n#### 堆外内存分配\n\n```\nDirectByteBuffer(int cap) {                   // package-private\n    super(-1, 0, cap, cap);\n    boolean pa = VM.isDirectMemoryPageAligned();\n    int ps = Bits.pageSize();\n    long size = Math.max(1L, (long)cap + (pa ? ps : 0));\n    // 保留总分配内存(按页分配)的大小和实际内存的大小\n    Bits.reserveMemory(size, cap);\n\n    long base = 0;\n    try {\n        // 通过unsafe.allocateMemory分配堆外内存，并返回堆外内存的基地址\n        base = unsafe.allocateMemory(size);\n    } catch (OutOfMemoryError x) {\n        Bits.unreserveMemory(size, cap);\n        throw x;\n    }\n    unsafe.setMemory(base, size, (byte) 0);\n    if (pa && (base % ps != 0)) {\n        // Round up to page boundary\n        address = base + ps - (base & (ps - 1));\n    } else {\n        address = base;\n    }\n    // 构建Cleaner对象用于跟踪DirectByteBuffer对象的垃圾回收，以实现当DirectByteBuffer被垃圾回收时，堆外内存也会被释放\n    cleaner = Cleaner.create(this, new Deallocator(base, size, cap));\n    att = null;\n}\n\n```\n\n#### Bits.reserveMemory(size, cap) 方法\n\n```\nstatic void reserveMemory(long size, int cap) {\n\n    if (!memoryLimitSet && VM.isBooted()) {\n        maxMemory = VM.maxDirectMemory();\n        memoryLimitSet = true;\n    }\n\n    // optimist!\n    if (tryReserveMemory(size, cap)) {\n        return;\n    }\n\n    final JavaLangRefAccess jlra = SharedSecrets.getJavaLangRefAccess();\n\n    // retry while helping enqueue pending Reference objects\n    // which includes executing pending Cleaner(s) which includes\n    // Cleaner(s) that free direct buffer memory\n    while (jlra.tryHandlePendingReference()) {\n        if (tryReserveMemory(size, cap)) {\n            return;\n        }\n    }\n\n    // trigger VM's Reference processing\n    System.gc();\n\n    // a retry loop with exponential back-off delays\n    // (this gives VM some time to do it's job)\n    boolean interrupted = false;\n    try {\n        long sleepTime = 1;\n        int sleeps = 0;\n        while (true) {\n            if (tryReserveMemory(size, cap)) {\n                return;\n            }\n            if (sleeps >= MAX_SLEEPS) {\n                break;\n            }\n            if (!jlra.tryHandlePendingReference()) {\n                try {\n                    Thread.sleep(sleepTime);\n                    sleepTime <<= 1;\n                    sleeps++;\n                } catch (InterruptedException e) {\n                    interrupted = true;\n                }\n            }\n        }\n\n        // no luck\n        throw new OutOfMemoryError(\"Direct buffer memory\");\n\n    } finally {\n        if (interrupted) {\n            // don't swallow interrupts\n            Thread.currentThread().interrupt();\n        }\n    }\n}\n\n```\n\n该方法用于在系统中保存总分配内存(按页分配)的大小和实际内存的大小。\n\n其中，如果系统中内存( 即，堆外内存 )不够的话：\n\n```\nfinal JavaLangRefAccess jlra = SharedSecrets.getJavaLangRefAccess();\n\n// retry while helping enqueue pending Reference objects\n// which includes executing pending Cleaner(s) which includes\n// Cleaner(s) that free direct buffer memory\nwhile (jlra.tryHandlePendingReference()) {\n    if (tryReserveMemory(size, cap)) {\n        return;\n    }\n}\n\n```\n\njlra.tryHandlePendingReference()会触发一次非堵塞的Reference#tryHandlePending(false)。该方法会将已经被JVM垃圾回收的DirectBuffer对象的堆外内存释放。\n因为在Reference的静态代码块中定义了：\n\n```\nSharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {\n    @Override\n    public boolean tryHandlePendingReference() {\n        return tryHandlePending(false);\n    }\n});\n\n```\n\n如果在进行一次堆外内存资源回收后，还不够进行本次堆外内存分配的话，则\n\n```\n// trigger VM's Reference processing\nSystem.gc();\n\n```\n\nSystem.gc()会触发一个full gc，当然前提是你没有显示的设置-XX:+DisableExplicitGC来禁用显式GC。并且你需要知道，调用System.gc()并不能够保证full gc马上就能被执行。\n所以在后面打代码中，会进行最多9次尝试，看是否有足够的可用堆外内存来分配堆外内存。并且每次尝试之前，都对延迟等待时间，已给JVM足够的时间去完成full gc操作。如果9次尝试后依旧没有足够的可用堆外内存来分配本次堆外内存，则抛出OutOfMemoryError(\"Direct buffer memory”)异常。\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405101406.png)\n\n注意，这里之所以用使用full gc的很重要的一个原因是：System.gc()会对新生代的老生代都会进行内存回收，这样会比较彻底地回收DirectByteBuffer对象以及他们关联的堆外内存.\nDirectByteBuffer对象本身其实是很小的，但是它后面可能关联了一个非常大的堆外内存，因此我们通常称之为冰山对象.\n我们做ygc的时候会将新生代里的不可达的DirectByteBuffer对象及其堆外内存回收了，但是无法对old里的DirectByteBuffer对象及其堆外内存进行回收，这也是我们通常碰到的最大的问题。( 并且堆外内存多用于生命期中等或较长的对象 )\n如果有大量的DirectByteBuffer对象移到了old，但是又一直没有做cms gc或者full gc，而只进行ygc，那么我们的物理内存可能被慢慢耗光，但是我们还不知道发生了什么，因为heap明明剩余的内存还很多(前提是我们禁用了System.gc – JVM参数DisableExplicitGC)。\n\n总的来说，Bits.reserveMemory(size, cap)方法在可用堆外内存不足以分配给当前要创建的堆外内存大小时，会实现以下的步骤来尝试完成本次堆外内存的创建：\n① 触发一次非堵塞的Reference#tryHandlePending(false)。该方法会将已经被JVM垃圾回收的DirectBuffer对象的堆外内存释放。\n② 如果进行一次堆外内存资源回收后，还不够进行本次堆外内存分配的话，则进行 System.gc()。System.gc()会触发一个full gc，但你需要知道，调用System.gc()并不能够保证full gc马上就能被执行。所以在后面打代码中，会进行最多9次尝试，看是否有足够的可用堆外内存来分配堆外内存。并且每次尝试之前，都对延迟等待时间，已给JVM足够的时间去完成full gc操作。\n注意，如果你设置了-XX:+DisableExplicitGC，将会禁用显示GC，这会使System.gc()调用无效。\n③ 如果9次尝试后依旧没有足够的可用堆外内存来分配本次堆外内存，则抛出OutOfMemoryError(\"Direct buffer memory”)异常。\n\n那么可用堆外内存到底是多少了？，即默认堆外存内存有多大：\n① 如果我们没有通过-XX:MaxDirectMemorySize来指定最大的堆外内存。则👇\n② 如果我们没通过-Dsun.nio.MaxDirectMemorySize指定了这个属性，且它不等于-1。则👇\n③ 那么最大堆外内存的值来自于directMemory = Runtime.getRuntime().maxMemory()，这是一个native方法\n\n```\nJNIEXPORT jlong JNICALL\nJava_java_lang_Runtime_maxMemory(JNIEnv *env, jobject this)\n{\n    return JVM_MaxMemory();\n}\n\nJVM_ENTRY_NO_ENV(jlong, JVM_MaxMemory(void))\n  JVMWrapper(\"JVM_MaxMemory\");\n  size_t n = Universe::heap()->max_capacity();\n  return convert_size_t_to_jlong(n);\nJVM_END\n\n```\n\n其中在我们使用CMS GC的情况下也就是我们设置的-Xmx的值里除去一个survivor的大小就是默认的堆外内存的大小了。\n\n#### 堆外内存回收\n\nCleaner是PhantomReference的子类，并通过自身的next和prev字段维护的一个双向链表。PhantomReference的作用在于跟踪垃圾回收过程，并不会对对象的垃圾回收过程造成任何的影响。\n所以cleaner = Cleaner.create(this, new Deallocator(base, size, cap)); 用于对当前构造的DirectByteBuffer对象的垃圾回收过程进行跟踪。\n当DirectByteBuffer对象从pending状态 ——> enqueue状态时，会触发Cleaner的clean()，而Cleaner的clean()的方法会实现通过unsafe对堆外内存的释放。\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405101539.png)\n\n\n\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405101614.png)\n\n\n\n\n👆虽然Cleaner不会调用到Reference.clear()，但Cleaner的clean()方法调用了remove(this)，即将当前Cleaner从Cleaner链表中移除，这样当clean()执行完后，Cleaner就是一个无引用指向的对象了，也就是可被GC回收的对象。\n\nthunk方法：\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405101655.png)\n\n#### 通过配置参数的方式来回收堆外内存\n\n同时我们可以通过-XX:MaxDirectMemorySize来指定最大的堆外内存大小，当使用达到了阈值的时候将调用System.gc()来做一次full gc，以此来回收掉没有被使用的堆外内存。\n\n### 堆外内存那些事\n\n#### 使用堆外内存的原因\n\n*   对垃圾回收停顿的改善\n    因为full gc 意味着彻底回收，彻底回收时，垃圾收集器会对所有分配的堆内内存进行完整的扫描，这意味着一个重要的事实——这样一次垃圾收集对Java应用造成的影响，跟堆的大小是成正比的。过大的堆会影响Java应用的性能。如果使用堆外内存的话，堆外内存是直接受操作系统管理( 而不是虚拟机 )。这样做的结果就是能保持一个较小的堆内内存，以减少垃圾收集对应用的影响。\n*   在某些场景下可以提升程序I/O操纵的性能。少去了将数据从堆内内存拷贝到堆外内存的步骤。\n\n#### 什么情况下使用堆外内存\n\n*   堆外内存适用于生命周期中等或较长的对象。( 如果是生命周期较短的对象，在YGC的时候就被回收了，就不存在大内存且生命周期较长的对象在FGC对应用造成的性能影响 )。\n*   直接的文件拷贝操作，或者I/O操作。直接使用堆外内存就能少去内存从用户内存拷贝到系统内存的操作，因为I/O操作是系统内核内存和设备间的通信，而不是通过程序直接和外设通信的。\n*   同时，还可以使用 池+堆外内存 的组合方式，来对生命周期较短，但涉及到I/O操作的对象进行堆外内存的再使用。( Netty中就使用了该方式 )\n\n#### 堆外内存 VS 内存池\n\n*   内存池：主要用于两类对象：①生命周期较短，且结构简单的对象，在内存池中重复利用这些对象能增加CPU缓存的命中率，从而提高性能；②加载含有大量重复对象的大片数据，此时使用内存池能减少垃圾回收的时间。\n*   堆外内存：它和内存池一样，也能缩短垃圾回收时间，但是它适用的对象和内存池完全相反。内存池往往适用于生命期较短的可变对象，而生命期中等或较长的对象，正是堆外内存要解决的。\n\n#### 堆外内存的特点\n\n*   对于大内存有良好的伸缩性\n*   对垃圾回收停顿的改善可以明显感觉到\n*   在进程间可以共享，减少虚拟机间的复制\n\n#### 堆外内存的一些问题\n\n*   堆外内存回收问题，以及堆外内存的泄漏问题。这个在上面的源码解析已经提到了\n*   堆外内存的数据结构问题：堆外内存最大的问题就是你的数据结构变得不那么直观，如果数据结构比较复杂，就要对它进行串行化（serialization），而串行化本身也会影响性能。另一个问题是由于你可以使用更大的内存，你可能开始担心虚拟内存（即硬盘）的速度对你的影响了。\n\n## 参考文章\n\n[http://lovestblog.cn/blog/2015/05/12/direct-buffer/](https://link.jianshu.com/?t=http://lovestblog.cn/blog/2015/05/12/direct-buffer/)\n\n[http://www.infoq.com/cn/news/2014/12/external-memory-heap-memory](https://link.jianshu.com/?t=http://www.infoq.com/cn/news/2014/12/external-memory-heap-memory)\n\n[http://www.jianshu.com/p/85e931636f27](https://www.jianshu.com/p/85e931636f27)\n"
  },
  {
    "path": "docs/Java/network/Java网络编程与NIO详解：浅谈Linux中Selector的实现原理.md",
    "content": "# 目录\n\n* [目录](#目录)\n  * [概述](#概述)\n  * [Selector的中的重要属性](#selector的中的重要属性)\n  * [Selector 源码解析](#selector-源码解析)\n    * [1、Selector的构建](#1、selector的构建)\n          * [接下来看下 selector.open()：](#接下来看下-selectoropen：)\n    * [EPollSelectorImpl](#epollselectorimpl)\n    * [EPollArrayWrapper](#epollarraywrapper)\n    * [ServerSocketChannel的构建](#serversocketchannel的构建)\n    * [将ServerSocketChannel注册到Selector](#将serversocketchannel注册到selector)\n    * [EPollSelectorImpl. implRegister](#epollselectorimpl-implregister)\n    * [Selection操作](#selection操作)\n    * [epoll原理](#epoll原理)\n  * [后记](#后记)\n  * [参考文章](#参考文章)\n\n\n# 目录\n  * [概述](#概述)\n  * [Selector的中的重要属性](#selector的中的重要属性)\n  * [Selector 源码解析](#selector-源码解析)\n    * [1、Selector的构建](#1、selector的构建)\n          * [接下来看下 selector.open()：](#接下来看下-selectoropen：)\n    * [EPollSelectorImpl](#epollselectorimpl)\n    * [EPollArrayWrapper](#epollarraywrapper)\n    * [ServerSocketChannel的构建](#serversocketchannel的构建)\n    * [将ServerSocketChannel注册到Selector](#将serversocketchannel注册到selector)\n    * [EPollSelectorImpl. implRegister](#epollselectorimpl-implregister)\n    * [Selection操作](#selection操作)\n    * [epoll原理](#epoll原理)\n  * [后记](#后记)\n  * [参考文章](#参考文章)\n\n\n本文转自互联网\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章将同步到我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《不可轻视的Java网络编程》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从计算机网络的基础知识入手，一步步地学习Java网络基础，从socket到nio、bio、aio和netty等网络编程知识，并且进行实战，网络编程是每一个Java后端工程师必须要学习和理解的知识点，进一步来说，你还需要掌握Linux中的网络编程原理，包括IO模型、网络编程框架netty的进阶原理，才能更完整地了解整个Java网络编程的知识体系，形成自己的知识框架。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供部分知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n## 概述\n\nSelector是NIO中实现I/O多路复用的关键类。Selector实现了通过一个线程管理多个Channel，从而管理多个网络连接的目的。\n\nChannel代表这一个网络连接通道，我们可以将Channel注册到Selector中以实现Selector对其的管理。一个Channel可以注册到多个不同的Selector中。\n\n当Channel注册到Selector后会返回一个SelectionKey对象，该SelectionKey对象则代表这这个Channel和它注册的Selector间的关系。并且SelectionKey中维护着两个很重要的属性：interestOps、readyOps\ninterestOps是我们希望Selector监听Channel的哪些事件。\n\n我们将我们感兴趣的事件设置到该字段，这样在selection操作时，当发现该Channel有我们所感兴趣的事件发生时，就会将我们感兴趣的事件再设置到readyOps中，这样我们就能得知是哪些事件发生了以做相应处理。\n\n## Selector的中的重要属性\n\nSelector中维护3个特别重要的SelectionKey集合，分别是\n\n*   keys：所有注册到Selector的Channel所表示的SelectionKey都会存在于该集合中。keys元素的添加会在Channel注册到Selector时发生。\n*   selectedKeys：该集合中的每个SelectionKey都是其对应的Channel在上一次操作selection期间被检查到至少有一种SelectionKey中所感兴趣的操作已经准备好被处理。该集合是keys的一个子集。\n*   cancelledKeys：执行了取消操作的SelectionKey会被放入到该集合中。该集合是keys的一个子集。\n\n下面的源码解析会说明上面3个集合的用处\n\n## Selector 源码解析\n\n下面我们通过一段对Selector的使用流程讲解来进一步深入其实现原理。\n首先先来段Selector最简单的使用片段\n\n```\nServerSocketChannel serverChannel = ServerSocketChannel.open();\nserverChannel.configureBlocking(false);\nint port = 5566;          \nserverChannel.socket().bind(new InetSocketAddress(port));\nSelector selector = Selector.open();\nserverChannel.register(selector, SelectionKey.OP_ACCEPT);\nwhile(true){\n    int n = selector.select();\n    if(n > 0) {\n        Iterator<SelectionKey> iter = selector.selectedKeys().iterator();\n        while (iter.hasNext()) {\n            SelectionKey selectionKey = iter.next();\n            ......\n            iter.remove();\n        }\n    }\n}\n\n```\n\n### 1、Selector的构建\n\nSocketChannel、ServerSocketChannel和Selector的实例初始化都通过SelectorProvider类实现。\n\nServerSocketChannel.open();\n\n```\npublic static ServerSocketChannel open() throws IOException {\n    return SelectorProvider.provider().openServerSocketChannel();\n}\n\n```\n\nSocketChannel.open();\n\n```\npublic static SocketChannel open() throws IOException {\n    return SelectorProvider.provider().openSocketChannel();\n}\n\n```\n\nSelector.open();\n\n```\npublic static Selector open() throws IOException {\n    return SelectorProvider.provider().openSelector();\n}\n\n```\n\n我们来进一步的了解下SelectorProvider.provider()\n\n```\npublic static SelectorProvider provider() {\n    synchronized (lock) {\n        if (provider != null)\n            return provider;\n        return AccessController.doPrivileged(\n            new PrivilegedAction<>() {\n                public SelectorProvider run() {\n                        if (loadProviderFromProperty())\n                            return provider;\n                        if (loadProviderAsService())\n                            return provider;\n                        provider = sun.nio.ch.DefaultSelectorProvider.create();\n                        return provider;\n                    }\n                });\n    }\n}\n\n```\n\n① 如果配置了“java.nio.channels.spi.SelectorProvider”属性，则通过该属性值load对应的SelectorProvider对象，如果构建失败则抛异常。\n② 如果provider类已经安装在了对系统类加载程序可见的jar包中，并且该jar包的源码目录META-INF/services包含有一个java.nio.channels.spi.SelectorProvider提供类配置文件，则取文件中第一个类名进行load以构建对应的SelectorProvider对象，如果构建失败则抛异常。\n③ 如果上面两种情况都不存在，则返回系统默认的SelectorProvider，即，sun.nio.ch.DefaultSelectorProvider.create();\n④ 随后在调用该方法，即SelectorProvider.provider()。则返回第一次调用的结果。\n\n不同系统对应着不同的sun.nio.ch.DefaultSelectorProvider\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405100409.png)\n\n这里我们看linux下面的sun.nio.ch.DefaultSelectorProvider\n\n```\npublic class DefaultSelectorProvider {\n\n    /**\n     * Prevent instantiation.\n     */\n    private DefaultSelectorProvider() { }\n\n    /**\n     * Returns the default SelectorProvider.\n     */\n    public static SelectorProvider create() {\n        return new sun.nio.ch.EPollSelectorProvider();\n    }\n\n}\n\n```\n\n可以看见，linux系统下sun.nio.ch.DefaultSelectorProvider.create(); 会生成一个sun.nio.ch.EPollSelectorProvider类型的SelectorProvider，这里对应于linux系统的epoll\n\n###### 接下来看下 selector.open()：\n\n```\n    /**\n     * Opens a selector.\n     *\n     * <p> The new selector is created by invoking the {@link\n     * java.nio.channels.spi.SelectorProvider#openSelector openSelector} method\n     * of the system-wide default {@link\n     * java.nio.channels.spi.SelectorProvider} object.  </p>\n     *\n     * @return  A new selector\n     *\n     * @throws  IOException\n     *          If an I/O error occurs\n     */\n    public static Selector open() throws IOException {\n        return SelectorProvider.provider().openSelector();\n    }\n\n```\n\n在得到sun.nio.ch.EPollSelectorProvider后调用openSelector()方法构建Selector，这里会构建一个EPollSelectorImpl对象。\n\n### EPollSelectorImpl\n\n```\nclass EPollSelectorImpl\n    extends SelectorImpl\n{\n\n    // File descriptors used for interrupt\n    protected int fd0;\n    protected int fd1;\n\n    // The poll object\n    EPollArrayWrapper pollWrapper;\n\n    // Maps from file descriptors to keys\n    private Map<Integer,SelectionKeyImpl> fdToKey;\n\n```\n\n```\nEPollSelectorImpl(SelectorProvider sp) throws IOException {\n    super(sp);\n    long pipeFds = IOUtil.makePipe(false);\n    fd0 = (int) (pipeFds >>> 32);\n    fd1 = (int) pipeFds;\n    try {\n        pollWrapper = new EPollArrayWrapper();\n        pollWrapper.initInterrupt(fd0, fd1);\n        fdToKey = new HashMap<>();\n    } catch (Throwable t) {\n        try {\n            FileDispatcherImpl.closeIntFD(fd0);\n        } catch (IOException ioe0) {\n            t.addSuppressed(ioe0);\n        }\n        try {\n            FileDispatcherImpl.closeIntFD(fd1);\n        } catch (IOException ioe1) {\n            t.addSuppressed(ioe1);\n        }\n        throw t;\n    }\n}\n\n```\n\nEPollSelectorImpl构造函数完成：\n① EPollArrayWrapper的构建，EpollArrayWapper将Linux的epoll相关系统调用封装成了native方法供EpollSelectorImpl使用。\n② 通过EPollArrayWrapper向epoll注册中断事件\n\n```\nvoid initInterrupt(int fd0, int fd1) {\n    outgoingInterruptFD = fd1;\n    incomingInterruptFD = fd0;\n    epollCtl(epfd, EPOLL_CTL_ADD, fd0, EPOLLIN);\n}\n\n```\n\n③ fdToKey：构建文件描述符-SelectionKeyImpl映射表，所有注册到selector的channel对应的SelectionKey和与之对应的文件描述符都会放入到该映射表中。\n\n### EPollArrayWrapper\n\nEPollArrayWrapper完成了对epoll文件描述符的构建，以及对linux系统的epoll指令操纵的封装。维护每次selection操作的结果，即epoll_wait结果的epoll_event数组。\nEPollArrayWrapper操纵了一个linux系统下epoll_event结构的本地数组。\n\n```\n* typedef union epoll_data {\n*     void *ptr;\n*     int fd;\n*     __uint32_t u32;\n*     __uint64_t u64;\n*  } epoll_data_t;\n*\n* struct epoll_event {\n*     __uint32_t events;\n*     epoll_data_t data;\n* };\n\n```\n\nepoll_event的数据成员(epoll_data_t data)包含有与通过epoll_ctl将文件描述符注册到epoll时设置的数据相同的数据。这里data.fd为我们注册的文件描述符。这样我们在处理事件的时候持有有效的文件描述符了。\n\nEPollArrayWrapper将Linux的epoll相关系统调用封装成了native方法供EpollSelectorImpl使用。\n\n```\nprivate native int epollCreate();\nprivate native void epollCtl(int epfd, int opcode, int fd, int events);\nprivate native int epollWait(long pollAddress, int numfds, long timeout,\n                             int epfd) throws IOException;\n\n```\n\n上述三个native方法就对应Linux下epoll相关的三个系统调用\n\n```\n// The fd of the epoll driver\nprivate final int epfd;\n\n // The epoll_event array for results from epoll_wait\nprivate final AllocatedNativeObject pollArray;\n\n// Base address of the epoll_event array\nprivate final long pollArrayAddress;\n\n```\n\n```\n// 用于存储已经注册的文件描述符和其注册等待改变的事件的关联关系。在epoll_wait操作就是要检测这里文件描述法注册的事件是否有发生。\nprivate final byte[] eventsLow = new byte[MAX_UPDATE_ARRAY_SIZE];\nprivate final Map<Integer,Byte> eventsHigh = new HashMap<>();\n\n```\n\n```\nEPollArrayWrapper() throws IOException {\n    // creates the epoll file descriptor\n    epfd = epollCreate();\n\n    // the epoll_event array passed to epoll_wait\n    int allocationSize = NUM_EPOLLEVENTS * SIZE_EPOLLEVENT;\n    pollArray = new AllocatedNativeObject(allocationSize, true);\n    pollArrayAddress = pollArray.address();\n}\n\n```\n\nEPoolArrayWrapper构造函数，创建了epoll文件描述符。构建了一个用于存放epoll_wait返回结果的epoll_event数组。\n\n### ServerSocketChannel的构建\n\nServerSocketChannel.open();\n\n返回ServerSocketChannelImpl对象，构建linux系统下ServerSocket的文件描述符。\n\n```\n// Our file descriptor\nprivate final FileDescriptor fd;\n\n// fd value needed for dev/poll. This value will remain valid\n// even after the value in the file descriptor object has been set to -1\nprivate int fdVal;\n\n```\n\n```\nServerSocketChannelImpl(SelectorProvider sp) throws IOException {\n    super(sp);\n    this.fd =  Net.serverSocket(true);\n    this.fdVal = IOUtil.fdVal(fd);\n    this.state = ST_INUSE;\n}\n\n```\n\n### 将ServerSocketChannel注册到Selector\n\nserverChannel.register(selector, SelectionKey.OP_ACCEPT);\n\n```\npublic final SelectionKey register(Selector sel, int ops,\n                                   Object att)\n    throws ClosedChannelException\n{\n    synchronized (regLock) {\n        if (!isOpen())\n            throw new ClosedChannelException();\n        if ((ops & ~validOps()) != 0)\n            throw new IllegalArgumentException();\n        if (blocking)\n            throw new IllegalBlockingModeException();\n        SelectionKey k = findKey(sel);\n        if (k != null) {\n            k.interestOps(ops);\n            k.attach(att);\n        }\n        if (k == null) {\n            // New registration\n            synchronized (keyLock) {\n                if (!isOpen())\n                    throw new ClosedChannelException();\n                k = ((AbstractSelector)sel).register(this, ops, att);\n                addKey(k);\n            }\n        }\n        return k;\n    }\n}\n\n```\n\n```\nprotected final SelectionKey register(AbstractSelectableChannel ch,\n                                      int ops,\n                                      Object attachment)\n{\n    if (!(ch instanceof SelChImpl))\n        throw new IllegalSelectorException();\n    SelectionKeyImpl k = new SelectionKeyImpl((SelChImpl)ch, this);\n    k.attach(attachment);\n    synchronized (publicKeys) {\n        implRegister(k);\n    }\n    k.interestOps(ops);\n    return k;\n}\n\n```\n\n① 构建代表channel和selector间关系的SelectionKey对象\n② implRegister(k)将channel注册到epoll中\n③ k.interestOps(int) 完成下面两个操作：\na) 会将注册的感兴趣的事件和其对应的文件描述存储到EPollArrayWrapper对象的eventsLow或eventsHigh中，这是给底层实现epoll_wait时使用的。\nb) 同时该操作还会将设置SelectionKey的interestOps字段，这是给我们程序员获取使用的。\n\n### EPollSelectorImpl. implRegister\n\n```\nprotected void implRegister(SelectionKeyImpl ski) {\n    if (closed)\n        throw new ClosedSelectorException();\n    SelChImpl ch = ski.channel;\n    int fd = Integer.valueOf(ch.getFDVal());\n    fdToKey.put(fd, ski);\n    pollWrapper.add(fd);\n    keys.add(ski);\n}\n\n```\n\n① 将channel对应的fd(文件描述符)和对应的SelectionKeyImpl放到fdToKey映射表中。\n② 将channel对应的fd(文件描述符)添加到EPollArrayWrapper中，并强制初始化fd的事件为0 ( 强制初始更新事件为0，因为该事件可能存在于之前被取消过的注册中。)\n③ 将selectionKey放入到keys集合中。\n\n### Selection操作\n\nselection操作有3中类型：\n① select()：该方法会一直阻塞直到至少一个channel被选择(即，该channel注册的事件发生了)为止，除非当前线程发生中断或者selector的wakeup方法被调用。\n② select(long time)：该方法和select()类似，该方法也会导致阻塞直到至少一个channel被选择(即，该channel注册的事件发生了)为止，除非下面3种情况任意一种发生：a) 设置的超时时间到达；b) 当前线程发生中断；c) selector的wakeup方法被调用\n③ selectNow()：该方法不会发生阻塞，如果没有一个channel被选择也会立即返回。\n\n我们主要来看看select()的实现 ：int n = selector.select();\n\n```\npublic int select() throws IOException {\n    return select(0);\n}\n\n```\n\n最终会调用到EPollSelectorImpl的doSelect\n\n```\nprotected int doSelect(long timeout) throws IOException {\n    if (closed)\n        throw new ClosedSelectorException();\n    processDeregisterQueue();\n    try {\n        begin();\n        pollWrapper.poll(timeout);\n    } finally {\n        end();\n    }\n    processDeregisterQueue();\n    int numKeysUpdated = updateSelectedKeys();\n    if (pollWrapper.interrupted()) {\n        // Clear the wakeup pipe\n        pollWrapper.putEventOps(pollWrapper.interruptedIndex(), 0);\n        synchronized (interruptLock) {\n            pollWrapper.clearInterrupted();\n            IOUtil.drain(fd0);\n            interruptTriggered = false;\n        }\n    }\n    return numKeysUpdated;\n}\n\n```\n\n① 先处理注销的selectionKey队列\n② 进行底层的epoll_wait操作\n③ 再次对注销的selectionKey队列进行处理\n④ 更新被选择的selectionKey\n\n先来看processDeregisterQueue():\n\n```\nvoid processDeregisterQueue() throws IOException {\n    Set var1 = this.cancelledKeys();\n    synchronized(var1) {\n        if (!var1.isEmpty()) {\n            Iterator var3 = var1.iterator();\n\n            while(var3.hasNext()) {\n                SelectionKeyImpl var4 = (SelectionKeyImpl)var3.next();\n\n                try {\n                    this.implDereg(var4);\n                } catch (SocketException var12) {\n                    IOException var6 = new IOException(\"Error deregistering key\");\n                    var6.initCause(var12);\n                    throw var6;\n                } finally {\n                    var3.remove();\n                }\n            }\n        }\n\n    }\n}\n\n```\n\n从cancelledKeys集合中依次取出注销的SelectionKey，执行注销操作，将处理后的SelectionKey从cancelledKeys集合中移除。执行processDeregisterQueue()后cancelledKeys集合会为空。\n\n```\nprotected void implDereg(SelectionKeyImpl ski) throws IOException {\n    assert (ski.getIndex() >= 0);\n    SelChImpl ch = ski.channel;\n    int fd = ch.getFDVal();\n    fdToKey.remove(Integer.valueOf(fd));\n    pollWrapper.remove(fd);\n    ski.setIndex(-1);\n    keys.remove(ski);\n    selectedKeys.remove(ski);\n    deregister((AbstractSelectionKey)ski);\n    SelectableChannel selch = ski.channel();\n    if (!selch.isOpen() && !selch.isRegistered())\n        ((SelChImpl)selch).kill();\n}\n\n```\n\n注销会完成下面的操作：\n① 将已经注销的selectionKey从fdToKey( 文件描述与SelectionKeyImpl的映射表 )中移除\n② 将selectionKey所代表的channel的文件描述符从EPollArrayWrapper中移除\n③ 将selectionKey从keys集合中移除，这样下次selector.select()就不会再将该selectionKey注册到epoll中监听\n④ 也会将selectionKey从对应的channel中注销\n⑤ 最后如果对应的channel已经关闭并且没有注册其他的selector了，则将该channel关闭\n完成👆的操作后，注销的SelectionKey就不会出现先在keys、selectedKeys以及cancelKeys这3个集合中的任何一个。\n\n接着我们来看EPollArrayWrapper.poll(timeout)：\n\n```\nint poll(long timeout) throws IOException {\n    updateRegistrations();\n    updated = epollWait(pollArrayAddress, NUM_EPOLLEVENTS, timeout, epfd);\n    for (int i=0; i<updated; i++) {\n        if (getDescriptor(i) == incomingInterruptFD) {\n            interruptedIndex = i;\n            interrupted = true;\n            break;\n        }\n    }\n    return updated;\n}\n\n```\n\nupdateRegistrations()方法会将已经注册到该selector的事件(eventsLow或eventsHigh)通过调用epollCtl(epfd, opcode, fd, events); 注册到linux系统中。\n这里epollWait就会调用linux底层的epoll_wait方法，并返回在epoll_wait期间有事件触发的entry的个数\n\n再看updateSelectedKeys()：\n\n```\nprivate int updateSelectedKeys() {\n    int entries = pollWrapper.updated;\n    int numKeysUpdated = 0;\n    for (int i=0; i<entries; i++) {\n        int nextFD = pollWrapper.getDescriptor(i);\n        SelectionKeyImpl ski = fdToKey.get(Integer.valueOf(nextFD));\n        // ski is null in the case of an interrupt\n        if (ski != null) {\n            int rOps = pollWrapper.getEventOps(i);\n            if (selectedKeys.contains(ski)) {\n                if (ski.channel.translateAndSetReadyOps(rOps, ski)) {\n                    numKeysUpdated++;\n                }\n            } else {\n                ski.channel.translateAndSetReadyOps(rOps, ski);\n                if ((ski.nioReadyOps() & ski.nioInterestOps()) != 0) {\n                    selectedKeys.add(ski);\n                    numKeysUpdated++;\n                }\n            }\n        }\n    }\n    return numKeysUpdated;\n}\n\n```\n\n该方法会从通过EPollArrayWrapper pollWrapper 以及 fdToKey( 构建文件描述符-SelectorKeyImpl映射表 )来获取有事件触发的SelectionKeyImpl对象，然后将SelectionKeyImpl放到selectedKey集合( 有事件触发的selectionKey集合，可以通过selector.selectedKeys()方法获得 )中，即selectedKeys。并重新设置SelectionKeyImpl中相关的readyOps值。\n\n\n但是，这里要注意两点：\n\n\n① 如果SelectionKeyImpl已经存在于selectedKeys集合中，并且发现触发的事件已经存在于readyOps中了，则不会使numKeysUpdated++；这样会使得我们无法得知该事件的变化。\n\n\n👆这点说明了为什么我们要在每次从selectedKey中获取到Selectionkey后，将其从selectedKey集合移除，就是为了当有事件触发使selectionKey能正确到放入selectedKey集合中，并正确的通知给调用者。\n\n\n再者，如果不将已经处理的SelectionKey从selectedKey集合中移除，那么下次有新事件到来时，在遍历selectedKey集合时又会遍历到这个SelectionKey，这个时候就很可能出错了。比如，如果没有在处理完OP_ACCEPT事件后将对应SelectionKey从selectedKey集合移除，那么下次遍历selectedKey集合时，处理到到该SelectionKey，相应的ServerSocketChannel.accept()将返回一个空(null)的SocketChannel。\n\n\n② 如果发现channel所发生I/O事件不是当前SelectionKey所感兴趣，则不会将SelectionKeyImpl放入selectedKeys集合中，也不会使numKeysUpdated++\n\n### epoll原理\n\nselect，poll，epoll都是IO多路复用的机制。I/O多路复用就是通过一种机制，一个进程可以监视多个描述符，一旦某个描述符就绪（一般是读就绪或者写就绪），能够通知程序进行相应的读写操作。但select，poll，epoll本质上都是同步I/O，因为他们都需要在读写事件就绪后自己负责进行读写，也就是说这个读写过程是阻塞的，而异步I/O则无需自己负责进行读写，异步I/O的实现会负责把数据从内核拷贝到用户空间。\n\nepoll是Linux下的一种IO多路复用技术，可以非常高效的处理数以百万计的socket句柄。\n\n在 select/poll中，进程只有在调用一定的方法后，内核才对所有监视的文件描述符进行扫描，而epoll事先通过epoll_ctl()来注册一 个文件描述符，一旦基于某个文件描述符就绪时，内核会采用类似callback的回调机制，迅速激活这个文件描述符，当进程调用epoll_wait() 时便得到通知。(此处去掉了遍历文件描述符，而是通过监听回调的的机制。这正是epoll的魅力所在。)\n如果没有大量的idle -connection或者dead-connection，epoll的效率并不会比select/poll高很多，但是当遇到大量的idle- connection，就会发现epoll的效率大大高于select/poll。\n\n注意：linux下Selector底层是通过epoll来实现的，当创建好epoll句柄后，它就会占用一个fd值，在linux下如果查看/proc/进程id/fd/，是能够看到这个fd的，所以在使用完epoll后，必须调用close()关闭，否则可能导致fd被耗尽。\n\n先看看使用c封装的3个epoll系统调用:\n\n*   **int epoll_create(int size)**\n    epoll_create建立一个epoll对象。参数size是内核保证能够正确处理的最大句柄数，多于这个最大数时内核可不保证效果。\n*   **int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)**\n    epoll_ctl可以操作epoll_create创建的epoll，如将socket句柄加入到epoll中让其监控，或把epoll正在监控的某个socket句柄移出epoll。\n*   **int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout)**\n    epoll_wait在调用时，在给定的timeout时间内，所监控的句柄中有事件发生时，就返回用户态的进程。\n\n    大概看看epoll内部是怎么实现的：\n\n1.  epoll初始化时，会向内核注册一个文件系统，用于存储被监控的句柄文件，调用epoll_create时，会在这个文件系统中创建一个file节点。同时epoll会开辟自己的内核高速缓存区，以红黑树的结构保存句柄，以支持快速的查找、插入、删除。还会再建立一个list链表，用于存储准备就绪的事件。\n2.  当执行epoll_ctl时，除了把socket句柄放到epoll文件系统里file对象对应的红黑树上之外，还会给内核中断处理程序注册一个回调函数，告诉内核，如果这个句柄的中断到了，就把它放到准备就绪list链表里。所以，当一个socket上有数据到了，内核在把网卡上的数据copy到内核中后，就把socket插入到就绪链表里。\n3.  当epoll_wait调用时，仅仅观察就绪链表里有没有数据，如果有数据就返回，否则就sleep，超时时立刻返回。\n\n    epoll的两种工作模式：\n\n*   LT：level-trigger，水平触发模式，只要某个socket处于readable/writable状态，无论什么时候进行epoll_wait都会返回该socket。\n*   ET：edge-trigger，边缘触发模式，只有某个socket从unreadable变为readable或从unwritable变为writable时，epoll_wait才会返回该socket。\n\nsocket读数据\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405100605.png)\n\n\nsocket写数据\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405100624.png)\n\n最后顺便说下在Linux系统中JDK NIO使用的是 LT ，而Netty epoll使用的是 ET。\n\n## 后记\n\n因为本人对计算机系统组成以及C语言等知识比较欠缺，因为文中相关知识点的表示也相当“肤浅”，如有不对不妥的地方望读者指出。同时我也会继续加强对该方面知识点的学习~\n\n## 参考文章\n\n[http://www.jianshu.com/p/0d497fe5484a](https://www.jianshu.com/p/0d497fe5484a)\n[http://remcarpediem.com/2017/04/02/Netty](https://link.jianshu.com/?t=http://remcarpediem.com/2017/04/02/Netty)源码-三-I-O模型和Java-NIO底层原理/\n\n"
  },
  {
    "path": "docs/Java/network/Java网络编程与NIO详解：深度解读Tomcat中的NIO模型.md",
    "content": "# 目录\n\n* [目录](#目录)\n  * [一、I/O复用模型解读](#一、io复用模型解读)\n  * [二、TOMCAT对IO模型的支持](#二、tomcat对io模型的支持)\n  * [三、TOMCAT中NIO的配置与使用](#三、tomcat中nio的配置与使用)\n  * [四、NioEndpoint组件关系图解读](#四、nioendpoint组件关系图解读)\n  * [五、NioEndpoint执行序列图](#五、nioendpoint执行序列图)\n  * [六、NioEndpoint源码解读](#六、nioendpoint源码解读)\n  * [七、关于性能](#七、关于性能)\n  * [八、总结](#八、总结)\n\n\n# 目录\n  * [一、I/O复用模型解读](#一、io复用模型解读)\n  * [二、TOMCAT对IO模型的支持](#二、tomcat对io模型的支持)\n  * [三、TOMCAT中NIO的配置与使用](#三、tomcat中nio的配置与使用)\n  * [四、NioEndpoint组件关系图解读](#四、nioendpoint组件关系图解读)\n  * [五、NioEndpoint执行序列图](#五、nioendpoint执行序列图)\n  * [六、NioEndpoint源码解读](#六、nioendpoint源码解读)\n  * [七、关于性能](#七、关于性能)\n  * [八、总结](#八、总结)\n\n本文转自：http://www.sohu.com/a/203838233_827544\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章将同步到我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《不可轻视的Java网络编程》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从计算机网络的基础知识入手，一步步地学习Java网络基础，从socket到nio、bio、aio和netty等网络编程知识，并且进行实战，网络编程是每一个Java后端工程师必须要学习和理解的知识点，进一步来说，你还需要掌握Linux中的网络编程原理，包括IO模型、网络编程框架netty的进阶原理，才能更完整地了解整个Java网络编程的知识体系，形成自己的知识框架。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供部分知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n> 摘要: I/O复用模型，是同步非阻塞，这里的非阻塞是指I/O读写，对应的是recvfrom操作，因为数据报文已经准备好，无需阻塞。\n\n说它是同步，是因为，这个执行是在一个线程里面执行的。有时候，还会说它又是阻塞的，实际上是指阻塞在select上面，必须等到读就绪、写就绪等网络事件。\n\n## 一、I/O复用模型解读\n\nTomcat的NIO是基于I/O复用来实现的。对这点一定要清楚，不然我们的讨论就不在一个逻辑线上。下面这张图学习过I/O模型知识的一般都见过，出自《UNIX网络编程》，I/O模型一共有阻塞式I/O，非阻塞式I/O，I/O复用(select/poll/epoll)，信号驱动式I/O和异步I/O。这篇文章讲的是I/O复用。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405104442.png)\nIO复用.png\n\n这里先来说下用户态和内核态，直白来讲，如果线程执行的是用户代码，当前线程处在用户态，如果线程执行的是内核里面的代码，当前线程处在内核态。更深层来讲，操作系统为代码所处的特权级别分了4个级别。\n\n不过现代操作系统只用到了0和3两个级别。0和3的切换就是用户态和内核态的切换。更详细的可参照《深入理解计算机操作系统》。I/O复用模型，是同步非阻塞，这里的非阻塞是指I/O读写，对应的是recvfrom操作，因为数据报文已经准备好，无需阻塞。\n\n说它是同步，是因为，这个执行是在一个线程里面执行的。有时候，还会说它又是阻塞的，实际上是指阻塞在select上面，必须等到读就绪、写就绪等网络事件。有时候我们又说I/O复用是多路复用，这里的多路是指N个连接，每一个连接对应一个channel，或者说多路就是多个channel。\n\n复用，是指多个连接复用了一个线程或者少量线程(在Tomcat中是Math.min(2,Runtime.getRuntime().availableProcessors()))。\n\n上面提到的网络事件有连接就绪，接收就绪，读就绪，写就绪四个网络事件。I/O复用主要是通过Selector复用器来实现的，可以结合下面这个图理解上面的叙述。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405104504.png)\nSelector图解.png\n\n## 二、TOMCAT对IO模型的支持\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405104517.png)\n\ntomcat支持IO类型图.png\n\ntomcat从6以后开始支持NIO模型，实现是基于JDK的java.nio包。这里可以看到对read body 和response body是Blocking的。关于这点在第6.3节源代码阅读有重点介绍。\n\n## 三、TOMCAT中NIO的配置与使用\n\n在Connector节点配置protocol=\"org.apache.coyote.http11.Http11NioProtocol\"，Http11NioProtocol协议下默认最大连接数是10000，也可以重新修改maxConnections的值，同时我们可以设置最大线程数maxThreads，这里设置的最大线程数就是Excutor的线程池的大小。\n\n在BIO模式下实际上是没有maxConnections，即使配置也不会生效，BIO模式下的maxConnections是保持跟maxThreads大小一致，因为它是一请求一线程模式。\n\n## 四、NioEndpoint组件关系图解读\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405104543.png)\n\ntomcatnio组成.png\n\n我们要理解tomcat的nio最主要就是对NioEndpoint的理解。它一共包含LimitLatch、Acceptor、Poller、SocketProcessor、Excutor5个部分。\n\nLimitLatch是连接控制器，它负责维护连接数的计算，nio模式下默认是10000，达到这个阈值后，就会拒绝连接请求。Acceptor负责接收连接，默认是1个线程来执行，将请求的事件注册到事件列表。\n\n有Poller来负责轮询，Poller线程数量是cpu的核数Math.min(2,Runtime.getRuntime().availableProcessors())。由Poller将就绪的事件生成SocketProcessor同时交给Excutor去执行。Excutor线程池的大小就是我们在Connector节点配置的maxThreads的值。\n\n在Excutor的线程中，会完成从socket中读取http request，解析成HttpServletRequest对象，分派到相应的servlet并完成逻辑，然后将response通过socket发回client。\n\n在从socket中读数据和往socket中写数据的过程，并没有像典型的非阻塞的NIO的那样，注册OP_READ或OP_WRITE事件到主Selector，而是直接通过socket完成读写，这时是阻塞完成的，但是在timeout控制上，使用了NIO的Selector机制，但是这个Selector并不是Poller线程维护的主Selector，而是BlockPoller线程中维护的Selector，称之为辅Selector。详细源代码可以参照 第6.3节。\n\n## 五、NioEndpoint执行序列图\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405104621.png)\ntomcatnio序列图.png\n\n在下一小节NioEndpoint源码解读中我们将对步骤1-步骤11依次找到对应的代码来说明。\n\n## 六、NioEndpoint源码解读\n\n6.1、初始化\n\n无论是BIO还是NIO，开始都会初始化连接限制，不可能无限增大，NIO模式下默认是10000。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405104637.png)\n**6.2、步骤解读**\n\n下面我们着重叙述跟NIO相关的流程，共分为11个步骤，分别对应上面序列图中的步骤。\n\n**步骤1**：绑定IP地址及端口，将ServerSocketChannel设置为阻塞。\n\n这里为什么要设置成阻塞呢，我们一直都在说非阻塞。Tomcat的设计初衷主要是为了操作方便。这样这里就跟BIO模式下一样了。只不过在BIO下这里返回的是\n\nSocket，NIO下这里返回的是SocketChannel。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405104855.png)\n**步骤2**：启动接收线程\n\n\n**步骤3**：ServerSocketChannel.accept()接收新连接\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405104937.png)\n\n**步骤4**：将接收到的链接通道设置为非阻塞\n\n**步骤5**：构造NioChannel对象\n\n**步骤6**：register注册到轮询线程\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405104957.png)\n\n**步骤7**：构造PollerEvent，并添加到事件队列\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405105014.png)\n\n**步骤8**：启动轮询线程\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405105027.png)\n\n**步骤9**：取出队列中新增的PollerEvent并注册到Selector\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405105043.png)\n\n**步骤10**：Selector.select()\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405105057.png)\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405105110.png)\n**步骤11**：根据选择的SelectionKey构造SocketProcessor提交到请求处理线程\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405105154.png)\n**6.3、NioBlockingSelector和BlockPoller介绍**\n\n上面的序列图有个地方我没有描述，就是NioSelectorPool这个内部类，是因为在整体理解tomcat的nio上面在序列图里面不包括它更好理解。\n\n在有了上面的基础后，我们在来说下NioSelectorPool这个类，对更深层了解Tomcat的NIO一定要知道它的作用。NioEndpoint对象中维护了一个NioSelecPool对象，这个NioSelectorPool中又维护了一个BlockPoller线程，这个线程就是基于辅Selector进行NIO的逻辑。\n\n以执行servlet后，得到response，往socket中写数据为例，最终写的过程调用NioBlockingSelector的write方法。代码如下：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405105223.png)\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405105237.png)\n\n也就是说当socket.write()返回0时，说明网络状态不稳定，这时将socket注册OP_WRITE事件到辅Selector，由BlockPoller线程不断轮询这个辅Selector，直到发现这个socket的写状态恢复了，通过那个倒数计数器，通知Worker线程继续写socket动作。看一下BlockSelector线程的代码逻辑：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405105251.png)\n\n使用这个辅Selector主要是减少线程间的切换，同时还可减轻主Selector的负担。\n\n## 七、关于性能\n\n下面这份报告是我们压测的一个结果，跟想象的是不是不太一样？几乎没有差别，实际上NIO优化的是I/O的读写，如果瓶颈不在这里的话，比如传输字节数很小的情况下，BIO和NIO实际上是没有差别的。\n\nNIO的优势更在于用少量的线程hold住大量的连接。还有一点，我们在压测的过程中，遇到在NIO模式下刚开始的一小段时间内容，会有错误，这是因为一般的压测工具是基于一种长连接，也就是说比如模拟1000并发，那么同时建立1000个连接，下一时刻再发送请求就是基于先前的这1000个连接来发送，还有TOMCAT的NIO处理是有POLLER线程来接管的，它的线程数一般等于CPU的核数，如果一瞬间有大量并发过来，POLLER也会顿时处理不过来。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405105304.png)\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405105318.png)\n\n## 八、总结\n\nNIO只是优化了网络IO的读写，如果系统的瓶颈不在这里，比如每次读取的字节说都是500b，那么BIO和NIO在性能上没有区别。NIO模式是最大化压榨CPU，把时间片都更好利用起来。\n\n对于操作系统来说，线程之间上下文切换的开销很大，而且每个线程都要占用系统的一些资源如内存，有关线程资源可参照这篇文章《一台java服务器可以跑多少个线程》。\n\n因此，使用的线程越少越好。而I/O复用模型正是利用少量的线程来管理大量的连接。在对于维护大量长连接的应用里面更适合用基于I/O复用模型NIO，比如web qq这样的应用。所以我们要清楚系统的瓶颈是I/O还是CPU的计算\n"
  },
  {
    "path": "docs/JavaWeb/JavaWeb技术总结.md",
    "content": "# 目录\n  * [Servlet及相关类](#servlet及相关类)\n  * [Jsp和ViewResolver](#jsp和viewresolver)\n  * [filter，listener](#filter，listener)\n  * [web.xml](#webxml)\n  * [war包](#war包)\n  * [tomcat基础](#tomcat基础)\n  * [log4j](#log4j)\n  * [数据库驱动和连接池](#数据库驱动和连接池)\n  * [单元测试](#单元测试)\n  * [Maven](#maven)\n  * [Git](#git)\n  * [Json和xml](#json和xml)\n  * [hibernate和mybatis](#hibernate和mybatis)\n\n\n    \n这篇总结主要是基于我之前两个系列的文章而来。主要是把重要的知识点用自己的话说了一遍，可能会有一些错误，还望见谅和指点。谢谢\n\n更多详细内容可以查看我的专栏文章：\n\nJavaWeb技术世界\n\nhttps://blog.csdn.net/column/details/21850.html\n\n\n<!-- more -->\n\n## Servlet及相关类\n\nservlet是一个接口，它的实现类有GenericServlet，而httpservlet是GenericServlet的一个子类，一般我们都会使用这个类。\n\nservletconfig是用于保存servlet配置信息的数据结构，而servletcontext则负责保持servlet的上下文，web应用启动时加载web.xml信息于servletconfig中。\n\n## Jsp和ViewResolver\n\njsp页面需要编译成class文件并通过tomcat的类加载器进行加载，形成servlet实例，请求到来时实际上执行的是servlet代码，然后最终再通过viewresolver渲染成页面。\n\n## filter，listener\n\nfilter是过滤器，也需要在web.xml中配置，是责任链式的调用，在servlet执行service方法前执行。\nlistener则是监听器，由于容器组件都实现了lifecycle接口，所以可以在组件上添加监听器来控制生命周期。\n\n## web.xml\n\nweb.xml用来配置servlet和servlet的配置信息，listener和filter。也可以配置静态文件的目录等。\n\n## war包\n\nwaWAR包\nWAR(Web Archive file)网络应用程序文件，是与平台无关的文件格式，它允许将许多文件组合成一个压缩文件。war专用在web方面 。\n\nJAVA WEB工程，都是打成WAR包进行发布。\n\n典型的war包内部结构如下：\n\nwebapp.war\n````\n  |    index.jsp\n\n  |\n\n  |— images\n\n  |— META-INF\n\n  |— WEB-INF\n\n          |   web.xml                   // WAR包的描述文件\n    \n          |\n    \n          |— classes\n    \n          |          action.class       // java类文件\n    \n          |\n    \n          |— lib\n    \n                    other.jar             // 依赖的jar包\n    \n                    share.jar\n````\n## tomcat基础\n\n上一篇文章关于网络编程和NIO已经讲过了，这里按住不表。\n\n## log4j\n\nlog4j是非常常用的日志组件，不过现在为了使用更通用的日志组件，一般使用slf4j来配置日志管理器，然后再介入日志源，比如log4j这样的日志组件。\n\n## 数据库驱动和连接池\n\n一般我们会使用class.forname加载数据库驱动，但是随着Spring的发展，现在一般会进行数据源DataSource这个bean的配置，bean里面填写你的数据来源信息即可，并且在实现类中可以选择支持连接池的数据源实现类，比如c3poDataSource，非常方便。\n\n数据库连接池本身和线程池类似，就是为了避免频繁建立数据库连接，保存了一部分连接并存放在集合里，一般可以用队列来存放。\n\n除此之外，还可以使用tomcat的配置文件来管理数据库连接池，只需要简单的一些配置，就可以让tomcat自动管理数据库的连接池了。\n应用需要使用的时候，通过jndi的方式访问即可，具体方法就是调用jndi命名服务的look方法。\n\n## 单元测试\n\n单元测试是工程中必不可少的组件，maven项目在打包期间会自动运行所有单元测试。一般我们使用junit做单元测试，统一地在test包中分别测试service和dao层，并且使用mock方法来构造假的数据，以便跳过数据库或者其他外部资源来完成测试。\n\n## Maven\n\nmaven是一个项目构建工具，基于约定大于配置的方式，规定了一个工程各个目录的用途，并且根据这些规则进行编译，测试和打包。\n同时他提供了方便的包管理方式，以及快速部署的优势。\n\n## Git\n\ngit是分布式的代码管理工具，比起svn有着分布式的优势。太过常见了，略了。\n\n## Json和xml\n数据描述形式不同，json更简洁。\n\n## hibernate和mybatis\n\n由于jdbc方式的数据库连接和语句执行太过繁琐，重复代码太多，后来提出了jdbctemplate对数据进行bean转换。\n\n但是还是差强人意，于是转而出现了hibernate这类的持久化框架。可以做到数据表和bean一一映射，程序只需要操作bean就可以完成数据库的curd。\n\nmybatis比hibernate更轻量级，mybatis支持原生sql查询，并且也可以使用bean映射，同时还可以自定义地配置映射对象，更加灵活，并且在多表查询上更有优势。\n\n\n"
  },
  {
    "path": "docs/JavaWeb/走进JavaWeb技术世界：Hibernate入门经典与注解式开发.md",
    "content": "# 目录\n* [前言](#前言)\n* [ORM概述](#orm概述)\n* [测试](#测试)\n* [相关类](#相关类)\n* [扩展](#扩展)\n* [参考文章](#参考文章)\n\n\n本文转载自互联网，侵删\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本系列文章将同步到我的个人博客：\n\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n本文是《走进JavaWeb技术世界》系列博文的其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，从servlet到框架，从ssm再到SpringBoot，一步步地学习JavaWeb基础知识，并上手进行实战，接着了解JavaWeb项目中经常要使用的技术和组件，包括日志组件、Maven、Junit，等等内容，以便让你更完整地了解整个JavaWeb技术体系，形成自己的知识框架。为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n**文末赠送8000G的Java架构师学习资料，需要的朋友可以到文末了解领取方式，资料包括Java基础、进阶、项目和架构师等免费学习资料，更有数据库、分布式、微服务等热门技术学习视频，内容丰富，兼顾原理和实践，另外也将赠送作者原创的Java学习指南、Java程序员面试指南等干货资源）**\n<!-- more -->\n\n## 前言\n\n本博文主要讲解介绍Hibernate框架，ORM的概念和Hibernate入门，相信你们看了就会使用Hibernate了!\n\n什么是Hibernate框架？\nHibernate是一种ORM框架，全称为 Object_Relative DateBase-Mapping，在Java对象与关系数据库之间建立某种映射，以实现直接存取Java对象！\n\n为什么要使用Hibernate？\n既然Hibernate是关于Java对象和关系数据库之间的联系的话，也就是我们MVC中的数据持久层->在编写程序中的DAO层...\n\n首先，我们来回顾一下我们在DAO层写程序的历程吧：\n\n在DAO层操作XML，将数据封装到XML文件上，读写XML文件数据实现CRUD\n在DAO层使用原生JDBC连接数据库，实现CRUD\n嫌弃JDBC的ConnectionStatementResultSet等对象太繁琐，使用对原生JDBC的封装组件-->DbUtils组件\n我们来看看使用DbUtils之后，程序的代码是怎么样的：\n\n````\npublic class CategoryDAOImpl implements zhongfucheng.dao.CategoryDao {\n\n    @Override\n    public void addCategory(Category category) {\n\n        QueryRunner queryRunner = new QueryRunner(Utils2DB.getDataSource());\n\n        String sql = \"INSERT INTO category (id, name, description) VALUES(?,?,?)\";\n        try {\n            queryRunner.update(sql, new Object[]{category.getId(), category.getName(), category.getDescription()});\n\n        } catch (SQLException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    @Override\n    public Category findCategory(String id) {\n        QueryRunner queryRunner = new QueryRunner(Utils2DB.getDataSource());\n        String sql = \"SELECT * FROM category WHERE id=?\";\n\n        try {\n            Category category = (Category) queryRunner.query(sql, id, new BeanHandler(Category.class));\n\n            return category;\n\n        } catch (SQLException e) {\n            throw new RuntimeException(e);\n        }\n\n    }\n\n    @Override\n    public List<Category> getAllCategory() {\n        QueryRunner queryRunner = new QueryRunner(Utils2DB.getDataSource());\n        String sql = \"SELECT * FROM category\";\n\n        try {\n            List<Category> categories = (List<Category>) queryRunner.query(sql, new BeanListHandler(Category.class));\n\n            return categories;\n        } catch (SQLException e) {\n            throw  new RuntimeException(e);\n        }\n\n    }\n}\n````\n其实使用DbUtils时，DAO层中的代码编写是很有规律的。\n\n当插入数据的时候，就将JavaBean对象拆分，拼装成SQL语句\n当查询数据的时候，用SQL把数据库表中的列组合，拼装成JavaBean对象\n也就是说：javaBean对象和数据表中的列存在映射关系!如果程序能够自动生成SQL语句就好了....那么Hibernate就实现了这个功能！\n\n简单来说：我们使用Hibernate框架就不用我们写很多繁琐的SQL语句，从而简化我们的开发！\n\n\n## ORM概述\n\n在介绍Hibernate的时候，说了Hibernate是一种ORM的框架。那什么是ORM呢？ORM是一种思想\n\nO代表的是Objcet\nR代表的是Relative\nM代表的是Mapping\nORM->对象关系映射....ORM关注是对象与数据库中的列的关系\n\n\n\nHibernate快速入门\n学习一个框架无非就是三个步骤：\n\n引入jar开发包\n配置相关的XML文件\n熟悉API\n引入相关jar包\n我们使用的是Hibernate3.6的版本\n\nhibernate3.jar核心 + required 必须引入的(6个) + jpa 目录 + 数据库驱动包\n\n\n编写对象和对象映射\n编写一个User对象->User.java\n\n````\npublic class User {\n\n    private int id;\n    private String username;\n    private String password;\n    private String cellphone;\n\n    //各种setter和getter\n}\n````\n编写对象映射->User.hbm.xml。一般它和JavaBean对象放在同一目录下\n\n我们是不知道该XML是怎么写的，可以搜索一下Hibernate文件夹中后缀为.hbm.xml。看看它们是怎么写的。然后复制一份过来\n\n````\n<?xml version=\"1.0\"?>\n<!DOCTYPE hibernate-mapping PUBLIC \n    \"-//Hibernate/Hibernate Mapping DTD 3.0//EN\"\n    \"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd\">\n\n<!-- \n\n  This mapping demonstrates content-based discrimination for the\n  table-per-hierarchy mapping strategy, using a formula\n  discriminator.\n\n-->\n\n<hibernate-mapping \n    package=\"org.hibernate.test.array\">\n\n    <class name=\"A\" lazy=\"true\" table=\"aaa\">\n\n        <id name=\"id\">\n            <generator class=\"native\"/>\n        </id>\n\n\n\n\n            <key column=\"a_id\"/>\n            <list-index column=\"idx\"/>\n            <one-to-many class=\"B\"/>\n\n\n\n\n    </class>\n\n    <class name=\"B\" lazy=\"true\" table=\"bbb\">\n        <id name=\"id\">\n            <generator class=\"native\"/>\n        </id>\n    </class>\n\n</hibernate-mapping>\n````\n在上面的模板上修改～下面会具体讲解这个配置文件!\n````\n<!--在domain包下-->\n<hibernate-mapping package=\"zhongfucheng.domain\">\n\n    <!--类名为User，表名也为User-->\n    <class name=\"User\"  table=\"user\">\n\n        <!--主键映射，属性名为id，列名也为id-->\n        <id name=\"id\" column=\"id\">\n            <!--根据底层数据库主键自动增长-->\n            <generator class=\"native\"/>\n\n        </id>\n\n        <!--非主键映射，属性和列名一一对应-->\n        <property name=\"username\" column=\"username\"/>\n        <property name=\"cellphone\" column=\"cellphone\"/>\n        <property name=\"password\" column=\"password\"/>\n    </class>\n</hibernate-mapping>\n````\n如果使用Intellij Idea生成的Hibernate可以指定生成出主配置文件hibernate.cfg.xml，它是要放在src目录下的\n\n如果不是自动生成的，我们可以在Hibernate的hibernate-distribution-3.6.0.Final\\project\\etc这个目录下可以找到\n\n它长得这个样子：\n\n````\n<?xml version='1.0' encoding='utf-8'?>\n<!DOCTYPE hibernate-configuration PUBLIC\n        \"-//Hibernate/Hibernate Configuration DTD//EN\"\n        \"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd\">\n<hibernate-configuration>\n    <session-factory>\n        <property name=\"connection.url.\"/>\n        <property name=\"connection.driver_class\"/>\n        <property name=\"connection.username\"/>\n        <property name=\"connection.password\"/>\n        <!-- DB schema will be updated if needed -->\n        <!-- <property name=\"hbm2ddl.auto\">update</property> -->\n    </session-factory>\n</hibernate-configuration>\n````\n通过上面的模板进行修改，后面会有对该配置文件进行讲解！\n\n````\n<hibernate-configuration>\n    <!-- 通常，一个session-factory节点代表一个数据库 -->\n    <session-factory>\n\n        <!-- 1\\. 数据库连接配置 -->\n        <property name=\"hibernate.connection.driver_class\">com.mysql.jdbc.Driver</property>\n        <property name=\"hibernate.connection.url\">jdbc:mysql:///zhongfucheng</property>\n        <property name=\"hibernate.connection.username\">root</property>\n        <property name=\"hibernate.connection.password\">root</property>\n        <!--\n            数据库方法配置， hibernate在运行的时候，会根据不同的方言生成符合当前数据库语法的sql\n         -->\n        <property name=\"hibernate.dialect\">org.hibernate.dialect.MySQL5Dialect</property>\n\n        <!-- 2\\. 其他相关配置 -->\n        <!-- 2.1 显示hibernate在运行时候执行的sql语句 -->\n        <property name=\"hibernate.show_sql\">true</property>\n        <!-- 2.2 格式化sql -->\n        <property name=\"hibernate.format_sql\">true</property>\n        <!-- 2.3 自动建表  -->\n        <property name=\"hibernate.hbm2ddl.auto\">create</property>\n\n        <!--3\\. 加载所有映射-->\n        <mapping resource=\"zhongfucheng/domain/User.hbm.xml\"/>\n\n    </session-factory>\n</hibernate-configuration>\n````\n## 测试\n````\npackage zhongfucheng.domain;\n\nimport org.hibernate.SessionFactory;\nimport org.hibernate.Transaction;\nimport org.hibernate.cfg.Configuration;\nimport org.hibernate.classic.Session;\n\n/**\n * Created by ozc on 2017/5/6.\n */\npublic class App {\n    public static void main(String[] args) {\n\n        //创建对象\n        User user = new User();\n        user.setPassword(\"123\");\n        user.setCellphone(\"122222\");\n        user.setUsername(\"nihao\");\n\n        //获取加载配置管理类\n        Configuration configuration = new Configuration();\n\n        //不给参数就默认加载hibernate.cfg.xml文件，\n        configuration.configure();\n\n        //创建Session工厂对象\n        SessionFactory factory = configuration.buildSessionFactory();\n\n        //得到Session对象\n        Session session = factory.openSession();\n\n        //使用Hibernate操作数据库，都要开启事务,得到事务对象\n        Transaction transaction = session.getTransaction();\n\n        //开启事务\n        transaction.begin();\n\n        //把对象添加到数据库中\n        session.save(user);\n\n        //提交事务\n        transaction.commit();\n\n        //关闭Session\n        session.close();\n    }\n}\n````\n值得注意的是：JavaBean的主键类型只能是int类型，因为在映射关系中配置是自动增长的，String类型是不能自动增长的。如果是你设置了String类型，又使用了自动增长，那么就会报出下面的错误！\n\n\n    Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 'zhongfucheng.user' does\n\n执行完程序后，Hibernate就为我们创建对应的表，并把数据存进了数据库了\n\n我们看看快速入门案例的代码用到了什么对象吧，然后一个一个讲解\n\n````\npublic static void main(String[] args) {\n\n    //创建对象\n    User user = new User();\n    user.setPassword(\"123\");\n    user.setCellphone(\"122222\");\n    user.setUsername(\"nihao\");\n\n    //获取加载配置管理类\n    Configuration configuration = new Configuration();\n\n    //不给参数就默认加载hibernate.cfg.xml文件，\n    configuration.configure();\n\n    //创建Session工厂对象\n    SessionFactory factory = configuration.buildSessionFactory();\n\n    //得到Session对象\n    Session session = factory.openSession();\n\n    //使用Hibernate操作数据库，都要开启事务,得到事务对象\n    Transaction transaction = session.getTransaction();\n\n    //开启事务\n    transaction.begin();\n\n    //把对象添加到数据库中\n    session.save(user);\n\n    //提交事务\n    transaction.commit();\n\n    //关闭Session\n    session.close();\n}\n````\n## 相关类\n\nConfiguration\n配置管理类：主要管理配置文件的一个类\n\n它拥有一个子类AnnotationConfiguration，也就是说：我们可以使用注解来代替XML配置文件来配置相对应的信息\n\n\nconfigure方法\nconfigure()方法用于加载配置文件\n\n加载主配置文件的方法\n\n如果指定参数，那么加载参数的路径配置文件\n如果不指定参数，默认加载src/目录下的hibernate.cfg.xml\n\nbuildSessionFactory方法\nbuildSessionFactory()用于创建Session工厂\n\n\nSessionFactory\nSessionFactory-->Session的工厂，也可以说代表了hibernate.cfg.xml这个文件...hibernate.cfg.xml的就有<session-factory>这么一个节点\n\nopenSession方法\n创建一个Session对象\n\ngetCurrentSession方法\n创建Session对象或取出Session对象\n\nSession\nSession是Hibernate最重要的对象，Session维护了一个连接（Connection），只要使用Hibernate操作数据库，都需要用到Session对象\n\n通常我们在DAO层中都会有以下的方法，Session也为我们提供了对应的方法来实现！\n\n````\npublic interface IEmployeeDao {\n\n    void save(Employee emp);\n    void update(Employee emp);\n    Employee findById(Serializable id);\n    List<Employee> getAll();\n    List<Employee> getAll(String employeeName);\n    List<Employee> getAll(int index, int count);\n    void delete(Serializable id);\n\n}\n````\n更新操作\n\n我们在快速入门中使用到了save(Objcet o)方法，调用了这个方法就把对象保存在数据库之中了。Session对象还提供着其他的方法来进行对数据库的更新\n\nsession.save(obj); 【保存一个对象】\nsession.update(obj); 【更新一个对象】\nsession.saveOrUpdate(obj); 【保存或者更新的方法】\n\n没有设置主键，执行保存；\n有设置主键，执行更新操作;\n如果设置主键不存在报错！\n我们来使用一下update()方法吧....既然是更新操作了，那么肯定需要设置主键的，不设置主键，数据库怎么知道你要更新什么。将id为1的记录修改成如下：\n````\nuser.setId(1);\nuser.setPassword(\"qwer\");\nuser.setCellphone(\"1111\");\nuser.setUsername(\"zhongfucheng\");\n````\n主键查询\n\n通过主键来查询数据库的记录，从而返回一个JavaBean对象\n\nsession.get(javaBean.class, int id); 【传入对应的class和id就可以查询】\nsession.load(javaBean.class, int id); 【支持懒加载】\nUser重写toString()来看一下效果：\n\n````\nUser user1 = (User) session.get(User.class, 1);\nSystem.out.println(user1);\n````\nHQL查询\n\nHQL:hibernate query language 即hibernate提供的面向对象的查询语言\n\n查询的是对象以及对象的属性【它查询的是对象以及属性，因此是区分大小写的！】。\n\n    SQL：Struct query language 结构化查询语言\n\n查询的是表以及列【不区分大小写】\nHQL是面向对象的查询语言，可以用来查询全部的数据！\n\n````\nQuery query = session.createQuery(\"FROM User\");\n\nList list = query.list();\nSystem.out.println(list);\n````\n当然啦，它也可以传递参数进去查询\n\n````\nQuery query = session.createQuery(\"FROM User WHERE id=?\");\n\n//这里的？号是从0开始的，并不像JDBC从1开始的！\nquery.setParameter(0, user.getId());\n\nList list = query.list();\nSystem.out.println(list);\n````\n\n\nQBC查询\n\nQBC查询: query by criteria 完全面向对象的查询\n\n从上面的HQL查询，我们就可以发现：HQL查询是需要SQL的基础的，因为还是要写少部分的SQL代码....QBC查询就是完全的面向对象查询...但是呢，我们用得比较少\n\n我们来看一下怎么使用吧：\n\n````\n//创建关于user对象的criteria对象\nCriteria criteria = session.createCriteria(User.class);\n\n//添加条件\ncriteria.add(Restrictions.eq(\"id\", 1));\n\n//查询全部数据\nList list = criteria.list();\nSystem.out.println(list);\n````\n\n本地SQL查询\n\n有的时候，如果SQL是非常复杂的，我们不能靠HQL查询来实现功能的话，我们就需要使用原生的SQL来进行复杂查询了！\n\n但是呢，它有一个缺陷：它是不能跨平台的...因此我们在主配置文件中已经配置了数据库的“方言“了。\n\n我们来简单使用一下把：\n\n````\n//将所有的记录封装成User对象存进List集合中\nSQLQuery sqlQuery = session.createSQLQuery(\"SELECT * FROM user\").addEntity(User.class);\n\nList list = sqlQuery.list();\n\nSystem.out.println(list);\n````\nbeginTransaction方法\n\n开启事务，返回的是一个事务对象....Hibernate规定所有的数据库操作都必须在事务环境下进行，否则报错！\n\n\n\nHibernate注解开发\n\n在Hibernate中我们一般都会使用注解，这样可以帮助我们大大简化hbm映射文件的配置。下面我就来为大家详细介绍。\n\nPO类注解配置\n\n首先肯定是搭建好Hibernate的开发环境啦，我在此也不过多赘述，读者自行实践。接着在src目录下创建一个cn.itheima.domain包，并在该包下创建一个Book实体类，由于Book实体类中写有注解配置，所以就不用编写那个映射配置文件啦！\n````\n@Entity // 定义了一个实体\n@Table(name=\"t_book\",catalog=\"hibernateTest\")\npublic class Book {\n    \n    @Id // 这表示一个主键\n    // @GeneratedValue 相当于native主键生成策略\n    @GeneratedValue(strategy=GenerationType.IDENTITY) // 相当于identity主键生成策略\n    private Integer id; // 主键\n    \n    @Column(name=\"c_name\", length=30, nullable=true)\n    private String name;\n    \n    @Temporal(TemporalType.TIMESTAMP) // 是用来定义日期类型\n    private Date publicationDate; // 出版日期\n    \n    @Type(type=\"double\") // 允许你去指定Hibernate里面的一些类型\n    private Double price; // 价格，如果没有添加注解，也会自动的生成在表中\n    \n    public Integer getId() {\n        return id;\n    }\n    public void setId(Integer id) {\n        this.id = id;\n    }\n    public String getName() {\n        return name;\n    }\n    public void setName(String name) {\n        this.name = name;\n    }\n    public Date getPublicationDate() {\n        return publicationDate;\n    }\n    public void setPublicationDate(Date publicationDate) {\n        this.publicationDate = publicationDate;\n    }\n    public Double getPrice() {\n        return price;\n    }\n    public void setPrice(Double price) {\n        this.price = price;\n    }\n\n}\n````\n下面我就来详细说一下Book实体类中的注解。\n````\n    @Entity：声明一个实体。\n    @Table：来描述类与表之间的对应关系。\n    \n    @Entity // 定义了一个实体\n    @Table(name=\"t_book\",catalog=\"hibernateTest\")\n    public class Book {\n        ......\n    }\n    \n    @id：声明一个主键。\n    @GeneratedValue：用它来声明一个主键生成策略。默认情况是native主键生成策略。可以选择的主键生成策略有：AUTO、IDENTITY、SEQUENCE\n    \n    @Id // 这表示一个主键\n    // @GeneratedValue 相当于native主键生成策略\n    @GeneratedValue(strategy=GenerationType.IDENTITY) // 相当于identity主键生成策略\n    private Integer id; // 主键\n    \n    @Column：定义列。\n    \n    @Column(name=\"c_name\", length=30, nullable=true)\n    private String name;\n````\n**注意：对于PO类中所有属性，如果你不写注解，默认情况下也会在表中生成对应的列，列的名称就是属性的名称，列的类型也即属性的类型**。\n@Temporal：声明日期类型。\n\n````\n    @Temporal(TemporalType.TIMESTAMP) // 是用来定义日期类型\n    private Date publicationDate; // 出版日期\n````\n日期类型可以选择的有：\n````\n    *   TemporalType.DATA：只有年月日。\n    *   TemporalType.TIME：只有小时分钟秒。\n    *   TemporalType.TIMESTAMP：有年月日小时分钟秒。\n    @Type：可允许你去指定Hibernate里面的一些类型。\n    \n    @Type(type=\"double\") // 允许你去指定Hibernate里面的一些类型\n    private Double price; // 价格，如果没有添加注解，也会自动的生成在表中\n````\n最后我们在src目录下创建一个cn.itheima.test包，在该包下编写一个HibernateAnnotationTest单元测试类，并在该类中编写一个用于测试PO类的注解开发的方法：\n````\npublic class HibernateAnnotationTest {\n\n    // 测试PO的注解开发\n    @Test\n    public void test1() {\n        Session session = HibernateUtils.openSession();\n        session.beginTransaction();\n\n        Book b = new Book();\n        b.setName(\"情书\");\n        b.setPrice(56.78);\n        b.setPublicationDate(new Date());\n\n        session.save(b);\n\n        session.getTransaction().commit();\n        session.close();\n    }\n\n}\n````\n现在来思考两个问题：\n\n如果主键生成策略我们想使用UUID类型呢？\n如何设定类的属性不在表中映射？\n这两个问题我们一起解决。废话不多说，直接上例子。在cn.itheima.domain包下再编写一个Person实体类，同样使用注解配置。\n````\n@Entity\n@Table(name=\"t_person\", catalog=\"hibernateTest\")\npublic class Person {\n\n    // 生成UUID的主键生成策略\n    @Id\n    @GenericGenerator(name=\"myuuid\", strategy=\"uuid\") // 声明一种主键生成策略(uuid)\n    @GeneratedValue(generator=\"myuuid\") // 引用uuid主键生成策略\n    private String id;\n\n    @Type(type=\"string\") // 允许你去指定Hibernate里面的一些类型\n    private String name;\n\n    @Transient\n    private String msg; // 现在这个属性不想生成在表中\n\n    public String getId() {\n        return id;\n    }\n\n    public void setId(String id) {\n        this.id = id;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public String getMsg() {\n        return msg;\n    }\n\n    public void setMsg(String msg) {\n        this.msg = msg;\n    }\n\n}\n\n````\n\n最后在HibernateAnnotationTest单元测试类中编写如下一个方法：\n````\npublic class HibernateAnnotationTest {\n\n    // 测试uuid的主键生成策略及不生成表中映射\n    @Test\n    public void test2() {\n        Session session = HibernateUtils.openSession();\n        session.beginTransaction();\n\n        Person p = new Person();\n        p.setName(\"李四\");\n        p.setMsg(\"这是一个好人\");\n\n        session.save(p);\n\n        session.getTransaction().commit();\n        session.close();\n    }\n\n}\n````\n至此，两个问题就解决了。\n注意：对于我们以上讲解的关于属性配置的注解，我们也可以在其对应的getXxx方法去使用。\n\nHibernate关联映射——一对多（多对一）\n仍以客户(Customer)和订单(Order)为例来开始我的表演。\n在src目录下创建一个cn.itheima.oneToMany包，并在该包编写这两个实体类：\n\n客户(Customer)类\n\n````\n// 客户 ---- 一的一方\n@Entity\n@Table(name=\"t_customer\")\npublic class Customer {\n\n@Id\n@GeneratedValue(strategy=GenerationType.IDENTITY)\nprivate Integer id; // 主键\nprivate String name; // 姓名\n\n// 描述客户可以有多个订单\n/*\n * targetEntity=\"...\"：相当于<one-to-many >\n */\n@OneToMany(targetEntity=Order.class,mappedBy=\"c\")\nprivate Set<Order> orders = new HashSet<Order>();\n\npublic Set<Order> getOrders() {\n    return orders;\n}\npublic void setOrders(Set<Order> orders) {\n    this.orders = orders;\n}\npublic Integer getId() {\n    return id;\n}\npublic void setId(Integer id) {\n    this.id = id;\n}\npublic String getName() {\n    return name;\n}\npublic void setName(String name) {\n    this.name = name;\n}\n\n}\n\n\n\n\n订单(Order)类\n\n// 订单 ---- 多的一方\n@Entity\n@Table(name=\"t_order\")\npublic class Order {\n\n@Id\n@GeneratedValue(strategy=GenerationType.IDENTITY)\nprivate Integer id;\nprivate Double money;\nprivate String receiverInfo; // 收货地址\n\n// 订单与客户关联\n@ManyToOne(targetEntity=Customer.class)\n@JoinColumn(name=\"c_customer_id\") // 指定外键列\nprivate Customer c; // 描述订单属于某一个客户\n\npublic Customer getC() {\n    return c;\n}\npublic void setC(Customer c) {\n    this.c = c;\n}\npublic Integer getId() {\n    return id;\n}\npublic void setId(Integer id) {\n    this.id = id;\n}\npublic Double getMoney() {\n    return money;\n}\npublic void setMoney(Double money) {\n    this.money = money;\n}\npublic String getReceiverInfo() {\n    return receiverInfo;\n}\npublic void setReceiverInfo(String receiverInfo) {\n    this.receiverInfo = receiverInfo;\n}\n\n}\n````\n\n这儿用到了`@OneToMany`和`@ManyToOne`这两个注解。\n以上两个实体类编写好之后，可以很明显的看出我们不需要写它们对应的映射配置文件了，是不是很爽呢！接下来，我就要编写测试程序测试一下了。现在我的需求是保存客户时，顺便保存订单，对于这种情况我们需要在Customer类中配置cascade操作，即配置cascade=\"save-update\"，配置的方式有两种，下面我细细说来：\n\n第一种方式，可以使用JPA提供的注解。\n那么@OneToMany注解就应修改为：\n````\n@OneToMany(targetEntity=Order.class,mappedBy=\"c\",cascade=CascadeType.ALL)\nprivate Set<Order> orders = new HashSet<Order>();\n````\n第二种方式，可以使用Hibernate提供的注解。\n那么@OneToMany注解就应修改为：\n````\n@OneToMany(targetEntity=Order.class,mappedBy=\"c\")\n@Cascade(CascadeType.SAVE_UPDATE)\nprivate Set<Order> orders = new HashSet<Order>();\n````\n两种方式都可以，口味任君选择，不过我倾向于第二种方式。\n接下来在HibernateAnnotationTest单元测试类中编写如下方法进行测试：\n````\npublic class HibernateAnnotationTest {\n\n    // 测试one-to-many注解操作(保存客户时级联保存订单)\n    @Test\n    public void test3() {\n        Session session = HibernateUtils.openSession();\n        session.beginTransaction();\n\n        // 1.创建一个客户\n        Customer c = new Customer();\n        c.setName(\"叶子\");\n\n        // 2.创建两个订单\n        Order o1 = new Order();\n        o1.setMoney(1000d);\n        o1.setReceiverInfo(\"武汉\");\n        Order o2 = new Order();\n        o2.setMoney(2000d);\n        o2.setReceiverInfo(\"天门\");\n\n        // 3.建立关系\n        c.getOrders().add(o1);\n        c.getOrders().add(o2);\n\n        // 4.保存客户，并级联保存订单\n        session.save(c);\n\n        session.getTransaction().commit();\n        session.close();\n    }\n\n}\n````\n这时运行以上方法，会发现虽然客户表的那条记录插进去了，但是订单表就变成这个鬼样了：\n\n订单表中没有关联客户的id，这是为什么呢？原因是我们在Customer类中配置了mappedBy=”c”，它代表的是外键的维护由Order方来维护，而Customer不维护，这时你在保存客户时，级联保存订单，是可以的，但是不能维护外键，所以，我们必须在代码中添加订单与客户之间的关系。所以须将test3方法修改为：\n````\npublic class HibernateAnnotationTest {\n\n    // 测试one-to-many注解操作(保存客户时级联保存订单)\n    @Test\n    public void test3() {\n        Session session = HibernateUtils.openSession();\n        session.beginTransaction();\n\n        // 1.创建一个客户\n        Customer c = new Customer();\n        c.setName(\"叶子\");\n\n        // 2.创建两个订单\n        Order o1 = new Order();\n        o1.setMoney(1000d);\n        o1.setReceiverInfo(\"武汉\");\n        Order o2 = new Order();\n        o2.setMoney(2000d);\n        o2.setReceiverInfo(\"天门\");\n\n        // 3.建立关系\n        // 原因：是为了维护外键，不然的话，外键就不能正确的生成！！！\n        o1.setC(c);\n        o2.setC(c);\n\n        // 原因：是为了进行级联操作\n        c.getOrders().add(o1);\n        c.getOrders().add(o2);\n\n        // 4.保存客户，并级联保存订单\n        session.save(c);\n\n        session.getTransaction().commit();\n        session.close();\n    }\n\n}\n````\n这时再测试，就没有任何问题啦！\n\n## 扩展\n\nHibernate注解@Cascade中的DELETE_ORPHAN已经过时了，如下：\n\n可使用下面方案来替换过时方案：\n\nHibernate关联映射——多对多\n以学生与老师为例开始我的表演，我是使用注解完成这种多对多的配置。使用@ManyToMany注解来配置多对多，只需要在一端配置中间表，另一端使用mappedBy表示放置外键的维护权。\n在src目录下创建一个cn.itheima.manyToMany包，并在该包编写这两个实体类：\n\n学生类\n````\n@Entity\n@Table(name=\"t_student\")\npublic class Student {\n\n    @Id\n    @GeneratedValue(strategy=GenerationType.IDENTITY)\n    private Integer id;\n\n    private String name;\n\n    @ManyToMany(targetEntity=Teacher.class)\n    // @JoinTable：使用@JoinTable来描述中间表，并描述中间表中外键与Student、Teacher的映射关系\n    // joinColumns：它是用来描述Student与中间表的映射关系\n    // inverseJoinColumns：它是用来描述Teacher与中间表的映射关系\n    @JoinTable(name=\"s_t\", joinColumns={@JoinColumn(name=\"c_student_id\",referencedColumnName=\"id\")}, inverseJoinColumns={@JoinColumn(name=\"c_teacher_id\")}) \n    private Set<Teacher> teachers = new HashSet<Teacher>();\n\n    public Integer getId() {\n        return id;\n    }\n    public void setId(Integer id) {\n        this.id = id;\n    }\n    public String getName() {\n        return name;\n    }\n    public void setName(String name) {\n        this.name = name;\n    }\n    public Set<Teacher> getTeachers() {\n        return teachers;\n    }\n    public void setTeachers(Set<Teacher> teachers) {\n        this.teachers = teachers;\n    }\n\n}\n````\n老师类\n````\n@Entity\n@Table(name=\"t_teacher\")\npublic class Teacher {\n\n    @Id\n    @GeneratedValue(strategy=GenerationType.IDENTITY)\n    private Integer id;\n\n    private String name;\n\n    @ManyToMany(targetEntity=Student.class, mappedBy=\"teachers\") // 代表由对方来维护外键\n    private Set<Student> students = new HashSet<Student>();\n\n    public Integer getId() {\n        return id;\n    }\n    public void setId(Integer id) {\n        this.id = id;\n    }\n    public String getName() {\n        return name;\n    }\n    public void setName(String name) {\n        this.name = name;\n    }\n    public Set<Student> getStudents() {\n        return students;\n    }\n    public void setStudents(Set<Student> students) {\n        this.students = students;\n    }\n\n}\n````\n接下来，我就要编写测试程序测试一下了。 从上面可看出我们将外键的维护权利交由Student类来维护，现在我们演示保存学生时，将老师也级联保存，对于这种情况我们需要在Student类中配置cascade操作，即配置cascade=”save-update”，如下：\n````\n    @JoinTable(name=\"s_t\", joinColumns={@JoinColumn(name=\"c_student_id\",referencedColumnName=\"id\")}, inverseJoinColumns={@JoinColumn(name=\"c_teacher_id\")}) \n    @Cascade(CascadeType.SAVE_UPDATE)\n    private Set<Teacher> teachers = new HashSet<Teacher>();\n````\n接下来在HibernateAnnotationTest单元测试类中编写如下方法进行测试：\n````\n    public class HibernateAnnotationTest {\n    \n        // 测试多对多级联保存(保存学生时同时保存老师)\n        @Test\n        public void test4() {\n            Session session = HibernateUtils.openSession();\n            session.beginTransaction();\n    \n            // 1.创建两个老师\n            Teacher t1 = new Teacher();\n            t1.setName(\"Tom\");\n    \n            Teacher t2 = new Teacher();\n            t2.setName(\"Fox\");\n    \n            // 2.创建两个学生\n            Student s1 = new Student();\n            s1.setName(\"张丹\");\n    \n            Student s2 = new Student();\n            s2.setName(\"叶紫\");\n    \n            // 3.学生关联老师\n            s1.getTeachers().add(t1);\n            s1.getTeachers().add(t2);\n    \n            s2.getTeachers().add(t1);\n            s2.getTeachers().add(t2);\n    \n            // 保存学生同时保存老师\n            session.save(s1);\n            session.save(s2);\n    \n            session.getTransaction().commit();\n            session.close();\n        }\n    \n    }\n````\n运行以上方法，一切正常。\n接着我们测试级联删除操作。见下图：\n这里写图片描述\n\n可在HibernateAnnotationTest单元测试类中编写如下方法进行测试：\n````\n    public class HibernateAnnotationTest {\n    \n        // 测试多对多级联删除(前提是建立了双向的级联)\n        @Test\n        public void test5() {\n            Session session = HibernateUtils.openSession();\n            session.beginTransaction();\n    \n            Student s = session.get(Student.class, 1);\n            session.delete(s);\n    \n            session.getTransaction().commit();\n            session.close();\n        }\n    \n    }\n````\n## 参考文章\n\n<https://segmentfault.com/a/1190000009707894>\n\n<https://www.cnblogs.com/hysum/p/7100874.html>\n\n<http://c.biancheng.net/view/939.html>\n\n<https://www.runoob.com/>\n\nhttps://blog.csdn.net/android_hl/article/details/53228348\n\n\n\n                     "
  },
  {
    "path": "docs/JavaWeb/走进JavaWeb技术世界：JDBC的进化与连接池技术.md",
    "content": "# 目录\n* [JDBC数据库连接池](#jdbc数据库连接池)\n  * [谈谈连接池、线程池技术原理](#谈谈连接池、线程池技术原理)\n* [JDBC 数据库连接池](#jdbc-数据库连接池)\n    * [什么情况下使用连接池?](#什么情况下使用连接池)\n    * [使用连接池的好处](#使用连接池的好处)\n    * [连接池的实现](#连接池的实现)\n  * [常用数据库连接池](#常用数据库连接池)\n  * [一、JDBC数据库连接池的必要性](#一、jdbc数据库连接池的必要性)\n  * [二、数据库连接池（connection pool）](#二、数据库连接池（connection-pool）)\n    * [　　数据库连接池简单介绍](#　　数据库连接池简单介绍)\n    * [　　数据库连接池工作原理：](#　　数据库连接池工作原理：)\n    * [　　数据库连接池技术的优点](#　　数据库连接池技术的优点)\n      * [　　**资源重用：**](#　　资源重用：)\n  * [三、两种开源的数据库连接池](#三、两种开源的数据库连接池)\n  * [参考文章](#参考文章)\n\n\n\n本文转载自互联网，侵删\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本系列文章将同步到我的个人博客：\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n本文是《走进JavaWeb技术世界》系列博文的其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，从servlet到框架，从ssm再到SpringBoot，一步步地学习JavaWeb基础知识，并上手进行实战，接着了解JavaWeb项目中经常要使用的技术和组件，包括日志组件、Maven、Junit，等等内容，以便让你更完整地了解整个JavaWeb技术体系，形成自己的知识框架。为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n**文末赠送8000G的Java架构师学习资料，需要的朋友可以到文末了解领取方式，资料包括Java基础、进阶、项目和架构师等免费学习资料，更有数据库、分布式、微服务等热门技术学习视频，内容丰富，兼顾原理和实践，另外也将赠送作者原创的Java学习指南、Java程序员面试指南等干货资源）**\n<!-- more -->\n\n# JDBC数据库连接池\n## 谈谈连接池、线程池技术原理\n\n做互联网研发，最早接触使用jdbc技术，为了数据库连接能够复用，会用到c3p0、dbcp等数据库连接池。应该是研发人员最早接触的数据库连接池，再到httpclient http连接池，再到微服务netty连接池，redis客户端连接池，以及jdk中线程池技术。\n\n   这么多数据库、http、netty连接池，jdk线程池，本质上都是连接池技术，连接池技术核心是连接或者说创建的资源复用。\n\n   连接池技术核心：通过减少对于连接创建、关闭来提升性能。用于用户后续使用，好处是后续使用不用在创建连接以及线程，因为这些都需要相关很多文件、连接资源、操作系统内核资源支持来完成构建，会消耗大量资源，并且创建、关闭会消耗应用程序大量性能。\n\n   网络连接本身会消耗大量内核资源，在linux系统下，网络连接创建本身tcp/ip协议栈在内核里面，连接创建关闭会消耗大量文件句柄（linux中万物皆文件，一种厉害抽象手段）系统资源。当下更多是应用tcp技术完成网络传输，反复打开关闭，需要操作系统维护大量tcp协议栈状态。\n\n   连接池本质上是构建一个容器，容器来存储创建好的线程、http连接、数据库连接、netty连接等。对于使用方相当于黑盒，按照接口进行使用就可以了。各个连接池构建、使用管理详细过程大概分成以下三部分。\n\n   第一部分：首先初始化连接池，根据设置相应参数，连接池大小、核心线程数、核心连接数等参数，初始化创建数据库、http、netty连接以及jdk线程。\n\n   第二部分：连接池使用，前边初始化好的连接池、线程池，直接从连接池、线程中取出资源即可进行使用，使用完后要记得交还连接池、线程池，通过池容器来对资源进行管理。\n\n   第三部分：对于连接池维护，连接池、线程池来维护连接、线程状态，不可用连接、线程进行销毁，正在使用连接、线程进行状态标注，连接、线程不够后并且少于设置最大连接、线程数，要进行新连接、线程创建。\n\n   通过上边可以了解到各种连接池技术以及线程池原理或者说套路，理解原理才能不被纷繁复杂表象掩盖。\n\n   下面谈谈构建自己连接池，其实理解了连接池、线程原理，可以使用ArrayList来构建自己连接池、线程池。初始化时创建配置连接数、线程，存储在ArrayList容器中，使用时从ArrayList从取出连接、线程进行使用，执行完任务后，提交回ArrayList容器。前提条件是单线程，在多线程状态下要用线程安全容器。\n\n   前边根据原理介绍了一个简单连接池、线程池怎样构建，实际工业级别线程池还要考虑到连接状态，短连接重连，线程池维护管理高效，线程池稳定等多个因素。\n\n   需要用到连接池而又没有相关开源产品可用时，java连接池可以使用common-pool2来构建，比如google开源gRPC技术，本身是高性能跨平台技术，但目前作为微服务使用，没有连接池、负载均衡等相应配套，这时可以根据需要自己基于Java容器构建自己连接池。也可以利用common-pool2构建连接池来提升应用性能，以及保持高可用。common-pool2本身不仅仅可以构建连接池使用，还可以用来构建对象池。\n\n   连接池还有一个副作用就是实现了高可用，在微服务场景下一个连接不可用，那么再从netty连接池中取出一个进行使用，避免了连接不可用问题。\n\n   掌握原理从比较全面掌握各种池技术，避免数据库连接池，再到httpclient http连接池，再到微服务netty连接池，redis客户端连接池，以及jdk中线程池，对象池各种各样池技术，使我们眼花缭乱，花费过多时间，掌握原理机制以不变应万变。\n\n   推广一下这个方法，其他技术也是类似，深入掌握其原理，就可以明白其他类似技术相似原理，避免疲于应对各种新技术。但每一种架构设计与实现又与领域有着关系，也不可讲原理不顾实际情况扩展。理论与架构设计、源码学习相结合才是最好的，希望有帮助。\n\n# JDBC 数据库连接池\n\n转自：\n\n### 什么情况下使用连接池?\n\n对于一个简单的数据库应用，由于对于数据库的访问不是很频繁。这时可以简单地在需要访问数据库时，就新创建一个连接，用完后就关闭它，这样做也不会带来什么明显的性能上的开销。但是对于一个复杂的数据库应用，情况就完全不同了。频繁的建立、关闭连接，会极大的减低系统的性能，因为对于连接的使用成了系统性能的瓶颈。\n\n### 使用连接池的好处\n\n1.  连接复用。通过建立一个数据库连接池以及一套连接使用管理策略，使得一个数据库连接可以得到高效、安全的复用，避免了数据库连接频繁建立、关闭的开销。\n2.  对于共享资源，有一个很著名的设计模式：资源池。该模式正是为了解决资源频繁分配、释放所造成的问题的。把该模式应用到数据库连接管理领域，就是建立一个数据库连接池，提供一套高效的连接分配、使用策略，最终目标是实现连接的高效、安全的复用。\n\n### 连接池的实现\n\n数据库连接池的基本原理是在内部对象池中维护一定数量的数据库连接，并对外暴露数据库连接获取和返回方法。\n\n外部使用者可通过 getConnection 方法获取连接，使用完毕后再通过 close 方法将连接返回，注意此时连接并没有关闭，而是由连接池管理器回收，并为下一次使用做好准备。\n\nJava 中有一个 DataSource 接口, 数据库连接池就是 DataSource 的一个实现\n\n\n\n## 常用数据库连接池\n\n*   Apache DBCP\n\n    *   [官网](http://commons.apache.org/proper/commons-dbcp/)\n*   C3P0\n\n    *   [官网](http://www.mchange.com/projects/c3p0/index.html)\n*   Druid\n\n    *   [GitHub](https://github.com/alibaba/druid)\n\n## 一、JDBC数据库连接池的必要性\n\n　　在使用开发基于数据库的web程序时，传统的模式基本是按以下步骤：\n\n　　①在主程序（如servlet、beans）中建立数据库连接。\n　　②进行sql操作\n　　③断开数据库连接。\n\n　　这种模式开发，存在的问题:\n\n　　①普通的JDBC数据库连接使用 DriverManager 来获取，每次向数据库建立连接的时候都要将 Connection 加载到内存中，再验证用户名和密码(得花费0.05s～1s的时间)。需要数据库连接的时候，就向数据库要求一个，执行完成后再断开连接。这样的方式将会消耗大量的资源和时间。数据库的连接资源并没有得到很好的重复利用.若同时有几百人甚至几千人在线，频繁的进行数据库连接操作将占用很多的系统资源，严重的甚至会造成服务器的崩溃。\n　　②对于每一次数据库连接，使用完后都得断开。否则，如果程序出现异常而未能关闭，将会导致数据库系统中的内存泄漏，最终将导致重启数据库。\n　　③这种开发不能控制被创建的连接对象数，系统资源会被毫无顾及的分配出去，如连接过多，也可能导致内存泄漏，服务器崩溃。\n\n## 二、数据库连接池（connection pool）\n\n### 　　数据库连接池简单介绍\n\n　　为解决传统开发中的数据库连接问题，可以采用数据库连接池技术。\n\n　　数据库连接池的基本思想就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接，当需要建立数据库连接时，只需从“缓冲池”中取出一个，使用完毕之后再放回去。\n\n　　数据库连接池负责分配、管理和释放数据库连接，它允许应用程序重复使用一个现有的数据库连接，而不是重新建立一个。\n\n　　数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中，这些数据库连接的数量是由最小数据库连接数来设定的。无论这些数据库连接是否被使用，连接池都将一直保证至少拥有这么多的连接数量。连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数，当应用程序向连接池请求的连接数超过最大连接数量时，这些请求将被加入到等待队列中。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405151824.png)\n### 　　数据库连接池工作原理：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405151846.png)\n\n### 　　数据库连接池技术的优点\n\n#### 　　**资源重用：**\n\n　　①由于数据库连接得以重用，避免了频繁创建，释放连接引起的大量性能开销。在减少系统消耗的基础上，另一方面也增加了系统运行环境的平稳性。\n\n　　**更快的系统反应速度：**\n　　数据库连接池在初始化过程中，往往已经创建了若干数据库连接置于连接池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言，直接利用现有可用连接，避免了数据库连接初始化和释放过程的时间开销，从而减少了系统的响应时间\n\n　　**新的资源分配手段：**\n　　对于多应用共享同一数据库的系统而言，可在应用层通过数据库连接池的配置，实现某一应用最大可用数据库连接数的限制，避免某一应用独占所有的数据库资源\n\n　　**统一的连接管理，避免数据库连接泄露：**\n　　在较为完善的数据库连接池实现中，可根据预先的占用超时设定，强制回收被占用连接，从而避免了常规数据库连接操作中可能出现的资源泄露\n\n## 三、两种开源的数据库连接池\n\n　　JDBC 的数据库连接池使用 javax.sql.DataSource 来表示，DataSource 只是一个接口，该接口通常由服务器(Weblogic, WebSphere, Tomcat)提供实现，也有一些开源组织提供实现：\n　　①DBCP 数据库连接池\n　　②C3P0 数据库连接池\n\n　　DataSource 通常被称为数据源，它包含连接池和连接池管理两个部分，习惯上也经常把 DataSource称为连接池\n\n　　数据源和数据库连接不同，数据源无需创建多个，它是产生数据库连接的工厂，因此整个应用只需要一个数据源即可。\n\n　　当数据库访问结束后，程序还是像以前一样关闭数据库连接：conn.close(); 但上面的代码并没有关闭数据库的物理连接，它仅仅把数据库连接释放，归还给了数据库连接池。\n\n\n\n\n\n## 参考文章\n\nhttps://blog.csdn.net/u010028461/article/details/78932109\nhttps://www.cnblogs.com/shaoxiaohuan/p/7755953.html\nhttps://www.cnblogs.com/albertrui/p/8421791.html\nhttps://blog.csdn.net/weixin_43124134/article/details/82586062\nhttps://www.jianshu.com/p/0737ac60c7df\n\n"
  },
  {
    "path": "docs/JavaWeb/走进JavaWeb技术世界：JSP与Servlet的曾经与现在.md",
    "content": "# 目录\n  * [servlet和jsp的区别](#servlet和jsp的区别)\n  * [servlet和jsp各自的特点](#servlet和jsp各自的特点)\n  * [通过MVC双剑合璧](#通过mvc双剑合璧)\n  * [JavaWeb基础知识](#javaweb基础知识)\n    * [一、Servlet 是什么？](#一、servlet-是什么？)\n    * [二、Servlet的生命周期](#二、servlet的生命周期)\n      * [init() 方法](#init-方法)\n      * [service() 方法](#service-方法)\n      * [destroy() 方法](#destroy-方法)\n  * [相关面试题](#相关面试题)\n    * [怎样理解Servlet的单实例多线程？**](#怎样理解servlet的单实例多线程？)\n    * [JSP的中存在的多线程问题：](#jsp的中存在的多线程问题：)\n    * [如何开发线程安全的Servlet](#如何开发线程安全的servlet)\n  * [同步对共享数据的操作](#同步对共享数据的操作)\n  * [五、servlet与jsp的区别](#五、servlet与jsp的区别)\n  * [参考文章](#参考文章)\n\n\n本文转载自互联网，侵删\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本系列文章将同步到我的个人博客：\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n本文是《走进JavaWeb技术世界》系列博文的其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，从servlet到框架，从ssm再到SpringBoot，一步步地学习JavaWeb基础知识，并上手进行实战，接着了解JavaWeb项目中经常要使用的技术和组件，包括日志组件、Maven、Junit，等等内容，以便让你更完整地了解整个JavaWeb技术体系，形成自己的知识框架。为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n**文末赠送8000G的Java架构师学习资料，需要的朋友可以到文末了解领取方式，资料包括Java基础、进阶、项目和架构师等免费学习资料，更有数据库、分布式、微服务等热门技术学习视频，内容丰富，兼顾原理和实践，另外也将赠送作者原创的Java学习指南、Java程序员面试指南等干货资源）**\n<!-- more -->\n\njsp作为Servlet技术的扩展，经常会有人将jsp和Servlet搞混。本文，将为大家带来servlet和jsp的区别，希望对大家有所帮助。\n\n## servlet和jsp的区别\n\n1、Servlet在Java代码中可以通过HttpServletResponse对象动态输出HTML内容。\n\n2、JSP是在静态HTML内容中嵌入Java代码，然后Java代码在被动态执行后生成HTML内容。\n\n## servlet和jsp各自的特点\n\n1、Servlet虽然能够很好地组织业务逻辑代码，但是在Java源文件中，因为是通过字符串拼接的方式生成动态HTML内容，这样就容易导致代码维护困难、可读性差。\n\n2、JSP虽然规避了Servlet在生成HTML内容方面的劣势，但是在HTML中混入大量、复杂的业务逻辑。\n\n## 通过MVC双剑合璧\n\nJSP和Servlet都有自身的适用环境，那么有没有什么办法能够让它们发挥各自的优势呢？答案是肯有的，MVC模式就能够完美解决这一问题。\n\nMVC模式，是Model-View-Controller的简称，是软件工程中的一种软件架构模式，分为三个基本部分，分别是：模型（Model）、视图（View）和控制器（Controller）：\n\nController——负责转发请求，对请求进行处理\n\nView——负责界面显示\n\nModel——业务功能编写（例如算法实现）、数据库设计以及数据存取操作实现\n\n在JSP/Servlet开发的软件系统中，这三个部分的描述如下所示：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405151458.png)\n\n1、Web浏览器发送HTTP请求到服务端，然后被Controller(Servlet)获取并进行处理（例如参数解析、请求转发）\n\n2、Controller(Servlet)调用核心业务逻辑——Model部分，获得结果\n\n3、Controller(Servlet)将逻辑处理结果交给View（JSP），动态输出HTML内容\n\n4、动态生成的HTML内容返回到浏览器显示\n\nMVC模式在Web开发中有很大的优势，它完美规避了JSP与Servlet各自的缺点，让Servlet只负责业务逻辑部分，而不会生成HTML代码；同时JSP中也不会充斥着大量的业务代码，这样能大提高了代码的可读性和可维护性。\n\n## JavaWeb基础知识\n\n### 一、Servlet 是什么？\n\n[Java](http://lib.csdn.net/base/java \"Java 知识库\")Servlet 是运行在 Web 服务器或应用服务器上的程序，它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的[数据库](http://lib.csdn.net/base/mysql \"MySQL知识库\")或应用程序之间的中间层。\n\n使用 Servlet，您可以收集来自网页表单的用户输入，呈现来自数据库或者其他源的记录，还可以动态创建网页。\n\n[Java](http://lib.csdn.net/base/javase \"Java SE知识库\")Servlet 通常情况下与使用 CGI（Common Gateway Interface，公共网关接口）实现的程序可以达到异曲同工的效果。但是相比于 CGI，Servlet 有以下几点优势：\n\n*   **1、性能明显更好。**\n*   **2、Servlet 在 Web 服务器的地址空间内执行。这样它就没有必要再创建一个单独的进程来处理每个客户端请求。**\n*   **3、Servlet 是独立于平台的，因为它们是用 Java 编写的。**\n*   **4、服务器上的 Java 安全管理器执行了一系列限制，以保护服务器计算机上的资源。因此，Servlet 是可信的。**\n*   **5、Java 类库的全部功能对 Servlet 来说都是可用的。它可以通过 sockets 和 RMI 机制与 applets、数据库或其他软件进行交互。**\n\n### 二、Servlet的生命周期\n\nServlet 生命周期可被定义为从创建直到毁灭的整个过程。以下是 Servlet 遵循的过程：\n\n*   **1、Servlet 通过调用init ()方法进行初始化。**\n*   **2、Servlet 调用service()方法来处理客户端的请求。**\n*   **3、Servlet 通过调用destroy()方法终止（结束）。**\n*   **4、最后，Servlet 是由 JVM 的垃圾回收器进行垃圾回收的。**\n\n#### init() 方法\n\ninit 方法被设计成只调用一次。它在第一次创建 Servlet 时被调用，在后续每次用户请求时不再调用。因此，它是用于一次性初始化，就像 Applet 的 init 方法一样。\n\nServlet 创建于用户第一次调用对应于该 Servlet 的 URL 时，但是您也可以指定 Servlet 在服务器第一次启动时被加载。\n\n#### service() 方法\n\nservice() 方法是执行实际任务的主要方法。Servlet 容器（即 Web 服务器）调用 service() 方法来处理来自客户端（浏览器）的请求，并把格式化的响应写回给客户端。\n\n每次服务器接收到一个 Servlet 请求时，服务器会产生一个新的线程并调用服务。service() 方法检查 HTTP 请求类型（GET、POST、PUT、DELETE 等），并在适当的时候调用 doGet、doPost、doPut，doDelete 等方法。\n\n#### destroy() 方法\n\ndestroy() 方法只会被调用一次，在 Servlet 生命周期结束时被调用。destroy() 方法可以让您的 Servlet 关闭数据库连接、停止后台线程、把 Cookie 列表或点击计数器写入到磁盘，并执行其他类似的清理活动。\n\n在调用 destroy() 方法之后，servlet 对象被标记为垃圾回收。\n\n执行后：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405151559.png)\n\n以后继续请求时：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405151623.png)\n\n可见，就绪请求时只有service()方法执行！\n## 相关面试题\n### 怎样理解Servlet的单实例多线程？**\n\n**不同的用户同时对同一个业务（如注册）发出请求，那这个时候容器里产生的有是几个servlet实例呢？**\n\n答案是：**只有一个servlet实例**。一个servlet是在第一次被访问时加载到内存并实例化的。同样的业务请求共享一个servlet实例。不同的业务请求一般对应不同的servlet。\n\n由于Servlet/JSP默认是以多线程模式执行的，所以，在编写代码时需要非常细致地考虑多线程的安全性问题。\n\n### JSP的中存在的多线程问题：\n\n当客户端第一次请求某一个JSP文件时，服务端把该JSP编译成一个CLASS文件，并创建一个该类的实例，然后创建一个线程处理CLIENT端的请求。如果有多个客户端同时请求该JSP文件，则服务端会创建多个线程。每个客户端请求对应一个线程。以多线程方式执行可大大降低对系统的资源需求,提高系统的并发量及响应时间。\n\n对JSP中可能用的的变量说明如下:\n\n**实例变量**: 实例变量是在堆中分配的,并被属于该实例的所有线程共享，所以**不是线程安全的。**\n\nJSP系统提供的8个类变量\n\nJSP中用到的**OUT,REQUEST,RESPONSE,SESSION,CONFIG,PAGE,PAGECONXT是线程安全的**(因为每个线程对应的request，respone对象都是不一样的，不存在共享问题),**APPLICATION在整个系统内被使用,所以不是线程安全的。**\n\n**局部变量**: 局部变量在堆栈中分配,因为每个线程都有它自己的堆栈空间,所以**是线程安全的**\n\n**静态类**: 静态类不用被实例化,就可直接使用,也**不是线程安全的**\n\n**外部资源**: 在程序中可能会有多个线程或进程同时操作同一个资源(如:多个线程或进程同时对一个文件进行写操作).此时也要注意同步问题.\n\nServlet单实例多线程机制：\n\nServlet采用多线程来处理多个请求同时访问。servlet依赖于一个线程池来服务请求。线程池实际上是一系列的工作者线程集合。Servlet使用一个调度线程来管理工作者线程。\n\n当容器收到一个Servlet请求，调度线程从线程池中选出一个工作者线程,将请求传递给该工作者线程，然后由该线程来执行Servlet的service方法。\n\n当这个线程正在执行的时候,容器收到另外一个请求,调度线程同样从线程池中选出另一个工作者线程来服务新的请求,容器并不关心这个请求是否访问的是同一个Servlet.当容器同时收到对同一个Servlet的多个请求的时候，那么这个Servlet的service()方法将在多线程中并发执行。\n\nServlet容器默认采用单实例多线程的方式来处理请求，这样减少产生Servlet实例的开销，提升了对请求的响应时间，对于Tomcat可以在server.xml中通过<Connector>元素设置线程池中线程的数目。\n\n### 如何开发线程安全的Servlet\n\n1、实现 SingleThreadModel 接口\n\n该接口指定了系统如何处理对同一个Servlet的调用。如果一个Servlet被这个接口指定,那么在这个Servlet中的service方法将不会有两个线程被同时执行，当然也就不存在线程安全的问题。这种方法只要将前面的Concurrent Test类的类头定义更改为：\n\n````\n\nPublic class Concurrent Test extends HttpServlet implements SingleThreadModel { \n………… \n}  \n````\n\n## 同步对共享数据的操作\n使用synchronized 关键字能保证一次只有一个线程可以访问被保护的区段\n\n避免使用实例变量\n\n本实例中的线程安全问题是由实例变量造成的，只要在Servlet里面的任何方法里面都不使用实例变量，那么该Servlet就是线程安全的。\n\n1) Struts2的Action是原型，非单实例的；会对每一个请求,产生一个Action的实例来处理\n\nStruts1 Action是单实例的\n\nmvc的controller也是如此。因此开发时要求必须是线程安全的，因为仅有Action的一个实例来处理所有的请求。单例策略限制了Struts1 Action能作的事，并且要在开发时特别小心。Action资源必须是线程安全的或同步的。\n\n2) Struts1的Action,Spring的Ioc容器管理的bean 默认是单实例的.\n\nSpring的Ioc容器管理的bean 默认是单实例的。\n\nStruts2 Action对象为每一个请求产生一个实例，因此没有线程安全问题。（实际上，servlet容器给每个请求产生许多可丢弃的对象，并且不会导致性能和垃圾回收问题）。\n\n当Spring管理Struts2的Action时，bean默认是单实例的，可以通过配置参数将其设置为原型。(scope=\"prototype ）\n\n## 五、servlet与jsp的区别\n\n\n1.jsp经编译后就变成了Servlet.(JSP的本质就是Servlet，JVM只能识别java的类，不能识别JSP的代码,Web容器将JSP的代码编译成JVM能够识别的java类)\n\n2.jsp更擅长表现于页面显示,servlet更擅长于逻辑控制.\n\n3.Servlet中没有内置对象，内置对象都是必须通过HttpServletRequest对象，HttpServletResponse对象以及HttpServlet对象得到.Jsp是Servlet的一种简化，使用Jsp只需要完成程序员需要输出到客户端的内容，Jsp中的Java脚本如何镶嵌到一个类中，由Jsp容器完成。而Servlet则是个完整的Java类，这个类的Service方法用于生成对客户端的响应。\n\n4.对于静态HTML标签，Servlet都必须使用页面输出流逐行输出\n\n\n## 参考文章\n\nhttps://www.w3cschool.cn/servlet/servlet-sxoy2p19.html\nhttps://blog.csdn.net/qq_19782019/article/details/80292110\nhttps://blog.csdn.net/qiuhuang_123/article/details/83617647\nhttps://blog.csdn.net/zt15732625878/article/details/79951933\nhttps://blog.csdn.net/android_hl/article/details/53228348\n\n"
  },
  {
    "path": "docs/JavaWeb/走进JavaWeb技术世界：JavaWeb的由来和基础知识.md",
    "content": "# 目录\n  * [什么是 Java Web](#什么是-java-web)\n    * [Web开发的历史](#web开发的历史)\n    * [开源框架时代](#开源框架时代)\n  * [Java Web基础知识](#java-web基础知识)\n    * [一、HTTP协议](#一、http协议)\n    * [二、服务器](#二、服务器)\n      * [1、概念](#1、概念)\n      * [2、web服务器](#2、web服务器)\n    * [三、JavaWeb项目结构](#三、javaweb项目结构)\n  * [参考文章](#参考文章)\n\n本文转载自互联网，侵删\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本系列文章将同步到我的个人博客：\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n本文是《走进JavaWeb技术世界》系列博文的其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，从servlet到框架，从ssm再到SpringBoot，一步步地学习JavaWeb基础知识，并上手进行实战，接着了解JavaWeb项目中经常要使用的技术和组件，包括日志组件、Maven、Junit，等等内容，以便让你更完整地了解整个JavaWeb技术体系，形成自己的知识框架。为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n\n\n## 什么是 Java Web\n如果你是70、80后的程序员，你一定要看一看这篇文章，保证满满的回忆。如果你是90后，那你更要看看这篇文章，因为你能找到java web发展的历史。\n\n言归正传，Java语言能长期霸占语言排行榜一个重要的原因就是强大的web开发能力，web开发是java的基石（在EJB推出的时候当时的Sun用基石来描述EJB），所以了解java web开发原理是非常重要的。如果仅仅跟大家聊java web开发原理未免有点单薄，今天我将把java web开发包含的主体内容跟头条的读者一起分享一下（一直计划写关于java web的文章，一直也没时间写，今天就当时开个头吧）。\n\n### Web开发的历史\n\nweb开发的历史其实并不久远，要搞清楚java web开发的特点（主要是优点），首先要了解web开发的历史（简单的回归一下）。早期的web是非常简单的结构，用户发出请求（request），服务器给出回应（response），这个时期的web应用，我们称为web site（网站），特点是一些列静态内容的集合。看一个图示：\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405151003.png)\n\n\n\n\n图中的服务器保持了一系列html脚本来响应用户的请求，可以说这个时期的web应用还是比较简单的，但是却确立了两个重要的对象：一个request（代表请求），另一个是response（代表回应）。如果把web开发的历史比喻成一部美国大片的话，那么request和response绝对是这部大片的那女主角，而且每一部都是不可或缺的主角（简单的说就是死不了）。\n\n看到这个图，不知道第一批从事web开发的80后是否和我一样，已经有点感触了，当年为了搞清楚这个结构，曾经连续多少个通宵做实验（实验环境比较恶劣）。没关系，这仅仅是个开始，我想当你看完这篇文章的时候，你会泪流满面的（相信我）。为了配合一下这张结构图，在web发展过程中，有一个小插曲，就是在web site向web application发展的过程中，出现了一个小“玩意儿”，就是applet，很多人了解java都是从使用java applet开始的（70，80后那一批程序员）。当时风靡校园（我当时在读大一）的网易聊天室，哎呀那个火啊（大家回忆一下你在学校机房上网时的兴奋），这个聊天室就是采用了applet构建的，当时applet给静态页面一个动态交互的可能，着实火了一段时间。现在知道applet的程序员，你已经暴露年龄了。看一张图片吧：\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405151021.png)\n\n\n\n\n我想这个时候，泪点低的70、80后已经有点湿润了吧。湿润的，自觉在这里停留一分钟，对着屏幕来张合影，发个朋友圈。\n\n过了这个插曲，真正的三层web开发来了，一个里程碑式的web处理方式CGI，看一张图：\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405151039.png)\n\n\n\n\nCGI的推出，使得web开发正式进入了动态处理时代，服务器能与客户有真正意义上的交流了，有能存储数据的数据库了，虽然CGI的使用周期并不长，但是一定要纪念一下它，毕竟它是里程碑式的变革。java web技术正是踩着CGI的肩膀来到了广大程序员的面前，java web解决了CGI的性能问题。CGI是以进程为单位管理请求的，而java web则是以线程为单位，处理能力更强，占用的资源更少，这个核心的组件就是Servlet。看一组资源占用图，先看CGI的：\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405151104.png)\n\n\n\n\n再看一下java web中的servlet资源图：\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405151117.png)\n\n\n\n\n孰优孰劣一目了然，Servlet解决相同数量的请求，却占用较少的系统资源，这就是为什么广大程序员抛弃了CGI转向java web的原因。\n\n另外，开发一个Servlet并不复杂，看一个Servlet编写的HelloWorld应用：\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405151132.png)\n\n\n\n\n这个代码结构是不是很亲切，是不是很有Coding的感觉，其实Servlet就是个java 类而已，只不过增加了几个限制而已，所以开发一个Servlet并不复杂。然后就是把它部署到web服务器上（Tomcat这个老人家现在身体依然硬朗！），然后就等待客户的请求就可以了。这是Servlet的三层部署图：\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405151210.png)\n\n\n\n\njava web开发的技术体系还包括javabean和jsp，采用MVC结构来组合这三个技术是java web开发的基础内容，先看一下MVC的功能图：\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405151230.png)\n\n\n\n\n再看一下组合使用Servlet+javaBean+JSP的Model2开发结构：\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405151248.png)\n\n\n\n\n这个结构是标准的Java web开发结构，现在是不是很少能看到这么“干净”的描述图了？以上就是标准的java web 开发的历史描述，当然这并不是说这些内容已经过时了，反而它一直是官方的标准解决方案。只不过web发展迎来了另一个阶段，繁荣的开源架构时代来了。。。\n\n### 开源框架时代\n\n这个时代的典型代表就是Struts、Spring和Hibernate，简称SSH。\n\n严格的说，这部分内容并不是官方解决方案，但是这些方案却得到了广大程序员的拥护，一方面原因是EJB的方案太重了，另一方面开源架构使用起来非常方便和灵活，所以从03年以后这些开源框架得到了普通的使用。\n\n下面我简单描述一下这三个框架：\n\nStruts基于MVC结构的解决方案，分为struts1（已经淘汰了，用过Struts1的程序员已经老了）和struts2两个版本，和Python一样，这两个版本不兼容，目前Struts2的最新版本是2.5.14.1，简单的说Struts就是构建了现成的MVC框架，程序员往这个框架里加代码就可以了，使用起来非常方便。\n\nHibernate框架完成了面向对象与面向关系的映射，让java程序以面向对象的方式操作面向关系的数据库。整体结构基于DAO进行扩展，很多操作只需要配置一下就可以了，极其方便。\n\nSpring提供了javaBean的容器，池化了javabean，提高了性能，而且核心代码不到2M，小巧且强大。\n\n关于这三个框架我在头条将写专门的文章介绍，今天就不再进行扩展了。\n\n今天看到这个问题，有感而发，原来我们80后真的老了，80后的程序员，看到这篇文章，有没有所感触？关注我吧，我们一起回忆，再一起继续奋斗！\n\n## Java Web基础知识\n\n\n### 一、HTTP协议\n\nHTTP(超文本传输协议)，它是一种主流B/S架构中应用的通信协议。具有以下特点：\n\n1、无状态\n\n服务端不会记录客户端每次提交的请求，服务器一旦相应客户端之后，就会结束本次的通信过程。客户端下一次的请求是一个新的 连接，和上一次通信没有任何关系。\n\n2、简单灵活\n\nHTTP是基于请求（request）和响应（response）的模型\n\n3、支持客户端与服务端\n\n支持主流的B/S架构的通信以及C/S架构的通信。\n\n注意：C/S架构可选的协议有多种，例如：TCP/IP,UDP,HTTP\n\n 而B/S架构通常只支持HTTP协议\n\n### 二、服务器\n\n#### 1、概念\n\n服务器通常由硬件和软件部分构成，统一对用户提供多种不同的服务。\n\n1、硬件：包括响应的CPU、内存、磁盘等等\n\n2、软件：包括操作系统、运行环境、服务器软件、数据库等等\n\n#### 2、web服务器\n\nweb服务器是提供服务端程序运行的一个环境，它本身也是一个软件。\n\n例如：将我们编写HTML文件放入到web服务器中，那么外界就可以通过浏览器访问我们的html页面\n\n常见的web服务器有Apache，Tomcat、Jetty、Nginx等等。\n\n而Tomcat、Jetty这些web服务器更准确的说是一个Servlet容器。\n\n### 三、JavaWeb项目结构\n\n| 项目根目录，例如：myweb、ch01 |  |  | 通常存放静态资源文件（如：html等等） |\n| --- | --- | --- | --- |\n|  | WEB-INF |  | 这个目录是当前项目私有的一个文件夹，只能提供给项目内部访问，对于客户端来说是访问不到了，通常这个目录下存放的是Java源代码、编译后的字节码文件以及Servlet的核心配置文件web.xml |\n|  |  | src | 存放java源代码的目录 |\n|  |  | classes | 存放编译后的字节码文件 |\n|  |  | lib | lib目录存放当前项目所需要的jar文件 |\n|  |  | JSP | 用于存放JSP动态页面 |\n|  |  | web.xml | 项目的配置文件，用于配置Servlet的请求映射、过滤器、监听器等等信息。每一个web项目都对应一个web.xml配置文件 |\n|  | META-INF |  | 配置应用程序、扩展程序、类加载服务等等 |\n\n\n\n## 参考文章\n\nhttps://blog.csdn.net/shanhanyu/article/details/80515791\nhttps://www.jianshu.com/p/d9b770a78da1\nhttps://www.cnblogs.com/albertrui/p/8427661.html\nhttps://blog.csdn.net/qq_41911570/article/details/83279327\n\n\n\n\n                     \n"
  },
  {
    "path": "docs/JavaWeb/走进JavaWeb技术世界：Java日志系统的诞生与发展.md",
    "content": "# 目录\n  * [Java日志系统的演变史](#java日志系统的演变史)\n    * [阶段一](#阶段一)\n    * [阶段二](#阶段二)\n    * [阶段三](#阶段三)\n    * [阶段四](#阶段四)\n    * [阶段五](#阶段五)\n  * [一、日志框架的分类](#一、日志框架的分类)\n  * [二、发展历程](#二、发展历程)\n    * [Log4j](#log4j)\n    * [J.U.L](#jul)\n    * [JCL（commons-logging）](#jcl（commons-logging）)\n    * [SLF4J & Logback](#slf4j--logback)\n  * [参考文章](#参考文章)\n\n\n\n本文转载自互联网，侵删\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本系列文章将同步到我的个人博客：\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n本文是《走进JavaWeb技术世界》系列博文的其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，从servlet到框架，从ssm再到SpringBoot，一步步地学习JavaWeb基础知识，并上手进行实战，接着了解JavaWeb项目中经常要使用的技术和组件，包括日志组件、Maven、Junit，等等内容，以便让你更完整地了解整个JavaWeb技术体系，形成自己的知识框架。为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n**文末赠送8000G的Java架构师学习资料，需要的朋友可以到文末了解领取方式，资料包括Java基础、进阶、项目和架构师等免费学习资料，更有数据库、分布式、微服务等热门技术学习视频，内容丰富，兼顾原理和实践，另外也将赠送作者原创的Java学习指南、Java程序员面试指南等干货资源）**\n<!-- more -->\n\n##  Java日志系统的演变史\n\n我们先看一个故事。项目经理A带着一帮兄弟开发了一套复杂的企业ERP系统，这个系统一连开发了好几年，开发人员也换了好几拨。\n\n### 阶段一\n最开始的时候，项目经理A安排小B在系统中添加日志功能，在控制台上打印一些必要的信息。最开始的时候，由于项目的功能比较少，于是小B就是用System.out.println的方式打印日志信息。经理A感觉这样使用比较方便，也便于项目小组人员的使用，于是就沿用了下来。\n\n### 阶段二\n此时小B被借调到其他项目，小C加入到了项目组中。此时项目经理A要求改造日志系统，要求能把日志写到一个文件中，方便以后分析用户行为。小C在查看了以前的日志方式之后，感觉特别low，于是自己写了一个日志框架，命名为xiaoC-logging.jar，此举收到了项目经理A的好评。\n\n### 阶段三\n项目组中加入了一个大牛老D，老D发现xiaoC-logging.jar这个日志框架虽然可以满足基本的日志要求，但是还不够高大上，没有一些诸如自动归档，异步写入文件，把日志文件写入NoSQL数据库中等功能。于是老D开发了一个更高级的日志框架叫oldD-logging.jar。\n\n### 阶段四\noldD-logging.jar开发完成之后，需要把原来的xiaoC-logging.jar中的日志API做修改，把之前的日志实现写下来，换上高大上的oldD-logging.jar。\n\n### 阶段五\n在这个卸载与上新的过程中，老D的工作量陡增，他感觉很累。不过姜还是老的辣，他参考了JDBC和spring中面向接口的编程方式，制定了一个日志的门面（一系列的接口），以后所有的日志的记录，都只面向接口编程，至于今后怎么去实现，都要遵循这个接口就可以了。\n\n\n那么在JAVA开发中，这正的日志系统是怎么演变的呢？简短地描述下日志发展，最先出现的是apache开源社区的log4j，这个日志确实是应用最广泛的日志工具，成为了java日志的事实上的标准。然而，当时Sun公司在jdk1.4中增加了JUL日志实现，企图对抗log4j，但是却造成了混乱，这个也是被人诟病的一点。当然也有其他日志工具的出现，这样必然造成开发者的混乱，因为这些日志系统互相没有关联，替换和统一也就变成了比较棘手的一件事。想象下你的应用使用log4j，然后使用了一个其他团队的库，他们使用了JUL，你的应用就得使用两个日志系统了，然后又有第二个库出现了，使用了simplelog。\n\n\n这个时候估计让你崩溃了，这是要闹哪样？这个状况交给你来想想办法，你该如何解决呢？进行抽象，抽象出一个接口层，对每个日志实现都适配或者转接，这样这些提供给别人的库都直接使用抽象层即可。不错，开源社区提供了commons-logging抽象，被称为JCL，也就是日志框架了，确实出色地完成了兼容主流的日志实现（log4j、JUL、simplelog），基本一统江湖，就连顶顶大名的spring也是依赖了JCL。\n\n看起来事物确实是美好，但是美好的日子不长，接下来另一个优秀的日志框架slf4j的加入导致了更加混乱的场面。比较巧的是slf4j的作者(Ceki Gülcü)就是log4j的作者，他觉得JCL不够优秀，所以他要自己搞一套更优雅的出来，于是slf4j日志体系诞生了，并为slf4j实现了一个亲子——logback，确实更加优雅，但是由于之前很多代码库已经使用JCL，虽然出现slf4j和JCL之间的桥接转换，但是集成的时候问题依然多多，对很多新手来说确实会很懊恼，因为比单独的log4j时代“复杂”多了，抱怨声确实很多。\n\n到此本来应该完了，但是Ceki Gülcü觉得还是得回头拯救下自己的“大阿哥”——log4j，于是log4j2诞生了，同样log4j2也参与到了slf4j日志体系中，想必将来会更加混乱。接下来详细解读日志系统的配合使用问题。slf4j的设计确实比较优雅，采用比较熟悉的方式——接口和实现分离，有个纯粹的接口层——slf4j-api工程，这个里边基本完全定义了日志的接口，所以对于开发来说，只需要使用这个即可。\n\n有接口就要有实现，比较推崇的实现是logback，logback完全实现了slf4j-api的接口，并且性能也比log4j更好，同时实现了变参占位符日志输出方式等等新特性。刚刚也提到log4j的使用比较普遍，所以支持这批用户依然是必须的，slf4j-log4j12也实现了slf4j-api，这个算是对log4j的适配器。同样推理，也会有对JUL的适配器slf4j-jdk14等等。为了使使用JCL等等其他日志系统后者实现的用户可以很简单地切换到slf4j上来，给出了各种桥接工程，比如：jcl-over-slf4j会把对JCL的调用都桥接到slf4j上来，可以看出jcl-over-slf4j的api和JCL是相同的，所以这两个jar是不能共存的。jul-to-slf4j是把对jul的调用桥接到slf4j上，log4j-over-slf4j是把对log4j的调用桥接到slf4j。\n\n\n## 一、日志框架的分类\n\n*   门面型日志框架：\n\n1.  JCL：　　Apache基金会所属的项目，是一套Java日志接口，之前叫Jakarta Commons Logging，后更名为Commons Logging\n2.  SLF4J： 是一套简易Java日志门面，**本身并无日志的实现**。（Simple Logging Facade for Java，缩写Slf4j）\n\n*   记录型日志框架:\n\n1.  JUL：　　JDK中的日志记录工具，也常称为JDKLog、jdk-logging，自Java1.4以来的官方日志实现。\n2.  Log4j：　 一个具体的日志实现框架。\n3.  Log4j2：  一个具体的日志实现框架，是LOG4J1的下一个版本，与Log4j 1发生了很大的变化，Log4j 2不兼容Log4j 1。\n4.  Logback：一个具体的日志实现框架，和Slf4j是同一个作者，但其性能更好。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405154329.png)\n\n## 二、发展历程\n\n要搞清楚它们的关系，就要从它们是在什么情况下产生的说起。我们按照时间的先后顺序来介绍。\n\n### Log4j\n\n在JDK 1.3及以前，Java打日志依赖System.out.println(), System.err.println()或者e.printStackTrace()，Debug日志被写到STDOUT流，错误日志被写到STDERR流。这样打日志有一个非常大的缺陷，即无法定制化，且日志粒度不够细。\n于是， Gülcü 于2001年发布了Log4j，后来成为Apache 基金会的顶级项目。Log4j 在设计上非常优秀，对后续的 Java Log 框架有长久而深远的影响，它定义的Logger、Appender、Level等概念如今已经被广泛使用。Log4j 的短板在于性能，在Logback 和 Log4j2 出来之后，Log4j的使用也减少了。\n\n### J.U.L\n\n受Logj启发，Sun在Java1.4版本中引入了java.util.logging，但是j.u.l功能远不如log4j完善，开发者需要自己编写Appenders（Sun称之为Handlers），且只有两个Handlers可用（Console和File），j.u.l在Java1.5以后性能和可用性才有所提升。\n\n### JCL（commons-logging）\n\n由于项目的日志打印必然选择两个框架中至少一个，这时候，Apache的JCL（commons-logging）诞生了。JCL 是一个Log Facade，只提供 Log API，不提供实现，然后有 Adapter 来使用 Log4j 或者 JUL 作为Log Implementation。\n在程序中日志创建和记录都是用JCL中的接口，在真正运行时，会看当前ClassPath中有什么实现，如果有Log4j 就是用 Log4j, 如果啥都没有就是用 JDK 的 JUL。\n这样，在你的项目中，还有第三方的项目中，大家记录日志都使用 JCL 的接口，然后最终运行程序时，可以按照自己的需求(或者喜好)来选择使用合适的Log Implementation。如果用Log4j, 就添加 Log4j 的jar包进去，然后写一个 Log4j 的配置文件；如果喜欢用JUL，就只需要写个 JUL 的配置文件。如果有其他的新的日志库出现，也只需要它提供一个Adapter，运行的时候把这个日志库的 jar 包加进去。\n不过，commons-logging对Log4j和j.u.l的配置问题兼容的并不好，使用commons-loggings还可能会遇到类加载问题，导致NoClassDefFoundError的错误出现。\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405154402.png)\n\n\n到这个时候一切看起来都很简单，很美好。接口和实现做了良好的分离，在统一的JCL之下，不改变任何代码，就可以通过配置就换用功能更强大，或者性能更好的日志库实现。\n\n这种简单美好一直持续到SLF4J出现。\n\n### SLF4J & Logback\n\nSLF4J（Simple Logging Facade for Java）和 Logback 也是Gülcü 创立的项目，目的是为了提供更高性能的实现。\n从设计模式的角度说，SLF4J 是用来在log和代码层之间起到门面作用，类似于 JCL 的 Log Facade。对于用户来说只要使用SLF4J提供的接口，即可隐藏日志的具体实现，SLF4J提供的核心API是一些接口和一个LoggerFactory的工厂类，用户只需按照它提供的统一纪录日志接口，最终日志的格式、纪录级别、输出方式等可通过具体日志系统的配置来实现，因此可以灵活的切换日志系统。\n\nLogback是log4j的升级版，当前分为三个目标模块：\n\n*   logback-core：核心模块，是其它两个模块的基础模块\n*   logback-classic：是log4j的一个改良版本，同时完整实现 SLF4J API 使你可以很方便地更换成其它日记系统如log4j 或 JDK14 Logging\n*   logback-access：访问模块与Servlet容器集成提供通过Http来访问日记的功能，是logback不可或缺的组成部分\n\nLogback相较于log4j有更多的优点：\n\n*   更快的执行速度\n*   更充分的测试\n*   logback-classic 非常自然的实现了SLF4J\n*   使用XML配置文件或者Groovy\n*   自动重新载入配置文件\n*   优雅地从I/O错误中恢复\n*   自动清除旧的日志归档文件\n*   自动压缩归档日志文件\n*   谨慎模式\n*   Lilith\n*   配置文件中的条件处理\n*   更丰富的过滤\n\n更详细的解释参见官网：[https://logback.qos.ch/reasonsToSwitch.html](https://link.jianshu.com/?t=https%3A%2F%2Flogback.qos.ch%2FreasonsToSwitch.html)\n\n到这里，你可能会问：Apache 已经有了个JCL，用来做各种Log lib统一的接口，如果 Gülcü 要搞一个更好的 Log 实现的话，直接写一个实现就好了，为啥还要搞一个和SLF4J呢?\n\n原因是Gülcü 认为 JCL 的 API 设计得不好，容易让使用者写出性能有问题的代码。关于这点，你可以参考这篇文章获得更详细的介绍：[https://zhuanlan.zhihu.com/p/24272450](https://link.jianshu.com/?t=https%3A%2F%2Fzhuanlan.zhihu.com%2Fp%2F24272450)\n\n现在事情就变复杂了。我们有了两个流行的 Log Facade，以及三个流行的 Log Implementation。Gülcü 是个追求完美的人，他决定让这些Log之间都能够方便的互相替换，所以做了各种 Adapter 和 Bridge 来连接:\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405154425.png)\n\n可以看到甚至 Log4j 和 JUL 都可以桥接到SLF4J，再通过 SLF4J 适配到到 Logback！需要注意的是不能有循环的桥接，比如下面这些依赖就不能同时存在:\n\n*   jcl-over-slf4j 和 slf4j-jcl\n*   log4j-over-slf4j 和 slf4j-log4j12\n*   jul-to-slf4j 和 slf4j-jdk14\n\n然而，事情在变得更麻烦！\n\nLog4j2\n\n现在有了更好的 SLF4J 和 Logback，慢慢取代JCL 和 Log4j ，事情到这里总该大统一圆满结束了吧。然而维护 Log4j 的人不这样想，他们不想坐视用户一点点被 SLF4J / Logback 蚕食，继而搞出了 Log4j2。\n\nLog4j2 和 Log4j1.x 并不兼容，设计上很大程度上模仿了 SLF4J/Logback，性能上也获得了很大的提升。Log4j2 也做了 Facade/Implementation 分离的设计，分成了 log4j-api 和 log4j-core。\n\n现在好了，我们有了三个流行的Log 接口和四个流行的Log实现，如果画出桥接关系的图来回事什么样子呢?\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405154503.png)\n\n看到这里是不是感觉有点晕呢？是的，我也有这种感觉。同样，在添加依赖的时候，要小心不要有循环依赖。\n\n## 参考文章\n\n<https://segmentfault.com/a/1190000009707894>\n\n<https://www.cnblogs.com/hysum/p/7100874.html>\n\n<http://c.biancheng.net/view/939.html>\n\n<https://www.runoob.com/>\n\nhttps://blog.csdn.net/android_hl/article/details/53228348\n\n\n\n                     \n"
  },
  {
    "path": "docs/JavaWeb/走进JavaWeb技术世界：Mybatis入门.md",
    "content": "# 目录\n* [前言](#前言)\n* [Mybatis是什么](#mybatis是什么)\n    * [特点](#特点)\n    * [核心类介绍](#核心类介绍)\n    * [功能架构：我们把Mybatis的功能架构分为三层](#功能架构：我们把mybatis的功能架构分为三层)\n    * [框架结构：](#框架结构：)\n    * [执行流程：](#执行流程：)\n* [与Hibernate的异同](#与hibernate的异同)\n* [参考文章](#参考文章)\n* [微信公众号](#微信公众号)\n    * [个人公众号：黄小斜](#个人公众号：黄小斜)\n* [mybatis新手上路](#mybatis新手上路)\n    * [MyBatis简介](#mybatis简介)\n    * [MyBatis整体架构及运行流程](#mybatis整体架构及运行流程)\n        * [1.数据源配置文件](#1数据源配置文件)\n        * [2.Sql映射文件](#2sql映射文件)\n        * [3.会话工厂与会话](#3会话工厂与会话)\n        * [4.运行流程](#4运行流程)\n    * [测试工程搭建](#测试工程搭建)\n\n\n本文转载自互联网，侵删\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本系列文章将同步到我的个人博客：\n\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n本文是《走进JavaWeb技术世界》系列博文的其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，从servlet到框架，从ssm再到SpringBoot，一步步地学习JavaWeb基础知识，并上手进行实战，接着了解JavaWeb项目中经常要使用的技术和组件，包括日志组件、Maven、Junit，等等内容，以便让你更完整地了解整个JavaWeb技术体系，形成自己的知识框架。为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n## 前言\n\n学习一个新东西前，如果能对他有一个比较直观的印象与定位，那么接下来的学习过程就会顺畅很多。所以本文主要是我对Mybatis的一个简单入门性的总结介绍（前提还是需要些必要的概念认知）。\n_PS:文末有参考列表_\n\n## Mybatis是什么\n\nMybatis是一个持久层框架，用于数据的持久化。主要表现为将SQL与POJO进行一个映射，将SQL从代码中解耦。基本概念如图：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/4226917-700b83c25876d6d0.png)\n\n\n使用时，以User为例，UserMapper定义了`findById`接口，该接口返回一个User对象，接口的实现为一个xml配置文件。该xml文件中定义对应接口中的实现所需要的SQL。从而达到将SQL与代码解耦的目标。\n\n```\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>  \n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\"   \n\"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">  \n\n<mapper namespace=\"com.mybatis.UserMapper\">  \n\n    <select id=\"findById\" parameterType=\"int\" resultType=\"User\">  \n        select user_id id,user_name userName,user_age age from t_user where user_id=#{id}  \n    </select>  \n\n</mapper> \n\n```\n\nMyBatis 是Apache的一个Java开源项目，是一款优秀的持久层框架，它支持定制化 SQL、存储过程以及高级映射。Mybatis可以将Sql语句配置在XML文件中，避免将Sql语句硬编码在Java类中。\n\n### 特点\n\n1.Mybatis通过参数映射方式，可以将参数灵活的配置在SQL语句中的配置文件中，避免在Java类中配置参数（JDBC）\n\n2.Mybatis通过输出映射机制，将结果集的检索自动映射成相应的Java对象，避免对结果集手工检索（JDBC）\n\n3.Mybatis可以通过Xml配置文件对数据库连接进行管理\n\n### 核心类介绍\n\n1.SqlSessionaFactoryBuilder ：该类主要用于创建 SqlSessionFactory, 这个类可以被实例化、使用和丢弃，一旦创建了 SqlSessionFactory，就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域（也就是局部方法变量）。\n\n2.SqlSessionFactory ：该类的作用了创建 SqlSession, 从名字上我们也能看出, 该类使用了工厂模式, 每次应用程序访问数据库, 我们就要通过 SqlSessionFactory 创建 SqlSession, 所以 SqlSessionFactory 和整个 Mybatis 的生命周期是相同的. 这也告诉我们不能创建多个同一个数据的 SqlSessionFactory, 如果创建多个, 会消耗尽数据库的连接资源, 导致服务器夯机. 应当使用单例模式. 避免过多的连接被消耗, 也方便管理。\n\n3.SqlSession ：SqlSession 相当于一个会话, 每次访问数据库都需要这样一个会话, 大家可能会想起了 JDBC 中的 Connection, 很类似, 但还是有区别的, 何况现在几乎所有的连接都是使用的连接池技术, 用完后直接归还而不会像 Session 一样销毁. 注意: 他是一个线程不安全的对象, 在设计多线程的时候我们需要特别的当心, 操作数据库需要注意其隔离级别, 数据库锁等高级特性, 此外, 每次创建的 SqlSession 都必须及时关闭它, 它长期存在就会使数据库连接池的活动资源减少, 对系统性能的影响很大, 我们一般在 finally 块中将其关闭. 还有, SqlSession 存活于一个应用的请求和操作, 可以执行多条 Sql, 保证事务的一致性。SqlSession在执行过程中，有包含了几大对象：\n\n3.1.Executor ：执行器，由它调度 StatementHandler、ParameterHandler、ResultSetHandler 等来执行对应的 SQL。其中 StatementHandler 是最重要的。\n\n3.2.StatementHandler ：作用是使用数据库的 Statement（PreparedStatement）执行操作，它是四大对象的核心，起到承上启下的作用，许多重要的插件都是通过拦截它来实现的。\n\n3.3.ParamentHandler ：用来处理 SQL 参数的。\n\n3.4.ResultSetHandler ：进行数据集的封装返回处理的。\n\n4.Mapper ：映射器是一些由你创建的、绑定你映射的语句的接口。映射器接口的实例是从 SqlSession 中获得的, 他的作用是发送 SQL, 然后返回我们需要的结果. 或者执行 SQL 从而更改数据库的数据, 因此它应该在 SqlSession 的事务方法之内, 在 Spring 管理的 Bean 中, Mapper 是单例的。\n\n### 功能架构：我们把Mybatis的功能架构分为三层\n\n(1)API接口层：提供给外部使用的接口API，开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。\n\n(2)数据处理层：负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。\n\n(3)基础支撑层：负责最基础的功能支撑，包括连接管理、事务管理、配置加载和缓存处理，这些都是共用的东西，将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405164855.png)\n\n### 框架结构：\n\n(1)加载配置：配置来源于两个地方，一处是配置文件，一处是Java代码的注解，将SQL的配置信息加载成为一个个MappedStatement对象（包括了传入参数映射配置、执行的SQL语句、结果映射配置），存储在内存中。\n\n(2)SQL解析：当API接口层接收到调用请求时，会接收到传入SQL的ID和传入对象（可以是Map、JavaBean或者基本数据类型），Mybatis会根据SQL的ID找到对应的MappedStatement，然后根据传入参数对象对MappedStatement进行解析，解析后可以得到最终要执行的SQL语句和参数。\n\n(3)SQL执行：将最终得到的SQL和参数拿到数据库进行执行，得到操作数据库的结果。\n\n(4)结果映射：将操作数据库的结果按照映射的配置进行转换，可以转换成HashMap、JavaBean或者基本数据类型，并将最终结果返回。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405164952.png)\n\n### 执行流程：\n\n1.获取SqlsessionFactory：根据配置文件（全局、sql映射）初始化configuration对象，\n\n2.获取sqlSession：创建一个DefaultSqlSession对象，包含Configuration及Executor（根据全局配置文件中defaultExecutorType创建对应的Executor）\n\n3.获取接口代理对象MapperProxy：DefaultSqlSession.getMapper拿到Mapper接口对应的MapperProxy\n\n4.执行增删改查\n\n1、调用DefaultSqlSession增删改查\n\n2、创建StatementHandler （同时创建ParameterHandler,ResultSetHandler）\n\n3、调用StatementHandler预编译参数以及设置参数值，使用ParameterHandler给sql设置参数\n\n4、调用StatementHandler增删改查\n\n5、ResultSetHandler封装结果\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20190821203129674.png)\n\n\n## 与Hibernate的异同\n\nMybatis开始逐渐流行起来，必然有其原因，简单了解了一下它与同为持久层框架的Hibernate的异同。\n\n*   映射模式\n    从上面的简单概念可以知道Mybatis实际上着力点在POJO与SQL的映射。而Hibernate则主要是POJO与数据库表的对象关系映射。前者掌控力度更细，代码量会相对多一点，后者灵活性则差一点，更为自动化一些，与PHP里的`Eloquent`属于同类型。\n*   性能\n    Mybatis基于原生JDBC，相比于对JDBC进行二次封装的Hibernate性能会更好一点。\n*   开发与维护\n    Hibernate配置好实体类后，使用起来是比较简洁，舒服的，但是前期学习曲线比较陡，后期调优比较麻烦。Mybatis对SQL掌控的颗粒更细一点，相比较而言看上去简陋些。由于直接映射SQL，迁移性是个问题。\n\n## 参考文章\n\n<https://segmentfault.com/a/1190000009707894>\n\n<https://www.cnblogs.com/hysum/p/7100874.html>\n\n<http://c.biancheng.net/view/939.html>\n\n<https://www.runoob.com/>\n\nhttps://blog.csdn.net/android_hl/article/details/53228348\n\n# mybatis新手上路\n\n## MyBatis简介\n\nMybatis是Apache的一个Java开源项目，是一个支持动态Sql语句的持久层框架。Mybatis可以将Sql语句配置在XML文件中，避免将Sql语句硬编码在Java类中。与JDBC相比：\n\n1.  Mybatis通过参数映射方式，可以将参数灵活的配置在SQL语句中的配置文件中，避免在Java类中配置参数（JDBC）\n2.  Mybatis通过输出映射机制，将结果集的检索自动映射成相应的Java对象，避免对结果集手工检索（JDBC）\n3.  Mybatis可以通过Xml配置文件对数据库连接进行管理。\n\n## MyBatis整体架构及运行流程\n\nMybatis整体构造由 数据源配置文件、Sql映射文件、会话工厂、会话、执行器和底层封装对象组成。\n\n### 1.数据源配置文件\n\n通过配置的方式将数据库的配置信息从应用程序中独立出来，由独立的模块管理和配置。Mybatis的数据源配置文件包含数据库驱动、数据库连接地址、用户名密码、事务管理等，还可以配置连接池的连接数、空闲时间等。\n\n一个SqlMapConfig.xml基本的配置信息如下：\n\n\n<configuration>\n    <!-- 加载数据库属性文件 -->\n    <properties resource=\"db.properties\"></properties>\n    <environments default=\"development\">\n        <environment id=\"development\">\n            <!--使用JDBC实务管理-->\n            <transactionManager type=\"JDBC\"></transactionManager>\n            <!--连接池 -->\n            <dataSource type=\"POOLED\">\n                <property name=\"driver\" value=\"${jdbc.driver}\"></property>\n                <property name=\"url\" value=\"${jdbc.url}\"></property>\n                <property name=\"username\" value=\"${jdbc.username}\" />\n                <property name=\"password\" value=\"${jdbc.password}\" />\n            </dataSource>\n        </environment>\n    </environments>\n</configuration>\n\n\n\n\n### 2.Sql映射文件\n\nMybatis中所有数据库的操作都会基于该映射文件和配置的sql语句，在这个配置文件中可以配置任何类型的sql语句。框架会根据配置文件中的参数配置，完成对sql语句以及输入输出参数的映射配置。\n\nMapper.xml配置文件大致如下：\n\n\n````\n<!DOCTYPE mapper \n    PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \n    \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.sl.dao.ProductDao\">\n    <!-- 根据id查询product表\n        resultType:返回值类型，一条数据库记录也就对应实体类的一个对象\n        parameterType:参数类型，也就是查询条件的类型 -->\n    <select id=\"selectProductById\" resultType=\"com.sl.po.Product\" parameterType=\"int\">\n     <!-- 这里和普通的sql 查询语句差不多，对于只有一个参数，后面的 #{id}表示占位符，里面不一定要写id,写啥都可以，但是不要空着，如果有多个参数则必须写pojo类里面的属性 --> select * from products where id = #{id} </select>\n</mapper>\n````\n\n\n### 3.会话工厂与会话\n\nMybatis中会话工厂SqlSessionFactory类可以通过加载资源文件，读取数据源配置SqlMapConfig.xml信息，从而产生一种可以与数据库交互的会话实例SqlSession，会话实例SqlSession根据Mapper.xml文件中配置的sql,对数据库进行操作。\n\n### 4.运行流程\n\n会话工厂SqlSessionFactory通过加载资源文件获取SqlMapConfig.xml配置文件信息，然后生成可以与数据库交互的会话实例SqlSession。\n\n会话实例可以根据Mapper配置文件中的Sql配置去执行相应的增删改查操作。\n\n在SqlSession会话实例内部，通过执行器Executor对数据库进行操作，Executor依靠封装对象Mappered Statement，它分装了从mapper.xml文件中读取的信息（sql语句，参数，结果集类型）。\n\nMybatis通过执行器与Mappered Statement的结合实现与数据库的交互。\n\n执行流程图：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/577318-20180702181255132-2135681101.png)\n\n## 测试工程搭建\n\n1.新建maven工程\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/577318-20180702181320571-1138935145.png)\n\n2\\. 添加依赖pom.xml\n\n\n\n````\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <groupId>com.sl</groupId>\n    <artifactId>mybatis-demo</artifactId>\n    <version>0.0.1-SNAPSHOT</version>\n    <properties>\n        <junit.version>4.12</junit.version>\n        <mybatis.version>3.4.1</mybatis.version>\n        <mysql.version>5.1.32</mysql.version>\n        <log4j.version>1.2.17</log4j.version>\n    </properties>\n    <dependencies>\n        <!-- 单元测试 -->\n        <dependency>\n            <groupId>junit</groupId>\n            <artifactId>junit</artifactId>\n            <version>${junit.version}</version>\n            <!-- <scope>test</scope> -->\n        </dependency>\n        <!-- Mybatis -->\n        <dependency>\n            <groupId>org.mybatis</groupId>\n            <artifactId>mybatis</artifactId>\n            <version>${mybatis.version}</version>\n        </dependency>\n        <!-- mysql -->\n        <dependency>\n            <groupId>mysql</groupId>\n            <artifactId>mysql-connector-java</artifactId>\n            <version>${mysql.version}</version>\n        </dependency>\n        <!-- 日志处理 -->\n        <dependency>\n            <groupId>log4j</groupId>\n            <artifactId>log4j</artifactId>\n            <version>${log4j.version}</version>\n        </dependency>\n    </dependencies>\n</project>\n\n````\n\n\n3.编写数据源配置文件SqlMapConfig.xml\n\n````\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE configuration PUBLIC \"-//mybatis.org//DTD Config 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-config.dtd\">\n<configuration>\n<!-- 加载配置文件 -->\n    <properties resource=\"db.properties\"></properties>\n    <environments default=\"development\">\n        <!-- id属性必须和上面的defaut一致 -->\n        <environment id=\"development\">\n            <transactionManager type=\"JDBC\"></transactionManager>\n            <!--dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象源 -->\n            <dataSource type=\"POOLED\">\n                <property name=\"driver\" value=\"${jdbc.driver}\"></property>\n                <property name=\"url\" value=\"${jdbc.url}\"></property>\n                <property name=\"username\" value=\"${jdbc.username}\" />\n                <property name=\"password\" value=\"${jdbc.password}\" />\n            </dataSource>\n        </environment>\n    </environments>\n     <!—申明mapper文件 -->\n        <mappers>\n        <!-- xml实现    注册productMapper.xml文件 -->\n        <mapper resource=\"mapper/productMapper.xml\"></mapper>\n    </mappers>\n</configuration>\n````\n\n\n4.编写SQL映射配置文件productMapper.xml\n\n\n````\n\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper \n    PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \n    \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.sl.mapper.ProductMapper\">\n\n\n    <select id=\"selectAllProduct\" resultType=\"com.sl.po.Product\"> select * from products </select>\n\n</mapper>\n\n````\n5.编写测试代码TestClient.java\n\n````\n//使用productMapper.xml配置文件\npublic class TestClient { //定义会话SqlSession\n    SqlSession session =null;\n\n\n    @Before public void init() throws IOException { //定义mabatis全局配置文件\n        String resource = \"SqlMapConfig.xml\"; //加载mybatis全局配置文件 //InputStream inputStream = TestClient.class.getClassLoader().getResourceAsStream(resource);\n\n InputStream inputStream = Resources.getResourceAsStream(resource);\n        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();\n        SqlSessionFactory factory = builder.build(inputStream); //根据sqlSessionFactory产生会话sqlsession\n        session = factory.openSession();    \n    } //查询所有user表所有数据\n @Test public void testSelectAllUser() {\n        String statement = \"com.sl.mapper.ProductMapper.selectAllProduct\";\n        List<Product> listProduct =session.selectList(statement); for(Product product:listProduct)\n        {\n            System.out.println(product);\n        } //关闭会话\n session.close();    \n    }\n\n}\n````\n\n````\npublic class Product { private int Id; private String Name; private String Description; private BigDecimal UnitPrice; private String ImageUrl; private Boolean IsNew; public int getId() { return Id;\n    } public void setId(int id) { this.Id = id;\n    } public String getName() { return Name;\n    } public void setName(String name) { this.Name = name;\n    } public String getDescription() { return Description;\n    } public void setDescription(String description) { this.Description = description;\n    } public BigDecimal getUnitPrice() { return UnitPrice;\n    } public void setUnitPrice(BigDecimal unitprice) { this.UnitPrice = unitprice;\n    } public String getImageUrl() { return Name;\n    } public void setImageUrl(String imageurl) { this.ImageUrl = imageurl;\n    } public boolean getIsNew() { return IsNew;\n    } public void setIsNew(boolean isnew) { this.IsNew = isnew;\n    }\n\n\n    @Override public String toString() { return \"Product [id=\" + Id + \", Name=\" + Name + \", Description=\" + Description + \", UnitPrice=\" + UnitPrice + \", ImageUrl=\" + ImageUrl + \", IsNew=\" + IsNew+ \"]\";\n    }\n\n}\n````\n6.运行测试用例\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/577318-20180702181558003-1700599553.png)\n\n\n\n\n                     "
  },
  {
    "path": "docs/JavaWeb/走进JavaWeb技术世界：Servlet工作原理详解.md",
    "content": "# 目录\n  * [什么是Servlet](#什么是servlet)\n  * [Servlet体系结构](#servlet体系结构)\n  * [Servlet工作原理](#servlet工作原理)\n  * [Servlet生命周期](#servlet生命周期)\n  * [Servlet中的Listener](#servlet中的listener)\n* [Cookie与Session](#cookie与session)\n  * [参考文章](#参考文章)\n\n\n\n本文转载自互联网，侵删\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本系列文章将同步到我的个人博客：\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n本文是《走进JavaWeb技术世界》系列博文的其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，从servlet到框架，从ssm再到SpringBoot，一步步地学习JavaWeb基础知识，并上手进行实战，接着了解JavaWeb项目中经常要使用的技术和组件，包括日志组件、Maven、Junit，等等内容，以便让你更完整地了解整个JavaWeb技术体系，形成自己的知识框架。为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n**文末赠送8000G的Java架构师学习资料，需要的朋友可以到文末了解领取方式，资料包括Java基础、进阶、项目和架构师等免费学习资料，更有数据库、分布式、微服务等热门技术学习视频，内容丰富，兼顾原理和实践，另外也将赠送作者原创的Java学习指南、Java程序员面试指南等干货资源）**\n<!-- more -->\n## 什么是Servlet\n\nServlet的作用是**为Java程序提供一个统一的web应用的规范**，方便程序员统一的使用这种规范来编写程序，应用容器可以使用提供的规范来实现自己的特性。比如tomcat的代码和jetty的代码就不一样，但作为程序员你只需要了解servlet规范就可以从request中取值，你可以操作session等等。不用在意应用服务器底层的实现的差别而影响你的开发。\n\nHTTP 协议只是一个规范，定义服务请求和响应的大致式样。Java servlet 类**将HTTP中那些低层的结构包装在 Java 类中**，这些类所包含的便利方法使其在 Java 语言环境中更易于处理。\n\n> 正如您正使用的特定 servlet 容器的配置文件中所定义的，当用户通过 URL 发出一个请求时，这些 Java servlet 类就将之转换成一个 HttpServletRequest，并发送给 URL 所指向的目标。当服务器端完成其工作时，Java 运行时环境（Java Runtime Environment）就将结果包装在一个 HttpServletResponse 中，然后将原 HTTP 响应送回给发出该请求的客户机。在与 Web 应用程序进行交互时，通常会发出多个请求并获得多个响应。所有这些都是在一个会话语境中，Java 语言将之包装在一个 HttpSession 对象中。在处理响应时，您可以访问该对象，并在创建响应时向其添加事件。它提供了一些跨请求的语境。\n\n**容器**（如 Tomcat）将为 servlet 管理运行时环境。您可以配置该容器，定制 J2EE 服务器的工作方式，以便将 servlet 暴露给外部世界。正如我们将看到的，通过该容器中的各种配置文件，您在 URL（由用户在浏览器中输入）与服务器端组件之间搭建了一座桥梁，这些组件将处理您需要该 URL 转换的请求。在运行应用程序时，该容器将**加载并初始化 servlet**，**管理其生命周期**。\n\n## Servlet体系结构\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405152028.png)\nServlet顶级类关联图\n\n\n\n**Servlet**\n\nServlet的框架是由两个Java包组成的：javax.servlet与javax.servlet.http。在javax.servlet包中定义了所有的Servlet类都必须实现或者扩展的通用接口和类。在javax.servlet.http包中定义了采用Http协议通信的HttpServlet类。Servlet的框架的核心是javax.servlet.Servlet接口，所有的Servlet都必须实现这个接口。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405152057.png)\n\n\nServlet接口\n\n\n\n在Servlet接口中定义了5个方法：\n\n```\n1\\. init(ServletConfig)方法：负责初始化Servlet对象，在Servlet的生命周期中，该方法执行一次；该方法执行在单线程的环境下，因此开发者不用考虑线程安全的问题；\n2\\. service(ServletRequest req,ServletResponse res)方法：负责响应客户的请求；为了提高效率，Servlet规范要求一个Servlet实例必须能够同时服务于多个客户端请求，即service()方法运行在多线程的环境下，Servlet开发者必须保证该方法的线程安全性；\n3\\. destroy()方法：当Servlet对象退出生命周期时，负责释放占用的资源；\n4\\. getServletInfo：就是字面意思，返回Servlet的描述；\n5\\. getServletConfig：这个方法返回由Servlet容器传给init方法的ServletConfig。\n\n```\n\n**ServletRequest & ServletResponse**\n\n对于每一个HTTP请求，servlet容器会创建一个封装了HTTP请求的ServletRequest实例传递给servlet的service方法，ServletResponse则表示一个Servlet响应，其隐藏了将响应发给浏览器的复杂性。通过ServletRequest的方法你可以获取一些请求相关的参数，而ServletResponse则可以将设置一些返回参数信息，并且设置返回内容。\n\n**ServletConfig**\n\nServletConfig封装可以通过@WebServlet或者web.xml传给一个Servlet的配置信息，以这种方式传递的每一条信息都称做初始化信息，初始化信息就是一个个K-V键值对。为了从一个Servlet内部获取某个初始参数的值，init方法中调用ServletConfig的getinitParameter方法或getinitParameterNames方法获取，除此之外，还可以通过getServletContext获取ServletContext对象。\n\n**ServletContext**\n\nServletContext是代表了Servlet应用程序。每个Web应用程序只有一个context。在分布式环境中，一个应用程序同时部署到多个容器中，并且每台Java虚拟机都有一个ServletContext对象。有了ServletContext对象后，就可以共享能通过应用程序的所有资源访问的信息，促进Web对象的动态注册，共享的信息通过一个内部Map中的对象保存在ServiceContext中来实现。保存在ServletContext中的对象称作属性。操作属性的方法：\n\n**GenericServlet**\n\n前面编写的Servlet应用中通过实现Servlet接口来编写Servlet，但是我们每次都必须为Servlet中的所有方法都提供实现，还需要将ServletConfig对象保存到一个类级别的变量中，GenericServlet抽象类就是为了为我们省略一些模板代码，实现了Servlet和ServletConfig，完成了一下几个工作：\n\n将init方法中的ServletConfig赋给一个类级变量，使的可以通过getServletConfig来获取。\n\n```\npublic void init(ServletConfig config) throws ServletException {\n        this.config = config;\n        this.init();\n}\n\n```\n\n同时为避免覆盖init方法后在子类中必须调用super.init(servletConfig)，GenericServlet还提供了一个不带参数的init方法，当ServletConfig赋值完成就会被第带参数的init方法调用。这样就可以通过覆盖不带参数的init方法编写初始化代码，而ServletConfig实例依然得以保存\n\n为Servlet接口中的所有方法提供默认实现。\n\n提供方法来包装ServletConfig中的方法。\n\n**HTTPServlet**\n\n在编写Servlet应用程序时，大多数都要用到HTTP，也就是说可以利用HTTP提供的特性，javax.servlet.http包含了编写Servlet应用程序的类和接口，其中很多覆盖了javax.servlet中的类型，我们自己在编写应用时大多时候也是继承的HttpServlet。\n\n## Servlet工作原理\n\n当Web服务器接收到一个HTTP请求时，它会先判断请求内容——如果是静态网页数据，Web服务器将会自行处理，然后产生响应信息；如果牵涉到动态数据，Web服务器会将请求转交给Servlet容器。此时Servlet容器会找到对应的处理该请求的Servlet实例来处理，结果会送回Web服务器，再由Web服务器传回用户端。\n\n针对同一个Servlet，Servlet容器会在第一次收到http请求时建立一个Servlet实例，然后启动一个线程。第二次收到http请求时，Servlet容器无须建立相同的Servlet实例，而是启动第二个线程来服务客户端请求。所以多线程方式不但可以提高Web应用程序的执行效率，也可以降低Web服务器的系统负担。\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405152119.png)\n\n\nWeb服务器工作流程\n\n\n\n接着我们描述一下Tomcat与Servlet是如何工作的，首先看下面的时序图：\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405152142.png)\n\n\nServlet工作原理时序图\n\n\n\n> 1.  Web Client 向Servlet容器（Tomcat）发出Http请求；\n>     \n>     \n> 2.  Servlet容器接收Web Client的请求；\n>     \n>     \n> 3.  Servlet容器创建一个HttpRequest对象，将Web Client请求的信息封装到这个对象中；\n>     \n>     \n> 4.  Servlet容器创建一个HttpResponse对象；\n>     \n>     \n> 5.  Servlet容器调用HttpServlet对象的service方法，把HttpRequest对象与HttpResponse对象作为参数传给 HttpServlet对象；\n>     \n>     \n> 6.  HttpServlet调用HttpRequest对象的有关方法，获取Http请求信息；\n>     \n>     \n> 7.  HttpServlet调用HttpResponse对象的有关方法，生成响应数据；\n>     \n>     \n> 8.  Servlet容器把HttpServlet的响应结果传给Web Client；\n\n## Servlet生命周期\n\n**在Servlet接口中定义了5个方法，其中3个方法代表了Servlet的生命周期：**\n\n```\n1\\. init(ServletConfig)方法：负责初始化Servlet对象，在Servlet的生命周期中，该方法执行一次；该方法执行在单线程的环境下，因此开发者不用考虑线程安全的问题；\n2\\. service(ServletRequest req,ServletResponse res)方法：负责响应客户的请求；为了提高效率，Servlet规范要求一个Servlet实例必须能够同时服务于多个客户端请求，即service()方法运行在多线程的环境下，Servlet开发者必须保证该方法的线程安全性；\n3\\. destroy()方法：当Servlet对象退出生命周期时，负责释放占用的资源；\n\n```\n\n编程注意事项说明：\n\n1.  当Server Thread线程执行Servlet实例的init()方法时，所有的Client Service Thread线程都不能执行该实例的service()方法，更没有线程能够执行该实例的destroy()方法，因此Servlet的init()方法是工作在单线程的环境下，开发者不必考虑任何线程安全的问题。\n2.  当服务器接收到来自客户端的多个请求时，服务器会在单独的Client Service Thread线程中执行Servlet实例的service()方法服务于每个客户端。此时会有多个线程同时执行同一个Servlet实例的service()方法，因此必须考虑线程安全的问题。\n3.  虽然service()方法运行在多线程的环境下，并不一定要同步该方法。而是要看这个方法在执行过程中访问的资源类型及对资源的访问方式。分析如下：\n\n```\n1\\. 如果service()方法没有访问Servlet的成员变量也没有访问全局的资源比如静态变量、文件、数据库连接等，而是只使用了当前线程自己的资源，比如非指向全局资源的临时变量、request和response对象等。该方法本身就是线程安全的，不必进行任何的同步控制。\n\n2\\. 如果service()方法访问了Servlet的成员变量，但是对该变量的操作是只读操作，该方法本身就是线程安全的，不必进行任何的同步控制。\n\n3\\. 如果service()方法访问了Servlet的成员变量，并且对该变量的操作既有读又有写，通常需要加上同步控制语句。\n\n4\\. 如果service()方法访问了全局的静态变量，如果同一时刻系统中也可能有其它线程访问该静态变量，如果既有读也有写的操作，通常需要加上同步控制语句。\n\n5\\. 如果service()方法访问了全局的资源，比如文件、数据库连接等，通常需要加上同步控制语句。\n\n```\n\n在创建一个 Java servlet 时，一般需要子类 HttpServlet。该类中的方法允许您访问请求和响应包装器（wrapper），您可以用这个包装器来处理请求和创建响应。Servlet的生命周期，简单的概括这就分为四步：\n\n```\nServlet类加载--->实例化--->服务--->销毁；\n\n```\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405152215.png)\n\n\nServlet生命周期\n\n**创建Servlet对象的时机：**\n\n1.  **默认情况下**，在Servlet容器启动后：客户首次向Servlet发出请求，Servlet容器会判断内存中是否存在指定的Servlet对象，如果没有则创建它，然后根据客户的请求创建HttpRequest、HttpResponse对象，从而调用Servlet对象的service方法；\n2.  **Servlet容器启动时**：当web.xml文件中如果<servlet>元素中指定了<load-on-startup>子元素时，Servlet容器在启动web服务器时，将按照顺序创建并初始化Servlet对象；\n3.  **Servlet的类文件被更新后，重新创建Servlet**。Servlet容器在启动时自动创建Servlet，这是由在web.xml文件中为Servlet设置的<load-on-startup>属性决定的。从中我们也能看到同一个类型的Servlet对象在Servlet容器中以单例的形式存在；\n\n> 注意：在web.xml文件中，某些Servlet只有<serlvet>元素，没有<servlet-mapping>元素，这样我们无法通过url的方式访问这些Servlet，这种Servlet通常会在<servlet>元素中配置一个<load-on-startup>子元素，让容器在启动的时候自动加载这些Servlet并调用init(ServletConfig config)方法来初始化该Servlet。其中方法参数config中包含了Servlet的配置信息，比如初始化参数，该对象由服务器创建。\n\n**销毁Servlet对象的时机：**\n\n**Servlet容器停止或者重新启动**：Servlet容器调用Servlet对象的destroy方法来释放资源。以上所讲的就是Servlet对象的生命周期。那么Servlet容器如何知道创建哪一个Servlet对象？Servlet对象如何配置？实际上这些信息是通过读取web.xml配置文件来实现的。\n\n```\n<servlet>\n    <!-- Servlet对象的名称 -->\n    <servlet-name>action<servlet-name>\n    <!-- 创建Servlet对象所要调用的类 -->\n    <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>\n    <init-param>\n        <!-- 参数名称 -->\n        <param-name>config</param-name>\n        <!-- 参数值 -->\n        <param-value>/WEB-INF/struts-config.xml</param-value>\n    </init-param>\n    <init-param>\n        <param-name>detail</param-name>\n        <param-value>2</param-value>\n    </init-param>\n    <init-param>\n        <param-name>debug</param-name>\n        <param-value>2</param-value>\n    </init-param>\n    <!-- Servlet容器启动时加载Servlet对象的顺序 -->\n    <load-on-startup>2</load-on-startup>\n</servlet>\n<!-- 要与servlet中的servlet-name配置节内容对应 -->\n<servlet-mapping>\n    <servlet-name>action</servlet-name>\n    <!-- 客户访问的Servlet的相对URL路径 -->\n    <url-pattern>*.do</url-pattern>\n</servlet-mapping>\n\n```\n\n> 当Servlet容器启动的时候读取<servlet>配置节信息，根据<servlet-class>配置节信息创建Servlet对象，同时根据<init-param>配置节信息创建HttpServletConfig对象，然后执行Servlet对象的init方法，并且根据<load-on-startup>配置节信息来决定创建Servlet对象的顺序，如果此配置节信息为负数或者没有配置，那么在Servlet容器启动时，将不加载此Servlet对象。当客户访问Servlet容器时，Servlet容器根据客户访问的URL地址，通过<servlet-mapping>配置节中的<url-pattern>配置节信息找到指定的Servlet对象，并调用此Servlet对象的service方法。\n\n在整个Servlet的生命周期过程中，**创建Servlet实例、调用实例的init()和destroy()方法都只进行一次**，当初始化完成后，Servlet容器会将该实例保存在内存中，通过调用它的service()方法，为接收到的请求服务。下面给出Servlet整个生命周期过程的UML序列图，如图所示：\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405152252.png)\n\n\nServlet生命周期\n\n\n> 如果需要让Servlet容器在启动时即加载Servlet，可以在web.xml文件中配置<load-on-startup>元素。\n\n## Servlet中的Listener\n\nListener 使用的非常广泛，它是基于观察者模式设计的，Listener 的设计对开发 Servlet 应用程序提供了一种快捷的手段，能够方便的从另一个纵向维度控制程序和数据。目前 Servlet 中提供了 5 种两类事件的观察者接口，它们分别是：4 个 EventListeners 类型的，ServletContextAttributeListener、ServletRequestAttributeListener、ServletRequestListener、HttpSessionAttributeListener 和 2 个 LifecycleListeners 类型的，ServletContextListener、HttpSessionListener。如下图所示：\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405152314.png)\n\nServlet中的Listener\n\n\n它们基本上涵盖了整个 Servlet 生命周期中，你感兴趣的每种事件。这些 Listener 的实现类可以配置在 web.xml 中的 <listener> 标签中。当然也可以在应用程序中动态添加 Listener，需要注意的是 ServletContextListener 在容器启动之后就不能再添加新的，因为它所监听的事件已经不会再出现。掌握这些 Listener 的使用，能够让我们的程序设计的更加灵活。\n\n# Cookie与Session\n\nServlet 能够给我们提供两部分数据，一个是在 Servlet 初始化时调用 init 方法时设置的 ServletConfig，这个类基本上含有了 Servlet 本身和 Servlet 所运行的 Servlet 容器中的基本信息。还有一部分数据是由 ServletRequest 类提供，从提供的方法中发现主要是描述这次请求的 HTTP 协议的信息。关于这一块还有一个让很多人迷惑的 Session 与 Cookie。\n\nSession 与 Cookie 的作用都是为了保持访问用户与后端服务器的交互状态。它们有各自的优点也有各自的缺陷。然而具有讽刺意味的是它们优点和它们的使用场景又是矛盾的，例如使用 Cookie 来传递信息时，随着 Cookie 个数的增多和访问量的增加，它占用的网络带宽也也会越来越大。所以大访问量的时候希望用 Session，但是 Session 的致命弱点是不容易在多台服务器之间共享，所以这也限制了 Session 的使用。\n\n不管 Session 和 Cookie 有什么不足，我们还是要用它们。下面详细讲一下，Session 如何基于 Cookie 来工作。实际上有三种方式能可以让 Session 正常工作：\n\n*   基于 URL Path Parameter，默认就支持\n*   基于 Cookie，如果你没有修改 Context 容器个 cookies 标识的话，默认也是支持的\n*   基于 SSL，默认不支持，只有 connector.getAttribute(\"SSLEnabled\") 为 TRUE 时才支持\n\n第一种情况下，当浏览器不支持 Cookie 功能时，浏览器会将用户的 SessionCookieName 重写到用户请求的 URL 参数中，它的传递格式如：\n\n```\n /path/Servlet?name=value&name2=value2&JSESSIONID=value3\n\n```\n\n接着 Request 根据这个 JSESSIONID 参数拿到 Session ID 并设置到 request.setRequestedSessionId 中。\n\n> 请注意如果客户端也支持 Cookie 的话，Tomcat 仍然会解析 Cookie 中的 Session ID，并会覆盖 URL 中的 Session ID。\n\n如果是第三种情况的话将会根据 javax.servlet.request.ssl_session 属性值设置 Session ID。\n\n有了 Session ID 服务器端就可以创建 HttpSession 对象了，第一次触发是通过 request. getSession() 方法，如果当前的 Session ID 还没有对应的 HttpSession 对象那么就创建一个新的，并将这个对象加到 org.apache.catalina. Manager 的 sessions 容器中保存，Manager 类将管理所有 Session 的生命周期，Session 过期将被回收，服务器关闭，Session 将被序列化到磁盘等。只要这个 HttpSession 对象存在，用户就可以根据 Session ID 来获取到这个对象，也就达到了状态的保持。\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405152357.png)\n\n\nSession相关类图\n\n\n\n上从图中可以看出从 request.getSession 中获取的 HttpSession 对象实际上是 StandardSession 对象的门面对象，这与前面的 Request 和 Servlet 是一样的原理。下图是 Session 工作的时序图：\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405152416.png)\n\n\nSession工作的时序图\n\n\n\n还有一点与 Session 关联的 Cookie 与其它 Cookie 没有什么不同，这个配置的配置可以通过 web.xml 中的 session-config 配置项来指定。\n\n\n## 参考文章\n\n<https://segmentfault.com/a/1190000009707894>\n\n<https://www.cnblogs.com/hysum/p/7100874.html>\n\n<http://c.biancheng.net/view/939.html>\n\n<https://www.runoob.com/>\n\nhttps://blog.csdn.net/android_hl/article/details/53228348\n\n\n\n                     \n"
  },
  {
    "path": "docs/JavaWeb/走进JavaWeb技术世界：Tomcat5总体架构剖析.md",
    "content": "# 目录\n\n  * [连接器（Connector）](#连接器（connector）)\n  * [容器（Container）](#容器（container）)\n\n\n \n本文转载自互联网，侵删\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本系列文章将同步到我的个人博客：\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n本文是《走进JavaWeb技术世界》系列博文的其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，从servlet到框架，从ssm再到SpringBoot，一步步地学习JavaWeb基础知识，并上手进行实战，接着了解JavaWeb项目中经常要使用的技术和组件，包括日志组件、Maven、Junit，等等内容，以便让你更完整地了解整个JavaWeb技术体系，形成自己的知识框架。为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n**文末赠送8000G的Java架构师学习资料，需要的朋友可以到文末了解领取方式，资料包括Java基础、进阶、项目和架构师等免费学习资料，更有数据库、分布式、微服务等热门技术学习视频，内容丰富，兼顾原理和实践，另外也将赠送作者原创的Java学习指南、Java程序员面试指南等干货资源）**\n<!-- more -->\n\nTomcat 是 Java WEB 开发接触最多的 Servlet 容器，但它不仅仅是一个 Servlet 容器，它还是一个 WEB 应用服务器，在微服务架构体系下，为了降低部署成本，减少资源的开销，追求的是轻量化与稳定，而 Tomcat 是一个轻量级应用服务器，自然被很多开发人员所接受。\n\nTomcat 里面藏着很多值得我们每个 Java WEB 开发者学习的知识，可以这么说，当你弄懂了 Tomcat 的设计原理，Java WEB 开发对你来说已经没有什么秘密可言了。本篇文章主要是跟大家聊聊 Tomcat 的内部架构体系，让大家对 Tomcat 有个整体的认知。\n\n前面我也说了，Tomcat 的本质其实就是一个 WEB 服务器 + 一个 Servlet 容器，那么它必然需要处理网络的连接与 Servlet 的管理，因此，Tomcat 设计了两个核心组件来实现这两个功能，分别是连接器和容器，连接器用来处理外部网络连接，容器用来处理内部 Servlet，我用一张图来表示它们的关系：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405153506.png)\n\n一个 Tomcat 代表一个 Server 服务器，一个 Server 服务器可以包含多个 Service 服务，Tomcat 默认的 Service 服务是 Catalina，而一个 Service 服务可以包含多个连接器，因为 Tomcat 支持多种网络协议，包括 HTTP/1.1、HTTP/2、AJP 等等，一个 Service 服务还会包括一个容器，容器外部会有一层 Engine 引擎所包裹，负责与处理连接器的请求与响应，连接器与容器之间通过 ServletRequest 和 ServletResponse 对象进行交流。\n\n也可以从 server.xml 的配置结构可以看出 tomcat 整体的内部结构：\n\n<section>\n\n```\n<Server port=\"8005\" shutdown=\"SHUTDOWN\">\n```\n\n</section>\n\n## 连接器（Connector）\n\n连接器负责将各种网络协议封装起来，对外部屏蔽了网络连接与 IO 处理的细节，将处理得到的 Request 对象传递给容器处理，Tomcat 将处理请求的细节封装到 ProtocolHandler，ProtocolHandler 是一个接口类型，通过实现 ProtocolHandler 来实现各种协议的处理，如 Http11AprProtocol：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405153520.png)\n\nProtocolHandler 采用组件模式的设计，将处理网络连接，字节流封装成 Request 对象，再将 Request 适配成 Servlet 处理 ServletRequest 对象这几个动作，用组件封装起来了，ProtocolHandler 包括了三个组件：Endpoint、Processor、Adapter。\n\nEndpoint 在 ProtocolHandler 实现类的构造方法中创建，如下：\n\n<section>\n\n```\npublic Http11AprProtocol() {\n```\n\n</section>\n\n**Endpoint 组件用来处理底层的 Socket 网络连接**，AprEndpoint 里面有个叫 SocketProcessor 的内部类，它负责为 AprEndpoint 将接收到的 Socket 请求转化成 Request 对象，SocketProcessor 实现了 Runnable 接口，它会有一个专门的线程池来处理，后面我会单独从源码的角度分析 Endpoint 组件的设计原理。\n\norg.apache.tomcat.util.net.AprEndpoint.SocketProcessor#doRun：\n\n<section>\n\n```\n// Process the request from this socket\n```\n\n</section>\n\nprocess 方法会创建一个 processor 对象，**调用它的 process 方法将 Socket 字节流封装成 Request 对象**，在创建 Processor 组件时，会将 Adapter 组件添加到 Processor 组件中：\n\norg.apache.coyote.http11.AbstractHttp11Protocol#createProcessor：\n\n<section>\n\n```\nprotected Processor createProcessor() {\n```\n\n</section>\n\n而 Adapter 组件在连接器初始化时就已经创建好了：\n\norg.apache.catalina.connector.Connector#initInternal：\n\n<section>\n\n```\n// Initialize adapter\n```\n\n</section>\n\n目前为止，Tomcat 只有一个 Adapter 实现类，就是 CoyoteAdapter。**Adapter 的主要作用是将 Request 对象适配成容器能够识别的 Request 对象**，比如 Servlet 容器，它的只能识别 ServletRequest 对象，这时候就需要 Adapter 适配器类作一层适配。\n\n以上连接器的各个组件，我用一张图说明它们直接的关系：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405153534.png)\n\n## 容器（Container）\n\n在 Tomcat 中一共设计了 4 种容器，它们分别为 Engine、Host、Context、Wrapper，它们的关系如下图所示：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405153549.png)\n\n•**Engine：表示一个虚拟主机的引擎，一个 Tomcat Server 只有一个 引擎，连接器所有的请求都交给引擎处理，而引擎则会交给相应的虚拟主机去处理请求；**•**Host：表示虚拟主机，一个容器可以有多个虚拟主机，每个主机都有对应的域名，在 Tomcat 中，一个 webapps 就代表一个虚拟主机，当然 webapps 可以配置多个；**•**Context：表示一个应用容器，一个虚拟主机可以拥有多个应用，webapps 中每个目录都代表一个 Context，每个应用可以配置多个 Servlet。**\n\n从上图可看出，**各个容器组件之间的关系是由大到小，即父子关系，它们之间关系形成一个树状的结构**，它们的实现类都实现了 Container 接口，它有如下方法来控制容器组件之间的关系：\n\n<section>\n\n```\nublic interface Container extends Lifecycle {\n```\n\n</section>\n\n容器组件之间通过以上几个方法，即可实现它们之间的父子关系，有没有发现，Container 接口还继承了 Lifecycle 接口，它有如下方法：\n\n<section>\n\n```\npublic interface Lifecycle {   \n```\n\n</section>\n\nTomcat 中有很多组件，组件通过实现 Lifecycle 接口，Tomcat 通过事件机制来实现对这些组件生命周期的管理。\n\n**Tomcat 的这种容器设计思想，其实是运用了组合设计模式的思想，组合设计模式最大的优点是可以自由添加节点，这样也就使得 Tomcat 的容器组件非常地容易进行扩展，符合设计模式中的开闭原则。**\n\n现在我们知道了 Tomcat 的容器组件的组合方式，那我们现在就来想一个问题：\n\n当一个请求过来时，Tomcat 是如何识别请求并将它交给特定 Servlet 来处理呢？\n\n从容器的组合关系可以看出，它们调用顺序必定是：\n\n<section>\n\n```\nEngine -> Host -> Context -> Wrapper -> Servlet\n```\n\n</section>\n\n那么 Tomcat 是如何来定位 Servlet 的呢？答案是利用 Mapper 组件来完成定位的工作。\n\n**Mapper 最主要的核心功能是保存容器组件之间访问路径的映射关系**，它是如何做到这点的呢？\n\n我们不妨先从源码入手：\n\norg.apache.catalina.core.StandardService：\n\n<section>\n\n```\nprotected final Mapper mapper = new Mapper();\n```\n\n</section>\n\nService 实现类中，已经初始化了 Mapper 组件以及它的监听类 MapperListener，这里先说明一下，在 Tomcat 组件中，标准的实现组件类前缀会有 Standard，比如：\n\n<section>\n\n```\norg.apache.catalina.core.StandardServer\n```\n\n</section>\n\n在 Service 服务启动的时候，会调用 MapperListener.start() 方法，最终会执行 MapperListener 的 startInternal 方法：\n\norg.apache.catalina.mapper.MapperListener#startInternal：\n\n<section>\n\n```\nContainer[] conHosts = engine.findChildren();\n```\n\n</section>\n\n该方法会注册新的虚拟主机，接着 registerHost() 方法会注册 context，以此类推，从而将容器组件直接的访问的路径都注册到 Mapper 中。\n\n定位 Servlet 的流程图：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405153609.png)\n"
  },
  {
    "path": "docs/JavaWeb/走进JavaWeb技术世界：Tomcat和其他WEB容器的区别.md",
    "content": "# 目录\n  * [Tomcat和物理服务器的区别](#tomcat和物理服务器的区别)\n    * [Tomcat：](#tomcat：)\n    * [物理服务器：](#物理服务器：)\n  * [详解tomcat 与 nginx，apache的区别及优缺点](#详解tomcat-与-nginx，apache的区别及优缺点)\n    * [定义：](#定义：)\n    * [区别](#区别)\n    * [总结](#总结)\n\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章首发于我的个人博客：\n> www.how2playlife.com\n\n\n本文转载自互联网，侵删\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本系列文章将同步到我的个人博客：\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n本文是《走进JavaWeb技术世界》系列博文的其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，从servlet到框架，从ssm再到SpringBoot，一步步地学习JavaWeb基础知识，并上手进行实战，接着了解JavaWeb项目中经常要使用的技术和组件，包括日志组件、Maven、Junit，等等内容，以便让你更完整地了解整个JavaWeb技术体系，形成自己的知识框架。为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n**文末赠送8000G的Java架构师学习资料，需要的朋友可以到文末了解领取方式，资料包括Java基础、进阶、项目和架构师等免费学习资料，更有数据库、分布式、微服务等热门技术学习视频，内容丰富，兼顾原理和实践，另外也将赠送作者原创的Java学习指南、Java程序员面试指南等干货资源）**\n<!-- more -->\n下面主要介绍下tomcat 与 nginx，apache的定义、区别及优缺点。\n\n## Tomcat和物理服务器的区别\n\n### Tomcat：\n\n1.本质：软件 Web 应用服务器----一个免费的开放源代码的Web 应用服务器，属于轻量级应用服务器，在中小型系统和并发访问用户不是很多的场合下被普遍使用，是开发和调试JSP 程序的首选。\n\n2.用途：\n\na． 当在一台机器（即物理服务器，也就是物理机）上配置好Apache 服务器，可利用它响应HTML页面的访问请求。实际上Tomcat是Apache 服务器的扩展，但运行时它是独立运行的，所以当你运行tomcat 时，它实际上作为一个与Apache 独立的进程单独运行的，Tomcat 实际上运行JSP 页面和Servlet\n\nb． Tomcat和IIS等Web服务器一样，具有处理HTML页面的功能，另外它还是一个Servlet和JSP容器，独立的Servlet容器是Tomcat的默认模式。\n\n### 物理服务器：\n\n1．本质：硬件，也就是我们经常讲的服务器或者物理机，我们的PC就是一台性能较低的网络服务器，常见的有 云服务器（例如阿里云ECS）等\n\n2．组成：处理器、硬盘、内存、系统总线等，和通用的计算机架构类似，但是由于需要提供高可靠的服务，因此在处理能力、稳定性、可靠性、安全性、可扩展性、可管理性等方面要求较高。\n\n\n## 详解tomcat 与 nginx，apache的区别及优缺点\n\n\n### 定义：\n\n1. Apache\n\nApache HTTP服务器是一个模块化的服务器，可以运行在几乎所有广泛使用的计算机平台上。其属于应用服务器。Apache支持支持模块多，性能稳定，Apache本身是静态解析，适合静态HTML、图片等，但可以通过扩展脚本、模块等支持动态页面等。\n\n(Apche可以支持PHPcgiperl,但是要使用Java的话，你需要Tomcat在Apache后台支撑，将Java请求由Apache转发给Tomcat处理。) 缺点：配置相对复杂，自身不支持动态页面。\n\n2. Tomcat：\n\nTomcat是应用(Java)服务器，它只是一个Servlet(JSP也翻译成Servlet)容器，可以认为是Apache的扩展，但是可以独立于Apache运行。\n\n3. Nginx\n\nNginx是俄罗斯人编写的十分轻量级的HTTP服务器,Nginx，它的发音为“engine X”，是一个高性能的HTTP和反向代理服务器，同时也是一个IMAP/POP3/SMTP 代理服务器。\n\n### 区别\n\nApache与Tomcat的比较\n\n相同点：\n\n两者都是Apache组织开发的两者都有HTTP服务的功能两者都是免费的 不同点：\n\nApache是专门用了提供HTTP服务的，以及相关配置的(例如虚拟主机、URL转发等等)，而Tomcat是Apache组织在符合Java EE的JSP、Servlet标准下开发的一个JSP服务器.\n\nApache是一个Web服务器环境程序,启用他可以作为Web服务器使用,不过只支持静态网页如(ASP,PHP,CGI,JSP)等动态网页的就不行。如果要在Apache环境下运行JSP的话就需要一个解释器来执行JSP网页,而这个JSP解释器就是Tomcat。\n\nApache:侧重于HTTPServer ，Tomcat:侧重于Servlet引擎，如果以Standalone方式运行，功能上与Apache等效，支持JSP，但对静态网页不太理想;\n\nApache是Web服务器，Tomcat是应用(Java)服务器，它只是一个Servlet(JSP也翻译成Servlet)容器，可以认为是Apache的扩展，但是可以独立于Apache运行。\n\n实际使用中Apache与Tomcat常常是整合使用：\n\n如果客户端请求的是静态页面，则只需要Apache服务器响应请求。 如果客户端请求动态页面，则是Tomcat服务器响应请求。 因为JSP是服务器端解释代码的，这样整合就可以减少Tomcat的服务开销。\n\n可以理解Tomcat为Apache的一种扩展。\n\n2\\. Nginx与Apache比较\n\n1) nginx相对于apache的优点\n\n轻量级，同样起web 服务，比apache占用更少的内存及资源 抗并发，nginx 处理请求是异步非阻塞的，而apache 则是阻塞型的，在高并发下nginx 能保持低资源低消耗高性能高度模块化的设计，编写模块相对简单提供负载均衡\n\n社区活跃，各种高性能模块出品迅速\n\n2) apache 相对于nginx 的优点\n\napache的 rewrite 比nginx 的强大 ;\n\n支持动态页面;\n\n支持的模块多，基本涵盖所有应用;\n\n性能稳定，而nginx相对bug较多。\n\n3) 两者优缺点比较\n\nNginx 配置简洁, Apache 复杂 ;\n\nNginx 静态处理性能比 Apache 高 3倍以上 ;\n\nApache 对 PHP 支持比较简单，Nginx 需要配合其他后端用;Apache 的组件比 Nginx 多 ;\n\napache是同步多进程模型，一个连接对应一个进程;nginx是异步的，多个连接(万级别)可以对应一个进程;\n\nnginx处理静态文件好,耗费内存少;\n\n动态请求由apache去做，nginx只适合静态和反向;\n\nNginx适合做前端服务器，负载性能很好;\n\nNginx本身就是一个反向代理服务器 ，且支持负载均衡\n\n### 总结\n\nNginx优点：负载均衡、反向代理、处理静态文件优势。nginx处理静态请求的速度高于apache;\n\nApache优点：相对于Tomcat服务器来说处理静态文件是它的优势，速度快。Apache是静态解析，适合静态HTML、图片等。\n\nTomcat：动态解析容器，处理动态请求，是编译JSPServlet的容器，Nginx有动态分离机制，静态请求直接就可以通过Nginx处理，动态请求才转发请求到后台交由Tomcat进行处理。\n\nApache在处理动态有优势，Nginx并发性比较好，CPU内存占用低，如果rewrite频繁，那还是Apache较适合。\n\n\n\n                     \n"
  },
  {
    "path": "docs/JavaWeb/走进JavaWeb技术世界：从JavaBean讲到Spring.md",
    "content": "# 目录\n  * [**Java Bean**](#java-bean)\n  * [**JSP + Java Bean**](#jsp--java-bean)\n  * [Enterprise Java bean](#enterprise-java-bean)\n  * [Spring](#spring)\n  * [JavaBean 和 Spring中Bean的区别](#javabean-和-spring中bean的区别)\n    * [Jave bean](#jave-bean)\n    * [springbean](#springbean)\n  * [参考文章](#参考文章)\n\n本文转载自互联网，侵删\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本系列文章将同步到我的个人博客：\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n本文是《走进JavaWeb技术世界》系列博文的其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，从servlet到框架，从ssm再到SpringBoot，一步步地学习JavaWeb基础知识，并上手进行实战，接着了解JavaWeb项目中经常要使用的技术和组件，包括日志组件、Maven、Junit，等等内容，以便让你更完整地了解整个JavaWeb技术体系，形成自己的知识框架。为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n<!-- more -->\n\n\n## **Java Bean**\n\nJava语言欠缺属性、事件、多重继承功能。所以，如果要在Java程序中实现一些面向对象编程的常见需求，只能手写大量胶水代码。Java Bean正是编写这套胶水代码的惯用模式或约定。这些约定包括getXxx、setXxx、isXxx、addXxxListener、XxxEvent等。遵守上述约定的类可以用于若干工具或库。\n\n举个例子，假如有人要用Java实现一个单向链表类，可能会这样写：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405154643.png)\n\n\n上述实现为了能够快速获取链表的大小，把链表大小缓存在size变量中。用法如下：\n\nJavaIntList myList = new JavaIntList( );\n\nSystem.out.println(myList.size);\n\n要节省内存，不要缓存size变量了，把代码改成这样：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405154703.png)\n\n发现找不到什么size变量。如果要找到size变量，你就必须保持向后兼容性。所以Java标准库中，绝对不会出现public int size这样的代码，而一定会一开始就写成：\n\nprivate int size;\n\npublic int getSize( ){return size;}\n\n让用户一开始就使用getSize，以便有朝一日修改getSize实现时，不破坏向后兼容性。这种public int getSize() { return size; }的惯用手法，就是Java Bean。\n\n## **JSP + Java Bean**\n\n在jsp上， 可以用java bean 来封装业务逻辑，保存数据到数据库， 像这样：\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405154720.png)\n\n其中jsp 直接用来接受用户的请求， 然后通过java bean 来处理业务， 具体的使用方法是：\n\n这就能把HTTP request中的所有参数都设置到 user 这个java bean 对应的属性上去。\n\n只要保证 http request中的参数名和 java bean 中的属性名是一样的。\n\n这个叫做JSP Model 1 的模型受到了很多Java程序员的欢迎 , 因为他们的应用规模都很小， 用Model 1 使得开发很快速，实际上， 这种方式和微软的asp , 以及和开源的php 几乎一样。\n\n但在项目中频繁使用了Model 1 导致整个系统的崩溃，因为系统中有好几千个jsp， 这些jsp互相调用(通过GET/POST), 到了最后调用关系无人能搞懂。\n\n为了解决这个问题，又推出了 ：JSP Model 2 ,  这是个模型真正的体现了Model-View-Controller的思想：\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405154740.png)\n\n\n\n\nServlet 充当Controller , jsp 充当 View ，Java bean 当然就是Model 了！\n\n业务逻辑， 页面显示， 和处理过程做了很好的分离。\n\n基于这个模型的扩展和改进， 很多Web开发框架开始如雨后春笋一样出现， 其中最著名的就是 SpringMVC了。\n\n## Enterprise Java bean\n\n越来越多企业程序员提出诉求：要分布式、要安全、要事务、要高可用性。\n\n诉求可以归结为：“我们只想关注我们的业务逻辑，我们不想，也不应该由我们来处理‘低级’的事务，多线程，连接池，以及其他各种各种的‘低级’API， 此外Java帝国一定得提供集群功能， 这样我们的一台机器死机以后，整个系统还能运转。 ”\n\n于是推出了J2EE， 像Java bean 一样， 这还是一个规范， 但是比Java bean 复杂的多， 其中有：\n\n**JDBC**: Java 数据库连接\n\n**JNDI**: Java 命名和目录接口， 通过一个名称就可以定位到一个数据源， 连jdbc连接都不用了\n\n**RMI**： 远程过程调用， 让一个机器上的java 对象可以调用另外一个机器上的java 对象\n\n**JMS**:  Java 消息服务， 可以使用消息队列了\n\n**JTA**： Java 事务管理， 支持分布式事务， 能在访问、更新多个数据库的时候，仍然保证事务， 还是分布式。\n\n**Java mail**: 收发邮件\n\nJ2EE 后来改成了Java EE。\n\n当然最重要的是， java bean 变成了**Enterprise Java bean**, 简称**EJB**。\n\n使用了EJB， 你就可以把精力只放在业务上了， 那些烦人的事务管理， 安全管理，线程 统统交给容器（应用服务器）来处理吧。\n\n我们还提供了额外的福利， 只要你的应用服务器是由多个机器组成的集群， EJB就可以无缝的运行在这个集群上， 你完全不用考虑一个机器死掉了应用该怎么办。我们都帮你搞定了。\n\n使用Session Bean ， 可以轻松的处理你的业务。\n\n使用实体Bean (Entity bean ) , 你和数据库打交道会变得极为轻松， 甚至sql 都不用写了。\n\n使用消息驱动Bean(Message Driven bean ) , 你可以轻松的和一个消息队列连接， 处理消息。\n\n## Spring\n\n然而，大部分的程序员就发现， EJB中用起来极为繁琐和笨重， 性能也不好， 为了获得所谓的分布式，反而背上了沉重的枷锁。\n\n实体Bean很快没人用了， 就连简单的无状态Session bean 也被大家所诟病， 其中一条罪状就是“代码的侵入性”。\n\n在定义EJB的时候没考虑那么多，程序员在定义一个Session bean的时候，需要写一大堆和业务完全没有关系的类。\n\n还需要被迫实现一些根本不应该实现的接口及其方法：\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/9915352-535d649b27315b2a.png)\n\n\n\n\n他们希望这个样子：\n````\npublic class HelloworldBean{\n\n  public String hello(){\n\n    return \"hello world\"\n\n }\n\n}\n````\n\n与此同时，他们还过分的要求保留事务、 安全这些必备的东西。\n\nSpring 框架顺应了POJO的潮流， 提供了一个spring 的容器来管理这些POJO, 也叫bean 。\n\n对于一个Bean 来说，如果你依赖别的Bean , 只需要声明即可， spring 容器负责把依赖的bean 给“注入进去“， 起初大家称之为控制反转(IoC)。\n\n后来 Martin flower 给这种方式起来个更好的名字，叫“依赖注入”（DI）。\n\n如果一个Bean 需要一些像事务，日志，安全这样的通用的服务， 也是只需要声明即可， spring 容器在运行时能够动态的“织入”这些服务， 这叫面向切面（AOP）。\n\n总之，spring和spring mvc极大的增加了Java对web开发领地的统治力。\n\n## JavaBean 和 Spring中Bean的区别\n先了解一下各自是什么吧!\n\n### Jave bean\njavaBean简单的讲就是实体类，用来封装对象，这个类里面全部都是属性值，和get，set方法。简单笼统的说就是一个类，一个可复用的类。javaBean在MVC设计模型中是model，又称模型层，在一般的程序中，我们称它为数据层，就是用来设置数据的属性和一些行为，然后我会提供获取属性和设置属性的get/set方法JavaBean是一种JAVA语言写成的可重用组件。为写成JavaBean，类必须是具体的和公共的，并且具有无参数的构造器。\n\n### springbean\n对于使用Spring框架的开发人员来说，我们主要做的主要有两件事情：①开发Bean;②配置Bean;而Spring帮我们做的就是根据配置文件来创建Bean实例，并调用Bean实例的方法来完成“依赖注入”，可以把Spring容器理解成一个大型工厂，Bean就是该工厂的产品，工厂(Spirng容器)里能生产出来什么样的产品（Bean），完全取决于我们在配置文件中的配置。其实就是根据配置文件产生对象,而不需要人为的手动去创造对象,降低了耦合.\n\n用处不同：传统javabean更多地作为值传递参数，而spring中的bean用处几乎无处不在，任何组件都可以被称为bean。\n\n写法不同：传统javabean作为值对象，要求每个属性都提供getter和setter方法；但spring中的bean只需为接受设值注入的属性提供setter方法。\n\njavabean的写法:\n````\n    public class A{\n    private String a;\n    private void setA(String a){\n    this.a = a;\n    }\n    private String getA(){\n    return a;\n    }\n    }\n    springbean的写法\n    \n    <bean id=\"p1\" class=\"com.zking.Pojo.Person\" scope=\"prototype\">\n    //及时加载 加载你的xml配置文件\n    ApplicationContext applicationContext = new ClassPathXmlApplicationContext(\"ApplicationContext.xml\");\n    //getbean输入你配置类的别名得到 person对象\n     Person p = (Person) applicationContext.getBean(\"p1\");\n````     \nid是给这个对象定的别名class是这个实体类的全路径名根据配置文件来创建Bean实例，并调用Bean实例的方法bean里面还有很多属性\n\n生命周期不同：传统javabean作为值对象传递，不接受任何容器管理其生命周期；spring中的bean有spring管理其生命周期行为。\n\n所有可以被spring容器实例化并管理的java类都可以称为bean。\n\n原来服务器处理页面返回的值都是直接使用request对象，后来增加了javabean来管理对象，所有页面值只要是和javabean对应，就可以用类.GET属性方法来获取值。javabean不只可以传参数，也可以处理数据，相当与把一个服务器执行的类放到了页面上，使对象管理相对不那么乱（对比asp的时候所有内容都在页面上完成）。\n\nspring中的bean，是通过配置文件、javaconfig等的设置，有spring自动实例化，用完后自动销毁的对象。让我们只需要在用的时候使用对象就可以，不用考虑如果创建类对象（这就是spring的注入）。一般是用在服务器端代码的执行上。\n\n## 参考文章\n微信公众号【码农翻身】\nhttps://blog.csdn.net/hmh13548571896/article/details/100628104\nhttps://www.cnblogs.com/xll1025/p/11366413.html\nhttps://blog.csdn.net/qqqnzhky/article/details/82747333\nhttps://www.cnblogs.com/mike-mei/p/9712836.html\nhttps://blog.csdn.net/qq_42245219/article/details/82748460\n                     \n"
  },
  {
    "path": "docs/JavaWeb/走进JavaWeb技术世界：从手动编译打包到项目构建工具Maven.md",
    "content": "# 目录\n  * [maven简介](#maven简介)\n    * [1.1 Maven是什么](#11-maven是什么)\n    * [1.2 Maven发展史](#12-maven发展史)\n    * [1.3 为什么要用Maven](#13-为什么要用maven)\n  * [Maven 新手入门](#maven-新手入门)\n    * [Maven概念](#maven概念)\n    * [maven的安装](#maven的安装)\n    * [maven目录](#maven目录)\n    * [Maven常用命令说明](#maven常用命令说明)\n    * [Maven使用](#maven使用)\n    * [传递性依赖](#传递性依赖)\n    * [依赖范围](#依赖范围)\n  * [Maven和Gradle的比较](#maven和gradle的比较)\n    * [依赖管理系统](#依赖管理系统)\n    * [多模块构建](#多模块构建)\n    * [一致的项目结构](#一致的项目结构)\n    * [一致的构建模型](#一致的构建模型)\n    * [插件机制](#插件机制)\n  * [参考文章](#参考文章)\n\n\n\n本文转载自互联网，侵删\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本系列文章将同步到我的个人博客：\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n本文是《走进JavaWeb技术世界》系列博文的其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，从servlet到框架，从ssm再到SpringBoot，一步步地学习JavaWeb基础知识，并上手进行实战，接着了解JavaWeb项目中经常要使用的技术和组件，包括日志组件、Maven、Junit，等等内容，以便让你更完整地了解整个JavaWeb技术体系，形成自己的知识框架。为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n**文末赠送8000G的Java架构师学习资料，需要的朋友可以到文末了解领取方式，资料包括Java基础、进阶、项目和架构师等免费学习资料，更有数据库、分布式、微服务等热门技术学习视频，内容丰富，兼顾原理和实践，另外也将赠送作者原创的Java学习指南、Java程序员面试指南等干货资源）**\n<!-- more -->\n## maven简介\n### 1.1 Maven是什么\n\nMaven是一个项目管理和综合工具。 Maven提供了开发人员构建一个完整的生命周期框架。开发者团队可以自动完成项目的基础工具建设， Maven使用标准的目录结构和默认构建生命周期。\n\n在多个开发者团队环境时， Maven可以设置按标准在非常短的时间里完成配置工作。 由于大部分项目的设置都很简单， 并且可重复使用， Maven让开发人员的工作更轻松， 同时创建报表， 检查， 构建和测试自动化设置。\n\n用过GitHub的同学看到这里应该感觉似曾相识，对，Maven和git的作用很相似，都是为了方便项目的创建与管理。\n\n概括地说， Maven简化和标准化项目建设过程。 处理编译， 分配， 文档， 团队协作和其他任务的无缝连接。 Maven增加可重用性并负责建立相关的任务。\n\n### 1.2 Maven发展史\n\nMaven设计之初， 是为了简化Jakarta Turbine项目的建设。 在几个项目， 每个项目包含了不同的Ant构建文件。 JAR检查到CVS。 Apache组织开发Maven可以建立多个项目， 发布项目信息， 项目部署， 在几个项目中JAR文件提供团队合作和帮助。\n\nMaven的经历了Maven-> Maven2 -> Maven3的发展。\n\n### 1.3 为什么要用Maven\n\nMaven之前我们经常使用Ant来进行Java项目的构建， 然后Ant仅是一个构建工具， 它并未对项目的中的工程依赖以及项目本身进行管理， 并且Ant作为构建工具未能消除软件构建的重复性， 因为不同的项目需要编写对应的Ant任务。\n\nMaven作为后来者， 继承了Ant的项目构建功能， 并且提供了依赖关系， 项目管理的功能， 因此它是一个项目管理和综合工具， 其核心的依赖管理， 项目信息管理， 中央仓库， 约定大于配置的核心功能使得Maven成为当前Java项目构建和管理工具的标准选择。\n\n学习Maven的理由是非常多：\n\n主流IDE（Eclipse,IDEA,Netbean） 够内置了Maven\n\nSpringFramework已经不再提供jar的下载， 直接通过Maven进行依赖下载。\n\n在github， 开源社区几乎所有流行的Java项目都是通过Maven进行构建和管理的。\n## Maven 新手入门\n\n### Maven概念\n\nMaven作为一个构建工具，不仅能帮我们自动化构建，还能够抽象构建过程，提供构建任务实现;它跨平台，对外提供了一致的操作接口，这一切足以使它成为优秀的、流行的构建工具。\n\nMaven不仅是构建工具，还是一个依赖管理工具和项目管理工具，它提供了中央仓库，能帮我自动下载构件。\n\n### maven的安装\n\n一：因为本人是window系统，所以这里只介绍window下如何安装，在安装Maven之前，先确认已经安装了JDK.\n[![image.png](http://www.pianshen.com/images/221/09092452baf3edd653f387516fb8be0d.png \"image.png\")](http://upload-images.jianshu.io/upload_images/5811881-5a7737962f83f677.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 \"image.png\")\n\n二：接着去[Maven官网](https://maven.apache.org/download.cgi)下载界面下载想要的版本解压到你想要的目录就行\n[![image.png](http://www.pianshen.com/images/434/28b5fb0701c54ac4ada5500ed99bdc12.png \"image.png\")](http://upload-images.jianshu.io/upload_images/5811881-16d9fd82c7f938ae.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 \"image.png\")\n\n[![image.png](http://www.pianshen.com/images/370/fb1719c12ec1fec62d766168eb5fb2d2.png \"image.png\")](http://upload-images.jianshu.io/upload_images/5811881-7482108a7ff71031.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 \"image.png\")\n\n三：最后设置一下环境变量，将Maven安装配置到操作系统环境中，主要就是配置M2_HOME和PATH两项，如图\n[![image.png](http://www.pianshen.com/images/162/46a29661ccbce3f798e931c61c9b39aa.png \"image.png\")](http://upload-images.jianshu.io/upload_images/5811881-ffdf167e64415703.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 \"image.png\")\n\n都搞定后，验证一下，打开doc输入 mvn -v如何得到下面信息就说明配置成功了\n[![image.png](http://www.pianshen.com/images/496/373fd8fcc75b3e1af5f038ea33c36aa0.png \"image.png\")](http://upload-images.jianshu.io/upload_images/5811881-c473853017951ebe.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 \"image.png\")\n\n### maven目录\n\n[![image.png](http://www.pianshen.com/images/307/3244327db95e1096a8f82cf2fc66e62b.png \"image.png\")](http://upload-images.jianshu.io/upload_images/5811881-8a4c77bcc9a4565a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 \"image.png\")\n\n*   bin目录：\n    该目录包含了mvn运行的脚本，这些脚本用来配置java命令，准备好classpath和相关的Java系统属性，然后执行Java命令。\n*   boot目录:\n    该目录只包含一个文件，该文件为plexus-classworlds-2.5.2.jar。plexus-classworlds是一个类加载器框架，相对于默认的java类加载器，它提供了更加丰富的语法以方便配置，Maven使用该框架加载自己的类库。\n*   conf目录:\n    该目录包含了一个非常重要的文件settings.xml。直接修改该文件，就能在机器上全局地定制Maven的行为，一般情况下，我们更偏向于复制该文件至~/.m2/目录下（~表示用户目录），然后修改该文件，在用户范围定制Maven的行为。\n*   lib目录:\n    该目录包含了所有Maven运行时需要的Java类库，Maven本身是分模块开发的，因此用户能看到诸如maven-core-3.0.jar、maven-model-3.0.jar之类的文件，此外这里还包含一些Maven用到的第三方依赖如commons-cli-1.2.jar、commons-lang-2.6.jar等等。\n\n### Maven常用命令说明\n\n    mvn clean：表示运行清理操作（会默认把target文件夹中的数据清理）。\n    mvn clean compile：表示先运行清理之后运行编译，会将代码编译到target文件夹中。\n    mvn clean test：运行清理和测试。\n    mvn clean package：运行清理和打包。\n    mvn clean install：运行清理和安装，会将打好的包安装到本地仓库中，以便其他的项目可以调用。\n    mvn clean deploy：运行清理和发布（发布到私服上面）。\n\n上面的命令大部分都是连写的，大家也可以拆分分别执行，这是活的，看个人喜好以及使用需求，Eclipse Run as对maven项目会提供常用的命令。\n\n### Maven使用\n\n\n\n    <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n    <project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n        xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n        <modelVersion>4.0.0</modelVersion>\n        <groupId>com.tengj</groupId>\n        <artifactId>springBootDemo1</artifactId>\n        <version>0.0.1-SNAPSHOT</version>\n        <name>springBootDemo1</name>\n    </project>\n\n\n代码的第一行是XML头，指定了该xml文档的版本和编码方式。\nproject是所有pom.xml的根元素，它还声明了一些POM相关的命名空间及xsd元素。\n根元素下的第一个子元素modelVersion指定了当前的POM模型的版本，对于Maven3来说，它只能是4.0.0\n代码中最重要是包含了groupId,artifactId和version了。这三个元素定义了一个项目基本的坐标，在Maven的世界，任何的jar、pom或者jar都是以基于这些基本的坐标进行区分的。\n\ngroupId定义了项目属于哪个组，随意命名，比如谷歌公司的myapp项目，就取名为 com.google.myapp\n\nartifactId定义了当前Maven项目在组中唯一的ID,比如定义hello-world。\n\nversion指定了项目当前的版本0.0.1-SNAPSHOT,SNAPSHOT意为快照，说明该项目还处于开发中，是不稳定的。\n\nname元素生命了一个对于用户更为友好的项目名称，虽然这不是必须的，但还是推荐为每个POM声明name,以方便信息交流\n\n## 依赖的配置\n\n\n\n    <project>\n    ...\n    <dependencies>\n        <dependency>\n            <groupId>实际项目</groupId>\n    　　　　 <artifactId>模块</artifactId>\n    　　　　 <version>版本</version>\n    　　　　 <type>依赖类型</type>\n    　　　　 <scope>依赖范围</scope>\n    　　　　 <optional>依赖是否可选</optional>\n    　　　　 <!—主要用于排除传递性依赖-->\n    　　　　 <exclusions>\n    　　　　     <exclusion>\n    　　　　　　　    <groupId>…</groupId>\n    　　　　　　　　　 <artifactId>…</artifactId>\n    　　　　　　　</exclusion>\n    　　　　 </exclusions>\n    　　</dependency>\n    <dependencies>\n    ...\n    </project>\n\n根元素project下的dependencies可以包含一个或者多个dependency元素，以声明一个或者多个项目依赖。每个依赖可以包含的元素有：\n\n*   grounpId、artifactId和version:以来的基本坐标，对于任何一个依赖来说，基本坐标是最重要的，Maven根据坐标才能找到需要的依赖。\n*   type:依赖的类型，对于项目坐标定义的packaging。大部分情况下，该元素不必声明，其默认值为jar\n*   scope:依赖的范围\n*   optional:标记依赖是否可选\n*   exclusions:用来排除传递性依赖\n\n## 依赖范围\n\n依赖范围就是用来控制依赖和三种classpath(编译classpath，测试classpath、运行classpath)的关系，Maven有如下几种依赖范围：\n\n*   compile:编译依赖范围。如果没有指定，就会默认使用该依赖范围。使用此依赖范围的Maven依赖，对于编译、测试、运行三种classpath都有效。典型的例子是spring-code,在编译、测试和运行的时候都需要使用该依赖。\n*   test:测试依赖范围。使用次依赖范围的Maven依赖，只对于测试classpath有效，在编译主代码或者运行项目的使用时将无法使用此依赖。典型的例子是Jnuit,它只有在编译测试代码及运行测试的时候才需要。\n*   provided:已提供依赖范围。使用此依赖范围的Maven依赖，对于编译和测试classpath有效，但在运行时候无效。典型的例子是servlet-api,编译和测试项目的时候需要该依赖，但在运行项目的时候，由于容器以及提供，就不需要Maven重复地引入一遍。\n*   runtime:运行时依赖范围。使用此依赖范围的Maven依赖，对于测试和运行classpath有效，但在编译主代码时无效。典型的例子是JDBC驱动实现，项目主代码的编译只需要JDK提供的JDBC接口，只有在执行测试或者运行项目的时候才需要实现上述接口的具体JDBC驱动。\n*   system:系统依赖范围。该依赖与三种classpath的关系，和provided依赖范围完全一致，但是，使用system范围的依赖时必须通过systemPath元素显示地指定依赖文件的路径。由于此类依赖不是通过Maven仓库解析的，而且往往与本机系统绑定，可能构成构建的不可移植，因此应该谨慎使用。systemPath元素可以引用环境变量，如：\n\n    \n    <dependency>\n        <groupId>javax.sql</groupId>\n        <artifactId>jdbc-stdext</artifactId>\n        <Version>2.0</Version>\n        <scope>system</scope>\n        <systemPath>${java.home}/lib/rt.jar</systemPath>\n    </dependency>\n\n*   import:导入依赖范围。该依赖范围不会对三种classpath产生实际的影响。\n    上述除import以外的各种依赖范围与三种classpath的关系如下:\n\n[![image.png](http://www.pianshen.com/images/89/9304bde50143be84e01bb47451e00a99.png \"image.png\")](http://upload-images.jianshu.io/upload_images/5811881-e7cdb7800f523b6b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 \"image.png\")\n\n### 传递性依赖\n\n比如一个account-email项目为例，account-email有一个compile范围的spring-code依赖，spring-code有一个compile范围的commons-logging依赖，那么commons-logging就会成为account-email的compile的范围依赖，commons-logging是account-email的一个传递性依赖\n\n\n有了传递性依赖机制，在使用Spring Framework的时候就不用去考虑它依赖了什么，也不用担心引入多余的依赖。Maven会解析各个直接依赖的POM，将那些必要的间接依赖，以传递性依赖的形式引入到当前的项目中。\n\n### 依赖范围\n\n假设A依赖于B,B依赖于C，我们说A对于B是第一直接依赖，B对于C是第二直接依赖，A对于C是传递性依赖。第一直接依赖和第二直接依赖的范围决定了传递性依赖的范围，如下图所示，最左边一行表示第一直接依赖范围，最上面一行表示第二直接依赖范围，中间的交叉单元格则表示传递依赖范围。\n\n[![image.png](http://www.pianshen.com/images/361/15e6b876f6226edf630f3fc9f92c9ec9.png \"image.png\")](http://upload-images.jianshu.io/upload_images/5811881-9e1e45b117656aac.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 \"image.png\")\n\n从上图中，我们可以发现这样的规律：\n\n*   当第二直接依赖的范围是compile的时候，传递性依赖的范围与第一直接依赖的范围一致；\n*   当第二直接依赖的范围是test的时候，依赖不会得以传递；\n*   当第二直接依赖的范围是provided的时候，只传递第一直接依赖范围也为provided的依赖，切传递依赖的范围同样为provided;\n*   当第二直接依赖的范围是runtime的时候，传递性依赖的范围与第一直接依赖的范围一致，但compile列外，此时传递性依赖范围为runtime.\n\n## Maven和Gradle的比较\n\nJava生态体系中有三大构建工具：Ant、Maven和Gradle。其中，Ant是由Apache软件基金会维护；Maven这个单词来自于意第绪语（犹太语），意为知识的积累，最初在Jakata Turbine项目中用来简化构建过程；Gradle是一个基于Apache Ant和Apache Maven概念的项目自动化构建开源工具，它使用一种基于Groovy的特定领域语言(DSL)来声明项目设置，抛弃了基于XML的各种繁琐配置。\n\n经过几年的发展，Ant几乎销声匿迹，而Maven由于较为不灵活的配置也渐渐被遗忘，而由于Gradle是基于Ant和Maven的一个优化版本，变得如日中天。\n\nMaven的主要功能主要分为依赖管理系统、多模块构建、一致的项目结构、一致的构建模型和插件机制。这里通过这五个方面介绍两者的不同：\n\n### 依赖管理系统\n\n在Maven的管理体系中，用GroupID、ArtifactID和Version组成的Coordination唯一标识一个依赖项。任何基于Maven构建的项目自身也必须定义这三项属性，生成的包可以是Jar包，也可以是War包或Ear包。\n\n一个典型的引用如下：\n\n```\n<dependencies>\n    <dependency>\n        <groupId>org.springframework.boot</groupId>\n        spring-boot-starter-data-jpa\n    </dependency>\n    <dependency>\n        <groupId>org.springframework.boot</groupId>\n        spring-boot-starter-thymeleaf\n    </dependency>\n    <dependency>\n        <groupId>org.springframework.boot</groupId>\n        spring-boot-starter-test\n        <scope>test</scope>\n    </dependency>\n</dependencies>\n\n```\n\n这里 GroupID类似于C#中的namespace或者Java中的package，而ArtifactID相当于Class，Version相当于不同版本，如果Version忽略掉，将选择最新的版本链接。\n\n同时，存储这些组件的仓库有远程仓库和本地仓库之分，远程仓库可以是使用世界公用的central仓库，也可以使用Apache Nexus自建的私有仓库；本地仓库则在本地计算机上。通过Maven安装目录下的settings.xml文件可以配置本地仓库的路径，以及采用的远程仓库地址。Gradle在设计时沿用了Maven这种依赖管理体系，同时也引入了改进，让依赖变得更加简洁：\n\n    dependencies {\n    // This dependency is exported to consumers, that is to say found on their compile classpath.\n    api 'org.apache.commons:commons-math3:3.6.1'\n    \n    \n    // This dependency is used internally, and not exposed to consumers on their own compile classpath.\n    implementation 'com.google.guava:guava:23.0'\n    \n    // Use JUnit test framework\n    testImplementation 'junit:junit:4.12'\n    \n    compile 'org.hibernate:hibernate-core:3.6.7.Final'\n    testCompile ‘junit:junit:4.+'\n    \n    \n    \n    }\n\n另外，Maven和Gradle对依赖项的审视也有所不同。在Maven中，一个依赖项有6种scope，分别是compile、provided、runtime、test、system、import。其中compile为默认。而gradle将其简化为4种，compile、runtime、testCompile、testRuntime。如上述代码“testCompile ‘junit:junit:4.+'”，在Gradle中支持动态的版本依赖，在版本号后面使用+号可以实现动态的版本管理。在解决依赖冲突方面Gradle的实现机制更加明确，两者都采用的是传递性依赖，而如果多个依赖项指向同一个依赖项的不同版本时可能会引起依赖冲突，Maven处理起来较为繁琐，而Gradle先天具有比较明确的策略。\n\n### 多模块构建\n\n在面向服务的架构中，通常将一个项目分解为多个模块。在Maven中需要定义parent POM(Project Object Model)作为一组module的通用配置模型，在POM文件中可以使用<modules>标签来定义一组子模块。parent POM中的build配置以及依赖配置会自动继承给子module。\n\nGradle也支持多模块构建，在parent的build.gradle中可以使用allprojects和subprojects代码块分别定义应用于所有项目或子项目中的配置。对于子模块中的定义放置在settings.gradle文件中，每一个模块代表project的对象实例，在parent的build.gradle中通过allproject或subprojects对这些对象进行操作，相比Maven更显灵活。\n\n    allprojects {\n    task nice << { task -> println \"I'm $task.project.name\" }\n    }\n\n执行命令gradle -q nice会依次打印出各模块的项目名称。\n\n### 一致的项目结构\n\nMaven指定了一套项目目录结构作为标准的java项目结构，Gradle也沿用了这一标准的目录结构。如果在Gradle项目中使用了Maven项目结构的话，在Gradle中无需进行多余的配置，只需在文件中包括apply plugin:'java'，系统会自动识别source、resource、test source、test resource等相应资源。\n\n同时，Gradle作为JVM上的构建工具，也支持Groovy、Scala等源代码的构建，同样功能Maven通过一些插件也能达到目的，但配置方面Gradle更灵活。\n\n### 一致的构建模型\n\n为了解决Ant中对项目构建缺乏标准化的问题，Maven设置了标准的项目周期，构建周期：验证、初始化、生成原始数据、处理原始数据、生成资源、处理资源、编译、处理类、生成测试原始数据、处理测试原始数据、生成测试资源、处理测试资源、测试编译、处理测试类、测试、预定义包、生成包文件、预集成测试、集成测试、后集成测试、核实、安装、部署。但这种构建周期也是Maven应用的劣势。因为Maven将项目的构建周期限制过严，无法在构建周期中添加新的阶段，只能将插件绑定到已有的阶段上。而Gradle在构建模型上非常灵活，可以创建一个task，并随时通过depends建立与已有task的依赖关系。\n\n### 插件机制\n\n两者都采用了插件机制，Maven是基于XML进行配置，而在Gradle中更加灵活。\n\n## 参考文章\nhttp://www.pianshen.com/article/4537698845\nhttps://www.jianshu.com/p/7248276d3bb5\nhttps://www.cnblogs.com/lykbk/p/erwerwerwerwerwerwe.html\nhttps://blog.csdn.net/u012131888/article/details/78209514\nhttps://blog.csdn.net/belvine/article/details/81073365\nhttps://blog.csdn.net/u012131888/article/details/78209514\n\n\n\n                     \n"
  },
  {
    "path": "docs/JavaWeb/走进JavaWeb技术世界：初探Tomcat9的HTTP请求过程.md",
    "content": "# 目录\n  * [初探Tomcat的HTTP请求过程](#初探tomcat的http请求过程)\n  * [Tomcat的组织结构](#tomcat的组织结构)\n    * [由Server.xml的结构看Tomcat的体系结构](#由serverxml的结构看tomcat的体系结构)\n  * [Tomcat Server处理一个HTTP请求的过程](#tomcat-server处理一个http请求的过程)\n  * [参考文章](#参考文章)\n\n本文转载自互联网，侵删\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本系列文章将同步到我的个人博客：\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n本文是《走进JavaWeb技术世界》系列博文的其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，从servlet到框架，从ssm再到SpringBoot，一步步地学习JavaWeb基础知识，并上手进行实战，接着了解JavaWeb项目中经常要使用的技术和组件，包括日志组件、Maven、Junit，等等内容，以便让你更完整地了解整个JavaWeb技术体系，形成自己的知识框架。为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n**文末赠送8000G的Java架构师学习资料，需要的朋友可以到文末了解领取方式，资料包括Java基础、进阶、项目和架构师等免费学习资料，更有数据库、分布式、微服务等热门技术学习视频，内容丰富，兼顾原理和实践，另外也将赠送作者原创的Java学习指南、Java程序员面试指南等干货资源）**\n<!-- more -->\n\n## 初探Tomcat的HTTP请求过程\n\n前言：\n1.作为Java开发人员，大多都对Tomcat不陌生，由Apache基金会提供技术支持与维护，因为其免费开源且易用，作为Web服务器深受市场欢迎，所以有必要对其进行深入的研究，本系列皆以Tomcat 8.5为研究课题，下载地址：[https://tomcat.apache.org/download-80.cgi](https://tomcat.apache.org/download-80.cgi)\n\n2.下图为 apache-tomcat-8.5.23.zip 在windows解压后的目录。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405152548.png)\n\n下面是解压后的一些关键目录:\n\n```\n* /bin - 启动和停止服务等批处理文件. ( *.sh) 文件 (为Unix系统)、 (*.bat) 文件 (for Windows系统)是一个功能性的复制文件. 自从Win32 command-line 开始是一些单一的，缺乏功能的组件, 现在有一些拓展性的功能\n* /conf - 配置文件和一些相关的DTD文件. 最重要的是 server.xml. 它是这个容器最主要的配置文件.\n* /logs - 日志文件会打印到这里\n* /webapps - 这里是你的应用程序部署的地方.\n```\n\n3.从最本质上讲，tomcat为一个servlet容器，首先研究一下Tomcat的架构，如下图：\n\n![图片描述](https://img.mukewang.com/5a26687d0001ca2712300718.png)\n\n架构诠释：\n\n1.Server(服务器)是Tomcat构成的顶级构成元素，所有一切均包含在Server中，Server的实现类StandardServer可以包含一个到多个Services,Service的实现类为StandardService调用了容器(Container)接口，其实是调用了Servlet Engine(引擎)，而且StandardService类中也指明了该Service归属的Server;\n\n2.Container: 引擎(Engine)、主机(Host)、上下文(Context)和Wraper均继承自Container接口，所以它们都是容器。但是，它们是有父子关系的，在主机(Host)、上下文(Context)和引擎(Engine)这三类容器中，引擎是顶级容器，直接包含是主机容器，而主机容器又包含上下文容器，所以引擎、主机和上下文从大小上来说又构成父子关系,虽然它们都继承自Container接口。\n\n3.连接器(Connector)将Service和Container连接起来，首先它需要注册到一个Service，它的作用就是把来自客户端的请求转发到Container(容器)，这就是它为什么称作连接器的原因。\n\n从功能的角度将Tomcat源代码分成5个子模块，分别是:\n\nJsper模块: 这个子模块负责jsp页面的解析、jsp属性的验证，同时也负责将jsp页面动态转换为java代码并编译成class文件。在Tomcat源代码中，凡是属于org.apache.jasper包及其子包中的源代码都属于这个子模块;\n\nServlet和Jsp模块: 这个子模块的源代码属于javax.servlet包及其子包，如我们非常熟悉的javax.servlet.Servlet接口、javax.servet.http.HttpServlet类及javax.servlet.jsp.HttpJspPage就位于这个子模块中;\n\nCatalina模块: 这个子模块包含了所有以org.apache.catalina开头的java源代码。该子模块的任务是规范了Tomcat的总体架构，定义了Server、Service、Host、Connector、Context、Session及Cluster等关键组件及这些组件的实现，这个子模块大量运用了Composite设计模式。同时也规范了Catalina的启动及停止等事件的执行流程。从代码阅读的角度看，这个子模块应该是我们阅读和学习的重点。\n\nConnector模块: 如果说上面三个子模块实现了Tomcat应用服务器的话，那么这个子模块就是Web服务器的实现。所谓连接器(Connector)就是一个连接客户和应用服务器的桥梁，它接收用户的请求，并把用户请求包装成标准的Http请求(包含协议名称，请求头Head，请求方法是Get还是Post等等)。同时，这个子模块还按照标准的Http协议，负责给客户端发送响应页面，比如在请求页面未发现时，connector就会给客户端浏览器发送标准的Http 404错误响应页面。\n\nResource模块: 这个子模块包含一些资源文件，如Server.xml及Web.xml配置文件。严格说来，这个子模块不包含java源代码，但是它还是Tomcat编译运行所必需的。\n\n## Tomcat的组织结构\n\n*   Tomcat是一个基于组件的服务器，它的构成组件都是可配置的，其中最外层的是Catalina servlet容器，其他组件按照一定的格式要求配置在这个顶层容器中。\n    Tomcat的各种组件都是在Tomcat安装目录下的/conf/server.xml文件中配置的。\n\n### 由Server.xml的结构看Tomcat的体系结构\n````\n<Server>                                                //顶层类元素，可以包括多个Service   \n\n    <Service>                                           //顶层类元素，可包含一个Engine，多个Connecter\n\n        <Connector>                                     //连接器类元素，代表通信接口\n\n                <Engine>                                //容器类元素，为特定的Service组件处理客户请求，要包含多个Host\n\n                        <Host>                          //容器类元素，为特定的虚拟主机组件处理客户请求，可包含多个Context\n\n                                <Context>               //容器类元素，为特定的Web应用处理所有的客户请求\n\n                                </Context>\n\n                        </Host>\n\n                </Engine>\n\n        </Connector>\n\n    </Service>\n\n</Server>\n````\n实际源码如下：\n````\n<?xml version='1.0' encoding='utf-8'?>\n\n<Server port=\"8005\" shutdown=\"SHUTDOWN\">\n\n  <Listener className=\"org.apache.catalina.startup.VersionLoggerListener\" />\n\n  <!-- Security listener. Documentation at /docs/config/listeners.html\n\n  <Listener className=\"org.apache.catalina.security.SecurityListener\" />\n\n  -->\n\n  <!--APR library loader. Documentation at /docs/apr.html -->\n\n  <Listener className=\"org.apache.catalina.core.AprLifecycleListener\" SSLEngine=\"on\" />\n\n  <!--Initialize Jasper prior to webapps are loaded. Documentation at /docs/jasper-howto.html -->\n\n  <Listener className=\"org.apache.catalina.core.JasperListener\" />\n\n  <!-- Prevent memory leaks due to use of particular java/javax APIs-->\n\n  <Listener className=\"org.apache.catalina.core.JreMemoryLeakPreventionListener\" />\n\n  <Listener className=\"org.apache.catalina.mbeans.GlobalResourcesLifecycleListener\" />\n\n  <Listener className=\"org.apache.catalina.core.ThreadLocalLeakPreventionListener\" />\n\n  <!-- Global JNDI resources\n\n       Documentation at /docs/jndi-resources-howto.html\n\n  -->\n\n  <GlobalNamingResources>\n\n    <!-- Editable user database that can also be used by\n\n         UserDatabaseRealm to authenticate users\n\n    -->\n\n    <Resource name=\"UserDatabase\" auth=\"Container\"\n\n              type=\"org.apache.catalina.UserDatabase\"\n\n              description=\"User database that can be updated and saved\"\n\n              factory=\"org.apache.catalina.users.MemoryUserDatabaseFactory\"\n\n              pathname=\"conf/tomcat-users.xml\" />\n\n  </GlobalNamingResources>\n\n  <!-- A \"Service\" is a collection of one or more \"Connectors\" that share\n\n       a single \"Container\" Note:  A \"Service\" is not itself a \"Container\",\n\n       so you may not define subcomponents such as \"Valves\" at this level.\n\n       Documentation at /docs/config/service.html\n\n   -->\n\n  <Service name=\"Catalina\">\n\n    <!--The connectors can use a shared executor, you can define one or more named thread pools-->\n\n    <!--\n\n    <Executor name=\"tomcatThreadPool\" namePrefix=\"catalina-exec-\"\n\n        maxThreads=\"150\" minSpareThreads=\"4\"/>\n\n    -->\n\n    <!-- A \"Connector\" represents an endpoint by which requests are received\n\n         and responses are returned. Documentation at :\n\n         Java HTTP Connector: /docs/config/http.html (blocking & non-blocking)\n\n         Java AJP  Connector: /docs/config/ajp.html\n\n         APR (HTTP/AJP) Connector: /docs/apr.html\n\n         Define a non-SSL HTTP/1.1 Connector on port 8080\n\n    -->\n\n    <Connector port=\"8080\" protocol=\"HTTP/1.1\"\n\n               connectionTimeout=\"20000\"\n\n               redirectPort=\"8443\" />\n\n    <!-- A \"Connector\" using the shared thread pool-->\n\n    <!--\n\n    <Connector executor=\"tomcatThreadPool\"\n\n               port=\"8080\" protocol=\"HTTP/1.1\"\n\n               connectionTimeout=\"20000\"\n\n               redirectPort=\"8443\" />\n\n    -->\n\n    <!-- Define a SSL HTTP/1.1 Connector on port 8443\n\n         This connector uses the BIO implementation that requires the JSSE\n\n         style configuration. When using the APR/native implementation, the\n\n         OpenSSL style configuration is required as described in the APR/native\n\n         documentation -->\n\n    <!--\n\n    <Connector port=\"8443\" protocol=\"org.apache.coyote.http11.Http11Protocol\"\n\n               maxThreads=\"150\" SSLEnabled=\"true\" scheme=\"https\" secure=\"true\"\n\n               clientAuth=\"false\" sslProtocol=\"TLS\" />\n\n    -->\n\n    <!-- Define an AJP 1.3 Connector on port 8009 -->\n\n    <Connector port=\"8009\" protocol=\"AJP/1.3\" redirectPort=\"8443\" />\n\n    <!-- An Engine represents the entry point (within Catalina) that processes\n\n         every request.  The Engine implementation for Tomcat stand alone\n\n         analyzes the HTTP headers included with the request, and passes them\n\n         on to the appropriate Host (virtual host).\n\n         Documentation at /docs/config/engine.html -->\n\n    <!-- You should set jvmRoute to support load-balancing via AJP ie :\n\n    <Engine name=\"Catalina\" defaultHost=\"localhost\" jvmRoute=\"jvm1\">\n\n    -->\n\n    <Engine name=\"Catalina\" defaultHost=\"localhost\">\n\n      <!--For clustering, please take a look at documentation at:\n\n          /docs/cluster-howto.html  (simple how to)\n\n          /docs/config/cluster.html (reference documentation) -->\n\n      <!--\n\n      <Cluster className=\"org.apache.catalina.ha.tcp.SimpleTcpCluster\"/>\n\n      -->\n\n      <!-- Use the LockOutRealm to prevent attempts to guess user passwords\n\n           via a brute-force attack -->\n\n      <Realm className=\"org.apache.catalina.realm.LockOutRealm\">\n\n        <!-- This Realm uses the UserDatabase configured in the global JNDI\n\n             resources under the key \"UserDatabase\".  Any edits\n\n             that are performed against this UserDatabase are immediately\n\n             available for use by the Realm.  -->\n\n        <Realm className=\"org.apache.catalina.realm.UserDatabaseRealm\"\n\n               resourceName=\"UserDatabase\"/>\n\n      </Realm>\n\n      <Host name=\"localhost\"  appBase=\"webapps\"\n\n            unpackWARs=\"true\" autoDeploy=\"true\">\n\n        <!-- SingleSignOn valve, share authentication between web applications\n\n             Documentation at: /docs/config/valve.html -->\n\n        <!--\n\n        <Valve className=\"org.apache.catalina.authenticator.SingleSignOn\" />\n\n        -->\n\n        <!-- Access log processes all example.\n\n             Documentation at: /docs/config/valve.html\n\n             Note: The pattern used is equivalent to using pattern=\"common\" -->\n\n        <Valve className=\"org.apache.catalina.valves.AccessLogValve\" directory=\"logs\"\n\n               prefix=\"localhost_access_log.\" suffix=\".txt\"\n\n               pattern=\"%h %l %u %t \"%r\" %s %b\" />\n\n      </Host>\n\n    </Engine>\n\n  </Service>\n\n</Server>\n````\n由上可得出Tomcat的体系结构：\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405152741.png)\n\n图一：Tomcat的体系结构\n\n由上图可看出Tomca的心脏是两个组件：Connecter和Container。一个Container可以选择多个Connecter，多个Connector和一个Container就形成了一个Service。Service可以对外提供服务，而Server服务器控制整个Tomcat的生命周期。\n\n## Tomcat Server处理一个HTTP请求的过程\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405152756.png)\n\n图三：Tomcat Server处理一个HTTP请求的过程\n\nTomcat Server处理一个HTTP请求的过程\n\n> 1、用户点击网页内容，请求被发送到本机端口8080，被在那里监听的Coyote HTTP/1.1 Connector获得。\n> \n> 2、Connector把该请求交给它所在的Service的Engine来处理，并等待Engine的回应。\n> \n> 3、Engine获得请求localhost/test/index.jsp，匹配所有的虚拟主机Host。\n> \n> 4、Engine匹配到名为localhost的Host（即使匹配不到也把请求交给该Host处理，因为该Host被定义为该Engine的默认主机），名为localhost的Host获得请求/test/index.jsp，匹配它所拥有的所有的Context。Host匹配到路径为/test的Context（如果匹配不到就把该请求交给路径名为“ ”的Context去处理）。\n> \n> 5、path=“/test”的Context获得请求/index.jsp，在它的mapping table中寻找出对应的Servlet。Context匹配到URL PATTERN为*.jsp的Servlet,对应于JspServlet类。\n> \n> 6、构造HttpServletRequest对象和HttpServletResponse对象，作为参数调用JspServlet的doGet（）或doPost（）.执行业务逻辑、数据存储等程序。\n> \n> 7、Context把执行完之后的HttpServletResponse对象返回给Host。\n> \n> 8、Host把HttpServletResponse对象返回给Engine。\n> \n> 9、Engine把HttpServletResponse对象返回Connector。\n> \n> 10、Connector把HttpServletResponse对象返回给客户Browser。　　\n> \n\n\n## 参考文章\n\nhttp://www.360doc.com/content/10/0730/19/61151_42573873.shtml\nhttps://my.oschina.net/leamon/blog/210133\nhttps://www.cnblogs.com/xll1025/p/11366264.html\nhttps://www.cnblogs.com/small-boy/p/8042860.html\n\n"
  },
  {
    "path": "docs/JavaWeb/走进JavaWeb技术世界：单元测试框架Junit.md",
    "content": "# 目录\n* [简介](#简介)\n* [概述](#概述)\n* [好处](#好处)\n* [Junit单元测试](#junit单元测试)\n    * [1 简介](#1-简介)\n    * [2 特点](#2-特点)\n    * [3 内容](#3-内容)\n        * [3.1 注解](#31-注解)\n        * [3.2 断言](#32-断言)\n    * [4 JUnit 3.X 和 JUnit 4.X 的区别](#4-junit-3x-和-junit-4x-的区别)\n        * [4.1 JUnit 3.X](#41-junit-3x)\n        * [4.2 JUnit 4.X](#42-junit-4x)\n        * [4.3 特别提醒](#43-特别提醒)\n    * [5 测试示例](#5-测试示例)\n        * [5.1 示例一：简单的 JUnit 3.X 测试](#51-示例一：简单的-junit-3x-测试)\n    * [6 个人建议](#6-个人建议)\n* [8 大单元测试框架](#8-大单元测试框架)\n\n\n\n本文转载自互联网，侵删\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本系列文章将同步到我的个人博客：\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n本文是《走进JavaWeb技术世界》系列博文的其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，从servlet到框架，从ssm再到SpringBoot，一步步地学习JavaWeb基础知识，并上手进行实战，接着了解JavaWeb项目中经常要使用的技术和组件，包括日志组件、Maven、Junit，等等内容，以便让你更完整地了解整个JavaWeb技术体系，形成自己的知识框架。为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n**文末赠送8000G的Java架构师学习资料，需要的朋友可以到文末了解领取方式，资料包括Java基础、进阶、项目和架构师等免费学习资料，更有数据库、分布式、微服务等热门技术学习视频，内容丰富，兼顾原理和实践，另外也将赠送作者原创的Java学习指南、Java程序员面试指南等干货资源）**\n<!-- more -->\n\n\n## 简介\n\n测试在软件开发中是一个很重要的方面，良好的测试可以在很大程度决定一个应用的命运。\n软件测试中，主要有3大种类：\n\n*   [单元测试](https://en.wikipedia.org/wiki/Unit_testing)\n    单元测试主要是用于测试程序模块，确保代码运行正确。单元测试是由开发者编写并进行运行测试。一般使用的测试框架是[JUnit](http://junit.org/junit4/)或者[TestNG](https://github.com/cbeust/testng)。测试用例一般是针对_方法_级别的测试。\n*   [集成测试](https://en.wikipedia.org/wiki/Integration_testing)\n    集成测试用于检测系统是否能正常工作。集成测试也是由开发者共同进行测试，与单元测试专注测试个人代码组件不同的是，集成测试是系统进行跨组件测试。\n*   [功能性测试](https://en.wikipedia.org/wiki/Functional_testing)\n    功能性测试是一种质量保证过程以及基于测试软件组件的规范下的由输入得到输出的一种黑盒测试。功能性测试通常由不同的测试团队进行测试，测试用例的编写要遵循组件规范，然后根据测试输入得到的实际输出与期望值进行对比，判断功能是否正确运行。\n\n## 概述\n\n本文只对[单元测试](https://en.wikipedia.org/wiki/Unit_testing)进行介绍，主要介绍如何在[Android Studio](https://developer.android.com/studio/index.html?gclid=Cj0KCQjwgIPOBRDnARIsAHA1X3SC5vOHyIHQnIIfJ8hqJSuTiCG6p3u2ff_ti3EIVeCIGJLnP82YCKoaArSPEALw_wcB)下进行单元测试，单元测试使用的测试框架为[JUnit](http://junit.org/junit4/)\n\n## 好处\n\n可能目前仍有很大一部分开发者未使用[单元测试](https://en.wikipedia.org/wiki/Unit_testing)对他们的代码进行测试，一方面可能是觉得没有必要，因为即使没有进行单元测试，程序照样运行得很好；另一方面，也许有些人也认同单元测试的好处，但是由于需要额外的学习成本，所以很多人也是没有时间或者说是没有耐心进行学习······\n这里我想说的是，如果大家去看下[github](https://www.jianshu.com/p/www.github.com)上目前主流的开源框架，star 数比较多的项目，一般都有很详尽的测试用例。所以说，单元测试对于我们的项目开发，还是挺有好处的。\n至于单元测试的好处，我这里提及几点：\n\n*   保证代码运行与我们预想的一样，代码正确性可以得到保证\n*   程序运行出错时，有利于我们对错误进行查找（因为我们忽略我们测试通过的代码）\n*   有利于提升代码架构设计（用于测试的用例应力求简单低耦合，因此编写代码的时候，开发者往往会为了对代码进行测试，将其他耦合的部分进行解耦处理）\n    \n\n## Junit单元测试\n\n本文实例讲述了java单元测试JUnit框架原理与用法。分享给大家供大家参考，具体如下：\n\n### 1 简介\n\nJUnit是一个Java语言的单元测试框架，它由 Kent Beck 和 Erich Gamma 建立，逐渐成为 xUnit 家族中最为成功的一个。\n\nJUnit有它自己的JUnit扩展生态圈，多数Java的开发环境都已经集成了JUnit作为单元测试的工具。在这里，一个单元可以是一个方法、类、包或者子系统。\n\n因此，单元测试是指对代码中的最小可测试单元进行检查和验证，以便确保它们正常工作。例如，我们可以给予一定的输入测试输出是否是所希望得到的结果。在本篇博客中，作者将着重介绍 JUnit 4.X 版本的特性，这也是我们在日常开发中使用最多的版本。\n\n### 2 特点\n\n    JUnit提供了注释以及确定的测试方法；\n    JUnit提供了断言用于测试预期的结果；\n    JUnit测试优雅简洁不需要花费太多的时间；\n    JUnit测试让大家可以更快地编写代码并且提高质量；\n    JUnit测试可以组织成测试套件包含测试案例，甚至其他测试套件；\n    Junit显示测试进度，如果测试是没有问题条形是绿色的，测试失败则会变成红色；\n    JUnit测试可以自动运行，检查自己的结果，并提供即时反馈，没有必要通过测试结果报告来手动梳理。\n\n### 3 内容\n\n#### 3.1 注解\n\n@Test ：该注释表示，用其附着的公共无效方法（即用public修饰的void类型的方法 ）可以作为一个测试用例；\n\n@Before ：该注释表示，用其附着的方法必须在类中的每个测试之前执行，以便执行测试某些必要的先决条件；\n\n@BeforeClass ：该注释表示，用其附着的静态方法必须执行一次并在类的所有测试之前，发生这种情况时一般是测试计算共享配置方法，如连接到数据库；\n\n@After ：该注释表示，用其附着的方法在执行每项测试后执行，如执行每一个测试后重置某些变量，删除临时变量等；\n\n@AfterClass ：该注释表示，当需要执行所有的测试在JUnit测试用例类后执行，AfterClass注解可以使用以清理建立方法，如断开数据库连接，注意：附有此批注（类似于BeforeClass）的方法必须定义为静态；\n\n@Ignore ：该注释表示，当想暂时禁用特定的测试执行可以使用忽略注释，每个被注解为@Ignore的方法将不被执行。\n\n\n````\n/\n* JUnit 注解示例\n*/\n@Test\npublic void testYeepay(){\n  Syetem.out.println(\"用@Test标示测试方法！\");\n}\n@AfterClass\npublic static void paylus(){\n  Syetem.out.println(\"用@AfterClass标示的方法在测试用例类执行完之后！\");\n}\n\n````\n\n\n\n\n\n#### 3.2 断言\n\n在这里，作者将介绍一些断言方法，所有这些方法都来自 org.junit.Assert 类，其扩展了 java.lang.Object 类并为它们提供编写测试，以便检测故障。简而言之，我们就是通过断言方法来判断实际结果与我们预期的结果是否相同，如果相同，则测试成功，反之，则测试失败。\n````\nvoid assertEquals([String message], expected value, actual value)：断言两个值相等，值的类型可以为int、short、long、byte、char 或者\njava.lang.Object，其中第一个参数是一个可选的字符串消息；\nvoid assertTrue([String message], boolean condition)：断言一个条件为真；\nvoid assertFalse([String message],boolean condition)：断言一个条件为假；\nvoid assertNotNull([String message], java.lang.Object object)：断言一个对象不为空(null)；\nvoid assertNull([String message], java.lang.Object object)：断言一个对象为空(null)；\nvoid assertSame([String message], java.lang.Object expected, java.lang.Object actual)：断言两个对象引用相同的对象；\nvoid assertNotSame([String message], java.lang.Object unexpected, java.lang.Object actual)：断言两个对象不是引用同一个对象；\nvoid assertArrayEquals([String message], expectedArray, resultArray)：断言预期数组和结果数组相等，数组的类型可以为int、long、short、char、byte 或者 java.lang.Object\n````\n### 4 JUnit 3.X 和 JUnit 4.X 的区别\n\n#### 4.1 JUnit 3.X\n\n（1）使用 JUnit 3.X 版本进行单元测试时，测试类必须要继承于 TestCase 父类；\n（2）测试方法需要遵循的原则：\n\n① public的；\n② void的；\n③ 无方法参数；\n④方法名称必须以 test 开头；\n\n（3）不同的测试用例之间一定要保持完全的独立性，不能有任何的关联；\n\n（4）要掌握好测试方法的顺序，不能依赖于测试方法自己的执行顺序。\n\n````\n/\n* 用 JUnit 3.X 进行测试\n*/\nimport junit.framework.Assert;\nimport junit.framework.TestCase;\npublic class TestOperation extends TestCase {\n  private Operation operation;\n  public TestOperation(String name) { // 构造函数\n    super(name);\n  }\n  @Override\n  public void setUp() throws Exception { // 在每个测试方法执行 [之前] 都会被调用，多用于初始化\n    System.out.println(\"欢迎使用Junit进行单元测试...\");\n    operation = new Operation();\n  }\n  @Override\n  public void tearDown() throws Exception { // 在每个测试方法执行 [之后] 都会被调用，多用于释放资源\n    System.out.println(\"Junit单元测试结束...\");\n  }\n  public void testDivideByZero() {\n    Throwable te = null;\n    try {\n      operation.divide(6, 0);\n      Assert.fail(\"测试失败\"); //断言失败\n    } catch (Exception e) {\n      e.printStackTrace();\n      te = e;\n    }\n    Assert.assertEquals(Exception.class, te.getClass());\n    Assert.assertEquals(\"除数不能为 0 \", te.getMessage());\n  }\n}\n````\n\n#### 4.2 JUnit 4.X\n\n（1）使用 JUnit 4.X 版本进行单元测试时，不用测试类继承TestCase父类；\n（2）JUnit 4.X 版本，引用了注解的方式进行单元测试；\n（3）JUnit 4.X 版本我们常用的注解包括：\n\n@Before注解：与JUnit 3.X 中的 setUp() 方法功能一样，在每个测试方法之前执行，多用于初始化；\n\n@After注解：与 JUnit 3.X 中的 tearDown() 方法功能一样，在每个测试方法之后执行，多用于释放资源；\n\n@Test(timeout = xxx)注解：设置当前测试方法在一定时间内运行完，否则返回错误；\n\n@Test(expected = Exception.class)注解：设置被测试的方法是否有异常抛出。抛出异常类型为：Exception.class；\n\n此外，我们可以通过阅读上面的第二部分“2 注解”了解更多的注解。\n\n````\n/\n* 用 JUnit 4.X 进行测试\n*/\nimport static org.junit.Assert.*;\nimport org.junit.After;\nimport org.junit.AfterClass;\nimport org.junit.Before;\nimport org.junit.BeforeClass;\nimport org.junit.Test;\npublic class TestOperation {\n  private Operation operation;\n  @BeforeClass\n  public static void globalInit() { // 在所有方法执行之前执行\n    System.out.println(\"@BeforeClass标注的方法，在所有方法执行之前执行...\");\n  }\n  @AfterClass\n  public static void globalDestory() { // 在所有方法执行之后执行\n    System.out.println(\"@AfterClass标注的方法，在所有方法执行之后执行...\");\n  }\n  @Before\n  public void setUp() { // 在每个测试方法之前执行\n    System.out.println(\"@Before标注的方法，在每个测试方法之前执行...\");\n    operation = new Operation();\n  }\n  @After\n  public void tearDown() { // 在每个测试方法之后执行\n    System.out.println(\"@After标注的方法，在每个测试方法之后执行...\");\n  }\n  @Test(timeout=600)\n  public void testAdd() { // 设置限定测试方法的运行时间 如果超出则返回错误\n    System.out.println(\"测试 add 方法...\");\n    int result = operation.add(2, 3);\n    assertEquals(5, result);\n  }\n  @Test\n  public void testSubtract() {\n    System.out.println(\"测试 subtract 方法...\");\n    int result = operation.subtract(1, 2);\n    assertEquals(-1, result);\n  }\n  @Test\n  public void testMultiply() {\n    System.out.println(\"测试 multiply 方法...\");\n    int result = operation.multiply(2, 3);\n    assertEquals(6, result);\n  }\n  @Test\n  public void testDivide() {\n    System.out.println(\"测试 divide 方法...\");\n    int result = 0;\n    try {\n      result = operation.divide(6, 2);\n    } catch (Exception e) {\n      fail();\n    }\n    assertEquals(3, result);\n  }\n  @Test(expected = Exception.class)\n  public void testDivideAgain() throws Exception {\n    System.out.println(\"测试 divide 方法，除数为 0 的情况...\");\n    operation.divide(6, 0);\n    fail(\"test Error\");\n  }\n  public static void main(String[] args) {\n  }\n}\n````\n#### 4.3 特别提醒\n\n通过以上两个例子，我们已经可以大致知道 JUnit 3.X 和 JUnit 4.X 两个版本的区别啦！\n\n首先，如果我们使用 JUnit 3.X，那么在我们写的测试类的时候，一定要继承 TestCase 类，但是如果我们使用 JUnit 4.X，则不需继承 TestCase 类，直接使用注解就可以啦！\n\n在 JUnit 3.X 中，还强制要求测试方法的命名为“ testXxxx ”这种格式；\n\n在 JUnit 4.X 中，则不要求测试方法的命名格式，但作者还是建议测试方法统一命名为“ testXxxx ”这种格式，简洁明了。\n\n此外，在上面的两个示例中，我们只给出了测试类，但是在这之前，还应该有一个被测试类，也就是我们真正要实现功能的类。现在，作者将给出上面示例中被测试的类，即 Operation 类：\n\n````\n/\n* 定义了加减乘除的法则\n*/\npublic class Operation {\n  public static void main(String[] args) {\n    System.out.println(\"a + b = \" + add(1,2));\n    System.out.println(\"a - b = \" + subtract(1,2));\n    System.out.println(\"a * b = \" + multiply(1,2));\n    System.out.println(\"a / b = \" + divide(4,2));\n    System.out.println(\"a / b = \" + divide(1,0));\n  }\n  public static int add(int a, int b) {\n    return a + b;\n  }\n  public static int subtract(int a, int b) {\n    return a - b;\n  }\n  public static int multiply(int a, int b) {\n    return a * b;\n  }\n  public static int divide(int a, int b) {\n    return a / b;\n  }\n}\n````\n\n\n### 5 测试示例\n\n#### 5.1 示例一：简单的 JUnit 3.X 测试\n\n````\nimport junit.framework.Test;\nimport junit.framework.TestCase;\nimport junit.framework.TestSuite;\nimport java.util.ArrayList;\nimport java.util.Collection;\n/\n * 1、创建一个测试类，继承TestCase类\n */\npublic class SimpleTestDemo extends TestCase {\n  public SimpleTestDemo(String name) {\n    super(name);\n  }\n  /\n   * 2、写一个测试方法，断言期望的结果\n   */\n  public void testEmptyCollection(){\n    Collection collection = new ArrayList();\n    assertTrue(collection.isEmpty());\n  }\n  /\n   * 3、写一个suite()方法，它会使用反射动态的创建一个包含所有的testXxxx方法的测试套件\n   */\n  public static Test suit(){\n    return new TestSuite(SimpleTestDemo.class);\n  }\n  /\n   * 4、写一个main()方法，以文本运行器的方式方便的运行测试\n   */\n  public static void main(String[] args) {\n    junit.textui.TestRunner.run(suit());\n  }\n}\n````\n\n\n### 6 个人建议\n\n有些童鞋可能会有一些误解，认为写测试代码没有用，而且还会增大自己的压力，浪费时间。但事实上，写测试代码与否，还是有很大区别的，如果是在小的项目中，或许这种区别还不太明显，但如果在大型项目中，一旦出现错误或异常，用人力去排查的话，那将会浪费很多时间，而且还不一定排查的出来，但是如果用测试代码的话，JUnit 就是自动帮我们判断一些代码的结果正确与否，从而节省的时间将会远远超过你写测试代码的时间。\n\n因此，个人建议：要养成编写测试代码的习惯，码一点、测一点；再码一点，再测一点，如此循环。在我们不断编写与测试代码的过程中，我们将会对类的行为有一个更为深入的了解，从而可以有效的提高我们的工作效率。下面，作者就给出一些具体的编写测试代码的技巧和较好的实践方法：\n\n1\\. 不要用 TestCase 的构造函数初始化 Fixture，而要用 setUp() 和 tearDown() 方法；\n2\\. 不要依赖或假定测试运行的顺序，因为 JUnit 会利用 Vector 保存测试方法，所以不同的平台会按不同的顺序从 Vector 中取出测试方法；\n3\\. 避免编写有副作用的 TestCase，例如：如果随后的测试依赖于某些特定的交易数据，就不要提交交易数据，只需要简单的回滚就可以了；\n4\\. 当继承一个测试类时，记得调用父类的 setUp() 和 tearDown() 方法；\n5\\. 将测试代码和工作代码放在一起，同步编译和更新；\n6\\. 测试类和测试方法应该有一致的命名方案，如在工作类名前加上 test 从而形成测试类名；\n7\\. 确保测试与时间无关，不要使用过期的数据进行测试，以至于导致在随后的维护过程中很难重现测试；\n8\\. 如果编写的软件面向国际市场，那么编写测试时一定要考虑国际化的因素；\n9\\. 尽可能地利用 JUnit 提供地 assert 和 fail 方法以及异常处理的方法，其可以使代码更为简洁；\n10\\. 测试要尽可能地小，执行速度快；\n11\\. 不要硬性规定数据文件的路径；\n12\\. 使用文档生成器做测试文档。\n\n## 8 大单元测试框架\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/4310fd169d4d464db44fa73f25a241c3.jpg)\n\n1.Arquillian\n\nArquillian是一个基于JVM的高度可扩展的测试平台，允许开发人员创建Java的自动化集成，功能和验收测试。Arquillian允许你在运行态时执行测试。Arquillian可用于管理容器（或容器）的生命周期，绑定测试用例，依赖类和资源。它还能够将压缩包部署到容器中，并在容器中执行测试并捕获结果并创建报告。\n\nArquillian集成了熟悉的测试框架，如JUnit 4、TestNG 5，并允许使用现有的IDE启动测试。并且由于其模块化设计，它能够运行Ant和Maven测试插件。Arquillian目的是简化项目集成测试和功能测试的编写，让它们能像单元测试一样简单。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/8c78fe84d7fa46b9b6dd9fc8ab001e00.jpg)\n\n2.JTEST\n\nJTest也被称为“Parasoft JTest”，是Parasoft公司生产的自动化Java软件测试和静态分析软件。 JTest包括用于单元测试用例生成和执行，静态代码分析，数据流静态分析和度量分析，回归测试，运行时错误检测的功能。\n\n还可以进行结对的代码审查流程自动化和运行时错误检测，例如：条件，异常，资源和内存泄漏，安全攻击漏洞等。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/582197f6e97549bd894380f2b7320cc9.jpg)\n\n3.The Grinder\n\n“The Grinder”是一个Java负载测试框架。并且通过使用大量负载注射器来为分布式测试提供便利。Grinder可以对具有Java API的任何内容加载测试。这包括HTTP Web服务器，SOAP、REST Web服务、应用程序服务器，包括自定义协议。测试脚本用强大的Jython和Clojure语言编写。Grinder的GUI控制台允许对多个负载注射器进行监控和控制，并自动管理客户端连接和Cookie，SSL，代理感知和连接限制。您可以在这里找到关于磨床功能的更多深入信息。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/2d5c47277d254ad783c08b5f03a42372_th.jpg)\n\n4.TestNG\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/0e1f6408b31d4665b4cc75b47750d58d.jpg)\n\nTestNG受JUnit和NUnit的启发，是为Java编程语言而设计的测试框架。TestNG主要设计用于覆盖更广泛的测试类别，如单元，功能，端到端，集成等。它还引入了一些新功能，使其更强大，更易于使用，如：注解，运行在大线程池中进行各种策略测试，多线程安全验证代码测试，灵活的测试配置，数据驱动的参数测试支持等等。\n\nTestNG有各种工具和插件（如Eclipse，IDEA，Maven等）支持。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/21bc4cc5fd924b16b29be4880f9cfeab_th.jpg)\n\n5.JUnit\n\nJUnit是为Java编程语言设计的单元测试框架。JUnit在测试驱动开发框架的开发中发挥了重要作用。它是单元测试框架之一，统称为由SUnit起源的xUnit。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/9a6d602756f94c1ea8682fc811d679ef.jpg)\n\n6.JWalk\n\nJWalk被设计为用于Java编程语言的单元测试工具包。它被设计为支持称为“Lazy系统单元测试”的测试范例。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/5d6b9c4de39c43df9eabc2fc5eecca5e_th.jpg)\n\nJWalkTester工具对任何由程序员提供的编译的Java类执行任何测试。它能够通过静态和动态分析以及来自程序员的提示来测试懒惰Lazy规范的一致性。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/34c2bae3fea44fd9a2ec54c7447207d5.jpg)\n\n7.Mockito\n\nMockito被设计为用于Java的开源测试框架，MIT许可证。Mockito允许程序员为了测试驱动开发（TDD）或行为驱动开发（BDD）而在自动化单元测试中创建和测试双对象（Mock对象）。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/cdef7a64440c499a865249fcbc91e38e.jpg)\n\n8 Powermock\n\nPowerMock是用于对源代码进行单元测试的Java框架，它可以作为其他模拟框架的扩展，比如原型Mockito或EasyMock，但具有更强大的功能。PowerMock利用自定义的类加载器和字节码操纵器来实现静态方法，构造函数，最终类和方法以及私有方法等的模拟。它主要是为了扩展现有的API，使用少量的方法和注解来实现额外的功能。\n\n\n"
  },
  {
    "path": "docs/JavaWeb/走进JavaWeb技术世界：极简配置的SpringBoot.md",
    "content": "# 目录\n* [Spring Boot 概述](#spring-boot-概述)\n    * [什么是 Spring Boot](#什么是-spring-boot)\n    * [使用 Spring Boot 有什么好处](#使用-spring-boot-有什么好处)\n* [Spring Boot 快速搭建](#spring-boot-快速搭建)\n    * [第一步：新建项目](#第一步：新建项目)\n    * [第二步：HelloController](#第二步：hellocontroller)\n    * [第三步：利用 IDEA 启动 Spring Boot](#第三步：利用-idea-启动-spring-boot)\n* [解析 Spring Boot 项目](#解析-spring-boot-项目)\n    * [解析 pom.xml 文件](#解析-pomxml-文件)\n    * [应用入口类](#应用入口类)\n    * [Spring Boot 的配置文件](#spring-boot-的配置文件)\n    * [Spring Boot 热部署](#spring-boot-热部署)\n* [Spring Boot 使用](#spring-boot-使用)\n    * [Spring Boot 支持 JSP](#spring-boot-支持-jsp)\n    * [集成 MyBatis](#集成-mybatis)\n* [springMVC和springboot的区别](#springmvc和springboot的区别)\n\n\n本文转载自互联网，侵删  \n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本系列文章将同步到我的个人博客：\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n本文是《走进JavaWeb技术世界》系列博文的其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，从servlet到框架，从ssm再到SpringBoot，一步步地学习JavaWeb基础知识，并上手进行实战，接着了解JavaWeb项目中经常要使用的技术和组件，包括日志组件、Maven、Junit，等等内容，以便让你更完整地了解整个JavaWeb技术体系，形成自己的知识框架。为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n**文末赠送8000G的Java架构师学习资料，需要的朋友可以到文末了解领取方式，资料包括Java基础、进阶、项目和架构师等免费学习资料，更有数据库、分布式、微服务等热门技术学习视频，内容丰富，兼顾原理和实践，另外也将赠送作者原创的Java学习指南、Java程序员面试指南等干货资源）**\n<!-- more -->  \n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-9323041dd0ce883e.png)\n\n## Spring Boot 概述\n\n> **Build Anything with Spring Boot：**Spring Boot is the starting point for building all Spring-based applications. Spring Boot is designed to get you up and running as quickly as possible, with minimal upfront configuration of Spring.\n\n上面是引自官网的一段话，大概是说： Spring Boot 是所有基于 Spring 开发的项目的起点。Spring Boot 的设计是为了让你尽可能快的跑起来 Spring 应用程序并且尽可能减少你的配置文件。\n\n#### 什么是 Spring Boot\n\n*   它使用 “习惯优于配置” （项目中存在大量的配置，此外还内置一个习惯性的配置，让你无须）的理念让你的项目快速运行起来。\n*   它并不是什么新的框架，而是默认配置了很多框架的使用方式，就像 Maven 整合了所有的 jar 包一样，Spring Boot 整合了所有框架（引自：[springboot(一)：入门篇——纯洁的微笑](http://www.ityouknow.com/springboot/2016/01/06/springboot(%E4%B8%80)-%E5%85%A5%E9%97%A8%E7%AF%87.html)）\n\n#### 使用 Spring Boot 有什么好处\n\n回顾我们之前的 SSM 项目，搭建过程还是比较繁琐的，需要：\n\n*   1）配置 web.xml，加载 spring 和 spring mvc\n*   2）配置数据库连接、配置日志文件\n*   3）配置家在配置文件的读取，开启注解\n*   4）配置mapper文件\n*   **.....**\n\n而使用 Spring Boot 来开发项目则只需要非常少的几个配置就可以搭建起来一个 Web 项目，并且利用 IDEA 可以自动生成生成，这简直是太爽了...\n\n*   划重点：简单、快速、方便地搭建项目；对主流开发框架的无配置集成；极大提高了开发、部署效率。\n\n* * *  \n\n## Spring Boot 快速搭建\n\n#### 第一步：新建项目\n\n选择 Spring Initializr ，然后选择默认的 url 点击【Next】：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-3e2c9c5742c10c86.png)\n\n然后修改一下项目的信息：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-28dbe478ff25a3a0.png)\n\n勾选上 Web 模板：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-532868b7e6760e03.png)\n\n选择好项目的位置，点击【Finish】：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-931cc2fb5c8964e9.png)\n\n如果是第一次配置 Spring Boot 的话可能需要等待一会儿 IDEA 下载相应的 依赖包，默认创建好的项目结构如下：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-9ac7acc56d5a32f0.png)\n\n项目结构还是看上去挺清爽的，少了很多配置文件，我们来了解一下默认生成的有什么：\n\n*   SpringbootApplication： 一个带有 main() 方法的类，用于启动应用程序\n*   SpringbootApplicationTests：一个空的 Junit 测试了，它加载了一个使用 Spring Boot 字典配置功能的 Spring 应用程序上下文\n*   application.properties：一个空的 properties 文件，可以根据需要添加配置属性\n*   pom.xml： Maven 构建说明文件\n\n#### 第二步：HelloController\n\n在【cn.wmyskxz.springboot】包下新建一个【HelloController】：\n\n```  \npackage cn.wmyskxz.springboot;  \n  \nimport org.springframework.web.bind.annotation.RequestMapping;  \nimport org.springframework.web.bind.annotation.RestController;  \n  \n/**  \n * 测试控制器  \n * * @author: @我没有三颗心脏  \n * @create: 2018-05-08-下午 16:46 */@RestController  \npublic class HelloController {  \n  \n    @RequestMapping(\"/hello\")    public String hello() {        return \"Hello Spring Boot!\";    }}  \n```  \n\n*   **@RestController 注解：**该注解是 @Controller 和 @ResponseBody 注解的合体版\n\n#### 第三步：利用 IDEA 启动 Spring Boot\n\n我们回到 SpringbootApplication 这个类中，然后右键点击运行：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-bf1aa6ed5c0db7b4.png)\n\n*   **注意**：我们之所以在上面的项目中没有手动的去配置 Tomcat 服务器，是因为 Spring Boot 内置了 Tomcat\n\n等待一会儿就会看到下方的成功运行的提示信息：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-63e43dc6a277de3e.png)\n\n可以看到我们的 Tomcat 运行在 8080 端口，我们来访问 “`/hello`” 地址试一下：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-6111e1913c5bf6d6.png)\n\n可以看到页面成功显示出我们返回的信息。\n\n* * *  \n\n## 解析 Spring Boot 项目\n\n> 这一部分参考自：[Spring Boot干货系列（一）优雅的入门篇 ——嘟嘟独立博客](http://tengj.top/2017/02/26/springboot1/)\n\n#### 解析 pom.xml 文件\n\n让我们来看看默认生成的 pom.xml 文件中到底有一些什么特别：\n\n```  \n<?xml version=\"1.0\" encoding=\"UTF-8\"?>  \n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"  \n    xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">    <modelVersion>4.0.0</modelVersion>  \n    <groupId>cn.wmyskxz</groupId>    springboot    <version>0.0.1-SNAPSHOT</version>    <packaging>jar</packaging>  \n    <name>springboot</name>    <description>Demo project for Spring Boot</description>  \n    <parent>        <groupId>org.springframework.boot</groupId>        spring-boot-starter-parent        <version>2.0.1.RELEASE</version>        <relativePath/> <!-- lookup parent from repository -->    </parent>  \n    <properties>        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>        <java.version>1.8</java.version>    </properties>  \n    <dependencies>        <dependency>            <groupId>org.springframework.boot</groupId>            spring-boot-starter-web        </dependency>  \n        <dependency>            <groupId>org.springframework.boot</groupId>            spring-boot-starter-test            <scope>test</scope>        </dependency>    </dependencies>  \n    <build>        <plugins>            <plugin>                <groupId>org.springframework.boot</groupId>                spring-boot-maven-plugin            </plugin>        </plugins>    </build></project>  \n```  \n\n我们可以看到一个比较陌生一些的标签`<parent>`，这个标签是在配置 Spring Boot 的父级依赖：\n\n```  \n<parent>  \n    <groupId>org.springframework.boot</groupId>    spring-boot-starter-parent    <version>2.0.1.RELEASE</version>    <relativePath/> <!-- lookup parent from repository --></parent>  \n```  \n\n有了这个，当前的项目才是 Spring Boot 项目，spring-boot-starter-parent 是一个特殊的 starter ，它用来提供相关的 Maven 默认依赖，**使用它之后，常用的包依赖就可以省去 version 标签。**\n\n关于具体 Spring Boot 提供了哪些 jar 包的依赖，我们可以查看本地 Maven 仓库下：\\repository\\org\\springframework\\boot\\spring-boot-dependencies\\2.0.1.RELEASE\\spring-boot-dependencies-2.0.1.RELEASE.pom 文件来查看，挺长的...\n\n#### 应用入口类\n\nSpring Boot 项目通常有一个名为 *Application 的入口类，入口类里有一个 main 方法，**这个 main 方法其实就是一个标准的 Javay 应用的入口方法。**\n\n**@SpringBootApplication**是 Spring Boot 的核心注解，它是一个组合注解，该注解组合了：**@Configuration、@EnableAutoConfiguration、@ComponentScan；**若不是用 @SpringBootApplication 注解也可以使用这三个注解代替。\n\n*   其中，**@EnableAutoConfiguration 让 Spring Boot 根据类路径中的 jar 包依赖为当前项目进行自动配置**，例如，添加了 spring-boot-starter-web 依赖，会自动添加 Tomcat 和 Spring MVC 的依赖，那么 Spring Boot 会对 Tomcat 和 Spring MVC 进行自动配置。\n*   **Spring Boot 还会自动扫描 @SpringBootApplication 所在类的同级包以及下级包里的 Bean**，所以入口类建议就配置在 grounpID + arctifactID 组合的包名下（这里为 cn.wmyskxz.springboot 包）\n\n#### Spring Boot 的配置文件\n\nSpring Boot 使用一个全局的配置文件 application.properties 或 application.yml，放置在【src/main/resources】目录或者类路径的 /config 下。\n\nSpring Boot 不仅支持常规的 properties 配置文件，还支持 yaml 语言的配置文件。yaml 是以数据为中心的语言，在配置数据的时候具有面向对象的特征。\n\nSpring Boot 的全局配置文件的作用是对一些默认配置的配置值进行修改。\n\n> *   简单实例一下\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-bcd65f7469b06608.png)\n\n我们同样的将 Tomcat 默认端口设置为 8080 ，并将默认的访问路径从 “`/`” 修改为 “`/hello`” 时，使用 properties 文件和 yml 文件的区别如上图。\n\n*   注意： yml 需要在 “`:`” 后加一个空格，幸好 IDEA 很好地支持了 yml 文件的格式有良好的代码提示；\n\n> *   我们可以自己配置多个属性\n\n我们直接把 .properties 后缀的文件删掉，使用 .yml 文件来进行简单的配置，然后使用 @Value 来获取配置属性：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-0e808a82254d6a4b.png)\n\n重启 Spring Boot ，输入地址：localhost:8080/hello 能看到正确的结果：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-c85216e8ea7910f0.png)\n\n*   **注意：**我们并没有在 yml 文件中注明属性的类型，而是在使用的时候定义的。\n\n你也可以在配置文件中使用当前配置：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-37e91abbc4550982.png)\n\n仍然可以得到正确的结果：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-eabc3cd39b44fd0d.png)\n\n*   **问题：**这样写配置文件繁琐而且可能会造成类的臃肿，因为有许许多多的 @Value 注解。\n\n> *   封装配置信息\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-2599817d8f2f50d2.png)\n\n我们可以把配置信息封装成一个类，首先在我们的 name 和 age 前加一个 student 前缀，然后新建一个 StudentProperties 的类用来封装这些信息，并用上两个注解：\n\n*   @Component：表明当前类是一个 Java Bean\n*   @ConfigurationProperties(prefix = \"student\")：表示获取前缀为 sutdent 的配置信息\n\n这样我们就可以在控制器中使用，重启得到正确信息：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-84dc1215d01f3fa9.png)\n\n#### Spring Boot 热部署\n\n在目前的 Spring Boot 项目中，当发生了任何修改之后我们都需要重新启动才能够正确的得到效果，这样会略显麻烦，Spring Boot 提供了热部署的方式，当发现任何类发生了改变，就会通过 JVM 类加载的方式，加载最新的类到虚拟机中，这样就不需要重新启动也能看到修改后的效果了。\n\n> *   做法也很简单，修改 pom.xml 即可！\n\n我们往 pom.xml 中添加一个依赖就可以了：\n\n```  \n<dependency>  \n    <groupId>org.springframework.boot</groupId>    spring-boot-devtools    <optional>true</optional> <!-- 这个需要为 true 热部署才有效 --></dependency>  \n```  \n\n重新启动 Spring Boot ，然后修改任意代码，就能观察到控制台的自动重启现象：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-cec869956c3cf158.png)\n\n> 关于如何在 IDEA 中配置热部署：[传送门](https://blog.csdn.net/xusheng_Mr/article/details/78771746)\n\n* * *  \n\n## Spring Boot 使用\n\n上面已经完成了 Spring Boot 项目的简单搭建，我们仅仅需要进行一些简单的设置，写一个 HelloController 就能够直接运行了，不要太简单...接下来我们再深入了解一下 Spring Boot 的使用。\n\n#### Spring Boot 支持 JSP\n\nSpring Boot 的默认视图支持是 Thymeleaf 模板引擎，但是这个我们不熟悉啊，我们还是想要使用 JSP 怎么办呢？\n\n> *   第一步：修改 pom.xml 增加对 JSP 文件的支持\n\n```  \n<!-- servlet依赖. -->  \n<dependency>  \n    <groupId>javax.servlet</groupId>    javax.servlet-api    <scope>provided</scope></dependency>  \n<dependency>  \n    <groupId>javax.servlet</groupId>    jstl</dependency>  \n  \n<!-- tomcat的支持.-->  \n<dependency>  \n    <groupId>org.apache.tomcat.embed</groupId>    tomcat-embed-jasper    <scope>provided</scope></dependency>  \n```  \n\n> *   第二步：配置试图重定向 JSP 文件的位置\n\n修改 application.yml 文件，将我们的 JSP 文件重定向到 /WEB-INF/views/ 目录下：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-7c17f7e10cfb2629.png)\n\n> *   第三步：修改 HelloController\n\n修改 @RestController 注解为 @Controller ，然后将 hello 方法修改为：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-2dc2c39cd962edc1.png)\n\n> *   第四步：新建 hello.jsp 文件\n\n在【src/main】目录下依次创建 webapp、WEB-INF、views 目录，并创建一个 hello.jsp 文件：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-a180556d7ead9605.png)\n\n> *   第五步：刷新网页\n\n因为我们部署了热部署功能，所以只需要等待控制台重启信息完成之后再刷新网页就可以看到正确效果了：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-cfd20f747ffca978.png)\n\n*   关于 404，使用 spring-boot:run 运行项目可以解决：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-27c1bf46487ba5eb.png)\n\n#### 集成 MyBatis\n\n> *   第一步：修改 pom.xml 增加对 MySql和 MyBatis 的支持\n\n```  \n  \n<!-- mybatis -->  \n<dependency>  \n    <groupId>org.mybatis.spring.boot</groupId>    mybatis-spring-boot-starter    <version>1.1.1</version></dependency>  \n<!-- mysql -->  \n<dependency>  \n    <groupId>mysql</groupId>    mysql-connector-java    <version>5.1.21</version></dependency>  \n```  \n\n> *   第二步：新增数据库链接参数\n\n这里我们就直接使用之前创建好的 student 表了吧：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-1eda563cfdfbae65.png)\n\n> *   第三步：创建 Student 实体类和 StudentMapper 映射类\n\n在【cn.wmyskxz.springboot】下新建一个【pojo】包，然后在其下创建一个 Student 类：\n\n```  \npublic class Student {  \n  \n    private Integer id;    private Integer student_id;    private String name;    private Integer age;    private String sex;    private Date birthday;  \n    /* getter and setter */}  \n```  \n\n在【cn.wmyskxz.springboot】下新建一个【mapper】包，然后在其下创建一个 StudentMapper 映射类：\n\n```  \npackage cn.wmyskxz.springboot.mapper;  \n  \nimport cn.wmyskxz.springboot.pojo.Student;  \nimport org.apache.ibatis.annotations.Mapper;  \nimport org.apache.ibatis.annotations.Select;  \n  \nimport java.util.List;  \n  \n@Mapper  \npublic interface StudentMapper {  \n  \n    @Select(\"SELECT * FROM student\")    List<Student> findAll();}  \n```  \n\n> *   第四步：编写 StudentController\n\n在【cn.wmyskxz.springboot】下新建一个【controller】包，然后在其下创建一个 StudentController ：\n\n```  \npackage cn.wmyskxz.springboot.controller;  \n  \nimport cn.wmyskxz.springboot.mapper.StudentMapper;  \nimport cn.wmyskxz.springboot.pojo.Student;  \nimport org.springframework.beans.factory.annotation.Autowired;  \nimport org.springframework.stereotype.Controller;  \nimport org.springframework.ui.Model;  \nimport org.springframework.web.bind.annotation.RequestMapping;  \n  \nimport java.util.List;  \n  \n/**  \n * Student 控制器  \n * * @author: @我没有三颗心脏  \n * @create: 2018-05-08-下午 20:25 */@Controller  \npublic class StudentController {  \n  \n    @Autowired    StudentMapper studentMapper;  \n    @RequestMapping(\"/listStudent\")    public String listStudent(Model model) {        List<Student> students = studentMapper.findAll();        model.addAttribute(\"students\", students);        return \"listStudent\";    }}  \n```  \n\n> 第五步：编写 listStudent.jsp 文件\n\n我们简化一下 JSP 的文件，仅显示两个字段的数据：\n\n```  \n<%@ page language=\"java\" contentType=\"text/html; charset=UTF-8\"  \n         pageEncoding=\"UTF-8\"%>  \n<%@ taglib uri=\"http://java.sun.com/jsp/jstl/core\" prefix=\"c\"%>  \n  \n<table align='center' border='1' cellspacing='0'>  \n    <tr>        <td>id</td>        <td>name</td>    </tr>    <c:forEach items=\"${students}\" var=\"s\" varStatus=\"st\">        <tr>            <td>${s.id}</td>            <td>${s.name}</td>        </tr>    </c:forEach></table>  \n```  \n\n> *   第六步：重启服务器运行\n\n因为往 pom.xml 中新增加了依赖的包，所以自动重启服务器没有作用，我们需要手动重启一次，然后在地址输入：localhost:8080/listStudent 查看效果：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-5fd3c075d07b5840.png)\n\n> 以上。\n\n## springMVC和springboot的区别\n\nSpring 框架就像一个家族，有众多衍生产品例如 boot、security、jpa等等。但他们的基础都是Spring 的 ioc和 aop ioc 提供了依赖注入的容器 aop ，解决了面向横切面的编程，然后在此两者的基础上实现了其他延伸产品的高级功能。Spring MVC是基于 Servlet 的一个 MVC 框架 主要解决 WEB 开发的问题，因为 Spring 的配置非常复杂，各种XML、 JavaConfig、hin处理起来比较繁琐。于是为了简化开发者的使用，从而创造性地推出了Spring boot，约定优于配置，简化了spring的配置流程。\n\n\n\n说得更简便一些：Spring 最初利用“工厂模式”（DI）和“代理模式”（AOP）解耦应用组件。大家觉得挺好用，于是按照这种模式搞了一个 MVC框架（一些用Spring 解耦的组件），用开发 web 应用（ SpringMVC ）。然后有发现每次开发都写很多样板代码，为了简化工作流程，于是开发出了一些“懒人整合包”（starter），这套就是 Spring Boot。\n\n**Spring MVC的功能**\n\nSpring MVC提供了一种轻度耦合的方式来开发web应用。\n\nSpring MVC是Spring的一个模块，式一个web框架。通过Dispatcher Servlet, ModelAndView 和 View Resolver，开发web应用变得很容易。解决的问题领域是网站应用程序或者服务开发——URL路由、Session、模板引擎、静态Web资源等等。\n\n**Spring Boot的功能**\n\nSpring Boot实现了自动配置，降低了项目搭建的复杂度。\n\n众所周知Spring框架需要进行大量的配置，Spring Boot引入自动配置的概念，让项目设置变得很容易。Spring Boot本身并不提供Spring框架的核心特性以及扩展功能，只是用于快速、敏捷地开发新一代基于Spring框架的应用程序。也就是说，它并不是用来替代Spring的解决方案，而是和Spring框架紧密结合用于提升Spring开发者体验的工具。同时它集成了大量常用的第三方库配置(例如Jackson, JDBC, Mongo, Redis, Mail等等)，Spring Boot应用中这些第三方库几乎可以零配置的开箱即用(out-of-the-box)，大部分的Spring Boot应用都只需要非常少量的配置代码，开发者能够更加专注于业务逻辑。\n\nSpring Boot只是承载者，辅助你简化项目搭建过程的。如果承载的是WEB项目，使用Spring MVC作为MVC框架，那么工作流程和你上面描述的是完全一样的，因为这部分工作是Spring MVC做的而不是Spring Boot。\n\n对使用者来说，换用Spring Boot以后，项目初始化方法变了，配置文件变了，另外就是不需要单独安装Tomcat这类容器服务器了，maven打出jar包直接跑起来就是个网站，但你最核心的业务逻辑实现与业务流程实现没有任何变化。\n\n所以，用最简练的语言概括就是：\n\nSpring 是一个“引擎”；\n\nSpring MVC 是基于Spring的一个 MVC 框架 ；\n\nSpring Boot 是基于Spring4的条件注册的一套快速开发整合包。"
  },
  {
    "path": "docs/JavaWeb/走进JavaWeb技术世界：浅析Tomcat请求处理流程与启动部署过程.md",
    "content": "# 目录\n  * [Overview](#overview)\n  * [Connector Init and Start](#connector-init-and-start)\n  * [Request Process](#request-process)\n    * [Acceptor](#acceptor)\n    * [Poller](#poller)\n    * [Worker](#worker)\n    * [Container](#container)\n  * [Reference](#reference)\n\n\n\n本文转载自互联网，侵删\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本系列文章将同步到我的个人博客：\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n本文是《走进JavaWeb技术世界》系列博文的其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，从servlet到框架，从ssm再到SpringBoot，一步步地学习JavaWeb基础知识，并上手进行实战，接着了解JavaWeb项目中经常要使用的技术和组件，包括日志组件、Maven、Junit，等等内容，以便让你更完整地了解整个JavaWeb技术体系，形成自己的知识框架。为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n**文末赠送8000G的Java架构师学习资料，需要的朋友可以到文末了解领取方式，资料包括Java基础、进阶、项目和架构师等免费学习资料，更有数据库、分布式、微服务等热门技术学习视频，内容丰富，兼顾原理和实践，另外也将赠送作者原创的Java学习指南、Java程序员面试指南等干货资源）**\n<!-- more -->\n\n很多东西在时序图中体现的已经非常清楚了，没有必要再一步一步的作介绍，所以本文以图为主，然后对部分内容加以简单解释。\n\n绘制图形使用的工具是 PlantUML + Visual Studio Code + PlantUML Extension\n\n本文对 Tomcat 的介绍以 Tomcat-9.0.0.M22 为标准。\n\nTomcat-9.0.0.M22 是 Tomcat 目前最新的版本，但尚未发布，它实现了 Servlet4.0 及 JSP2.3 并提供了很多新特性，需要 1.8 及以上的 JDK 支持等等，详情请查阅 Tomcat-9.0-doc。\n\n> https://tomcat.apache.org/tomcat-9.0-doc/index.html\n\n## Overview\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405154006.png)\n\nConnector 启动以后会启动一组线程用于不同阶段的请求处理过程。\n\n1.  Acceptor 线程组。用于接受新连接，并将新连接封装一下，选择一个 Poller 将新连接添加到 Poller 的事件队列中。\n\n2.  Poller 线程组。用于监听 Socket 事件，当 Socket 可读或可写等等时，将 Socket 封装一下添加到 worker 线程池的任务队列中。\n\n3.  worker 线程组。用于对请求进行处理，包括分析请求报文并创建 Request 对象，调用容器的 pipeline 进行处理。\n\nAcceptor、Poller、worker 所在的 ThreadPoolExecutor 都维护在 NioEndpoint 中。\n\n## Connector Init and Start\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405154024.png)\n\n1.  initServerSocket()，通过 ServerSocketChannel.open() 打开一个 ServerSocket，默认绑定到 8080 端口，默认的连接等待队列长度是 100， 当超过 100 个时会拒绝服务。我们可以通过配置 conf/server.xml 中 Connector 的 acceptCount 属性对其进行定制。\n\n2.  createExecutor() 用于创建 Worker 线程池。默认会启动 10 个 Worker 线程，Tomcat 处理请求过程中，Woker 最多不超过 200 个。我们可以通过配置 conf/server.xml 中 Connector 的 minSpareThreads 和 maxThreads 对这两个属性进行定制。\n\n3.  Pollor 用于检测已就绪的 Socket。默认最多不超过 2 个，Math.min(2,Runtime.getRuntime().availableProcessors());。我们可以通过配置 pollerThreadCount 来定制。\n\n4.  Acceptor 用于接受新连接。默认是 1 个。我们可以通过配置 acceptorThreadCount 对其进行定制。\n\n## Request Process\n\n### Acceptor\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405154041.png)\n\n1.  Acceptor 在启动后会阻塞在 ServerSocketChannel.accept(); 方法处，当有新连接到达时，该方法返回一个 SocketChannel。\n\n2.  配置完 Socket 以后将 Socket 封装到 NioChannel 中，并注册到 Poller,值的一提的是，我们一开始就启动了多个 Poller 线程，注册的时候，连接是公平的分配到每个 Poller 的。NioEndpoint 维护了一个 Poller 数组，当一个连接分配给 pollers[index] 时，下一个连接就会分配给 pollers[(index+1)%pollers.length].\n\n3.  addEvent() 方法会将 Socket 添加到该 Poller 的 PollerEvent 队列中。到此 Acceptor 的任务就完成了。\n\n### Poller\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405154054.png)\n1.  selector.select(1000)。当 Poller 启动后因为 selector 中并没有已注册的 Channel，所以当执行到该方法时只能阻塞。所有的 Poller 共用一个 Selector，其实现类是 sun.nio.ch.EPollSelectorImpl\n\n2.  events() 方法会将通过 addEvent() 方法添加到事件队列中的 Socket 注册到 EPollSelectorImpl，当 Socket 可读时，Poller 才对其进行处理\n\n3.  createSocketProcessor() 方法将 Socket 封装到 SocketProcessor 中，SocketProcessor 实现了 Runnable 接口。worker 线程通过调用其 run() 方法来对 Socket 进行处理。\n\n4.  execute(SocketProcessor) 方法将 SocketProcessor 提交到线程池，放入线程池的 workQueue 中。workQueue 是 BlockingQueue 的实例。到此 Poller 的任务就完成了。\n\n### Worker\n\n![](https://img-blog.csdnimg.cn/20190808094814420.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0FsYmVuWGll,size_16,color_FFFFFF,t_70)\n\n*   worker 线程被创建以后就执行 ThreadPoolExecutor 的 runWorker() 方法，试图从 workQueue 中取待处理任务，但是一开始 workQueue 是空的，所以 worker 线程会阻塞在 workQueue.take() 方法。\n\n*   当新任务添加到 workQueue后，workQueue.take() 方法会返回一个 Runnable，通常是 SocketProcessor,然后 worker 线程调用 SocketProcessor 的 run() 方法对 Socket 进行处理。\n\n*   createProcessor() 会创建一个 Http11Processor, 它用来解析 Socket，将 Socket 中的内容封装到 Request 中。注意这个 Request 是临时使用的一个类，它的全类名是 org.apache.coyote.Request，\n\n*   postParseRequest() 方法封装一下 Request，并处理一下映射关系(从 URL 映射到相应的 Host、Context、Wrapper)。\n\n1.  CoyoteAdapter 将 Rquest 提交给 Container 处理之前，并将 org.apache.coyote.Request 封装到 org.apache.catalina.connector.Request，传递给 Container 处理的 Request 是 org.apache.catalina.connector.Request。\n\n2.  connector.getService().getMapper().map()，用来在 Mapper 中查询 URL 的映射关系。映射关系会保留到 org.apache.catalina.connector.Request 中，Container 处理阶段 request.getHost() 是使用的就是这个阶段查询到的映射主机，以此类推 request.getContext()、request.getWrapper() 都是。\n\n*   connector.getService().getContainer().getPipeline().getFirst().invoke() 会将请求传递到 Container 处理，当然了 Container 处理也是在 Worker 线程中执行的，但是这是一个相对独立的模块，所以单独分出来一节。\n\n### Container\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405154120.png)\n\n*   需要注意的是，基本上每一个容器的 StandardPipeline 上都会有多个已注册的 Valve，我们只关注每个容器的 Basic Valve。其他 Valve 都是在 Basic Valve 前执行。\n\n*   request.getHost().getPipeline().getFirst().invoke() 先获取对应的 StandardHost，并执行其 pipeline。\n\n*   request.getContext().getPipeline().getFirst().invoke() 先获取对应的 StandardContext,并执行其 pipeline。\n\n*   request.getWrapper().getPipeline().getFirst().invoke() 先获取对应的 StandardWrapper，并执行其 pipeline。\n\n*   最值得说的就是 StandardWrapper 的 Basic Valve，StandardWrapperValve\n\n1.  allocate() 用来加载并初始化 Servlet，值的一提的是 Servlet 并不都是单例的，当 Servlet 实现了 SingleThreadModel 接口后，StandardWrapper 会维护一组 Servlet 实例，这是享元模式。当然了 SingleThreadModel在 Servlet 2.4 以后就弃用了。\n\n2.  createFilterChain() 方法会从 StandardContext 中获取到所有的过滤器，然后将匹配 Request URL 的所有过滤器挑选出来添加到 filterChain 中。\n\n3.  doFilter() 执行过滤链,当所有的过滤器都执行完毕后调用 Servlet 的 service() 方法。\n\n## Reference\n\n1.  《How Tomcat works》\n\n    https://www.amazon.com/How-Tomcat-Works-Budi-Kurniawan/dp/097521280X\n\n2.  《Tomcat 架构解析》– 刘光瑞\n\n    http://product.dangdang.com/25084132.html\n\n3.  Tomcat-9.0-doc\n\n    https://tomcat.apache.org/tomcat-9.0-doc/index.html\n\n4.  apache-tomcat-9.0.0.M22-src\n\n    http://www-eu.apache.org/dist/tomcat/tomcat-9/v9.0.0.M22/src/\n\n5.  tomcat架构分析 (connector NIO 实现)\n\n    http://gearever.iteye.com/blog/1844203\n\n\n\n\n\n                     \n"
  },
  {
    "path": "docs/JavaWeb/走进JavaWeb技术世界：深入浅出Mybatis基本原理.md",
    "content": "# 目录\n* [引言](#引言)\n* [工作原理原型图](#工作原理原型图)\n* [工作原理解析](#工作原理解析)\n* [mybatis层次图：](#mybatis层次图：)\n    * [MyBatis框架及原理分析](#MyBatis框架及原理分析)\n* [MyBatis的配置](#mybatis的配置)\n* [MyBatis的主要成员](#mybatis的主要成员)\n* [参考文章](#参考文章)\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章首发于我的个人博客：\n\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《走进JavaWeb技术世界》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，从servlet到框架，从ssm再到SpringBoot，一步步地学习JavaWeb基础知识，并上手进行实战，接着了解JavaWeb项目中经常要使用的技术和组件，包括日志组件、Maven、Junit，等等内容，以便让你更完整地了解整个JavaWeb技术体系，形成自己的知识框架。为了更好地总结和检验你的学习成果，本系列文章也会提供每个知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n**文末赠送8000G的Java架构师学习资料，需要的朋友可以到文末了解领取方式，资料包括Java基础、进阶、项目和架构师等免费学习资料，更有数据库、分布式、微服务等热门技术学习视频，内容丰富，兼顾原理和实践，另外也将赠送作者原创的Java学习指南、Java程序员面试指南等干货资源）**\n<!-- more -->\n\n## 引言\n\n在mybatis的基础知识中我们已经可以对mybatis的工作方式窥斑见豹（参考：《MyBatis————基础知识》）。\n\n但是，为什么还要要学习mybatis的工作原理？因为，随着mybatis框架的不断发展，如今已经越来越趋于自动化，从代码生成，到基本使用，我们甚至不需要动手写一句SQL就可以完成一个简单应用的全部CRUD操作。\n\n从原生mybatis到mybatis-spring，到mybatis-plus再到mybatis-plus-spring-boot-starter。spring在发展，mybatis同样在随之发展。\n\n万变的外表终将迷惑人们的双眼，只要抓住核心我们永远不会迷茫！\n\n## 工作原理原型图\n\n用最直观的图，来征服你的心！\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405165221.png)\n\n## 工作原理解析\n\nmybatis应用程序通过SqlSessionFactoryBuilder从mybatis-config.xml配置文件（也可以用Java文件配置的方式，需要添加@Configuration）中构建出SqlSessionFactory（SqlSessionFactory是线程安全的）；\n\n然后，SqlSessionFactory的实例直接开启一个SqlSession，再通过SqlSession实例获得Mapper对象并运行Mapper映射的SQL语句，完成对数据库的CRUD和事务提交，之后关闭SqlSession。\n\n说明：SqlSession是单线程对象，因为它是非线程安全的，是持久化操作的独享对象，类似jdbc中的Connection，底层就封装了jdbc连接。\n\n详细流程如下：\n\n1、加载mybatis全局配置文件（数据源、mapper映射文件等），解析配置文件，MyBatis基于XML配置文件生成Configuration，和一个个MappedStatement（包括了参数映射配置、动态SQL语句、结果映射配置），其对应着<select | update | delete | insert>标签项。\n\n2、SqlSessionFactoryBuilder通过Configuration对象生成SqlSessionFactory，用来开启SqlSession。\n\n3、SqlSession对象完成和数据库的交互：\na、用户程序调用mybatis接口层api（即Mapper接口中的方法）\nb、SqlSession通过调用api的Statement ID找到对应的MappedStatement对象\nc、通过Executor（负责动态SQL的生成和查询缓存的维护）将MappedStatement对象进行解析，sql参数转化、动态sql拼接，生成jdbc Statement对象\nd、JDBC执行sql。\n\ne、借助MappedStatement中的结果映射关系，将返回结果转化成HashMap、JavaBean等存储结构并返回。\n\n## mybatis层次图：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405165208.png)\n\n### MyBatis框架及原理分析\n\nMyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架，其主要就完成2件事情：\n\n1.  封装JDBC操作\n2.  利用反射打通Java类与SQL语句之间的相互转换\n\nMyBatis的主要设计目的就是让我们对执行SQL语句时对输入输出的数据管理更加方便，所以方便地写出SQL和方便地获取SQL的执行结果才是MyBatis的核心竞争力。\n\n## MyBatis的配置\n\nMyBatis框架和其他绝大部分框架一样，需要一个配置文件，其配置文件大致如下：\n\n````\n    <configuration>\n        <settings>\n            <setting name=\"cacheEnabled\" value=\"true\"/>\n            <setting name=\"lazyLoadingEnabled\" value=\"false\"/>\n            <!--<setting name=\"logImpl\" value=\"STDOUT_LOGGING\"/> &lt;!&ndash; 打印日志信息 &ndash;&gt;-->\n        </settings>\n    \n        <typeAliases>\n            <typeAlias type=\"com.luo.dao.UserDao\" alias=\"User\"/>\n        </typeAliases>\n    \n        <environments default=\"development\">\n            <environment id=\"development\">\n                <transactionManager type=\"JDBC\"/> <!--事务管理类型-->\n                <dataSource type=\"POOLED\">\n                    <property name=\"username\" value=\"luoxn28\"/>\n                    <property name=\"password\" value=\"123456\"/>\n                    <property name=\"driver\" value=\"com.mysql.jdbc.Driver\"/>\n                    <property name=\"url\" value=\"jdbc:mysql://192.168.1.150/ssh_study\"/>\n                </dataSource>\n            </environment>\n        </environments>\n    \n        <mappers>\n            <mapper resource=\"userMapper.xml\"/>\n        </mappers>\n    </configuration>\n````\n\n\n\n以上配置中，最重要的是数据库参数的配置，比如用户名密码等，如果配置了数据表对应的mapper文件，则需要将其加入到<mappers>节点下。\n\n## MyBatis的主要成员\n\n*   Configuration    MyBatis所有的配置信息都保存在Configuration对象之中，配置文件中的大部分配置都会存储到该类中\n*   SqlSession      作为MyBatis工作的主要顶层API，表示和数据库交互时的会话，完成必要数据库增删改查功能\n*   Executor        MyBatis执行器，是MyBatis 调度的核心，负责SQL语句的生成和查询缓存的维护\n*   StatementHandler 封装了JDBC Statement操作，负责对JDBC statement 的操作，如设置参数等\n*   ParameterHandler 负责对用户传递的参数转换成JDBC Statement 所对应的数据类型\n*   ResultSetHandler  负责将JDBC返回的ResultSet结果集对象转换成List类型的集合\n*   TypeHandler     负责java数据类型和jdbc数据类型(也可以说是数据表列类型)之间的映射和转换\n*   MappedStatement MappedStatement维护一条<select|update|delete|insert>节点的封装\n*   SqlSource       负责根据用户传递的parameterObject，动态地生成SQL语句，将信息封装到BoundSql对象中，并返回\n*   BoundSql       表示动态生成的SQL语句以及相应的参数信息\n\n以上主要成员在一次数据库操作中基本都会涉及，在SQL操作中重点需要关注的是SQL参数什么时候被设置和结果集怎么转换为JavaBean对象的，这两个过程正好对应StatementHandler和ResultSetHandler类中的处理逻辑。\n\n![](http://img.blog.csdn.net/20141028140852531?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbHVhbmxvdWlz/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)\n\n(图片来自[《深入理解mybatis原理》 MyBatis的架构设计以及实例分析](http://blog.csdn.net/luanlouis/article/details/40422941))\n\n\n## 参考文章\n\nhttps://www.jianshu.com/p/e398435fc1c4\nhttps://segmentfault.com/a/1190000015117926?utm_source=tag-newest#articleHeader4\nhttps://blog.csdn.net/u014745069/article/details/80788127\nhttps://blog.csdn.net/u014297148/article/details/78696096\nhttps://blog.csdn.net/weixin_43184769/article/details/91126687\n\n\n\n                     \n\n"
  },
  {
    "path": "docs/Spring全家桶/Spring/SpringAOP的概念与作用.md",
    "content": "# Spring ܵ AOP\n\n## Spring ܵ AOP\n\nSpring ܵһؼ**ı**(AOP)ܡıҪѳ߼ֽɲͬĲֳΪνĹע㡣һӦóĶĹܱΪ**йע**Щйעڸ϶Ӧóҵ߼иָĺܺõӣ־¼ơʽ񡢰ȫԺͻȡ\n\n OOP УؼԪģ࣬ AOP еԪģ档עӦó໥ϣAOP ԰ӰĶжԺйעAOP ǱԵĴ Perl.NETJava ԡ\n\nSpring AOP ģṩһӦó磬ִһʱڷִ֮ǰ֮ӶĹܡ\n\n## AOP \n\nǿʼʹ AOP ֮ǰϤһ AOP Щﲢض Spring AOP йصġ\n\n|  |  |\n| --- | --- |\n| Aspect | һģһṩ APIs磬һ־ģΪ˼¼־ AOP áӦóӵķ棬ȡ |\n| Join point | Ӧóһ㣬ڲ AOP 档Ҳ˵ʵʵӦóУһʹ Spring AOP ܡ |\n| Advice | ʵж֮ǰִ֮еķڳִڼͨ Spring AOP ʵʱõĴ롣 |\n| Pointcut | һһӵ㣬֪ͨӦñִСʹñʽģʽָǽ AOP пġ |\n| Introduction | ·ԵеС |\n| Target object | һ߶֪ͨĶԶһҲΪ֪ͨ |\n| Weaving | Weaving ѷӵӦóͻ߶ϣһ֪ͨĶЩڱʱʱʱɡ |\n\n## ֪ͨ\n\nSpring ʹᵽ֪ͨ\n\n| ֪ͨ |  |\n| --- | --- |\n| ǰ֪ͨ | һִ֮ǰִ֪ͨ |\n| ֪ͨ | һִ֮󣬲ִ֪ͨ |\n| غ֪ͨ | һִֻ֮ڷɹʱִ֪ͨ |\n| ׳쳣֪ͨ | һִֻ֮ڷ˳׳쳣ʱִ֪ͨ |\n| ֪ͨ | ڽ鷽֮ǰִ֪֮ͨ |\n\n## ʵԶ巽\n\nSpring ֧ **@AspectJ annotation style** ķ**ģʽ**ķʵԶ巽档ַѾӽڽϸ͡\n\n|  |  |\n| --- | --- |\n| [XML Schema based](https://www.w3cschool.cn/wkspring/omps1mm6.html) | ʹóԼõ XML ʵֵġ |\n| [@AspectJ based](https://www.w3cschool.cn/wkspring/k4q21mm8.html) | @AspectJ һķΪ Java 5 ע͵ĳ Java ע͡ |\n\n\n\n## Spring л AOP  XMLܹ\n\nΪڱڵʹ aop ռǩҪ spring-aop ܹ\n\n```\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<beans xmlns=\"http://www.springframework.org/schema/beans\"\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" \n    xmlns:aop=\"http://www.springframework.org/schema/aop\"\n    xsi:schemaLocation=\"http://www.springframework.org/schema/beans\n    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd \n    http://www.springframework.org/schema/aop \n    http://www.springframework.org/schema/aop/spring-aop-3.0.xsd \">\n\n   <!-- bean definition & AOP specific configuration -->\n\n</beans>\n\n```\n\n㻹ҪӦó CLASSPATH ʹ AspectJ ļЩļһ AspectJ װõ lib Ŀ¼ǿõģ Internet ǡ(עaspectjweaver.jar Ѱ)\n\n*   aspectjrt.jar\n\n*   aspectjweaver.jar\n\n*   aspectj.jar\n\n*   aopalliance.jar\n\n## һ aspect\n\nһ **aspect** ʹ Ԫģֵ֧ bean ʹ **ref** õģʾ\n\n```\n\n   \n   ...\n   \n\n<bean id=\"aBean\" class=\"...\">\n...\n</bean>\n```\n\naBean úע룬ǰ½㿴 Spring bean һ\n\n## һ\n\nһ****ȷʹòִͬеĸȤӵ㣨ڴõ XML ܹʱ㽫ᰴʾ壺\n\n```\n\n   \n   \n   ...\n   \n\n<bean id=\"aBean\" class=\"...\">\n...\n</bean>\n```\n\nʾһΪ businessService 㣬㽫 com.tutorialspoint µ Student е getName() ƥ䣺\n\n```\n\n   \n   \n   ...\n   \n\n<bean id=\"aBean\" class=\"...\">\n...\n</bean>\n```\n\n## \n\nʹԪ͵֪ͨ£\n\n```\n\n   \n      \n      <!-- a before advice definition -->\n      \n      <!-- an after advice definition -->\n      \n      <!-- an after-returning advice definition -->\n      <!--The doRequiredTask method must have parameter named retVal -->\n      \n      <!-- an after-throwing advice definition -->\n      <!--The doRequiredTask method must have parameter named ex -->\n      \n      <!-- an around advice definition -->\n      \n   ...\n   \n\n<bean id=\"aBean\" class=\"...\">\n...\n</bean>\n```\n\nԶԲͬĽʹͬ **doRequiredTask** ߲ͬķЩΪ aspect ģһ塣\n\n##  AOP  XML ܹʾ\n\nΪᵽĻ AOP  XML ܹĸǱдһʾʵּ顣Ϊǵʾʹü飬ʹ Eclipse IDE ڹ״̬Ȼ²贴һ Spring Ӧó\n\n|  |  |\n| --- | --- |\n| 1 | һΪ _SpringExample_ ĿĿ **src** ļ´һΪ _com.tutorialspoint_ İ |\n| 2 | ʹ _Add External JARs_ ѡ Spring ļ _Spring Hello World Example_ ½н͵ |\n| 3 | Ŀ Spring AOP ָĿļ **aspectjrt.jar aspectjweaver.jar**  **aspectj.jar** |\n| 4 |  _com.tutorialspoint_ ´ Java  **Logging** _Student_  _MainApp_ |\n| 5 |  **src** ļ´ Beans ļ _Beans.xml_ |\n| 6 | һǴ Java ļ Bean ļݣҰ½͵Ӧó |\n\n **Logging.java** ļݡʵ aspect ģһʾڸõķ\n\n```\npackage com.tutorialspoint;\npublic class Logging {\n   /** \n    * This is the method which I would like to execute\n    * before a selected method execution.\n    */\n   public void beforeAdvice(){\n      System.out.println(\"Going to setup student profile.\");\n   }\n   /** \n    * This is the method which I would like to execute\n    * after a selected method execution.\n    */\n   public void afterAdvice(){\n      System.out.println(\"Student profile has been setup.\");\n   }\n   /** \n    * This is the method which I would like to execute\n    * when any method returns.\n    */\n   public void afterReturningAdvice(Object retVal){\n      System.out.println(\"Returning:\" + retVal.toString() );\n   }\n   /**\n    * This is the method which I would like to execute\n    * if there is an exception raised.\n    */\n   public void AfterThrowingAdvice(IllegalArgumentException ex){\n      System.out.println(\"There has been an exception: \" + ex.toString());   \n   }  \n}\n```\n\n **Student.java** ļݣ\n\n```\npackage com.tutorialspoint;\npublic class Student {\n   private Integer age;\n   private String name;\n   public void setAge(Integer age) {\n      this.age = age;\n   }\n   public Integer getAge() {\n      System.out.println(\"Age : \" + age );\n      return age;\n   }\n   public void setName(String name) {\n      this.name = name;\n   }\n   public String getName() {\n      System.out.println(\"Name : \" + name );\n      return name;\n   }  \n   public void printThrowException(){\n       System.out.println(\"Exception raised\");\n       throw new IllegalArgumentException();\n   }\n}\n```\n\n **MainApp.java** ļݣ\n\n```\npackage com.tutorialspoint;\nimport org.springframework.context.ApplicationContext;\nimport org.springframework.context.support.ClassPathXmlApplicationContext;\npublic class MainApp {\n   public static void main(String[] args) {\n      ApplicationContext context = \n             new ClassPathXmlApplicationContext(\"Beans.xml\");\n      Student student = (Student) context.getBean(\"student\");\n      student.getName();\n      student.getAge();      \n      student.printThrowException();\n   }\n}\n```\n\nļ **Beans.xml**\n\n```\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<beans xmlns=\"http://www.springframework.org/schema/beans\"\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" \n    xmlns:aop=\"http://www.springframework.org/schema/aop\"\n    xsi:schemaLocation=\"http://www.springframework.org/schema/beans\n    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd \n    http://www.springframework.org/schema/aop \n    http://www.springframework.org/schema/aop/spring-aop-3.0.xsd \">\n\n   \n      \n         \n         \n         \n         \n         \n      \n   \n\n   <!-- Definition for student bean -->\n   <bean id=\"student\" class=\"com.tutorialspoint.Student\">\n      <property name=\"name\"  value=\"Zara\" />\n      <property name=\"age\"  value=\"11\"/>      \n   </bean>\n\n   <!-- Definition for logging aspect -->\n   <bean id=\"logging\" class=\"com.tutorialspoint.Logging\"/> \n\n</beans>\n\n```\n\nһѾɵĴԴļ bean ļһӦóӦóһжĻ⽫Ϣ\n\n```\nGoing to setup student profile.\nName : Zara\nStudent profile has been setup.\nReturning:Zara\nGoing to setup student profile.\nAge : 11\nStudent profile has been setup.\nReturning:11\nGoing to setup student profile.\nException raised\nStudent profile has been setup.\nThere has been an exception: java.lang.IllegalArgumentException\n.....\nother exception content\n```\n\nһ涨 com.tutorialspoint  ѡз Ǽһ£Ҫһķ֮ǰִ֮Ľ飬ͨ滻ʹʵͷƵ㶨еǺţ*ִС\n\n```\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<beans xmlns=\"http://www.springframework.org/schema/beans\"\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" \n    xmlns:aop=\"http://www.springframework.org/schema/aop\"\n    xsi:schemaLocation=\"http://www.springframework.org/schema/beans\n    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd \n    http://www.springframework.org/schema/aop \n    http://www.springframework.org/schema/aop/spring-aop-3.0.xsd \">\n\n   \n   \n      \n      \n      \n   \n   \n\n   <!-- Definition for student bean -->\n   <bean id=\"student\" class=\"com.tutorialspoint.Student\">\n      <property name=\"name\"  value=\"Zara\" />\n      <property name=\"age\"  value=\"11\"/>      \n   </bean>\n\n   <!-- Definition for logging aspect -->\n   <bean id=\"logging\" class=\"com.tutorialspoint.Logging\"/> \n\n</beans>\n\n```\n\nҪִͨЩ֮ʾӦó⽫Ϣ\n\n```\nGoing to setup student profile.\nName : Zara\nStudent profile has been setup.\nAge : 11\nException raised\n.....\nother exception content\n```\n\n\n\n## Spring л AOP  @AspectJ\n\n@AspectJ Ϊͨ Java 5 עע͵ͨ Java ָ࣬ aspects һַͨĻڼܹ XML ļаԪأ@AspectJ ֧ǿõġ\n\n```\n\n```\n\n㻹ҪӦó CLASSPATH ʹ AspectJ ļЩļһ AspectJ װõ lib Ŀ¼ǿõģûУ Internet ǡ\n\n*   aspectjrt.jar\n\n*   aspectjweaver.jar\n\n*   aspectj.jar\n\n*   aopalliance.jar\n\n## һ aspect\n\nAspects κ bean һǽ @AspectJ ע֮⣬һзֶΣʾ\n\n```\npackage org.xyz;\nimport org.aspectj.lang.annotation.Aspect;\n@Aspect\npublic class AspectModule {\n}\n```\n\nǽ XML а½ãͺκ bean һ\n\n```\n<bean id=\"myAspect\" class=\"org.xyz.AspectModule\">\n   <!-- configure properties of aspect here as normal -->\n</bean>\n\n```\n\n## һ\n\nһ****ȷʹòִͬеĸȤӵ㣨ڴõ XML ܹʱ֣\n\n*   һʽǸȤĸִС\n\n*   һǩһƺĲǲɵģʵӦǿյġ\n\nʾжһΪ businessService 㣬㽫 com.xyz.myapp.service µпõÿһƥ䣺\n\n```\nimport org.aspectj.lang.annotation.Pointcut;\n@Pointcut(\"execution(* com.xyz.myapp.service.*.*(..))\") // expression \nprivate void businessService() {}  // signature\n```\n\nʾжһΪ getname 㣬㽫 com.tutorialspoint µ Student е getName() ƥ䣺\n\n```\nimport org.aspectj.lang.annotation.Pointcut;\n@Pointcut(\"execution(* com.tutorialspoint.Student.getName(..))\") \nprivate void getname() {}\n```\n\n## \n\nʹ @{ADVICE-NAME} עеһʾѾһǩ businessService()\n\n```\n@Before(\"businessService()\")\npublic void doBeforeTask(){\n ...\n}\n@After(\"businessService()\")\npublic void doAfterTask(){\n ...\n}\n@AfterReturning(pointcut = \"businessService()\", returning=\"retVal\")\npublic void doAfterReturnningTask(Object retVal){\n  // you can intercept retVal here.\n  ...\n}\n@AfterThrowing(pointcut = \"businessService()\", throwing=\"ex\")\npublic void doAfterThrowingTask(Exception ex){\n  // you can intercept thrown exception here.\n  ...\n}\n@Around(\"businessService()\")\npublic void doAroundTask(){\n ...\n}\n```\n\nΪһ鶨ڽ֮ǰһʾ\n\n```\n@Before(\"execution(* com.xyz.myapp.service.*.*(..))\")\npublic doBeforeTask(){\n ...\n}\n```\n\n##  AOP  @AspectJ ʾ\n\nΪᵽĹڻ AOP  @AspectJ ĸǱдһʾʵּ顣Ϊǵʾʹü飬ʹ Eclipse IDE ڹ״̬Ȼ²贴һ Spring Ӧó\n\n|  |  |\n| --- | --- |\n| 1 | һΪ _SpringExample_ ĿĿ **src** ļ´һΪ _com.tutorialspoint_ İ |\n| 2 | ʹ _Add External JARs_ ѡ Spring ļ _Spring Hello World Example_ ½н͵ |\n| 3 | Ŀ Spring AOP ָĿļ **aspectjrt.jar aspectjweaver.jar**  **aspectj.jar** |\n| 4 |  _com.tutorialspoint_ ´ Java  **Logging** _Student_  _MainApp_ |\n| 5 |  **src** ļ´ Beans ļ _Beans.xml_ |\n| 6 | һǴ Java ļ Bean ļݣҰ½͵Ӧó |\n\n **Logging.java** ļݡʵ aspect ģһʾڸõķ\n\n```\npackage com.tutorialspoint;\nimport org.aspectj.lang.annotation.Aspect;\nimport org.aspectj.lang.annotation.Pointcut;\nimport org.aspectj.lang.annotation.Before;\nimport org.aspectj.lang.annotation.After;\nimport org.aspectj.lang.annotation.AfterThrowing;\nimport org.aspectj.lang.annotation.AfterReturning;\nimport org.aspectj.lang.annotation.Around;\n@Aspect\npublic class Logging {\n   /** Following is the definition for a pointcut to select\n    *  all the methods available. So advice will be called\n    *  for all the methods.\n    */\n   @Pointcut(\"execution(* com.tutorialspoint.*.*(..))\")\n   private void selectAll(){}\n   /** \n    * This is the method which I would like to execute\n    * before a selected method execution.\n    */\n   @Before(\"selectAll()\")\n   public void beforeAdvice(){\n      System.out.println(\"Going to setup student profile.\");\n   }\n   /** \n    * This is the method which I would like to execute\n    * after a selected method execution.\n    */\n   @After(\"selectAll()\")\n   public void afterAdvice(){\n      System.out.println(\"Student profile has been setup.\");\n   }\n   /** \n    * This is the method which I would like to execute\n    * when any method returns.\n    */\n   @AfterReturning(pointcut = \"selectAll()\", returning=\"retVal\")\n   public void afterReturningAdvice(Object retVal){\n      System.out.println(\"Returning:\" + retVal.toString() );\n   }\n   /**\n    * This is the method which I would like to execute\n    * if there is an exception raised by any method.\n    */\n   @AfterThrowing(pointcut = \"selectAll()\", throwing = \"ex\")\n   public void AfterThrowingAdvice(IllegalArgumentException ex){\n      System.out.println(\"There has been an exception: \" + ex.toString());   \n   }  \n}\n```\n\n **Student.java** ļݣ\n\n```\npackage com.tutorialspoint;\npublic class Student {\n   private Integer age;\n   private String name;\n   public void setAge(Integer age) {\n      this.age = age;\n   }\n   public Integer getAge() {\n      System.out.println(\"Age : \" + age );\n      return age;\n   }\n   public void setName(String name) {\n      this.name = name;\n   }\n   public String getName() {\n      System.out.println(\"Name : \" + name );\n      return name;\n   }\n   public void printThrowException(){\n      System.out.println(\"Exception raised\");\n      throw new IllegalArgumentException();\n   }\n}\n```\n\n **MainApp.java** ļݣ\n\n```\npackage com.tutorialspoint;\nimport org.springframework.context.ApplicationContext;\nimport org.springframework.context.support.ClassPathXmlApplicationContext;\npublic class MainApp {\n   public static void main(String[] args) {\n      ApplicationContext context = \n             new ClassPathXmlApplicationContext(\"Beans.xml\");\n      Student student = (Student) context.getBean(\"student\");\n      student.getName();\n      student.getAge();     \n      student.printThrowException();\n   }\n}\n```\n\nļ **Beans.xml**\n\n```\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<beans xmlns=\"http://www.springframework.org/schema/beans\"\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" \n    xmlns:aop=\"http://www.springframework.org/schema/aop\"\n    xsi:schemaLocation=\"http://www.springframework.org/schema/beans\n    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd \n    http://www.springframework.org/schema/aop \n    http://www.springframework.org/schema/aop/spring-aop-3.0.xsd \">\n\n    \n\n   <!-- Definition for student bean -->\n   <bean id=\"student\" class=\"com.tutorialspoint.Student\">\n      <property name=\"name\"  value=\"Zara\" />\n      <property name=\"age\"  value=\"11\"/>      \n   </bean>\n\n   <!-- Definition for logging aspect -->\n   <bean id=\"logging\" class=\"com.tutorialspoint.Logging\"/> \n\n</beans>\n\n```\n\nһѾɵĴԴļ bean ļһӦóӦóһжĻ⽫Ϣ\n\n```\nGoing to setup student profile.\nName : Zara\nStudent profile has been setup.\nReturning:Zara\nGoing to setup student profile.\nAge : 11\nStudent profile has been setup.\nReturning:11\nGoing to setup student profile.\nException raised\nStudent profile has been setup.\nThere has been an exception: java.lang.IllegalArgumentException\n.....\nother exception content\n```\n\n\n\n\n\n# ο\nhttps://www.w3cschool.cn/wkspring\nhttps://www.runoob.com/w3cnote/basic-knowledge-summary-of-spring.html\nhttp://codepub.cn/2015/06/21/Basic-knowledge-summary-of-Spring\nhttps://dunwu.github.io/spring-tutorial\nhttps://mszlu.com/java/spring\nhttp://c.biancheng.net/spring/aop-module.html"
  },
  {
    "path": "docs/Spring全家桶/Spring/SpringBean的定义与管理（核心）.md",
    "content": "### ע\n\nSpringܵĺĹ\n\n*   SpringΪ󹤳𴴽еJavaЩJava󱻳ΪBean\n*   SpringBean֮ϵSpringʹһֱΪ\"ע\"ķʽBean֮ϵ\n\nʹע룬ΪBeanעֵͨעBeanáעһĽʽBeanļ֯һ𣬶Ӳķʽһ\n\n#### ע\n\nRod Johnsonǵһ߶ļJavaʵЭϵˣַʽһ֣ƷתInverse of ControlIoCMartine FowlerΪַʽһƣע루Dependency Injection˲ע룬ǿƷת京ȫͬĳJava󣨵ߣҪһJava󣨱󣩵ķʱڴͳģʽͨ\n\n1.  ԭʼ: ****Ȼٵñķ\n2.  򵥹ģʽ: ҵĹȻ****ͨȥȡٵñķ\n\nע****֣Ȼᵼµ뱻ʵӲϣǳĿάʹSpring֮󣬵****ȡ󣬵ֻҪ****SpringΪߵĳԱֵɣɴ˿ɼʹSpring󣬵߻ȡķʽԭȡ˱ܡRod Johnson֮ΪƷת\n\nSpringĽǶSpring𽫱ֵߵĳԱ൱ΪעʵMartine Fowler֮Ϊע롣\n\n#### ֵע\n\nֵעָIoCͨԱsetterע뱻ע뷽ʽ򵥡ֱۣSpringעʹá\n\n#### ע\n\nùϵķʽΪע롣ͨ˵SpringڵײԷ䷽ʽִдָĹִдĹʱͿùԳԱִгʼǹעıʡ\n\n#### ע뷽ʽĶԱ\n\nֵעŵ㣺\n\n*   봫ͳJavaBeanдƣ򿪷Ա⡢ܡͨsetter趨ϵԵøֱۡȻ\n*   ڸӵϵùע룬ᵼ¹ӷףĶSpringڴBeanʵʱҪͬʱʵȫʵ½ʹֵע룬ܱЩ⡣\n*   ĳЩԱѡ£Ĺӱء\n\nע£\n\n*   עڹоϵע˳ע롣\n*   ϵ仯BeanעôΪûsetterеϵȫڹ趨뵣ĺĴϵƻ\n*   ϵֻڹ趨ֻĴ߲ܸıϵĵ߶ԣڲϵȫ͸ϸھ۵ԭ\n\n**_ע⣺_**\nֵעΪעΪעԡϵ仯ע룬ùע룻ϵע룬ǲֵע롣\n\n### SpringеBean\n\nڿ˵ʹSpringҪ£ٿBeanBeanSpring˵ҪľǸļBeanʵBeanʵķ\"ע\"νIoCıʡ\n\n#### Bean\n\nͨSpringһBeanʵʱBeanʵʵΪBeanָضSpring֧\n\n1.  singleton: ģʽSpring IoCУsingletonBeanֻһʵ\n2.  prototype: ÿͨgetBean()ȡprototypeBeanʱһµBeanʵ\n3.  request: һHTTPrequestBeanֻһʵζţͬһHTTPڣÿBeanõͬһʵֻWebӦʹSpringʱЧ\n4.  session bean ĶΪ HTTP Ự ֻweb-aware Spring ApplicationContextЧ\n5.  global session: ÿȫֵHTTP SessionӦһBeanʵڵ͵£ʹportlet contextʱЧֻͬWebӦЧ\n\nָBeanSpringĬʹsingletonprototypeBeanĴٴ۱Ƚϴ󡣶singletonBeanʵһɹͿظʹáˣӦþ⽫Beanóprototype\n\n### ʹԶװעBean\n\nSpringԶװBeanBean֮ϵʹrefʽָBeanSpringXMLļݣĳֹΪBeanע뱻Bean\nSpringԶװͨ`<beans/>`Ԫص`default-autowire`ָԶļеBeanãҲͨ`<bean/>`Ԫص`autowire`ָֻԸBeaná\n\n`autowire``default-autowire`Խֵ\n\n*   `no`: ʹԶװ䡣BeanͨrefԪض塣ĬãڽϴĲ𻷾вıãʽúܹõϵ\n*   `byName`: setterԶװ䡣SpringȫBeanҳidsetterȥsetǰ׺СдĸͬBeanע롣ûҵƥBeanʵSpringκע롣\n*   `byType`: setterβԶװ䡣SpringеȫBeanһBeansetterβƥ䣬ԶעBeanҵBean׳һ쳣ûҵBeanʲôᷢsetterᱻá\n*   `constructor`: byTypeƣԶƥ乹Ĳǡҵһ빹ƥBean׳һ쳣\n*   `autodetect`: SpringBeanڲṹоʹconstructorbyTypeԡҵһĬϵĹ캯ôͻӦbyTypeԡ\n\n**һBeanʹԶװʹrefʽָʱʽָԶװڴ͵ӦãʹԶװ䡣ȻʹԶװɼļĹϵԺ͸ԡϵװԴļͣBeanBean֮Ͻ͵Σڸ߲ν**\n\n <!--ͨÿԽBeanųԶװ֮-->  <bean  id=\"\"  autowire-candidate=\"false\"/>  <!--֮⣬beansԪָ֧ģʽַabcβBeanųԶװ֮-->  <beans  default-autowire-candidates=\"*abc\"/> \n\n## Bean3ַʽ\n\n### ʹùBeanʵ\n\nʹùBeanʵùע룬SpringײBean޲ʵҪBeanṩ޲Ĺ\n\nĬϵĹBeanʵSpringBeanʵִĬϳʼеĻ͵ֵʼΪ0falseе͵ֵʼΪnull\n\n### ʹþ̬Bean\n\nʹþ̬BeanʵʱclassҲָʱclassԲָBeanʵʵ࣬Ǿ̬࣬Spring֪ͨĸBeanʵ\n\n֮⣬Ҫʹfactory-methodָ̬Springþ̬һBeanʵһָBeanʵSpringĴͨBeanʵȫһ̬Ҫʹ`<constructor-arg.../>`Ԫָ̬Ĳ\n\n### ʵBean\n\nʵ뾲ֻ̬һͬþֻ̬ʹù༴ɣʵҪʵʹʵʱBeanʵ`<bean.../>`Ԫclassԣʵʹ`factory-bean`ָʵ\nʵBean`<bean.../>`ԪʱҪָԣ\n\n*   factory-bean: ԵֵΪBeanid\n*   factory-method: ָʵĹ\n\nʵʱҪʹ`<constructor-arg.../>`Ԫȷֵ\n\n## ЭͬBean\n\nsingletonBeanprototypeBeanʱͬԭΪSpringʼʱԤʼе`singleton Bean``singleton Bean``prototype Bean`Springڳʼ`singleton Bean`֮ǰȴ`prototypeBean`ȻŴ`singleton Bean`ｫ`prototype Bean`ע`singleton Bean`\nͬķ֣\n\n*   ע: singletonBeanÿҪprototypeBeanʱµBeanʵɱ֤ÿע`prototype Bean`ʵµʵ\n*   ÷ע: עͨʹlookupע룬ʹlookupעSpringдBeanĳ巽زBeanĽҵBeanͨһ`non-singleton Bean`SpringͨʹJDK̬cglib޸Ŀͻ˵Ķ룬ӶʵҪ\n\nõڶַʹ÷ע롣Ϊʹlookupע룬Ҫ\n\n1.  BeanʵඨΪ࣬һ󷽷ȡBean\n2.  `<bean.../>`Ԫ`<lookup-method.../>`ԪSpringΪBeanʵʵָĳ󷽷\n\n**_ע⣺_**\n\n> Springʱ̬ǿķʽʵ`<lookup-method.../>`Ԫָĳ󷽷ĿʵֹӿڣSpringJDK̬ʵָó࣬Ϊ֮ʵֳ󷽷ĿûʵֹӿڣSpringcglibʵָó࣬Ϊ֮ʵֳ󷽷Spring4.0spring-core-xxx.jarѾcglib⡣\n\n## ֺ\n\nSpringṩֳõĺ\n\n*   Bean: ֺBeanкBeanжǿ\n*   : ֺIoCкǿܡ\n\n### Bean\n\nBeanһBeanBeanṩidԣҪеBeanִкΪеĿBeanɴȣBeanΪBeanBeanBeanʵɹ֮󣬶BeanʵнһǿBeanʵ`BeanPostProcessor`ӿڣͬʱʵָýӿڵ\n\n1.  `Object postProcessBeforeInitialization(Object bean, String name) throws BeansException`: ÷ĵһϵͳкBeanʵڶǸBeanid\n2.  `Object postProcessAfterinitialization(Object bean, String name) throws BeansException`: ÷ĵһϵͳкBeanʵڶǸBeanid\n\nһעBeanBeanͻԶÿBeanʱԶBeanĻصʱͼ\n\n![bean-post-process](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/e8970a58f831cd7613b680ef0541e7c7.jpg)\n\nעһ㣬ʹ`BeanFactory`ΪSpringֶעBeanȡBeanʵȻֶעᡣ\n\nBeanPostProcessor bp =  (BeanPostProcessor)beanFactory.getBean(\"bp\"); beanFactory.addBeanPostProcessor(bp);  Person p =  (Person)beanFactory.getBean(\"person\");\n\n### \n\nBeanеBeanʵʵ`BeanFactoryPostProcessor`ӿڣʵָýӿڵһ`postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)`ʵָ÷ķǶSpringеĴִԶSpringԶչȻҲԶSpringκδ\n\n`BeanPostProcessor``ApplicationContext`Զ⵽еԶעʹ`BeanFactory`ΪSpringֶø`BeanFactory`\n\n## Spring\"\"֧\n\n### Bean\n\nSpringṩ¼AnnotationעSpring Bean\n\n*   `@Component`: עһͨSpring Bean\n*   `@Controller`: עһ\n*   `@Service`: עһҵ߼\n*   `@Repository`: עһDAO\n\nSpringļãָԶɨİ\n\n<context:component-scan  base-package=\"edu.shu.spring.domain\"/>\n\n### ʹ@Resource\n\n`@Resource`λ`javax.annotation`£JavaEE淶һ`Annotation`Springֱӽ˸`Annotation`ͨʹø`Annotation`ΪĿBeanָЭBeanʹ`@Resource``<property.../>`ԪصrefͬЧ\n`@Resource`setterҲֱʵʹ`@Resource`ʵӼ򵥣ʱSpringֱʹJavaEE淶Fieldע룬ʱsetterԲҪ\n\n### ʹ@PostConstruct@PreDestroyΪ\n\n`@PostConstruct``@PreDestroy`ͬλjavax.annotation£ҲJavaEE淶AnnotationSpringֱӽǣڶSpringBeanΪǶηκԡǰεķʱBeanĳʼεķʱBean֮ǰķ\n\n### Spring4.0ǿԶװ;ȷװ\n\nSpringṩ`@Autowired`עָԶװ䣬`@Autowired`setterͨʵ͹ȡʹ`@Autowired`עsetterʱĬϲbyTypeԶװԡֲ£Զװ͵ĺѡBeanʵжʱͿ쳣Ϊʵ־ȷԶװ䣬Springṩ`@Qualifier`ע⣬ͨʹ`@Qualifier`BeanidִԶװ䡣\n\n# ο\n\nhttps://www.w3cschool.cn/wkspring\nhttps://www.runoob.com/w3cnote/basic-knowledge-summary-of-spring.html\nhttp://codepub.cn/2015/06/21/Basic-knowledge-summary-of-Spring\nhttps://dunwu.github.io/spring-tutorial\nhttps://mszlu.com/java/spring\nhttp://c.biancheng.net/spring/aop-module.html"
  },
  {
    "path": "docs/Spring全家桶/Spring/Spring中对于数据库的访问.md",
    "content": "\n## Դ\n\n### Spring Դ\n\nSpring Դжַʽһһо٣\n\n#### [#](https://dunwu.github.io/spring-tutorial/pages/1b774c/#%E4%BD%BF%E7%94%A8-jndi-%E6%95%B0%E6%8D%AE%E6%BA%90)ʹ JNDI Դ\n\n Spring Ӧò֧ JNDI  WEB ϣ WebSphereJBossTomcat ȣͿʹ JNDI ȡԴ\n\n\n\n```\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<beans xmlns=\"http://www.springframework.org/schema/beans\"xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n  xmlns:jee=\"http://www.springframework.org/schema/jee\"\n  xsi:schemaLocation=\"http://www.springframework.org/schema/beans\n            http://www.springframework.org/schema/beans/spring-beans-3.2.xsd\nhttp://www.springframework.org/schema/jee\nhttp://www.springframework.org/schema/jee/spring-jee-3.2.xsd\">\n\n  <!-- 1.ʹbeanjndiԴ -->\n  <bean id=\"dataSource\" class=\"org.springframework.jndi.JndiObjectFactoryBean\">\n    <property name=\"jndiName\" value=\"java:comp/env/jdbc/orclight\" />\n  </bean>\n\n  <!-- 2.ʹjeeǩjndiԴ1ȼۣҪռ -->\n  <jee:jndi-lookup id=\"dataSource\" jndi-name=\" java:comp/env/jdbc/orclight\" />\n</beans>\n\n```\n\n\n\n#### [#](https://dunwu.github.io/spring-tutorial/pages/1b774c/#%E4%BD%BF%E7%94%A8%E6%95%B0%E6%8D%AE%E5%BA%93%E8%BF%9E%E6%8E%A5%E6%B1%A0)ʹݿӳ\n\nSpring ûṩݿӳصʵ֣Ҫѡʵݿӳءһʹ [Druid (opens new window)](https://github.com/alibaba/druid)Ϊݿӳصʾ\n\n\n\n```\n<bean id=\"dataSource\" class=\"com.alibaba.druid.pool.DruidDataSource\"\n        init-method=\"init\" destroy-method=\"close\">\n    <property name=\"driverClassName\" value=\"${jdbc.driver}\"/>\n    <property name=\"url\" value=\"${jdbc.url}\"/>\n    <property name=\"username\" value=\"${jdbc.username}\"/>\n    <property name=\"password\" value=\"${jdbc.password}\"/>\n\n    <!-- óʼСС -->\n    <property name=\"initialSize\" value=\"1\"/>\n    <property name=\"minIdle\" value=\"1\"/>\n    <property name=\"maxActive\" value=\"10\"/>\n\n    <!-- ûȡӵȴʱʱ -->\n    <property name=\"maxWait\" value=\"10000\"/>\n\n    <!-- üòŽһμ⣬ҪرյĿӣλǺ -->\n    <property name=\"timeBetweenEvictionRunsMillis\" value=\"60000\"/>\n\n    <!-- һڳСʱ䣬λǺ -->\n    <property name=\"minEvictableIdleTimeMillis\" value=\"300000\"/>\n\n    <property name=\"testWhileIdle\" value=\"true\"/>\n\n    <!-- ｨΪTRUEֹȡӲ -->\n    <property name=\"testOnBorrow\" value=\"true\"/>\n    <property name=\"testOnReturn\" value=\"false\"/>\n\n    <!-- PSCacheָÿPSCacheĴС -->\n    <property name=\"poolPreparedStatements\" value=\"true\"/>\n    <property name=\"maxPoolPreparedStatementPerConnectionSize\"\n              value=\"20\"/>\n\n    <!-- ύʽĬϾTRUEԲ -->\n\n    <property name=\"defaultAutoCommit\" value=\"true\"/>\n\n    <!-- ֤ЧSQLͬòͬ -->\n    <property name=\"validationQuery\" value=\"select 1 \"/>\n    <property name=\"filters\" value=\"stat\"/>\n  </bean>\n\n```\n\n\n\n#### [#](https://dunwu.github.io/spring-tutorial/pages/1b774c/#%E5%9F%BA%E4%BA%8E-jdbc-%E9%A9%B1%E5%8A%A8%E7%9A%84%E6%95%B0%E6%8D%AE%E6%BA%90) JDBC Դ\n\n\n\n```\n<bean id=\"dataSource\" class=\"org.springframework.jdbc.datasource.DriverManagerDataSource\">\n  <property name=\"driverClassName\" value=\"${jdbc.driver}\"/>\n  <property name=\"url\" value=\"${jdbc.url}\"/>\n  <property name=\"username\" value=\"${jdbc.username}\"/>\n  <property name=\"password\" value=\"${jdbc.password}\"/>\n</bean>\n```\n\n\n#### ʹJDBC\n\n: 2022/11/16 20:10 / Ķ: 946668\n\n* * *\n\n\n\nǰ[JDBC](https://www.liaoxuefeng.com/wiki/1252599548343744/1255943820274272)ʱѾJavaʹJDBCӿڷʹϵݿʱҪ¼\n\n*   ȫ`DataSource`ʵʾݿӳأ\n*   Ҫдݿķڲ²ݿ⣺\n    *   ȫ`DataSource`ʵȡ`Connection`ʵ\n    *   ͨ`Connection`ʵ`PreparedStatement`ʵ\n    *   ִSQL䣬ǲѯͨ`ResultSet`ȡ޸ģ`int`\n\nȷдJDBCĹؼʹ`try ... finally`ͷԴ漰ĴҪȷύع\n\nSpringʹJDBCͨIoCһ`DataSource`ʵȻSpringṩһ`JdbcTemplate`ԷǲJDBCˣͨ£ǻʵһ`JdbcTemplate`˼壬Ҫʹ[Templateģʽ](https://www.liaoxuefeng.com/wiki/1252599548343744/1281319636041762)\n\nдʾ߲ԴʱǿƼʹ[HSQLDB](http://hsqldb.org/)ݿ⣬һJavaдĹϵݿ⣬ڴģʽļģʽУֻһjarǳʺʾ߲Դ롣\n\nʵʹΪȴMaven`spring-data-jdbc`Ȼ\n\n*   org.springframework:spring-context:6.0.0\n*   org.springframework:spring-jdbc:6.0.0\n*   jakarta.annotation:jakarta.annotation-api:2.1.1\n*   com.zaxxer:HikariCP:5.0.1\n*   org.hsqldb:hsqldb:2.7.1\n\n`AppConfig`УҪ¼Bean\n\n```\n@Configuration\n@ComponentScan\n@PropertySource(\"jdbc.properties\")\npublic class AppConfig {\n\n    @Value(\"${jdbc.url}\")\n    String jdbcUrl;\n\n    @Value(\"${jdbc.username}\")\n    String jdbcUsername;\n\n    @Value(\"${jdbc.password}\")\n    String jdbcPassword;\n\n    @Bean\n    DataSource createDataSource() {\n        HikariConfig config = new HikariConfig();\n        config.setJdbcUrl(jdbcUrl);\n        config.setUsername(jdbcUsername);\n        config.setPassword(jdbcPassword);\n        config.addDataSourceProperty(\"autoCommit\", \"true\");\n        config.addDataSourceProperty(\"connectionTimeout\", \"5\");\n        config.addDataSourceProperty(\"idleTimeout\", \"60\");\n        return new HikariDataSource(config);\n    }\n\n    @Bean\n    JdbcTemplate createJdbcTemplate(@Autowired DataSource dataSource) {\n        return new JdbcTemplate(dataSource);\n    }\n}\n\n```\n\nУ\n\n1.  ͨ`@PropertySource(\"jdbc.properties\")`ȡݿļ\n2.  ͨ`@Value(\"${jdbc.url}\")`עļã\n3.  һDataSourceʵʵ`HikariDataSource`ʱҪõעã\n4.  һJdbcTemplateʵҪע`DataSource`ͨעġ\n\nHSQLDBдһļ`jdbc.properties`\n\n```\n# ݿļΪtestdb:\njdbc.url=jdbc:hsqldb:file:testdb\n\n# HsqldbĬϵûsaǿַ:\njdbc.username=sa\njdbc.password=\n\n```\n\nͨHSQLDBԴĹʼݿдһBeanSpringʱԶһ`users`\n\n```\n@Component\npublic class DatabaseInitializer {\n    @Autowired\n    JdbcTemplate jdbcTemplate;\n\n    @PostConstruct\n    public void init() {\n        jdbcTemplate.update(\"CREATE TABLE IF NOT EXISTS users (\" //\n                + \"id BIGINT IDENTITY NOT NULL PRIMARY KEY, \" //\n                + \"email VARCHAR(100) NOT NULL, \" //\n                + \"password VARCHAR(100) NOT NULL, \" //\n                + \"name VARCHAR(100) NOT NULL, \" //\n                + \"UNIQUE (email))\");\n    }\n}\n\n```\n\nڣ׼ϡֻҪҪݿBeanУע`JdbcTemplate`ɣ\n\n```\n@Component\npublic class UserService {\n    @Autowired\n    JdbcTemplate jdbcTemplate;\n    ...\n}\n\n```\n\n### JdbcTemplate÷\n\nSpringṩ`JdbcTemplate`TemplateģʽṩһϵԻصΪصĹ߷ĿǱⷱ`try...catch`䡣\n\nԾʾ˵JdbcTemplate÷\n\nǿ`T execute(ConnectionCallback<T> action)`ṩJdbc`Connection`ʹã\n\n```\npublic User getUserById(long id) {\n    // ע⴫ConnectionCallback:\n    return jdbcTemplate.execute((Connection conn) -> {\n        // ֱʹconnʵҪͷصJdbcTemplateԶͷ:\n        // ڲֶPreparedStatementResultSettry(...)ͷ:\n        try (var ps = conn.prepareStatement(\"SELECT * FROM users WHERE id = ?\")) {\n            ps.setObject(1, id);\n            try (var rs = ps.executeQuery()) {\n                if (rs.next()) {\n                    return new User( // new User object:\n                            rs.getLong(\"id\"), // id\n                            rs.getString(\"email\"), // email\n                            rs.getString(\"password\"), // password\n                            rs.getString(\"name\")); // name\n                }\n                throw new RuntimeException(\"user not found by id.\");\n            }\n        }\n    });\n}\n\n```\n\nҲ˵صȡConnectionȻκλConnectionĲ\n\nٿ`T execute(String sql, PreparedStatementCallback<T> action)`÷\n\n```\npublic User getUserByName(String name) {\n    // ҪSQL䣬ԼPreparedStatementCallback:\n    return jdbcTemplate.execute(\"SELECT * FROM users WHERE name = ?\", (PreparedStatement ps) -> {\n        // PreparedStatementʵѾJdbcTemplateڻصԶͷ:\n        ps.setObject(1, name);\n        try (var rs = ps.executeQuery()) {\n            if (rs.next()) {\n                return new User( // new User object:\n                        rs.getLong(\"id\"), // id\n                        rs.getString(\"email\"), // email\n                        rs.getString(\"password\"), // password\n                        rs.getString(\"name\")); // name\n            }\n            throw new RuntimeException(\"user not found by id.\");\n        }\n    });\n}\n\n```\n\nǿ`T queryForObject(String sql, RowMapper<T> rowMapper, Object... args)`\n\n```\npublic User getUserByEmail(String email) {\n    // SQLRowMapperʵ:\n    return jdbcTemplate.queryForObject(\"SELECT * FROM users WHERE email = ?\",\n            (ResultSet rs, int rowNum) -> {\n                // ResultSetĵǰӳΪһJavaBean:\n                return new User( // new User object:\n                        rs.getLong(\"id\"), // id\n                        rs.getString(\"email\"), // email\n                        rs.getString(\"password\"), // password\n                        rs.getString(\"name\")); // name\n            },\n            email);\n}\n\n```\n\n`queryForObject()`УSQLԼSQL`JdbcTemplate`Զ`PreparedStatement`Զִвѯ`ResultSet`ṩ`RowMapper`Ҫǰ`ResultSet`ĵǰӳһJavaBeanءУʹ`Connection``PreparedStatement``ResultSet`Ҫֶ\n\n`RowMapper`һJavaBeanʵԷκJava磬ʹ`SELECT COUNT(*)`ѯʱԷ`Long`\n\n```\npublic long getUsers() {\n    return jdbcTemplate.queryForObject(\"SELECT COUNT(*) FROM users\", (ResultSet rs, int rowNum) -> {\n        // SELECT COUNT(*)ѯֻһУȡһ:\n        return rs.getLong(1);\n    });\n}\n\n```\n\nضм¼һУ`query()`\n\n```\npublic List<User> getUsers(int pageIndex) {\n    int limit = 100;\n    int offset = limit * (pageIndex - 1);\n    return jdbcTemplate.query(\"SELECT * FROM users LIMIT ? OFFSET ?\",\n            new BeanPropertyRowMapper<>(User.class),\n            limit, offset);\n}\n\n```\n\n`query()`ĲȻSQLSQLԼ`RowMapper`ʵֱʹSpringṩ`BeanPropertyRowMapper`ݿĽṹǡúJavaBeanһ£ô`BeanPropertyRowMapper`ͿֱӰһм¼תΪJavaBean\n\nִеĲǲѯǲ롢ºɾôҪʹ`update()`\n\n```\npublic void updateUser(User user) {\n    // SQLSQLظµ:\n    if (1 != jdbcTemplate.update(\"UPDATE users SET name = ? WHERE id = ?\", user.getName(), user.getId())) {\n        throw new RuntimeException(\"User not found by id\");\n    }\n}\n\n```\n\nֻһ`INSERT`Ƚ⣬ǾĳһУͨҪȡֵ`JdbcTemplate`ṩһ`KeyHolder`һ\n\n```\npublic User register(String email, String password, String name) {\n    // һKeyHolder:\n    KeyHolder holder = new GeneratedKeyHolder();\n    if (1 != jdbcTemplate.update(\n        // 1:PreparedStatementCreator\n        (conn) -> {\n            // PreparedStatementʱָRETURN_GENERATED_KEYS:\n            var ps = conn.prepareStatement(\"INSERT INTO users(email, password, name) VALUES(?, ?, ?)\",\n                    Statement.RETURN_GENERATED_KEYS);\n            ps.setObject(1, email);\n            ps.setObject(2, password);\n            ps.setObject(3, name);\n            return ps;\n        },\n        // 2:KeyHolder\n        holder)\n    ) {\n        throw new RuntimeException(\"Insert failed.\");\n    }\n    // KeyHolderлȡصֵ:\n    return new User(holder.getKey().longValue(), email, password, name);\n}\n\n```\n\n`JdbcTemplate`طǲһһܡҪǿǣ`JdbcTemplate`ֻǶJDBCһ򵥷װĿǾֶд`try(resource) {...}`Ĵ룬ڲѯҪͨ`RowMapper`ʵJDBCJavaת\n\nܽһ`JdbcTemplate`÷Ǿǣ\n\n*   Լ򵥲ѯѡ`query()``queryForObject()`ΪֻṩSQL䡢`RowMapper`\n*   Ը²ѡ`update()`ΪֻṩSQLͲ\n*   κθӵĲҲͨ`execute(ConnectionCallback)`ʵ֣Ϊõ`Connection`ͿκJDBC\n\nʵʹȻǸֲѯƱṹʱܹJavaBeanһһӦôֱʹ`BeanPropertyRowMapper`ͺܷ㡣ṹJavaBeanһô죿ǾҪ΢дһ²ѯʹĽṹJavaBeanһ¡\n\n磬`office_address`JavaBean`workAddress`Ҫָдѯ£\n\n```\nSELECT id, email, office_address AS workAddress, name FROM users WHERE email = ?\n```\n\nʹ`JdbcTemplate`ʱõķ`List<T> query(String, RowMapper, Object...)``RowMapper`þǰ`ResultSet`һм¼ӳΪJava Bean\n\nְѹϵݿı¼ӳΪJavaĹ̾ORMObject-Relational MappingORMȿ԰Ѽ¼תJavaҲ԰JavaתΪм¼\n\nʹ`JdbcTemplate``RowMapper`ԿԭʼORMҪʵָԶORMѡORMܣ[Hibernate](https://hibernate.org/)\n\nSpringмHibernate\n\nHibernateΪORMܣ`JdbcTemplate`HibernateȻҪJDBCԣҪJDBCӳأԼHibernateMavenУǼ\n\n*   org.springframework:spring-context:6.0.0\n*   org.springframework:spring-orm:6.0.0\n*   jakarta.annotation:jakarta.annotation-api:2.1.1\n*   jakarta.persistence:jakarta.persistence-api:3.1.0\n*   org.hibernate:hibernate-core:6.1.4.Final\n*   com.zaxxer:HikariCP:5.0.1\n*   org.hsqldb:hsqldb:2.7.1\n\n`AppConfig`УȻҪ`DataSource`JDBCļԼʽ\n\n```\n@Configuration\n@ComponentScan\n@EnableTransactionManagement\n@PropertySource(\"jdbc.properties\")\npublic class AppConfig {\n    @Bean\n    DataSource createDataSource() {\n        ...\n    }\n}\n\n```\n\nΪHibernateҪһ`LocalSessionFactoryBean`\n\n```\npublic class AppConfig {\n    @Bean\n    LocalSessionFactoryBean createSessionFactory(@Autowired DataSource dataSource) {\n        var props = new Properties();\n        props.setProperty(\"hibernate.hbm2ddl.auto\", \"update\"); // Ҫʹ\n        props.setProperty(\"hibernate.dialect\", \"org.hibernate.dialect.HSQLDialect\");\n        props.setProperty(\"hibernate.show_sql\", \"true\");\n        var sessionFactoryBean = new LocalSessionFactoryBean();\n        sessionFactoryBean.setDataSource(dataSource);\n        // ɨָpackageȡentity class:\n        sessionFactoryBean.setPackagesToScan(\"com.itranswarp.learnjava.entity\");\n        sessionFactoryBean.setHibernateProperties(props);\n        return sessionFactoryBean;\n    }\n}\n\n```\n\nע[Bean](https://www.liaoxuefeng.com/wiki/1252599548343744/1308043627200545)н`FactoryBean``LocalSessionFactoryBean`һ`FactoryBean`Զһ`SessionFactory`HibernateУ`Session`ǷװһJDBC `Connection`ʵ`SessionFactory`ǷװJDBC `DataSource`ʵ`SessionFactory`ӳأÿҪݿʱ`SessionFactory`һµ`Session`൱ڴӳػȡһµ`Connection``SessionFactory`Hibernateṩĵһ󣬵`LocalSessionFactoryBean`SpringṩΪǷ㴴`SessionFactory`ࡣ\n\nע⵽洴`LocalSessionFactoryBean`Ĵ룬`Properties`Hibernateʼ`SessionFactory`ʱõãõο[Hibernateĵ](https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#configurations)ֻ3ã\n\n*   `hibernate.hbm2ddl.auto=update`ʾԶݿıṹעⲻҪã\n*   `hibernate.dialect=org.hibernate.dialect.HSQLDialect`ָʾHibernateʹõݿHSQLDBHibernateʹһHQLĲѯ䣬SQLƣڡ롱SQLʱ趨ݿ⡰ԡݿŻSQL\n*   `hibernate.show_sql=true`HibernateӡִеSQLڵԷǳãǿԷؿHibernateɵSQLǷǵԤڡ\n\n`DataSource``Properties`֮⣬ע⵽`setPackagesToScan()`Ǵһ`package`ƣָʾHibernateɨJava࣬ԶҳӳΪݿ¼JavaBeanǻϸαдHibernateҪJavaBean\n\nţǻҪ`HibernateTransactionManager`\n\n```\npublic class AppConfig {\n    @Bean\n    PlatformTransactionManager createTxManager(@Autowired SessionFactory sessionFactory) {\n        return new HibernateTransactionManager(sessionFactory);\n    }\n}\n\n```\n\n`HibernateTransactionManager`HibernateʹʽġΪֹеöϣνݿṹӳΪJava\n\nµݿ\n\n```\nCREATE TABLE user\n    id BIGINT NOT NULL AUTO_INCREMENT,\n    email VARCHAR(100) NOT NULL,\n    password VARCHAR(100) NOT NULL,\n    name VARCHAR(100) NOT NULL,\n    createdAt BIGINT NOT NULL,\n    PRIMARY KEY (`id`),\n    UNIQUE KEY `email` (`email`)\n);\n\n```\n\nУ`id``email``password``name``VARCHAR`ͣ`email`ΨһȷΨһԣ`createdAt`洢͵ʱJavaBeanʾ£\n\n```\npublic class User {\n    private Long id;\n    private String email;\n    private String password;\n    private String name;\n    private Long createdAt;\n\n    // getters and setters\n    ...\n}\n\n```\n\nӳϵʮ׶ҪһЩעHibernateΰ`User`ӳ䵽¼\n\n```\n@Entity\npublic class User {\n    @Id\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    @Column(nullable = false, updatable = false)\n    public Long getId() { ... }\n\n    @Column(nullable = false, unique = true, length = 100)\n    public String getEmail() { ... }\n\n    @Column(nullable = false, length = 100)\n    public String getPassword() { ... }\n\n    @Column(nullable = false, length = 100)\n    public String getName() { ... }\n\n    @Column(nullable = false, updatable = false)\n    public Long getCreatedAt() { ... }\n}\n\n```\n\nһJavaBeanӳ䣬Ǿͱһ`@Entity`Ĭ£ӳı`user`ʵʵıͬʵʱ`users`׷һ`@Table(name=\"users\")`ʾ\n\n```\n@Entity\n@Table(name=\"users)\npublic class User {\n    ...\n}\n\n```\n\nÿԵݿеӳ`@Column()`ʶ`nullable`ָʾǷΪ`NULL``updatable`ָʾǷ`UPDATE`䣬`length`ָʾ`String`͵еĳȣûָĬ`255`\n\nҪ`@Id`ʶ׷һ`@GeneratedValue`ԱHibernateܶȡֵ\n\nϸĵͯЬܻע⵽`id`Ͳ`long``Long`ΪHibernate⵽Ϊ`null`Ͳ`INSERT`ֵָǷݿɵֵHibernateΪǵĳֵָ`INSERT`ֱг`long`ֶǾĬֵ`0`ˣÿβֵ0³һ붼ʧܡ\n\n`createdAt`Ȼͣǲûʹ`long``Long`ΪʹûͻᵼfindByExampleѯֻμǣΪӳʹõJavaBeanԶʹðװͶǻ͡\n\nʹHibernateʱҪʹû͵ԣʹðװͣLongInteger\n\nƵģٶһ`Book`ࣺ\n\n```\n@Entity\npublic class Book {\n    @Id\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    @Column(nullable = false, updatable = false)\n    public Long getId() { ... }\n\n    @Column(nullable = false, length = 100)\n    public String getTitle() { ... }\n\n    @Column(nullable = false, updatable = false)\n    public Long getCreatedAt() { ... }\n}\n\n```\n\nϸ۲`User``Book`ᷢǶ`id``createdAt`һģݿṹкܳÿͨǻͳһʹһɻƣ`createdAt`ʾʱ䣬`updatedAt`ʾ޸ʱֶͨΡ\n\n`User``Book`ظЩֶͨΣǿ԰ᵽһУ\n\n```\n@MappedSuperclass\npublic abstract class AbstractEntity {\n\n    private Long id;\n    private Long createdAt;\n\n    @Id\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    @Column(nullable = false, updatable = false)\n    public Long getId() { ... }\n\n    @Column(nullable = false, updatable = false)\n    public Long getCreatedAt() { ... }\n\n    @Transient\n    public ZonedDateTime getCreatedDateTime() {\n        return Instant.ofEpochMilli(this.createdAt).atZone(ZoneId.systemDefault());\n    }\n\n    @PrePersist\n    public void preInsert() {\n        setCreatedAt(System.currentTimeMillis());\n    }\n}\n\n```\n\n`AbstractEntity`˵Ҫעһ`@MappedSuperclass`ʾڼ̳С⣬ע⵽Ƕһ`@Transient`һ⡱ԡΪ`getCreatedDateTime()`ǼóԣǴݿֵ˱Ҫע`@Transient`Hibernate᳢ԴݿȡΪ`createdDateTime`ڵֶδӶ\n\nע⵽`@PrePersist`ʶķʾǽһJavaBean־ûݿ֮ǰִINSERT䣩Hibernateִи÷ǾͿԶú`createdAt`ԡ\n\n`AbstractEntity`ǾͿԴ`User``Book`\n\n```\n@Entity\npublic class User extends AbstractEntity {\n\n    @Column(nullable = false, unique = true, length = 100)\n    public String getEmail() { ... }\n\n    @Column(nullable = false, length = 100)\n    public String getPassword() { ... }\n\n    @Column(nullable = false, length = 100)\n    public String getName() { ... }\n}\n\n```\n\nע⵽ʹõע`jakarta.persistence`JPA淶һֻ֡ʹעķʽHibernateӳϵٽܴͳıȽϷXMLáͨSpringHibernateʱҲҪ`hibernate.cfg.xml`ļһ仰ܽ᣺\n\nʹSpringHibernateJPAע⣬κζXMLá\n\n`User``Book`ORMJava Bean֮ͨΪEntity Bean\n\n`user`ɾĲ顣ΪʹHibernateˣҪģʵǶ`User`JavaBeanСɾĲ顱Ǳдһ`UserService`ע`SessionFactory`\n\n```\n@Component\n@Transactional\npublic class UserService {\n    @Autowired\n    SessionFactory sessionFactory;\n}\n\n```\n\n### Insert\n\nҪ־ûһ`User`ʵֻ`persist()``register()`Ϊ£\n\n```\npublic User register(String email, String password, String name) {\n    // һUser:\n    User user = new User();\n    // úø:\n    user.setEmail(email);\n    user.setPassword(password);\n    user.setName(name);\n    // ҪidΪʹ\n    // 浽ݿ:\n    sessionFactory.getCurrentSession().persist(user);\n    // ѾԶid:\n    System.out.println(user.getId());\n    return user;\n}\n\n```\n\n### Delete\n\nɾһ`User`൱ڴӱɾӦļ¼עHibernate`id`ɾ¼ˣҪȷ`User``id`Բɾ¼\n\n```\npublic boolean deleteUser(Long id) {\n    User user = sessionFactory.getCurrentSession().byId(User.class).load(id);\n    if (user != null) {\n        sessionFactory.getCurrentSession().remove(user);\n        return true;\n    }\n    return false;\n}\n\n```\n\nͨɾ¼ʱһ÷ȸظü¼ɾע⵽¼ʱ`load()``null`\n\n### Update\n\n¼¼൱ȸ`User`ָԣȻ`merge()`\n\n```\npublic void updateUser(Long id, String name) {\n    User user = sessionFactory.getCurrentSession().byId(User.class).load(id);\n    user.setName(name);\n    sessionFactory.getCurrentSession().merge(user);\n}\n\n```\n\nǰڶ`User`ʱеԱע`@Column(updatable=false)`Hibernateڸ¼¼ʱֻ`@Column(updatable=true)`Լ뵽`UPDATE`УṩһİȫԣС޸`User``email``createdAt`ԣִ`update()`ʱ¶ӦݿСҲμǣHibernateṩģƹHibernateֱͨJDBCִ`UPDATE`ȻԸݿеֵ\n\nǱдĴ󲿷ַǸָĲѯ`id`ѯǿֱӵ`load()`Ҫʹѯ磬ִ²ѯ\n\n```\nSELECT * FROM user WHERE email = ? AND password = ?\n\n```\n\nʹʲôѯ\n\n### ʹHQLѯ\n\nһֳõĲѯֱӱдHibernateõHQLѯ\n\n```\nList<User> list = sessionFactory.getCurrentSession()\n        .createQuery(\"from User u where u.email = ?1 and u.password = ?2\", User.class)\n        .setParameter(1, email).setParameter(2, password)\n        .list();\n\n```\n\nSQLȣHQLʹHibernateԶתΪʵʵıϸHQL﷨Բο[Hibernateĵ](https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#query-language)\n\n˿ֱӴHQLַ⣬Hibernateʹһ`NamedQuery`ѯ֣Ȼ󱣴עСʹ`NamedQuery`ʱҪ`User`ע\n\n```\n@NamedQueries(\n    @NamedQuery(\n        // ѯ:\n        name = \"login\",\n        // ѯ:\n        query = \"SELECT u FROM User u WHERE u.email = :e AND u.password = :pwd\"\n    )\n)\n@Entity\npublic class User extends AbstractEntity {\n    ...\n}\n\n```\n\nע⵽`NamedQuery``jakarta.persistence.NamedQuery`ֱӴHQLе㲻ͬǣռλʹ`:e``:pwd`\n\nʹ`NamedQuery`ֻҪѯͲ\n\n```\npublic User login(String email, String password) {\n    List<User> list = sessionFactory.getCurrentSession()\n        .createNamedQuery(\"login\", User.class) // NamedQuery\n        .setParameter(\"e\", email) // e\n        .setParameter(\"pwd\", password) // pwd\n        .list();\n    return list.isEmpty() ? null : list.get(0);\n}\n\n```\n\nֱдHQLʹ`NamedQuery`ӡǰ߿ڴֱ۵ؿѯ䣬߿`User`ͳһزѯ\n\nһǽSpringмHibernateHibernateǵһ㷺ʹõORMܣǺܶС黹˵JPAJava Persistence APIɶ\n\nJPA֮ǰҪע⵽JavaEE1999ͷˣServletJMS׼ƽ̨ͬJavaڷǳڱ׼УҸѽӿڶˣȻ󣬸ԻؼҸɻȥʵֽӿڣûͿڲͬĳṩĲƷѡ񣬻лΪûдʱֻҪýӿڣҪþĵײʵ֣JDBC\n\nJPAJavaEEһORM׼ʵʵHibernateûɶ𣬵ûʹJPAôõľ`jakarta.persistence`׼`org.hibernate`ĵΪJPAֻǽӿڣԣҪѡһʵֲƷJDBCӿںMySQLһ\n\nʹJPAʱҲȫѡHibernateΪײʵ֣ҲѡJPAṩ[EclipseLink](https://www.eclipse.org/eclipselink/)SpringJPAļɣ֧ѡHibernateEclipseLinkΪʵ֡ȻHibernateΪJPAʵΪӣʾJPAĻ÷\n\nʹHibernateһֻҪ\n\n*   org.springframework:spring-context:6.0.0\n*   org.springframework:spring-orm:6.0.0\n*   jakarta.annotation:jakarta.annotation-api:2.1.1\n*   jakarta.persistence:jakarta.persistence-api:3.1.0\n*   org.hibernate:hibernate-core:6.1.4.Final\n*   com.zaxxer:HikariCP:5.0.1\n*   org.hsqldb:hsqldb:2.7.1\n\nʵһڼHibernateȫһΪHibernateṩԼĽӿڣҲṩJPAӿڣJPAӿھ൱ͨJPAHibernate\n\nȻ`AppConfig`ʽ`DataSource`\n\n```\n@Configuration\n@ComponentScan\n@EnableTransactionManagement\n@PropertySource(\"jdbc.properties\")\npublic class AppConfig {\n    @Bean\n    DataSource createDataSource() { ... }\n}\n\n```\n\nʹHibernateʱҪһ`LocalSessionFactoryBean`Զһ`SessionFactory`ʹJPAҲƵģҲһ`LocalContainerEntityManagerFactoryBean`Զһ`EntityManagerFactory`\n\n```\n@Bean\npublic LocalContainerEntityManagerFactoryBean createEntityManagerFactory(@Autowired DataSource dataSource) {\n    var emFactory = new LocalContainerEntityManagerFactoryBean();\n    // עDataSource:\n    emFactory.setDataSource(dataSource);\n    // ɨָpackageȡentity class:\n    emFactory.setPackagesToScan(AbstractEntity.class.getPackageName());\n    // ʹHibernateΪJPAʵ:\n    emFactory.setJpaVendorAdapter(new HibernateJpaVendorAdapter());\n    // :\n    var props = new Properties();\n    props.setProperty(\"hibernate.hbm2ddl.auto\", \"update\"); // Ҫʹ\n    props.setProperty(\"hibernate.dialect\", \"org.hibernate.dialect.HSQLDialect\");\n    props.setProperty(\"hibernate.show_sql\", \"true\");\n    emFactory.setJpaProperties(props);\n    return emFactory;\n}\n\n```\n\n۲룬Ҫע`DataSource`趨Զɨ`package`⣬ҪָJPAṩ̣ʹSpringṩһ`HibernateJpaVendorAdapter`HibernateԼҪã`Properties`ʽע롣\n\nǻҪʵһ`JpaTransactionManager`ʵʽ\n\n```\n@Bean\nPlatformTransactionManager createTxManager(@Autowired EntityManagerFactory entityManagerFactory) {\n    return new JpaTransactionManager(entityManagerFactory);\n}\n\n```\n\nǾJPAȫʼЩͯЬܴ֪JPAҪ`persistence.xml`ļԼӵ`orm.xml`ļǸظߴңʹSpring+HibernateΪJPAʵ֣κļ\n\nEntity BeanúһȫͬȫAnnotationעֻľҵͨJPAӿڲݿ⡣\n\n`UserService`Ϊ˱ע`@Component``@Transactional`⣬Ҫעһ`EntityManager`ǲҪʹ`Autowired``@PersistenceContext`\n\n```\n@Component\n@Transactional\npublic class UserService {\n    @PersistenceContext\n    EntityManager em;\n}\n\n```\n\nǻعһJDBCHibernateJPAṩĽӿڣʵϣǵĹϵ£\n\n| JDBC | Hibernate | JPA |\n| --- | --- | --- |\n| DataSource | SessionFactory | EntityManagerFactory |\n| Connection | Session | EntityManager |\n\n`SessionFactory``EntityManagerFactory`൱`DataSource``Session``EntityManager`൱`Connection`ÿҪݿʱҪȡµ`Session``EntityManager`ٹرա\n\nǣע⵽`UserService`עĲ`EntityManagerFactory``EntityManager`ұע`@PersistenceContext`ѵʹJPA̲߳ͬһ`EntityManager`\n\nʵעĲ`EntityManager`һ`EntityManager`Ĵ࣬൱ڣ\n\n```\npublic class EntityManagerProxy implements EntityManager {\n    private EntityManagerFactory emf;\n}\n\n```\n\nSpringע`@PersistenceContext``EntityManager`ԶעôڱҪʱԶ`EntityManager`仰˵߳õ`EntityManager`Ȼͬһ࣬ôڲԲ̻ͬ߳ᴴͬ`EntityManager`ʵ\n\nܽһ£ע`@PersistenceContext``EntityManager`Ա̰߳ȫع\n\nˣ`UserService`ÿҵ񷽷ֱʹ`EntityManager`ͺܷ㡣ѯΪ\n\n```\npublic User getUserById(long id) {\n    User user = this.em.find(User.class, id);\n    if (user == null) {\n        throw new RuntimeException(\"User not found by id: \" + id);\n    }\n    return user;\n}\n\n```\n\nHQLѯƣJPAʹJPQLѯ﷨HQLࣺ\n\n```\npublic User fetchUserByEmail(String email) {\n    // JPQLѯ:\n    TypedQuery<User> query = em.createQuery(\"SELECT u FROM User u WHERE u.email = :e\", User.class);\n    query.setParameter(\"e\", email);\n    List<User> list = query.getResultList();\n    if (list.isEmpty()) {\n        return null;\n    }\n    return list.get(0);\n}\n\n```\n\nͬģJPAҲ֧`NamedQuery`ȸѯִٰ֣ѯ\n\n```\npublic User login(String email, String password) {\n    TypedQuery<User> query = em.createNamedQuery(\"login\", User.class);\n    query.setParameter(\"e\", email);\n    query.setParameter(\"pwd\", password);\n    List<User> list = query.getResultList();\n    return list.isEmpty() ? null : list.get(0);\n}\n\n```\n\n`NamedQuery`ͨעע`User`ϣĶһڵ`User`һ\n\n```\n@NamedQueries(\n    @NamedQuery(\n        name = \"login\",\n        query = \"SELECT u FROM User u WHERE u.email=:e AND u.password=:pwd\"\n    )\n)\n@Entity\npublic class User {\n    ...\n}\n\n```\n\nݿɾĵĲԷֱʹ`persist()``remove()``merge()`ΪEntity Beanʹ÷ǳ򵥣ﲻٶ\n\n#### MyBatis\n\n: 2022/11/16 21:07 / Ķ: 601258\n\n* * *\n\n\n\nʹHibernateJPAݿʱORMɵҪǰResultSetÿһбJava Bean߰Java BeanԶתINSERTUPDATEĲУӶʵORM\n\nORM֪֮ΰӳ䵽Java BeanΪJava Beanϸ㹻עΪԪݣORMܻȡJava Beanע󣬾֪ν˫ӳ䡣\n\nôORMθJava Bean޸ģԱ`update()`и±Ҫԣ\n\nʹ[Proxyģʽ](https://www.liaoxuefeng.com/wiki/1252599548343744/1281319432618017)ORMܶȡUserʵʵϲUser࣬Ǵ̳࣬User࣬ÿsetter˸д\n\n```\npublic class UserProxy extends User {\n    boolean _isNameChanged;\n\n    public void setName(String name) {\n        super.setName(name);\n        _isNameChanged = true;\n    }\n}\n\n```\n\nԸٵÿԵı仯\n\nһԶһϵʱֱͨgetterѯݿ⣺\n\n```\npublic class UserProxy extends User {\n    Session _session;\n    boolean _isNameChanged;\n\n    public void setName(String name) {\n        super.setName(name);\n        _isNameChanged = true;\n    }\n\n    /**\n     * ȡUserAddress:\n     */\n    public Address getAddress() {\n        Query q = _session.createQuery(\"from Address where userId = :userId\");\n        q.setParameter(\"userId\", this.getId());\n        List<Address> list = query.list();\n        return list.isEmpty() ? null : list(0);\n    }\n}\n\n```\n\nΪʵĲѯUserProxy뱣HibernateĵǰSessionǣύSessionԶرգʱٻȡ`getAddress()`޷ݿ⣬߻ȡĲһµݡˣORMAttached/Detached״̬ʾǰJava BeanSessionķΧڣSessionһ롱󡣺ܶѧ޷ȷ״̬仯߽磬ͻɴ`PersistentObjectException`쳣ʽ״̬ʹͨJava Beanڱøӡ\n\n⣬HibernateJPAΪʵּݶݿ⣬ʹHQLJPQLѯһתضݿSQL޷лݿ⣬һԶתܿ⣬SQLŻ鷳\n\nORMͨṩ˻棬һΪһͶ档һָһSessionΧڵĻ棬龰ǸѯʱβѯԷͬһʵ\n\n```\nUser user1 = session.load(User.class, 123);\nUser user2 = session.load(User.class, 123);\n\n```\n\nָSessionĻ棬һĬϹرգҪֶá漫ݵĲһԣԭSQLǳᵼĸ¡磺\n\n```\n// ߳1ȡ:\nUser user1 = session1.load(User.class, 123);\n...\n// һʱ߳2ȡ:\nUser user2 = session2.load(User.class, 123);\n\n```\n\nЧʱ̶߳ȡUserʵһģǣݿӦм¼ȫܱ޸ģ磺\n\n```\n-- û100:\nUPDATE users SET bonus = bonus + 100 WHERE createdAt <= ?\n\n```\n\nORM޷ж`id=123`ûǷܸ`UPDATE`Ӱ졣ǵݿֶ֧ͨӦó򣬴UPDATEִУORMܾ͸֪ˡ\n\nǰORM֮ܳΪȫԶORMܡ\n\nԱSpringṩJdbcTemplateORMȣҪм\n\n1.  ѯҪֶṩMapperʵԱResultSetÿһбΪJava\n2.  ɾĲĲбҪֶ룬UserʵΪ[user.id, user.name, user.email]бȽ鷳\n\nJdbcTemplateȷԣÿζȡһݿǻ棬ִеSQLȫȷģȱǴȽϷ`INSERT INTO users VALUES (?,?,?)`Ǹӡ\n\nԣȫԶORMHibernateдȫJdbcTemplate֮䣬һְԶORMֻResultSetԶӳ䵽Java BeanԶJava BeanԼдSQL[MyBatis](https://mybatis.org/)һְԶORMܡ\n\nSpringмMyBatis\n\nȣҪMyBatisΣSpringûHibernateöMyBatisļɣԣҪMyBatisٷԼһSpringɵĿ⣺\n\n*   org.mybatis:mybatis:3.5.11\n*   org.mybatis:mybatis-spring:3.0.0\n\nǰһȴ`DataSource`Ǳزٵģ\n\n```\n@Configuration\n@ComponentScan\n@EnableTransactionManagement\n@PropertySource(\"jdbc.properties\")\npublic class AppConfig {\n    @Bean\n    DataSource createDataSource() { ... }\n}\n\n```\n\nٻعһHibernateJPA`SessionFactory``EntityManagerFactory`MyBatis֮Ӧ`SqlSessionFactory``SqlSession`\n\n| JDBC | Hibernate | JPA | MyBatis |\n| --- | --- | --- | --- |\n| DataSource | SessionFactory | EntityManagerFactory | SqlSessionFactory |\n| Connection | Session | EntityManager | SqlSession |\n\nɼORM·ƵġʹMyBatisĺľǴ`SqlSessionFactory`Ҫ`SqlSessionFactoryBean`\n\n```\n@Bean\nSqlSessionFactoryBean createSqlSessionFactoryBean(@Autowired DataSource dataSource) {\n    var sqlSessionFactoryBean = new SqlSessionFactoryBean();\n    sqlSessionFactoryBean.setDataSource(dataSource);\n    return sqlSessionFactoryBean;\n}\n\n```\n\nΪMyBatisֱʹSpringʽˣʹJDBCһģ\n\n```\n@Bean\nPlatformTransactionManager createTxManager(@Autowired DataSource dataSource) {\n    return new DataSourceTransactionManager(dataSource);\n}\n\n```\n\nHibernateͬǣMyBatisʹMapperʵӳ䣬Mapperǽӿڡ`User`Ϊ`User``users`֮ӳ`UserMapper`д£\n\n```\npublic interface UserMapper {\n\t@Select(\"SELECT * FROM users WHERE id = #{id}\")\n\tUser getById(@Param(\"id\") long id);\n}\n\n```\n\nע⣺Mapper`JdbcTemplate``RowMapper`ĸǶ`users`ĽӿڷǶһ`User getById(long)`ѯҪӿڷҪȷдѯSQLע`@Select`ǡSQLκβ뷽ƶӦ磬idͨע`@Param()`Ϊ`id`SQLｫ滻ռλ`#{id}`\n\nжôÿֱSQLдӦռλɣ\n\n```\n@Select(\"SELECT * FROM users LIMIT #{offset}, #{maxResults}\")\nList<User> getAll(@Param(\"offset\") int offset, @Param(\"maxResults\") int maxResults);\n\n```\n\nע⣺MyBatisִвѯ󣬽ݷķԶResultSetÿһתΪUserʵתȻǰӦͬ򵥵ķʽǱдSELECTı\n\n```\n-- created_timecreatedAt:\nSELECT id, name, email, created_time AS createdAt FROM users\n\n```\n\nִINSERT΢鷳㣬ΪϣUserʵˣķӿ`@Insert`ע£\n\n```\n@Insert(\"INSERT INTO users (email, password, name, createdAt) VALUES (#{user.email}, #{user.password}, #{user.name}, #{user.createdAt})\")\nvoid insert(@Param(\"user\") User user);\n\n```\n\nĲ`user`User࣬SQLõʱ`#{obj.property}`ķʽдռλHibernateȫԶORMȣMyBatisдINSERT䡣\n\n`users``id`ôSQLв`id`ϣȡҪټһ`@Options`ע⣺\n\n```\n@Options(useGeneratedKeys = true, keyProperty = \"id\", keyColumn = \"id\")\n@Insert(\"INSERT INTO users (email, password, name, createdAt) VALUES (#{user.email}, #{user.password}, #{user.name}, #{user.createdAt})\")\nvoid insert(@Param(\"user\") User user);\n\n```\n\n`keyProperty``keyColumn`ֱָJavaBeanԺݿ\n\nִ`UPDATE``DELETE`ԱȽϼ򵥣Ƕ巽£\n\n```\n@Update(\"UPDATE users SET name = #{user.name}, createdAt = #{user.createdAt} WHERE id = #{user.id}\")\nvoid update(@Param(\"user\") User user);\n\n@Delete(\"DELETE FROM users WHERE id = #{id}\")\nvoid deleteById(@Param(\"id\") long id);\n\n```\n\n`UserMapper`ӿڣҪӦʵִЩݿķȻԼдʵ࣬ǳ˱д`UserMapper`ӿ⣬`BookMapper``BonusMapper`һһд̫鷳ˣMyBatisṩһ`MapperFactoryBean`ԶMapperʵࡣһ򵥵ע\n\n```\n@MapperScan(\"com.itranswarp.learnjava.mapper\")\n...ע...\npublic class AppConfig {\n    ...\n}\n\n```\n\n`@MapperScan`ͿMyBatisԶɨָMapperʵࡣҵ߼Уǿֱע룺\n\n```\n@Component\n@Transactional\npublic class UserService {\n    // עUserMapper:\n    @Autowired\n    UserMapper userMapper;\n\n    public User getUserById(long id) {\n        // Mapper:\n        User user = userMapper.getById(id);\n        if (user == null) {\n            throw new RuntimeException(\"User not found by id.\");\n        }\n        return user;\n    }\n}\n\n```\n\nɼҵ߼Ҫͨ`XxxMapper`ݿⷽݿ⡣\n\n### XML\n\nSpringмMyBatisķʽֻҪõע⣬ûκXMLļMyBatisҲʹXMLӳϵSQL䣬磬`User`ʱֵ춯̬SQL\n\n```\n<update id=\"updateUser\">\n  UPDATE users SET\n  <set>\n    <if test=\"user.name != null\"> name = #{user.name} </if>\n    <if test=\"user.hobby != null\"> hobby = #{user.hobby} </if>\n    <if test=\"user.summary != null\"> summary = #{user.summary} </if>\n  </set>\n  WHERE id = #{user.id}\n</update>\n\n```\n\nдXMLõŵǿװ̬SQLҰSQLһȱ̫÷ʱ鿴SQLҪλXMLСǲXML÷ʽҪ˽ͯЬĶ[ٷĵ](https://mybatis.org/mybatis-3/zh/configuration.html)\n\nʹMyBatisSQLҪȫдŵִеSQLԼдSQLSQLŻǳ򵥣ҲԱд⸴ӵSQLʹݿض﷨лݿܾͲ̫סϢǴ󲿷ĿûлݿȫĳݿдŻSQL\n\n# ο\nhttps://www.w3cschool.cn/wkspring\nhttps://www.runoob.com/w3cnote/basic-knowledge-summary-of-spring.html\nhttp://codepub.cn/2015/06/21/Basic-knowledge-summary-of-Spring\nhttps://dunwu.github.io/spring-tutorial\nhttps://mszlu.com/java/spring\nhttp://c.biancheng.net/spring/aop-module.html"
  },
  {
    "path": "docs/Spring全家桶/Spring/Spring中对于校验功能的支持.md",
    "content": "# Spring У\n\nJava API 淶(`JSR303`)`Bean`Уı׼`validation-api`ûṩʵ֡`hibernate validation`Ƕ淶ʵ֣Уע`@Email``@Length`ȡ`Spring Validation`Ƕ`hibernate validation`Ķηװ֧`spring mvc`ԶУ顣\n\n## [#](https://dunwu.github.io/spring-tutorial/pages/fe6aad/#%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8)\n\n### [#](https://dunwu.github.io/spring-tutorial/pages/fe6aad/#%E5%BC%95%E5%85%A5%E4%BE%9D%E8%B5%96)\n\n spring-boot 汾С 2.3.xspring-boot-starter-web Զ hibernate-validator  spring-boot 汾 2.3.xҪֶ\n\n\n\n```\n<dependency>\n  <groupId>org.hibernate.validator</groupId>\n  hibernate-validator-parent\n  <version>6.2.5.Final</version>\n</dependency>\n\n```\n\n\n\n web ˵ΪֹǷҵӰ죬 Controller һҪУģ󲿷£Ϊʽ\n\n*   POSTPUT ʹ requestBody ݲ\n*   GET ʹ requestParam/PathVariable ݲ\n\nʵϣ requestBody У黹ǷУ飬նǵ Hibernate Validator ִУ飬Spring Validation ֻһװ\n\n### [#](https://dunwu.github.io/spring-tutorial/pages/fe6aad/#%E6%A0%A1%E9%AA%8C%E7%A4%BA%E4%BE%8B)Уʾ\n\n1ʵϱУע\n\n\n\n```\n@Data\n@NoArgsConstructor\n@AllArgsConstructor\npublic class User implements Serializable {\n\n    @NotNull\n    private Long id;\n\n    @NotBlank\n    @Size(min = 2, max = 10)\n    private String name;\n\n    @Min(value = 1)\n    @Max(value = 100)\n    private Integer age;\n\n}\n\n```\n\n\n\n2ڷУע\n\n\n\n```\n@Slf4j\n@Validated\n@RestController\n@RequestMapping(\"validate1\")\npublic class ValidatorController {\n\n    /**\n     * {@link RequestBody} У\n     */\n    @PostMapping(value = \"save\")\n    public DataResult<Boolean> save(@Valid @RequestBody User entity) {\n        log.info(\"һ¼{}\", JSONUtil.toJsonStr(entity));\n        return DataResult.ok(true);\n    }\n\n    /**\n     * {@link RequestParam} У\n     */\n    @GetMapping(value = \"queryByName\")\n    public DataResult<User> queryByName(\n        @RequestParam(\"username\")\n        @NotBlank\n        @Size(min = 2, max = 10)\n        String name\n    ) {\n        User user = new User(1L, name, 18);\n        return DataResult.ok(user);\n    }\n\n    /**\n     * {@link PathVariable} У\n     */\n    @GetMapping(value = \"detail/{id}\")\n    public DataResult<User> detail(@PathVariable(\"id\") @Min(1L) Long id) {\n        User user = new User(id, \"\", 18);\n        return DataResult.ok(user);\n    }\n\n}\n\n```\n\n\n\n3У׳ `ConstraintViolationException`  `MethodArgumentNotValidException` 쳣\n\n### [#](https://dunwu.github.io/spring-tutorial/pages/fe6aad/#%E7%BB%9F%E4%B8%80%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86)ͳһ쳣\n\nʵĿУͨͳһ쳣һѺõʾ\n\n\n\n```\n@Slf4j\n@ControllerAdvice\npublic class GlobalExceptionHandler {\n\n    /**\n     * в֪쳣\n     */\n    @ResponseBody\n    @ResponseStatus(HttpStatus.OK)\n    @ExceptionHandler(Throwable.class)\n    public Result handleException(Throwable e) {\n        log.error(\"δ֪쳣\", e);\n        return new Result(ResultStatus.HTTP_SERVER_ERROR.getCode(), e.getMessage());\n    }\n\n    /**\n     * ͳһУ쳣(ͨ)\n     *\n     * @param e ConstraintViolationException\n     * @return {@link DataResult}\n     */\n    @ResponseBody\n    @ResponseStatus(HttpStatus.BAD_REQUEST)\n    @ExceptionHandler({ ConstraintViolationException.class })\n    public Result handleConstraintViolationException(final ConstraintViolationException e) {\n        log.error(\"ConstraintViolationException\", e);\n        List<String> errors = new ArrayList<>();\n        for (ConstraintViolation<?> violation : e.getConstraintViolations()) {\n            Path path = violation.getPropertyPath();\n            List<String> pathArr = StrUtil.split(path.toString(), ',');\n            errors.add(pathArr.get(0) + \" \" + violation.getMessage());\n        }\n        return new Result(ResultStatus.REQUEST_ERROR.getCode(), CollectionUtil.join(errors, \",\"));\n    }\n\n    /**\n     * У쳣\n     *\n     * @param e MethodArgumentNotValidException\n     * @return {@link DataResult}\n     */\n    @ResponseBody\n    @ResponseStatus(HttpStatus.BAD_REQUEST)\n    @ExceptionHandler({ MethodArgumentNotValidException.class })\n    private Result handleMethodArgumentNotValidException(final MethodArgumentNotValidException e) {\n        log.error(\"MethodArgumentNotValidException\", e);\n        List<String> errors = new ArrayList<>();\n        for (ObjectError error : e.getBindingResult().getAllErrors()) {\n            errors.add(((FieldError) error).getField() + \" \" + error.getDefaultMessage());\n        }\n        return new Result(ResultStatus.REQUEST_ERROR.getCode(), CollectionUtil.join(errors, \",\"));\n    }\n\n}\n\n```\n\n\n\n## [#](https://dunwu.github.io/spring-tutorial/pages/fe6aad/#%E8%BF%9B%E9%98%B6%E4%BD%BF%E7%94%A8)ʹ\n\n### [#](https://dunwu.github.io/spring-tutorial/pages/fe6aad/#%E5%88%86%E7%BB%84%E6%A0%A1%E9%AA%8C)У\n\nʵĿУܶҪʹͬһ DTO ղͬУܿǲһġʱ򣬼򵥵 DTO ֶϼԼע޷⡣ˣspring-validation ֧˷УĹܣר⡣\n\n1\n\n\n\n```\n@Target({ ElementType.FIELD, ElementType.PARAMETER })\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface AddCheck { }\n\n@Target({ ElementType.FIELD, ElementType.PARAMETER })\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface EditCheck { }\n\n```\n\n\n\n2ʵϱУע\n\n\n\n```\n@Data\npublic class User2 {\n\n    @NotNull(groups = EditCheck.class)\n    private Long id;\n\n    @NotNull(groups = { AddCheck.class, EditCheck.class })\n    @Size(min = 2, max = 10, groups = { AddCheck.class, EditCheck.class })\n    private String name;\n\n    @IsMobile(message = \"Чֻ\", groups = { AddCheck.class, EditCheck.class })\n    private String mobile;\n\n}\n\n```\n\n\n\n3ڷϸݲͬУ\n\n\n\n```\n@Slf4j\n@Validated\n@RestController\n@RequestMapping(\"validate2\")\npublic class ValidatorController2 {\n\n    /**\n     * {@link RequestBody} У\n     */\n    @PostMapping(value = \"add\")\n    public DataResult<Boolean> add(@Validated(AddCheck.class) @RequestBody User2 entity) {\n        log.info(\"һ¼{}\", JSONUtil.toJsonStr(entity));\n        return DataResult.ok(true);\n    }\n\n    /**\n     * {@link RequestBody} У\n     */\n    @PostMapping(value = \"edit\")\n    public DataResult<Boolean> edit(@Validated(EditCheck.class) @RequestBody User2 entity) {\n        log.info(\"༭һ¼{}\", JSONUtil.toJsonStr(entity));\n        return DataResult.ok(true);\n    }\n\n}\n\n```\n\n\n\n### [#](https://dunwu.github.io/spring-tutorial/pages/fe6aad/#%E5%B5%8C%E5%A5%97%E6%A0%A1%E9%AA%8C)ǶУ\n\nǰʾУDTO ֶζǻͺ String ͡ʵʳУпĳֶҲһȣʹǶУ顣 post 磬汣 User Ϣʱͬʱ Job ϢҪעǣʱ DTO ĶӦֶα@Valid ע⡣\n\n\n\n```\n@Data\npublic class UserDTO {\n\n    @Min(value = 10000000000000000L, groups = Update.class)\n    private Long userId;\n\n    @NotNull(groups = {Save.class, Update.class})\n    @Length(min = 2, max = 10, groups = {Save.class, Update.class})\n    private String userName;\n\n    @NotNull(groups = {Save.class, Update.class})\n    @Length(min = 6, max = 20, groups = {Save.class, Update.class})\n    private String account;\n\n    @NotNull(groups = {Save.class, Update.class})\n    @Length(min = 6, max = 20, groups = {Save.class, Update.class})\n    private String password;\n\n    @NotNull(groups = {Save.class, Update.class})\n    @Valid\n    private Job job;\n\n    @Data\n    public static class Job {\n\n        @Min(value = 1, groups = Update.class)\n        private Long jobId;\n\n        @NotNull(groups = {Save.class, Update.class})\n        @Length(min = 2, max = 10, groups = {Save.class, Update.class})\n        private String jobName;\n\n        @NotNull(groups = {Save.class, Update.class})\n        @Length(min = 2, max = 10, groups = {Save.class, Update.class})\n        private String position;\n    }\n\n    /**\n     * ʱУ\n     */\n    public interface Save {\n    }\n\n    /**\n     * µʱУ\n     */\n    public interface Update {\n    }\n}\nƴ\n\n```\n\n\n\nǶУԽϷУһʹáоǶ׼УԼÿһУ飬`List<Job>`ֶλ list ÿһ Job 󶼽У\n\n### [#](https://dunwu.github.io/spring-tutorial/pages/fe6aad/#%E8%87%AA%E5%AE%9A%E4%B9%89%E6%A0%A1%E9%AA%8C%E6%B3%A8%E8%A7%A3)ԶУע\n\n1ԶУע `@IsMobile`\n\n\n\n```\n@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })\n@Retention(RUNTIME)\n@Constraint(validatedBy = MobileValidator.class)\npublic @interface IsMobile {\n\n    String message();\n\n    Class<?>[] groups() default {};\n\n    Class<? extends Payload>[] payload() default {};\n\n}\n\n```\n\n\n\n2ʵ `ConstraintValidator` ӿڣд `@IsMobile` УעĽ\n\n\n\n```\nimport cn.hutool.core.util.StrUtil;\nimport io.github.dunwu.spring.core.validation.annotation.IsMobile;\nimport io.github.dunwu.tool.util.ValidatorUtil;\n\nimport javax.validation.ConstraintValidator;\nimport javax.validation.ConstraintValidatorContext;\n\npublic class MobileValidator implements ConstraintValidator<IsMobile, String> {\n\n    @Override\n    public void initialize(IsMobile isMobile) { }\n\n    @Override\n    public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {\n        if (StrUtil.isBlank(s)) {\n            return false;\n        } else {\n            return ValidatorUtil.isMobile(s);\n        }\n    }\n\n}\n\n```\n\n\n\n### [#](https://dunwu.github.io/spring-tutorial/pages/fe6aad/#%E8%87%AA%E5%AE%9A%E4%B9%89%E6%A0%A1%E9%AA%8C)ԶУ\n\nͨʵ `org.springframework.validation.Validator` ӿԶУ顣\n\nҪ\n\n*   ʵ `supports` \n*   ʵ `validate` \n    *   ͨ `Errors` ռ\n        *   `ObjectError`Bean\n        *   `FieldError`BeanԣProperty\n    *   ͨ `ObjectError`  `FieldError`  `MessageSource` ʵֻȡյĴİ\n\n\n\n```\npackage io.github.dunwu.spring.core.validation;\n\nimport io.github.dunwu.spring.core.validation.annotation.Valid;\nimport io.github.dunwu.spring.core.validation.config.CustomValidatorConfig;\nimport io.github.dunwu.spring.core.validation.entity.Person;\nimport org.springframework.stereotype.Component;\nimport org.springframework.validation.Errors;\nimport org.springframework.validation.ValidationUtils;\nimport org.springframework.validation.Validator;\n\nimport java.lang.annotation.Annotation;\nimport java.lang.reflect.Field;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\n@Component\npublic class CustomValidator implements Validator {\n\n    private final CustomValidatorConfig validatorConfig;\n\n    public CustomValidator(CustomValidatorConfig validatorConfig) {\n        this.validatorConfig = validatorConfig;\n    }\n\n    /**\n     * Уֻ Person У\n     */\n    @Override\n    public boolean supports(Class<?> clazz) {\n        return Person.class.equals(clazz);\n    }\n\n    @Override\n    public void validate(Object target, Errors errors) {\n        ValidationUtils.rejectIfEmpty(errors, \"name\", \"name.empty\");\n\n        List<Field> fields = getFields(target.getClass());\n        for (Field field : fields) {\n            Annotation[] annotations = field.getAnnotations();\n            for (Annotation annotation : annotations) {\n                if (annotation.annotationType().getAnnotation(Valid.class) != null) {\n                    try {\n                        ValidatorRule validatorRule = validatorConfig.findRule(annotation);\n                        if (validatorRule != null) {\n                            validatorRule.valid(annotation, target, field, errors);\n                        }\n                    } catch (Exception e) {\n                        e.printStackTrace();\n                    }\n                }\n            }\n        }\n    }\n\n    private List<Field> getFields(Class<?> clazz) {\n        // Field\n        List<Field> fields = new ArrayList<>();\n        // classͲΪ\n        while (clazz != null) {\n            // Ե\n            Collections.addAll(fields, clazz.getDeclaredFields());\n            clazz = clazz.getSuperclass();\n        }\n        return fields;\n    }\n\n}\n\n```\n\n\n\n### [#](https://dunwu.github.io/spring-tutorial/pages/fe6aad/#%E5%BF%AB%E9%80%9F%E5%A4%B1%E8%B4%A5-fail-fast)ʧ(Fail Fast)\n\nSpring Validation ĬϻУֶΣȻ׳쳣ͨһЩ򵥵ã Fali Fast ģʽһУʧܾء\n\n\n\n```\n@Bean\npublic Validator validator() {\n    ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)\n            .configure()\n            // ʧģʽ\n            .failFast(true)\n            .buildValidatorFactory();\n    return validatorFactory.getValidator();\n}\n\n```\n\n\n\n## [#](https://dunwu.github.io/spring-tutorial/pages/fe6aad/#spring-%E6%A0%A1%E9%AA%8C%E5%8E%9F%E7%90%86)Spring Уԭ\n\n### [#](https://dunwu.github.io/spring-tutorial/pages/fe6aad/#spring-%E6%A0%A1%E9%AA%8C%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF)Spring Уʹó\n\n*   Spring У飨Validator\n*   Spring ݰ󶨣DataBinder\n*   Spring Web 󶨣WebDataBinder\n*   Spring WebMVC/WebFlux У\n\n### [#](https://dunwu.github.io/spring-tutorial/pages/fe6aad/#validator-%E6%8E%A5%E5%8F%A3%E8%AE%BE%E8%AE%A1)Validator ӿ\n\n*   ӿְ\n    *   Spring ڲУӿڣ̵ͨķʽУĿ\n*   ķ\n    *   `supports(Class)`УĿܷУ\n    *   `validate(Object,Errors)`УĿ󣬲Уʧܵ Errors \n*   \n    *   ռ`org.springframework.validation.Errors`\n    *   Validator ࣺ`org.springframework.validation.ValidationUtils`\n\n### [#](https://dunwu.github.io/spring-tutorial/pages/fe6aad/#errors-%E6%8E%A5%E5%8F%A3%E8%AE%BE%E8%AE%A1)Errors ӿ\n\n*   ӿְ\n    *   ݰ󶨺Уռӿڣ Java Bean ǿ\n*   ķ\n    *   `reject` أռİ\n    *   `rejectValue` أռֶеĴİ\n*   \n    *   Java Bean `org.springframework.validation.ObjectError`\n    *   Java Bean Դ`org.springframework.validation.FieldError`\n\n### [#](https://dunwu.github.io/spring-tutorial/pages/fe6aad/#errors-%E6%96%87%E6%A1%88%E6%9D%A5%E6%BA%90)Errors İԴ\n\nErrors İɲ\n\n*   ѡ Errors ʵ֣磺`org.springframework.validation.BeanPropertyBindingResult`\n*    reject  rejectValue \n*   ȡ Errors  ObjectError  FieldError\n*    ObjectError  FieldError е code  args MessageSource ʵ֣磺`ResourceBundleMessageSource`\n\n### [#](https://dunwu.github.io/spring-tutorial/pages/fe6aad/#spring-web-%E6%A0%A1%E9%AA%8C%E5%8E%9F%E7%90%86)spring web Уԭ\n\n#### [#](https://dunwu.github.io/spring-tutorial/pages/fe6aad/#requestbody-%E5%8F%82%E6%95%B0%E6%A0%A1%E9%AA%8C%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86)RequestBody Уʵԭ\n\n spring-mvc У`RequestResponseBodyMethodProcessor` ڽ `@RequestBody` עĲԼ`@ResponseBody` עķֵġУִвУ߼϶ڽķ `resolveArgument()` У\n\n\n\n```\n@Override\npublic Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,\n    NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {\n\n    parameter = parameter.nestedIfOptional();\n    Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());\n    String name = Conventions.getVariableNameForParameter(parameter);\n\n    if (binderFactory != null) {\n        WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);\n        if (arg != null) {\n            // ԽвУ\n            validateIfApplicable(binder, parameter);\n            if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {\n                // У׳ MethodArgumentNotValidException\n                throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());\n            }\n        }\n        if (mavContainer != null) {\n            mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());\n        }\n    }\n\n    return adaptArgumentIfNecessary(arg, parameter);\n}\n\n```\n\n\n\nԿresolveArgument() validateIfApplicable()вУ顣\n\n\n\n```\nprotected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {\n    // ȡע⣬ @RequestBody@Valid@Validated\n    Annotation[] annotations = parameter.getParameterAnnotations();\n    for (Annotation ann : annotations) {\n        // ȳԻȡ @Validated ע\n        Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);\n        // ע @ValidatedֱӿʼУ顣\n        // ûУôжϲǰǷ Valid ͷע⡣\n        if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith(\"Valid\")) {\n            Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));\n            Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});\n            // ִУ\n            binder.validate(validationHints);\n            break;\n        }\n    }\n}\n\n```\n\n\n\nϴ룬ͽ Spring Ϊʲôͬʱ֧ `@Validated``@Valid` ע⡣\n\nһ WebDataBinder.validate() ʵ֣\n\n\n\n```\n@Override\npublic void validate(Object target, Errors errors, Object... validationHints) {\n    if (this.targetValidator != null) {\n        processConstraintViolations(\n            // ˴ Hibernate Validator ִУ\n            this.targetValidator.validate(target, asValidationGroups(validationHints)), errors);\n    }\n}\n\n```\n\n\n\nͨ룬Կ Spring Уʵǻ Hibernate Validator ķװ\n\n#### [#](https://dunwu.github.io/spring-tutorial/pages/fe6aad/#%E6%96%B9%E6%B3%95%E7%BA%A7%E5%88%AB%E7%9A%84%E5%8F%82%E6%95%B0%E6%A0%A1%E9%AA%8C%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86)ĲУʵԭ\n\nSpring ָ֧ݷȥءУ飬ԭӦ AOP ˵ͨ `MethodValidationPostProcessor` ̬ע AOP 棬Ȼʹ `MethodValidationInterceptor` е㷽֯ǿ\n\n\n\n```\npublic class MethodValidationPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessorimplements InitializingBean {\n    @Override\n    public void afterPropertiesSet() {\n        // Ϊ @Validated ע Bean \n        Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true);\n        //  Advisor ǿ\n        this.advisor = new DefaultPointcutAdvisor(pointcut, createMethodValidationAdvice(this.validator));\n    }\n\n    //  Adviceʾһ\n    protected Advice createMethodValidationAdvice(@Nullable Validator validator) {\n        return (validator != null ? new MethodValidationInterceptor(validator) : new MethodValidationInterceptor());\n    }\n}\n\n```\n\n\n\nſһ `MethodValidationInterceptor`\n\n\n\n```\npublic class MethodValidationInterceptor implements MethodInterceptor {\n    @Override\n    public Object invoke(MethodInvocation invocation) throws Throwable {\n        // ǿķֱ\n        if (isFactoryBeanMetadataMethod(invocation.getMethod())) {\n            return invocation.proceed();\n        }\n        // ȡϢ\n        Class<?>[] groups = determineValidationGroups(invocation);\n        ExecutableValidator execVal = this.validator.forExecutables();\n        Method methodToValidate = invocation.getMethod();\n        Set<ConstraintViolation<Object>> result;\n        try {\n            // У飬ջίи Hibernate Validator У\n            result = execVal.validateParameters(\n                invocation.getThis(), methodToValidate, invocation.getArguments(), groups);\n        }\n        catch (IllegalArgumentException ex) {\n            ...\n        }\n        // 쳣ֱ׳\n        if (!result.isEmpty()) {\n            throw new ConstraintViolationException(result);\n        }\n        // ķ\n        Object returnValue = invocation.proceed();\n        // ԷֵУ飬ջίиHibernate ValidatorУ\n        result = execVal.validateReturnValue(invocation.getThis(), methodToValidate, returnValue, groups);\n        // 쳣ֱ׳\n        if (!result.isEmpty()) {\n            throw new ConstraintViolationException(result);\n        }\n        return returnValue;\n    }\n}\n\n```\n\n\n\nʵϣ requestBody У黹ǷУ飬նǵ Hibernate Validator ִУ飬Spring Validation ֻһװ\n\n## [#](https://dunwu.github.io/spring-tutorial/pages/fe6aad/#%E9%97%AE%E9%A2%98)\n\n**Spring ЩУ**\n\n*   `org.springframework.validation.Validator`\n*   ռ`org.springframework.validation.Errors`\n*   Java Bean `org.springframework.validation.ObjectError`\n*   Java Bean Դ`org.springframework.validation.FieldError`\n*   Bean Validation 䣺`org.springframework.validation.beanvalidation.LocalValidatorFactoryBean`\n\n## [#](https://dunwu.github.io/spring-tutorial/pages/fe6aad/#%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99)ο\n\n*   [Spring ٷĵ֮ Core Technologies(opens new window)](https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#beans)\n*   [С署 Spring ı˼롷(opens new window)](https://time.geekbang.org/course/intro/265)\n*   https://juejin.cn/post/6856541106626363399\n\n\n# ο\nhttps://www.w3cschool.cn/wkspring\nhttps://www.runoob.com/w3cnote/basic-knowledge-summary-of-spring.html\nhttp://codepub.cn/2015/06/21/Basic-knowledge-summary-of-Spring\nhttps://dunwu.github.io/spring-tutorial\nhttps://mszlu.com/java/spring\nhttp://c.biancheng.net/spring/aop-module.html"
  },
  {
    "path": "docs/Spring全家桶/Spring/Spring中的Environment环境变量.md",
    "content": "\n\n\n\nڰøٵĴ롢дϵͳ Spring Boot ȻΪ Java Ӧÿʵ׼ Spring Boot ṩڶУ**Զ**ǶһԣSpring Boot һΪԱԶɿ伴á߱ĳһܵ Bean£Զõ Bean պҵ󣬵ĳЩ£òظǣʱֻҪ͵ Bean ɣΪԶõ Bean `@ConditionalOnMissingBean`עΡ˵ǣֻ΢һЩϸڣĸĶ˿ں (server.port) Դ URL (spring.datasource.url) ѹûҪ`ServerProperties``DataSourceProperties` Bean Զõ Bean Spring Boot ΪԶõ Bean ṩ1000΢ԣҪʱֻҪڻвļ (application.properties/application.yml) нָɣ Spring Boot `Externalized Configuration` (⻯) ԡ\n\nȻⲿԴڻвļ֣ȤĶ߿Ķ Spring Boot ٷĵ Spring У`BeanFactory` Bean Ľɫ`Environment`ͬλΪһⲿԴеԶᱻӵ _Environment_ С**΢Ľ죬ⲿԴ_Disconf__Apollo_  _Nacos_ ȷֲʽģ Spring ĵ̣ҪףжȡȻᱻ׷ӵ _Environment_ **\n\n֮дƪ£`jasypt`һνӴ2018꣬ʱͺܺʵֶԼӽܵģҪʵôһҪϤ Bean ڡIoC չ (IoC Container Extension Points)  Spring Boot ̵֪ʶҪ _Environment_\n\n> jasypt ʮּ򵥡ͨ`jasypt-maven-plugin`һ maven ΪֵģȻ`ENC()`滻ֵɡ£\n>\n> ```\n> jasypt.encryptor.password=crimson_typhoon\n> \n> spring.datasource.url=jdbc:mysql://HOST:PORT/db_sql_boy?characterEncoding=UTF-8\n> spring.datasource.hikari.driver-class-name=com.mysql.cj.jdbc.Driver\n> spring.datasource.hikari.username=root\n> spring.datasource.hikari.password=ENC(qS8+DEIlHxvhPHgn1VaW3oHkn2twrmwNOHewWLIfquAXiCDBrKwvIhDoqalKyhIF)\n> ƴ\n> ```\n\n## 1 ʶ Environmnent\n\nʵʹУ _Environment_ 򽻵ĻᲢࣻҵ Bean ȷʵҪȡⲿԴеĳһֵֶ _Environment_ ע뵽ҵ Bean УҲֱʵ`EnvironmentAware`ӿڣõ _Environment_ ͵ Bean ʵ֮ͨ`getProperty()`ȡֵ_Environment_ ӿʾ\n\n```\npublic interface Environment extends PropertyResolver {\n    String[] getActiveProfiles();\n    String[] getDefaultProfiles();\n    boolean acceptsProfiles(Profiles profiles);\n}\n\npublic interface PropertyResolver {\n    boolean containsProperty(String key);\n    String getProperty(String key);\n    String getProperty(String key, String defaultValue);\n    <T> T getProperty(String key, Class<T> targetType);\n    <T> T getProperty(String key, Class<T> targetType, T defaultValue);\n    String resolvePlaceholders(String text);\n}\nƴ\n```\n\n**ҲҪ _Environment_  _getProperty()_ 󵼣ⲿԴеԲԵΪάȱӵ _Environment_ еģ`PropertySource`Ϊά**_PropertySource_ ǶԴƺ͸ԴһԵĳ`MapPropertySource`һ򵥵ʵ֣ͨ _Map<String, Object>_ صԡ_PropertySource_ £\n\n```\npublic abstract class PropertySource<T> {\n    protected final String name;\n    protected final T source;\n\n    public PropertySource(String name, T source) {\n        this.name = name;\n        this.source = source;\n    }\n\n    public String getName() { return this.name; }\n    public T getSource() { return this.source; }\n    public abstract Object getProperty(String name);\n}\nƴ\n```\n\n _PropertySource_ _PropertySource_ Ǿ߱ȡֵһġ\n\n####  ****getProperty()ڲִ߼****\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/QQ%E6%88%AA%E5%9B%BE20230416193319.jpg)\n\nһ㣬_Environment_ ʵлһ`PropertyResolver`͵ĳԱ _PropertyResolver_ ִ _getProperty()_ ߼_PropertyResolver_ ʵֻԱֱǣ`ConversionService``PropertySources`ȣ_PropertyResolver_  `PropertySources` е _PropertySource_ȡԭֵȻί _ConversionService_ ԭֵת (бҪĻ)**Ȼ PropertySource Ǿ߱ȡֵһģ߱ռλתм߱ PropertyResolver Ҳӡ֤һӣڼѧУûʲôмһ˵ģУǾټһ**\n\n####  ****PropertySourceڲ߼****\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/QQ%E6%88%AA%E5%9B%BE20230416193319.jpg)\n\n_Environment_ ʵг˳`PropertyResolver`͵ĳԱ⣬һ`MutablePropertySources`͵ĳԱṩֱӲ _MutablePropertySources_ ķֻͨ`getPropertySources()`ȡ _MutablePropertySources_ ʵȻ _MutablePropertySources_ е`addFirst()``addLast()``replace()`ȷȥ _PropertySource__MutablePropertySources_  _PropertySources_ Ψһһʵ࣬ͼʾ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230416193420.png)\n\nܵ˵_Environment_ Ƕ _PropertySource_  _Profile_ Ķ _Profile_ ĸӦóҪ𵽲ͬлʱһЩͨͬ磬Դ URL ڿͲԻͻ᲻һSpring 3.1汾ʼֻ֧ _Profile_ á\n\n**Profile in Spring 3.1**\n\n Spring 3.1汾ʱSpring Boot δ˵ʱ _Profile_ ԻЩ**覴**ģ覲褡Ҫڣͬһ͵ BeanΡһС覴ã\n\n```\n@Configuration(proxyBeanMethods = false)\npublic class DataSourceConfig {\n    @Bean\n    @Profile(\"dev\")\n    public DataSource devDataSource () {\n        return DataSourceBuilder.create()\n                .driverClassName(\"com.mysql.jdbc.Driver\")\n                .url(\"jdbc:mysql://DEV_HOST:PORT/db_sql_boy?characterEncoding=UTF-8\")\n                .username(\"dev\")\n                .password(\"dev\")\n                .build();\n    }\n\n    @Bean\n    @Profile(\"test\")\n    public DataSource testDataSource () {\n        return DataSourceBuilder.create()\n                .driverClassName(\"com.mysql.jdbc.Driver\")\n                .url(\"jdbc:mysql://TEST_HOST:PORT/db_sql_boy?characterEncoding=UTF-8\")\n                .username(\"test\")\n                .password(\"test\")\n                .build();\n    }\n}\nƴ\n```\n\n**Profile in Spring Boot**\n\nSpring Boot `@Profile`עӵˡٷп϶Ҳʶ _Profile in Spring 3.1_ 覴ã Spring Boot ĵһ汾 _(1.0.0.RELEASE)_ оȲ֧Ϊ _application.properties_  _application.yml_  _Profile_ ˡζһţ\n\n```\n@Configuration(proxyBeanMethods = false)\npublic class DataSourceConfig {\n    @Bean\n    public DataSource devDataSource (DataSourceProperties dataSourceProperties) {\n        return DataSourceBuilder.create()\n                .driverClassName(dataSourceProperties.getDriverClassName())\n                .url(dataSourceProperties.getUrl())\n                .username(dataSourceProperties.getUsername())\n                .password(dataSourceProperties.getPassword())\n                .build();\n    }\n}\nƴ\n```\n\n_application-dev.properties_ £\n\n```\nspring.datasource.url=jdbc:mysql://DEV_HOST:PORT/db_sql_boy?characterEncoding=UTF-8\nspring.datasource.hikari.driver-class-name=com.mysql.jdbc.Driver\nspring.datasource.hikari.password=dev\nspring.datasource.hikari.username=dev\nƴ\n```\n\n_application-test.properties_ £\n\n```\nspring.datasource.url=jdbc:mysql://TEST_HOST:PORT/db_sql_boy?characterEncoding=UTF-8\nspring.datasource.hikari.driver-class-name=com.mysql.jdbc.Driver\nspring.datasource.hikari.password=test\nspring.datasource.hikari.username=test\nƴ\n```\n\nԭ Spring 3.1  Spring Boot Уͨ`spring.profiles.active`Ϊ _Environment_ ָ _Profile__Environment_ Ĭϼ _Profile_ Ϊ`default`дԺһ⣺һ㣬`@Profile` עҪ _@Configuration_ ע _@Bean_ עʹã _spring.profiles.active_ ֵΪ _dev_ ʱôЩ _@Configuration_  _@Bean_ ע (û`@Profile`עӰ)  Bean ᱻΪ`BeanDefinition`ʵ𣿴ǻġ`ConfigurationClassPostProcessor` _@Configuration_ Ϊ _BeanDefinition_ڴ˹лִ`ConditionEvaluator``shouldSkip()`Ҫ£\n\n```\npublic class ConditionEvaluator {\n    public boolean shouldSkip(AnnotatedTypeMetadata metadata, ConfigurationCondition.ConfigurationPhase phase) {\n        if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {\n            return false;\n        }\n\n        if (phase == null) {\n            if (metadata instanceof AnnotationMetadata &&\n                    ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {\n                return shouldSkip(metadata, ConfigurationCondition.ConfigurationPhase.PARSE_CONFIGURATION);\n            }\n            return shouldSkip(metadata, ConfigurationCondition.ConfigurationPhase.REGISTER_BEAN);\n        }\n\n        List<Condition> conditions = new ArrayList<>();\n        for (String[] conditionClasses : getConditionClasses(metadata)) {\n            for (String conditionClass : conditionClasses) {\n                Condition condition = getCondition(conditionClass, this.context.getClassLoader());\n                conditions.add(condition);\n            }\n        }\n\n        AnnotationAwareOrderComparator.sort(conditions);\n\n        for (Condition condition : conditions) {\n            ConfigurationCondition.ConfigurationPhase requiredPhase = null;\n            if (condition instanceof ConfigurationCondition) {\n                requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();\n            }\n            if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n}\nƴ\n```\n\n`shouldSkip()`һ _if_ Ǵ𰸣`@Profile`ע`@Conditional(ProfileCondition.class)`Σһͷû`Condition`Ӱֱӷ`false`ˣǾǲ˼ඣ\n\n_Environment_ еЩ _PropertySource_ ɶðȻΪ _Bean_ ඣϻ˵ͼ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230416193443.png)\n\n> ǰ visio  processOn ͼһ draw.ioû뵽㣬ǿҰһ\n\n## 2 Environmnent ʼ\n\nҪ Spring Boot  _Environmnt_ оעЩ _PropertySource_λ`SpringApplication`е`run(String... args)`£\n\n```\npublic class SpringApplication {\n    public ConfigurableApplicationContext run(String... args) {\n        StopWatch stopWatch = new StopWatch();\n        stopWatch.start();\n        DefaultBootstrapContext bootstrapContext = createBootstrapContext();\n        ConfigurableApplicationContext context = null;\n        configureHeadlessProperty();\n        SpringApplicationRunListeners listeners = getRunListeners(args);\n        listeners.starting(bootstrapContext, this.mainApplicationClass);\n        try {\n            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);\n            ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);\n            configureIgnoreBeanInfo(environment);\n            Banner printedBanner = printBanner(environment);\n            context = createApplicationContext();\n            context.setApplicationStartup(this.applicationStartup);\n            prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);\n            refreshContext(context);\n            afterRefresh(context, applicationArguments);\n            stopWatch.stop();\n            if (this.logStartupInfo) {\n                new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);\n            }\n            listeners.started(context);\n            callRunners(context, applicationArguments);\n        } catch (Throwable ex) {\n            handleRunFailure(context, ex, listeners);\n            throw new IllegalStateException(ex);\n        }\n\n        try {\n            listeners.running(context);\n        } catch (Throwable ex) {\n            handleRunFailure(context, ex, null);\n            throw new IllegalStateException(ex);\n        }\n        return context;\n    }\n}\nƴ\n```\n\nԿ_Environmnt_ ĳʼ`refreshContext(context)`֮ǰɵģǺʵġ_run()_ ܸӣ뱾ϵ߼ֻ**һ**\n\n```\nprepareEnvironment(listeners, bootstrapContext, applicationArguments);\nƴ\n```\n\nֱ߼\n\n### 2.1 prepareEnvironment()\n\nȻݶ`prepareEnvironment()`ڣСһ\n\n```\npublic class SpringApplication {\n    private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,\n                                                       DefaultBootstrapContext bootstrapContext,\n                                                       ApplicationArguments applicationArguments) {\n        // 2.1.1\n        ConfigurableEnvironment environment = getOrCreateEnvironment();\n        // 2.1.2\n        configureEnvironment(environment, applicationArguments.getSourceArgs());\n        // 2.1.3\n        ConfigurationPropertySources.attach(environment);\n        // 2.1.4\n        listeners.environmentPrepared(bootstrapContext, environment);\n        DefaultPropertiesPropertySource.moveToEnd(environment);\n        bindToSpringApplication(environment);\n        ConfigurationPropertySources.attach(environment);\n        return environment;\n    }\n}\nƴ\n```\n\n#### 2.1.1 getOrCreateEnvironment()\n\n`getOrCreateEnvironment()`Ҫ𹹽 _Environment_ ʵǰӦǻ`ͬI/O`ģ͵ģ _Environment_ ѡ`ApplicationServletEnvironment`෴أǰӦǻ`첽I/O`ģ͵ģ _Environment_ ѡ`ApplicationReactiveWebEnvironment`ǹлǻ Spring MVC ӦãSpring MVC һ`Servlet API`֮ϡͬ I/O ģ͵ Java Web ܣ I/O ģζһ HTTP Ӧһ̣߳ÿһ HTTP ڸ߳ɴġ_ApplicationServletEnvironment_ ̳йϵͼʾ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230416193515.png)\n\nͼԿ _ApplicationServletEnvironment_ ൱Ӵִ _ApplicationServletEnvironment_ 췽ʱȻᴥ๹췽е߼**Ϊ**\n\n```\npublic abstract class AbstractEnvironment implements ConfigurableEnvironment {\n    public AbstractEnvironment() {\n        this(new MutablePropertySources());\n    }\n\n    protected AbstractEnvironment(MutablePropertySources propertySources) {\n        this.propertySources = propertySources;\n        // createPropertyResolver(propertySources)\n        // |___ ConfigurationPropertySources.createPropertyResolver(propertySources)\n        //      |___ new ConfigurationPropertySourcesPropertyResolver(propertySources)\n        this.propertyResolver = createPropertyResolver(propertySources);\n        customizePropertySources(propertySources);\n    }\n}\nƴ\n```\n\n```\npublic class StandardServletEnvironment extends StandardEnvironment implements ConfigurableWebEnvironment {\n    @Override\n    protected void customizePropertySources(MutablePropertySources propertySources) {\n        propertySources.addLast(new StubPropertySource(\"servletConfigInitParams\"));\n        propertySources.addLast(new StubPropertySource(\"servletContextInitParams\"));\n        super.customizePropertySources(propertySources);\n    }\n}\nƴ\n```\n\n```\npublic class StandardEnvironment extends AbstractEnvironment {\n    @Override\n    protected void customizePropertySources(MutablePropertySources propertySources) {\n        propertySources.addLast(\n                new PropertiesPropertySource(\"systemProperties\", (Map) System.getProperties()));\n        propertySources.addLast(\n                new SystemEnvironmentPropertySource(\"systemEnvironment\", (Map) System.getenv()));\n    }\n}\nƴ\n```\n\n _ApplicationServletEnvironment_ 췽ִУʱ _Environment_  _MutablePropertySources_ ͵ĳԱ`propertySources`Ѿ**** _PropertySource_ ˣǣ`servletConfigInitParams``servletContextInitParams``systemProperties``systemEnvironment`⣬ҲҪס _ApplicationServletEnvironment_ еҪԱ`MutablePropertySources``ConfigurationPropertySourcesPropertyResolver`\n\n#### 2.1.2 configureEnvironment()\n\n`configureEnvironment()`е߼Ҳܼ򵥹ȣΪ _Environment_ е _PropertySourcesPropertyResolver_ 趨 _ConversionService_Ȼ _Environment_ е _MutablePropertySources_ ׷һΪ`commandLineArgs` _PropertySource_ ʵעʹõ`addFirst()`ŶζΪ`commandLineArgs` _PropertySource_ ȼߵġҪ߼£\n\n```\npublic class SpringApplication {\n    protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {\n        if (this.addConversionService) {\n            environment.getPropertyResolver().setConversionService(new ApplicationConversionService());\n        }\n        if (this.addCommandLineProperties && args.length > 0) {\n            MutablePropertySources sources = environment.getPropertySources();\n            sources.addFirst(new SimpleCommandLinePropertySource(args));\n        }\n    }\n}\nƴ\n```\n\n`SimpleCommandLinePropertySource`\n\n```\npublic class SimpleCommandLinePropertySource extends CommandLinePropertySource<CommandLineArgs> {\n    public SimpleCommandLinePropertySource(String... args) {\n        // 丸๹췽Ϊsuper(\"commandLineArgs\", source)\n        super(new SimpleCommandLineArgsParser().parse(args));\n    }\n}\nƴ\n```\n\nвǱȽϳõģ Spring Boot Ӧʱв`java -jar app.jar --server.port=8088`\n\n#### 2.1.3 ConfigurationPropertySources.attach()\n\n`attach()`Ҫ _Environment_  _MutablePropertySources_ ͷλòһΪ`configurationProperties` _PropertySource_ ʵҪ߼£\n\n```\npublic final class ConfigurationPropertySources {\n    public static void attach(org.springframework.core.env.Environment environment) {\n        MutablePropertySources sources = ((ConfigurableEnvironment) environment).getPropertySources();\n        PropertySource<?> attached = getAttached(sources);\n        if (attached != null && attached.getSource() != sources) {\n            sources.remove(ATTACHED_PROPERTY_SOURCE_NAME);\n            attached = null;\n        }\n        if (attached == null) {\n            sources.addFirst(new ConfigurationPropertySourcesPropertySource(\"configurationProperties\", new SpringConfigurationPropertySources(sources)));\n        }\n    }\n\n    static PropertySource<?> getAttached(MutablePropertySources sources) {\n        return (sources != null) ? sources.get(\"configurationProperties\") : null;\n    }\n}\nƴ\n```\n\n߶˺þãѹûΪ`configurationProperties` _PropertySource_ ɶá󣬻ڹٷĵй`Relaxed Binding` (ɰ) в³ЩߡͨȽֱӡȣ _application.properties_ ׷һ`a.b.my-first-key=hello spring environment`Ȼͨ _Environment_ ȡֵ£\n\n```\n@SpringBootApplication\npublic class DemoApplication {\n    public static void main(String[] args) {\n        ConfigurableApplicationContext configurableApplicationContext = SpringApplication.run(DemoApplication.class, args);\n        ConfigurableWebEnvironment environment = (ConfigurableWebEnvironment)\n                configurableApplicationContext.getBean(Environment.class);\n        System.out.println(environment.getProperty(\"a.b.my-first-key\"));\n    }\n}\nƴ\n```\n\nӦú󣬿̨ӡ _hello spring environment_ Ԥġɵͨ`environment.getProperty(\"a.b.myfirstkey\")``environment.getProperty(\"a.b.my-firstkey\")`Ȼܹȡݡ`a.b.myfirstkey``a.b.my-firstkey`ļеƣֻƶѣȷ****ȤĶ߿ DEBUG еԭ\n\n#### 2.1.4 listeners.environmentPrepared()\n\núڰ壬λУҪ `environmentPrepared()`㲥һ`ApplicationEnvironmentPreparedEvent`¼`EnvironmentPostProcessorApplicationListener`Ӧ¼Ӧǵ͵**۲ģʽ**Ҫ£\n\n```\npublic class SpringApplicationRunListeners {\n    private final List<SpringApplicationRunListener> listeners;\n\n    void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {\n        doWithListeners(\"spring.boot.application.environment-prepared\",\n                (listener) -> listener.environmentPrepared(bootstrapContext, environment));\n    }\n\n    private void doWithListeners(String stepName, Consumer<SpringApplicationRunListener> listenerAction) {\n        StartupStep step = this.applicationStartup.start(stepName);\n        this.listeners.forEach(listenerAction);\n        step.end();\n    }\n}\n\npublic class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {\n    @Override\n    public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,\n                                    ConfigurableEnvironment environment) {\n        this.initialMulticaster.multicastEvent(\n                new ApplicationEnvironmentPreparedEvent(bootstrapContext, this.application, this.args, environment));\n    }\n}\n\npublic class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {\n    @Override\n    public void multicastEvent(ApplicationEvent event) {\n        multicastEvent(event, resolveDefaultEventType(event));\n    }\n\n    @Override\n    public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {\n        ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));\n        Executor executor = getTaskExecutor();\n        for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {\n            if (executor != null) {\n                executor.execute(() -> invokeListener(listener, event));\n            } else {\n                invokeListener(listener, event);\n            }\n        }\n    }\n}\nƴ\n```\n\nһ`EnvironmentPostProcessorApplicationListener`®ɽĿ\n\n```\npublic class EnvironmentPostProcessorApplicationListener implements SmartApplicationListener, Ordered {\n    @Override\n    public void onApplicationEvent(ApplicationEvent event) {\n        if (event instanceof ApplicationEnvironmentPreparedEvent) {\n            onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);\n        }\n        if (event instanceof ApplicationPreparedEvent) {\n            onApplicationPreparedEvent();\n        }\n        if (event instanceof ApplicationFailedEvent) {\n            onApplicationFailedEvent();\n        }\n    }\n    private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {\n        ConfigurableEnvironment environment = event.getEnvironment();\n        SpringApplication application = event.getSpringApplication();\n        for (EnvironmentPostProcessor postProcessor : getEnvironmentPostProcessors(application.getResourceLoader(), event.getBootstrapContext())) {\n            postProcessor.postProcessEnvironment(environment, application);\n        }\n    }\n}\nƴ\n```\n\n`EnvironmentPostProcessor` Spring Boot Ϊ _Environment_ չ㡣ùٷĵбȽϾһ仰_Allows for customization of the application's Environment prior to the application context being refreshed__EnvironmentPostProcessor_ һԽӿڣ£\n\n```\npublic interface EnvironmentPostProcessor {\n    void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application);\n}\nƴ\n```\n\n _EnvironmentPostProcessorApplicationListener_ ¼߼У`getEnvironmentPostProcessors`سе _EnvironmentPostProcessor_ һڲ߼\n\n```\npublic interface EnvironmentPostProcessorsFactory {\n    static EnvironmentPostProcessorsFactory fromSpringFactories(ClassLoader classLoader) {\n        return new ReflectionEnvironmentPostProcessorsFactory(\n                classLoader, \n                SpringFactoriesLoader.loadFactoryNames(EnvironmentPostProcessor.class, classLoader)\n        );\n    }\n}\nƴ\n```\n\n`SpringFactoriesLoader`һ̽\n\n```\npublic final class SpringFactoriesLoader {\n\n    public static final String FACTORIES_RESOURCE_LOCATION = \"META-INF/spring.factories\";\n\n    public static List<String> loadFactoryNames(Class<?> factoryType, ClassLoader classLoader) {\n        ClassLoader classLoaderToUse = classLoader;\n        if (classLoaderToUse == null) {\n            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();\n        }\n        String factoryTypeName = factoryType.getName();\n        return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());\n    }\n\n    private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {\n        Map<String, List<String>> result = cache.get(classLoader);\n        if (result != null) {\n            return result;\n        }\n\n        result = new HashMap<>();\n        try {\n            Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);\n            while (urls.hasMoreElements()) {\n                URL url = urls.nextElement();\n                UrlResource resource = new UrlResource(url);\n                Properties properties = PropertiesLoaderUtils.loadProperties(resource);\n                for (Map.Entry<?, ?> entry : properties.entrySet()) {\n                    String factoryTypeName = ((String) entry.getKey()).trim();\n                    String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String) entry.getValue());\n                    for (String factoryImplementationName : factoryImplementationNames) {\n                        result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())\n                                .add(factoryImplementationName.trim());\n                    }\n                }\n            }\n            result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()\n                    .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));\n            cache.put(classLoader, result);\n        } catch (IOException ex) {\n            throw new IllegalArgumentException(\"Unable to load factories from location [\" + FACTORIES_RESOURCE_LOCATION + \"]\", ex);\n        }\n        return result;\n    }\n}\nƴ\n```\n\n> **Spring SPI**\n>\n> > _SpringFactoriesLoader_ һ߼ Spring е`SPI`ƣֱ׵˵Ǵ`classpath`µ`META-INF/spring.factories` ļм _EnvironmentPostProcessor_ ͽԼʵֵ _EnvironmentPostProcessor_ ŵļоˡʵ`JDK`е`SPI`ƺƹ\n\nڵǰ汾Spring Boot 7 _EnvironmentPostProcessor_ ʵࡣȽϵ͵ķ¡\n\n**RandomValuePropertySourceEnvironmentPostProcessor**\n\n`RandomValuePropertySourceEnvironmentPostProcessor` _Environment_ ׷һΪ`random` _PropertySource_`RandomValuePropertySource`£\n\n```\npublic class RandomValuePropertySourceEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {\n    public static final int ORDER = Ordered.HIGHEST_PRECEDENCE + 1;\n    private final Log logger;\n\n    public RandomValuePropertySourceEnvironmentPostProcessor(Log logger) {\n        this.logger = logger;\n    }\n\n    @Override\n    public int getOrder() {\n        return ORDER;\n    }\n\n    @Override\n    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {\n        RandomValuePropertySource.addToEnvironment(environment, this.logger);\n    }\n}\nƴ\n```\n\nô _RandomValuePropertySource_ ɶأҪ磺`environment.getProperty(\"random.int(5,10)\")`Իȡһ`random.int`ΪԻȡһ _int_ ͵`random.long`ΪԻȡһ _long_ ͵`random.int(5,10)`ΪԻȡһ _[5, 10}_  _int_ ͵淨̽\n\n_SystemEnvironmentPropertySourceEnvironmentPostProcessor_\n\nǰ_Environment_ ѾһΪ`systemEnvironment` _PropertySource_`SystemEnvironmentPropertySource``SystemEnvironmentPropertySourceEnvironmentPostProcessor`ڽ _SystemEnvironmentPropertySource_ 滻Ϊ`OriginAwareSystemEnvironmentPropertySource`զе㡰ѿӷƨһ١ĸоأ\n\n```\npublic class SystemEnvironmentPropertySourceEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {\n    public static final int DEFAULT_ORDER = SpringApplicationJsonEnvironmentPostProcessor.DEFAULT_ORDER - 1;\n    private int order = DEFAULT_ORDER;\n\n    @Override\n    public int getOrder() {\n        return this.order;\n    }\n\n    @Override\n    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {\n        String sourceName = \"systemEnvironment\";\n        PropertySource<?> propertySource = environment.getPropertySources().get(sourceName);\n        if (propertySource != null) {\n            replacePropertySource(environment, sourceName, propertySource, application.getEnvironmentPrefix());\n        }\n    }\n    private void replacePropertySource(ConfigurableEnvironment environment, String sourceName,\n                                       PropertySource<?> propertySource, String environmentPrefix) {\n        Map<String, Object> originalSource = (Map<String, Object>) propertySource.getSource();\n        SystemEnvironmentPropertySource source = new OriginAwareSystemEnvironmentPropertySource(sourceName, originalSource, environmentPrefix);\n        environment.getPropertySources().replace(sourceName, source);\n    }\n}\nƴ\n```\n\n**SpringApplicationJsonEnvironmentPostProcessor**\n\nͨ`java -jar -Dspring.application.json={\"name\":\"duxiaotou\"} app.jar` Spring Boot Ӧõʱ򣬸ԻᱻԶӵ JVM ϵͳ (ʵ _-Dkey=value_ ʽԾ)Ч`System.setProperty(key, value)``SPRING_APPLICATION_JSON`һϵͳʱȻҲ`System.getenv()`г֡ǰᵽ`System.getProperties()``systemProperties`һ _PropertySource_`System.getenv()``systemEnvironment`һ _PropertySource_`SpringApplicationJsonEnvironmentPostProcessor`ڴ _PropertySource_ гȡ _spring.application.json_  _SPRING_APPLICATION_JSON_  _JSON_  _Environment_ ׷һΪ`spring.application.json` _PropertySource_`JsonPropertySource`\n\n**ConfigDataEnvironmentPostProcessor**\n\n`ConfigDataEnvironmentPostProcessor``optional:classpath:/``optional:classpath:/config/``optional:file:./``optional:file:./config/``optional:file:./config/*/`ЩĿ¼µ _application.properties_ ļسָ _spring.profiles.active_ĻͬʱҲὫЩĿ¼µ _application-{profile}.properties_ ļسգ_ConfigDataEnvironmentPostProcessor_  _Environment_ ׷`OriginTrackedMapPropertySource` _PropertySource_ λ _Environment_ β _application-{profile}.properties_  _OriginTrackedMapPropertySource_  _application.properties_  _OriginTrackedMapPropertySource_ ǰģһͦҪ\n\n## 3 jasypt ԭ\n\n> `jasypt``jasypt-spring-boot-starter`ǲͬдģֻΪ jasypt  Spring Boot ѡʵ\n\n_application.properties_ ļйԴһܺģ£\n\n```\nspring.datasource.hikari.password=ENC(4+t9a5QG8NkNdWVS6UjIX3dj18UtYRMqU6eb3wUKjivOiDHFLZC/RTK7HuWWkUtV)\nƴ\n```\n\n`HikariDataSource`󣬸 Bean  _password_ ֶεֵզͱΪܺ _qwe@1234_ һأȻSpring Boot Ϊ _Environment_ ṩ`EnvironmentPostProcessor`һչʵ͵컻գûʹ Spring еһ _IoC չ_`BeanFactoryPostProcessor`ҲȫԵģΪִе _BeanFactoryPostProcessor_ е`postProcessBeanFactory()`߼ʱֻ`BeanDefinition`ļأûʵ _BeanDefinition_ Ӧ Bean\n\n濴һ`EnableEncryptablePropertiesBeanFactoryPostProcessor`еݣ\n\n```\npublic class EnableEncryptablePropertiesBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered {\n\n    private final ConfigurableEnvironment environment;\n    private final EncryptablePropertySourceConverter converter;\n\n    public EnableEncryptablePropertiesBeanFactoryPostProcessor(ConfigurableEnvironment environment, EncryptablePropertySourceConverter converter) {\n        this.environment = environment;\n        this.converter = converter;\n    }\n\n    @Override\n    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {\n        MutablePropertySources propSources = environment.getPropertySources();\n        converter.convertPropertySources(propSources);\n    }\n\n    @Override\n    public int getOrder() {\n        return Ordered.LOWEST_PRECEDENCE - 100;\n    }\n}\nƴ\n```\n\nԴ _BeanFactoryPostProcessor_ `EncryptablePropertySourceConverter` _MutablePropertySources_ һתôתɶأ\n\nţ _EncryptablePropertySourceConverter_£\n\n```\npublic class EncryptablePropertySourceConverter {\n\n    public void convertPropertySources(MutablePropertySources propSources) {\n        propSources.stream()\n                .filter(ps -> !(ps instanceof EncryptablePropertySource))\n                .map(this::makeEncryptable)\n                .collect(toList())\n                .forEach(ps -> propSources.replace(ps.getName(), ps));\n    }\n\n    public <T> PropertySource<T> makeEncryptable(PropertySource<T> propertySource) {\n        if (propertySource instanceof EncryptablePropertySource \n                || skipPropertySourceClasses.stream().anyMatch(skipClass -> skipClass.equals(propertySource.getClass()))) {\n            return propertySource;\n        }\n        PropertySource<T> encryptablePropertySource = convertPropertySource(propertySource);\n        return encryptablePropertySource;\n    }\n\n    private <T> PropertySource<T> convertPropertySource(PropertySource<T> propertySource) {\n        PropertySource<T> encryptablePropertySource;\n        if (propertySource instanceof SystemEnvironmentPropertySource) {\n            encryptablePropertySource = (PropertySource<T>) new EncryptableSystemEnvironmentPropertySourceWrapper((SystemEnvironmentPropertySource) propertySource, propertyResolver, propertyFilter);\n        } else if (propertySource instanceof MapPropertySource) {\n            encryptablePropertySource = (PropertySource<T>) new EncryptableMapPropertySourceWrapper((MapPropertySource) propertySource, propertyResolver, propertyFilter);\n        } else if (propertySource instanceof EnumerablePropertySource) {\n            encryptablePropertySource = new EncryptableEnumerablePropertySourceWrapper<>((EnumerablePropertySource) propertySource, propertyResolver, propertyFilter);\n        } else {\n            encryptablePropertySource = new EncryptablePropertySourceWrapper<>(propertySource, propertyResolver, propertyFilter);\n        }\n        return encryptablePropertySource;\n    }\n}\nƴ\n```\n\nȻԭ _PropertySource_ תΪһ`EncryptablePropertySourceWrapper`϶ʵĽܣģ\n\n`EncryptablePropertySourceWrapper`£\n\n```\npublic class EncryptablePropertySourceWrapper<T> extends PropertySource<T> implements EncryptablePropertySource<T> {\n    private final CachingDelegateEncryptablePropertySource<T> encryptableDelegate;\n\n    public EncryptablePropertySourceWrapper(PropertySource<T> delegate, EncryptablePropertyResolver resolver, EncryptablePropertyFilter filter) {\n        super(delegate.getName(), delegate.getSource());\n        encryptableDelegate = new CachingDelegateEncryptablePropertySource<>(delegate, resolver, filter);\n    }\n\n    @Override\n    public Object getProperty(String name) {\n        return encryptableDelegate.getProperty(name);\n    }\n\n    @Override\n    public PropertySource<T> getDelegate() {\n        return encryptableDelegate;\n    }\n}\nƴ\n```\n\nʧûɶ߼ _getProperty_ ߼ίɸ`CachingDelegateEncryptablePropertySource`\n\nû취ֻܵ _CachingDelegateEncryptablePropertySource_ һ̽ˣ\n\n```\npublic class CachingDelegateEncryptablePropertySource<T> extends PropertySource<T> implements EncryptablePropertySource<T> {\n    private final PropertySource<T> delegate;\n    private final EncryptablePropertyResolver resolver;\n    private final EncryptablePropertyFilter filter;\n    private final Map<String, Object> cache;\n\n    public CachingDelegateEncryptablePropertySource(PropertySource<T> delegate, EncryptablePropertyResolver resolver, EncryptablePropertyFilter filter) {\n        super(delegate.getName(), delegate.getSource());\n        this.delegate = delegate;\n        this.resolver = resolver;\n        this.filter = filter;\n        this.cache = new HashMap<>();\n    }\n\n    @Override\n    public PropertySource<T> getDelegate() {\n        return delegate;\n    }\n\n    @Override\n    public Object getProperty(String name) {\n        if (cache.containsKey(name)) {\n            return cache.get(name);\n        }\n        synchronized (name.intern()) {\n            if (!cache.containsKey(name)) {\n                Object resolved = getProperty(resolver, filter, delegate, name);\n                if (resolved != null) {\n                    cache.put(name, resolved);\n                }\n            }\n            return cache.get(name);\n        }\n    }\n}\nƴ\n```\n\nڣ`EncryptablePropertySource`п˽ܵ߼У`EncryptablePropertyDetector`̽ǷҪܣҪͨжϸֵǷ`ENC()`\n\n```\npublic interface EncryptablePropertySource<T> extends OriginLookup<String> {\n    default Object getProperty(EncryptablePropertyResolver resolver, EncryptablePropertyFilter filter, PropertySource<T> source, String name) {\n        Object value = source.getProperty(name);\n        if (value != null && filter.shouldInclude(source, name) && value instanceof String) {\n            String stringValue = String.valueOf(value);\n            return resolver.resolvePropertyValue(stringValue);\n        }\n        return value;\n    }\n}\n\npublic class DefaultPropertyResolver implements EncryptablePropertyResolver {\n\n    private final Environment environment;\n    private StringEncryptor encryptor;\n    private EncryptablePropertyDetector detector;\n\n    @Override\n    public String resolvePropertyValue(String value) {\n        return Optional.ofNullable(value)\n                .map(environment::resolvePlaceholders)\n                .filter(detector::isEncrypted)\n                .map(resolvedValue -> {\n                    try {\n                        String unwrappedProperty = detector.unwrapEncryptedValue(resolvedValue.trim());\n                        String resolvedProperty = environment.resolvePlaceholders(unwrappedProperty);\n                        return encryptor.decrypt(resolvedProperty);\n                    } catch (EncryptionOperationNotPossibleException e) {\n                        throw new DecryptionException(\"Unable to decrypt property: \" + value + \" resolved to: \" + resolvedValue + \". Decryption of Properties failed,  make sure encryption/decryption \" +\n                                \"passwords match\", e);\n                    }\n                })\n                .orElse(value);\n    }\n}\nƴ\n```\n\n## 4 ܽ\n\nܽԵ־Ͳ˵ˣ˼Ȫӿˮ300֡ϣҼסڵǰ Spring Boot 汾У`ApplicationServletEnvironment` _Environment_սί`ConfigurationPropertySourcesPropertyResolver`ȥȡֵ\n\n\n\nߣԳСͷ\nӣhttps://juejin.cn/post/7098299623759937543\nԴϡ\nȨСҵתϵ߻Ȩҵתע\n\n# ο\n\nhttps://www.w3cschool.cn/wkspring\nhttps://www.runoob.com/w3cnote/basic-knowledge-summary-of-spring.html\nhttp://codepub.cn/2015/06/21/Basic-knowledge-summary-of-Spring\nhttps://dunwu.github.io/spring-tutorial\nhttps://mszlu.com/java/spring\nhttp://c.biancheng.net/spring/aop-module.html"
  },
  {
    "path": "docs/Spring全家桶/Spring/Spring中的事件处理机制.md",
    "content": "\n\n\n\n# Spring е¼\n\n\n\n2022-05-16 15:29 \n\n\n\n\n\n\n\n\n\n## Spring е¼\n\nѾ½ Spring ĺ **ApplicationContext** beans ڡ beans ʱApplicationContext ĳЩ͵¼磬ʱContextStartedEvent ֹͣʱContextStoppedEvent \n\nͨ ApplicationEvent  ApplicationListener ӿṩ ApplicationContext д¼һ bean ʵ ApplicationListenerôÿ ApplicationEvent  ApplicationContext ϣǸ bean ᱻ֪ͨ\n\nSpring ṩµı׼¼\n\n|  | Spring ¼ &  |\n| --- | --- |\n| 1 | **ContextRefreshedEvent**ApplicationContext ʼˢʱ¼Ҳ ConfigurableApplicationContext ӿʹ refresh()  |\n| 2 | **ContextStartedEvent**ʹ ConfigurableApplicationContext ӿе start()  ApplicationContext ʱ¼Եݿ⣬ڽܵ¼κֹͣӦó |\n| 3 | **ContextStoppedEvent**ʹ ConfigurableApplicationContext ӿе stop() ֹͣ ApplicationContext ʱ¼ڽܵ¼ҪĹ |\n| 4 | **ContextClosedEvent**ʹ ConfigurableApplicationContext ӿе close() ر ApplicationContext ʱ¼һѹرյĵĩˣܱˢ» |\n| 5 | **RequestHandledEvent**һ web-specific ¼ bean HTTP Ѿ |\n\n Spring ¼ǵ̵߳ģһ¼ֱҳеĽߵõĸϢý̱̽ˣ¼ʹãӦóʱӦע⡣\n\n## ¼\n\nΪ˼¼һ bean Ӧʵֻһ **onApplicationEvent()**  ApplicationListener ӿڡˣдһ¼δģԼοôִлĳЩ¼\n\nǡλʹ Eclipse IDEȻĲһ Spring Ӧó\n\n|  |  |\n| --- | --- |\n| 1 | һΪ SpringExample ĿڴĿ **src** ļдһ com.tutorialspoint |\n| 2 | ʹ Add External JARs ѡ Spring ⣬ͼ Spring Hello World Example ½ڡ |\n| 3 |  com.tutorialspoint д Java  HelloWorldCStartEventHandlerCStopEventHandler  MainApp |\n| 4 |  **src** ļд Bean ļ Beans.xml |\n| 5 | һǴ Java ļ Bean ļݣӦó򣬽ʾ |\n\n **HelloWorld.java** ļݣ\n\n```\npackage com.tutorialspoint;\npublic class HelloWorld {\n   private String message;\n   public void setMessage(String message){\n      this.message  = message;\n   }\n   public void getMessage(){\n      System.out.println(\"Your Message : \" + message);\n   }\n}\n```\n\n **CStartEventHandler.java** ļݣ\n\n```\npackage com.tutorialspoint;\nimport org.springframework.context.ApplicationListener;\nimport org.springframework.context.event.ContextStartedEvent;\npublic class CStartEventHandler \n   implements ApplicationListener<ContextStartedEvent>{\n   public void onApplicationEvent(ContextStartedEvent event) {\n      System.out.println(\"ContextStartedEvent Received\");\n   }\n}\n```\n\n **CStopEventHandler.java** ļݣ\n\n```\npackage com.tutorialspoint;\nimport org.springframework.context.ApplicationListener;\nimport org.springframework.context.event.ContextStoppedEvent;\npublic class CStopEventHandler \n   implements ApplicationListener<ContextStoppedEvent>{\n   public void onApplicationEvent(ContextStoppedEvent event) {\n      System.out.println(\"ContextStoppedEvent Received\");\n   }\n}\n```\n\n **MainApp.java** ļݣ\n\n```\npackage com.tutorialspoint;\n\nimport org.springframework.context.ConfigurableApplicationContext;\nimport org.springframework.context.support.ClassPathXmlApplicationContext;\n\npublic class MainApp {\n   public static void main(String[] args) {\n      ConfigurableApplicationContext context = \n      new ClassPathXmlApplicationContext(\"Beans.xml\");\n\n      // Let us raise a start event.\n      context.start();\n\n      HelloWorld obj = (HelloWorld) context.getBean(\"helloWorld\");\n\n      obj.getMessage();\n\n      // Let us raise a stop event.\n      context.stop();\n   }\n}\n```\n\nļ **Beans.xml** ļ\n\n```\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<beans xmlns=\"http://www.springframework.org/schema/beans\"\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"http://www.springframework.org/schema/beans\n    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd\">\n\n   <bean id=\"helloWorld\" class=\"com.tutorialspoint.HelloWorld\">\n      <property name=\"message\" value=\"Hello World!\"/>\n   </bean>\n\n   <bean id=\"cStartEventHandler\" \n         class=\"com.tutorialspoint.CStartEventHandler\"/>\n\n   <bean id=\"cStopEventHandler\" \n         class=\"com.tutorialspoint.CStopEventHandler\"/>\n\n</beans>\n```\n\nһ˴Դ bean ļǾͿиӦóӦóһжϢ\n\n```\nContextStartedEvent Received\nYour Message : Hello World!\nContextStoppedEvent Received\n```\n\n\n\n\n\n\n\n## Spring еԶ¼\n\nдͷԼԶ¼ಽ衣һ¸˵дʹԶ Spring ¼\n\n|  |  |\n| --- | --- |\n| 1 | һΪ SpringExample ĿڴĿ **src** ļдһ com.tutorialspoint |\n| 2 | ʹ Add External JARs ѡ Spring ⣬ͼ Spring Hello World Example ½ڡ |\n| 3 | ͨչ **ApplicationEvent**,һ¼ CustomEvent붨һĬϵĹ캯Ӧô ApplicationEvent м̳еĹ캯 |\n| 4 | һ¼࣬Դκзٶ EventClassPublisher ʵ ApplicationEventPublisherAware㻹Ҫ XML ļΪһ bean֮ʶ bean Ϊ¼ߣΪʵ ApplicationEventPublisherAware ӿڡ |\n| 5 | ¼һбٶ EventClassHandler ʵ ApplicationListener ӿڣʵԶ¼ onApplicationEvent  |\n| 6 |  **src** ļд bean ļ Beans.xml  MainApp ࣬Ϊһ Spring ӦóС |\n| 7 | һǴ Java ļ Bean ļݣӦó򣬽ʾ |\n\n **CustomEvent.java** ļݣ\n\n```\npackage com.tutorialspoint;\nimport org.springframework.context.ApplicationEvent;\npublic class CustomEvent extends ApplicationEvent{ \n   public CustomEvent(Object source) {\n      super(source);\n   }\n   public String toString(){\n      return \"My Custom Event\";\n   }\n}\n\n```\n\n **CustomEventPublisher.java** ļݣ\n\n```\npackage com.tutorialspoint;\nimport org.springframework.context.ApplicationEventPublisher;\nimport org.springframework.context.ApplicationEventPublisherAware;\npublic class CustomEventPublisher \n   implements ApplicationEventPublisherAware {\n   private ApplicationEventPublisher publisher;\n   public void setApplicationEventPublisher\n              (ApplicationEventPublisher publisher){\n      this.publisher = publisher;\n   }\n   public void publish() {\n      CustomEvent ce = new CustomEvent(this);\n      publisher.publishEvent(ce);\n   }\n}\n```\n\n **CustomEventHandler.java** ļݣ\n\n```\npackage com.tutorialspoint;\nimport org.springframework.context.ApplicationListener;\npublic class CustomEventHandler \n   implements ApplicationListener<CustomEvent>{\n   public void onApplicationEvent(CustomEvent event) {\n      System.out.println(event.toString());\n   }\n}\n```\n\n **MainApp.java** ļݣ\n\n```\npackage com.tutorialspoint;\nimport org.springframework.context.ConfigurableApplicationContext;\nimport org.springframework.context.support.ClassPathXmlApplicationContext;\npublic class MainApp {\n   public static void main(String[] args) {\n      ConfigurableApplicationContext context = \n      new ClassPathXmlApplicationContext(\"Beans.xml\");    \n      CustomEventPublisher cvp = \n      (CustomEventPublisher) context.getBean(\"customEventPublisher\");\n      cvp.publish();  \n      cvp.publish();\n   }\n}\n```\n\nļ **Beans.xml**\n\n```\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<beans xmlns=\"http://www.springframework.org/schema/beans\"\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"http://www.springframework.org/schema/beans\n    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd\">\n\n   <bean id=\"customEventHandler\" \n      class=\"com.tutorialspoint.CustomEventHandler\"/>\n\n   <bean id=\"customEventPublisher\" \n      class=\"com.tutorialspoint.CustomEventPublisher\"/>\n\n</beans>\n```\n\nһ˴Դ bean ļǾͿиӦóӦóһжϢ\n\n```\nMy Custom Event\nMy Custom Event\n```\n\n# ο\nhttps://www.w3cschool.cn/wkspring\nhttps://www.runoob.com/w3cnote/basic-knowledge-summary-of-spring.html\nhttp://codepub.cn/2015/06/21/Basic-knowledge-summary-of-Spring\nhttps://dunwu.github.io/spring-tutorial\nhttps://mszlu.com/java/spring\nhttp://c.biancheng.net/spring/aop-module.html"
  },
  {
    "path": "docs/Spring全家桶/Spring/Spring中的资源管理.md",
    "content": "## Resource ӿ\n\nԱ׼ URL ʻƣSpring  `org.springframework.core.io.Resource` ӿڳ˶ԵײԴķʽӿڣṩһ׸õķʷʽ\n\n\n\n```\npublic interface Resource extends InputStreamSource {\n\n    boolean exists();\n\n    boolean isReadable();\n\n    boolean isOpen();\n\n    boolean isFile();\n\n    URL getURL() throws IOException;\n\n    URI getURI() throws IOException;\n\n    File getFile() throws IOException;\n\n    ReadableByteChannel readableChannel() throws IOException;\n\n    long contentLength() throws IOException;\n\n    long lastModified() throws IOException;\n\n    Resource createRelative(String relativePath) throws IOException;\n\n    String getFilename();\n\n    String getDescription();\n}\n\n```\n\n\n\n `Resource` ӿڵĶʾչ `InputStreamSource` ӿڡ`Resource` ĵķ£\n\n*   `getInputStream()` - λҴ򿪵ǰԴصǰԴ `InputStream`ÿεö᷵һµ `InputStream`Ҫر\n*   `exists()` - жϵǰԴǷĴڡ\n*   `isOpen()` - жϵǰԴǷһѴ򿪵 `InputStream`Ϊ true `InputStream` ܱζȡֻȡһȻرԱԴй©гԴʵַ false`InputStreamResource` ⡣\n*   `getDescription()` - صǰԴԴʱԴڴϢһ˵Դһȫ޶ļƣǵǰԴʵ URL\n\n Spring Դӿڣ\n\n|  | ӿ |\n| --- | --- |\n|  | `org.springframework.core.io.InputStreamSource` |\n| ֻԴ | `org.springframework.core.io.Resource` |\n| дԴ | `org.springframework.core.io.WritableResource` |\n| Դ | `org.springframework.core.io.support.EncodedResource` |\n| Դ | `org.springframework.core.io.ContextResource` |\n\n\n\n![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20221223155859.png)\n\n## [#](https://dunwu.github.io/spring-tutorial/pages/a1549f/#%E5%86%85%E7%BD%AE%E7%9A%84-resource-%E5%AE%9E%E7%8E%B0)õ Resource ʵ\n\nSpring õ Resource ʵ֣\n\n| ԴԴ | ǰ׺ | ˵ |\n| --- | --- | --- |\n| [`UrlResource`(opens new window)](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#resources-implementations-urlresource) | `file:``https:``ftp:`  | `UrlResource` װһ `java.net.URL` **ڷʿͨ URL ʵκζ**ļHTTPS ĿꡢFTP Ŀȡ URL ͨ׼ַʽʾ˿ʹʵı׼ǰ׺ָʾһ URL һ URL ͵ `file`ڷļϵͳ·`https`ͨ HTTPS ЭԴ`ftp`ͨ FTP Դȵȡ |\n| [`ClassPathResource`(opens new window)](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#resources-implementations-classpathresource) | `classpath:` | `ClassPathResource` **·ϼԴ**ʹ߳ļָ class еһԴ |\n| [`FileSystemResource`(opens new window)](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#resources-implementations-filesystemresource) | `file:` | `FileSystemResource` ** `java.io.File` Դʵ**֧ `java.nio.file.Path` Ӧ Spring ı׼ַ·ת`FileSystemResource` ֽ֧Ϊļ URL |\n| [`PathResource`(opens new window)](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#resources-implementations-pathresource) |  | `PathResource`  `java.nio.file.Path` Դʵ֡ |\n| [`ServletContextResource`(opens new window)](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#resources-implementations-servletcontextresource) |  | `ServletContextResource` ** `ServletContext` Դʵ**ʾӦ Web ӦóĿ¼е· |\n| [`InputStreamResource`(opens new window)](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#resources-implementations-inputstreamresource) |  | `InputStreamResource` **ָ `InputStream` Դʵ**ע⣺ `InputStream` ѱ򿪣򲻿Զζȡ |\n| [`ByteArrayResource`(opens new window)](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#resources-implementations-bytearrayresource) |  | `ByteArrayResource` ָĶԴʵ֡Ϊֽ鴴һ `ByteArrayInputStream` |\n\n\n\n## [#](https://dunwu.github.io/spring-tutorial/pages/a1549f/#resourceloader-%E6%8E%A5%E5%8F%A3)ResourceLoader ӿ\n\n`ResourceLoader` ӿڼ `Resource` 䶨£\n\n\n\n```\npublic interface ResourceLoader {\n\n    Resource getResource(String location);\n\n    ClassLoader getClassLoader();\n}\n\n```\n\n\n\nSpring Ҫ ResourceLoader ʵ֣\n\n![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20221223164745.png)\n\nSpring Уе `ApplicationContext` ʵ `ResourceLoader` ӿڡˣ `ApplicationContext` ͨ `getResource()` ȡ `Resource` ʵ\n\nʾ\n\n\n\n```\n// ûָԴǰ׺Spring ᳢ԷغʵԴ\nResource template = ctx.getResource(\"some/resource/path/myTemplate.txt\");\n// ָ classpath: ǰ׺Spring ǿʹ ClassPathResource\nResource template = ctx.getResource(\"classpath:some/resource/path/myTemplate.txt\");\n// ָ file:http  URL ǰ׺Spring ǿʹ UrlResource\nResource template = ctx.getResource(\"file:///some/resource/path/myTemplate.txt\");\nResource template = ctx.getResource(\"http://myhost.com/resource/path/myTemplate.txt\");\n\n```\n\n\n\n±о Spring ݸλ·ԴĲԣ\n\n| ǰ׺ |  | ˵ |\n| --- | --- | --- |\n| `classpath:` | `classpath:com/myapp/config.xml` | · |\n| `file:` | `file:///data/config.xml` |  URL ʽļϵͳ |\n| `http:` | `http://myserver/logo.png` |  URL ʽ |\n|  | `/data/config.xml` | ɵײ ApplicationContext ʵ־ |\n\n\n\n## [#](https://dunwu.github.io/spring-tutorial/pages/a1549f/#resourcepatternresolver-%E6%8E%A5%E5%8F%A3)ResourcePatternResolver ӿ\n\n`ResourcePatternResolver` ӿ `ResourceLoader` ӿڵչǶԣλģʽ `Resource` \n\n\n\n```\npublic interface ResourcePatternResolver extends ResourceLoader {\n\n    String CLASSPATH_ALL_URL_PREFIX = \"classpath*:\";\n\n    Resource[] getResources(String locationPattern) throws IOException;\n}\n\n```\n\n\n\n`PathMatchingResourcePatternResolver` һʵ֣ `ApplicationContext` ֮ʹãҲԱ `ResourceArrayPropertyEditor`  `Resource[]` bean ԡ`PathMatchingResourcePatternResolver` ָܹԴλ·Ϊһƥ `Resource` \n\n> ע⣺κα׼ `ApplicationContext` еĬ `ResourceLoader` ʵ `PathMatchingResourcePatternResolver` һʵʵ `ResourcePatternResolver` ӿڡ\n\n## [#](https://dunwu.github.io/spring-tutorial/pages/a1549f/#resourceloaderaware-%E6%8E%A5%E5%8F%A3)ResourceLoaderAware ӿ\n\n`ResourceLoaderAware` ӿһĻصӿڣṩ `ResourceLoader` õĶ`ResourceLoaderAware` ӿڶ£\n\n\n\n```\npublic interface ResourceLoaderAware {\n    void setResourceLoader(ResourceLoader resourceLoader);\n}\n\n```\n\n\n\nһʵ `ResourceLoaderAware` ӦóУΪ Spring  beanʱᱻӦóʶΪ `ResourceLoaderAware`ȻӦóĻ `setResourceLoader(ResourceLoader)`ΪṩסSpring еӦóĶʵ `ResourceLoader` ӿڣ\n\n `ApplicationContext` һ `ResourceLoader` bean ʵ `ApplicationContextAware` ӿڲֱʹṩӦóԴ ǣһ˵ֻҪЩʹרŵ `ResourceLoader` ӿڡ ô뽫ϵԴؽӿڣԱΪʵóӿڣϵ Spring `ApplicationContext` ӿڡ\n\nӦóУʹ `ResourceLoader` ԶװΪʵ `ResourceLoaderAware` ӿڵͳĹ캯 `byType` ԶװģʽֱܹΪ캯 setter ṩ `ResourceLoader` Ϊ˻øԣԶװֶκͶ뿼ʹûעԶװ书ܡ £`ResourceLoader` ԶӵҪ `ResourceLoader` ͵ֶΡ캯򷽷УֻҪֶΡ캯򷽷 `@Autowired` ע⼴ɡ\n\n## [#](https://dunwu.github.io/spring-tutorial/pages/a1549f/#%E8%B5%84%E6%BA%90%E4%BE%9D%E8%B5%96)Դ\n\n bean Ҫͨĳֶ̬ȷṩԴ·ô bean ʹ `ResourceLoader`  `ResourcePatternResolver` ӿԴ 磬Ǽĳģ壬ضԴȡûĽɫ ԴǾ̬ģȫ `ResourceLoader` ӿڣ `ResourcePatternResolver` ӿڣʹã bean Ҫ `Resource` ԣעġ\n\nʹעЩԱü򵥵ԭӦóĶעᲢʹһ JavaBeans `PropertyEditor`Խ `String` ·תΪ `Resource`  磬 MyBean һ `Resource` ͵ģԡ\n\nʾ\n\n\n\n```\n<bean id=\"myBean\" class=\"example.MyBean\">\n    <property name=\"template\" value=\"some/resource/path/myTemplate.txt\"/>\n</bean>\n\n```\n\n\n\nע⣬õģԴ·ûǰ׺ΪӦóı `ResourceLoader`ԴҪͨ `ClassPathResource``FileSystemResource`  ServletContextResource أȡĵȷ͡\n\nҪǿʹضԴͣʹǰ׺ ʾʾǿʹ `ClassPathResource`  `UrlResource`ڷļϵͳļ\n\n\n\n```\n<property name=\"template\" value=\"classpath:some/resource/path/myTemplate.txt\">\n<property name=\"template\" value=\"file:///some/resource/path/myTemplate.txt\"/>\n\n```\n\n\n\nͨ `@Value` עԴļ `myTemplate.txt`ʾ£\n\n\n\n```\n@Component\npublic class MyBean {\n\n    private final Resource template;\n\n    public MyBean(@Value(\"${template.path}\") Resource template) {\n        this.template = template;\n    }\n\n    // ...\n}\n\n```\n\n\n\nSpring  `PropertyEditor` Դļ·ַ `Resource` 󣬲ע뵽 MyBean Ĺ췽\n\nҪضԴļʹ `classpath*:` ǰ׺磺`classpath*:/config/templates/*.txt`\n\n\n\n```\n@Component\npublic class MyBean {\n\n    private final Resource[] templates;\n\n    public MyBean(@Value(\"${templates.path}\") Resource[] templates) {\n        this.templates = templates;\n    }\n\n    // ...\n}\n\n```\n\n\n\n## [#](https://dunwu.github.io/spring-tutorial/pages/a1549f/#%E5%BA%94%E7%94%A8%E4%B8%8A%E4%B8%8B%E6%96%87%E5%92%8C%E8%B5%84%E6%BA%90%E8%B7%AF%E5%BE%84)ӦĺԴ·\n\n### [#](https://dunwu.github.io/spring-tutorial/pages/a1549f/#%E6%9E%84%E9%80%A0%E5%BA%94%E7%94%A8%E4%B8%8A%E4%B8%8B%E6%96%87)Ӧ\n\nӦĹ캯ضӦַַͣͨΪԴλ·繹Ķ XML ļ\n\nʾ\n\n\n\n```\nApplicationContext ctx = new ClassPathXmlApplicationContext(\"conf/appContext.xml\");\nApplicationContext ctx = new FileSystemXmlApplicationContext(\"conf/appContext.xml\");\nApplicationContext ctx = new FileSystemXmlApplicationContext(\"classpath:conf/appContext.xml\");\nApplicationContext ctx = new ClassPathXmlApplicationContext(\n                new String[] {\"services.xml\", \"daos.xml\"}, MessengerService.class);\n\n```\n\n\n\n### [#](https://dunwu.github.io/spring-tutorial/pages/a1549f/#%E4%BD%BF%E7%94%A8%E9%80%9A%E9%85%8D%E7%AC%A6%E6%9E%84%E9%80%A0%E5%BA%94%E7%94%A8%E4%B8%8A%E4%B8%8B%E6%96%87)ʹͨӦ\n\nApplicationContext еԴ·ǵһ·һһӳ䵽ĿԴҲͨʽɰ classpath*Ҳǰ׺ ant ʽʹ spring  PathMatcher ƥ䣩\n\nʾ\n\n\n\n```\nApplicationContext ctx = new ClassPathXmlApplicationContext(\"classpath*:conf/appContext.xml\");\n\n```\n\n\n\nʹ `classpath*` ʾ·ƥļƵԴᱻȡ(Ͼǵ ClassLoader.getResources() ŽȡԴװյӦġ\n\nλ·ಿ֣`classpath*:` ǰ׺ PathMatcher ʹã磺`classpath*:META-INF/*-beans.xml`\n\n## [#](https://dunwu.github.io/spring-tutorial/pages/a1549f/#%E9%97%AE%E9%A2%98)\n\nSpring ԴЩͣ\n\n*   XML Դ\n*   Properties Դ\n*   YAML Դ\n\n## [#](https://dunwu.github.io/spring-tutorial/pages/a1549f/#%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99)ο\n\n*   [Spring ٷĵ֮ Core Technologies(opens new window)](https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#beans)\n*   [С署 Spring ı˼롷](https://time.geekbang.org/course/intro/265)\n\n# ο\nhttps://www.w3cschool.cn/wkspring\nhttps://www.runoob.com/w3cnote/basic-knowledge-summary-of-spring.html\nhttp://codepub.cn/2015/06/21/Basic-knowledge-summary-of-Spring\nhttps://dunwu.github.io/spring-tutorial\nhttps://mszlu.com/java/spring\nhttp://c.biancheng.net/spring/aop-module.html"
  },
  {
    "path": "docs/Spring全家桶/Spring/Spring中的配置元数据（管理配置的基本数据）.md",
    "content": "\n\n# Spring Ԫ\n\n\n\n## [#](https://dunwu.github.io/spring-tutorial/pages/55f315/#spring-%E9%85%8D%E7%BD%AE%E5%85%83%E4%BF%A1%E6%81%AF)Spring ԪϢ\n\n*   Spring Bean ԪϢ - BeanDefinition\n*   Spring Bean ԪϢ - PropertyValues\n*   Spring ԪϢ\n*   Spring ⲿԪϢ - PropertySource\n*   Spring Profile ԪϢ - @Profile\n\n## [#](https://dunwu.github.io/spring-tutorial/pages/55f315/#spring-bean-%E9%85%8D%E7%BD%AE%E5%85%83%E4%BF%A1%E6%81%AF)Spring Bean ԪϢ\n\nBean ԪϢ - BeanDefinition\n\n*   GenericBeanDefinitionͨ BeanDefinition\n*   RootBeanDefinition Parent  BeanDefinition ߺϲ BeanDefinition\n*   AnnotatedBeanDefinitionעע BeanDefinition\n\n## [#](https://dunwu.github.io/spring-tutorial/pages/55f315/#spring-bean-%E5%B1%9E%E6%80%A7%E5%85%83%E4%BF%A1%E6%81%AF)Spring Bean ԪϢ\n\n*   Bean ԪϢ - PropertyValues\n    *   ޸ʵ - MutablePropertyValues\n    *   ԪسԱ - PropertyValue\n*   Bean Ĵ洢 - AttributeAccessor\n*   Bean ԪϢԪ - BeanMetadataElement\n\n## [#](https://dunwu.github.io/spring-tutorial/pages/55f315/#spring-%E5%AE%B9%E5%99%A8%E9%85%8D%E7%BD%AE%E5%85%83%E4%BF%A1%E6%81%AF)Spring ԪϢ\n\nSpring XML ԪϢ - beans Ԫ\n\n| beans Ԫ | Ĭֵ | ʹó |\n| --- | --- | --- |\n| profile | nullգ | Spring Profiles ֵ |\n| default-lazy-init | default |  outter beans default-lazy-init Դʱ̳иֵΪfalse |\n| default-merge | default |  outter beans default-merge Դʱ̳иֵΪfalse |\n| default-autowire | default |  outter beans default-autowire Դʱ̳иֵΪno |\n| default-autowire-candidates | nullգ | Ĭ Spring Beans  pattern |\n| default-init-method | nullգ | Ĭ Spring Beans Զʼ |\n| default-destroy-method | nullգ | Ĭ Spring Beans Զٷ |\n\nSpring XML ԪϢ - Ӧ\n\n| XML Ԫ | ʹó |\n| --- | --- |\n| `<context:annotation-config />` |  Spring ע |\n| `<context:component-scan />` | Spring @Component ԼԶעɨ |\n| `<context:load-time-weaver />` |  Spring LoadTimeWeaver |\n| `<context:mbean-export />` | ¶ Spring Beans Ϊ JMX Beans |\n| `<context:mbean-server />` | ǰƽ̨Ϊ MBeanServer |\n| `<context:property-placeholder />` | ⲿԴΪ Spring  |\n| `<context:property-override />` | ⲿԴ Spring  |\n\n## [#](https://dunwu.github.io/spring-tutorial/pages/55f315/#%E5%9F%BA%E4%BA%8E-xml-%E6%96%87%E4%BB%B6%E8%A3%85%E8%BD%BD-spring-bean-%E9%85%8D%E7%BD%AE%E5%85%83%E4%BF%A1%E6%81%AF) XML ļװ Spring Bean ԪϢ\n\nײʵ - XmlBeanDefinitionReader\n\n| XML Ԫ | ʹó |\n| --- | --- |\n| `<beans:beans />` |  XML ԴµĶ Spring Beans  |\n| `<beans:bean />` |  Spring Bean 壨BeanDefinition |\n| `<beans:alias />` | Ϊ Spring Bean 壨BeanDefinitionӳ |\n| `<beans:import />` | ⲿ Spring XML Դ |\n\n## [#](https://dunwu.github.io/spring-tutorial/pages/55f315/#%E5%9F%BA%E4%BA%8E-properties-%E6%96%87%E4%BB%B6%E8%A3%85%E8%BD%BD-spring-bean-%E9%85%8D%E7%BD%AE%E5%85%83%E4%BF%A1%E6%81%AF) Properties ļװ Spring Bean ԪϢ\n\nײʵ - PropertiesBeanDefinitionReader\n\n| Properties  | ʹó |\n| --- | --- |\n| `class` | Bean ȫ޶ |\n| `abstract` | ǷΪ BeanDefinition |\n| `parent` | ָ parent BeanDefinition  |\n| `lazy-init` | ǷΪӳٳʼ |\n| `ref` |  Bean  |\n| `scope` |  Bean  scope  |\n| ${n} | n ʾ n+1  |\n\n## [#](https://dunwu.github.io/spring-tutorial/pages/55f315/#%E5%9F%BA%E4%BA%8E-java-%E6%B3%A8%E8%A7%A3%E8%A3%85%E8%BD%BD-spring-bean-%E9%85%8D%E7%BD%AE%E5%85%83%E4%BF%A1%E6%81%AF) Java עװ Spring Bean ԪϢ\n\nSpring ģʽע\n\n| Spring ע | ˵ | ʼ汾 |\n| --- | --- | --- |\n| `@Repository` | ݲִģʽע | 2.0 |\n| `@Component` | ͨģʽע | 2.5 |\n| `@Service` | ģʽע | 2.5 |\n| `@Controller` | Web ģʽע | 2.5 |\n| `@Configuration` | ģʽע | 3.0 |\n\nSpring Bean ע\n\n| Spring ע | ˵ | ʼ汾 |\n| --- | --- | --- |\n| `@Bean` | 滻 XML Ԫ `<bean>` | 3.0 |\n| `@DependsOn` |  XML  `<bean depends-on=\"...\"/>` | 3.0 |\n| `@Lazy` |  XML  `<bean lazy-init=\"true | falses\" />` | 3.0 |\n| `@Primary` | 滻 XML Ԫ `<bean primary=\"true | false\" />` | 3.0 |\n| `@Role` | 滻 XML Ԫ `<bean role=\"...\" />` | 3.1 |\n| `@Lookup` |  XML  `<bean lookup-method=\"...\">` | 4.1 |\n\nSpring Bean עע\n\n| Spring ע | ˵ | ʼ汾 |\n| --- | --- | --- |\n| `@Autowired` | Bean ע룬ֶ֧ҷʽ | 2.5 |\n| `@Qualifier` | ϸȵ @Autowired  | 2.5 |\n\n \n\n| Java ע | ˵ | ʼ汾 |\n| --- | --- | --- |\n| @Resource |  @Autowired | 2.5 |\n| @Inject |  @Autowired | 2.5 |\n\nSpring Bean װע\n\n| Spring ע | ˵ | ʼ汾 |\n| --- | --- | --- |\n| @Profile | ûװ | 3.1 |\n| @Conditional | װ | 4.0 |\n\nSpring Bean ڻصע\n\n| Spring ע | ˵ | ʼ汾 |\n| --- | --- | --- |\n| @PostConstruct | 滻 XML Ԫ <bean init-method=\"...\"></bean> InitializingBean | 2.5 |\n| @PreDestroy | 滻 XML Ԫ <bean destroy-method=\"...\"></bean> DisposableBean | 2.5 |\n\nSpring BeanDefinition ע\n\n| Spring ע | ˵ | ʼ汾 |\n| --- | --- | --- |\n| XML Դ | XmlBeanDefinitionReader | 1.0 |\n| Properties Դ | PropertiesBeanDefinitionReader | 1.0 |\n| Java ע | AnnotatedBeanDefinitionReader | 3.0 |\n\n## [#](https://dunwu.github.io/spring-tutorial/pages/55f315/#spring-bean-%E9%85%8D%E7%BD%AE%E5%85%83%E4%BF%A1%E6%81%AF%E5%BA%95%E5%B1%82%E5%AE%9E%E7%8E%B0)Spring Bean ԪϢײʵ\n\n### [#](https://dunwu.github.io/spring-tutorial/pages/55f315/#spring-xml-%E8%B5%84%E6%BA%90-beandefinition-%E8%A7%A3%E6%9E%90%E4%B8%8E%E6%B3%A8%E5%86%8C)Spring XML Դ BeanDefinition ע\n\n API - XmlBeanDefinitionReader\n\n*   Դ - Resource\n*   ײ - BeanDefinitionDocumentReader\n    *   XML  - Java DOM Level 3 API\n    *   BeanDefinition  - BeanDefinitionParserDelegate\n    *   BeanDefinition ע - BeanDefinitionRegistry\n\n### [#](https://dunwu.github.io/spring-tutorial/pages/55f315/#spring-properties-%E8%B5%84%E6%BA%90-beandefinition-%E8%A7%A3%E6%9E%90%E4%B8%8E%E6%B3%A8%E5%86%8C)Spring Properties Դ BeanDefinition ע\n\n API - PropertiesBeanDefinitionReader\n\n*   Դ\n    *   ֽ - Resource\n    *   ַ - EncodedResouce\n*   ײ\n    *   洢 - java.util.Properties\n    *   BeanDefinition  - API ڲʵ\n    *   BeanDefinition ע - BeanDefinitionRegistry\n\n### [#](https://dunwu.github.io/spring-tutorial/pages/55f315/#spring-java-%E6%B3%A8%E5%86%8C-beandefinition-%E8%A7%A3%E6%9E%90%E4%B8%8E%E6%B3%A8%E5%86%8C)Spring Java ע BeanDefinition ע\n\n API - AnnotatedBeanDefinitionReader\n\n*   Դ\n    *    - java.lang.Class\n*   ײ\n    *    - ConditionEvaluator\n    *   Bean Χ - ScopeMetadataResolver\n    *   BeanDefinition  - ڲ API ʵ\n    *   BeanDefinition  - AnnotationConfigUtils.processCommonDefinitionAnnotations\n    *   BeanDefinition ע - BeanDefinitionRegistry\n\n## [#](https://dunwu.github.io/spring-tutorial/pages/55f315/#%E5%9F%BA%E4%BA%8E-xml-%E6%96%87%E4%BB%B6%E8%A3%85%E8%BD%BD-spring-ioc-%E5%AE%B9%E5%99%A8%E9%85%8D%E7%BD%AE%E5%85%83%E4%BF%A1%E6%81%AF) XML ļװ Spring IoC ԪϢ\n\nSpring IoC  XML \n\n| ռ | ģ | Schema Դ URL |\n| --- | --- | --- |\n| beans | spring-beans | https://www.springframework.org/schema/beans/spring-beans.xsd |\n| context | spring-context | https://www.springframework.org/schema/context/spring-context.xsd |\n| aop | spring-aop | https://www.springframework.org/schema/aop/spring-aop.xsd |\n| tx | spring-tx | https://www.springframework.org/schema/tx/spring-tx.xsd |\n| util | spring-beans | beans https://www.springframework.org/schema/util/spring-util.xsd |\n| tool | spring-beans | https://www.springframework.org/schema/tool/spring-tool.xsd |\n\n## [#](https://dunwu.github.io/spring-tutorial/pages/55f315/#%E5%9F%BA%E4%BA%8E-java-%E6%B3%A8%E8%A7%A3%E8%A3%85%E8%BD%BD-spring-ioc-%E5%AE%B9%E5%99%A8%E9%85%8D%E7%BD%AE%E5%85%83%E4%BF%A1%E6%81%AF) Java עװ Spring IoC ԪϢ\n\nSpring IoC װע\n\n| Spring ע | ˵ | ʼ汾 |\n| --- | --- | --- |\n| @ImportResource | 滻 XML Ԫ `<import>` | 3.0 |\n| @Import |  Configuration Class | 3.0 |\n| @ComponentScan | ɨָ package ±ע Spring ģʽע | 3.1 |\n\nSpring IoC ע\n\n| Spring ע | ˵ | ʼ汾 |\n| --- | --- | --- |\n| @PropertySource | Գ PropertySource ע | 3.1 |\n| @PropertySources | @PropertySource ע | 4.0 |\n\n## [#](https://dunwu.github.io/spring-tutorial/pages/55f315/#%E5%9F%BA%E4%BA%8E-extensible-xml-authoring-%E6%89%A9%E5%B1%95-springxml-%E5%85%83%E7%B4%A0) Extensible XML authoring չ SpringXML Ԫ\n\nSpring XML չ\n\n*   д XML Schema ļ XML ṹ\n*   Զ NamespaceHandler ʵ֣ռ\n*   Զ BeanDefinitionParser ʵ֣XML Ԫ BeanDefinition \n*   ע XML չռ XML Schema ӳ\n\n## [#](https://dunwu.github.io/spring-tutorial/pages/55f315/#extensible-xml-authoring-%E6%89%A9%E5%B1%95%E5%8E%9F%E7%90%86)Extensible XML authoring չԭ\n\n### [#](https://dunwu.github.io/spring-tutorial/pages/55f315/#%E8%A7%A6%E5%8F%91%E6%97%B6%E6%9C%BA)ʱ\n\n*   AbstractApplicationContext#obtainFreshBeanFactory\n    *   AbstractRefreshableApplicationContext#refreshBeanFactory\n        *   AbstractXmlApplicationContext#loadBeanDefinitions\n            *   ...\n                *   XmlBeanDefinitionReader#doLoadBeanDefinitions\n                    *   ...\n                        *   BeanDefinitionParserDelegate#parseCustomElement\n\n### [#](https://dunwu.github.io/spring-tutorial/pages/55f315/#%E6%A0%B8%E5%BF%83%E6%B5%81%E7%A8%8B)\n\nBeanDefinitionParserDelegate#parseCustomElement(org.w3c.dom.Element, BeanDefinition)\n\n*   ȡ namespace\n*   ͨ namespace  NamespaceHandler\n*    ParserContext\n*   Ԫأȡ BeanDefinintion\n\n## [#](https://dunwu.github.io/spring-tutorial/pages/55f315/#%E5%9F%BA%E4%BA%8E-properties-%E6%96%87%E4%BB%B6%E8%A3%85%E8%BD%BD%E5%A4%96%E9%83%A8%E5%8C%96%E9%85%8D%E7%BD%AE) Properties ļװⲿ\n\nע\n\n*   @org.springframework.context.annotation.PropertySource\n*   @org.springframework.context.annotation.PropertySources\n\nAPI \n\n*   org.springframework.core.env.PropertySource\n*   org.springframework.core.env.PropertySources\n\n## [#](https://dunwu.github.io/spring-tutorial/pages/55f315/#%E5%9F%BA%E4%BA%8E-yaml-%E6%96%87%E4%BB%B6%E8%A3%85%E8%BD%BD%E5%A4%96%E9%83%A8%E5%8C%96%E9%85%8D%E7%BD%AE) YAML ļװⲿ\n\nAPI \n\n*   org.springframework.beans.factory.config.YamlProcessor\n    *   org.springframework.beans.factory.config.YamlMapFactoryBean\n    *   org.springframework.beans.factory.config.YamlPropertiesFactoryBean\n\n## [#](https://dunwu.github.io/spring-tutorial/pages/55f315/#%E9%97%AE%E9%A2%98)\n\n**Spring Ƚ XML Schema Щ**\n\n| ռ | ģ | Schema Դ URL |\n| --- | --- | --- |\n| beans | spring-beans | https://www.springframework.org/schema/beans/spring-beans.xsd |\n| context | spring-context | https://www.springframework.org/schema/context/spring-context.xsd |\n| aop | spring-aop | https://www.springframework.org/schema/aop/spring-aop.xsd |\n| tx | spring-tx | https://www.springframework.org/schema/tx/spring-tx.xsd |\n| util | spring-beans | beans https://www.springframework.org/schema/util/spring-util.xsd |\n| tool | spring-beans | https://www.springframework.org/schema/tool/spring-tool.xsd |\n\n**Spring ԪϢЩ**\n\n*   Bean ԪϢͨý飨 XMLProeprties ȣ BeanDefinition\n*   IoC ԪϢͨý飨 XMLProeprties ȣ IoC ΪעAOP \n*   ⲿãͨԴ ProeprtiesYAML ȣ PropertySource\n*   Spring Profileͨⲿãṩ֧\n\n**Extensible XML authoring ȱ**\n\n*   ߸ӶȣԱҪϤ XML Schemaspring.handlersspring.schemas Լ Spring API\n*   ǶԪֽ֧ͨҪʹ÷ݹǶ׽ķʽǶףӣԪ\n*   XML ܽϲSpring XML  DOM Level 3 API ʵ֣ API ⣬Ȼܽϲ\n*   XML ֲԲܺͱԵ XML ܣ JAXB\n\n## [#](https://dunwu.github.io/spring-tutorial/pages/55f315/#%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99)ο\n\n*   [Spring ٷĵ֮ Core Technologies(opens new window)](https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#beans)\n*   [С署 Spring ı˼롷(opens new window)](https://time.geekbang.org/course/intro/265)\n\n\n\n# ο\nhttps://www.w3cschool.cn/wkspring\nhttps://www.runoob.com/w3cnote/basic-knowledge-summary-of-spring.html\nhttp://codepub.cn/2015/06/21/Basic-knowledge-summary-of-Spring\nhttps://dunwu.github.io/spring-tutorial\nhttps://mszlu.com/java/spring\nhttp://c.biancheng.net/spring/aop-module.html"
  },
  {
    "path": "docs/Spring全家桶/Spring/Spring事务基本用法.md",
    "content": "# SpringĽӿ\n\n[Spring](http://www.voidme.com/spring) ǻ AOP ʵֵģ AOP ԷΪλġSpring ԷֱΪΪ뼶ֻͳʱԣЩṩӦõķԡ\n\n [Java](http://www.voidme.com/java) EE õķֲģʽУSpring λҵ߼㣬ṩĽ\n\n Spring ѹ libs Ŀ¼УһΪ spring-tx-3.2.13.RELEASE.jar ļļ Spring ṩ JAR аĽӿڣPlatformTransactionManagerTransactionDefinition  TransactionStatus\n\n JAR ĺ׺ jar ĳ zip ʽ󣬽ѹѹѹļе \\org\\springframework\\transaction Ŀ¼󣬸Ŀ¼еļͼ 1 ʾ\n\n![Ľӿ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/760_a12_405.png)  \nͼ 1  Ľӿ\n\nͼ 1 УעļǱڽҪĺĽӿڡĽӿڵüṩķ¡\n\n#### 1\\. PlatformTransactionManager\n\nPlatformTransactionManager ӿ Spring ṩƽ̨ڹ񡣸ýӿṩ¡\n\n*   TransactionStatus getTransactionTransactionDefinition definitionڻȡ״̬Ϣ\n*   void commitTransactionStatus statusύ\n*   void rollbackTransactionStatus statusڻع\n\nĿУSpring  xml õϸϢװ TransactionDefinition УȻͨ getTransaction() ״̬TransactionStatusһĲ\n\n#### 2\\. TransactionDefinition\n\nTransactionDefinition ӿ壨ĶṩϢȡķа¡\n\n*   String getName()ȡơ\n*   int getIsolationLevel()ȡĸ뼶\n*   int getPropagationBehavior()ȡĴΪ\n*   int getTimeout()ȡĳʱʱ䡣\n*   boolean isReadOnly()ȡǷֻ\n\nУĴΪָͬһУͬǰʹõ񡣴Ϊ 1 ʾ\n\n|  | ֵ |    |  \n| --- | --- | --- |  \n| PROPAGATION_REQUIRED | required | ֵ֧ǰ A ѾУ B ֱʹá򽫴 |  \n| PROPAGATION_SUPPORTS | supports | ֵ֧ǰ A ѾУ B ֱʹáԷ״ִ̬ |  \n| PROPAGATION_MANDATORY | mandatory | ֵ֧ǰ A û׳쳣 |  \n| PROPAGATION_REQUIRES_NEW | requires_new | µ A ѾУ A  |  \n| PROPAGATION_NOT_SUPPORTED | not_supported | ֵ֧ǰԷ״ִ̬С A ѾУ |  \n| PROPAGATION_NEVER | never | ֵ֧ǰ A У׳쳣 |  \n| PROPAGATION.NESTED | nested | Ƕ񣬵ײ㽫ʹ Savepoint γǶ |  \n\nУΪԿǷҪԼδ\n\nͨ£ݵĲѯıԭݣԲҪݵӡ޸ĺɾȲûָĴΪ Spring3 ĬϵĴΪ required\n\n#### 3\\. TransactionStatus\n\nTransactionStatus ӿ״̬ĳһʱ״̬Ϣа 2 ʾ\n\n<caption> 2  Ĳ</caption>  \n|  | ˵ |  \n| --- | --- |  \n| void flush() | ˢ |  \n| boolean hasSavepoint() | ȡǷڱ |  \n| boolean isCompleted() | ȡǷ |  \n| boolean isNewTransaction() | ȡǷ |  \n| boolean isRollbackOnly() | ȡǷع |  \n| void setRollbackOnly() | ع |  \n\n# SpringʽXMLʽʵ֣\n\n[Spring](http://www.voidme.com/spring) ַʽһǴͳıʽͨдʵֵһǻ AOP ʵֵʽʵʿУʽʹãֻ Spring ʽϸ⡣\n\nSpring ʽڵײ AOP ŵ̵ͨķʽֻҪļнصĹͿԽӦõҵ߼С\n\nSpring ʵʽҪַʽ\n\n*    XML ʽʽ\n*   ͨ Annotation עⷽʽ\n\nͨת˵İʹ XML ķʽʵ Spring ʽ\n\n#### 1\\. Ŀ\n\n MyEclipse дһΪ springDemo03  Web Ŀ Spring ֺ֧ JAR Ƶ Web Ŀ lib Ŀ¼Уӵ·¡ӵ JAR ͼ 1 ʾ\n\n![ҪJAR](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/761_732_fcf.png)  \nͼ 1  ҪJAR\n\nͼ 1 пԿӵ spring-tx-3.2.13.RELEASE.jarԼ [MySQL](http://www.voidme.com/mysql) JDBC  C3P0  JAR \n\n#### 2\\. ݿ⡢Լ\n\n MySQL дһΪ spring ݿ⣬Ȼڸݿдһ account вݣ SQL ִʾ\n\nCREATE DATABASE spring;  \nUSE spring;  \nCREATE TABLE account (  \nid INT (11) PRIMARY KEY AUTO_INCREMENT,  \nusername VARCHAR(20) NOT NULL,  \nmoney INT DEFAULT NULL  \n);  \nINSERT INTO account VALUES (1,'zhangsan',1000);  \nINSERT INTO account VALUES (2,'lisi',1000);\n\nִк account еͼ 2 ʾ\n\n![ִн](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/762_276_8a5.PNG)  \nͼ 2  ִн\n\n#### 3\\.  c3p0-db.properties\n\nĿ src ´һΪ c3p0-db.properties ļʹ C3P0 ԴҪڸļã\n````  \njdbc.driverClass = com.mysql.jdbc.Driver  \njdbc.jdbcUrl = jdbc:mysql://localhost:3306/spring  \njdbc.user = root  \njdbc.password = root  \n````  \n#### 4\\. ʵ DAO\n\n#### 1 AccountDao ӿ\n\nĿ src Ŀ¼´һΪ com.mengma.dao İڸð´һӿ AccountDaoڽӿдտķʾ\n````  \npackage com.mengma.dao;  \npublic interface AccountDao {  \n    //   \n    public void out(String outUser, int money);  \n    // տ  \n    public void in(String inUser, int money);} ````  \nУ out()  in() ֱڱʾտ  \n  \n#### 2DAOӿʵ  \n  \nĿ src Ŀ¼´һΪ com.mengma.dao.impl İڸð´ʵ AccountDaoImplʾ  \n````  \npackage com.mengma.dao.impl;\n\nimport org.springframework.jdbc.core.JdbcTemplate;  \nimport com.mengma.dao.AccountDao;\n\npublic class AccountDaoImpl implements AccountDao {  \nprivate JdbcTemplate jdbcTemplate;  \npublic void setJdbcTemplate(JdbcTemplate jdbcTemplate) {        this.jdbcTemplate = jdbcTemplate;    }  \n// ʵַ  \npublic void out(String outUser, int money) {        this.jdbcTemplate.update(\"update account set money =money-?\"                + \"where username =?\", money, outUser);    }  \n// տʵַ  \npublic void in(String inUser, int money) {        this.jdbcTemplate.update(\"update account set money =money+?\"                + \"where username =?\", money, inUser);    }} ````  \nУʹ JdbcTemplate  update() ʵ˸²\n\n#### 5\\. ʵ Service\n\n#### 1 Service ӿ\n\nĿ src Ŀ¼´һΪ com.mengma.service İڸð´ӿ AccountServiceʾ\n````  \npackage com.mengma.service;  \n  \npublic interface AccountService {  \n    // ת  \n    public void transfer(String outUser, String inUser, int money);} ````  \n#### 2 Service ӿʵ  \n  \nĿ src Ŀ¼´һΪ com.mengma.service.impl İڸð´ʵ AccountServiceImplʾ  \n````  \npackage com.mengma.service.impl;\n\nimport com.mengma.dao.AccountDao;\n\npublic class AccountServiceImpl {  \nprivate AccountDao accountDao;  \npublic void setAccountDao(AccountDao accountDao) {        this.accountDao = accountDao;    }  \npublic void transfer(String outUser, String inUser, int money) {        this.accountDao.out(outUser, money);        this.accountDao.in(inUser, money);    }} ````  \nпԿʵ AccountService ӿڣת˵ķʵ֣ݲĲͬ DAO Ӧķ\n\n#### 6\\.  Spring ļ\n\nĿ src Ŀ¼´ Spirng ļ applicationContext.xml༭ʾ\n````  \n <?xml version=\"1.0\" encoding=\"UTF-8\"?><beans xmlns=\"http://www.springframework.org/schema/beans\"  \n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"    xmlns:context=\"http://www.springframework.org/schema/context\"    xmlns:tx=\"http://www.springframework.org/schema/tx\"    xmlns:aop=\"http://www.springframework.org/schema/aop\"    xsi:schemaLocation=\"http://www.springframework.org/schema/beans            http://www.springframework.org/schema/beans/spring-beans-2.5.xsd            http://www.springframework.org/schema/context  \n            http://www.springframework.org/schema/context/spring-context.xsd            http://www.springframework.org/schema/tx            http://www.springframework.org/schema/tx/spring-tx-2.5.xsd            http://www.springframework.org/schema/aop            http://www.springframework.org/schema/aop/spring-aop-2.5.xsd\">    <!-- propertiesļ -->    <context:property-placeholder location=\"classpath:c3p0-db.properties\" />    <!-- ԴȡpropertiesļϢ -->    <bean id=\"dataSource\" class=\"com.mchange.v2.c3p0.ComboPooledDataSource\">        <property name=\"driverClass\" value=\"${jdbc.driverClass}\" />        <property name=\"jdbcUrl\" value=\"${jdbc.jdbcUrl}\" />        <property name=\"user\" value=\"${jdbc.user}\" />        <property name=\"password\" value=\"${jdbc.password}\" />    </bean>    <!-- jdbcģ -->    <bean id=\"jdbcTemplate\" class=\"org.springframework.jdbc.core.JdbcTemplate\">        <property name=\"dataSource\" ref=\"dataSource\" />    </bean>    <!-- dao -->  \n    <bean id=\"accountDao\" class=\"com.mengma.dao.impl.AccountDaoImpl\">        <property name=\"jdbcTemplate\" ref=\"jdbcTemplate\" />    </bean>    <!-- service -->  \n    <bean id=\"accountService\" class=\"com.mengma.service.impl.AccountServiceImpl\">        <property name=\"accountDao\" ref=\"accountDao\" />    </bean>    <!-- Դ -->    <bean id=\"txManager\"        class=\"org.springframework.jdbc.datasource.DataSourceTransactionManager\">        <property name=\"dataSource\" ref=\"dataSource\" />    </bean>    <!-- д֪ͨǿ֪ͨҪд;ִϸ -->    <tx:advice id=\"txAdvice\" transaction-manager=\"txManager\">        <tx:attributes>            <!-- 㷽飬nameʾƣ*ʾⷽƣpropagationôΪread-onlyʾ뼶Ƿֻ -->            <tx:method name=\"find*\" propagation=\"SUPPORTS\"                rollback-for=\"Exception\" />            <tx:method name=\"*\" propagation=\"REQUIRED\" isolation=\"DEFAULT\"                read-only=\"false\" />        </tx:attributes>    </tx:advice>    <!-- aopдSpringԶĿɴҪʹAspectJıʽ -->    <aop:config>        <!--  -->        <aop:pointcut expression=\"execution(* com.mengma.service.*.*(..))\"            id=\"txPointCut\" />        <!-- 棺֪ͨ -->        <aop:advisor pointcut-ref=\"txPointCut\" advice-ref=\"txAdvice\" />    </aop:config></beans> ````  \nУ <beans> ǵĵ 613  14 дֱ AOP ռ 4250 дʹ <tx:advice> ֪ͨݡ  \n  \n 5258 дʹ  Ƕ棬е 54 дӦ AspectJ ʽ com.mengma.service зӦ򣬵 57 дʹ  ǽ֪ͨϣ AOP ʽɡ  \n  \n#### 7\\.   \n  \nĿ src Ŀ¼´ com.mengma.test İڸð´ AccountTestʾ  \n````  \npackage com.mengma.test;  \nimport org.junit.Test;  \nimport org.springframework.context.ApplicationContext;  \nimport org.springframework.context.support.ClassPathXmlApplicationContext;  \nimport com.mengma.service.AccountService;  \npublic class AccountTest {  \n@Test    public void test() {        // Spring  \nString xmlPath = \"applicationContext.xml\";        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(                xmlPath);        AccountService accountService = (AccountService) applicationContext                .getBean(\"accountService\");        accountService.transfer(\"zhangsan\", \"lisi\", 100);    }} ````  \nģתҵ񣬴 zhangsan ˻ lisi ˻ת 100 Ԫʹ JUnit  test() гɹ󣬲ѯ account ͼ 3 ʾ\n\nͼ 3 ĲѯпԿzhangsan ɹ lisi ת 100 Ԫ\n\n![ѯ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/763_9ae_de5.PNG)  \nͼ 3  ѯ\n\nͨ޸İģתʧܵڵ transfer() һд롰int i=1/0ģϵͳϵʾ\n````  \n public void transfer(String outUser, String inUser, int money) {    this.accountDao.out(outUser, money);    //ģϵ  \n    int i = 1/0;    this.accountDao.in(inUser, money);\n } \n````  \n    \n² test() JUnit ̨Ϣͼ 4 ʾ  \n  \n![̨](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/764_7a3_f52.png)  \nͼ 4  ̨  \n  \nͼ 4 пԿִвԷʱ˳ 0 쳣Ϣʱٴβѯ account ѯͼ 5 ʾ  \n  \nͼ 5 ĲѯпԿеݲûз仯ڳִй׳쳣ύתʧܡɴ˿֪Spring Чˡ  \n  \n![ѯ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/763_9ae_de5.PNG)  \nͼ 5  ѯ  \n  \n# SpringʽAnnotationעⷽʽʵ֣  \n  \n [Spring](http://www.voidme.com/spring) Уʹû XML ķʽʵʽ⣬ͨ Annotation עķʽʵʽ  \n  \nʹ Annotation ķʽǳ򵥣ֻҪĿ£¡  \n  \n#### 1 Spring עʾ  \n````  \n<tx:annotation-driven transaction-manager=\"txManager\"/>\n````  \n#### 2Ҫʹҵ߷ע @Transactional @Transactional Ĳ @Transactional Ĳͼ 1 ʾ  \n  \n![@Transactionalб](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/765_7af_ff7.png)  \nͼ 1  @Transactionalб  \n  \nͨ޸ġ [SpringXMLʵ](http://www.voidme.com/spring/spring-transaction-management-by-xml)̳ת˵İʹ Annotation עķʽʵ Spring ʽ  \n  \n#### 1\\. ע  \n  \n޸ Spring ļ applicationContext.xml޸ĺʾ  \n\n\n````  \n<?xml version=\"1.0\" encoding=\"UTF-8\"?>  \n<beans xmlns=\"http://www.springframework.org/schema/beans\"  \nxmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"    xmlns:context=\"http://www.springframework.org/schema/context\"    xmlns:tx=\"http://www.springframework.org/schema/tx\"    xmlns:aop=\"http://www.springframework.org/schema/aop\"    xsi:schemaLocation=\"http://www.springframework.org/schema/beans            http://www.springframework.org/schema/beans/spring-beans-2.5.xsd            http://www.springframework.org/schema/context  \nhttp://www.springframework.org/schema/context/spring-context.xsd            http://www.springframework.org/schema/tx            http://www.springframework.org/schema/tx/spring-tx-2.5.xsd            http://www.springframework.org/schema/aop            http://www.springframework.org/schema/aop/spring-aop-2.5.xsd\">    <!-- propertiesļ -->    <context:property-placeholder location=\"classpath:c3p0-db.properties\" />    <!-- ԴȡpropertiesļϢ -->    <bean id=\"dataSource\" class=\"com.mchange.v2.c3p0.ComboPooledDataSource\">        <property name=\"driverClass\" value=\"${jdbc.driverClass}\" />        <property name=\"jdbcUrl\" value=\"${jdbc.jdbcUrl}\" />        <property name=\"user\" value=\"${jdbc.user}\" />        <property name=\"password\" value=\"${jdbc.password}\" />    </bean>    <!-- jdbcģ -->    <bean id=\"jdbcTemplate\" class=\"org.springframework.jdbc.core.JdbcTemplate\">        <property name=\"dataSource\" ref=\"dataSource\" />    </bean>    <!-- dao -->  \n<bean id=\"accountDao\" class=\"com.mengma.dao.impl.AccountDaoImpl\">        <property name=\"jdbcTemplate\" ref=\"jdbcTemplate\" />    </bean>    <!-- service -->  \n<bean id=\"accountService\" class=\"com.mengma.service.impl.AccountServiceImpl\">        <property name=\"accountDao\" ref=\"accountDao\" />    </bean>    <!-- Դ -->    <bean id=\"txManager\"        class=\"org.springframework.jdbc.datasource.DataSourceTransactionManager\">        <property name=\"dataSource\" ref=\"dataSource\" />    </bean>    <!-- ע -->    <tx:annotation-driven transaction-manager=\"txManager\"/></beans> ````  \n````\nпԿԭļȣֻ޸֣Ӳע\n\nҪעǣѧϰ AOP עⷽʽʱҪļпע⴦ָɨЩµע⣬ûпע⴦Ϊڵ 3335 ֶ AccountServiceImpl @Transactional עڸУԻֱЧ\n\n#### 2\\.  @Transactional ע\n\n޸ AccountServiceImplļ @Transactional ע⼰Ӻʾ\n````  \npackage com.mengma.service.impl;  \n  \nimport org.springframework.transaction.annotation.Isolation;  \nimport org.springframework.transaction.annotation.Propagation;  \nimport org.springframework.transaction.annotation.Transactional;  \n  \nimport com.mengma.dao.AccountDao;  \n  \n@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, readOnly = false)  \npublic class AccountServiceImpl {  \n    private AccountDao accountDao;  \n    public void setAccountDao(AccountDao accountDao) {        this.accountDao = accountDao;    }  \n    public void transfer(String outUser, String inUser, int money) {        this.accountDao.out(outUser, money);        // ģϵ  \n        int i = 1 / 0;        this.accountDao.in(inUser, money);    \n}}\n````\n\nҪעǣʹ @Transactional עʱ֮áзָ  \n  \nʹ JUnit ٴ test() ʱ̨ͬͼ 2 ʾ쳣Ϣ˵ʹû Annotation עķʽͬʵ Spring ʽע͵ģϵĴвԣת˲ɡ  \n  \n![н](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/764_7a3_f52.png)  \nͼ 2  н  \n  \n# ο  \nhttps://www.w3cschool.cn/wkspring  \nhttps://www.runoob.com/w3cnote/basic-knowledge-summary-of-spring.html  \nhttp://codepub.cn/2015/06/21/Basic-knowledge-summary-of-Spring  \nhttps://dunwu.github.io/spring-tutorial  \nhttps://mszlu.com/java/spring  \nhttp://c.biancheng.net/spring/aop-module.html"
  },
  {
    "path": "docs/Spring全家桶/Spring/Spring合集.md",
    "content": "# Spring\n\n\n# SpringԴ\n\n# SpringMVC\n\n# SpringMVC Դ\n\n# SpringBoot\n## springbootǰ\n## springbootĻʹ\n## springbootĳע\n## springbootĺ\n## springbootĻԭ\n## springbootԴ\n\n# SpringBoot Դ\n\n# SpringCloud \n# SpringCloud Դ\n\n"
  },
  {
    "path": "docs/Spring全家桶/Spring/Spring容器与IOC.md",
    "content": "IoC  Inversion of Control ļдΪƷתһżһ˼룬һҪ̷ָܹƳϡĳ\n\nSpring ͨ IoC  Java ʵͳʼƶ֮ϵǽ IoC  Java Ϊ Spring Beanʹùؼ new  Java ûκ\n\nIoC  Spring Ҫĺ֮һᴩ Spring ӵɳ̡\n\n## ƷתIoC\n\nڴͳ Java ӦУһҪһеԻ򷽷ͨͨ new Object() ķʽߵĶ󴴽ȻʵԻ򷽷ĵáΪ˷ǿԽǰ߳Ϊߡ߳ΪߡҲ˵ű߶󴴽ĿȨ\n\n Spring ӦУJava 󴴽ĿȨ IoC ģ²¡\n\nԱͨ XML ļע⡢Java ȷʽ Java ж壬 XML ļʹ <bean> ǩ Java ʹ @Component עȡ \nSpring ʱIoC Զݶ壬Щ󴴽Щ IoC Ķ󱻳Ϊ Spring Bean \nҪʹĳ Bean ʱֱӴ IoC лȡͨ ApplicationContext  getBean() Ҫֶͨ루 new Obejct() ķʽ\n\nIoC ı䲻ǴģǴ˼Ϸˡӻλĸı䡣ԭһҪʹʲôԴͻԼ Spring ӦУIoC Ȩ˱һĵȴ IoC ҪĶBean\n\nְ淢˿ȨķתԭͨʵֵĶĴת IoC æʵ֣ǽ̳Ϊ Spring ġƷת\n\n## ע루DI\n\n˽ IoC ֮ǻҪ˽һǳҪĸע롣\n\nע루Denpendency InjectionдΪ DI Martin Fowler  2004 ڶԡƷתнʱġMartin Fowler ΪƷתһʺܻɬ޷˺ֱӵ⡰ﷴתˡʹáע롱桰Ʒת\n\nУͶ֮ǴһֽĹϵ˵ϵһҪõһ󣬼дһԣһĶ\n\n磬һΪ B  Java ࣬Ĵ¡\n\n\n\n\n\n \n\npublic class  B { \nString  bid; \nA  a; \n}\n\n \n\n\n\n\n\nӴԿB дһ A ͵Ķ aʱǾͿ˵ B Ķڶ aעǾǻ֡ϵġ\n\n֪Ʒת˼ Spring Ĵڶ󴴽УSpring ԶϵĶע뵽ǰУνġע롱\n\nע뱾 [Spring Bean ע](http://c.biancheng.net/spring/attr-injection.html)һֻ֣һԶѡ\n\n## IoC Ĺԭ\n\n Java Уϵͳеĸ֮䡢ģ֮䡢ϵͳӲϵͳ֮䣬ٶһϹϵ\n\nһϵͳ϶ȹߣôͻά⣬ȫûϵĴ뼸޷κιڼеĹܶҪ֮໥Э໥ɡڳʱе˼һ㶼ڲӰϵͳܵǰ£޶ȵĽ϶ȡ\n\nIoC ײͨģʽJava ķơXML ȼ϶Ƚ͵޶ȣҪ¡\n\nļ Bean.xmlУԸԼ֮ϵã\nǿ԰ IoC һĲƷ Spring Bean\nʱزЩļõĻϢԼ֮ϵ\nIoC  Java ķƣӦĶ󣨼 Spring Beanϵע뵽ĶС\n\nڶĻϢ֮ϵļжģûڴнϣ˼ʹı䣬ҲֻҪļн޸ļɣ Java ޸ģ Spring IoC ʵֽԭ\n\n## IoC ʵ\n\nIoC ˼ IoC ʵֵģIoC ײʵһ Bean Spring Ϊṩֲͬ IoC Ƿֱ BeanFactory  ApplicationContext\n\n### BeanFactory\n\nBeanFactory  IoC Ļʵ֣Ҳ Spring ṩ򵥵 IoC ṩ IoC Ĺܣ org.springframework.beans.factory.BeanFactory ӿڶ塣\n\nBeanFactory أlazy-loadƣڼļʱ̴ Java ֻглȡʹãԶʱŻᴴ\n\n#### ʾ 1\n\nͨһʵʾʾ BeanFactory ʹá\n\n1\\.  HelloSpring ĿУ MainApp Ĵ޸Ϊʹ BeanFactory ȡ HelloWorld Ķ󣬾¡\n\n\n\n\n\n \n\npublic static void main(String[] args) {\nBeanFactory  context = new ClassPathXmlApplicationContext(\"Beans.xml\");\nHelloWorld  obj = context.getBean(\"helloWorld\", HelloWorld.class);\nobj.getMessage();\n}\n\n \n\n\n\n\n\n2.  MainApp.javą¡\n\n message : Hello World! \n\n> ע⣺BeanFactory  Spring ڲʹýӿڣͨ²ṩԱʹá\n\n### ApplicationContext\n\nApplicationContext  BeanFactory ӿڵӽӿڣǶ BeanFactory չApplicationContext  BeanFactory ĻҵĹܣ AOP̣ʻֵ֧ȡ\n\nApplicationContext ӿõʵ࣬±\n\n| ʵ |  | ʾ |\n| --- | --- | --- |\n| ClassPathXmlApplicationContext | · ClassPath ָ XML ļ ApplicationContext ʵ | ApplicationContext applicationContext = new ClassPathXmlApplicationContext(String configLocation); |\n| FileSystemXmlApplicationContext | ָļϵͳ·ָ XML ļ ApplicationContext ʵ | ApplicationContext applicationContext = new FileSystemXmlApplicationContext(String configLocation); |\n\n> ϱʾУ configLocation ָ Spring ļƺλã Beans.xml\n\n#### ʾ 2\n\nǾͨһʵʾ ApplicationContext ʹá\n\n1. ޸ HelloSpring Ŀ MainApp  main() Ĵ룬¡\n\n\n\n\n\n \n````\npublic static void main(String[] args) {\n//ʹ FileSystemXmlApplicationContext ָ·µļ Bean.xml\nBeanFactory  context = new FileSystemXmlApplicationContext(\"D:\\\\eclipe workspace\\\\spring workspace\\\\HelloSpring\\\\src\\\\Beans.xml\");\nHelloWorld  obj = context.getBean(\"helloWorld\", HelloWorld.class);\nobj.getMessage();\n}\n````\n \n\n\n\n\n\n2.  MainApp.javą¡\n\n message : Hello World! \n\n# ο\nhttps://www.w3cschool.cn/wkspring\nhttps://www.runoob.com/w3cnote/basic-knowledge-summary-of-spring.html\nhttp://codepub.cn/2015/06/21/Basic-knowledge-summary-of-Spring\nhttps://dunwu.github.io/spring-tutorial\nhttps://mszlu.com/java/spring\nhttp://c.biancheng.net/spring/aop-module.html"
  },
  {
    "path": "docs/Spring全家桶/Spring/Spring常见注解.md",
    "content": "# Springע\n## 1 \n\nǶ֪SpringĵԾIOC+AOPIOCԭʵһSpringSpring Beanʵ\nDIҲע룬ΪҪĵĺĻ⣬עעĸҪȷ֪ġ\nǰϰxmlļһbeanڣǸʹעʹDIĹ\n\n\nǿʹ org.springframework.beans.factory.annotation  org.springframework.context.annotation еע Spring DI Ĺܡ\n\nͨЩΪSpring ע͡ǽڱ̳жлعˡ\n\n\n## 2 DIע\n\n### 2.1 @Autowired\n\nǿʹ @Autowired  Spring Ҫע\nǿԽע빹캯setter ֶעһʹá\nConstructor injection:\n\n**ע**\n\n````\nclass Car {\n    Engine engine;\n\n    @Autowired\n    Car(Engine engine) {\n        this.engine = engine;\n    }\n}\n````\n\n**Setterע**\n````\nclass Car {\n    Engine engine;\n\n    @Autowired\n    void setEngine(Engine engine) {\n        this.engine = engine;\n    }\n}\n````\n**ע**\n````\nclass Car {\n    @Autowired\n    Engine engine;\n}\n````\n\n@Autowired һΪ required ĲĬֵΪ true\n\nҲʵ bean ʱ Spring Ϊ Ϊ true ʱ׳쳣κݡ\n\nע⣬ʹù캯ע룬й캯ǿԵġ\n\n 4.3 汾ʼǲҪʽʹ @Autowired ע⹹캯캯\n\n### 2.2 @Bean\n\n@Bean ʵ Spring bean Ĺ\n\n```\n@Bean\nEngine engine() {\n    return new Engine();\n}\n````\n\nҪ͵ʵʱSpring Щ\n\nɵ bean 빤ͬ Բͬķʽǿʹôע͵ƻֵֵǲƵı\n\n````\n@Bean(\"engine\")\nEngine getEngine() {\n    return new Engine();\n}\n````\nһַǳbeanʽΪܶbeanһʼڴﶨõģҪʱа蹹\n\nǿɵͶBeanҲԸԶbeanơ\n\nע⣬@Bean ע͵ķ@Configuration С\n\n### 2.3 @Qualifier\n\nʹ@Qualifier @Autowired ṩҪڲȷʹõbean id bean ơ\n\n磬 bean ʵͬĽӿڣ\n````\nclass Bike implements Vehicle {}\n\nclass Car implements Vehicle {}\n\n````\n\n Spring Ҫעһ Vehicle beanԶƥ䶨 £ǿʹ @Qualifier עʽṩ bean ơ\n\n**ע**\n````\n@Autowired\nBiker(@Qualifier(\"bike\") Vehicle vehicle) {\nthis.vehicle = vehicle;\n}\n````\n\n**Setterע**\n\n````\n@Autowired\nvoid setVehicle(@Qualifier(\"bike\") Vehicle vehicle) {\nthis.vehicle = vehicle;\n}\n````\n:\n\n````\n@Autowired\n@Qualifier(\"bike\")\nvoid setVehicle(Vehicle vehicle) {\nthis.vehicle = vehicle;\n````\n**ע**\n\n````\n@Autowired\n@Qualifier(\"bike\")\nVehicle vehicle;\n````\nעǿƽõĲ࣬ǵһӿжʵʱͻᾭó\n\n### 2.4 @Required\n\n@Required  setter ϱҪͨ XML \n````\n@Required\nvoid setColor(String color) {\nthis.color = color;\n}\n````\nxml\n````\n<bean class=\"com.baeldung.annotations.Bike\">\n    <property name=\"color\" value=\"green\" />\n</bean>\n````\n򣬽׳ BeanInitializationException\nǳټ÷֪һ¾\n\n### 2.5 @Value\nǿʹ @Value ֵע bean 빹캯setter ֶעݡ\n\nҲǷǳõһע⣬ΪǺܶʱҪapplication.propertiesļȡֵ\n\n**ע**\n````\nEngine(@Value(\"8\") int cylinderCount) {\nthis.cylinderCount = cylinderCount;\n}\n````\n\n**setterע**\n\n````\n@Autowired\nvoid setCylinderCount(@Value(\"8\") int cylinderCount) {\nthis.cylinderCount = cylinderCount;\n}\n````\n\n:\n````\n\n@Value(\"8\")\nvoid setCylinderCount(int cylinderCount) {\nthis.cylinderCount = cylinderCount;\n}\n````\n\n**ע**\n````\n@Value(\"8\")\nint cylinderCount;\n````\n\nȻע뾲ֵ̬ûõġ ˣǿ @Value ʹռλַⲿԴжֵ .properties  .yaml ļС\n````\n\nengine.fuelType=petrol\n````\n\nǿͨ·ʽע engine.fuelType ֵ\n\n````\n@Value(\"${engine.fuelType}\")\nString fuelType;\n````\n\nʹ SpEL ʹ@Value ߼ʾǹ@Value ҵ\n\n### 2.6 @DependsOn\nǿʹôע Spring ע bean ֮ǰʼ bean ͨΪԶģ bean ֮ʽϵ\n\nֻʽʱҪע⣬JDBCػ߾̬ʼ\n\nǿָ bean Ƶʹ @DependsOn ע͵ֵҪһ bean Ƶ飺\n\n````\n@DependsOn(\"engine\")\nclass Car implements Vehicle {}\n````\nAlternatively, if we define a bean with the @Bean annotation, the factory method should be annotated with @DependsOn:\n````\n@Bean\n@DependsOn(\"fuel\")\nEngine engine() {\nreturn new Engine();\n}\n````\n### 2.7 @Lazy\nسʼǵ bean ʱʹ @Lazy Ĭ£Spring Ӧóĵ/ʱеشе bean\n\nǣЩҪʱ beanӦóʱ\n\nעΪǷȷλöͬ ǿ԰ڣ\n\nһ @Bean ע͵ bean ӳٷã˴ bean\n@Configuration а@Bean ܵӰ\n\nһ @Component ࣬ @Configuration ࣬ bean ӳٳʼ\n\n@Autowired 캯setter ֶΣӳټͨ\n\n````\n@Configuration\n@Lazy\nclass VehicleFactoryConfig {\n\n    @Bean\n    @Lazy(false)\n    Engine engine() {\n        return new Engine();\n    }\n}\n````\nͬһõע⡣\n\nάһдbeanĿʱᷢкܶbeanܶǰʹõģһҪʼʱͽгʼ԰ǽʡܶʱܡ\n\n\n### 2.8 @Lookup\nͬһȽõע\n\n@Lookup  Spring ǵʱط͵ʵ\n\nϣSpring Ǵע͵ķʹǷķͺͲΪ BeanFactory#getBean Ĳ\n\n@Lookup ڣ\n\nԭ bean עԼbean Provider\n\nɾӵһԭ Spring beanôǼ⣺\n\nǵĵ Spring bean ηЩԭ Spring bean\n\nڣProvider ϶һַʽ @Lookup ĳЩͨá\n\nҪעǣspringĬʹõĵbeanҪעԭbeanǲҪĶ⹤\n\nȣǴһԭ beanԺǽע뵽 bean У\n````\n@Component\n@Scope(\"prototype\")\npublic class SchoolNotification {\n// ... prototype-scoped state\n}\n````\nʹ@Lookupǿͨ bean ȡ SchoolNotification ʵ\n\n````\n@Component\npublic class StudentServices {\n\n    // ... member variables, etc.\n\n    @Lookup\n    public SchoolNotification getNotification() {\n        return null;\n    }\n\n    // ... getters and setters\n}\n````\nUsing @Lookup, we can get an instance of SchoolNotification through our singleton bean:\n````\n@Test\npublic void whenLookupMethodCalled_thenNewInstanceReturned() {\n// ... initialize context\nStudentServices first = this.context.getBean(StudentServices.class);\nStudentServices second = this.context.getBean(StudentServices.class);\n\n    assertEquals(first, second); \n    assertNotEquals(first.getNotification(), second.getNotification()); \n}\n````\nע⣬ StudentServices Уǽ getNotification Ϊ\n\nΪ Spring ͨ beanFactory.getBean(StudentNotification.class) ˸÷ǿԽա\n\n\n### 2.9 @Primary\nʱҪͬ͵bean Щ£ע뽫ɹΪ Spring ֪Ҫĸ bean\n\nѾ˴ѡ@Qualifier нߵ㲢ָ bean ơ\n\nȻʱҪһض beanҪ bean\n\nǿʹ@Primary @Primary õbeanunqualifiedעϱѡ\n\n````\n@Component\n@Primary\nclass Car implements Vehicle {}\n\n@Component\nclass Bike implements Vehicle {}\n\n@Component\nclass Driver {\n@Autowired\nVehicle vehicle;\n}\n\n@Component\nclass Biker {\n@Autowired\n@Qualifier(\"bike\")\nVehicle vehicle;\n}\n````\nǰʾУҪ ˣ Driver УSpring עһ Car bean Ȼ Biker bean Уֶ vehicle ֵһ Bike Ϊqualifiedġ\n\n### 2.10 @Scope\n\nͨӦ˵beanscopeĬ϶ǵģʵspring beanֶֶ֧÷ΧŲͬڡ\n\nʹ@Scope @Component @Bean ķΧ ǵԭ͡󡢻ỰglobalSession һЩԶ巶Χ\n\nӦöֵΪ\n````\nsingleton\nprototype\nrequest\nsession\napplication\nwebsocket\n````\n\n\n````\n@Component\n@Scope(\"prototype\")\nclass Engine {}\n````\nǿһЩrequestsessionwebsocketbeanͨӦǺصģô洢ûϢsession֮bean\n\nôʹãҪľ峡ѡˣһܴĻ⣬ȲչˣԺڵܡ\n\n## 3 ע\nǿʹñעӦóġ\n\n\n### 3.1 @Profile\n\nϣ Spring ضļڻ״̬ʱʹ@Component @Bean ǿʹ@Profile бǡ\n\nǿʹע͵ֵļƣ\n\nͨעòͬá\n\n\n````\npublic interface DatasourceConfig {\npublic void setup();\n}\n````\n\nǿã\n\n````\n@Component\n@Profile(\"dev\")\npublic class DevDatasourceConfig implements DatasourceConfig {\n@Override\npublic void setup() {\nSystem.out.println(\"Setting up datasource for DEV environment. \");\n}\n}\n````\nã\n\n````\n@Component\n@Profile(\"production\")\npublic class ProductionDatasourceConfig implements DatasourceConfig {\n@Override\npublic void setup() {\nSystem.out.println(\"Setting up datasource for PRODUCTION environment. \");\n}\n}\n````\nȻҲʹxml͵ļbean\n\nxml\n````\n<beans profile=\"local\">\n    <bean id=\"localDatasourceConfig\" \n      class=\"org.test.profiles.LocalDatasourceConfig\" />\n</beans>\n````\n### 3.2 @Import\n\nǿʹض @Configuration ࣬ʹôעɨ衣 ǿΪЩṩ@Import ֵ\n\n˵Ҫõ@ConfigurationעbeanôspringӦñҪɨ赽Ŀ¼ǡûжԸ·ɨ裬ֻʹ·µĵ࣬ôǾͿʹ@Importˡ\n\nע⻹Ƿǳõģһ\n\n````\n@Import(VehiclePartSupplier.class)\nclass VehicleFactoryConfig {}\n\n@Configuration\nclass VehiclePartSupplier{\n}\n````\n\n### 3.3 @ImportResource\n\n˵һ bean.xml ļҪ beans.xml ж bean 뵽 Spring Boot Уβأ\n\n1.Spring ʽļ bean.xml ˴ٸʾ˵ xml һ helloServiceʾ\n````\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<beans xmlns=\"http://www.springframework.org/schema/beans\"\nxmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\nxsi:schemaLocation=\"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd\">\n\n    <!-- HelloService xmlķʽ,ע뵽-->\n    <bean id=\"helloService\" class=\"com.demo.springboot.service.HelloService\"></bean>\n</beans>\n````\n2.ʹ@ImportResourceע⣬ xml \n````\n/**\n * Spring BootûSpringļԼдļҲԶʶ\n * SpringļЧصSpring \n * ʹ@ImportResourceע⣬עһ(˴)\n */\n@SpringBootApplication\n@ImportResource(locations = {\"classpath:beans.xml\"})\npublic class BootApplication {\n\n    public static void main(String[] args) {\n        // SpringӦ         \n        SpringApplication.run(BootApplication.class,args);\n\n    }\n}\n\n````\n### 3.4 @PropertySource\nͨע⣬ǿΪӦóöļ\n\n@PropertySource עṩһַԻƣڽ PropertySource ӵ Spring  Environment У @Configuration һʹá\n\nʹ @Value ȥöԣ磺@Value(\"testbean.name\")ҲָĬֵ磺@Value(\"testbean.name:defaultValue\")\n\n÷ʾ\n\nһļapp.properties\n````\ntestbean.name=myTestBean\n````\n @Configuration ʹ @PropertySource  app.properties ø Environment  PropertySources ϡ\n````\n@Configuration\n@PropertySource(\"classpath:/com/myco/app.properties\")\npublic class AppConfig {\n\n    @Autowired\n    Environment env;\n \n    @Bean\n    public TestBean testBean() {\n        TestBean testBean = new TestBean();\n        testBean.setName(env.getProperty(\"testbean.name\"));\n        return testBean;\n    }\n}\n````\n\nע⣺ʹ @Autowired  Environment ע뵽УȻ testBean() ʹá\nУ testBean.getName() ءmyTestBeanַ\n\n@PropertySource  Java 8 ظעԣζǿαһࣺ\n\n````\n@Configuration\n@PropertySource(\"classpath:/annotations.properties\")\n@PropertySource(\"classpath:/vehicle-factory.properties\")\nclass VehicleFactoryConfig {}\n````\n\n### 3.5 @PropertySources\n÷ͬϣֻһǿʹעָ@PropertySource ã\n````\n@Configuration\n@PropertySources({\n@PropertySource(\"classpath:/annotations.properties\"),\n@PropertySource(\"classpath:/vehicle-factory.properties\")\n})\nclass VehicleFactoryConfig {}\n````\nע⣬ Java 8 ǿͨظע͹ʵͬĹܡ\n\n## 4.\n\nڱУǿ Spring ע͵ĸ ǿ bean ӺӦģԼΪɨࡣ\n\nspringϵеĳע⻹кܶ࣬һƪ²ȫǣ©ӭ䡣\n\n# Spring Beanע\n\n## 1 \nڱ̳Уǽڶ岻ͬ bean  Spring bean ע͡\n\nмַ Spring  bean ȣǿʹ XML ǡ ǻʹ@Bean ע bean\n\nǿʹ org.springframework.stereotype еע֮һǸ࣬ಿɨ衣\n\n## 2 @ComponentScan\nǾʹõһע⣬ǵӦУʱһɨеİرǵҪɨⲿjarеbeanʱǳá\n\nԼSpringBootApplicationϣҲԼ@configurationעϵ\n\nɨ裬Spring Զɨе bean\n\n@ComponentScan ʹעɨЩࡣ \n\nǿֱʹ basePackages  value ֮һָƣvalue  basePackages ı\n\n````\n@Configuration\n@ComponentScan(basePackages = \"com.baeldung.annotations\")\nclass VehicleFactoryConfig {}\n````\n⣬ǿʹ basePackageClasses ָеࣺ\n\n````\n@Configuration\n@ComponentScan(basePackageClasses = VehicleFactoryConfig.class)\nclass VehicleFactoryConfig {}\n````\n\n飬ǿΪÿṩ\n\nδָɨ跢ڴ @ComponentScan עͬһС\n\n@ComponentScan  Java 8 ظעԣζǿαһࣺ\n\n````\n@Configuration\n@ComponentScan(basePackages = \"com.baeldung.annotations\")\n@ComponentScan(basePackageClasses = VehicleFactoryConfig.class)\nclass VehicleFactoryConfig {}\n````\n\nߣǿʹ @ComponentScans ָ @ComponentScan ã\n\n````\n@Configuration\n@ComponentScans({\n@ComponentScan(basePackages = \"com.baeldung.annotations\"),\n@ComponentScan(basePackageClasses = VehicleFactoryConfig.class)\n})\n````\n````\nclass VehicleFactoryConfig {\n}\n````\nʹ XML ʱɨͬ򵥣\n\n````\n<context:component-scan base-package=\"com.baeldung\"/>\n````\n\n### 3 @Component\n\n@Component ༶ע⡣ ɨڼ䣬Spring Framework Զʹ@Component עࣺ\n````\n@Component\nclass CarUtility {\n// ...\n}\n````\n\nĬ£ bean ʵͬĸСд ⣬ǿʹôע͵Ŀѡֵָͬơ\n\n@Repository@Service@Configuration @Controller Ǵ@Component ע⣬ǹͬbean Ϊ \n\nSpring ɨԶǡ\n\nͨ˵ǻmvcӦǻõע⣬ڷwebӦиؿʹ@componentעbean\n\n### 4 @Repository\n\nDAO or Repository classes usually represent the database access layer in an application, and should be annotated with @Repository:\n````\n@Repository\nclass VehicleRepository {\n// ...\n}\n````\nʹôע͵һŵԶ־쳣ת ʹó־Կܣ Hibernateʱʹ @Repository ע͵׳ı쳣ԶתΪ Spring  DataAccessExeption ࡣ\n\nҪ쳣תҪԼ PersistenceExceptionTranslationPostProcessor bean\n````\n@Bean\npublic PersistenceExceptionTranslationPostProcessor exceptionTranslation() {\nreturn new PersistenceExceptionTranslationPostProcessor();\n}\n````\nע⣬ڴ£Spring Զִ衣\n\nͨ XML ã\n````\n<bean class=\n\"org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor\"/>\n````\n\n### 5 @Service\nӦóҵ߼ͨפڷУǽʹ@Service עָʾһڸò㣺\n\n````\n@Service\npublic class VehicleService {\n// ...    \n}\n````\n### 6 @Controller\n@Controller һ༶ע⣬ Spring Framework Ϊ Spring MVC еĿ\n\nspring@Controller עbeanܶ飬ǻSpringMVCص\n\n````\n@Controller\npublic class VehicleController {\n// ...\n}\n\n````\n## 7 @Configuration\n\n԰@Bean ע͵ bean 巽\n````\n@Configuration\nclass VehicleFactoryConfig {\n\n    @Bean\n    Engine engine() {\n        return new Engine();\n    }\n\n}\n````\n## 8 AOPע\nʹ Spring עʱ״һ㣬оض͵ΪĿꡣ\n\n磬 DAO 㷽ִʱ䡣 ǽ·棨ʹ AspectJ עͣ @Repository ͣ\n\n```\n@Aspect\n@Component\npublic class PerformanceAspect {\n@Pointcut(\"within(@org.springframework.stereotype.Repository *)\")\npublic void repositoryClassMethods() {};\n\n    @Around(\"repositoryClassMethods()\")\n    public Object measureMethodExecutionTime(ProceedingJoinPoint joinPoint) \n      throws Throwable {\n        long start = System.nanoTime();\n        Object returnValue = joinPoint.proceed();\n        long end = System.nanoTime();\n        String methodName = joinPoint.getSignature().getName();\n        System.out.println(\n          \"Execution of \" + methodName + \" took \" + \n          TimeUnit.NANOSECONDS.toMillis(end - start) + \" ms\");\n        return returnValue;\n    }\n}\n````\n\nڴʾУǴһ㣬ƥʹ@Repository ע͵ез Ȼʹ@Around ֪ͨλǸ㣬ȷطõִʱ䡣\n\n⣬ʹַǿΪÿӦó־¼ܹƺΪ\n\nȻˣaspectJע⻹ܶ࣬棬δҲᵥдĽܡ\n\n## 9 \n\nڱУǼ Spring עͲǸԴ͡\n\nǻѧϰʹɨҵע͵ࡣ\n\n˽Щעε¸ɾֲԼӦóע֮ķ롣 ǻʹøСΪǲҪֶʽ bean\n\n# ο\nhttps://www.baeldung.com/spring-annotations\n\n"
  },
  {
    "path": "docs/Spring全家桶/Spring/Spring概述.md",
    "content": "Spring  Java EE һĿԴܣɱΪSpring ֮ Rod Johnson  2002 ĿҪ Java ҵӦóĿѶȺڡ\n\nSpring Եһֱ󿪷ԱΪ Java ҵӦó򿪷ѡʱգSpring ٲȻΪ Java EE ʣΪ˹ Java EE Ӧõʵ׼\n\n## Spring ĵ뷢չ\n\nڵ J2EEJava EE ƽ̨Ƴ EJB ΪĵĿʽֿʽʵʵĿдֱ׶ˣʹøӡӷסǿڳֲѶȴȡ\n\nRod Johnson  2004 ĳ顶Expert One-on-One J2EE Development without EJBУ EJB ӷ׵Ľṹһķͷ񶨣ֱԸӼķʽ滻\n\nⱾУRod Johnson ͨһ 3 дĸչʾڲʹ EJB ¹һչ Java ӦóУRod Johnson длṹ룬аõ Java ӿں࣬ ApplicationContextBeanFactory ȡЩĸΪ com.interface21Ϊṩ 21 ͵һο\n\nⱾӰԶ Rod Johnson  com.interface21 Ĵ뿪Դ¿ܲΪSpringΪSpring һƴһɨƽͳ J2EE ĺ\n\n2003  2 £Spring 0.9 汾 Apache 2.0 ԴЭ飻2004  4 £Spring 1.0 汾ʽĿǰΪֹSpring Ѿ뵽˵ 5 汾Ҳǳ˵ Spring 5\n\n## Spring ͹\n\nڲͬﾳУSpring ĺǲͬġǾͷֱӡ塱͡塱Ƕȣ Spring нܡ\n\n###  SpringSpring ջ\n\nϵ Spring ָ Spring Framework Ϊĵ Spring ջ\n\nʮķչSpring ѾһӦÿܣ𽥷չΪһɶͬĿģ飩ɵĳ켼 Spring FrameworkSpring MVCSpringBootSpring CloudSpring DataSpring Security ȣ Spring Framework ĿĻ\n\nЩĿ˴ҵӦÿƼȸݣܹԱչвϲĸʵ⣬Ա˸õĿ顣\n\n| Ŀ |  |  \n| --- | --- |  \n| Spring Data | Spring ṩݷģ飬 JDBC  ORM ṩ˺ܺõ֧֡ͨԱʹһͳһķʽλڲͬݿеݡ |  \n| Spring Batch | һרҵϵͳеճܣܹԱĿ׳ЧӦó |  \n| Spring Security | ǰΪ Acegi Spring нϳģ֮һһԶƻ֤ͷʿƿܡ |  \n| Spring Mobile | Ƕ Spring MVC չƶ Web ӦõĿ |  \n| Spring Boot |  Spring Ŷṩȫ¿ܣΪ Spring ԼһЩ伴õãԼ Spring ӦõĴ̡ |  \n| Spring Cloud | һ Spring Boot ʵֵ΢ܡĳһżһϵ΢ܵ򼯺ϡϳġ֤΢ͨ Spring Boot ˼ٷװεиӵúʵԭΪԱṩһ׼׶ײάķֲʽϵͳ߰ |  \n\n###  SpringSpring Framework\n\n Spring ָ Spring FrameworkͨǽΪ Spring ܡ\n\nSpring һֲġ Java Ӧóһվʽ Spring ջĺĺͻΪ˽ҵӦÿĸԶġ\n\nSpring Ĳ֣ IoC  AOP\n\n|  |  |  \n| --- | --- |  \n| IOC | Inverse of Control ļдΪƷתָѴ̽ Spring й |  \n| AOP | Aspect Oriented Programming ļдΪ̡AOP װĹΪЩҵ޹أȴΪҵģͬõ߼װϵͳظ룬ģ϶ȡ⣬AOP һЩϵͳϵ⣬־Ȩ޵ȡ |  \n\nSpring һֻ Bean ı̵̼ظı Java 硣Spring ʹü򵥡 Java Bean ǰֻ EJB ɵĹʹúܶิӵĴźͼ࣬ EJB ӷסЧĿģʽķĿĺάչ\n\nʵʿУӦóͨϵֱܹΪֲ㣨webҵ߼㣨service־ò㣨dao\n\nSpring  Java EE ӦøĽÿһ㶼ṩ˼֧֡\n\n*   ڱֲṩ˶ Spring MVCStruts2 ȿܵϣ\n*   ҵ߼ṩ˹ͼ¼־Ĺܣ\n*   ڳ־ò㻹 MyBatisHibernate  JdbcTemplate ȼݿзʡ\n\nֵ Spring һȫĽЩѾнϺýSpring ظ顣\n\nϿSpring ܸ Java ԱߵɶȣҵĳҲṩõĽڿԴܵ˹㷺Ļӭұ󲿷ֹ˾Ϊ Java Ŀѡܡ\n\n## Spring Framework ص\n\nSpring ܾ¼ص㡣\n\n### **򻯿**\n\nSpring һ󹤳ԽжĴϵά Spring \n\n### **㼯ɸ**\n\nSpring ųĿԴܣڲṩ˶Ըܣ Struts2HibernateMyBatis ȣֱ֧֡\n\n### ** Java EE API ʹѶ**\n\nSpring  Java EE зǳõһЩ APIJDBCJavaMailԶ̵õȣṩ˷װʹЩ API ӦõѶȴ󽵵͡\n\n### **Ĳ**\n\nSpring ֧ JUnit4ͨעⷽز Spring \n\n### **AOP ̵֧**\n\nSpring ṩ̣ԷʵֶԳȨغмصȹܡ\n\n### **ʽ֧**\n\nֻҪͨþͿɶĹֶ̡\n\nSpring ܻҵӦÿĸ棬 20 ͬģ顣\n\n spring-aop      spring-context-indexer  spring-instrument  spring-orm    spring-web  \nspring-aspects  spring-context-support  spring-jcl         spring-oxm    spring-webflux  \nspring-beans    spring-core             spring-jdbc        spring-r2dbc  spring-webmvc  \nspring-context  spring-expression       spring-jms         spring-test   spring-websocket  \nspring-messaging   spring-tx     \n\n![Springϵṹͼ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/163550G63-0.png)  \nͼ1Springܹͼ\n\nͼа Spring ܵģ飬ЩģһҵӦÿڿпԸѡԵʹҪģ顣ֱЩģýм򵥽ܡ\n\n## 1\\. Data Access/Integrationݷʣɣ\n\nݷʣɲ JDBCORMOXMJMS  Transactions ģ飬¡\n\n*   JDBC ģ飺ṩһ JBDC ģ壬ʹЩģͳ߳ JDBC 뻹бƣܵ Spring ĺô\n*   ORM ģ飺ṩеġ-ϵӳ޷켯ɵ API JPAJDOHibernate  MyBatis ȡһʹ Spring \n*   OXM ģ飺ṩһ֧ Object /XML ӳĳʵ֣ JAXBCastorXMLBeansJiBX  XStream Java ӳ XML ݣ߽XML ӳ Java \n*   JMS ģ飺ָ Java Ϣṩһ ϢߡϢߡģڸӼ򵥵ʹ JMSJMS Ӧó֮䣬ֲʽϵͳзϢ첽ͨš\n*   Transactions ģ飺ֱ֧̺ʽ\n\n## 2\\. Web ģ\n\nSpring  Web  WebServletWebSocket  Portlet ¡\n\n*   Web ģ飺ṩ˻ Web ԣļϴܡʹõ Servlet  IOC ʼԼ Web Ӧġ\n*   Servlet ģ飺ṩһ Spring MVC Web ʵ֡Spring MVC ṩ˻עԴע롢򵥵ݰ󶨡֤ȼһ׷ǳõ JSP ǩȫ޷ Spring Э\n*   WebSocket ģ飺ṩ˼򵥵ĽӿڣûֻҪʵӦĽӿھͿԿٵĴ WebSocket ServerӶʵ˫ͨѶ\n*   Portlet ģ飺ṩ Portlet ʹ MVC ʵ֣ Web-Servlet ģĹܡ\n\n## 3\\. Core ContainerSpring ĺ\n\nSpring ĺģ齨Ļ Beans ģ顢Core ģ顢Context ģ SpEL ʽģɣûЩҲ AOPWeb ϲĹܡ¡\n\n*   Beans ģ飺ṩ˿ܵĻ֣Ʒתע롣\n*   Core ģ飺װ Spring ܵĵײ㲿֣ԴʡתһЩùࡣ\n*   Context ģ飺 Core  Beans ģĻ֮ϣ Beans ģ鹦ܲԴ󶨡֤ʻJava EE ֧֡ڡ¼ȡApplicationContext ӿģĽ㡣\n*   SpEL ģ飺ṩǿıʽַ֧֣֧ʺ޸ֵãַ֧ʼ޸顢֧߼㣬ִ֧ Spring ȡ BeanҲ֧бͶӰѡһбۺϵȡ\n\n## 4\\. AOPAspectsInstrumentation  Messaging\n\n Core Container ֮ AOPAspects ģ飬£\n\n*   AOP ģ飺ṩʵ֣ṩ־¼Ȩ޿ơͳƵͨùܺҵ߼ļ̬ܶİЩӵҪĴУ˾ְҵ߼ͨùܵϡ\n*   Aspects ģ飺ṩ AspectJ ļɣһǿҳ̣AOPܡ\n*   Instrumentation ģ飺ṩ๤ߵֺ֧ʵ֣ضӦ÷ʹá\n*   messaging ģ飺Spring 4.0 ԺϢSpring-messagingģ飬ģṩ˶ϢϵṹЭ֧֡\n\n## 5\\. Test ģ\n\nTest ģ飺Spring ֧ Junit  TestNG ԿܣһṩһЩ Spring ĲԹܣڲ Web ʱģ Http Ĺܡ"
  },
  {
    "path": "docs/Spring全家桶/Spring/第一个Spring应用.md",
    "content": "## \nǿȰ Spring  Jar Լ Commons-loggin 뵽ĿУӣҪٵ Spring  Jar\n\n````\norg.springframework.core-5.3.13.jar\norg.springframework.beans-5.3.13.jar\nspring-context-5.3.13.jar\nspring-expression-5.3.13.jar\ncommons.logging-1.2.jar\n````\nȻƼʹmaven\n\n##  Java \n    HelloSpring д net.biancheng.c Ȼ´ HelloWorld.java  MainApp.java ࡣ\n\nHelloWorld.java Ĵ\n````\npackage net.biancheng.c;\npublic class HelloWorld {\n    private String message;\n    public void setMessage(String message) {\n        this.message = message;\n    }\n    public void getMessage() {\n        System.out.println(\"message : \" + message);\n    }\n}\n````\nMainApp.java Ĵ\n````\npackage net.biancheng.c;\nimport org.springframework.context.ApplicationContext;\nimport org.springframework.context.support.ClassPathXmlApplicationContext;\n\n    public class MainApp {\n        public static void main(String[] args) {\n            ApplicationContext context = new ClassPathXmlApplicationContext(\"Beans.xml\");\n            HelloWorld obj = context.getBean(\"helloWorld\",HelloWorld.class);\n            obj.getMessage();\n        }\n    }\n````\nϴ룬Ҫע㣺\n\n ApplicationContext ʱʹ ClassPathXmlApplicationContext ࣬ڼ Spring ļͳʼжBean\nApplicationContext.getBean() ȡ Bean÷ֵΪ ObjectͨǿתΪ HelloWorld ʵ󣬵е getMessage() \n\n## ļ\n\n src Ŀ¼£һ Spring ļ Beans.xml¡\n````\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<beans xmlns=\"http://www.springframework.org/schema/beans\"\nxmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\nxsi:schemaLocation=\"http://www.springframework.org/schema/beans\nhttp://www.springframework.org/schema/beans/spring-beans-3.0.xsd\">\n    <bean id=\"helloWorld\" class=\"net.biancheng.c.HelloWorld\">\n        <property name=\"message\" value=\"Hello World!\" />\n    </bean>\n</beans>\n````\nҲԽļΪЧƣҪעǣļ MainApp.java жȡļһ¡\n\nBeans.xml ڸͬ Bean Ψһ IDӦ Bean Ըֵ磬ϴУǿڲӰ£ message ֵ\n## г\n\n    MainApp.javaEclipse IDE ̨ʾϢ¡\n   message : Hello World!\n\nˣǾͳɹ˵һ Spring Ӧó"
  },
  {
    "path": "docs/Spring全家桶/SpringBoot/SpringBoot中的任务调度与@Async.md",
    "content": "\n\n<header>\n\n# Spring Boot\n\nݽվѸѧϰʼǡܽоղء֤ȷԣʹöķ뱾վ޹أ\n\n</header>\n\n\n\n<script>( adsbygoogle = window.adsbygoogle || []).push({});</script>\n\n\n\nִضʱεĹ̡Spring BootΪSpringӦóϱдȳṩ˺ܺõ֧֡\n\n## Java Cronʽ\n\nJava CronʽCronTriggerʵ`org.quartz.Trigger`ࡣ йJava cronʽĸϢĴ -\n\n*   [https://docs.oracle.com/cd/E12058_01/doc/doc.1014/e12030/cron_expressions.html](https://docs.oracle.com/cd/E12058_01/doc/doc.1014/e12030/cron_expressions.html)\n\n`[@EnableScheduling](https://github.com/EnableScheduling \"@EnableScheduling\")`עΪӦóõȳ򡣽עӵSpring BootӦóļС\n\n```\n@SpringBootApplication\n@EnableScheduling\n\npublic class DemoApplication {\n   public static void main(String[] args) {\n      SpringApplication.run(DemoApplication.class, args);\n   }\n}\n\n```\n\n`[@Scheduled](https://github.com/Scheduled \"@Scheduled\")`עضʱڴȳ\n\n```\n@Scheduled(cron = \"0 * 9 * * ?\")\npublic void cronJobSch() throws Exception {\n}\n\n```\n\nһʾ룬ʾÿ9:00ʼÿ9:59ִ\n\n```\npackage com.yiibai.demo.scheduler;\n\nimport java.text.SimpleDateFormat;\nimport java.util.Date;\nimport org.springframework.scheduling.annotation.Scheduled;\nimport org.springframework.stereotype.Component;\n\n@Component\npublic class Scheduler {\n   @Scheduled(cron = \"0 * 9 * * ?\")\n   public void cronJobSch() {\n      SimpleDateFormat sdf = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss.SSS\");\n      Date now = new Date();\n      String strDate = sdf.format(now);\n      System.out.println(\"Java cron job expression:: \" + strDate);\n   }\n}\n\n```\n\nĻͼʾӦó`09:03:23`Ҵʱÿһִһcronҵȳ\n\n![](/uploads/images/2018/10/05/103218_77311.jpg)\n\n## ̶\n\n̶ʵȳضʱִȴǰһɡ ֵԺΪλ ʾʾڴ˴ -\n\n```\n@Scheduled(fixedRate = 1000)\npublic void fixedRateSch() { \n}\n\n```\n\n˴ʾӦóʱÿִʾ -\n\n```\npackage com.yiibai.demo.scheduler;\n\nimport java.text.SimpleDateFormat;\nimport java.util.Date;\nimport org.springframework.scheduling.annotation.Scheduled;\nimport org.springframework.stereotype.Component;\n\n@Component\npublic class Scheduler {\n   @Scheduled(fixedRate = 1000)\n   public void fixedRateSch() {\n      SimpleDateFormat sdf = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss.SSS\");\n\n      Date now = new Date();\n      String strDate = sdf.format(now);\n      System.out.println(\"Fixed Rate scheduler:: \" + strDate);\n   }\n}\n\n```\n\nעĻͼʾ`09:12:00`Ӧó֮ÿһ̶ʵȳִ\n\n![](/uploads/images/2018/10/05/103355_72877.jpg)\n\n## ̶ӳ\n\n̶ӳٵȳضʱִ Ӧõȴһɡ ֵӦԺΪλ ˴ʾʾ -\n\n```\n@Scheduled(fixedDelay = 1000, initialDelay = 1000)\npublic void fixedDelaySch() {\n}\n\n```\n\n`initialDelay`ڳʼӳֵ֮һִʱ䡣\n\nӦó`3`ÿִһʾʾ -\n\n```\npackage com.yiibai.demo.scheduler;\n\nimport java.text.SimpleDateFormat;\nimport java.util.Date;\nimport org.springframework.scheduling.annotation.Scheduled;\nimport org.springframework.stereotype.Component;\n\n@Component\npublic class Scheduler {\n   @Scheduled(fixedDelay = 1000, initialDelay = 3000)\n   public void fixedDelaySch() {\n      SimpleDateFormat sdf = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss.SSS\");\n      Date now = new Date();\n      String strDate = sdf.format(now);\n      System.out.println(\"Fixed Delay scheduler:: \" + strDate);\n   }\n}\n\n```\n\nִʾ`09:18:39`ʼӦóÿ`3`󣬹̶ӳټƻ(ÿִһ)\n\n\n\n\n\n//Ķhttps://www.yiibai.com/spring-boot/spring_boot_scheduling.html\n\n@EnableAsync ע\nҪʹ @AsyncҪʹ @EnableAsync ע⿪ Spring Boot е첽ԡ\n\n@Configuration\n@EnableAsync\npublic class AppConfig {\n}\nϸ˵ԲοAsyncConfigurer(opens new window)\n\n#@Async ע\n#ֵ֧÷\n1޷ֵ\n\n @Async עη첽ʽá仰˵ڵô˷ʱأʵִзύ Spring TaskExecutor С򵥵£ԽעӦڷ void ķʾʾ\n\n@Async\nvoid doSomething() {\n// this will be executed asynchronously\n}\n2޷ֵ\n\nʹ @Scheduled עע͵ķͬЩָΪʱɵԡʽãĵá磬´ @Async עĺϷӦã\n\n@Async\nvoid doSomething(String s) {\n// this will be executed asynchronously\n}\n3зֵ\n\n첽÷ֵķǣЩҪ Future ͵ķֵȻṩ첽ִеĺôԱ߿ڵ Future ϵ get() ֮ǰִʾʾڷֵķʹ@Async\n\n@Async\nFuture<String> returnSomething(int i) {\n// this will be executed asynchronously\n}\n#ֵ֧÷\n@Async ڻصһʹã @PostConstruct\n\nҪ첽ʼ Spring beanʹõĳʼ Spring beanȻĿϵ @Async ע͵ķʾʾ\n\npublic class SampleBeanImpl implements SampleBean {\n\n    @Async\n    void doSomething() {\n        // ...\n    }\n\n}\n\npublic class SampleBeanInitializer {\n\n    private final SampleBean bean;\n\n    public SampleBeanInitializer(SampleBean bean) {\n        this.bean = bean;\n    }\n\n    @PostConstruct\n    public void initialize() {\n        bean.doSomething();\n    }\n\n}\n#ȷִָ\nĬ£ڷָ @Async ʱʹõִ첽֧ʱõִʹ XML  AsyncConfigurer ʵ֣УΪ annotation-driven ԪءǣҪָʾִиʱӦʹĬִֵʹ @Async ע value ԡʾʾִд˲\n\n@Async(\"otherExecutor\")\nvoid doSomething(String s) {\n// this will be executed asynchronously by \"otherExecutor\"\n}\n£otherExecutor Spring κ Executor bean ƣҲκ Executor ޶ƣ磬ʹ <qualifier> Ԫػ Spring  @Qualifier עָ \n\n# @Async 쳣\n @Async ķֵΪ Future ʱ׹ڷִڼ׳쳣Ϊڵ get ʱ׳쳣ǣڷֵΪ void ͵ķ쳣ᱻ޷䡣ṩ AsyncUncaughtExceptionHandler 쳣ʾʾִд˲\n\npublic class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {\n\n    @Override\n    public void handleUncaughtException(Throwable ex, Method method, Object... params) {\n        // handle exception\n    }\n}\nĬ£¼쳣ʹ AsyncConfigurer  <taskannotation-driven /> XML ԪضԶ AsyncUncaughtExceptionHandler"
  },
  {
    "path": "docs/Spring全家桶/SpringBoot/SpringBoot中的日志管理.md",
    "content": "## 4\\. ־\n\n\n\n\n\nSpring Bootڲ־ʹ [Commons Logging](https://commons.apache.org/logging) Եײ־ʵֱֿš Ϊ [Java Util Logging](https://docs.oracle.com/javase/17/docs/api/java/util/logging/package-summary.html)  [Log4j2](https://logging.apache.org/log4j/2.x/)  [Logback](https://logback.qos.ch/) ṩĬá ÿһ£¼loggerԤΪʹÿ̨Ҳѡļ\n\n\n\n\n\nĬ£ʹ StarterĬʹLogback ʵLogback·ҲڣȷʹJava Util LoggingCommons LoggingLog4JSLF4Jⶼȷ\n\n\n\n\n\n|  | кܶJava־ܡ бܻң벻Ҫġ һ˵㲻Ҫı־Spring BootĬֵͺܺá |\n| --- | --- |\n\n\n\n\n\n|  | ӦóһservletӦ÷ʱJava Util Logging APIִе־ᱻ͵Ӧó־С ԷֹѾӦóִе־Ӧó־С |\n| --- | --- |\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/features.html#features.logging.log-format)4.1\\. ־ʽ\n\n\n\nSpring BootĬϵ־ʽӡ\n\n\n\n\n\n\n\n 2023-03-03T21:18:18.827+08:00  INFO 19388 --- [           main] o.s.b.d.f.s.MyApplication                : Starting MyApplication using Java 17 with PID 19388 (/opt/apps/myapp.jar started by myuser in /opt/apps/)\n2023-03-03T21:18:18.834+08:00  INFO 19388 --- [           main] o.s.b.d.f.s.MyApplication                : No active profile set, falling back to 1 default profile: \"default\"\n2023-03-03T21:18:20.439+08:00  INFO 19388 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)\n2023-03-03T21:18:20.461+08:00  INFO 19388 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]\n2023-03-03T21:18:20.461+08:00  INFO 19388 --- [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.5]\n2023-03-03T21:18:20.600+08:00  INFO 19388 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext\n2023-03-03T21:18:20.602+08:00  INFO 19388 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1685 ms\n2023-03-03T21:18:21.078+08:00  INFO 19388 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''\n2023-03-03T21:18:21.093+08:00  INFO 19388 --- [           main] o.s.b.d.f.s.MyApplication                : Started MyApplication in 2.998 seconds (process running for 3.601) \n\n\n\n\n\n\n\nĿ¡\n\n\n\n\n\n*   DateʱTimeȷ룬\n\n*   ־: `ERROR`, `WARN`, `INFO`, `DEBUG`,  `TRACE`.\n\n*   ID\n\n*   һ `---` ָʵ־ϢĿʼ\n\n*   ߳ƣڷУڿ̨ܻᱻضϣ\n\n*   ¼ƣͨԴƣͨд\n\n*   ־Ϣ\n\n\n\n\n\n|  | Logbackû `FATAL`  ӳ䵽 `ERROR` |\n| --- | --- |\n\n\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/features.html#features.logging.console-output)4.2\\. ̨\n\n\n\nĬ£־ `ERROR``WARN`  `INFO` Ϣ̨ Ҳͨ `--debug` ־Ӧó `debug` ģʽ\n\n\n\n\n\n\n\n```\n$ java -jar myapp.jar --debug\n```\n\n\n\n\n\n\n\n|  | Ҳ `application.properties` ָ `debug=true` |\n| --- | --- |\n\n\n\n\n\ndebugģʽʱһЩļ¼ǶʽHibernateSpring BootΪϢ debugģʽζŽӦóΪ `DEBUG` ¼Ϣ\n\n\n\n\n\n⣬ͨӦóʱʹ `--trace` ־ `application.properties` ʹ `trace=true`  trace ģʽ ԶһЩļ¼ǶʽHibernate schemaɺSpringϣиټ¼\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/features.html#features.logging.console-output.color-coded)4.2.1\\. ɫ\n\n\n\nն֧ANSIͻʹòɫĶ Խ `spring.output.ansi.enabled` Ϊ [ֵֵ֧](https://docs.spring.io/spring-boot/docs/3.1.0-SNAPSHOT/api/org/springframework/boot/ansi/AnsiOutput.Enabled.html)ԸԶ⡣\n\n\n\n\n\nɫͨʹ `%clr` תؼõġ 򵥵ʽУת־ɫʾ\n\n\n\n\n\n\n\n```\n%clr(%5p)\n```\n\n\n\n\n\n\n\n±־ɫӳϵ\n\n\n\n<colgroup><col><col></colgroup>\n| ־ | ɫ |\n| --- | --- |\n| `FATAL` |  |\n| `ERROR` |  |\n| `WARN` |  |\n| `INFO` |  |\n| `DEBUG` |  |\n| `TRACE` |  |\n\n\n\n⣬ҲͨΪתṩһѡָӦʹõɫʽ 磬ҪʹıΪɫʹá\n\n\n\n\n\n\n\n```\n%clr(%d{yyyy-MM-dd'T'HH:mm:ss.SSSXXX}){yellow}\n```\n\n\n\n\n\n\n\n֧ɫʽ\n\n\n\n\n\n*   `blue`\n\n*   `cyan`\n\n*   `faint`\n\n*   `green`\n\n*   `magenta`\n\n*   `red`\n\n*   `yellow`\n\n\n\n\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/features.html#features.logging.file-output)4.3\\. ļ\n\n\n\nĬ£Spring Bootֻ̨¼־д־ļ ڿ̨֮д־ļҪ `logging.file.name`  `logging.file.path` ԣ磬 `application.properties` У\n\n\n\n\n\n±ʾ `logging.*` αһʹá\n\n\n\n<caption>Table 5\\. Logging properties</caption><colgroup><col><col><col><col></colgroup>\n| `logging.file.name` | `logging.file.path` | Example | Description |\n| --- | --- | --- | --- |\n| _(none)_ | _(none)_ |  | ֻڿ̨м¼ |\n| ָļ | _(none)_ | `my.log` | дָ־ļ ƿһȷеλãҲ뵱ǰĿ¼λá |\n| _(none)_ | ָĿ¼ | `/var/log` |  `spring.log` дָĿ¼ ƿһȷеλãҲ뵱ǰĿ¼λá |\n\n\n\n־ļڴﵽ10MBʱͻֻ̨һĬ»¼ `ERROR` `WARN`  `INFO` Ϣ\n\n\n\n\n\n|  | ־Զʵʵ־ʩ ˣضԣLogback `logback.configurationFile` spring Boot |\n| --- | --- |\n\n\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/features.html#features.logging.file-rotation)4.4\\. ļֻ־\n\n\n\nʹLogbackʹ `application.properties`  `application.yaml` ļ΢־ֻá ־ϵͳ㽫ҪԼֱֻã磬ʹLog4J2ôһ `log4j2.xml`  `log4j2-spring.xml` ļ\n\n\n\n\n\nֻ֧ԡ\n\n\n\n<colgroup><col><col></colgroup>\n|  | ˵ |\n| --- | --- |\n| `logging.logback.rollingpolicy.file-name-pattern` | ڴ־鵵ļģʽ |\n| `logging.logback.rollingpolicy.clean-history-on-start` | ӦóʱǷ־鵵 |\n| `logging.logback.rollingpolicy.max-file-size` | ־ļ鵵ǰߴ磨ļﵽͻ鵵 |\n| `logging.logback.rollingpolicy.total-size-cap` | ־ڱɾǰߴ磨鵵ļռôССᱻɾ |\n| `logging.logback.rollingpolicy.max-history` | ҪĹ鵵־ļĬΪ7 |\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/features.html#features.logging.log-levels)4.5\\. ־\n\n\n\nֵ֧־ϵͳͨʹ `logging.level.<logger-name>=<level>` Spring `Environment`磬 `application.properties`־ `level`  `TRACE`, `DEBUG`, `INFO`, `WARN`, `ERROR`, `FATAL`,  `OFF` ֮һ `root` ¼loggerļͨ `logging.level.root` á\n\n\n\n\n\nʾ `application.properties` Ǳڵ־á\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nlogging.level.root=warn\nlogging.level.org.springframework.web=debug\nlogging.level.org.hibernate=error\n```\n\n\n\n\n\n\n\nҲʹû־ 磬`LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_WEB=DEBUG`  `org.springframework.web` Ϊ `DEBUG` \n\n\n\n\n\n|  | ֻڰ־ ڿɰǽתΪСдĸԲַʽΪ־ ҪΪһ־ʹ[`SPRING_APPLICATION_JSON`](https://springdoc.cn/spring-boot/features.html#features.external-config.application-json) |\n| --- | --- |\n\n\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/features.html#features.logging.log-groups)4.6\\. ־飨Log Groups\n\n\n\nܹص־¼飬Աͬʱǽãͨá 磬ܾı __ Tomcatصļ¼ļ¼𣬵㲻׼ס߼İ\n\n\n\n\n\nΪ˰⣬Spring BootSpring `Environment` ж־顣 磬ͨ `application.properties` м tomcat group \n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nlogging.group.tomcat=org.apache.catalina,org.apache.coyote,org.apache.tomcat\n\n```\n\n\n\n\n\n\n\nһú󣬾Ϳһдıloggerļ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nlogging.level.tomcat=trace\n\n```\n\n\n\n\n\n\n\nSpring BootԤ־飬Կ伴á\n\n\n\n<colgroup><col><col></colgroup>\n|  | еlogger |\n| --- | --- |\n| web | `org.springframework.core.codec`, `org.springframework.http`, `org.springframework.web`, `org.springframework.boot.actuate.endpoint.web`, `org.springframework.boot.web.servlet.ServletContextInitializerBeans` |\n| sql | `org.springframework.jdbc.core`, `org.hibernate.SQL`, `org.jooq.tools.LoggerListener` |\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/features.html#features.logging.shutdown-hook)4.7\\. ʹ־ Shutdown Hook\n\n\n\nΪӦóֹʱͷ־ԴṩһShutdown HookJVM˳ʱ־ϵͳ ӦówarļʽģShutdown HookԶעᡣ ӦóиӵĲνṹShutdown Hook޷ ܣùػӣоײ־ϵͳֱṩѡ 磬Logbackṩ [context selectors](https://logback.qos.ch/manual/loggingSeparation.html)ÿ¼Լб ʹ `logging.register-shutdown-hook` Shutdown Hook Ϊ `false` עᡣ  `application.properties`  `application.yaml` ļøԡ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nlogging.register-shutdown-hook=false\n\n```\n\n\n\n\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/features.html#features.logging.custom-log-configuration)4.8\\. Զ־\n\n\n\n־ϵͳͨclasspathϰʵĿҿͨclasspathĸĿ¼» Spring `Environment` ָλṩһʵļһƣ `logging.config`\n\n\n\n\n\nͨʹ `org.springframework.boot.logging.LoggingSystem` ϵͳԣǿSpring Bootʹض־ϵͳ ֵӦ `LoggingSystem` ʵֵȫ Ҳͨʹ `none` ֵȫSpring Boot־á\n\n\n\n\n\n|  | S־ڴ `ApplicationContext` ֮ǰʼģԲܴSpring `@Configuration` ļе `@PropertySources` ־ ı־ϵͳȫͣΨһͨSystem properties |\n| --- | --- |\n\n\n\n\n\n־ϵͳļ\n\n\n\n<colgroup><col><col></colgroup>\n| ־ϵͳ | ļ |\n| --- | --- |\n| Logback | `logback-spring.xml`, `logback-spring.groovy`, `logback.xml`  `logback.groovy` |\n| Log4j2 | `log4j2-spring.xml`  `log4j2.xml` |\n| JDK (Java Util Logging) | `logging.properties` |\n\n\n\n|  | ڿܵ£ǽʹ `-spring` ־ã磬 `logback-spring.xml`  `logback.xml`  ʹñ׼λãSpringȫ־ʼ |\n| --- | --- |\n\n\n\n\n\n|  |  \"ִеjar \"ʱJava Util LoggingһЩ֪⣬ᵼ⡣ ܵĻǽڴ \"ִеjar\" ʱʹ |\n| --- | --- |\n\n\n\n\n\nΪ˰ƣһЩԴSpring `Environment` תƵSystem properties±ʾ\n\n\n\n| Spring Environment | System Property | ע |\n| --- | --- | --- |\n| `logging.exception-conversion-word` | `LOG_EXCEPTION_CONVERSION_WORD` | ¼쳣ʱʹõתʡ |\n| `logging.file.name` | `LOG_FILE` | ˣĬϵ־С |\n| `logging.file.path` | `LOG_PATH` | ˣĬϵ־С |\n| `logging.pattern.console` | `CONSOLE_LOG_PATTERN` | ڿ̨stdoutʹõ־ģʽ |\n| `logging.pattern.dateformat` | `LOG_DATEFORMAT_PATTERN` | date ʽ. |\n| `logging.charset.console` | `CONSOLE_LOG_CHARSET` | ̨־ַ롣 |\n| `logging.threshold.console` | `CONSOLE_LOG_THRESHOLD` | ڿ̨־¼־ |\n| `logging.pattern.file` | `FILE_LOG_PATTERN` | Ҫļʹõ־ģʽ `LOG_FILE` ã |\n| `logging.charset.file` | `FILE_LOG_CHARSET` | ļ־ַ루 `LOG_FILE` ã |\n| `logging.threshold.file` | `FILE_LOG_THRESHOLD` | ļ־¼־ |\n| `logging.pattern.level` | `LOG_LEVEL_PATTERN` | Ⱦ־ʱʹõĸʽĬΪ `%5p`  |\n| `PID` | `PID` | ǰĽID |\n\n\n\nʹLogbackҲᱻתơ\n\n\n\n| Spring Environment | System Property | ע |\n| --- | --- | --- |\n| `logging.logback.rollingpolicy.file-name-pattern` | `LOGBACK_ROLLINGPOLICY_FILE_NAME_PATTERN` | ־ļģʽĬΪ `${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz`  |\n| `logging.logback.rollingpolicy.clean-history-on-start` | `LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START` | Ƿʱ鵵־ļ |\n| `logging.logback.rollingpolicy.max-file-size` | `LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE` | ־ļС |\n| `logging.logback.rollingpolicy.total-size-cap` | `LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP` | Ҫ־ݵܴС |\n| `logging.logback.rollingpolicy.max-history` | `LOGBACK_ROLLINGPOLICY_MAX_HISTORY` | Ҫ鵵־ļ |\n\n\n\nֵ֧־ϵͳڽļʱԴ System properties лȡԡ Ӽ `spring-boot.jar` еĬá\n\n\n\n\n\n*   [Logback](https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/logback/defaults.xml)\n\n*   [Log4j 2](https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/log4j2/log4j2.xml)\n\n*   [Java Util logging](https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/java/logging-file.properties)\n\n\n\n\n\n|  | ־ʹռλӦʹ[Spring Boot﷨](https://springdoc.cn/spring-boot/features.html#features.external-config.files.property-placeholders)ǵײܵ﷨ ֵעǣʹLogbackӦʹ `:` ΪĬֵ֮ķָʹ `:-`  |\n| --- | --- |\n\n\n\n\n\n|  | ֻͨ `LOG_LEVEL_PATTERN` ʹLogback `logging.pattern.level` ־MDCʱݡ 磬ʹ `logging.pattern.level=user:%X{user} %5p` ôĬϵ־ʽһ \"user\" MDCĿڵĻʾ 2019-08-30 12:30:04.031 user:someone INFO 22174 --- [  nio-8080-exec-0] demo.ControllerHandling authenticated request  |\n| --- | --- |\n\n\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/features.html#features.logging.logback-extensions)4.9\\. Logback չ\n\n\n\nSpring BootһЩLogbackչ԰и߼á  `logback-spring.xml` ļʹЩչ\n\n\n\n\n\n|  | Ϊ׼ `logback.xml` ļأ㲻ʹչ Ҫʹ `logback-spring.xml` ߶һ `logging.config` ԡ |\n| --- | --- |\n\n\n\n\n\n|  | չ [Logbackɨ](https://logback.qos.ch/manual/configuration.html#autoScan) һʹá ͼļ޸ĻᵼµĴ󱻼¼ |\n| --- | --- |\n\n\n\n\n\n\n\n ERROR in ch.qos.logback.core.joran.spi.Interpreter@4:71 - no applicable action for [springProperty], current ElementPath is [[configuration][springProperty]]\nERROR in ch.qos.logback.core.joran.spi.Interpreter@4:71 - no applicable action for [springProfile], current ElementPath is [[configuration][springProfile]] \n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/features.html#features.logging.logback-extensions.profile-specific)4.9.1\\. ضļ\n\n\n\n`<springProfile>` ǩԸݻSpringļѡԵذųõĲ֣ ֧ `<configuration>` Ԫصκεط ʹ `name` ָõļ `<springProfile>` ǩ԰һļƣ `staging` һļʽ ļʽӵļ߼ `production & (eu-central | eu-west)`  鿴 [Spring ܲοָ](https://docs.spring.io/spring-framework/docs/6.0.5/reference/html/core.html#beans-definition-profiles-java) ˽ϸڡ бʾļ\n\n\n\n\n\n\n\n```\n<springProfile name=\"staging\">\n    <!-- configuration to be enabled when the \"staging\" profile is active -->\n</springProfile>\n\n<springProfile name=\"dev | staging\">\n    <!-- configuration to be enabled when the \"dev\" or \"staging\" profiles are active -->\n</springProfile>\n\n<springProfile name=\"!production\">\n    <!-- configuration to be enabled when the \"production\" profile is not active -->\n</springProfile>\n```\n\n\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/features.html#features.logging.logback-extensions.environment-properties)4.9.2\\. ԣEnvironment Properties\n\n\n\n`<springProperty>` ǩԷ Spring `Environment` еԣԱLogbackʹá Logbackз `application.properties` ļеֵá ñǩĹʽLogbackı׼ `<property>` ǩơ Ȼ㲻ֱָһ `value` ָԵ `source`  `Environment`  Ҫ `local` Χĵط洢ԣʹ `scope` ԡ ҪһֵĬֵһû `Environment` ãʹ `defaultValue` ԡ ʾιԱLogbackʹá\n\n\n\n\n\n\n\n```\n<springProperty scope=\"context\" name=\"fluentHost\" source=\"myapp.fluentd.host\"\n        defaultValue=\"localhost\"/>\n\n    <remoteHost>${fluentHost}</remoteHost>\n    ...\n\n```\n\n\n\n\n\n\n\n|  | `source` kebabָ `my.property-name`  ȻԿͨʹÿɵĹӵ `Environment` С |\n| --- | --- |\n\n\n\n\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/features.html#features.logging.log4j2-extensions)4.10\\. Log4j2 չ\n\n\n\nSpring BootһЩLog4j2չ԰и߼áκ `log4j2-spring.xml` ļʹЩչ\n\n\n\n\n\n|  | Ϊ׼ `log4j2.xml` ļأ㲻ʹչҪʹ `log4j2-spring.xml` ߶һ ``logging.config`` ԡ |\n| --- | --- |\n\n\n\n\n\n|  | ЩչȡLog4Jṩ [Spring Boot֧](https://logging.apache.org/log4j/2.x/log4j-spring-boot/index.html) ӦȷĹв `org.apache.logging.log4j:log4j-spring-boot` ģ顣 |\n| --- | --- |\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/features.html#features.logging.log4j2-extensions.profile-specific)4.10.1\\. ضļ\n\n\n\n`<SpringProfile>` ǩԸݻSpringļѡԵذųõĲ֡ļֱ֧ `<Configuration>` Ԫصκεطʹ `name` ָĸļá `<SpringProfile>` ǩ԰һļƣ `staging`һļʽ ļʽӵļ߼ `production & (eu-central | eu-west)`鿴 [Springܲοָ](https://docs.spring.io/spring-framework/docs/6.0.5/reference/html/core.html#beans-definition-profiles-java) ˽ϸڡ бʾļ\n\n\n\n\n\n\n\n```\n<SpringProfile name=\"staging\">\n    <!-- configuration to be enabled when the \"staging\" profile is active -->\n</SpringProfile>\n\n<SpringProfile name=\"dev | staging\">\n    <!-- configuration to be enabled when the \"dev\" or \"staging\" profiles are active -->\n</SpringProfile>\n\n<SpringProfile name=\"!production\">\n    <!-- configuration to be enabled when the \"production\" profile is not active -->\n</SpringProfile>\n```\n\n\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/features.html#features.logging.log4j2-extensions.environment-properties-lookup)4.10.2\\. EnvironmentԲ\n\n\n\nLog4j2Spring `Environment` еԣʹ `spring:` ǰ׺ [](https://logging.apache.org/log4j/2.x/manual/lookups.html)Log4j2з `application.properties` ļеֵá\n\n\n\n\n\nʾһΪ `applicationName` Log4j2ԣSpring `Environment` жȡ `spring.application.name`\n\n\n\n\n\n\n\n```\n<Properties>\n    <Property name=\"applicationName\">${spring:spring.application.name}</Property>\n</Properties>\n```\n\n\n\n\n\n\n\n|  | ѯkeyӦkebabfָ `my.property-name` |\n| --- | --- |\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/features.html#features.logging.log4j2-extensions.environment-property-source)4.10.3\\. Log4j2 ϵͳԣSystem Properties\n\n\n\nLog4j2֧һЩ [System Properties](https://logging.apache.org/log4j/2.x/manual/configuration.html#SystemProperties)øĿ磬`log4j2.skipJansi` ϵͳԿ `ConsoleAppender` ǷWindowsϳʹ [Jansi](https://github.com/fusesource/jansi) \n\n\n\n\n\nLog4j2 ʼصϵͳԶԴSpring `Environment` лá磬 `application.properties` ļ `log4j2.skipJansi=false` `ConsoleAppender` WindowsʹJansi\n\n\n\n\n\n|  | ֻеϵͳԣsystem propertiesͲϵͳڼصֵʱŻῼSpring `Environment` |\n| --- | --- |\n\n\n\n\n\n|  | Log4j2ʼڼصϵͳԲSpring `Environment`磬Log4j2ѡĬLog4j2ʵֵ Spring Environment ֮ǰʹõġ |\n| --- | --- |\n\n\n\n\n\n\n\n"
  },
  {
    "path": "docs/Spring全家桶/SpringBoot/SpringBoot常见注解.md",
    "content": "## 1 \nSpring Boot ͨԶùʹ Spring øס\n\nڱٽ̳Уǽ̽ org.springframework.boot.autoconfigure  org.springframework.boot.autoconfigure.condition еע⡣\n\n## 2 @SpringBootApplication\nʹע Spring Boot Ӧóࣺ\n\n@SpringBootApplication\nʹע Spring Boot Ӧóࣺ\n````\n@SpringBootApplication\nclass VehicleFactoryApplication {\n\n    public static void main(String[] args) {\n        SpringApplication.run(VehicleFactoryApplication.class, args);\n    }\n}\n````\n@SpringBootApplication װ@Configuration@EnableAutoConfiguration @ComponentScan ע⼰Ĭԡ\n\n## 3 @EnableAutoConfiguration\n\n@EnableAutoConfiguration˼壬Զá ζ Spring Boot ·вԶ bean ԶӦǡ\n\nע⣬Ǳ뽫ע@Configuration һʹã\n\n````\n@Configuration\n@EnableAutoConfiguration\nclass VehicleFactoryConfig {}\n````\n\n## 4 @ConfigurationԼ\n\n@Configurationãעϣspring(Ӧ)\n\nspringbeanʹõxmlļһbeanspringbootУΪãspringṩ@Configurationһע\n\n൱ڰѸΪspringxmlļе<beans>\n\n@ConfigurationעУʹ@BeanעעķصͶֱעΪbean\n\n@ConfigureעĶ£\n````\n@Target(ElementType.TYPE)\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\n@Component\npublic @interface Configuration {\nString value() default \"\";\n}\n````\nӶײǺ@Component @Configuration к @Component ácontext:component-scan/@ComponentScanܴ@Configurationעࡣ\n\nͨǱдԶԶʱϣ Spring ʹǡ ǿͨеעʵһ㡣\n\nǿԽ˲еעͷ@Configuration @Bean ϡ\n\nڽĲУǽֻÿĻ ˽Ϣƪ¡\n\n### 4.1 @ConditionalOnClass and @ConditionalOnMissingClass\nһжϵע⣬Ҫ֪ܶʱǰbeanģǸⲿjarǷмغжϵġ\n\nʱ򣬾ҪⲿǷǷǷظbean\n\nʹЩעͲе/ڣSpring ʹñǵԶ bean\n\n````\n@Configuration\n@ConditionalOnClass(DataSource.class)\nclass MySQLAutoconfiguration {\n//...\n}\n````\n\n### 4.2 @ConditionalOnBean and @ConditionalOnMissingBean\n\nҪض bean Ĵڻ򲻴ʱǿʹЩעͣ\n\nһעЩͬΪǵжbean\n\n````\n@Bean\n@ConditionalOnBean(name = \"dataSource\")\nLocalContainerEntityManagerFactoryBean entityManagerFactory() {\n// ...\n}\n````\n### 4.3 @ConditionalOnProperty\nͨע⣬ǿԶԵֵ\n\nҪע⣬ֵԴapplication.propertiesļе\n\n````\n@Bean\n@ConditionalOnProperty(\nname = \"usemysql\",\nhavingValue = \"local\"\n)\nDataSource dataSource() {\n// ...\n}\n````\n \n### 4.4 @ConditionalOnResource\n\nǿ Spring ڴضԴʱʹö壺\n˼壬ҪclasspathԴļʱŽмأҲǺܳõһע⡣\n\n````\n\n@ConditionalOnResource(resources = \"classpath:mysql.properties\")\nProperties  ditionalProperties() {\n// ...\n}\n````\n \n### 4.5 @ConditionalOnWebApplication and @ConditionalOnNotWebApplication\nעͨںwebǿȫ޹\n\nʹЩעͣǿԸݵǰӦóǷ Web Ӧó\n````\n\n@ConditionalOnWebApplication\nHealthCheckController healthCheckController() {\n// ...\n}\n````\n \n### 4.6 @ConditionalExpression\nspringbootΪ뵽ע⻹ҪôɴԼдӦûɣ\n\nǿڸӵʹע⡣  SpEL ʽΪʱSpring ʹñǵĶ壺\n\n````\n@Bean\n@ConditionalOnExpression(\"${usemysql} && ${mysqlserver == 'local'}\")\nDataSource dataSource() {\n// ...\n}\n````\n\n### 4.7 @Conditional\nʲô⣿\nspringbootҲṩʲôʽˣֱûдһжtruefalse\n\nڸӵǿԴһԶࡣ Ǹ Spring Զ @Conditional һʹã\n\n````\n@Conditional(HibernateCondition.class)\nProperties  ditionalProperties() {\n//...\n}\n````\n\n## 5 ܽ\nڱУǸ΢Զù̲ΪԶԶ bean ṩ"
  },
  {
    "path": "docs/Spring全家桶/SpringBoot/SpringBoot应用也可以部署到外部Tomcat.md",
    "content": "\n\n<header>\n\n# Spring Boot Tomcat\n\nݽվѸѧϰʼǡܽоղء֤ȷԣʹöķ뱾վ޹أ\n\n</header>\n\n\n\n<script>( adsbygoogle = window.adsbygoogle || []).push({});</script>\n\n\n\nͨʹSpring BootӦó򣬿ԴһwarļԲWebСڱУѧϰδWARļTomcat WebвSpring BootӦó\n\n## Spring Boot Servletʼ\n\nͳĲʽʹSpring BootӦó`[@SpringBootApplication](https://github.com/SpringBootApplication \"@SpringBootApplication\")`չ`SpringBootServletInitializer`ࡣ `SpringBootServletInitializer`ļʹServletʱӦó\n\nJARļSpring BootӦóļĴ -\n\n```\npackage com.yiibai.demo;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\n\n@SpringBootApplication\npublic class DemoApplication {\n   public static void main(String[] args) {\n      SpringApplication.run(DemoApplication.class, args);\n   }\n}\n\n```\n\nҪչ`SpringBootServletInitializer`֧WARļ Spring BootӦóļĴ -\n\n```\npackage com.yiibai.demo;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.boot.builder.SpringApplicationBuilder;\nimport org.springframework.boot.web.support.SpringBootServletInitializer;\n\n@SpringBootApplication\npublic class DemoApplication  extends SpringBootServletInitializer {\n   @Override\n   protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {\n      return application.sources(DemoApplication.class);\n   }\n   public static void main(String[] args) {\n      SpringApplication.run(DemoApplication.class, args);\n   }\n}\n\n```\n\n## Main\n\nSpring BootУҪڹļָࡣ\nMaven`pom.xml``start`࣬ʾ -\n\n```\n<start-class>com.yiibai.demo.DemoApplication</start-class>\n\n```\n\nGradle`build.gradle`ʾ -\n\n```\nmainClassName=\"com.yiibai.demo.DemoApplication\"\n\n```\n\n## JARΪWAR\n\nʹ´뽫װJARΪWAR\n\nMaven_pom.xml_ нװΪWARʾ -\n\n```\n<packaging>war</packaging>\n\n```\n\nGradle_build.gradle_ Ӧówarʾ -\n\n```\napply plugin: 'war'\napply plugin: 'application'\n\n```\n\nGradlNowдһ򵥵Rest˵ַ:`\"Hello World from Tomcat\"` ҪдRest˵㣬ҪSpring Boot Web starterӵļС\n\nMavenʹʾĴ_pom.xml_ Spring Boot -\n\n```\n<dependency>\n   <groupId>org.springframework.boot</groupId>\n   spring-boot-starter-web\n</dependency>\n\n```\n\nGradleʹʾĴ_build.gradle_ Spring Boot starter -\n\n```\ndependencies {\n   compile('org.springframework.boot:spring-boot-starter-web')\n}\n\n```\n\nڣʹʾĴSpring Boot Applicationļбдһ򵥵Rest˵ -\n\n```\npackage com.yiibai.demo;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.boot.builder.SpringApplicationBuilder;\nimport org.springframework.boot.web.support.SpringBootServletInitializer;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\n@SpringBootApplication\n@RestController\npublic class DemoApplication  extends SpringBootServletInitializer {\n   @Override\n   protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {\n      return application.sources(DemoApplication.class);\n   }\n   public static void main(String[] args) {\n      SpringApplication.run(DemoApplication.class, args);\n   }\n\n   @RequestMapping(value = \"/\")\n   public String hello() {\n      return \"Hello World from Tomcat\";\n   }\n}\n\n```\n\n## Ӧó\n\nڣʹMavenGradleһWARļԲTomcatУԴӦóʾ\n\nMavenʹ`mvn package`Ӧó Ȼ󴴽WARļĿĿ¼ҵĻͼʾ -\n\n![](/uploads/images/2018/09/27/084613_17931.jpg)\n\nGradleʹ`gradle clean build`Ӧó Ȼ󣬽WARļ`build/libs`Ŀ¼ҵ۲˴ĻͼԱõ -\n\n![](/uploads/images/2018/09/27/084717_10144.jpg)\n\n## Tomcat\n\nڣTomcatwebappsĿ¼²WARļ۲˴ʾĻͼԱõ -\n\n![](/uploads/images/2018/09/27/084759_50620.jpg)\n\nɹ󣬵ҳеURL => `http://localhost:8080/demo-0.0.1-SNAPSHOT/`۲ͼʾ -\n\n![](/uploads/images/2018/09/27/084848_70593.jpg)\n\n£\n\nļ_pom.xml_ -\n\n```\n<?xml version = \"1.0\" encoding = \"UTF-8\"?>\n<project xmlns = \"http://maven.apache.org/POM/4.0.0\" \n   xmlns:xsi = \"http://www.w3.org/2001/XMLSchema-instance\"\n\nxsi:schemaLocation = \"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n<modelVersion>4.0.0</modelVersion>\n\n   <groupId>com.yiibai</groupId>\n   demo\n   <version>0.0.1-SNAPSHOT</version>\n   <packaging>war</packaging>\n   <name>demo</name>\n   <description>Demo project for Spring Boot</description>\n\n   <parent>\n      <groupId>org.springframework.boot</groupId>\n      spring-boot-starter-parent\n      <version>1.5.8.RELEASE</version>\n      <relativePath/> <!-- lookup parent from repository -->\n   </parent>\n\n   <properties>\n      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n      <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n      <java.version>1.8</java.version>\n      <start-class>com.yiibai.demo.DemoApplication</start-class>\n   </properties>\n\n   <dependencies>\n      <dependency>\n         <groupId>org.springframework.boot</groupId>\n         spring-boot-starter-web\n      </dependency>\n      <dependency>\n         <groupId>org.springframework.boot</groupId>\n         spring-boot-starter-test\n         <scope>test</scope>\n      </dependency>\n   </dependencies>\n\n   <build>\n      <plugins>\n         <plugin>\n            <groupId>org.springframework.boot</groupId>\n            spring-boot-maven-plugin\n         </plugin>\n      </plugins>\n   </build>\n\n</project>\n\n```\n\nļ_build.gradle_\n\n```\nbuildscript {\n   ext {\n      springBootVersion = '1.5.8.RELEASE'\n   }\n   repositories {\n      mavenCentral()\n   }\ndependencies {\n      classpath(\"org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}\")\n   }\n}\n\napply plugin: 'java'\napply plugin: 'eclipse'\napply plugin: 'org.springframework.boot'\napply plugin: 'war'\napply plugin: 'application'\n\ngroup = 'com.yiibai'\nversion = '0.0.1-SNAPSHOT'\nsourceCompatibility = 1.8\nmainClassName = \"com.yiibai.demo.DemoApplication\"\n\nrepositories {\n   mavenCentral()\n}\ndependencies {\n   compile('org.springframework.boot:spring-boot-starter-web')\n   testCompile('org.springframework.boot:spring-boot-starter-test')\n}\n\n```\n\nSpring BootӦóļĴ -\n\n```\npackage com.yiibai.demo;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.boot.builder.SpringApplicationBuilder;\nimport org.springframework.boot.web.support.SpringBootServletInitializer;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\n@SpringBootApplication\n@RestController\npublic class DemoApplication  extends SpringBootServletInitializer {\n   @Override\n   protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {\n      return application.sources(DemoApplication.class);\n   }\n   public static void main(String[] args) {\n      SpringApplication.run(DemoApplication.class, args);\n   }\n\n   @RequestMapping(value = \"/\")\n   public String hello() {\n      return \"Hello World from Tomcat\";\n   }\n}\n```\n\n\n\n\n\n//Ķhttps://www.yiibai.com/spring-boot/spring_boot_tomcat_deployment.html\n\n"
  },
  {
    "path": "docs/Spring全家桶/SpringBoot/SpringBoot打包与启动.md",
    "content": "在使用`maven`构建`springboot`项目时，`springboot`相关 jar 包可以使用`parent方式`引入（即在`pom.xml`的`parent`节点引入`springboot`的`GAV`:`org.springframework.boot:spring-boot-starter-parent:2.1.1.RELEASE`），也可以使用`非parent方式`引入（即在 pom 的 dependencyManagement 节点引入`springboot`的`GAV`:`org.springframework.boot:spring-boot-dependencies:2.1.1.RELEASE`）。同时，在打包时，我们可以打成 jar 包，也可以打成 war 包，本文旨在梳理各引入、打包方式的异同。\n\n### 1\\. parent 方式引入，打成 jar 包\n\nparent 方式，即在 pom 文件中，将 springboot 的依赖当成项目的 parent 引入，pom 文件示例如下：\n\n```\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 \n         http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <!-- 引入springboot的parent -->\n    <parent>\n        <groupId>org.springframework.boot</groupId>\n        spring-boot-starter-parent\n        <version>2.1.1.RELEASE</version>\n    </parent>\n\n    <groupId>com.gitee.funcy</groupId>\n    springboot-parent-jar\n    <version>1.0.0</version>\n    <packaging>jar</packaging>\n    <name>springboot parent jar打包方式</name>\n\n    <properties>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n        <java.version>1.8</java.version>\n        <maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>\n    </properties>\n\n    <dependencies>\n        <!-- springboot 的基础依赖 -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            spring-boot-starter\n        </dependency>\n        <!-- springboot 的web 依赖 -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            spring-boot-starter-web\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <!-- maven编译插件 -->\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                maven-compiler-plugin\n                <version>${maven-compiler-plugin.version}</version>\n                <configuration>\n                    <source>${java.version}</source>\n                    <target>${java.version}</target>\n                    <encoding>${project.build.sourceEncoding}</encoding>\n                </configuration>\n            </plugin>\n            <!-- springboot编译插件 -->\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                spring-boot-maven-plugin\n                <!-- 其他内容不用指定，因为 spring-boot-starter-parent 已经指定了-->\n            </plugin>\n        </plugins>\n    </build>\n\n</project>\n\n```\n\n添加一个 controller:\n\n```\npackage com.gitee.funcy.mavenparent.jar.controller;\n\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\n/**\n * ｛这里添加描述｝\n *\n * @author funcy\n * @date 2019-12-13 10:43 下午\n */\n@RestController\npublic class IndexController {\n\n    @RequestMapping(\"/\")\n    public String helloWorld() {\n        return \"hello world\";\n    }\n\n}\n\n```\n\n再引入启动类：\n\n```\npackage com.gitee.funcy.mavenparent.jar;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\n\n/**\n * ｛这里添加描述｝\n *\n * @author funcy\n * @date 2019-12-13 10:36 下午\n */\n@SpringBootApplication\npublic class Main {\n\n    public static void main(String[] args) {\n        SpringApplication.run(Main.class, args);\n    }\n\n}\n\n```\n\n运行 Main 方法，请求`http://localhost:8080/`，结果如下：\n\n```\n $ curl http://localhost:8080/\nhello world\n\n```\n\n可以看到，项目运行成功。\n\n接着，尝试使用 jar 包启动：\n\n```\n# 打包\n mvn clean install -Dmaven.test.skip=true\n # 启动jar包\n java -jar target/springboot-parent-jar-1.0.0.jar\n\n```\n\n可以看到，项目启动成功，请求请求`http://localhost:8080/`，也能显示正确结果。\n\n### 2\\. 非 parent 方式引入，打成 jar 包\n\n在实际项目中，项目的 parent 依赖可能给了其他项目，此时 parent 引用就无法进行了，这时我们需要非 parent 引入。非 parent 引入的 pom 如下：\n\n```\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 \n         http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>com.gitee.funcy</groupId>\n    springboot-jar\n    <version>1.0.0</version>\n    <packaging>jar</packaging>\n    <name>springboot非parent jar打包方式</name>\n\n    <properties>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n        <java.version>1.8</java.version>\n        <spring-boot.version>2.1.1.RELEASE</spring-boot.version>\n        <maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>\n    </properties>\n\n    <dependencyManagement>\n        <dependencies>\n            <dependency>\n                <!-- Import dependency management from Spring Boot -->\n                <groupId>org.springframework.boot</groupId>\n                spring-boot-dependencies\n                <version>${spring-boot.version}</version>\n                <type>pom</type>\n                <scope>import</scope>\n            </dependency>\n        </dependencies>\n    </dependencyManagement>\n\n    <dependencies>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            spring-boot-starter\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            spring-boot-starter-web\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                maven-compiler-plugin\n                <version>${maven-compiler-plugin.version}</version>\n                <configuration>\n                    <source>${java.version}</source>\n                    <target>${java.version}</target>\n                    <encoding>${project.build.sourceEncoding}</encoding>\n                </configuration>\n            </plugin>\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                spring-boot-maven-plugin\n                <version>${spring-boot.version}</version>\n                <!-- 与parent引入相比，需要指定goals，否则会打包失败-->\n                <executions>\n                    <execution>\n                        <goals>\n                            <goal>repackage</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n        </plugins>\n    </build>\n\n</project>\n\n```\n\n再添加一个`ControllerIndexController.java`与启动类`Main.java`，这两个文件与上述示例相同，这里就不作展示了。\n\n运行 Main 方法，请求`http://localhost:8080/`，结果如下：\n\n```\n $ curl http://localhost:8080/\nhello world\n\n```\n\n可以看到，项目运行成功。\n\n接着，尝试使用 jar 包启动：\n\n```\n# 打包\n mvn clean install -Dmaven.test.skip=true\n # 启动jar包\n java -jar target/springboot-jar-1.0.0.jar\n\n```\n\n可以看到，项目启动成功，请求请求`http://localhost:8080/`，也能显示正确结果。\n\n### 3\\. parent 方式引入，打成 war 包\n\n以上两种方式都是打成 jar，为了兼容传统的 servlet 应用，springboot 也支持打包 war 包，parent 引入打包 war 包的 pom 文件如下：\n\n```\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 \n         http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <!-- 引入springboot的parent -->\n    <parent>\n        <groupId>org.springframework.boot</groupId>\n        spring-boot-starter-parent\n        <version>2.1.1.RELEASE</version>\n    </parent>\n\n    <groupId>com.gitee.funcy</groupId>\n    springboot-parent-war\n    <version>1.0.0</version>\n    <!-- 指定打包方式为war包 -->\n    <packaging>war</packaging>\n    <name>springboot parent war打包方式</name>\n\n    <properties>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n        <java.version>1.8</java.version>\n        <maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>\n    </properties>\n\n    <dependencies>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            spring-boot-starter\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            spring-boot-starter-web\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            spring-boot-test\n            <scope>provided</scope>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                maven-compiler-plugin\n                <version>${maven-compiler-plugin.version}</version>\n                <configuration>\n                    <source>${java.version}</source>\n                    <target>${java.version}</target>\n                    <encoding>${project.build.sourceEncoding}</encoding>\n                </configuration>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                maven-war-plugin\n                <!-- 保持与 spring-boot-dependencies 版本一致 -->\n                <version>3.2.2</version>\n            </plugin>\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                spring-boot-maven-plugin\n            </plugin>\n        </plugins>\n    </build>\n\n</project>\n\n```\n\n再添加一个`ControllerIndexController.java`与启动类`Main.java`，这两个文件与上述示例相同，这里就不作展示了。\n\n除此之外，war 包方式还需要添加一个类，用以实现`SpringBootServletInitializer`，该类与启动类`Main.java`位于同一个包下，主要是用来引导 tomcat 等 servlet 容器加载 servlet，内容如下：\n\n```\n/**\n * ｛这里添加描述｝\n *\n * @author funcy\n * @date 2019-12-20 1:22 下午\n */\npublic class StartApplication extends SpringBootServletInitializer {\n\n    @Override\n    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {\n        // 注意这里要指向原先用main方法执行的Application启动类\n        return builder.sources(Main.class);\n    }\n}\n\n```\n\n运行 Main 方法，请求`http://localhost:8080/`，结果如下：\n\n```\n $ curl http://localhost:8080/\nhello world\n\n```\n\n可以看到，项目运行成功。\n\n接着，尝试使用 jar 包启动：\n\n```\n# 打包\n mvn clean install -Dmaven.test.skip=true\n # 启动jar包\n java -jar target/springboot-parent-war-1.0.0.jar\n\n```\n\n可以看到，项目启动成功，请求请求`http://localhost:8080/`，也能显示正确结果。\n\n### 4\\. 非 parent 方式引入，打成 war 包\n\n同样地，打成 war 包时，也可使用非 parent 引入方式：\n\n```\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 \n         http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>com.gitee.funcy</groupId>\n    springboot-war\n    <version>1.0.0</version>\n    <!-- 指定打包方式为war包 -->\n    <packaging>war</packaging>\n    <name>springboot非parent war打包方式</name>\n\n    <properties>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n        <java.version>1.8</java.version>\n        <spring-boot.version>2.1.1.RELEASE</spring-boot.version>\n        <maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>\n    </properties>\n\n    <dependencyManagement>\n        <dependencies>\n            <dependency>\n                <!-- Import dependency management from Spring Boot -->\n                <groupId>org.springframework.boot</groupId>\n                spring-boot-dependencies\n                <version>${spring-boot.version}</version>\n                <type>pom</type>\n                <scope>import</scope>\n            </dependency>\n        </dependencies>\n    </dependencyManagement>\n\n    <dependencies>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            spring-boot-starter\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            spring-boot-starter-web\n        </dependency>\n        <!-- 测试war包中 WEB-INF/lib-provided/ 目录内容-->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            spring-boot-test\n            <scope>provided</scope>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                maven-compiler-plugin\n                <version>${maven-compiler-plugin.version}</version>\n                <configuration>\n                    <source>${java.version}</source>\n                    <target>${java.version}</target>\n                    <encoding>${project.build.sourceEncoding}</encoding>\n                </configuration>\n            </plugin>\n            <!-- 打成war包时，需要添加该插件 -->\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                maven-war-plugin\n                <!-- 保持与 spring-boot-dependencies 版本一致 -->\n                <version>3.2.2</version>\n            </plugin>\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                spring-boot-maven-plugin\n                <version>${spring-boot.version}</version>\n                <executions>\n                    <execution>\n                        <goals>\n                            <goal>repackage</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n        </plugins>\n    </build>\n\n</project>\n\n```\n\n再添加一个`ControllerIndexController.java`、`StartApplication.java`与启动类`Main.java`，这三个文件与上述示例相同，这里就不作展示了。\n\n运行 Main 方法，请求`http://localhost:8080/`，结果如下：\n\n```\n $ curl http://localhost:8080/\nhello world\n\n```\n\n可以看到，项目运行成功。\n\n接着，尝试使用 jar 包启动：\n\n```\n# 打包\n mvn clean install -Dmaven.test.skip=true\n # 启动jar包\n java -jar target/springboot-war-1.0.0.jar\n\n```\n\n可以看到，项目启动成功，请求请求`http://localhost:8080/`，也能显示正确结果。\n\n### 5\\. 总结\n\nspringboot 引入及打包方式组合下来有如下四种：\n\n| 打包 / 引入 | parent 方式 | 非 parent 方式 |\n| --- | --- | --- |\n| jar | parent-jar 方式 | 非 parent-jar 方式 |\n| war | parent-war 方式 | 非 parent-war 方式 |\n\n### 1\\. 开发时启动\n\n在开发时启动 springboot 应用，指的是直接运行源码，如在开发时在 ide 中运行启动类的 main () 方法。\n\n#### 1.1 在 ide 中执行启动类的`main()`方法\n\n自从有了 springboot 后，web 项目就不必再放到 web 容器中运行了，直接运行项目的`main()`方法就行了：\n\n![](https://oscimg.oschina.net/oscnet/up-040888025c22694b18d7be748b8cbb89f06.png)\n\n启动日志如下：\n\n```\n  .   ____          _            __ _ _\n /\\\\ / ___'_ __ _ _(_)_ __  __ _ \\ \\ \\ \\\n( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\\n \\\\/  ___)| |_)| | | | | || (_| |  ) ) ) )\n  '  |____| .__|_| |_|_| |_\\__, | / / / /\n =========|_|==============|___/=/_/_/_/\n :: Spring Boot ::        (v2.1.1.RELEASE)\n\n2020-01-07 21:11:16.365  INFO 84046 --- [           main] com.gitee.funcy.maven.jar.Main           : Starting Main on l with PID 84046 (/Users/funcy/IdeaProjects/myproject/springboot-demo/springboot-maven/springboot-jar/target/classes started by funcy in /Users/funcy/IdeaProjects/myproject/springboot-demo)\n2020-01-07 21:11:16.368  INFO 84046 --- [           main] com.gitee.funcy.maven.jar.Main           : No active profile set, falling back to default profiles: default\n2020-01-07 21:11:17.468  INFO 84046 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)\n2020-01-07 21:11:17.497  INFO 84046 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]\n2020-01-07 21:11:17.497  INFO 84046 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/9.0.13\n2020-01-07 21:11:17.513  INFO 84046 --- [           main] o.a.catalina.core.AprLifecycleListener   : The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: [/Users/funcy/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:.]\n2020-01-07 21:11:17.605  INFO 84046 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext\n2020-01-07 21:11:17.605  INFO 84046 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1206 ms\n2020-01-07 21:11:17.861  INFO 84046 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'\n2020-01-07 21:11:18.096  INFO 84046 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''\n2020-01-07 21:11:18.100  INFO 84046 --- [           main] com.gitee.funcy.maven.jar.Main           : Started Main in 1.988 seconds (JVM running for 2.34)\n2020-01-07 21:11:32.155  INFO 84046 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'\n2020-01-07 21:11:32.155  INFO 84046 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'\n2020-01-07 21:11:32.223  INFO 84046 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 68 ms\n\n```\n\n访问`http://localhost:8080/`，结果如下：\n\n```\n$ curl http://localhost:8080\nhello world\n\n```\n\n以上启动方式**war 与 jar 打包方式**都支持。\n\n#### 1.2`mvn spring-boot:run`启动\n\n这种方式也是源码启动，在命令行界面进入项目对应的源码目录下，然后执行`mvn spring-boot:run`命令：\n\n```\nspringboot-parent-war $ mvn spring-boot:run\n[INFO] Scanning for projects...\n[INFO] \n[INFO] ---------------< com.gitee.funcy:springboot-parent-war >----------------\n[INFO] Building springboot parent war打包方式 1.0.0\n[INFO] --------------------------------[ war ]---------------------------------\n[INFO] \n[INFO] >>> spring-boot-maven-plugin:2.1.1.RELEASE:run (default-cli) > test-compile @ springboot-parent-war >>>\n[INFO] \n[INFO] --- maven-resources-plugin:3.1.0:resources (default-resources) @ springboot-parent-war ---\n[INFO] Using 'UTF-8' encoding to copy filtered resources.\n[INFO] Copying 0 resource\n[INFO] Copying 0 resource\n[INFO] \n[INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ springboot-parent-war ---\n[INFO] Changes detected - recompiling the module!\n[INFO] Compiling 3 source files to /Users/funcy/IdeaProjects/myproject/springboot-demo/springboot-maven/springboot-parent-war/target/classes\n[INFO] \n[INFO] --- maven-resources-plugin:3.1.0:testResources (default-testResources) @ springboot-parent-war ---\n[INFO] Using 'UTF-8' encoding to copy filtered resources.\n[INFO] skip non existing resourceDirectory /Users/funcy/IdeaProjects/myproject/springboot-demo/springboot-maven/springboot-parent-war/src/test/resources\n[INFO] \n[INFO] --- maven-compiler-plugin:3.8.1:testCompile (default-testCompile) @ springboot-parent-war ---\n[INFO] No sources to compile\n[INFO] \n[INFO] <<< spring-boot-maven-plugin:2.1.1.RELEASE:run (default-cli) < test-compile @ springboot-parent-war <<<\n[INFO] \n[INFO] \n[INFO] --- spring-boot-maven-plugin:2.1.1.RELEASE:run (default-cli) @ springboot-parent-war ---\n\n  .   ____          _            __ _ _\n /\\\\ / ___'_ __ _ _(_)_ __  __ _ \\ \\ \\ \\\n( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\\n \\\\/  ___)| |_)| | | | | || (_| |  ) ) ) )\n  '  |____| .__|_| |_|_| |_\\__, | / / / /\n =========|_|==============|___/=/_/_/_/\n :: Spring Boot ::        (v2.1.1.RELEASE)\n\n2020-01-07 21:40:50.577  INFO 84448 --- [           main] com.gitee.funcy.mavenparent.war.Main     : Starting Main on funcydeMacBook-Pro.local with PID 84448 (/Users/funcy/IdeaProjects/myproject/springboot-demo/springboot-maven/springboot-parent-war/target/classes started by funcy in /Users/funcy/IdeaProjects/myproject/springboot-demo/springboot-maven/springboot-parent-war)\n2020-01-07 21:40:50.579  INFO 84448 --- [           main] com.gitee.funcy.mavenparent.war.Main     : No active profile set, falling back to default profiles: default\n2020-01-07 21:40:51.311  INFO 84448 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)\n2020-01-07 21:40:51.336  INFO 84448 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]\n2020-01-07 21:40:51.337  INFO 84448 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/9.0.13\n2020-01-07 21:40:51.347  INFO 84448 --- [           main] o.a.catalina.core.AprLifecycleListener   : The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: [/Users/funcy/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:.]\n2020-01-07 21:40:51.406  INFO 84448 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext\n2020-01-07 21:40:51.406  INFO 84448 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 800 ms\n2020-01-07 21:40:51.582  INFO 84448 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'\n2020-01-07 21:40:51.736  INFO 84448 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''\n2020-01-07 21:40:51.739  INFO 84448 --- [           main] com.gitee.funcy.mavenparent.war.Main     : Started Main in 1.39 seconds (JVM running for 3.943)\n2020-01-07 21:41:04.068  INFO 84448 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'\n2020-01-07 21:41:04.069  INFO 84448 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'\n2020-01-07 21:41:04.076  INFO 84448 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 7 ms\n\n```\n\n可以看到，项目启动成功，请求`http://localhost:8080`，也能获得结果：\n\n```\n $ curl http://localhost:8080\nhello world\n\n```\n\n以上启动方式**war 与 jar 打包方式**都支持。\n\n### 2\\. jar 包启动\n\n#### 2.1`java -jar`方式启动\n\n对于打成`jar包`的`springboot`项目，使用`java -jar xxx.jar`命令即可启动：\n\n```\n:target $ java -jar springboot-jar-1.0.0.jar\n\n  .   ____          _            __ _ _\n /\\\\ / ___'_ __ _ _(_)_ __  __ _ \\ \\ \\ \\\n( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\\n \\\\/  ___)| |_)| | | | | || (_| |  ) ) ) )\n  '  |____| .__|_| |_|_| |_\\__, | / / / /\n =========|_|==============|___/=/_/_/_/\n :: Spring Boot ::        (v2.1.1.RELEASE)\n\n2020-01-07 21:47:47.075  INFO 85080 --- [           main] com.gitee.funcy.maven.jar.Main           : Starting Main on funcydeMacBook-Pro.local with PID 85080 (/Users/funcy/IdeaProjects/myproject/springboot-demo/springboot-maven/springboot-jar/target/springboot-jar-1.0.0.jar started by funcy in /Users/funcy/IdeaProjects/myproject/springboot-demo/springboot-maven/springboot-jar/target)\n2020-01-07 21:47:47.077  INFO 85080 --- [           main] com.gitee.funcy.maven.jar.Main           : No active profile set, falling back to default profiles: default\n2020-01-07 21:47:48.152  INFO 85080 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)\n2020-01-07 21:47:48.186  INFO 85080 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]\n2020-01-07 21:47:48.186  INFO 85080 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/9.0.13\n2020-01-07 21:47:48.202  INFO 85080 --- [           main] o.a.catalina.core.AprLifecycleListener   : The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: [/Users/funcy/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:.]\n2020-01-07 21:47:48.303  INFO 85080 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext\n2020-01-07 21:47:48.303  INFO 85080 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1177 ms\n2020-01-07 21:47:48.502  INFO 85080 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'\n2020-01-07 21:47:48.677  INFO 85080 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''\n2020-01-07 21:47:48.680  INFO 85080 --- [           main] com.gitee.funcy.maven.jar.Main           : Started Main in 1.977 seconds (JVM running for 2.398)\n\n```\n\n访问`http://localhost:8080`，同样也能获得结果.\n\n#### 2.2`java org.springframework.boot.loader.JarLauncher`方式启动\n\n这种启动方式就魔幻了：好好的一个 jar，要先解压，然后直接运行里面的类，操作如下：\n\n```\ntarget $ unzip -d ./tmp springboot-jar-1.0.0.jar\nArchive:  springboot-jar-1.0.0.jar\n   creating: ./tmp/META-INF/\n  inflating: ./tmp/META-INF/MANIFEST.MF  \n   creating: ./tmp/org/\n   creating: ./tmp/org/springframework/\n   creating: ./tmp/org/springframework/boot/\n··· 省略其他内容\ntarget $ cd tmp/\ntmp $ java org.springframework.boot.loader.JarLauncher\n\n  .   ____          _            __ _ _\n /\\\\ / ___'_ __ _ _(_)_ __  __ _ \\ \\ \\ \\\n( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\\n \\\\/  ___)| |_)| | | | | || (_| |  ) ) ) )\n  '  |____| .__|_| |_|_| |_\\__, | / / / /\n =========|_|==============|___/=/_/_/_/\n :: Spring Boot ::        (v2.1.1.RELEASE)\n\n2020-01-07 21:56:00.472  INFO 85431 --- [           main] com.gitee.funcy.maven.jar.Main           : Starting Main on funcydeMacBook-Pro.local with PID 85431 (/Users/funcy/IdeaProjects/myproject/springboot-demo/springboot-maven/springboot-jar/target/tmp/BOOT-INF/classes started by funcy in /Users/funcy/IdeaProjects/myproject/springboot-demo/springboot-maven/springboot-jar/target/tmp)\n2020-01-07 21:56:00.475  INFO 85431 --- [           main] com.gitee.funcy.maven.jar.Main           : No active profile set, falling back to default profiles: default\n2020-01-07 21:56:01.589  INFO 85431 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)\n2020-01-07 21:56:01.619  INFO 85431 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]\n2020-01-07 21:56:01.619  INFO 85431 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/9.0.13\n2020-01-07 21:56:01.634  INFO 85431 --- [           main] o.a.catalina.core.AprLifecycleListener   : The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: [/Users/funcy/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:.]\n2020-01-07 21:56:01.722  INFO 85431 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext\n2020-01-07 21:56:01.722  INFO 85431 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1203 ms\n2020-01-07 21:56:01.931  INFO 85431 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'\n2020-01-07 21:56:02.154  INFO 85431 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''\n2020-01-07 21:56:02.157  INFO 85431 --- [           main] com.gitee.funcy.maven.jar.Main           : Started Main in 2.025 seconds (JVM running for 2.472)\n\n```\n\n总结下，步骤如下：\n\n1.  进入项目`target/`目录\n2.  解压`jar包`到`tmp`目录：`unzip -d ./tmp springboot-jar-1.0.0.jar`\n3.  进入`tmp目录`：`cd tmp/`\n4.  运行：`java org.springframework.boot.loader.JarLauncher`\n\n访问`http://localhost:8080`，也能得到正确结果。\n\n> 注：这种神奇的启动方式在什么情况下会使用呢？我曾经见过一些项目组，为了安全会把生产的配置文件放在服务器上，在部署项目的时候，先解压 jar 包，然后替换相应的配置文件，再运行。这种解压 jar 包、替换配置文件的方式就可以用此启动方式了。当然，这些解压、替换、启动等操作都会写进 shell 脚本里，自动化运行。\n\n### 3\\. war 包启动\n\n#### 3.1`java -jar`方式启动\n\n项目都打成`war包`了，还能使用`java -jar`启动？是的，`springboot`就是这么方便：\n\n```\ntarget $ java -jar springboot-war-1.0.0.war\n\n  .   ____          _            __ _ _\n /\\\\ / ___'_ __ _ _(_)_ __  __ _ \\ \\ \\ \\\n( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\\n \\\\/  ___)| |_)| | | | | || (_| |  ) ) ) )\n  '  |____| .__|_| |_|_| |_\\__, | / / / /\n =========|_|==============|___/=/_/_/_/\n :: Spring Boot ::        (v2.1.1.RELEASE)\n\n2020-01-07 22:11:54.284  INFO 85638 --- [           main] com.gitee.funcy.maven.war.Main           : Starting Main on funcydeMacBook-Pro.local with PID 85638 (/Users/funcy/IdeaProjects/myproject/springboot-demo/springboot-maven/springboot-war/target/springboot-war-1.0.0.war started by funcy in /Users/funcy/IdeaProjects/myproject/springboot-demo/springboot-maven/springboot-war/target)\n2020-01-07 22:11:54.287  INFO 85638 --- [           main] com.gitee.funcy.maven.war.Main           : No active profile set, falling back to default profiles: default\n2020-01-07 22:11:55.257  INFO 85638 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)\n2020-01-07 22:11:55.286  INFO 85638 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]\n2020-01-07 22:11:55.287  INFO 85638 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/9.0.13\n2020-01-07 22:11:55.299  INFO 85638 --- [           main] o.a.catalina.core.AprLifecycleListener   : The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: [/Users/funcy/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:.]\n2020-01-07 22:11:55.711  INFO 85638 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext\n2020-01-07 22:11:55.711  INFO 85638 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1379 ms\n2020-01-07 22:11:55.873  INFO 85638 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'\n2020-01-07 22:11:56.031  INFO 85638 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''\n2020-01-07 22:11:56.034  INFO 85638 --- [           main] com.gitee.funcy.maven.war.Main           : Started Main in 2.066 seconds (JVM running for 2.469)\n2020-01-07 22:12:01.189  INFO 85638 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'\n2020-01-07 22:12:01.190  INFO 85638 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'\n2020-01-07 22:12:01.195  INFO 85638 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 5 ms\n\n```\n\n看，项目真的跑起来了！\n\n#### 3.2`java org.springframework.boot.loader.WarLauncher`方式启动\n\n`springboot`的`jar包`可以解压，然后运行某个类来启动，`war包`竟然也有这种方法！`jar包`的启动类是`org.springframework.boot.loader.JarLauncher`，相应的`war包`启动类是`org.springframework.boot.loader.WarLauncher`，步骤如下：\n\n1.  进入项目`target/`目录\n2.  解压`war包`到`tmp`目录：`unzip -d ./tmp springboot-war-1.0.0.war`\n3.  进入`tmp目录`：`cd tmp/`\n4.  运行：`java org.springframework.boot.loader.WarLauncher`\n\n过程如下：\n\n```\ntarget $ unzip -d ./tmp springboot-war-1.0.0.war\nArchive:  springboot-war-1.0.0.war\n   creating: ./tmp/META-INF/\n  inflating: ./tmp/META-INF/MANIFEST.MF  \n   creating: ./tmp/org/\n   creating: ./tmp/org/springframework/\n   creating: ./tmp/org/springframework/boot/\n··· 省略其他\ntarget $ cd tmp/\ntmp $ java org.springframework.boot.loader.WarLauncher\n\n  .   ____          _            __ _ _\n /\\\\ / ___'_ __ _ _(_)_ __  __ _ \\ \\ \\ \\\n( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\\n \\\\/  ___)| |_)| | | | | || (_| |  ) ) ) )\n  '  |____| .__|_| |_|_| |_\\__, | / / / /\n =========|_|==============|___/=/_/_/_/\n :: Spring Boot ::        (v2.1.1.RELEASE)\n\n2020-01-07 22:17:09.637  INFO 85782 --- [           main] com.gitee.funcy.maven.war.Main           : Starting Main on funcydeMacBook-Pro.local with PID 85782 (/Users/funcy/IdeaProjects/myproject/springboot-demo/springboot-maven/springboot-war/target/tmp/WEB-INF/classes started by funcy in /Users/funcy/IdeaProjects/myproject/springboot-demo/springboot-maven/springboot-war/target/tmp)\n2020-01-07 22:17:09.640  INFO 85782 --- [           main] com.gitee.funcy.maven.war.Main           : No active profile set, falling back to default profiles: default\n2020-01-07 22:17:10.576  INFO 85782 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)\n2020-01-07 22:17:10.603  INFO 85782 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]\n2020-01-07 22:17:10.604  INFO 85782 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/9.0.13\n2020-01-07 22:17:10.616  INFO 85782 --- [           main] o.a.catalina.core.AprLifecycleListener   : The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: [/Users/funcy/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:.]\n2020-01-07 22:17:10.725  INFO 85782 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext\n2020-01-07 22:17:10.725  INFO 85782 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1046 ms\n2020-01-07 22:17:10.942  INFO 85782 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'\n2020-01-07 22:17:11.137  INFO 85782 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''\n2020-01-07 22:17:11.140  INFO 85782 --- [           main] com.gitee.funcy.maven.war.Main           : Started Main in 1.817 seconds (JVM running for 2.183)\n2020-01-07 22:17:15.024  INFO 85782 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'\n2020-01-07 22:17:15.024  INFO 85782 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'\n2020-01-07 22:17:15.029  INFO 85782 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 5 ms\n\n```\n\n可以看到，项目也启动成功了！\n\n#### 3.3 传统方式启动：使用 tomcat 容器\n\n最初的`war包`就是放在 tomcat 等容器中运行的，我们也来试试`war包`在 tomcat 容器中运行情况如何。这里说的 tomcat 容器是指在[tomcat 官网](https://www.oschina.net/action/GoToLink?url=http%3A%2F%2Ftomcat.apache.org%2F \"tomcat官网\")下载的容器，非`springboot`内置容器。这里我下载的是`apache-tomcat-8.5.47`，过程如下：\n\n```\n... 省略tomcat日志输出\n  .   ____          _            __ _ _\n /\\\\ / ___'_ __ _ _(_)_ __  __ _ \\ \\ \\ \\\n( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\\n \\\\/  ___)| |_)| | | | | || (_| |  ) ) ) )\n  '  |____| .__|_| |_|_| |_\\__, | / / / /\n =========|_|==============|___/=/_/_/_/\n :: Spring Boot ::        (v2.1.1.RELEASE)\n\n2020-01-07 22:28:23.519  INFO 85904 --- [ost-startStop-1] c.g.funcy.maven.war.StartApplication     : Starting StartApplication on funcydeMacBook-Pro.local with PID 85904 (/Users/funcy/Applications/Tomcat/apache-tomcat-8.5.47/webapps/springboot-war-1.0.0/WEB-INF/classes started by funcy in /Users/funcy/Applications/Tomcat/apache-tomcat-8.5.47)\n2020-01-07 22:28:23.523  INFO 85904 --- [ost-startStop-1] c.g.funcy.maven.war.StartApplication     : No active profile set, falling back to default profiles: default\n2020-01-07 22:28:24.256  INFO 85904 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 676 ms\n2020-01-07 22:28:24.655  INFO 85904 --- [ost-startStop-1] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'\n2020-01-07 22:28:24.920  INFO 85904 --- [ost-startStop-1] c.g.funcy.maven.war.StartApplication     : Started StartApplication in 1.86 seconds (JVM running for 3.98)\n07-Jan-2020 22:28:24.974 信息 [localhost-startStop-1] org.apache.jasper.servlet.TldScanner.scanJars 至少有一个JAR被扫描用于TLD但尚未包含TLD。 为此记录器启用调试日志记录，以获取已扫描但未在其中找到TLD的完整JAR列表。 在扫描期间跳过不需要的JAR可以缩短启动时间和JSP编译时间。\n07-Jan-2020 22:28:24.999 信息 [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployWAR Deployment of web application archive [/Users/funcy/Applications/Tomcat/apache-tomcat-8.5.47/webapps/springboot-war-1.0.0.war] has finished in [3,468] ms\n07-Jan-2020 22:28:25.000 信息 [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory 把web 应用程序部署到目录 [/Users/funcy/Applications/Tomcat/apache-tomcat-8.5.47/webapps/docs]\n07-Jan-2020 22:28:25.010 信息 [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [/Users/funcy/Applications/Tomcat/apache-tomcat-8.5.47/webapps/docs] has finished in [10] ms\n07-Jan-2020 22:28:25.010 信息 [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory 把web 应用程序部署到目录 [/Users/funcy/Applications/Tomcat/apache-tomcat-8.5.47/webapps/manager]\n07-Jan-2020 22:28:25.027 信息 [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [/Users/funcy/Applications/Tomcat/apache-tomcat-8.5.47/webapps/manager] has finished in [17] ms\n07-Jan-2020 22:28:25.027 信息 [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory 把web 应用程序部署到目录 [/Users/funcy/Applications/Tomcat/apache-tomcat-8.5.47/webapps/examples]\n07-Jan-2020 22:28:25.181 信息 [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [/Users/funcy/Applications/Tomcat/apache-tomcat-8.5.47/webapps/examples] has finished in [154] ms\n07-Jan-2020 22:28:25.181 信息 [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory 把web 应用程序部署到目录 [/Users/funcy/Applications/Tomcat/apache-tomcat-8.5.47/webapps/ROOT]\n07-Jan-2020 22:28:25.191 信息 [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [/Users/funcy/Applications/Tomcat/apache-tomcat-8.5.47/webapps/ROOT] has finished in [10] ms\n07-Jan-2020 22:28:25.191 信息 [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory 把web 应用程序部署到目录 [/Users/funcy/Applications/Tomcat/apache-tomcat-8.5.47/webapps/host-manager]\n07-Jan-2020 22:28:25.202 信息 [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [/Users/funcy/Applications/Tomcat/apache-tomcat-8.5.47/webapps/host-manager] has finished in [11] ms\n07-Jan-2020 22:28:25.206 信息 [main] org.apache.coyote.AbstractProtocol.start 开始协议处理句柄[\"http-nio-8080\"]\n07-Jan-2020 22:28:25.212 信息 [main] org.apache.coyote.AbstractProtocol.start 开始协议处理句柄[\"ajp-nio-8009\"]\n07-Jan-2020 22:28:25.213 信息 [main] org.apache.catalina.startup.Catalina.start Server startup in 3717 ms\n2020-01-07 22:29:30.754  INFO 85904 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'\n2020-01-07 22:29:30.767  INFO 85904 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet        : Completed initialization in 12 ms\n\n```\n\n请求`http://localhost:8080/springboot-war-1.0.0/`，结果如下：\n\n```\n$ curl 'http://localhost:8080/springboot-war-1.0.0/'\nhello world\n\n```\n\n可以看到，已经部署成功了。\n\n### 4\\. 总结\n\n|  | main () 方法 | mvn 命令 | java -jar | java xxx.WarLauncher | java xxx.JarLauncher | 外置容器 |\n| --- | --- | --- | --- | --- | --- | --- |\n| war | 支持 | 支持 | 支持 | 支持 | 不支持 | 支持 |\n| jar | 支持 | 支持 | 支持 | 不支持 | 支持 | 不支持\n\n### 1\\. maven 打包后的文件\n\n进入`springboot-jar/target`目录，使用`tree`命令，目录结构如下：\n\n```\n $ tree\n.\n├── classes\n│ └── com\n│     └── gitee\n│         └── funcy\n│             └── maven\n│                 └── jar\n│                     ├── Main.class\n│                     └── controller\n│                         └── IndexController.class\n├── generated-sources\n│ └── annotations\n├── maven-archiver\n│ └── pom.properties\n├── maven-status\n│ └── maven-compiler-plugin\n│     └── compile\n│         └── default-compile\n│             ├── createdFiles.lst\n│             └── inputFiles.lst\n├── springboot-jar-1.0.0.jar\n└── springboot-jar-1.0.0.jar.original\n\n14 directories, 7 files\n\n```\n\n注意`springboot-jar-1.0.0.jar`与`springboot-jar-1.0.0.jar.original`的区别：`springboot-jar-1.0.0.jar.original`属于原始 Maven 打包 jar 文件，该文件仅包含应用本地资源，如编译后的 classes 目录下的资源文件等，未引入第三方依赖资源；而`springboot-jar-1.0.0.jar`引入了第三方依赖资源（主要为 jar 包）。\n\n使用`unzip springboot-jar-1.0.0.jar -d tmp`解压 jar 包，内容如下：\n\n```\n $ tree tmp/\ntmp/\n├── BOOT-INF\n│ ├── classes\n│ │ └── com\n│ │     └── gitee\n│ │         └── funcy\n│ │             └── maven\n│ │                 └── jar\n│ │                     ├── Main.class\n│ │                     └── controller\n│ │                         └── IndexController.class\n│ └── lib\n│     ├── classmate-1.4.0.jar\n│     ├── hibernate-validator-6.0.13.Final.jar\n│     ├── jackson-annotations-2.9.0.jar\n│     ├── jackson-core-2.9.7.jar\n│     ├── jackson-databind-2.9.7.jar\n│     ├── jackson-datatype-jdk8-2.9.7.jar\n│     ├── jackson-datatype-jsr310-2.9.7.jar\n│     ├── jackson-module-parameter-names-2.9.7.jar\n│     ├── javax.annotation-api-1.3.2.jar\n│     ├── jboss-logging-3.3.2.Final.jar\n│     ├── jul-to-slf4j-1.7.25.jar\n│     ├── log4j-api-2.11.1.jar\n│     ├── log4j-to-slf4j-2.11.1.jar\n│     ├── logback-classic-1.2.3.jar\n│     ├── logback-core-1.2.3.jar\n│     ├── slf4j-api-1.7.25.jar\n│     ├── snakeyaml-1.23.jar\n│     ├── spring-aop-5.1.3.RELEASE.jar\n│     ├── spring-beans-5.1.3.RELEASE.jar\n│     ├── spring-boot-2.1.1.RELEASE.jar\n│     ├── spring-boot-autoconfigure-2.1.1.RELEASE.jar\n│     ├── spring-boot-starter-2.1.1.RELEASE.jar\n│     ├── spring-boot-starter-json-2.1.1.RELEASE.jar\n│     ├── spring-boot-starter-logging-2.1.1.RELEASE.jar\n│     ├── spring-boot-starter-tomcat-2.1.1.RELEASE.jar\n│     ├── spring-boot-starter-web-2.1.1.RELEASE.jar\n│     ├── spring-context-5.1.3.RELEASE.jar\n│     ├── spring-core-5.1.3.RELEASE.jar\n│     ├── spring-expression-5.1.3.RELEASE.jar\n│     ├── spring-jcl-5.1.3.RELEASE.jar\n│     ├── spring-web-5.1.3.RELEASE.jar\n│     ├── spring-webmvc-5.1.3.RELEASE.jar\n│     ├── tomcat-embed-core-9.0.13.jar\n│     ├── tomcat-embed-el-9.0.13.jar\n│     ├── tomcat-embed-websocket-9.0.13.jar\n│     └── validation-api-2.0.1.Final.jar\n├── META-INF\n│ ├── MANIFEST.MF\n│ └── maven\n│     └── com.gitee.funcy\n│         └── springboot-jar\n│             ├── pom.properties\n│             └── pom.xml\n└── org\n    └── springframework\n        └── boot\n            └── loader\n                ├── ExecutableArchiveLauncher.class\n                ├── JarLauncher.class\n                ├── LaunchedURLClassLoader$UseFastConnectionExceptionsEnumeration.class\n                ├── LaunchedURLClassLoader.class\n                ├── Launcher.class\n                ├── MainMethodRunner.class\n                ├── PropertiesLauncher$1.class\n                ├── PropertiesLauncher$ArchiveEntryFilter.class\n                ├── PropertiesLauncher$PrefixMatchingArchiveFilter.class\n                ├── PropertiesLauncher.class\n                ├── WarLauncher.class\n                ├── archive\n                │ ├── Archive$Entry.class\n                │ ├── Archive$EntryFilter.class\n                │ ├── Archive.class\n                │ ├── ExplodedArchive$1.class\n                │ ├── ExplodedArchive$FileEntry.class\n                │ ├── ExplodedArchive$FileEntryIterator$EntryComparator.class\n                │ ├── ExplodedArchive$FileEntryIterator.class\n                │ ├── ExplodedArchive.class\n                │ ├── JarFileArchive$EntryIterator.class\n                │ ├── JarFileArchive$JarFileEntry.class\n                │ └── JarFileArchive.class\n                ├── data\n                │ ├── RandomAccessData.class\n                │ ├── RandomAccessDataFile$1.class\n                │ ├── RandomAccessDataFile$DataInputStream.class\n                │ ├── RandomAccessDataFile$FileAccess.class\n                │ └── RandomAccessDataFile.class\n                ├── jar\n                │ ├── AsciiBytes.class\n                │ ├── Bytes.class\n                │ ├── CentralDirectoryEndRecord.class\n                │ ├── CentralDirectoryFileHeader.class\n                │ ├── CentralDirectoryParser.class\n                │ ├── CentralDirectoryVisitor.class\n                │ ├── FileHeader.class\n                │ ├── Handler.class\n                │ ├── JarEntry.class\n                │ ├── JarEntryFilter.class\n                │ ├── JarFile$1.class\n                │ ├── JarFile$2.class\n                │ ├── JarFile$JarFileType.class\n                │ ├── JarFile.class\n                │ ├── JarFileEntries$1.class\n                │ ├── JarFileEntries$EntryIterator.class\n                │ ├── JarFileEntries.class\n                │ ├── JarURLConnection$1.class\n                │ ├── JarURLConnection$JarEntryName.class\n                │ ├── JarURLConnection.class\n                │ ├── StringSequence.class\n                │ └── ZipInflaterInputStream.class\n                └── util\n                    └── SystemPropertyUtils.class\n\n21 directories, 91 files\n\n```\n\n可以看到，文件中主要分为如下几个目录：\n\n*   `BOOT-INF/classes`目录存放应用编译后的 class 文件；\n*   `BOOT-INF/lib`目录存放应用依赖的 jar 包；\n*   `META-INF/`目录存放应用依赖的 jar 包；\n*   `org/`目录存放 spring boot 相关的 class 文件。\n\n### 2.`java -jar`启动 springboot jar 包\n\njava 官方规定，`java -jar`命令引导的具体启动类必须配置在`MANIFEST.MF`文件中，而根据`jar文件规范`，`MANIFEST.MF`文件必须存放在`/META-INF/`目录下。因此，启动类配置在 jar 包的`/META-INF/MANIFEST.MF`文件中，查看该文件，内容如下：\n\n```\n$ cat MANIFEST.MF \nManifest-Version: 1.0\nArchiver-Version: Plexus Archiver\nBuilt-By: fangchengyan\nStart-Class: com.gitee.funcy.maven.jar.Main\nSpring-Boot-Classes: BOOT-INF/classes/\nSpring-Boot-Lib: BOOT-INF/lib/\nSpring-Boot-Version: 2.1.1.RELEASE\nCreated-By: Apache Maven 3.6.0\nBuild-Jdk: 1.8.0_222\nMain-Class: org.springframework.boot.loader.JarLauncher\n\n```\n\n发现`Main-Class`属性指向的`Class`为`org.springframework.boot.loader.JarLauncher`，而该类存放在 jar 包的`org/springframework/boot/loader/`目录下，并且项目的引导类定义在`Start-Class`属性性中，该属性并非 java 平台标准`META-INF/MANIFEST.MF`属性。\n\n> 注：\n>\n> 1.  `org.springframework.boot.loader.JarLauncher`是可执行 jar 的启动器，`org.springframework.boot.loader.WarLauncher`是可执行 war 的启动器。\n>\n>\n> 2.  `org.springframework.boot.loader.JarLauncher`所在的 jar 文件的 Maven GAV 信息为`org.springframework.boot:spring-boot-loader:${springboot-version}`，通常情况下，这个依赖没有必要引入 springboot 项目的 pom.xml 文件。\n\n查看`JarLauncher`源码，如下：\n\n```\npublic class JarLauncher extends ExecutableArchiveLauncher {\n\n\tstatic final String BOOT_INF_CLASSES = \"BOOT-INF/classes/\";\n\n\tstatic final String BOOT_INF_LIB = \"BOOT-INF/lib/\";\n\n\tpublic JarLauncher() {\n\t}\n\n\tprotected JarLauncher(Archive archive) {\n\t\tsuper(archive);\n\t}\n\n\t@Override\n\tprotected boolean isNestedArchive(Archive.Entry entry) {\n\t\tif (entry.isDirectory()) {\n\t\t\treturn entry.getName().equals(BOOT_INF_CLASSES);\n\t\t}\n\t\treturn entry.getName().startsWith(BOOT_INF_LIB);\n\t}\n\n\tpublic static void main(String[] args) throws Exception {\n\t\tnew JarLauncher().launch(args);\n\t}\n\n}\n\n```\n\n可以发现，`BOOT-INF/classes/`与`BOOT-INF/lib/`分别使用常量`BOOT_INF_CLASSES`和`BOOT_INF_LIB`表示，并且用于`isNestedArchive(Archive.Entry)`方法判断，从该方法的实现分析，方法参数`Archive.Entry`看似为 jar 文件中的资源，比如`application.properties`。\n\n`Archive.Entry`有两种实现，其中一种为`org.springframework.boot.loader.archive.JarFileArchive.JarFileEntry`，基于`java.util.jar.JarEntry`，表示`FAT JAR`嵌入资源，另一种为`org.springframework.boot.loader.archive.ExplodedArchive.FileEntry`，基于文件系统实现。这也说明了`JarLauncher`支持`JAR`和`文件系统`两种启动方式。\n\n> 文件系统启动方式如下：\n>\n> 1.  解压 jar 包到`temp`目录：`unzip springboot-jar-1.0.0.jar -d tmp`\n> 2.  进入`temp`目录，运行命令：`java org.springframework.boot.loader.JarLauncher`可以看到，项目同样能正常启动。\n\n在`JarLauncher`作为引导类时，当执行`java -jar`命令时，`/META-INF`资源的`Main-Class`属性将调用其`main(String[])`方法，实际上调用的是`JarLauncher#launch(args)`方法，而该方法继承于基类`org.springframework.boot.loader.Launcher`，它们之间的继承关系如下：\n\n*   `org.springframework.boot.loader.Launcher`\n  *   `org.springframework.boot.loader.ExecutableArchiveLauncher`\n    *   `org.springframework.boot.loader.JarLauncher`\n    *   `org.springframework.boot.loader.WarLauncher`\n\n简单来说，springboot jar 启动过程如下：\n\n1.  `java -jar xxx.jar`运行的是`JarLauncher`\n2.  `JarLauncher#main(String[])`方法会调用`Launcher#launch(String[])`方法，创建 ClassLoader () 及调用项目的`main`方法\n  *   项目主类的获取实现位于`ExecutableArchiveLauncher#getMainClass()`，主要是从`/META-INF/MANIFEST.MF`获取`Start-Class`属性\n  *   项目主类的 main () 方法调用位于`MainMethodRunner#run()`，使用反射方式进行调用\n\n### 3.`java -jar`启动 springboot war 包\n\n从上面的分析，我们得到了启动 jar 包的`org.springframework.boot.loader.JarLauncher`以及启动 war 包的`org.springframework.boot.loader.WarLauncher`，这里我们来分析下`WarLauncher`上如何工作的。\n\n`WarLauncher`代码如下：\n\n```\npublic class WarLauncher extends ExecutableArchiveLauncher {\n\n\tprivate static final String WEB_INF = \"WEB-INF/\";\n\n\tprivate static final String WEB_INF_CLASSES = WEB_INF + \"classes/\";\n\n\tprivate static final String WEB_INF_LIB = WEB_INF + \"lib/\";\n\n\tprivate static final String WEB_INF_LIB_PROVIDED = WEB_INF + \"lib-provided/\";\n\n\tpublic WarLauncher() {\n\t}\n\n\tprotected WarLauncher(Archive archive) {\n\t\tsuper(archive);\n\t}\n\n\t@Override\n\tpublic boolean isNestedArchive(Archive.Entry entry) {\n\t\tif (entry.isDirectory()) {\n\t\t\treturn entry.getName().equals(WEB_INF_CLASSES);\n\t\t}\n\t\telse {\n\t\t\treturn entry.getName().startsWith(WEB_INF_LIB)\n\t\t\t\t\t|| entry.getName().startsWith(WEB_INF_LIB_PROVIDED);\n\t\t}\n\t}\n\n\tpublic static void main(String[] args) throws Exception {\n\t\tnew WarLauncher().launch(args);\n\t}\n\n}\n\n```\n\n可以看到，`WEB-INF/classes/`、`WEB-INF/lib/`、`WEB-INF/lib-provided/`均为`WarLauncher`的`Class Path`，其中`WEB-INF/classes/`、`WEB-INF/lib/`是传统的 Servlet 应用的 ClassPath 路径，而`WEB-INF/lib-provided/`属性 springboot`WarLauncher`定制实现。那么`WEB-INF/lib-provided/`究竟是干嘛的呢？看到`provided`，我们可以大胆猜想`WEB-INF/lib-provided/`存放的是`pom.xml`文件中，`scope`为`provided`的 jar。\n\n为了验证以上猜想，修改的 pom.xml 文件如下：\n\n```\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 \n         http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>com.gitee.funcy</groupId>\n    springboot-war\n    <version>1.0.0</version>\n    <!-- 指定打包方式为war包 -->\n    <packaging>war</packaging>\n    <name>springboot非parent war打包方式</name>\n\n    <properties>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n        <java.version>1.8</java.version>\n        <spring-boot.version>2.1.1.RELEASE</spring-boot.version>\n        <maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>\n    </properties>\n\n    <dependencyManagement>\n        <dependencies>\n            <dependency>\n                <!-- Import dependency management from Spring Boot -->\n                <groupId>org.springframework.boot</groupId>\n                spring-boot-dependencies\n                <version>${spring-boot.version}</version>\n                <type>pom</type>\n                <scope>import</scope>\n            </dependency>\n        </dependencies>\n    </dependencyManagement>\n\n    <dependencies>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            spring-boot-starter\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            spring-boot-starter-web\n        </dependency>\n        <!-- 测试war包中 WEB-INF/lib-provided/ 目录内容-->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            spring-boot-test\n            <scope>provided</scope>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                maven-compiler-plugin\n                <version>${maven-compiler-plugin.version}</version>\n                <configuration>\n                    <source>${java.version}</source>\n                    <target>${java.version}</target>\n                    <encoding>${project.build.sourceEncoding}</encoding>\n                </configuration>\n            </plugin>\n            <!-- 打成war包时，需要添加该插件 -->\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                maven-war-plugin\n                <!-- 保持与 spring-boot-dependencies 版本一致 -->\n                <version>3.2.2</version>\n            </plugin>\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                spring-boot-maven-plugin\n                <version>${spring-boot.version}</version>\n                <executions>\n                    <execution>\n                        <goals>\n                            <goal>repackage</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n        </plugins>\n    </build>\n\n</project>\n\n```\n\n这里我们添加了 springboot 的测试 jar`org.springframework.boot:spring-boot-test`，并将其`scope`设置为`provided`. 运行 maven 打包命令`mvn clean install -Dmaven.test.skip=true`，可以看到项目能正常打包。\n\n打包完成后，进入`target`目录，运行`java -jar springboot-war-1.0.0.war`，项目能正常启动。\n\n接下来，我们来看看`springboot-war-1.0.0.war`有些啥。首先使用`unzip springboot-war-1.0.0.war -d tmp`命令解压，再使用`tree -h`命令查看文件结构，结果如下\n\n```\n $ tree -h\n.\n├── [ 128]  META-INF\n│ ├── [ 311]  MANIFEST.MF\n│ └── [  96]  maven\n│     └── [  96]  com.gitee.funcy\n│         └── [ 128]  springboot-war\n│             ├── [  95]  pom.properties\n│             └── [3.3K]  pom.xml\n├── [ 160]  WEB-INF\n│ ├── [  96]  classes\n│ │ └── [  96]  com\n│ │     └── [  96]  gitee\n│ │         └── [  96]  funcy\n│ │             └── [  96]  maven\n│ │                 └── [ 160]  war\n│ │                     ├── [ 688]  Main.class\n│ │                     ├── [ 891]  StartApplication.class\n│ │                     └── [  96]  controller\n│ │                         └── [ 646]  IndexController.class\n│ ├── [1.2K]  lib\n│ │ ├── [ 65K]  classmate-1.4.0.jar\n│ │ ├── [1.1M]  hibernate-validator-6.0.13.Final.jar\n│ │ ├── [ 65K]  jackson-annotations-2.9.0.jar\n│ │ ├── [316K]  jackson-core-2.9.7.jar\n│ │ ├── [1.3M]  jackson-databind-2.9.7.jar\n│ │ ├── [ 33K]  jackson-datatype-jdk8-2.9.7.jar\n│ │ ├── [ 98K]  jackson-datatype-jsr310-2.9.7.jar\n│ │ ├── [8.4K]  jackson-module-parameter-names-2.9.7.jar\n│ │ ├── [ 26K]  javax.annotation-api-1.3.2.jar\n│ │ ├── [ 65K]  jboss-logging-3.3.2.Final.jar\n│ │ ├── [4.5K]  jul-to-slf4j-1.7.25.jar\n│ │ ├── [258K]  log4j-api-2.11.1.jar\n│ │ ├── [ 17K]  log4j-to-slf4j-2.11.1.jar\n│ │ ├── [284K]  logback-classic-1.2.3.jar\n│ │ ├── [461K]  logback-core-1.2.3.jar\n│ │ ├── [ 40K]  slf4j-api-1.7.25.jar\n│ │ ├── [294K]  snakeyaml-1.23.jar\n│ │ ├── [360K]  spring-aop-5.1.3.RELEASE.jar\n│ │ ├── [656K]  spring-beans-5.1.3.RELEASE.jar\n│ │ ├── [935K]  spring-boot-2.1.1.RELEASE.jar\n│ │ ├── [1.2M]  spring-boot-autoconfigure-2.1.1.RELEASE.jar\n│ │ ├── [ 413]  spring-boot-starter-2.1.1.RELEASE.jar\n│ │ ├── [ 421]  spring-boot-starter-json-2.1.1.RELEASE.jar\n│ │ ├── [ 423]  spring-boot-starter-logging-2.1.1.RELEASE.jar\n│ │ ├── [ 422]  spring-boot-starter-tomcat-2.1.1.RELEASE.jar\n│ │ ├── [ 421]  spring-boot-starter-web-2.1.1.RELEASE.jar\n│ │ ├── [1.0M]  spring-context-5.1.3.RELEASE.jar\n│ │ ├── [1.2M]  spring-core-5.1.3.RELEASE.jar\n│ │ ├── [274K]  spring-expression-5.1.3.RELEASE.jar\n│ │ ├── [ 23K]  spring-jcl-5.1.3.RELEASE.jar\n│ │ ├── [1.3M]  spring-web-5.1.3.RELEASE.jar\n│ │ ├── [782K]  spring-webmvc-5.1.3.RELEASE.jar\n│ │ ├── [3.1M]  tomcat-embed-core-9.0.13.jar\n│ │ ├── [244K]  tomcat-embed-el-9.0.13.jar\n│ │ ├── [257K]  tomcat-embed-websocket-9.0.13.jar\n│ │ └── [ 91K]  validation-api-2.0.1.Final.jar\n│ └── [  96]  lib-provided\n│     └── [194K]  spring-boot-test-2.1.1.RELEASE.jar\n└── [  96]  org\n    └── [  96]  springframework\n        └── [  96]  boot\n            └── [ 544]  loader\n                ├── [3.5K]  ExecutableArchiveLauncher.class\n                ├── [1.5K]  JarLauncher.class\n                ├── [1.5K]  LaunchedURLClassLoader$UseFastConnectionExceptionsEnumeration.class\n                ├── [5.6K]  LaunchedURLClassLoader.class\n                ├── [4.6K]  Launcher.class\n                ├── [1.5K]  MainMethodRunner.class\n                ├── [ 266]  PropertiesLauncher$1.class\n                ├── [1.4K]  PropertiesLauncher$ArchiveEntryFilter.class\n                ├── [1.9K]  PropertiesLauncher$PrefixMatchingArchiveFilter.class\n                ├── [ 19K]  PropertiesLauncher.class\n                ├── [1.7K]  WarLauncher.class\n                ├── [ 416]  archive\n                │ ├── [ 302]  Archive$Entry.class\n                │ ├── [ 437]  Archive$EntryFilter.class\n                │ ├── [ 945]  Archive.class\n                │ ├── [ 273]  ExplodedArchive$1.class\n                │ ├── [1.1K]  ExplodedArchive$FileEntry.class\n                │ ├── [1.5K]  ExplodedArchive$FileEntryIterator$EntryComparator.class\n                │ ├── [3.7K]  ExplodedArchive$FileEntryIterator.class\n                │ ├── [5.1K]  ExplodedArchive.class\n                │ ├── [1.7K]  JarFileArchive$EntryIterator.class\n                │ ├── [1.1K]  JarFileArchive$JarFileEntry.class\n                │ └── [7.2K]  JarFileArchive.class\n                ├── [ 224]  data\n                │ ├── [ 485]  RandomAccessData.class\n                │ ├── [ 282]  RandomAccessDataFile$1.class\n                │ ├── [2.6K]  RandomAccessDataFile$DataInputStream.class\n                │ ├── [3.2K]  RandomAccessDataFile$FileAccess.class\n                │ └── [3.9K]  RandomAccessDataFile.class\n                ├── [ 768]  jar\n                │ ├── [4.9K]  AsciiBytes.class\n                │ ├── [ 616]  Bytes.class\n                │ ├── [3.0K]  CentralDirectoryEndRecord.class\n                │ ├── [5.1K]  CentralDirectoryFileHeader.class\n                │ ├── [4.5K]  CentralDirectoryParser.class\n                │ ├── [ 540]  CentralDirectoryVisitor.class\n                │ ├── [ 345]  FileHeader.class\n                │ ├── [ 12K]  Handler.class\n                │ ├── [3.5K]  JarEntry.class\n                │ ├── [ 299]  JarEntryFilter.class\n                │ ├── [2.0K]  JarFile$1.class\n                │ ├── [1.2K]  JarFile$2.class\n                │ ├── [1.3K]  JarFile$JarFileType.class\n                │ ├── [ 15K]  JarFile.class\n                │ ├── [1.6K]  JarFileEntries$1.class\n                │ ├── [2.0K]  JarFileEntries$EntryIterator.class\n                │ ├── [ 14K]  JarFileEntries.class\n                │ ├── [ 702]  JarURLConnection$1.class\n                │ ├── [4.2K]  JarURLConnection$JarEntryName.class\n                │ ├── [9.6K]  JarURLConnection.class\n                │ ├── [3.5K]  StringSequence.class\n                │ └── [1.8K]  ZipInflaterInputStream.class\n                └── [  96]  util\n                    └── [5.1K]  SystemPropertyUtils.class\n\n22 directories, 93 files\n\n```\n\n相比于`FAT JAR`的解压目录，`War`增加了`WEB-INF/lib-provided`，并且该目录仅有一个 jar 文件，即`spring-boot-test-2.1.1.RELEASE.jar`，这正是我们在 pom.xml 文件中设置的`scope`为`provided`的 jar 包。\n\n由此可以得出结论：**`WEB-INF/lib-provided`存放的是`scope`为`provided`的 jar 包**。\n\n我们现来看下`META-INF/MANIFEST.MF`的内容：\n\n```\n$ cat META-INF/MANIFEST.MF \nManifest-Version: 1.0\nBuilt-By: fangchengyan\nStart-Class: com.gitee.funcy.maven.war.Main\nSpring-Boot-Classes: WEB-INF/classes/\nSpring-Boot-Lib: WEB-INF/lib/\nSpring-Boot-Version: 2.1.1.RELEASE\nCreated-By: Apache Maven 3.6.0\nBuild-Jdk: 1.8.0_222\nMain-Class: org.springframework.boot.loader.WarLauncher\n\n```\n\n可以看到，该文件与 jar 包中的`META-INF/MANIFEST.MF`很相似，在文件中同样定义了`Main-Class`与`Start-Class`，这也说明了该 war 可以使用`java -jar xxx.jar`和`java org.springframework.boot.loader.WarLauncher`启动，这也与我们的验证结果一致。\n\n### 4\\. tomcat 等外部容器启动 war 包\n\n在 springboo 刚开始推广的时候，我们还是习惯于将项目打成 war 包，然后部署到 tomcat 等 web 容器中运行。那 springboot 的 war 包是如何做到既能用 java 命令启动，又能放在 tomcat 容器中启动呢？这就是之前提到的`WEB-INF/lib-provided`目录的功能了。\n\n传统的`servlet`应用的`class path`路径仅关注`WEB-INF/classes/`和`WEB-INF/lib/`，`WEB-INF/lib-provided/`目录下的 jar 包将被`servlet`容器忽略，如`servlet api`，该 api 由`servlet`容器提供。我们在打包时，可以把`servlet`相关 jar 包的`scope`设置成`provided`，这样就完美实现了`servlet`容器启动与`java`命令启动的兼容：\n\n*   当部署到`servlet`容器中时，`WEB-INF/lib-provided/`目录下的 jar 包就被容器忽略了（由于`servlet`容器本身就提供了`servlet`的相关 jar 包，如果不忽略，就会出现 jar 包重复引入问题）；\n*   当使用`java`命令执行时，此时无`servlet`容器提供`servlet`的相关 jar 包，而`WarLauncher`在运行过程中会加载`WEB-INF/lib-provided/`目录下的 jar 包。"
  },
  {
    "path": "docs/Spring全家桶/SpringBoot/SpringBoot生产环境工具Actuator.md",
    "content": "\n\n# \n\n[Back to index](https://springdoc.cn/spring-boot/index.html)\n\n*   [1\\. Ĺ](https://springdoc.cn/spring-boot/actuator.html#actuator.enabling)\n*   [2\\. ˵㣨Endpoint](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints)\n*   [3\\. ͨHTTPмغ͹](https://springdoc.cn/spring-boot/actuator.html#actuator.monitoring)\n*   [4\\. ͨJMXмغ͹](https://springdoc.cn/spring-boot/actuator.html#actuator.jmx)\n*   [5\\. ɹ۲ԣObservability](https://springdoc.cn/spring-boot/actuator.html#actuator.observability)\n*   [6\\. ־¼Logger](https://springdoc.cn/spring-boot/actuator.html#actuator.loggers)\n*   [7\\. ָ꣨Metrics](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics)\n*   [8\\. ׷٣Tracing](https://springdoc.cn/spring-boot/actuator.html#actuator.micrometer-tracing)\n*   [9\\. ](https://springdoc.cn/spring-boot/actuator.html#actuator.auditing)\n*   [10\\. ¼ HTTP Exchange](https://springdoc.cn/spring-boot/actuator.html#actuator.http-exchanges)\n*   [11\\. ̼](https://springdoc.cn/spring-boot/actuator.html#actuator.process-monitoring)\n*   [12\\. Cloud Foundry ֧](https://springdoc.cn/spring-boot/actuator.html#actuator.cloud-foundry)\n*   [13\\. ʲô](https://springdoc.cn/spring-boot/actuator.html#actuator.whats-next)\n\n\n\n\n\n\n\n\n\n\n\n\n\n|  | վ([springdoc.cn](https://springdoc.cn/))еԴ [spring.io](https://spring.io/) ԭʼȨ [spring.io](https://spring.io/) [springboot.io - Spring Boot](https://springboot.io/) з룬ɹѧϰоδɣýκתءû֮صΪ ̱Spring  Pivotal Software, Inc. Լҵ̱ꡣ |\n| --- | --- |\n\n\n\n\n\nSpring BootһЩĹܣ԰ڽӦó򷢲ʱغ͹Ӧó ѡͨʹHTTP˵ʹJMXͼӦó ơָռҲԶӦӦó\n\n\n\n\n\n\n\n\n\n## [](https://springdoc.cn/spring-boot/actuator.html#actuator.enabling)1\\. Ĺ\n\n\n\n\n\n[`spring-boot-actuator`](https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-actuator) ģṩSpring Bootܡ ЩܵƼӶ `spring-boot-starter-actuator` Starter \n\n\n\n\n\n\n\nActuatorĶ\n\n\n\nactuatorִ һָƶĳĻеװáactuator ԴһСı仯в˶\n\n\n\n\n\n\n\n\n\nҪڻMavenĿactuator Starter \n\n\n\n\n\n\n\n```\n<dependencies>\n    <dependency>\n        <groupId>org.springframework.boot</groupId>\n        spring-boot-starter-actuator\n    </dependency>\n</dependencies>\n```\n\n\n\n\n\n\n\nGradleʹ\n\n\n\n\n\n\n\n```\ndependencies {\n    implementation 'org.springframework.boot:spring-boot-starter-actuator'\n}\n```\n\n\n\n\n\n\n\n\n\n\n\n## [](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints)2\\. ˵㣨Endpoint\n\n\n\n\n\nActuator ˵㣨endpointԼزӦó򻥶 Spring BootһЩõĶ˵㣬ԼĶ˵㡣 磬`health` ˵ṩӦó򽡿Ϣ\n\n\n\n\n\n[û](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.enabling)ÿĶ˵㣬[ͨHTTPJMXǣʹǿԶ̷ʣ](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.exposing)һ˵㱻úͱ¶ʱΪǿõġõĶ˵ֻǿʱŻᱻԶáӦóѡͨHTTP¶ж˵ID `/actuator` ǰ׺ӳ䵽һURL磬Ĭ£`health` ˵㱻ӳ䵽 `/actuator/health`\n\n\n\n\n\n|  | Ҫ˽actuatorĶ˵ԼǵӦʽ뿴APIĵ [HTML](https://docs.spring.io/spring-boot/docs/3.1.0-SNAPSHOT/actuator-api/htmlsingle)  [PDF](https://docs.spring.io/spring-boot/docs/3.1.0-SNAPSHOT/actuator-api/pdf/spring-boot-actuator-web-api.pdf) |\n| --- | --- |\n\n\n\n\n\nǼ޹صնˡ\n\n\n\n<colgroup><col><col></colgroup>\n| ID | ˵ |\n| --- | --- |\n| `auditevents` | ǰӦó¼Ϣ Ҫһ `AuditEventRepository` bean |\n| `beans` | ʾӦóSpring Beanб |\n| `caches` | ʾõĻ档 |\n| `conditions` | ʾúԶԼǷϻ򲻷ϵԭ |\n| `configprops` | ʾ `@ConfigurationProperties` б |\n| `env` | ¶Spring `ConfigurableEnvironment` еԡ |\n| `flyway` | ʾκѾӦõFlywayݿǨơ Ҫһ `Flyway` bean |\n| `health` | ʾӦóĽϢ |\n| `httpexchanges` | ʾ HTTP exchange ϢĬ£ 100  HTTP request/response exchange Ҫһ `HttpExchangeRepository` bean |\n| `info` | ʾӦóϢ |\n| `integrationgraph` | ʾSpringͼ Ҫ `spring-integration-core` |\n| `loggers` | ʾ޸Ӧóloggerá |\n| `liquibase` | ʾκѾӦõLiquibaseݿǨơ Ҫһ `Liquibase` Bean |\n| `metrics` | ʾǰӦó metrics Ϣ |\n| `mappings` | ʾ `@RequestMapping` ·б |\n| `quartz` | ʾйQuartz Scheduler JobϢ |\n| `scheduledtasks` | ʾӦóеļƻ |\n| `sessions` | Spring Sessionֵ֧ĻỰ洢мɾûỰ ҪһʹSpring SessionĻServletWebӦó |\n| `shutdown` | ӦóŵعرաֻʹjarʱЧĬǽõġ |\n| `startup` | ʾ `ApplicationStartup` ռ[](https://springdoc.cn/spring-boot/features.html#features.spring-application.startup-tracking)Ҫ `SpringApplication` Ϊ `BufferingApplicationStartup` |\n| `threaddump` | Performs a thread dump. |\n\n\n\nӦóһWebӦóSpring MVCSpring WebFluxJerseyʹ¶Ķ˵㡣\n\n\n\n<colgroup><col><col></colgroup>\n| ID | ˵ |\n| --- | --- |\n| `heapdump` | һdumpļ HotSpot JVMϣһ `HPROF` ʽļ OpenJ9 JVMϣһ `PHD` ʽļ |\n| `logfile` | ־ļݣ `logging.file.name`  `logging.file.path` ѱã ֧ʹHTTP `Range` ͷ־ļĲݡ |\n| `prometheus` | Կɱ Prometheus ץȡĸʽչʾmetric  `micrometer-registry-prometheus` |\n\n\n\n### [](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.enabling)2.1\\. ö˵\n\n\n\nĬ£ `shutdown` ж˵㶼á Ҫһ˵ãʹ `management.endpoint.<id>.enabled` ԡ  `shutdown` ˵㡣\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.endpoint.shutdown.enabled=true\n\n```\n\n\n\n\n\n\n\nϣ˵ǡѡáǡѡá뽫 `management.endpoints.enabled-by-default` Ϊ `false`ʹõ˵ `enabled` ѡá  `info` ˵㣬˵㡣\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.endpoints.enabled-by-default=false\nmanagement.endpoint.info.enabled=true\n\n```\n\n\n\n\n\n\n\n|  | õĶ˵Ӧóȫɾֻı䱩¶˵ļʹ [`include`  `exclude` ](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.exposing)档 |\n| --- | --- |\n\n\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.exposing)2.2\\. ¶˵\n\n\n\nĬ£ֻhealth˵ͨHTTPJMX¶ġ ڶ˵ܰϢӦϸǺʱ¶ǡ\n\n\n\n\n\nҪıЩ˵㱻¶ʹض `include`  `exclude` ԡ\n\n\n\n<colgroup><col><col></colgroup>\n|  | Ĭ |\n| --- | --- |\n| `management.endpoints.jmx.exposure.exclude` |  |\n| `management.endpoints.jmx.exposure.include` | `health` |\n| `management.endpoints.web.exposure.exclude` |  |\n| `management.endpoints.web.exposure.include` | `health` |\n\n\n\n`include` г˱¶Ķ˵ID `exclude` г˲ӦñĶ˵ID `exclude`  `include` ԡ һ˵IDб `include`  `exclude` ԡ\n\n\n\n\n\n磬ҪͨJMXֻ `health`  `info` ˵㣬ʹԡ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.endpoints.jmx.exposure.include=health,info\n\n```\n\n\n\n\n\n\n\n`*` ѡж˵㡣 磬ҪͨHTTPеĶ `env`  `beans` ˵㣬ʹԡ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.endpoints.web.exposure.include=*\nmanagement.endpoints.web.exposure.exclude=env,beans\n\n```\n\n\n\n\n\n\n\n|  | `*` YAMLо⺬壬ųеĶ˵㣬һҪš |\n| --- | --- |\n\n\n\n\n\n|  | Ӧóǹ¶ģǿҽҲ[Ķ˵](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.security) |\n| --- | --- |\n\n\n\n\n\n|  | ڶ˵㱩¶ʱʵʩԼĲԣעһ `EndpointFilter` bean |\n| --- | --- |\n\n\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.security)2.3\\. ȫSecurity\n\n\n\nΪ˰ȫĬֻ `/health` ˵ͨHTTP ʹ `management.endpoints.web.exposure.include` ñ¶Ķ˵㡣\n\n\n\n\n\n|  |  `management.endpoints.web.exposure.include` ֮ǰȷ¶ִϢڷǽ֮󣬻Spring Security֮Ķ֤ȫ |\n| --- | --- |\n\n\n\n\n\nSpring Securityclasspathϣû `SecurityFilterChain` beanô `/health` ִ֮actuatorSpring BootԶ֤ȫ 㶨һԶ `SecurityFilterChain` beanSpring BootԶþͻȫִķʹ\n\n\n\n\n\nΪHTTP˵Զ尲ȫ磬ֻĳֽɫûʣSpring BootṩһЩ `RequestMatcher` Spring Securityʹá\n\n\n\n\n\nһ͵Spring Securityÿܿӡ\n\n\n\n\n\n\n\nJava\n\nKotlin\n\n\n\n\n\n```\n@Configuration(proxyBeanMethods = false)\npublic class MySecurityConfiguration {\n\n    @Bean\n    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {\n        http.securityMatcher(EndpointRequest.toAnyEndpoint());\n        http.authorizeHttpRequests((requests) -> requests.anyRequest().hasRole(\"ENDPOINT_ADMIN\"));\n        http.httpBasic(withDefaults());\n        return http.build();\n    }\n\n}\n\n```\n\n\n\n\n\n\n\nǰʹ `EndpointRequest.toAnyEndpoint()` ƥһκζ˵㣬ȻȷеĶ˵㶼 `ENDPOINT_ADMIN` Ľɫ `EndpointRequest` ϻƥ APIĵ [HTML](https://docs.spring.io/spring-boot/docs/3.1.0-SNAPSHOT/actuator-api/htmlsingle)  [PDF](https://docs.spring.io/spring-boot/docs/3.1.0-SNAPSHOT/actuator-api/pdf/spring-boot-actuator-web-api.pdf)\n\n\n\n\n\nڷǽ沿Ӧóϣִ˵㶼ܱʣҪ֤ ͨı `management.endpoints.web.exposure.include` һ㣬ʾ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.endpoints.web.exposure.include=*\n\n```\n\n\n\n\n\n\n\n⣬Spring SecurityҪԶ尲ȫãδ֤ķʶ˵㣬ʾ\n\n\n\n\n\n\n\nJava\n\nKotlin\n\n\n\n\n\n```\n@Configuration(proxyBeanMethods = false)\npublic class MySecurityConfiguration {\n\n    @Bean\n    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {\n        http.securityMatcher(EndpointRequest.toAnyEndpoint());\n        http.authorizeHttpRequests((requests) -> requests.anyRequest().permitAll());\n        return http.build();\n    }\n\n}\n\n```\n\n\n\n\n\n\n\n|  | ǰУֻactuator˵㡣 Spring Bootİȫκ `SecurityFilterChain` bean¶ȫ˳Ҫһ `SecurityFilterChain` beanӦó֡ |\n| --- | --- |\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.security.csrf)2.3.1\\. վα챣CSRF\n\n\n\nSpring BootSpring SecurityĬֵCSRFĬ±򿪡 ζʹĬϰȫʱҪ `POST`shutdownloggers˵㣩`PUT`  `DELETE` actuator˵403ֹĴ\n\n\n\n\n\n|  | ǽֻ㴴ķ񱻷ͻʹʱȫCSRF |\n| --- | --- |\n\n\n\n\n\n [Springȫοָ](https://docs.spring.io/spring-security/reference/6.1.0-M1/features/exploits/csrf.html) ҵCSRFϢ\n\n\n\n\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.caching)2.4\\. ö˵\n\n\n\n˵Զ治ҪκβĶȡӦ Ҫö˵㻺Ӧʱ䣬ʹ `cache.time-to-live` ԡ ӽ `beans` ˵ĻʱΪ10롣\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.endpoint.beans.cache.time-to-live=10s\n\n```\n\n\n\n\n\n\n\n|  | `management.endpoint.<name>` ǰ׺ΨһرʶõĶ˵㡣 |\n| --- | --- |\n\n\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.hypermedia)2.5\\.  Actuator Web ˵ĳý壨Hypermedia\n\n\n\nһ discovery page ӵж˵С Ĭ£discovery page  `/actuator` ǿõġ\n\n\n\n\n\nҪ discovery pageӦóԡ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.endpoints.web.discovery.enabled=false\n\n```\n\n\n\n\n\n\n\nһԶĹ·ʱdiscovery page Զ `/actuator` Ƶĵĸ 磬· `/management`discovery pageԴ `/management` á ·Ϊ `/` ʱҳãԷֹmappingͻĿԡ\n\n\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.cors)2.6\\. CORS֧\n\n\n\n[ԴԴ](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing)CORS [W3Cһ淶](https://www.w3.org/TR/cors/)ķʽָֿȨʹSpring MVCSpring WebFluxActuatorWeb˵֧\n\n\n\n\n\nCORS֧Ĭǽõģֻ `management.endpoints.web.cors.allowed-origins` ԺŻá  `example.com` е `GET`  `POST`\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.endpoints.web.cors.allowed-origins=https://example.com\nmanagement.endpoints.web.cors.allowed-methods=GET,POST\n\n```\n\n\n\n\n\n\n\n|  | μ [`CorsEndpointProperties`](https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/CorsEndpointProperties.java) Իѡб |\n| --- | --- |\n\n\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.implementing-custom)2.7\\. ʵԶ˵\n\n\n\nһ `@Endpoint` ע `@Bean`κδ `@ReadOperation``@WriteOperation`  `@DeleteOperation` ע͵ķԶͨJMXWebӦóҲͨHTTP ͨʹJerseySpring MVCSpring WebFlux˵ͨHTTP¶ JerseySpring MVCãʹSpring MVC\n\n\n\n\n\nӱ¶һһԶ\n\n\n\n\n\n\n\nJava\n\nKotlin\n\n\n\n\n\n```\n@ReadOperation\npublic CustomData getData() {\n    return new CustomData(\"test\", 5);\n}\n\n```\n\n\n\n\n\n\n\nҲͨʹ `@JmxEndpoint`  `@WebEndpoint` дضĶ˵㡣 Щ˵㱻ǸԵļϡ 磬`@WebEndpoint` ֻͨHTTP¶ͨJMX\n\n\n\n\n\nͨʹ `@EndpointWebExtension`  `@EndpointJmxExtension` дضļչ ЩעṩضĲǿеĶ˵㡣\n\n\n\n\n\nҪWebܵضܣʵservletSpring `@Controller`  `@RestController` ˵㣬ǲͨJMXʹòͬWebʱá\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.implementing-custom.input)2.7.1\\. \n\n\n\n˵ϵĲͨ롣 ͨwebʱЩֵURLĲѯJSON塣 ͨJMXʱӳ䵽MBeanĲС Ĭ£Ǳġ ǿͨʹ `@javax.annotation.Nullable`  `@org.springframework.lang.Nullable` עΪѡ\n\n\n\n\n\nԽJSONеÿӳ䵽˵һ һJSON塣\n\n\n\n\n\n\n\n```\n{\n    \"name\": \"test\",\n    \"counter\": 42\n}\n```\n\n\n\n\n\n\n\nһдòҪ `String name`  `int counter` ʾ\n\n\n\n\n\n\n\nJava\n\nKotlin\n\n\n\n\n\n```\n@WriteOperation\npublic void updateData(String name, int counter) {\n    // injects \"test\" and 42\n}\n\n```\n\n\n\n\n\n\n\n|  | Ϊ˵Ǽ֪ģڷǩָֻ򵥵͡ رǣ֧ `CustomData` һ `name`  `counter` Եĵһ |\n| --- | --- |\n\n\n\n\n\n|  | Ϊӳ䵽Ĳʵֶ˵JavaӦ `-parameters` 룬ʵֶ˵KotlinӦ `-java-parameters` 롣 ʹSpring BootGradleʹMaven `spring-boot-starter-parent`⽫Զ |\n| --- | --- |\n\n\n\n\n\n##### [](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.implementing-custom.input.conversion)ת\n\n\n\nбҪݸ˵ĲԶתΪ͡ ڵò֮ǰͨJMXHTTPյ뱻תΪͣʹ `ApplicationConversionService` ʵԼκ `Converter`  `GenericConverter` Bean `@EndpointConverter` ޶\n\n\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.implementing-custom.web)2.7.2\\. ԶWEB˵\n\n\n\n `@Endpoint``@WebEndpoint`  `@EndpointWebExtension` ԶʹJerseySpring MVCSpring WebFluxͨHTTP JerseySpring MVCãʹSpring MVC\n\n\n\n\n\n##### [](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.implementing-custom.web.request-predicates)WEB˵νʣPredicates\n\n\n\nһνʻΪweb¶Ķ˵ϵÿoperationԶɡ\n\n\n\n\n\n\n\n##### [](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.implementing-custom.web.path-predicates)Path\n\n\n\npathνɶ˵ID籩¶Ķ˵Ļ· ĬϵĻ· `/actuator` 磬һIDΪ `sessions` Ķ˵νʹ `/actuator/sessions` Ϊ·\n\n\n\n\n\nͨ `@Selector` עһһ· ĲΪһ·ӵ·νС ڵö˵ʱñֵᱻ 벶ʣ·Ԫأһ `@Selector(Match=ALL_REMAINING)`ʹΪһ `String[]` תݵ͡\n\n\n\n\n\n\n\n##### [](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.implementing-custom.web.method-predicates)HTTP method\n\n\n\nHTTP methodνɲ;ģ±ʾ\n\n\n\n<colgroup><col><col></colgroup>\n| Operation | HTTP method |\n| --- | --- |\n| `@ReadOperation` | `GET` |\n| `@WriteOperation` | `POST` |\n| `@DeleteOperation` | `DELETE` |\n\n\n\n\n\n##### [](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.implementing-custom.web.consumes-predicates)Consumes\n\n\n\nʹrequest body `@WriteOperation`HTTP `POST`νʵ `consumes` Ӿ `application/vnd.spring-boot.actuator.v2+json, application/json` `consumes` Ӿǿյġ\n\n\n\n\n\n\n\n##### [](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.implementing-custom.web.produces-predicates)Produces\n\n\n\nνʵ `produces` Ӿ `@DeleteOperation``@ReadOperation`  `@WriteOperation` ע͵ `produces` Ծ ǿѡġ ʹ`produces` ӾԶȷ\n\n\n\n\n\n `void`  `Void` `produces` ӾΪա  `org.springframework.core.io.Resource``produces` Ӿ `application/octet-stream` `produces` Ӿ `application/vnd.spring-boot.actuator.v2+json, application/json`\n\n\n\n\n\n\n\n##### [](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.implementing-custom.web.response-status)WEB˵Ӧ״̬\n\n\n\n˵ĬӦ״̬ȡڲͣдɾͲصݣеĻ\n\n\n\n\n\n `@ReadOperation` һֵӦ״̬200(Ok) ûзһֵӦ״̬404(Not Found)\n\n\n\n\n\n `@WriteOperation`  `@DeleteOperation` һֵӦ״̬200OK ûзһֵӦ״̬204No Content\n\n\n\n\n\nһڵʱûĲ߲ܱתΪͣͲᱻãӦ״̬400Bad Request\n\n\n\n\n\n\n\n##### [](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.implementing-custom.web.range-requests)WEB˵ Range \n\n\n\nʹHTTP rangeһHTTPԴһ֡ ʹSpring MVCSpring Web Fluxʱ `org.springframework.core.io.Resource` ĲԶַ֧Χ\n\n\n\n\n\n|  | ʹJerseyʱ֧ Range  |\n| --- | --- |\n\n\n\n\n\n\n\n##### [](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.implementing-custom.web.security)Web˵İȫ\n\n\n\nweb˵webض˵չϵĲԽյǰ `java.security.Principal`  `org.springframework.boot.actuate.endpoint.SecurityContext` Ϊ ǰͨ `@Nullable` һʹãΪ֤δ֤ûṩͬΪ ͨͨʹ `isUserInRole(String)` ִȨ顣\n\n\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.implementing-custom.servlet)2.7.3\\. Servlet ˵\n\n\n\nһServletΪһ˵㱩¶ʵһ `@ServletEndpoint` ע࣬ͬʱʵ `Supplier<EndpointServlet>` Servlet˵ṩservletĸεϣȴ˿ֲԡ ǵĿеServletΪһ˵ µĶ˵㣬Ӧѡ `@Endpoint`  `@WebEndpoint` ע⡣\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.implementing-custom.controller)2.7.4\\. Controller ˵\n\n\n\nʹ `@ControllerEndpoint`  `@RestControllerEndpoint` ʵһSpring MVCSpring WebFluxĶ˵㡣 ͨʹSpring MVCSpring WebFluxı׼עӳ䣬 `@RequestMapping`  `@GetMapping`˵ID·ǰ׺ ˵ṩSpringWebܸļɣȴ˿ֲԡ Ӧѡ `@Endpoint`  `@WebEndpoint` ע⡣\n\n\n\n\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.health)2.8\\. Ϣ\n\n\n\nʹýϢеӦó״̬ ϵͳʱѱˡ `health` ˵㱩¶Ϣȡ `management.endpoint.health.show-details`  `management.endpoint.health.show-components` ԣǿΪֵ֮һ\n\n\n\n<colgroup><col><col></colgroup>\n| ֵ | ˵ |\n| --- | --- |\n| `never` | ϸڴӲʾ |\n| `when-authorized` | ϸֻʾȨû ȨĽɫͨʹ `management.endpoint.health.roles` á |\n| `always` | ʾû |\n\n\n\nĬֵ `never` ûڶ˵һɫʱǱΪǱȨġ ˵ûýɫĬֵ֤ûΪȨġ ͨʹ `management.endpoint.health.roles` ýɫ\n\n\n\n\n\n|  | ѾӦóϣʹ `always`İȫãsecurity configuration֤ͷ֤ûhealth˵㡣 |\n| --- | --- |\n\n\n\n\n\nϢǴ [`HealthContributorRegistry`](https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthContributorRegistry.java) ռģĬ£ [`HealthContributor`](https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthContributor.java) ʵ `ApplicationContext` У Spring BootһЩԶõ `HealthContributor`ҲԱдԼġ\n\n\n\n\n\nһ `HealthContributor` һ `HealthIndicator` һ `CompositeHealthContributor` һ `HealthIndicator` ṩʵʵĽϢ `Status` һ `CompositeHealthContributor` ṩ `HealthContributors` ϡ ۺcontributorγһ״ṹʾϵͳĽ״\n\n\n\n\n\nĬ£յϵͳ״һ `StatusAggregator` óģһ״̬бÿ `HealthIndicator` ״̬ беĵһ״̬彡״̬ û `HealthIndicator` ص״̬ `StatusAggregator` ֪ģͻʹ `UNKNOWN` ״̬\n\n\n\n\n\n|  | ʹ `HealthContributorRegistry` ʱעȡעὡָꡣ |\n| --- | --- |\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.health.auto-configured-health-indicators)2.8.1\\. ԶõHealthIndicators\n\n\n\nʵʱSpring BootԶ±г `HealthIndicators` Ҳͨ `management.health.key.enabled` ûͣѡָꡣ ±г `key`\n\n\n\n<colgroup><col><col><col></colgroup>\n| Key | Name | ˵ |\n| --- | --- | --- |\n| `cassandra` | [`CassandraDriverHealthIndicator`](https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cassandra/CassandraDriverHealthIndicator.java) | CassandraݿǷѾ |\n| `couchbase` | [`CouchbaseHealthIndicator`](https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/couchbase/CouchbaseHealthIndicator.java) | CouchbaseȺǷѾ |\n| `db` | [`DataSourceHealthIndicator`](https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/jdbc/DataSourceHealthIndicator.java) | ǷԻ`DataSource`ӡ |\n| `diskspace` | [`DiskSpaceHealthIndicator`](https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/system/DiskSpaceHealthIndicator.java) | ̿ռǷ㡣 |\n| `elasticsearch` | [`ElasticsearchRestHealthIndicator`](https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchRestHealthIndicator.java) | ElasticsearchȺǷѾ |\n| `hazelcast` | [`HazelcastHealthIndicator`](https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/hazelcast/HazelcastHealthIndicator.java) | HazelcastǷѾ |\n| `influxdb` | [`InfluxDbHealthIndicator`](https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/influx/InfluxDbHealthIndicator.java) | InfluxDBǷѾ |\n| `jms` | [`JmsHealthIndicator`](https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/jms/JmsHealthIndicator.java) | һJMSǷѾ |\n| `ldap` | [`LdapHealthIndicator`](https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/ldap/LdapHealthIndicator.java) | һLDAPǷ |\n| `mail` | [`MailHealthIndicator`](https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/mail/MailHealthIndicator.java) | һʼǷ |\n| `mongo` | [`MongoHealthIndicator`](https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/data/mongo/MongoHealthIndicator.java) | MongoݿǷѾ |\n| `neo4j` | [`Neo4jHealthIndicator`](https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/neo4j/Neo4jHealthIndicator.java) | Neo4jݿǷѾ |\n| `ping` | [`PingHealthIndicator`](https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/PingHealthIndicator.java) | Ӧ `UP`  |\n| `rabbit` | [`RabbitHealthIndicator`](https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/amqp/RabbitHealthIndicator.java) | һRabbitǷѾ |\n| `redis` | [`RedisHealthIndicator`](https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/data/redis/RedisHealthIndicator.java) | RedisǷѾ |\n\n\n\n|  | ͨ `management.health.defaults.enabled` ǡ |\n| --- | --- |\n\n\n\n\n\n `HealthIndicators` ǿõģĬ²á\n\n\n\n<colgroup><col><col><col></colgroup>\n| Key | Name | ˵ |\n| --- | --- | --- |\n| `livenessstate` | [`LivenessStateHealthIndicator`](https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/availability/LivenessStateHealthIndicator.java) | ʾ Liveness ӦóĿ״̬ |\n| `readinessstate` | [`ReadinessStateHealthIndicator`](https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/availability/ReadinessStateHealthIndicator.java) | ¶ Readiness ӦóĿ״̬ |\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.health.writing-custom-health-indicators)2.8.2\\. дԶHealthIndicators\n\n\n\nΪṩԶĽϢעʵ [`HealthIndicator`](https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicator.java) ӿڵSpring Bean Ҫṩһ `health()` ʵ֣һ `Health` Ӧ `Health` ӦӦðһstatusѡҪʾϸڡ Ĵʾһ `HealthIndicator` ʵ\n\n\n\n\n\n\n\nJava\n\nKotlin\n\n\n\n\n\n```\n@Component\npublic class MyHealthIndicator implements HealthIndicator {\n\n    @Override\n    public Health health() {\n        int errorCode = check();\n        if (errorCode != 0) {\n            return Health.down().withDetail(\"Error Code\", errorCode).build();\n        }\n        return Health.up().build();\n    }\n\n    private int check() {\n        // perform some specific health check\n        return ...\n    }\n\n}\n\n```\n\n\n\n\n\n\n\n|  | һ `HealthIndicator` ıʶIDû `HealthIndicator` ׺Bean֣ڵĻ ǰУϢһΪ `my` Ŀҵ |\n| --- | --- |\n\n\n\n\n\n|  | ָͨͨHTTPõģҪκӳʱ֮ǰӦ κνָӦʱ䳬10룬Spring Boot¼һϢ ֵʹ `management.endpoint.health.logging.slow-indicator-threshold` ԡ |\n| --- | --- |\n\n\n\n\n\nSpring BootԤ [`Status`](https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/Status.java) ⣬`Health` Էشϵͳ״̬Զ `Status` £㻹Ҫṩ `StatusAggregator` ӿڵԶʵ֣ͨʹ `management.endpoint.health.status.order` Ĭʵ֡\n\n\n\n\n\n磬һ `HealthIndicator` ʵʹһΪ `FATAL`  `Status` Ϊ˳Ӧóԡ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.endpoint.health.status.order=fatal,down,out-of-service,unknown,up\n\n```\n\n\n\n\n\n\n\nӦеHTTP״̬뷴ӳ彡״̬ Ĭ£`OUT_OF_SERVICE`  `DOWN` ӳ䵽503 κδӳĽ״̬ `UP`ӳΪ200 ͨHTTPʽ˵㣬ܻעԶ״̬ӳ䡣 Զӳ `DOWN`  `OUT_OF_SERVICE` Ĭӳ䡣 뱣Ĭӳ䣬ȷǣԼκԶӳ䡣 磬Խ `FATAL` ӳΪ503񲻿ã `DOWN`  `OUT_OF_SERVICE` Ĭӳ䡣\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.endpoint.health.status.http-mapping.down=503\nmanagement.endpoint.health.status.http-mapping.fatal=503\nmanagement.endpoint.health.status.http-mapping.out-of-service=503\n\n```\n\n\n\n\n\n\n\n|  | ҪĿƣԶԼ `HttpCodeStatusMapper` bean |\n| --- | --- |\n\n\n\n\n\n±ʾ״̬Ĭ״̬ӳ䡣\n\n\n\n<colgroup><col><col></colgroup>\n| Status | Mapping |\n| --- | --- |\n| `DOWN` | `SERVICE_UNAVAILABLE` (`503`) |\n| `OUT_OF_SERVICE` | `SERVICE_UNAVAILABLE` (`503`) |\n| `UP` | Ĭûӳ䣬HTTP״̬Ϊ `200` |\n| `UNKNOWN` | Ĭûӳ䣬HTTP״̬Ϊ `200` |\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.health.reactive-health-indicators)2.8.3\\. Ӧʽָ\n\n\n\nӦʽӦóЩʹSpring WebFluxӦó`ReactiveHealthContributor` ṩһԼȡӦóĽ״ 봫ͳ `HealthContributor` ƣϢ [`ReactiveHealthContributorRegistry`](https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthContributorRegistry.java) ռĬ£ [`HealthContributor`](https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthContributor.java)  [`ReactiveHealthContributor`](https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthContributor.java) ʵ `ApplicationContext` \n\n\n\n\n\nӦʽAPIмĳ `HealthContributors` ڵԵִС\n\n\n\n\n\n|  | һӦʽӦóУӦʹ `ReactiveHealthContributorRegistry` ʱעȡעὡָꡣ Ҫעһͨ `HealthContributor`Ӧ `ReactiveHealthContributor#adapt` װ |\n| --- | --- |\n\n\n\n\n\nΪ˴ӦʽAPIṩԶĽϢעʵ [`ReactiveHealthIndicator`](https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthIndicator.java) ӿڵSpring Bean Ĵʾһ `ReactiveHealthIndicator` ʾʵ֡\n\n\n\n\n\n\n\nJava\n\nKotlin\n\n\n\n\n\n```\n@Component\npublic class MyReactiveHealthIndicator implements ReactiveHealthIndicator {\n\n    @Override\n    public Mono<Health> health() {\n        return doHealthCheck().onErrorResume((exception) ->\n            Mono.just(new Health.Builder().down(exception).build()));\n    }\n\n    private Mono<Health> doHealthCheck() {\n        // perform some specific health check\n        return ...\n    }\n\n}\n\n```\n\n\n\n\n\n\n\n|  | ΪԶ󣬿ԿǴ `AbstractReactiveHealthIndicator` չ |\n| --- | --- |\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.health.auto-configured-reactive-health-indicators)2.8.4\\. Զõ ReactiveHealthIndicators\n\n\n\nʵʱSpring BootԶµ `ReactiveHealthIndicators`\n\n\n\n<colgroup><col><col><col></colgroup>\n| Key | Name | ˵ |\n| --- | --- | --- |\n| `cassandra` | [`CassandraDriverReactiveHealthIndicator`](https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cassandra/CassandraDriverReactiveHealthIndicator.java) | CassandraݿǷѾ |\n| `couchbase` | [`CouchbaseReactiveHealthIndicator`](https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/couchbase/CouchbaseReactiveHealthIndicator.java) | CouchbaseȺǷѾ |\n| `elasticsearch` | [`ElasticsearchReactiveHealthIndicator`](https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/data/elasticsearch/ElasticsearchReactiveHealthIndicator.java) | ElasticsearchȺǷѾ |\n| `mongo` | [`MongoReactiveHealthIndicator`](https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/data/mongo/MongoReactiveHealthIndicator.java) | MongoݿǷѾ |\n| `neo4j` | [`Neo4jReactiveHealthIndicator`](https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/neo4j/Neo4jReactiveHealthIndicator.java) | Neo4jݿǷѾ |\n| `redis` | [`RedisReactiveHealthIndicator`](https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/data/redis/RedisReactiveHealthIndicator.java) | RedisǷѾ |\n\n\n\n|  | бҪӦʽָȡָꡣ ⣬κûбȷ `HealthIndicator` ᱻԶװ |\n| --- | --- |\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.health.groups)2.8.5\\. Health飨Health Groups\n\n\n\nʱָ֯ɿڲͬĿĵǺõġ\n\n\n\n\n\nҪһָ飬ʹ `management.endpoint.health.group.<name>` ԣָһָIDб `include`  `exclude` 磬Ҫһֻݿָ飬Զ¡\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.endpoint.health.group.custom.include=db\n\n```\n\n\n\n\n\n\n\nȻͨ `[localhost:8080/actuator/health/custom](http://localhost:8080/actuator/health/custom)` \n\n\n\n\n\nͬҪһ飬ݿָųڸ֮⣬ָ꣬Զ¡\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.endpoint.health.group.custom.exclude=db\n\n```\n\n\n\n\n\n\n\nĬ£̳ϵͳͬ `StatusAggregator`  `HttpCodeStatusMapper` á ȻҲÿĻ϶Щ ҪҲԸ `show-details`  `roles` ԡ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.endpoint.health.group.custom.show-details=when-authorized\nmanagement.endpoint.health.group.custom.roles=admin\nmanagement.endpoint.health.group.custom.status.order=fatal,up\nmanagement.endpoint.health.group.custom.status.http-mapping.fatal=500\nmanagement.endpoint.health.group.custom.status.http-mapping.out-of-service=500\n\n```\n\n\n\n\n\n\n\n|  | ҪעԶ `StatusAggregator`  `HttpCodeStatusMapper` Bean飬ʹ `@Qualifier(\"groupname\")` |\n| --- | --- |\n\n\n\n\n\nһҲ԰/ųһ `CompositeHealthContributor` Ҳֻ/ųһ `CompositeHealthContributor` ĳ ʹȫɣʾ\n\n\n\n\n\n\n\n```\nmanagement.endpoint.health.group.custom.include=\"test/primary\"\nmanagement.endpoint.health.group.custom.exclude=\"test/primary/b\"\n```\n\n\n\n\n\n\n\nУ`custom` 齫Ϊ `primary`  `HealthContributor`Ǹ `test` һɲ֡ `primary` һ壬Ϊ `b`  `HealthContributor` ų `custom` ֮⡣\n\n\n\n\n\n˿ڻ˿ڵĶ·ṩ KubernetesƻкãЩУڰȫǣΪִ˵ʹһĹ˿Ǻܳġ һĶ˿ڿܵ²ɿĽ飬ΪʹɹӦóҲ޷ һ·ãʾ\n\n\n\n\n\n\n\n```\nmanagement.endpoint.health.group.live.additional-path=\"server:/healthz\"\n```\n\n\n\n\n\n\n\n⽫ʹ `live` ˿ `/healthz` Ͽá ǰ׺ǿԵģ `server:`˿ڣ `management:`˿ڣã ·һһ·Ρ\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.health.datasource)2.8.6\\. Դ\n\n\n\n`DataSource` ָʾ׼Դ·ԴBeanĽ״ ·ԴĽ״ÿĿԴĽ״ ڽ˵ӦУ·ԴÿĿ궼ͨʹ·ɼġ 㲻ϣָа·Դ뽫 `management.health.db.ignore-routing-data-sources` Ϊ `true`\n\n\n\n\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.kubernetes-probes)2.9\\. Kubernetes ̽\n\n\n\nKubernetesϵӦóͨ [̽](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-probes) ṩйڲ״̬Ϣ [Kubernetes](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/)kubeletЩ̽벢ԽӦ\n\n\n\n\n\nĬ£Spring Boot[Ӧÿ״̬](https://springdoc.cn/spring-boot/features.html#features.spring-application.application-availability) KubernetesУactuator `ApplicationAvailability` ӿռ Liveness  Readiness Ϣר[ָ](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.health.auto-configured-health-indicators)ʹЩϢ`LivenessStateHealthIndicator`  `ReadinessStateHealthIndicator` Щָʾȫֽ˵㣨`\"/actuator/health\"` Ҳͨʹ[](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.health.groups)ΪHTTP̽룺`\"/actuator/health/liveness\"`  `\"/actuator/health/readiness\"`\n\n\n\n\n\nȻ¶˵ϢKubernetesʩ\n\n\n\n\n\n\n\n```\nlivenessProbe:\n  httpGet:\n    path: \"/actuator/health/liveness\"\n    port: \n  failureThreshold: ...\n  periodSeconds: ...\n\nreadinessProbe:\n  httpGet:\n    path: \"/actuator/health/readiness\"\n    port: \n  failureThreshold: ...\n  periodSeconds: ...\n```\n\n\n\n\n\n\n\n|  | `` ӦñΪִ˵õĶ˿ڡ WebĶ˿ڣҲһĹ˿ڣ `\"management.server.port\"` Ѿá |\n| --- | --- |\n\n\n\n\n\nֻеӦó[Kubernetesʱ](https://springdoc.cn/spring-boot/deployment.html#deployment.cloud.kubernetes)ЩŻԶá ͨʹ `management.endpoint.health.probes.enabled` κλǡ\n\n\n\n\n\n|  | һӦóʱ䳬õЧڣKubernetes ᵽ `\"startupProbe\"` ΪһܵĽһ˵ﲻһҪ `\"startupProbe\"`Ϊ `\"readinessProbe\"` ֮ǰʧЧζӦó׼֮ǰյȻӦóҪܳʱԿʹ `\"startupProbe\"` ȷKubernetesӦóɱ[̽ӦóеΪ](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.kubernetes-probes.lifecycle)Ĳ֡ |\n| --- | --- |\n\n\n\n\n\nActuator˵㱻һĹУôЩ˵Ͳʹͬʩ˿ڡӳء £ʹ磬ܽµӣ̽Ҳܳɹ ԭ˿ `liveness`  `readiness` Ǹ⡣ ͨʵ֡\n\n\n\n\n\n\n\n```\nmanagement.endpoint.health.probes.add-additional-paths=true\n```\n\n\n\n\n\n\n\n⽫ʹ `liveness`  `/livez` ã`readiness`  `readyz` ˿ڿá\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.kubernetes-probes.external-state)2.9.1\\. Kubernetes̽ⲿ״̬\n\n\n\nִ liveness  readiness ̽Ϊ顣ζе[Ĺ](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.health.groups)Ƕǿõġ磬öĽָꡣ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.endpoint.health.group.readiness.include=readinessState,customCheck\n\n```\n\n\n\n\n\n\n\nĬ£Spring BootЩָꡣ\n\n\n\n\n\nliveness ̽벻ӦⲿϵͳĽ顣[ӦóЧ״̬](https://springdoc.cn/spring-boot/features.html#features.spring-application.application-availability.liveness)ƻKubernetes᳢ͨӦóʵ⡣ζţһⲿϵͳݿ⡢Web APIⲿ棩ֹϣKubernetesܻӦóʵϡ\n\n\n\n\n\n readiness ̽⣬ⲿϵͳѡӦó򿪷ԱԭSpring Boot׼״̬̽вκζĽ顣[Ӧóʵreadiness stateunready](https://springdoc.cn/spring-boot/features.html#features.spring-application.application-availability.readiness)KubernetesͲὫ·ɵʵһЩⲿϵͳܲӦʵ£ǿԱ׼״̬̽СⲿϵͳܲӦóĹؼӦóж·ͻˣ£ǾԲӦñڡҵǣһӦʵⲿϵͳܳжϡ׼̽УⲿʱӦóᱻֹͣ񣬻߲ջи߲εĹϣҲͨڵʹö·\n\n\n\n\n\n|  | һӦóʵû׼ã`type=ClusterIP`  `NodePort` Kubernetes񲻽κδӡûHTTPӦ503ȣΪûӡ`type=LoadBalancer` ķܽҲܲӣȡṩߡһȷ [ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) ķҲһȡʵֵķʽӦ?ڷδε connection refusedڸؾڵ£HTTP 503Ǻпܵġ |\n| --- | --- |\n\n\n\n\n\n⣬һӦóʹ Kubernetes [autoscaling](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/)ܻӦó򱻴ӸƽȡͬķӦȡautoscalerá\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.kubernetes-probes.lifecycle)2.9.2\\. Ӧóں̽״̬\n\n\n\nKubernetes Probesֵ֧һҪӦóڵһԡ `AvailabilityState`Ӧóڴڲ״̬ʵʵ̽루¶״̬֮ ʵʵ̽루¶˸״̬֮кܴ ӦóڵĲͬ׶Σ̽޷ʹá\n\n\n\n\n\nSpring Boot[͹رڼ䷢application event](https://springdoc.cn/spring-boot/features.html#features.spring-application.application-events-and-listeners)̽ԼЩ¼¶ `AvailabilityState` Ϣ\n\n\n\n\n\n±ʾ˲ͬ׶ε `AvailabilityState` HTTP connector״̬\n\n\n\n\n\nһSpring BootӦóʱ\n\n\n\n<colgroup><col><col><col><col><col></colgroup>\n| ׶ | LivenessState | ReadinessState | HTTP server | ע |\n| --- | --- | --- | --- | --- |\n| Starting | `BROKEN` | `REFUSING_TRAFFIC` | δ | Kubernetes \"liveness\" ̽룬ʱӦó |\n| Started | `CORRECT` | `REFUSING_TRAFFIC` | ܾ | Ӧóıˢ¡Ӧóִ񣬻ûյ |\n| Ready | `CORRECT` | `ACCEPTING_TRAFFIC` |  | ѾɡӦóڽ |\n\n\n\nһSpring BootӦóرʱ\n\n\n\n<colgroup><col><col><col><col><col></colgroup>\n| ͣ׶ | Liveness State | Readiness State | HTTP server | ע |\n| --- | --- | --- | --- | --- |\n| Running | `CORRECT` | `ACCEPTING_TRAFFIC` |  | Ҫرա |\n| Graceful shutdown | `CORRECT` | `REFUSING_TRAFFIC` | µ󱻾ܾ | ã [ŹػᴦС](https://springdoc.cn/spring-boot/web.html#web.graceful-shutdown) |\n| Shutdown complete | N/A | N/A | ر | ӦóıرգӦó򱻹رա |\n\n\n\n|  | KubernetesĸϢμ[Kubernetes](https://springdoc.cn/spring-boot/deployment.html#deployment.cloud.kubernetes.container-lifecycle)֡ |\n| --- | --- |\n\n\n\n\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.info)2.10\\. ӦϢ\n\n\n\nӦóϢ˴ `ApplicationContext` ж [`InfoContributor`](https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/info/InfoContributor.java) BeanռĸϢ Spring BootһЩԶõ `InfoContributor` BeanҲԱдԼġ\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.info.auto-configured-info-contributors)2.10.1\\. Զõ InfoContributor\n\n\n\nʵʱSpringԶ `InfoContributor` Bean\n\n\n\n<colgroup><col><col><col><col></colgroup>\n| ID | Name | ˵ | ǰ |\n| --- | --- | --- | --- |\n| `build` | [`BuildInfoContributor`](https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/info/BuildInfoContributor.java) | ¶˹Ϣ | һ `META-INF/build-info.properties` Դ |\n| `env` | [`EnvironmentInfoContributor`](https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/info/EnvironmentInfoContributor.java) | ¶ `Environment`  `info.` ͷκԡ | None. |\n| `git` | [`GitInfoContributor`](https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/info/GitInfoContributor.java) | ¶gitϢ | һ `git.properties` Դ |\n| `java` | [`JavaInfoContributor`](https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/info/JavaInfoContributor.java) | ¶JavaʱRuntimeϢ | None. |\n| `os` | [`OsInfoContributor`](https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/info/OsInfoContributor.java) | ¶ϵͳϢ | None. |\n\n\n\n˹ߣcontributorǷ `management.info.<id>.enabled` Կơ ͬcontributorвͬĬֵȡǵȾ¶Ϣʡ\n\n\n\n\n\nûȾӦñã`env``java`  `os` contributor Ĭǽõġ ͨ `management.info.<id>.enabled` Ϊ `true` ǡ\n\n\n\n\n\n`build`  `git` ϢcontributorĬõġ ͨ `management.info.<id>.enabled` Ϊ `false` á ⣬ҪÿһĬõcontributor뽫 `management.info.defaults.enabled` Ϊ `false`\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.info.custom-application-information)2.10.2\\. ԶӦϢApplication Information\n\n\n\n `env` contributor ʱͨ `info.*` Spring `info` ˵¶ݡ `info` keyµ `Environment` ԶԶ¶ 磬 `application.properties` ļá\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\ninfo.app.encoding=UTF-8\ninfo.app.java.source=17\ninfo.app.java.target=17\n\n```\n\n\n\n\n\n\n\n|  | ӲЩֵ㻹 [ڹʱչϢ](https://springdoc.cn/spring-boot/howto.html#howto.properties-and-configuration.expand-properties)ʹMavenԽǰӸд¡PropertiesYaml```info.app.encoding=@project.build.sourceEncoding@info.app.java.source=@java.version@info.app.java.target=@java.version@``` |\n| --- | --- |\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.info.git-commit-information)2.10.3\\. Git Commit Ϣ\n\n\n\n`info` ˵һõĹܹ `git` ԴĿʱ״̬Ϣ һ `GitProperties` beanʹ `info` ˵Щԡ\n\n\n\n\n\n|  | classpathĸ `git.properties` ļ`GitProperties` BeanͻᱻԶáϸڼ \"[gitϢ](https://springdoc.cn/spring-boot/howto.html#howto.build.generate-git-info)\" |\n| --- | --- |\n\n\n\n\n\nĬ£˵ᱩ¶ `git.branch``git.commit.id`  `git.commit.time` ԣڣ 㲻ЩԳڶ˵ӦУҪ `git.properties` ļųǡ ʾgitϢ `git.properties` ȫݣʹ `management.info.git.mode` ԣʾ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.info.git.mode=full\n\n```\n\n\n\n\n\n\n\nҪ `info` ˵ȫgitύϢ `management.info.git.enabled` Ϊ `false`ʾ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.info.git.enabled=false\n\n```\n\n\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.info.build-information)2.10.4\\. Ϣ\n\n\n\n `BuildProperties` Beanǿõģ`info` ˵ҲԷĹϢclasspathе `META-INF/build-info.properties` ļãͻᷢ\n\n\n\n\n\n|  | MavenGradleɸļ \"[ɹϢ](https://springdoc.cn/spring-boot/howto.html#howto.build.generate-info)\" |\n| --- | --- |\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.info.java-information)2.10.5\\. JavaϢ\n\n\n\n`info` ˵㷢˹JavaлϢϸڼ [`JavaInfo`](https://docs.spring.io/spring-boot/docs/3.1.0-SNAPSHOT/api/org/springframework/boot/info/JavaInfo.html)\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.info.os-information)2.10.6\\. ϵͳOSϢ\n\n\n\n`info` ˵㷢ĲϵͳϢϸڼ [`OsInfo`](https://docs.spring.io/spring-boot/docs/3.1.0-SNAPSHOT/api/org/springframework/boot/info/OsInfo.html)`\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.info.writing-custom-info-contributors)2.10.7\\. дԶ InfoContributor\n\n\n\nΪṩԶӦóϢעʵ [`InfoContributor`](https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/info/InfoContributor.java) ӿڵSpring Bean\n\n\n\n\n\nӹһֻһֵ `example` Ŀ\n\n\n\n\n\n\n\nJava\n\nKotlin\n\n\n\n\n\n```\n@Component\npublic class MyInfoContributor implements InfoContributor {\n\n    @Override\n    public void contribute(Info.Builder builder) {\n        builder.withDetail(\"example\", Collections.singletonMap(\"key\", \"value\"));\n    }\n\n}\n\n```\n\n\n\n\n\n\n\n `info` ˵㣬Ӧÿһ¶ĿӦ\n\n\n\n\n\n\n\n```\n{\n    \"example\": {\n        \"key\" : \"value\"\n    }\n}\n```\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n## [](https://springdoc.cn/spring-boot/actuator.html#actuator.monitoring)3\\. ͨHTTPмغ͹\n\n\n\n\n\nڿһWebӦóSpring Boot ActuatorԶõĶ˵㣬ʹͨHTTP ĬϵĹʹö˵ `id`  `/actuator` ǰ׺ΪURL· 磬`health`  `/actuator/health` ʽ\n\n\n\n\n\n|  | Actuator ֧ Spring MVCSpring WebFluxJersey JerseySpring MVCãʹSpring MVC |\n| --- | --- |\n\n\n\n\n\n|  | Ϊ˻APIĵ [HTML](https://docs.spring.io/spring-boot/docs/3.1.0-SNAPSHOT/actuator-api/htmlsingle)  [PDF](https://docs.spring.io/spring-boot/docs/3.1.0-SNAPSHOT/actuator-api/pdf/spring-boot-actuator-web-api.pdf) мصȷJSONӦJacksonһҪ |\n| --- | --- |\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/actuator.html#actuator.monitoring.customizing-management-server-context-path)3.1\\. ƹ˵·\n\n\n\nʱΪ˵㶨ǰ׺Ǻõġ 磬ӦóѾ `/actuator` Ŀġ ʹ `management.endpoints.web.base-path` ı˵ǰ׺ʾ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.endpoints.web.base-path=/manage\n\n```\n\n\n\n\n\n\n\nǰ `application.properties` ӽ˵ `/actuator/{id}` Ϊ `/manage/{id}` 磬`/manage/info`\n\n\n\n\n\n|  | ǹ˿ڱΪ[ʹòͬHTTP˿](https://springdoc.cn/spring-boot/actuator.html#actuator.monitoring.customizing-management-server-port)¶˵㣬 `management.endpoints.web.base-path`  `server.servlet.context-path` Servlet WebӦã `spring.webflux.base-path` reactive WebӦã `management.server.port` `management.endpoints.web.base-path`  `management.server.base-path` ġ |\n| --- | --- |\n\n\n\n\n\nѶ˵ӳ䵽ͬ·ʹ `management.endpoints.web.path-mapping` ԡ\n\n\n\n\n\nӽ `/actuator/health` ӳΪ `/healthcheck`\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.endpoints.web.base-path=/\nmanagement.endpoints.web.path-mapping.health=healthcheck\n\n```\n\n\n\n\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/actuator.html#actuator.monitoring.customizing-management-server-port)3.2\\. ƹ˿\n\n\n\nڻƵĲ˵ͨʹĬϵHTTP˿¶˵һǵѡ ȻӦóԼУܸϲʹòͬHTTP˿¶˵㡣\n\n\n\n\n\n `management.server.port` ıHTTP˿ڣʾ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.server.port=8081\n\n```\n\n\n\n\n\n\n\n|  |  Cloud Foundry ϣĬ£Ӧóڶ˿ 8080 Ͻ HTTP  TCP ·ɵ  Cloud Foundry ʹԶ˿ڣҪȷӦó·ԽתԶ˿ڡ |\n| --- | --- |\n\n\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/actuator.html#actuator.monitoring.management-specific-ssl)3.3\\. ManagementSSL\n\n\n\nΪʹԶ˿ʱҲͨʹø `management.server.ssl.*` ùSSL 磬ùͨHTTPṩ񣬶ӦóʹHTTPSʾ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nserver.port=8443\nserver.ssl.enabled=true\nserver.ssl.key-store=classpath:store.jks\nserver.ssl.key-password=secret\nmanagement.server.port=8080\nmanagement.server.ssl.enabled=false\n\n```\n\n\n\n\n\n\n\nߣ͹ʹSSLʹòͬԿ洢ʾ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nserver.port=8443\nserver.ssl.enabled=true\nserver.ssl.key-store=classpath:main.jks\nserver.ssl.key-password=secret\nmanagement.server.port=8080\nmanagement.server.ssl.enabled=true\nmanagement.server.ssl.key-store=classpath:management.jks\nmanagement.server.ssl.key-password=secret\n\n```\n\n\n\n\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/actuator.html#actuator.monitoring.customizing-management-server-address)3.4\\. Managementַ\n\n\n\nͨ `management.server.address` ƹ˵Ŀõַ ֻڲάϼֻ `localhost` ӣá\n\n\n\n\n\n|  | ֻе˿˿ڲͬʱڲͬĵַϽм |\n| --- | --- |\n\n\n\n\n\n `application.properties` Զ̹ӡ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.server.port=8081\nmanagement.server.address=127.0.0.1\n\n```\n\n\n\n\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/actuator.html#actuator.monitoring.disabling-http-endpoints)3.5\\. HTTP˵\n\n\n\n㲻ͨHTTP¶˵㣬԰ѹ˿Ϊ `-1`ʾ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.server.port=-1\n\n```\n\n\n\n\n\n\n\nҲͨʹ `management.endpoints.web.exposure.exclude` ʵ֣ʾ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.endpoints.web.exposure.exclude=*\n\n```\n\n\n\n\n\n\n\n\n\n\n\n\n\n## [](https://springdoc.cn/spring-boot/actuator.html#actuator.jmx)4\\. ͨJMXмغ͹\n\n\n\n\n\nJavaչJMXṩһ׼Ļغ͹Ӧó Ĭ£ùδá ͨ `spring.jmx.enabled` Ϊ `true`  Spring Bootʵ `MBeanServer` ΪIDΪ `mbeanServer` Bean κδSpring JMXעBean`@ManagedResource``@ManagedAttribute`  `@ManagedOperation`ᱩ¶\n\n\n\n\n\nƽ̨ṩһ׼ `MBeanServer` Spring BootʹڱҪʱĬΪVM `MBeanServer` Щʧˣͻᴴһµ `MBeanServer`\n\n\n\n\n\nϸڼ [`JmxAutoConfiguration`](https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jmx/JmxAutoConfiguration.java) ࡣ\n\n\n\n\n\nĬ£Spring BootҲ˵ΪJMX MBeans `org.springframework.boot` ¹ ҪȫJMXеĶ˵עᣬԿעԼ `EndpointObjectNameFactory` ʵ֡\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/actuator.html#actuator.jmx.custom-mbean-names)4.1\\. ԶMBean\n\n\n\nMBeanͨɶ˵ `id` ɡ 磬 `health` ˵㱻¶Ϊ `org.springframework.boot:type=Endpoint,name=Health`\n\n\n\n\n\nӦóһϵSpring `ApplicationContext`ַܻᷢͻ Ϊ˽⣬Խ `spring.jmx.unique-names` Ϊ `true`MBean־Ψһġ\n\n\n\n\n\n㻹Զ屩¶˵JMX ʾ `application.properties` һӡ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nspring.jmx.unique-names=true\nmanagement.endpoints.jmx.domain=com.example.myapp\n\n```\n\n\n\n\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/actuator.html#actuator.jmx.disable-jmx-endpoints)4.2\\. JMX˵\n\n\n\n㲻ͨJMX¶˵㣬԰ `management.endpoints.jmx.exposure.exclude` Ϊ `*`ʾ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.endpoints.jmx.exposure.exclude=*\n\n```\n\n\n\n\n\n\n\n\n\n\n\n\n\n## [](https://springdoc.cn/spring-boot/actuator.html#actuator.observability)5\\. ɹ۲ԣObservability\n\n\n\n\n\nɹ۲ָⲿ۲һеϵͳڲ״̬֧ɣ־͸١\n\n\n\n\n\nڶ͸٣Spring Bootʹ [Micrometer Observation](https://micrometer.io/docs/observation)ҪԼĹ۲죨⽫¶͸٣עһ `ObservationRegistry`\n\n\n\n\n\n\n\n```\n@Component\npublic class MyCustomObservation {\n\n    private final ObservationRegistry observationRegistry;\n\n    public MyCustomObservation(ObservationRegistry observationRegistry) {\n        this.observationRegistry = observationRegistry;\n    }\n\n    public void doSomething() {\n        Observation.createNotStarted(\"doSomething\", this.observationRegistry)\n                .lowCardinalityKeyValue(\"locale\", \"en-US\")\n                .highCardinalityKeyValue(\"userId\", \"42\")\n                .observe(() -> {\n                    // Execute business logic here\n                });\n    }\n\n}\n\n```\n\n\n\n\n\n\n\n|  | Ϳȵıǩӵָ׷У߿ȵıǩֻӵ׷С |\n| --- | --- |\n\n\n\n\n\n`ObservationPredicate``GlobalObservationConvention`  `ObservationHandler` ͵ Bean Զעᵽ `ObservationRegistry` ϡע `ObservationRegistryCustomizer` Beanһע\n\n\n\n\n\nϸ [Micrometer Observation ĵ](https://micrometer.io/docs/observation)\n\n\n\n\n\n|  | JDBCR2DBCĿɹ۲ԣObservabilityʹõĿá JDBC [Datasource Micrometer Ŀ](https://github.com/jdbc-observations/datasource-micrometer) ṩһ Spring Boot StarterڵJDBCʱԶ۲졣 [οĵ](https://jdbc-observations.github.io/datasource-micrometer/docs/current/docs/html/)ĶϢR2DBC [R2DBC۲Spring BootԶ](https://github.com/spring-projects-experimental/r2dbc-micrometer-spring-boot) ΪR2DBCѯô۲졣 |\n| --- | --- |\n\n\n\n\n\n½ڽṩ־ָ׷ٵĸϸڡ\n\n\n\n\n\n\n\n\n\n## [](https://springdoc.cn/spring-boot/actuator.html#actuator.loggers)6\\. ־¼Logger\n\n\n\n\n\nSpring Boot Actuatorʱ鿴Ӧó־Ĺܡ Բ鿴б򵥸־¼ãȷõ־Լ־ܸЧ־ɡ Щ֮һ\n\n\n\n\n\n*   `TRACE`\n\n*   `DEBUG`\n\n*   `INFO`\n\n*   `WARN`\n\n*   `ERROR`\n\n*   `FATAL`\n\n*   `OFF`\n\n*   `null`\n\n\n\n\n\n`null` ʾûȷá\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/actuator.html#actuator.loggers.configure)6.1\\. һ Logger\n\n\n\nҪһļ¼`POST` һʵ嵽ԴURIʾ\n\n\n\n\n\n\n\n```\n{\n    \"configuredLevel\": \"DEBUG\"\n}\n```\n\n\n\n\n\n\n\n|  | Ҫ reset ã¼ض𣨲ʹĬãԴһ `null` ֵΪ `configuredLevel` |\n| --- | --- |\n\n\n\n\n\n\n\n\n\n\n\n## [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics)7\\. ָ꣨Metrics\n\n\n\n\n\nSpring Boot ActuatorΪ [Micrometer](https://micrometer.io/) ṩԶãMicrometerһ֧ [ڶϵͳ](https://micrometer.io/docs) Ӧóָӿڣ\n\n\n\n\n\n*   [AppOptics](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.export.appoptics)\n\n*   [Atlas](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.export.atlas)\n\n*   [Datadog](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.export.datadog)\n\n*   [Dynatrace](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.export.dynatrace)\n\n*   [Elastic](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.export.elastic)\n\n*   [Ganglia](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.export.ganglia)\n\n*   [Graphite](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.export.graphite)\n\n*   [Humio](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.export.humio)\n\n*   [Influx](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.export.influx)\n\n*   [JMX](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.export.jmx)\n\n*   [KairosDB](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.export.kairos)\n\n*   [New Relic](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.export.newrelic)\n\n*   [OpenTelemetry](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.export.otlp)\n\n*   [Prometheus](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.export.prometheus)\n\n*   [SignalFx](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.export.signalfx)\n\n*   [Simple (in-memory)](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.export.simple)\n\n*   [Stackdriver](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.export.stackdriver)\n\n*   [StatsD](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.export.statsd)\n\n*   [Wavefront](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.export.wavefront)\n\n\n\n\n\n|  | Ҫ˽MicrometerĹܣμ [οĵ](https://micrometer.io/docs)ر [](https://micrometer.io/docs/concepts) |\n| --- | --- |\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.getting-started)7.1\\. \n\n\n\nSpring BootԶһϵ `MeterRegistry`ΪclasspathϷֵÿֵ֧ʵһע ʱclasspathж `micrometer-registry-{system}` Spring Bootעˡ\n\n\n\n\n\nעйͬص㡣 磬ʹ Micrometer עʵclasspathϣҲԽһضע ӽDatadog\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.datadog.metrics.export.enabled=false\n\n```\n\n\n\n\n\n\n\nҲԽеעעض˵ʾ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.defaults.metrics.export.enabled=false\n\n```\n\n\n\n\n\n\n\nSpring BootκԶõעӵ `Metrics` ϵȫ־̬עȷҪ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.metrics.use-global-registry=false\n\n```\n\n\n\n\n\n\n\nע `MeterRegistryCustomizer` Beanһעκαע֮ǰӦͨǩ\n\n\n\n\n\n\n\nJava\n\nKotlin\n\n\n\n\n\n```\n@Configuration(proxyBeanMethods = false)\npublic class MyMeterRegistryConfiguration {\n\n    @Bean\n    public MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {\n        return (registry) -> registry.config().commonTags(\"region\", \"us-east-1\");\n    }\n\n}\n\n```\n\n\n\n\n\n\n\nͨķͽӦضעʵ֡\n\n\n\n\n\n\n\nJava\n\nKotlin\n\n\n\n\n\n```\n@Configuration(proxyBeanMethods = false)\npublic class MyMeterRegistryConfiguration {\n\n    @Bean\n    public MeterRegistryCustomizer<GraphiteMeterRegistry> graphiteMetricsNamingConvention() {\n        return (registry) -> registry.config().namingConvention(this::name);\n    }\n\n    private String name(String name, Meter.Type type, String baseUnit) {\n        return ...\n    }\n\n}\n\n```\n\n\n\n\n\n\n\nSpring Boot [ instrumentation](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.supported)ͨûרעơ\n\n\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.export)7.2\\. ֵ֧ļϵͳ\n\n\n\nڼҪÿֵ֧ļϵͳ\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.export.appoptics)7.2.1\\. AppOptics\n\n\n\nĬ£AppOpticsעĻᶨڽָ͵ `[api.appoptics.com/v1/measurements](https://api.appoptics.com/v1/measurements)`Ҫָ굼 SaaS [AppOptics](https://micrometer.io/docs/registry/appOptics)ṩAPIơ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.appoptics.metrics.export.api-token=YOUR_TOKEN\n\n```\n\n\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.export.atlas)7.2.2\\. Atlas\n\n\n\nĬ£ָᱻ㱾ػϵ [Atlas](https://micrometer.io/docs/registry/atlas)ṩ [Atlas server](https://github.com/Netflix/atlas) λá\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.atlas.metrics.export.uri=https://atlas.example.com:7101/api/v1/publish\n\n```\n\n\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.export.datadog)7.2.3\\. Datadog\n\n\n\nһDatadogעĻᶨڽָ͵ [datadoghq](https://www.datadoghq.com/) Ҫָ굽 [Datadog](https://micrometer.io/docs/registry/datadog)ṩAPIԿ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.datadog.metrics.export.api-key=YOUR_KEY\n\n```\n\n\n\n\n\n\n\nṩһӦԿѡôԪݣǱͺͻλҲ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.datadog.metrics.export.api-key=YOUR_API_KEY\nmanagement.datadog.metrics.export.application-key=YOUR_APPLICATION_KEY\n\n```\n\n\n\n\n\n\n\nĬ£ָ걻͵Datadog [site](https://docs.datadoghq.com/getting_started/site) `[api.datadoghq.com](https://api.datadoghq.com/)` DatadogĿйվϣҪָͨ꣬ӦURI\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.datadog.metrics.export.uri=https://api.datadoghq.eu\n\n```\n\n\n\n\n\n\n\n㻹ԸıDatadogָʱ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.datadog.metrics.export.step=30s\n\n```\n\n\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.export.dynatrace)7.2.4\\. Dynatrace\n\n\n\nDynatraceṩָȡAPIΪ [Micrometer](https://micrometer.io/docs/registry/dynatrace) ʵֵġ [](https://www.dynatrace.com/support/help/how-to-use-dynatrace/metrics/metric-ingestion/ingestion-methods/micrometer) ҵDynatraceMicrometerָĵ`v1` ռеֻڵ [Timeseries v1 API](https://www.dynatrace.com/support/help/dynatrace-api/environment-api/metric-v1/) ʱ`v2` ռеֻڵ [Metrics v2 API](https://www.dynatrace.com/support/help/dynatrace-api/environment-api/metric-v2/post-ingest-metrics/) ʱע⣬üÿֻܵAPI `v1`  `v2` 汾`v2` 汾ѡ `device-id`v1Ҫv2вʹã `v1` ռбãômetric `v1` ˵㡣򣬾ͼٶ `v2` 汾\n\n\n\n\n\n##### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.export.dynatrace.v2-api)v2 API\n\n\n\nַͨʽʹv2 API\n\n\n\n\n\n###### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.export.dynatrace.v2-api.auto-config)Զ\n\n\n\nDynatraceԶOneAgentDynatrace Operator for Kubernetesص\n\n\n\n\n\n**OneAgent**OneAgentָԶ [local OneAgent ingest endpoint](https://www.dynatrace.com/support/help/how-to-use-dynatrace/metrics/metric-ingestion/ingestion-methods/local-api/)  ȡ˵㽫ָתDynatraceˡ\n\n\n\n\n\n**Dynatrace Kubernetes Operator**ڰװDynatrace OperatorKubernetesʱעԶӲԱȡĶ˵URIAPIơ\n\n\n\n\n\nĬΪ `io.micrometer:micrometer-registry-dynatrace` ֮⣬Ҫرá\n\n\n\n\n\n\n\n###### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.export.dynatrace.v2-api.manual-config)ֶ\n\n\n\nûԶãҪ [Metrics v2 API](https://www.dynatrace.com/support/help/dynatrace-api/environment-api/metric-v2/post-ingest-metrics/) Ķ˵һ API ơAPIƱ Ingest metrics `metrics.ingest`Ȩáǽ齫ƵķΧһȨϡȷ˵URI·磬`/api/v2/metrics/ingest`\n\n\n\n\n\nMetrics API v2ȡ˵URLĲѡͬ\n\n\n\n\n\n*   SaaS: `https://{your-environment-id}.live.dynatrace.com/api/v2/metrics/ingest`\n\n*   Managed deployments: `https://{your-domain}/e/{your-environment-id}/api/v2/metrics/ingest`\n\n\n\n\n\n `example` environment id öֵ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.dynatrace.metrics.export.uri=https://example.live.dynatrace.com/api/v2/metrics/ingest\nmanagement.dynatrace.metrics.export.api-token=YOUR_TOKEN\n\n```\n\n\n\n\n\n\n\nʹDynatrace v2 APIʱʹ¿ѡܣϸڿ [Dynatraceĵ](https://www.dynatrace.com/support/help/how-to-use-dynatrace/metrics/metric-ingestion/ingestion-methods/micrometer#dt-configuration-properties) ҵ\n\n\n\n\n\n*   Metric key ǰ׺һǰ׺ǰ׺ӵеmetric keyС\n\n*   DynatraceԪʵOneAgentDynatraceԱУöԪݣ磬̻Podḻָꡣ\n\n*   Ĭάȡָӵеļֵԡ Micrometerָ˾ͬıǩǽĬdimension\n\n*   ʹDynatrace Summary instrumentĳЩ£Micrometer Dynatraceעָ걻ܾ Micrometer 1.9.xУͨDynatraceضժҪ⡣ Ϊ `false` ʹMicrometerص1.9.x֮ǰĬΪ ֻڴMicrometer 1.8.xǨƵ1.9.xʱʱſʹá\n\n\n\n\n\nԲָURIAPIƣʾ £ʹԶõĶ˵㡣\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.dynatrace.metrics.export.v2.metric-key-prefix=your.key.prefix\nmanagement.dynatrace.metrics.export.v2.enrich-with-dynatrace-metadata=true\nmanagement.dynatrace.metrics.export.v2.default-dimensions.key1=value1\nmanagement.dynatrace.metrics.export.v2.default-dimensions.key2=value2\nmanagement.dynatrace.metrics.export.v2.use-dynatrace-summary-instruments=true\n\n```\n\n\n\n\n\n\n\n\n\n\n\n##### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.export.dynatrace.v1-api)v1 API (Legacy)\n\n\n\nDynatrace v1 APIָעͨʹ [Timeseries v1 API](https://www.dynatrace.com/support/help/dynatrace-api/environment-api/metric-v1/) ڽָ͵õURI Ϊеã `device-id` ʱv1Ҫv2вʹãָ걻Timeseries v1˵㡣 Ҫ [Dynatrace](https://micrometer.io/docs/registry/dynatrace) ָ꣬ṩAPIơ豸IDURI\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.dynatrace.metrics.export.uri=https://{your-environment-id}.live.dynatrace.com\nmanagement.dynatrace.metrics.export.api-token=YOUR_TOKEN\nmanagement.dynatrace.metrics.export.v1.device-id=YOUR_DEVICE_ID\n\n```\n\n\n\n\n\n\n\nv1APIָURIָ·Ϊv1Ķ˵·Զӡ\n\n\n\n\n\n\n\n##### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.export.dynatrace.version-independent-settings)汾޹ص\n\n\n\nAPI˵⣬㻹ԸıDynatraceָļʱ䡣 Ĭϵĵʱ `60s` ӽʱΪ30롣\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.dynatrace.metrics.export.step=30s\n\n```\n\n\n\n\n\n\n\n [Micrometerĵ](https://micrometer.io/docs/registry/dynatrace)  [Dynatraceĵ](https://www.dynatrace.com/support/help/how-to-use-dynatrace/metrics/metric-ingestion/ingestion-methods/micrometer) ҵΪMicrometerDynatrace exporterĸϢ\n\n\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.export.elastic)7.2.5\\. Elastic\n\n\n\nĬ£ָ걻㱾ػϵ [Elastic](https://micrometer.io/docs/registry/elastic)  ͨʹṩҪʹõElasticλá\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.elastic.metrics.export.host=https://elastic.example.com:8086\n\n```\n\n\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.export.ganglia)7.2.6\\. Ganglia\n\n\n\nĬ£ָ걻㱾ػϵ [Ganglia](https://micrometer.io/docs/registry/ganglia) ṩ [Ganglia server](http://ganglia.sourceforge.net/) Ͷ˿ڣʾ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.ganglia.metrics.export.host=ganglia.example.com\nmanagement.ganglia.metrics.export.port=9649\n\n```\n\n\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.export.graphite)7.2.7\\. Graphite\n\n\n\nĬ£ָᱻ㱾ػϵ [Graphite](https://micrometer.io/docs/registry/graphite) ṩ [Graphite server](https://graphiteapp.org/) Ͷ˿ڣʾ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.graphite.metrics.export.host=graphite.example.com\nmanagement.graphite.metrics.export.port=9004\n\n```\n\n\n\n\n\n\n\nMicrometerṩһĬϵ `HierarchicalNameMapper`dimensional meter IDhttps://micrometer.io/docs/registry/graphite#_hierarchical_name_mapping[ӳ䵽 flat hierarchical name]\n\n\n\n\n\n|  | ҪΪ붨 `GraphiteMeterRegistry` ṩԼ `HierarchicalNameMapper` Լ壬ṩһԶõ `GraphiteConfig`  `Clock` BeanJavaKotlin```@Configuration(proxyBeanMethods = false)public class MyGraphiteConfiguration {    @Bean    public GraphiteMeterRegistry graphiteMeterRegistry(GraphiteConfig config, Clock clock) {        return new GraphiteMeterRegistry(config, clock, this::toHierarchicalName);    }    private String toHierarchicalName(Meter.Id id, NamingConvention convention) {        return ...    }}``` |\n| --- | --- |\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.export.humio)7.2.8\\. Humio\n\n\n\nĬ£HumioעĻᶨڽָ͵ [cloud.humio.com](https://cloud.humio.com/)  Ҫָ굼SaaS [Humio](https://micrometer.io/docs/registry/humio)ṩAPIơ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.humio.metrics.export.api-token=YOUR_TOKEN\n\n```\n\n\n\n\n\n\n\n㻹ӦһǩȷָԴ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.humio.metrics.export.tags.alpha=a\nmanagement.humio.metrics.export.tags.bravo=b\n\n```\n\n\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.export.influx)7.2.9\\. Influx\n\n\n\nĬ£ָᱻڱػϵ [Influx](https://micrometer.io/docs/registry/influx) v1ʵĬáҪָ굽InfluxDB v2 `org``bucket` дָauthentication `token`ͨ·ʽṩҪʹõ [Influx server](https://www.influxdata.com/) λá\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.influx.metrics.export.uri=https://influx.example.com:8086\n\n```\n\n\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.export.jmx)7.2.10\\. JMX\n\n\n\nMicrometerṩ˶ [JMX](https://micrometer.io/docs/registry/jmx) ķֲӳ䣬ҪΪһۺͿֲķʽ鿴صĶ Ĭ£ָ걻 `metrics` JMX ͨ·ʽṩҪʹõ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.jmx.metrics.export.domain=com.example.app.metrics\n\n```\n\n\n\n\n\n\n\nMicrometerṩһĬϵ `HierarchicalNameMapper`dimensional meter ID  [ӳ䵽 flat hierarchical name](https://micrometer.io/docs/registry/jmx#_hierarchical_name_mapping)\n\n\n\n\n\n|  | ҪΪ붨 `JmxMeterRegistry` ṩԼ `HierarchicalNameMapper` Լ壬ṩһԶõ `JmxConfig`  `Clock` BeanJavaKotlin```@Configuration(proxyBeanMethods = false)public class MyJmxConfiguration {    @Bean    public JmxMeterRegistry jmxMeterRegistry(JmxConfig config, Clock clock) {        return new JmxMeterRegistry(config, clock, this::toHierarchicalName);    }    private String toHierarchicalName(Meter.Id id, NamingConvention convention) {        return ...    }}``` |\n| --- | --- |\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.export.kairos)7.2.11\\. KairosDB\n\n\n\nĬ£ָ걻㱾ػϵ [KairosDB](https://micrometer.io/docs/registry/kairos) ͨ·ʽṩҪʹõ [KairosDB server](https://kairosdb.github.io/) λá\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.kairos.metrics.export.uri=https://kairosdb.example.com:8080/api/v1/datapoints\n\n```\n\n\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.export.newrelic)7.2.12\\. New Relic\n\n\n\nNew RelicעĻᶨڽָ͵ [New Relic](https://micrometer.io/docs/registry/new-relic)Ҫָ굽 [New Relic](https://newrelic.com/)ṩAPIԿ˻ID\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.newrelic.metrics.export.api-key=YOUR_KEY\nmanagement.newrelic.metrics.export.account-id=YOUR_ACCOUNT_ID\n\n```\n\n\n\n\n\n\n\n㻹ԸıNew Relicָʱ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.newrelic.metrics.export.step=30s\n\n```\n\n\n\n\n\n\n\nĬ£ָͨREST÷ģclasspathJava Agent APIҲʹ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.newrelic.metrics.export.client-provider-type=insights-agent\n\n```\n\n\n\n\n\n\n\nͨԼ `NewRelicClientProvider` ȫơ\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.export.otlp)7.2.13\\. OpenTelemetry\n\n\n\nĬ£ָ걻㱾ػϵ [OpenTelemetry](https://micrometer.io/docs/registry/otlp) ͨ·ʽṩҪʹõ [OpenTelemtry metric endpoint](https://opentelemetry.io/) λá\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.otlp.metrics.export.url=https://otlp.example.com:4318/v1/metrics\n\n```\n\n\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.export.prometheus)7.2.14\\. Prometheus\n\n\n\n[Prometheus](https://micrometer.io/docs/registry/prometheus) ϣscrapeѯӦóʵָꡣSpring Boot `/actuator/prometheus` ṩһactuator˵㣬Աʵĸʽ [Prometheus scrape](https://prometheus.io/)\n\n\n\n\n\n|  | Ĭ£ö˵ǲõģ뱻¶ϸμ[¶˵](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.exposing) |\n| --- | --- |\n\n\n\n\n\n `scrape_config` ӵ `prometheus.yml`\n\n\n\n\n\n\n\n```\nscrape_configs:\n  - job_name: \"spring\"\n    metrics_path: \"/actuator/prometheus\"\n    static_configs:\n      - targets: [\"HOST:PORT\"]\n```\n\n\n\n\n\n\n\nҲ֧ [Prometheus Exemplars](https://prometheus.io/docs/prometheus/latest/feature_flags/#exemplars-storage)ҪܣӦһ `SpanContextSupplier` Beanʹ [Micrometer Tracing](https://micrometer.io/docs/tracing)⽫ΪԶã룬ǿԴԼġ鿴 [Prometheus ĵ](https://prometheus.io/docs/prometheus/latest/feature_flags/#exemplars-storage) ΪҪPrometheusȷãֻ֧ʹ [OpenMetrics](https://github.com/OpenObservability/OpenMetrics/blob/v1.0.0/specification/OpenMetrics.md#exemplars) ʽ\n\n\n\n\n\nڶݵĻҵܴڵʱ䲻޷ȡʹ [Prometheus Pushgateway](https://github.com/prometheus/pushgateway) ָ֧֣걩¶PrometheusҪPrometheus Pushgateway֧֣Ŀ\n\n\n\n\n\n\n\n```\n<dependency>\n    <groupId>io.prometheus</groupId>\n    simpleclient_pushgateway\n</dependency>\n```\n\n\n\n\n\n\n\nPrometheus Pushgatewayclasspathϣ `management.prometheus.metrics.export.pushgateway.enabled` ԱΪ `true` ʱһ `PrometheusPushGatewayManager` beanͱԶˡ Prometheus PushgatewayָĹ\n\n\n\n\n\nͨʹ `management.prometheus.metrics.export.pushgateway` µ `PrometheusPushGatewayManager` ڸ߼ãҲṩԼ `PrometheusPushGatewayManager` bean\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.export.signalfx)7.2.15\\. SignalFx\n\n\n\nSignalFxעĻᶨڽָ͵ [SignalFx](https://micrometer.io/docs/registry/signalFx)Ҫָ굽 [SignalFx](https://www.signalfx.com/)ṩaccess token\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.signalfx.metrics.export.access-token=YOUR_ACCESS_TOKEN\n\n```\n\n\n\n\n\n\n\nҲԸıSignalFxָʱ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.signalfx.metrics.export.step=30s\n\n```\n\n\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.export.simple)7.2.16\\. Simple\n\n\n\nMicrometerṩһ򵥵ġڴеĺˣûעú˻ԶΪá㿴 [metrics endpoint](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.endpoint) ռЩ\n\n\n\n\n\nһʹκõĺˣڴеĺ˾ͻԶرաҲȷؽ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.simple.metrics.export.enabled=false\n\n```\n\n\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.export.stackdriver)7.2.17\\. Stackdriver\n\n\n\nStackdriverעĻᶨ [Stackdriver](https://cloud.google.com/stackdriver/) ָꡣҪָ굽SaaS [Stackdriver](https://micrometer.io/docs/registry/stackdriver)ṩGoogle Cloud project ID\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.stackdriver.metrics.export.project-id=my-project\n\n```\n\n\n\n\n\n\n\n㻹ԸıStackdriverָʱ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.stackdriver.metrics.export.step=30s\n\n```\n\n\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.export.statsd)7.2.18\\. StatsD\n\n\n\nStatsDעеؽָͨUDP͸StatsD agentĬ£ָ걻㱾ػϵ [StatsD](https://micrometer.io/docs/registry/statsD) agentͨ·ʽṩStatsD˿ںЭ飬Աʹá\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.statsd.metrics.export.host=statsd.example.com\nmanagement.statsd.metrics.export.port=9125\nmanagement.statsd.metrics.export.protocol=udp\n\n```\n\n\n\n\n\n\n\n㻹ԸıҪʹõStatsD·Э飨ĬΪDatadog\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.statsd.metrics.export.flavor=etsy\n\n```\n\n\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.export.wavefront)7.2.19\\. Wavefront\n\n\n\nWavefrontעڽָ͵ [Wavefront](https://micrometer.io/docs/registry/wavefront)ֱӽָ굼 [Wavefront](https://www.wavefront.com/)ṩAPI token\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.wavefront.api-token=YOUR_API_TOKEN\n\n```\n\n\n\n\n\n\n\n⣬ĻʹWavefront sidecarڲתָݵWavefront API\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.wavefront.uri=proxy://localhost:2878\n\n```\n\n\n\n\n\n\n\nָ귢Wavefront [Wavefrontĵ](https://docs.wavefront.com/proxies_installing.html)  `proxy://HOST:PORT` ʽ\n\n\n\n\n\nҲԸıWavefrontָʱ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.wavefront.metrics.export.step=30s\n\n```\n\n\n\n\n\n\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.supported)7.3\\. ֵָ֧ MetricͶMeter\n\n\n\nSpring BootΪָļṩԶעᡣ ڴ£Ĭֵṩ˺ָ꣬Էκֵ֧ļϵͳС\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.supported.jvm)7.3.1\\. JVMָ\n\n\n\nԶͨʹú Micrometer JVM JVMָ `jvm.` meter name ·\n\n\n\n\n\nṩJVMָꡣ\n\n\n\n\n\n*   ڴͻϸ\n\n*   ռйصͳ\n\n*   ߳\n\n*   غжص\n\n*   JVMİ汾Ϣ\n\n*   JIT ʱ\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.supported.system)7.3.2\\. ϵͳָ\n\n\n\nԶͨʹúMicrometerʵϵͳ ϵͳָ `system.``process.`  `disk.` meter ·\n\n\n\n\n\nṩϵͳָꡣ\n\n\n\n\n\n*   CPUָ\n\n*   ļָ\n\n*   ʱָ꣨ӦóѾеʱ;ʱĹ̶\n\n*   õĴ̿ռ\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.supported.application-startup)7.3.3\\. Ӧóָ\n\n\n\nԶñ¶Ӧóʱָꡣ\n\n\n\n\n\n*   `application.started.time`: Ӧóʱ䡣\n\n*   `application.ready.time`Ӧó׼Ϊṩʱ䡣\n\n\n\n\n\nָӦȫǵġ\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.supported.logger)7.3.4\\. ־¼ָ\n\n\n\nԶLogbackLog4J2¼ ϸ `log4j2.events.`  `logback.events.` meter¹\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.supported.tasks)7.3.5\\. ִк͵ָ\n\n\n\nԶʹпõ `ThreadPoolTaskExecutor`  `ThreadPoolTaskScheduler` BeanֻܱҪײ `ThreadPoolExecutor` á ָexecutorǣexecutorBeanơ\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.supported.spring-mvc)7.3.6\\. Spring MVC ָ\n\n\n\nԶܹ Spring MVC Controllerͱʽhandlerж Ĭ£ָ `http.server.requests` Ϊɵġ ͨ `management.observations.http.server.requests.name` Ƹơ\n\n\n\n\n\nڲĹ۲observationĸϢμ [Spring Framework οĵ](https://docs.spring.io/spring-framework/docs/6.0.5/reference/html/integration.html#integration.observability.http-server.servlet)\n\n\n\n\n\nҪӵĬϱǩУṩһ̳ `org.springframework.http.server.observation` е `DefaultServerRequestObservationConvention`  `@Bean`Ҫ滻Ĭϱǩṩһʵ `ServerRequestObservationConvention`  `@Bean`\n\n\n\n\n\n|  | ĳЩ£Webд쳣ᱻ¼ΪǩӦóѡ벢ͨ[handled exception Ϊ request attribute](https://springdoc.cn/spring-boot/web.html#web.servlet.spring-mvc.error-handling)¼쳣 |\n| --- | --- |\n\n\n\n\n\nĬ£󶼱 ҪԶṩһʵ `FilterRegistrationBean<WebMvcMetricsFilter>`  `@Bean`\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.supported.spring-webflux)7.3.7\\. Spring WebFlux ָ\n\n\n\nԶܹSpring WebFlux controllerͱʽhandlerж Ĭ£ָ `http.server.requests` Ϊɵġ ͨ `management.observations.http.server.requests.name` Ƹơ\n\n\n\n\n\nڲĹ۲observationĸϢμ [Spring Framework οĵ](https://docs.spring.io/spring-framework/docs/6.0.5/reference/html/integration.html#integration.observability.http-server.reactive)\n\n\n\n\n\nҪӵĬϱǩУṩ̳ `org.springframework.http.server.reactive.observation` е `DefaultServerRequestObservationConvention`  `@Bean`Ҫ滻Ĭϱǩṩһʵ `ServerRequestObservationConvention`  `@Bean`\n\n\n\n\n\n|  | ĳЩ£ʹд쳣ᱻ¼ΪǩӦóѡ벢ͨ[handled exceptionΪrequest attribute](https://springdoc.cn/spring-boot/web.html#web.reactive.webflux.error-handling)¼쳣 |\n| --- | --- |\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.supported.jersey)7.3.8\\. Jersey Server ָ\n\n\n\nԶʹJersey JAX-RSʵܱ Ĭ£ָ `http.server.requests` Ϊɵġ ͨ `management.observations.http.server.requests.name` ơ\n\n\n\n\n\nĬ£Jerseyָ걻ΪϢ\n\n\n\n<colgroup><col><col></colgroup>\n| Tag | ˵ |\n| --- | --- |\n| `exception` | ʱ׳κ쳣ļ |\n| `method` | ķ磬`GET`  `POST` |\n| `outcome` | ĽӦ״̬롣 1xx `INFORMATIONAL`2xx `SUCCESS`3xx `REDIRECTION`4xx `CLIENT_ERROR`5xx `SERVER_ERROR` |\n| `status` | ӦHTTP״̬루磬`200`  `500` |\n| `uri` | ܵĻڽб滻֮ǰURIģ壨磺`/api/person/{id}` |\n\n\n\nҪƱǩṩһʵ `JerseyTagsProvider`  `@Bean`\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.supported.http-clients)7.3.9\\. HTTP Client ָ\n\n\n\nSpring Boot Actuator `RestTemplate`  `WebClient` ĹߡΪˣעԶõĹʹʵ\n\n\n\n\n\n*   `RestTemplateBuilder`  `RestTemplate`\n\n*   `WebClient.Builder`  `WebClient`\n\n\n\n\n\nҲֶӦøߵcustomizer `ObservationRestTemplateCustomizer`  `ObservationWebClientCustomizer`\n\n\n\n\n\nĬ£ָ `http.client.requests` ɵġ\n\n\n\n\n\nͨ `management.observations.http.client.requests.name` ֡\n\n\n\n\n\nڲĹ۲observationĸϢμ [Spring Framework οĵ](https://docs.spring.io/spring-framework/docs/6.0.5/reference/html/integration.html#integration.observability.http-client)\n\n\n\n\n\nҪʹ `RestTemplate` ʱƱǩṩһʵ `org.springframework.http.client.observation`  `ClientRequestObservationConvention`  `@Bean`Ҫʹ `WebClient` ʱԶǩṩһʵ `org.springframework.web.reactive.function.client`  `ClientRequestObservationConvention`  `@Bean`\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.supported.tomcat)7.3.10\\. Tomcat ָ\n\n\n\nԶý `MBeanRegistry` ʱŻTomcat Ĭ£`MBeanRegistry` ǽõģͨ `server.tomcat.mbeanregistry.enabled` Ϊ `true` \n\n\n\n\n\nTomcatָ `tomcat.` meter ·\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.supported.cache)7.3.11\\. Cache ָ\n\n\n\nԶÿʱпõ `Cache` ʵм⣬ָ `cache` Ϊǰ׺\n\n\n\n\n\nǱǱ׼Ļָ꼯\n\n\n\n\n\nҲʹöġԻָꡣ\n\n\n\n\n\n֧»⡣\n\n\n\n\n\n*   Cache2k\n\n*   Caffeine\n\n*   Hazelcast\n\n*   κμݵJCacheJSR-107ʵ\n\n*   Redis\n\n\n\n\n\nָɻƺ `CacheManager` ǣ`CacheManager` Beanġ\n\n\n\n\n\n|  | ֻʱõĻ汻󶨵ע ûڻжĻ棬׶κʱĻԱ̷ʽĻ棬Ҫȷעᡣ һ `CacheMetricsRegistrar` Beanʹ̸ס |\n| --- | --- |\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.supported.spring-graphql)7.3.12\\. Spring GraphQL ָ\n\n\n\nμ [Spring GraphQL οĵ](https://docs.spring.io/spring-graphql/docs/1.1.2/reference/html/)\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.supported.jdbc)7.3.13\\. DataSource ָ\n\n\n\nԶʹпõ `DataSource` ָǰ׺Ϊ `jdbc.connections` ԴĽǱʾеǰġеġĺСǱ\n\n\n\n\n\n׼ҲɻbeanƼ `DataSource` ǡ\n\n\n\n\n\n|  | Ĭ£Spring BootΪֵ֧ԴṩԪݡ ϲԴ֧֣Ӷ `DataSourcePoolMetadataProvider` Bean  `DataSourcePoolMetadataProvidersConfiguration` ˽ʵ |\n| --- | --- |\n\n\n\n\n\n⣬Hikariضָ `hikaricp` ǰ׺¶ ÿָ궼poolǣ `spring.datasource.name` ƣ\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.supported.hibernate)7.3.14\\. Hibernate ָ\n\n\n\n `org.hibernate.orm:hibernate-micrometer` classpathϣͳƹܵHibernate `EntityManagerFactory` ʵᱻһΪ `hibernate` ָ⡣\n\n\n\n\n\n׼Ҳ `EntityManagerFactory` ǣBeanġ\n\n\n\n\n\nҪͳƣ׼JPA `hibernate.generate_statistics` Ϊ `true` Զõ `EntityManagerFactory` á\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nspring.jpa.properties[hibernate.generate_statistics]=true\n\n```\n\n\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.supported.spring-data-repository)7.3.15\\. Spring Data Repository ָ\n\n\n\nԶܹSpring Data `Repository` ĵýж Ĭ£ָ `spring.data.repository.invocations` Ϊɡ ͨ `management.metrics.data.repository.metric-name` Զơ\n\n\n\n\n\n`io.micrometer.core.annotation` е `@Timed` ע֧ `Repository` ӿںͷ㲻¼ `Repository` õĶmetricԽ `management.metrics.data.repository.autotime.enabled` Ϊ `false`רʹ `@Timed` ע⡣\n\n\n\n\n\n|  | һ `longTask = true`  `@Timed` עΪ÷һʱʱҪһ metric nameҿʱtask timerӡ |\n| --- | --- |\n\n\n\n\n\nĬ£repositoryصĶ׼ΪϢ\n\n\n\n<colgroup><col><col></colgroup>\n| Tag | ˵ |\n| --- | --- |\n| `repository` | Դ `Repository` ļ |\n| `method` | õ `Repository` ơ |\n| `state` | ״̬`SUCCESS`, `ERROR`, `CANCELED`, `RUNNING` |\n| `exception` | ׳κ쳣ļ |\n\n\n\nҪ滻ĬϱǩҪṩһʵ `RepositoryTagsProvider`  `@Bean`\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.supported.rabbitmq)7.3.16\\. RabbitMQ ָ\n\n\n\nԶʹпõ RabbitMQ ӹָΪ `rabbitmq`\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.supported.spring-integration)7.3.17\\. Spring Integration ָ\n\n\n\nֻҪ `MeterRegistry` beanSpring IntegrationͻԶṩ [Micrometer support](https://docs.spring.io/spring-integration/docs/6.1.0-M1/reference/html/system-management.html#micrometer-integration) ׼ `spring.integration.` meter ·\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.supported.kafka)7.3.18\\. Kafka ָ\n\n\n\nԶΪԶõ߹߹ֱעһ `MicrometerConsumerListener`  `MicrometerProducerListener` Ϊ `StreamsBuilderFactoryBean` עһ `KafkaStreamsMicrometerListener` ϸڣSpring Kafkaĵе [Micrometer Native Metrics](https://docs.spring.io/spring-kafka/docs/3.0.3/reference/html/#micrometer-native) ֡\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.supported.mongodb)7.3.19\\. MongoDB ָ\n\n\n\nڼҪMongoDBĿö\n\n\n\n\n\n##### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.supported.mongodb.command)MongoDBָ\n\n\n\nԶý `MongoMetricsCommandListener` Զõ `MongoClient` עᡣ\n\n\n\n\n\nһΪ `mongodb.driver.commands` timerָ걻ײMongoDB driverÿ Ĭ£ÿָ궼ΪϢ\n\n\n\n<colgroup><col><col></colgroup>\n| Tag | ˵ |\n| --- | --- |\n| `command` | ơ |\n| `cluster.id` | ͸ļȺıʶ |\n| `server.address` | ķĵַ |\n| `status` | Ľ`SUCCESS`  `FAILED |\n\n\n\nΪ滻ĬϵĶǩһ `MongoCommandTagsProvider` beanʾ\n\n\n\n\n\n\n\nJava\n\nKotlin\n\n\n\n\n\n```\n@Configuration(proxyBeanMethods = false)\npublic class MyCommandTagsProviderConfiguration {\n\n    @Bean\n    public MongoCommandTagsProvider customCommandTagsProvider() {\n        return new CustomCommandTagsProvider();\n    }\n\n}\n\n```\n\n\n\n\n\n\n\nҪԶõԡ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.metrics.mongo.command.enabled=false\n\n```\n\n\n\n\n\n\n\n\n\n##### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.supported.mongodb.connection-pool)MongoDB ӳָ\n\n\n\nԶý `MongoMetricsConnectionPoolListener` Զõ `MongoClient` עᡣ\n\n\n\n\n\nΪӳشĲָꡣ\n\n\n\n\n\n*   `mongodb.driver.pool.size` ӳصĵǰСкʹõĳԱ\n\n*   `mongodb.driver.pool.checkedout` 浱ǰʹе\n\n*   `mongodb.driver.pool.waitqueuesize` ӵĵȴеĵǰС\n\n\n\n\n\nĬ£ÿָ궼ΪϢ\n\n\n\n<colgroup><col><col></colgroup>\n| Tag | ˵ |\n| --- | --- |\n| `cluster.id` | ӳӦļȺıʶ |\n| `server.address` | ӳӦķĵַ |\n\n\n\nҪȡĬϵĶǩ붨һ `MongoConnectionPoolTagsProvider` bean\n\n\n\n\n\n\n\nJava\n\nKotlin\n\n\n\n\n\n```\n@Configuration(proxyBeanMethods = false)\npublic class MyConnectionPoolTagsProviderConfiguration {\n\n    @Bean\n    public MongoConnectionPoolTagsProvider customConnectionPoolTagsProvider() {\n        return new CustomConnectionPoolTagsProvider();\n    }\n\n}\n\n```\n\n\n\n\n\n\n\nҪԶõӳضԡ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.metrics.mongo.connectionpool.enabled=false\n\n```\n\n\n\n\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.supported.jetty)7.3.20\\. Jetty ָ\n\n\n\nԶͨʹMicrometer `JettyServerThreadPoolMetrics` ΪJetty `ThreadPool` ָꡣ Jetty `Connector` ʵָͨʹMicrometer `JettyConnectionMetrics` 󶨣 `server.ssl.enabled` Ϊ `true` ʱMicrometer `JettySslHandshakeMetrics`\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.supported.timed-annotation)7.3.21\\. @Timed ע֧\n\n\n\nҪSpring Bootֱֵ֧ĵطʹ `@Timed`ο [Micrometer ĵ](https://micrometer.io/docs/concepts#_the_timed_annotation)\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.supported.redis)7.3.22\\. Redis ָ\n\n\n\nԶΪԶõ `LettuceConnectionFactory` עһ `MicrometerCommandLatencyRecorder` ϸڣLettuceĵ [Micrometer Metrics](https://lettuce.io/core/6.2.3.RELEASE/reference/index.html#command.latency.metrics.micrometer)\n\n\n\n\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.registering-custom)7.4\\. עԶָ\n\n\n\nҪעԶ뽫 `MeterRegistry` עС\n\n\n\n\n\n\n\nJava\n\nKotlin\n\n\n\n\n\n```\n@Component\npublic class MyBean {\n\n    private final Dictionary dictionary;\n\n    public MyBean(MeterRegistry registry) {\n        this.dictionary = Dictionary.load();\n        registry.gauge(\"dictionary.size\", Tags.empty(), this.dictionary.getWords().size());\n    }\n\n}\n\n```\n\n\n\n\n\n\n\nĶ׼Beanǽʹ `MeterBinder` עǡ\n\n\n\n\n\n\n\nJava\n\nKotlin\n\n\n\n\n\n```\npublic class MyMeterBinderConfiguration {\n\n    @Bean\n    public MeterBinder queueSize(Queue queue) {\n        return (registry) -> Gauge.builder(\"queueSize\", queue::size).register(registry);\n    }\n\n}\n\n```\n\n\n\n\n\n\n\nʹ `MeterBinder` ȷȷϵڼֵʱBeanǿõġ 㷢Ӧóظһָ꣬ô `MeterBinder` ʵҲá\n\n\n\n\n\n|  | Ĭ£ `MeterBinder` Beanָ궼Զ󶨵Spring `MeterRegistry` |\n| --- | --- |\n\n\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.customizing)7.5\\. Ƹָ\n\n\n\nҪض `Meter` ʵԶ壬ʹ `io.micrometer.core.instrument.config.MeterFilter` ӿڡ\n\n\n\n\n\n磬 `com.example` ͷǱID `mytag.region` ǩΪ `mytag.area`¹\n\n\n\n\n\n\n\nJava\n\nKotlin\n\n\n\n\n\n```\n@Configuration(proxyBeanMethods = false)\npublic class MyMetricsFilterConfiguration {\n\n    @Bean\n    public MeterFilter renameRegionTagMeterFilter() {\n        return MeterFilter.renameTag(\"com.example\", \"mytag.region\", \"mytag.area\");\n    }\n\n}\n\n```\n\n\n\n\n\n\n\n|  | Ĭ£е `MeterFilter` BeanԶ󶨵Spring `MeterRegistry` ȷʹSpring `MeterRegistry` עָ꣬ʹ `Metrics` κξ̬ ЩʹõǲSpringȫע |\n| --- | --- |\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.customizing.common-tags)7.5.1\\. ǩTag\n\n\n\nͨñǩһڶлά꣬ʵ򡢶ջȡ ñǩǱԽãʾ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.metrics.tags.region=us-east-1\nmanagement.metrics.tags.stack=prod\n\n```\n\n\n\n\n\n\n\nǰΪֵΪ `us-east-1`  `prod` Ǳ `region`  `stack` ǩ\n\n\n\n\n\n|  | ʹGraphiteͨǩ˳ǺҪġ ʹַܱ֤ǩ˳򣬽GraphiteûһԶ `MeterFilter` 档 |\n| --- | --- |\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.customizing.per-meter-properties)7.5.2\\. Per-meter Properties\n\n\n\n `MeterFilter` Bean㻹ʹÿĻӦһ޵Զ幦ܡ ʹSpring Boot `PropertiesMeterFilter`ÿĶƱӦԸƿͷκαID ӹ˵κID `example.remote` ͷǱ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.metrics.enable.example.remote=false\n\n```\n\n\n\n\n\n\n\nper-meterĶơ\n\n\n\n<caption>Table 1\\. Per-meter customizations</caption><colgroup><col><col></colgroup>\n| Property | ˵ |\n| --- | --- |\n| `management.metrics.enable` | ǷܾضIDMeter ܵMeter `MeterRegistry` й˵ |\n| `management.metrics.distribution.percentiles-histogram` | Ƿ񷢲ʺϼɾۼάȣİٷλֱֵͼ |\n| `management.metrics.distribution.minimum-expected-value`, `management.metrics.distribution.maximum-expected-value` | ͨǯԤֵķΧٵֱͼͰ |\n| `management.metrics.distribution.percentiles` | Ӧóмİٷλֵ |\n| `management.metrics.distribution.expiry`, `management.metrics.distribution.buffer-length` | ͨڻλлǸȨأλڿõĹںתΪ õĻȡ |\n| `management.metrics.distribution.slo` | һۻֱͼеͰķˮƽĿ궨塣 |\n\n\n\n `percentiles-histogram` ٷ-ֱͼ`percentiles`ٷ `slo` ĸĸϸڣμMicrometerĵе [Histograms and percentiles ֱͼͰٷ](https://micrometer.io/docs/concepts#_histograms_and_percentiles)\n\n\n\n\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.endpoint)7.6\\. ָ˵\n\n\n\nSpring Bootṩһ `metrics` ˵㣬ԵʹӦóռָꡣö˵Ĭǲõģ빫ϸμ [¶˵](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints.exposing)\n\n\n\n\n\n `/actuator/metrics` ʾһõǱб ͨṩΪѡ鿴ĳضǱϢ磬`/actuator/metrics/jvm.memory.max`\n\n\n\n\n\n|  | ʹõӦʹõһ£ļϵͳо淶֡ 仰˵ `jvm.memory.max` PrometheusʾΪ `jvm_memory_max`ΪȻӦʹ `jvm.memory.max` Ϊѡ `metrics` ˵мǱ |\n| --- | --- |\n\n\n\n\n\nҲURLĩβ `tag=KEY:VALUE` ѯԶǱά??磬`/actuator/metrics/jvm.memory.max?tag=area:nonheap`\n\n\n\n\n\n|  | ĲֵǱƥǱκӦõıǩͳ _ܺ_ ǰУص `Value` ͳǶѵ Code CacheCompressed Class Space  Metaspace ڴ桰㼣֮͡ ֻ Metaspace ߴ磬һ `tag=id:Metaspace` -- `/actuator/metrics/jvm.memory.max?tag=area:nonheap&tag=id:Metaspace` |\n| --- | --- |\n\n\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/actuator.html#actuator.metrics.micrometer-observation)7.7\\.  Micrometer Observation\n\n\n\nһ `DefaultMeterObservationHandler` Զע `ObservationRegistry` ϣΪÿɵĹ۲죨completed observationmetric\n\n\n\n\n\n\n\n\n\n\n\n## [](https://springdoc.cn/spring-boot/actuator.html#actuator.micrometer-tracing)8\\. ׷٣Tracing\n\n\n\n\n\nSpring Boot Actuator Ϊ Micrometer Tracing ṩԹԶã [Micrometer Tracing](https://micrometer.io/docs/tracing) е׷tracerһӿڣfacade\n\n\n\n\n\n|  | Ҫ˽ Micrometer Tracing ܵϢ [οĵ](https://micrometer.io/docs/tracing) |\n| --- | --- |\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/actuator.html#actuator.micrometer-tracing.tracers)8.1\\. ֵ֧׷\n\n\n\nSpring BootΪ׷ṩԶá\n\n\n\n\n\n*   ʹ [Zipkin](https://zipkin.io/)  [Wavefront](https://docs.wavefront.com/)  [OpenTelemetry](https://opentelemetry.io/)\n\n*   ʹ [Zipkin](https://zipkin.io/)  [Wavefront](https://docs.wavefront.com/)  [OpenZipkin Brave](https://github.com/openzipkin/brave)\n\n\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/actuator.html#actuator.micrometer-tracing.getting-started)8.2\\. \n\n\n\nҪһʼ׷ٵʾӦó򡣾ǵĿĶԣ[getting-started.html](https://springdoc.cn/spring-boot/getting-started.html#getting-started.first-application) 漰ļ򵥵 Hello World! web㹻ˡǽʹ `OpenTelemetry` ׷ `Zipkin` Ϊ׷ٺˡ\n\n\n\n\n\nعһ£ǵҪӦô뿴ġ\n\n\n\n\n\n\n\n```\n@RestController\n@SpringBootApplication\npublic class MyApplication {\n\n    private static final Log logger = LogFactory.getLog(MyApplication.class);\n\n    @RequestMapping(\"/\")\n    String home() {\n        logger.info(\"home() has been called\");\n        return \"Hello World!\";\n    }\n\n    public static void main(String[] args) {\n        SpringApplication.run(MyApplication.class, args);\n    }\n\n}\n\n```\n\n\n\n\n\n\n\n|  |  `home()` Уһӵlogger䣬ںҪ |\n| --- | --- |\n\n\n\n\n\nڣǱ\n\n\n\n\n\n*   `org.springframework.boot:spring-boot-starter-actuator`\n\n*   `io.micrometer:micrometer-tracing-bridge-otel` -  Micrometer Observation API  OpenTelemetry ıҪ\n\n*   `io.opentelemetry:opentelemetry-exporter-zipkin` - Zipkin [traces](https://micrometer.io/docs/tracing#_glossary) Ҫġ\n\n\n\n\n\nµ application properties:\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.tracing.sampling.probability=1.0\n\n```\n\n\n\n\n\n\n\nĬ£Spring Bootֻ10%вԷֹ׷ٺ˲ظԽлΪ100%ÿ󶼻ᱻ͵ٺˡ\n\n\n\n\n\nΪռͿӻ٣ҪһиٵĺˡʹZipkinΪǵĸٺˡ [Zipkinָ](https://zipkin.io/pages/quickstart) ṩڱZipkin˵\n\n\n\n\n\nZipkinкӦó\n\n\n\n\n\nweb `[localhost:8080](http://localhost:8080/)`Ӧÿ\n\n\n\n\n\n\n\n Hello World! \n\n\n\n\n\n\n\nĻѾΪHTTP󴴽һ observationŽӵ `OpenTelemetry`Zipkinһµĸ٣trace\n\n\n\n\n\nڣ `[localhost:9411](http://localhost:9411/)` Zipkinû棬 \"Run Query\" ťгռĸϢӦÿһ׷١ \"Show\" ť鿴׷ٵϸڡ\n\n\n\n\n\n|  | ͨ `logging.pattern.level` Ϊ `%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}]`־аǰĸ٣trace span id |\n| --- | --- |\n\n\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/actuator.html#actuator.micrometer-tracing.tracer-implementations)8.3\\. Tracerʵ\n\n\n\nMicrometer Tracerֶ֧ʾʵ֣Spring Bootжϡ\n\n\n\n\n\n׷ʵֶҪ `org.springframework.boot:spring-boot-starter-actuator` \n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.micrometer-tracing.tracer-implementations.otel-zipkin)8.3.1\\. ʹ Zipkin  OpenTelemetry\n\n\n\n*   `io.micrometer:micrometer-tracing-bridge-otel` -  Micrometer Observation API  OpenTelemetry ıҪ\n\n*   `io.opentelemetry:opentelemetry-exporter-zipkin` - ZipkintraceҪġ\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.micrometer-tracing.tracer-implementations.otel-wavefront)8.3.2\\. ʹ Wavefront  OpenTelemetry\n\n\n\n*   `io.micrometer:micrometer-tracing-bridge-otel` -  Micrometer Observation API  OpenTelemetry ıҪ\n\n*   `io.micrometer:micrometer-tracing-reporter-wavefront` - WavefronttraceҪġ\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.micrometer-tracing.tracer-implementations.brave-zipkin)8.3.3\\. ʹ Zipkin  OpenZipkin Brave\n\n\n\n*   `io.micrometer:micrometer-tracing-bridge-brave` -  Micrometer Observation API  Brave ıҪ\n\n*   `io.zipkin.reporter2:zipkin-reporter-brave` - Zipkin trace Ҫġ\n\n\n\n\n\n|  | ĿûʹSpring MVCSpring WebFluxҲҪʹ `io.zipkin.reporter2:zipkin-sender-urlconnection`  |\n| --- | --- |\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/actuator.html#actuator.micrometer-tracing.tracer-implementations.brave-wavefront)8.3.4\\. ʹWavefrontOpenZipkin Brave\n\n\n\n*   `io.micrometer:micrometer-tracing-bridge-brave` - ӲMicrometer Observation APIBraveıҪ\n\n*   `io.micrometer:micrometer-tracing-reporter-wavefront` - WavefronttraceҪġ\n\n\n\n\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/actuator.html#actuator.micrometer-tracing.creating-spans)8.4\\. Զȣspan\n\n\n\nͨһ observation ԼspanΪˣ `ObservationRegistry` ע뵽С\n\n\n\n\n\n\n\n```\n@Component\nclass CustomObservation {\n\n    private final ObservationRegistry observationRegistry;\n\n    CustomObservation(ObservationRegistry observationRegistry) {\n        this.observationRegistry = observationRegistry;\n    }\n\n    void someOperation() {\n        Observation observation = Observation.createNotStarted(\"some-operation\", this.observationRegistry);\n        observation.lowCardinalityKeyValue(\"some-tag\", \"some-value\");\n        observation.observe(() -> {\n            // Business logic ...\n        });\n    }\n\n}\n\n```\n\n\n\n\n\n\n\n⽫һΪ \"some-operation\"  observationǩΪǩΪ \"some-tag=some-value\"\n\n\n\n\n\n|  | ڲmetric´һspanҪʹ Micrometer  [ͼTracer API](https://micrometer.io/docs/tracing#_using_micrometer_tracing_directly) |\n| --- | --- |\n\n\n\n\n\n\n\n\n\n\n\n## [](https://springdoc.cn/spring-boot/actuator.html#actuator.auditing)9\\. \n\n\n\n\n\nһSpring SecurityãSpring Boot ActuatorһƿܣԷ¼ĬΪ authentication success, failure  access denied 쳣 һܶڱʵʩ֤ʧܵԷǳá\n\n\n\n\n\nͨӦóṩһ `AuditEventRepository` ͵beanơ Ϊ˷㣬Spring Bootṩһ `InMemoryAuditEventRepository` `InMemoryAuditEventRepository` Ĺޣǽֻڿʹ 뿼ǴԼ `AuditEventRepository` ʵ֡\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/actuator.html#actuator.auditing.custom)9.1\\. \n\n\n\nΪ˶Ʒİȫ¼ṩԼ `AbstractAuthenticationAuditListener`  `AbstractAuthorizationAuditListener` ʵ֡\n\n\n\n\n\nҲΪԼҵ¼ʹƷ Ҫһ㣬Ҫô `AuditEventRepository` beanעԼֱʹҪôSpring `ApplicationEventPublisher`  `AuditApplicationEvent`ͨʵ `ApplicationEventPublisherAware`\n\n\n\n\n\n\n\n\n\n\n\n## [](https://springdoc.cn/spring-boot/actuator.html#actuator.http-exchanges)10\\. ¼ HTTP Exchange\n\n\n\n\n\nͨӦóṩһ `HttpExchangeRepository` ͵ bean  HTTP exchange ļ¼Ϊ˷Spring Boot ṩ `InMemoryHttpExchangeRepository`Ĭ£洢100 request/response exchange׷ٽtracing solutionsȣ`InMemoryHttpExchangeRepository` ޵ģǽֻڿʹǽʹһĸٻ۲ `Zipkin`  `OpenTelemetry`⣬ҲԴԼ `HttpExchangeRepository`\n\n\n\n\n\nʹ `httpexchanges` ˵ȡ洢 `HttpExchangeRepository` е request/response exchange Ϣ\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/actuator.html#actuator.http-exchanges.custom)10.1\\. Զ HTTP Exchange ¼\n\n\n\nҪԶÿ¼ exchange Ŀʹ `management.httpexchanges.recording.include` ԡ\n\n\n\n\n\nҪȫֹ±룬뽫 `management.httpexchanges.recording.enabled` Ϊ `false`\n\n\n\n\n\n\n\n\n\n\n\n## [](https://springdoc.cn/spring-boot/actuator.html#actuator.process-monitoring)11\\. ̼\n\n\n\n\n\n `spring-boot` ģУҵļ࣬Щļڽ̼ͨá\n\n\n\n\n\n*   `ApplicationPidFileWriter` һӦóPIDļĬ£ӦóĿ¼£ļΪ `application.pid`\n\n*   `WebServerPortFileWriter` һļеWebĶ˿ڣĬ£ӦóĿ¼£ļΪ `application.port`\n\n\n\n\n\nĬ£Щдûбǡ\n\n\n\n\n\n*   [ͨչ](https://springdoc.cn/spring-boot/actuator.html#actuator.process-monitoring.configuration)\n\n*   [Ա̷ʽʵֽ̼](https://springdoc.cn/spring-boot/actuator.html#actuator.process-monitoring.programmatically)\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/actuator.html#actuator.process-monitoring.configuration)11.1\\. չ\n\n\n\n `META-INF/spring.factories` ļУԼдPIDļlistenerһ߶\n\n\n\n\n\n\n\n org.springframework.context.ApplicationListener=\\\norg.springframework.boot.context.ApplicationPidFileWriter,\\\norg.springframework.boot.web.context.WebServerPortFileWriter \n\n\n\n\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/actuator.html#actuator.process-monitoring.programmatically)11.2\\. Ա̷ʽʵֽ̼\n\n\n\nҲͨ `SpringApplication.addListeners(?)` ʵ `Writer` һ  `Writer` 캯Զļ·\n\n\n\n\n\n\n\n\n\n\n\n## [](https://springdoc.cn/spring-boot/actuator.html#actuator.cloud-foundry)12\\. Cloud Foundry ֧\n\n\n\n\n\nSpring Boot actuatorģ֧֣𵽼ݵ Cloud Foundry ʵʱֽ֧ `/cloudfoundryapplication` ·Ϊ `@Endpoint` Beanṩһȫ·ߡ\n\n\n\n\n\nչ֧ʹ Cloud Foundry  UI鿴ѲӦó Web Ӧó򣩵õ Spring Boot ִϢǿ 磬Ӧó״̬ҳ԰ĽϢǵ͵ running  stopped ״̬\n\n\n\n\n\n|  | ͨû޷ֱӷ `/cloudfoundryapplication` · Ҫʹøö˵㣬дһЧ UAA ơ |\n| --- | --- |\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/actuator.html#actuator.cloud-foundry.disable)12.1\\. չ Cloud Foundry Actuator ֧\n\n\n\nȫ `/cloudfoundryapplication` ˵㣬 `application.properties` ļá\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.cloudfoundry.enabled=false\n\n```\n\n\n\n\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/actuator.html#actuator.cloud-foundry.ssl)12.2\\. Cloud Foundryǩ֤\n\n\n\nĬ£`/cloudfoundryapplication` ˵İȫ֤Ը Cloud Foundry  SSL á  Cloud Foundry UAA  Cloud Controller ʹǩ֤飬Ҫԡ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmanagement.cloudfoundry.skip-ssl-validation=true\n\n```\n\n\n\n\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/actuator.html#actuator.cloud-foundry.custom-context-path)12.3\\. Զ Context Path\n\n\n\n context-path Ϊ `/` κݣ Cloud Foundry ˵Ӧóĸá 磬 `server.servlet.context-path=/app` Cloud Foundry ˵ `/app/cloudfoundryapplication/*` á\n\n\n\n\n\nϣ Cloud Foundry ˵ʼ `/cloudfoundryapplication/*` ã۷·ΣҪӦóȷá ʹõ Web ͬͬ  Tomcatá\n\n\n\n\n\n\n\nJava\n\nKotlin\n\n\n\n\n\n```\n@Configuration(proxyBeanMethods = false)\npublic class MyCloudFoundryConfiguration {\n\n    @Bean\n    public TomcatServletWebServerFactory servletWebServerFactory() {\n        return new TomcatServletWebServerFactory() {\n\n            @Override\n            protected void prepareContext(Host host, ServletContextInitializer[] initializers) {\n                super.prepareContext(host, initializers);\n                StandardContext child = new StandardContext();\n                child.addLifecycleListener(new Tomcat.FixContextListener());\n                child.setPath(\"/cloudfoundryapplication\");\n                ServletContainerInitializer initializer = getServletContextInitializer(getContextPath());\n                child.addServletContainerInitializer(initializer, Collections.emptySet());\n                child.setCrossContext(true);\n                host.addChild(child);\n            }\n\n        };\n    }\n\n    private ServletContainerInitializer getServletContextInitializer(String contextPath) {\n        return (classes, context) -> {\n            Servlet servlet = new GenericServlet() {\n\n                @Override\n                public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {\n                    ServletContext context = req.getServletContext().getContext(contextPath);\n                    context.getRequestDispatcher(\"/cloudfoundryapplication\").forward(req, res);\n                }\n\n            };\n            context.addServlet(\"cloudfoundry\", servlet).addMapping(\"/*\");\n        };\n    }\n\n}\n\n```\n\n\n\n\n\n\n\n\n\n\n\n\n\n## [](https://springdoc.cn/spring-boot/actuator.html#actuator.whats-next)13\\. ʲô\n\n\n\n\n\nһ [Graphite](https://graphiteapp.org/) ͼιߡ\n\n\n\n\n\nԼĶ [ѡ](https://springdoc.cn/spring-boot/deployment.html#deployment) ǰȥ˽йSpring Boot [߲](https://springdoc.cn/spring-boot/build-tool-plugins.html#build-tool-plugins)һЩϢ\n\n\n\n\n\n\n\n"
  },
  {
    "path": "docs/Spring全家桶/SpringBoot/SpringBoot的Starter机制.md",
    "content": "starterSpringBootеһ·ЧĽĿ̵ĸӳ̶ȣڼ򻯿ŷǳõЧתһƬ£ϸspring boot staterʲôʲô\n\nSpring Boot StarterSpringBootбһָstackoverflowѾ˸starterʲô뿴Ļش[](https://stackoverflow.com/a/28273660)https://stackoverflow.com/questions/28273543/what-are-spring-boot-starter-jars/28273660#28273660\n\n![](https://images2018.cnblogs.com/blog/697611/201804/697611-20180409110042391-1447358002.png)\n\n˼˵starterһֶsynthesizeϳɣʲô˼أҿԾٸ˵\n\n###  ? ͳ\n\nûstarter֮ǰҪSpringʹjpaҿҪ²\n\n1.  MavenʹõݿJDBCjar\n2.  jpa\n3.  xxx.xmlһЩϢ\n4.  ĵֱ\n\nҪעǣ**_ÿ½һҪõjpaĿʱҪظһ_**ҲڵһԼĿʱGoogleԼһ˰ʱ˸ֵ֮jpaˡЩо˻OneNoteνĿĹ̸¼ĲԼҪõļݣһٴjpaĿʱ򣬾ͲҪٴȥGoogleˣֻҪűʼٰ֮еļcopy&pasteͿˡ\n\nĲҲ㲻Уʵûstarter֮ǰôɵģм⣺\n\n1.  ̱ȽϷһӳĿ\n2.  ͣcopy&paste[Dont repeat yourself](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself)\n3.  ڵһõʱ߱ȽСףҪѵʱ\n\n### ʹSpring Boot StarterЧ\n\nstarterҪĿľΪ˽Щ⡣\n\nstarterstarterõ˿Լȥ鷳ҪעǲͬstarterΪ˽ͬڲʵֿܻкܴĲ죬jpastarterRedisstarterʵ־ͲһΪstarterısynthesizeһ߼ĳҲеDockerΪǶһװĲ֪DockerΪ˽ʲôģҲDockerstarterһȡ\n\nstarterʵ֣Ȼͬstarterʵв죬ǻ϶ʹõͬݣConfigurationPropertiesAutoConfigurationΪSpring BootšԼáһʹConfigurationPropertiesǵãЩöһĬֵûдԭʼõ£ĬֵͻЧںܶǷǳõġ֮⣬starterConfigurationPropertiesʹеԱۼһļУһresourcesĿ¼µapplication.propertiesǾ͸SpringĿXML\n\nstarter߼\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/697611-20180409110236645-2097376440.png)\n\nstarterjarԼֶõʱjarûʲôͬǿΪstarterʵǰһЩòԼѼ򵥽û˰ûȥ˷ĹڡԼá£ConfigurationPropertiesûνòΪ?`application.properties`?ļĴڣʹҪԶãеҲֻҪһļнУʹǳ㡣\n\n˽starterʵǰûõĲ֮Ҫstarterͱstarter֮䲢ǾϵǸϵǿԸһһstarterûʹʱӵļ򵥷㡣ǿԸһеһstarterñʹʱӵļ򵥷㣬ʵSpring BootŶѾд󲿷ֵеǵstarter[](https://github.com/spring-projects/spring-boot/tree/v1.5.7.RELEASE/spring-boot-starters)鿴Щstarterб\n\nspringboot ô˾ȻûԶstarter붼Խһ¡\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230416200541.png)\n\n# SpringBoot starter\n\nSpringBootеstarterһַǳҪĻƣܹǰӵãͳһɽstarterӦֻҪmavenstarterSpringBootԶɨ赽ҪصϢӦĬástarterǰ˸ĴҪøϢšSpringBootԶͨclasspath·µ෢ҪBeanעIOCSpringBootṩճҵӦзֳspring-boot-starterģ顣Щģ鶼ѭԼ׵ĬãǵЩãѭԼá\n\n# Զstarter\n\nճʱһЩҵ֮Ĺܻģ飬ĿãһĿҲҪãÿζ¼ɵĻͻ鷳ʱֻҪЩܻģװһstarterĻʹõʱȥͺܷˡ\n\n## Զstarter\n\nʵԶstarterܼ򵥣Ҫ5\n\n1.  ½ģ飬淶 springbootԴstarter淶Ϊspring-boot-starter-xxx Զstarter淶Ϊxxx-spring-boot-starter\n\n xxx-spring-boot-autoconfigureԶúĴ\n xxx-spring-boot-starter\nҪԶô뿪ԽϵһģСֻspringbootٷ齫ģֿ\n2\\. spring-boot-autoconfigure\n3\\. ԶXXXProperties : ԸҪҪļеġ\n4\\. ԶXXXAutoConfigurationࣺҪԶʱһЩ߼ͬʱҲҪXXXProperties Ч\n5\\. Զspring.factoriesļresources/META-INFһspring.factoriesļspring-configuration-metadata.jsonspring-configuration-metadata.jsonļдļʱʾҪɲҪеĻʾѺáspring.factoriesڵԶ࣬Ҫ\n\n## ʵ\n\nΪ˷ֻһģˣ\n\n1.  һģ飬Ϊspring-boot-starter-my-starterӦpomļ\n\n```\n\t<groupId>com.example</groupId>\n\tspring-boot-starter-my-starter\n\t<version>1.0</version>\n\t<name>my-starter</name>\nƴ\n```\n\n1.  spring-boot-autoconfigure ʹõspring-boot-autoconfigure汾2.6.2\n\n```\n<dependencies>\n    <dependency>\n        <groupId>org.springframework.boot</groupId>\n        spring-boot-autoconfigure\n        <version>2.6.2</version>\n    </dependency>\n</dependencies>\nƴ\n```\n\n1.  ԶXXXProperties \n\n```\n@ConfigurationProperties(prefix = \"com.arron\")\npublic class MyStarterProperties {\n\n    private String name;\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n}\nƴ\n```\n\nٴһMyStarterConfigڶȡMyStarterProperties \n\n```\npublic class MyStarterConfig {\n\n    private MyStarterProperties myStarterProperties;\n\n    private String name;\n\n    public MyStarterConfig(MyStarterProperties myStarterProperties) {\n        this.myStarterProperties = myStarterProperties;\n    }\n\n    public String getName() {\n        return myStarterProperties.getName();\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n}\nƴ\n```\n\n1.  ԶXXXAutoConfiguration\n\n```\n@Configuration\n// EnableConfigurationProperties valueе\n@EnableConfigurationProperties(value = {MyStarterProperties.class})\npublic class MyStarterAutoConfiguration {\n\n    @Autowired\n    private MyStarterProperties myStarterProperties;\n\n    @Bean\n    @ConditionalOnMissingBean(MyStarterConfig.class)\n    public MyStarterConfig myStarterConfig(){\n        return new MyStarterConfig(myStarterProperties);\n    }\n\n}\nƴ\n```\n\n1.  resources/META-INFһspring.factoriesļ\n\nspring.factories\n\n```\norg.springframework.boot.autoconfigure.EnableAutoConfiguration=com.example.myStarter.MyStarterAutoConfiguration\nƴ\n```\n\nspring-configuration-metadata.json\n\n```\n{\n  \"group\": [\n    {\n      \"name\": \"com.arron\",\n      \"type\": \"com.example.myStarter.MyStarterProperties\",\n      \"sourceType\": \"com.example.myStarter.MyStarterProperties\"\n    }\n  ],\n  \"properties\": [\n    {\n      \"name\": \"com.arron.name\",\n      \"type\": \"java.lang.String\",\n      \"description\": \"my start name\",\n      \"sourceType\": \"com.example.myStarter.MyStarterProperties\",\n      \"defaultValue\": \"MyStarterProperties name\"\n    }\n  ]\n}\nƴ\n```\n\n## \n\nҵͼmaveninstallװ ![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230416200527082.png)\n\nȻ½һĿвԣĿ̾Ͳˡ\n\n1.  \n\n```\n\t<dependency>\n       <groupId>com.example</groupId>\n       spring-boot-starter-my-starter\n       <version>1.0</version>\n   </dependency>\nƴ\n```\n\n1.  ļԣ\n\n```\ncom:\n  arron:\n    name: myname\nƴ\n```\n\n1.  Ԫԣ\n\n```\n@RunWith(SpringRunner.class)\n@SpringBootTest\nclass RabbitmqApplicationTests {\n    @Autowired\n    private MyStarterConfig myStarterConfig;\n\n    @Test\n    public void testMyStarter(){\n        String name = myStarterConfig.getName();\n        System.out.println(name);\n    }\n}\nƴ\n```\n\n̨\n\n```\nmyname\nƴ\n```\n\nˣһԶspringboot starterˡ\n\n# ע\n\nЩעԶstarterǿܻõ\n\n*   @Conditionalһжϣעbean\n*   @ConditionalOnMissingBeanbeanʱ,ʵǰBean\n*   @ConditionalOnPropertyļ㶨򴴽bean򲻴\n*   @ConditionalOnBeanbeanʱ,ʵǰBean\n*   @ConditionalOnClass ·ϴڣʵǰBean\n*   @ConditionalOnMissingClass ·ϲڣʵǰBean\n\n\n\nߣ\nӣhttps://juejin.cn/post/7127468724046528525\nԴϡ\nȨСҵתϵ߻Ȩҵתע\n\n"
  },
  {
    "path": "docs/Spring全家桶/SpringBoot/SpringBoot的前世今生.md",
    "content": "# SpringBootǰ\n\nSpring Boot 2.0 Ƴּһѧϰ Spring Boot ȣ͵Ҹ˵Ĳ͵ķӾͿԸܵҶѧϰ Spring Boot 飬ôôѧϰ Spring Boot ֮ʱԼҲ˼ Spring Boot ıʲôSpring ҵǻʲôĿǴ Spring Boot? ͳҵʹ Spring Boot Ǵʲô?\n\nЩ⣬һ˽ Spring Boot ʲô?\n\n## Spring ʷ\n\n˵ Spring Boot ǲò˽һ Spring ҵΪ Spring Boot Դ Spirng 壬 Spring Boot ĵ Sping ܵķչϢϢء\n\nʱص2002꣬ʱ Java EE  EJB ʱ򣬺֪ܶ˾ǲô˼ĿʱһСΪ EJB ̫ӷףеĿҪʹ EJB ִͿܣӦûһָõķ⡣\n\nΪ֤뷨ȷģ200210дһ顶 Expert One-on-One J2EE ˵ʱ Java ҵӦó򿪷ָ Java EE  EJB дڵһЩҪȱݡⱾУһͨ Java עĸ򵥵Ľ\n\nУչʾڲʹ EJB ¹չλԤϵͳΪ˹Ӧóд˳ 30,000 еĻṹ룬ĿеĸΪ com.interface21׿ԴΪ interface21Ҳ Spring ǰ\n\n˭أǴ Rod Johnson ͼ, Rod Johnson Ϥѧ˼ѧλͬʱѧλ˳Ծڻص֮ǰѧĲʿѧλ Rod Johnson Ѿ뿪 Spring ΪһʹͶˣͬʱҲǶ˾Ķ£۷塣\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/springboot-hot.png)\n\nⱾ鷢һһ J2EE ƺͿһڶ졣ⱾṩĴ󲿷ֻܹ붼Ǹ߶ȿõġ 2003  Rod Johnson ͬڴ˿ܵĻϿһȫµĿΪ Spring , Rod Johnson  Spring Ǵͳ J2EE µĿʼ Spring չ쳵\n\n*   2004  03 £1.0 淢\n*   2006  10 £2.0 淢\n*   2007  11 ¸Ϊ SpringSourceͬʱ Spring 2.5\n*   2009  12 £Spring 3.0 \n*   2013  12 £Pivotal  Spring  4.0\n*   2017  09 £Spring 5.0 \n\n## Spring Boot ĵ\n\nʹ Spring пĸ˺ҵԽԽ࣬Spring ҲһһСܱһȫĿԴSpring ı߽粻ϵĽ䣬˺ Spring κˣĿԴм Spring Ӧ֧֣ Spring ֱ֮ҲһЩ⡣\n\nSpring ÿһԴҪһЩãǿĿԽԽӴҪɺܶ࿪Դ˺ʹ Spirng ĿҪܶļ̫÷ǳ⣬ó˺ Spring Ϊõ\n\nSpring ƺҲʶЩ⣬ôһԽЩ⣬ʱ΢ĸҲ𣬿ٿ΢СӦñøΪȣSpring պôôһϣ 2013 ʼ Spring Boot Ŀз20144£Spring Boot 1.0.0 \n\nSpring Boot ֮ܵԴĳע½һЩ˺ҵʹ Spring BootѸϲԴֱ2016꣬ڹ Spring Boot űʹڼܶо Spring Boot Ŀд˴ Spring Boot £ͬʱһЩ˾ҵڲСģʹãʹþ˳2016굽2018꣬ʹ Spring Boot ҵ͸˿ԽԽ࣬Ǵ Spring Boot ؼֵİٶָͿԿ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/springboot-hot.png)\n\nͼΪ2014굽2018 Spring Boot İٶָԿ Spring Boot 2.0 Ƴ߷塣\n\nȻ Spring Boot Ϊȡ Spring ,Spring Boot  Spring ΪǸ׵ʹ Spring Spring Boot гӦSpring ٷҲǳ Spring Boot ĺչѾ Spring Boot Ϊ˾Ŀƹ㣬ŵ˹ϵһλã˺ Spring Boot ĳչҲá\n\n# \n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230415111020.png)\n\nspringspringbootĻ\n\nSpring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can \"just run\".\n\nWe take an opinionated view of the Spring platform and third-party libraries so you can get started with minimum fuss. Most Spring Boot applications need minimal Spring configuration.\n\n֮ڸǣspringboot԰ǿٹӦ\n\nֻҪһ@SpringBootApplication עӦڣɱʶΪһspringbootӦ\n\nңspringboot̳˴ĵ⣬ǽȫҪãͿԽӦôΪspringbootܱѾú˴Ĭá\n\n# \n\nFeatures\n\n*   Create stand-alone Spring applications\n*   Embed Tomcat, Jetty or Undertow directly (no need to deploy WAR files)\n*   Provide opinionated 'starter' dependencies to simplify your build configuration\n*   Automatically configure Spring and 3rd party libraries whenever possible\n*   Provide production-ready features such as metrics, health checks, and externalized configuration\n*   Absolutely no code generation and no requirement for XML configuration\n\nٷspringbootԵ\n\n1һspringӦãڶ΢񣬻һwebӦ\n\n2õtomcatҲҪspringӦôwarֻҪjarв𼴿ɡ\n\n3ṩstarterӼspringboot̬еã統ʹspringwebʱǲҪӶspringmvcصmavenֱspring-boot-starter-webͿˣstarterԶصҰ汾ţؼ򻯺Żmaven\n\n4Զװspring͵⣬ҪָͨעļԶװ䣬һһЩǻԼԶװƣ԰ǽʡĶãҲԶװⲿjarṩspringbeaná\n\n5ṩصԣءָ㡢ȹܣspringbootṩǿ̬ﲻspringbootԼҲһЩⲿ̬\n\n6ڴɣҲҪxmlļ֤springbootĿȣԼü򻯵ļ׷ǽҪapplication.propertiesлòҲspringbootҵ̬ɵĶclassļ\n\n# SpringĹϵ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230415111139.png)\n\n## SpringFrameworkʲô⣿\n\nSpringJavaҵ棨Java Enterprise EditionJEEҲJ2EEƷ迪EnterpriseJavaBeanEJBSpringΪҵJavaṩһԼ򵥵ķͨע̣ü򵥵JavaPlain Old Java ObjectPOJOʵEJBĹܡ\n\n1.ʹSpringIOC,֮ϵSpring,֮,ǸרעӦ߼\n\n2.ṩڶ,,WSȡ\n\n3.ܺõ֧AOP,̡\n\n4.Ŀṩ˺ܺõļ֧,Hibernate,Struts2,JPA\n\n5.Spring DIƽҵ滻ĸԡ\n\n6.Springڵ,Ⱦ͡\n\n7.Springĸ߶ȿɿ,ǿSpring,߿ѡSpringֻȫ\n\n## SpringFrameworkûнʲô⣿\n\nȻSpringģȴġһʼSpringXMLãǺܶXMLáSpring 2.5˻עɨ裬˴ӦóʽXMLáSpring 3.0˻JavaãһͰȫĿع÷ʽԴXML\n\nЩö˿ʱġΪ˼Springúͽҵ֮Ҫ˼άлԱдüռ˱дӦó߼ʱ䡣пһSpringʵãͬʱҪĻرҲ١\n\n֮⣬ĿҲһʱ顣ڻʱҪҪЩ꣬һҪ֮ϵ꣬һѡİ汾֮Ĳͻ谭ĿĿȡ\n\n## SpringBootSpringȱ\n\nSpringBootSpringȱеĸƺŻԼõ˼룬ÿԱ߼ҵ֮˼άлȫĵͶ뵽߼ҵĴдУӶ˿Чʣһ̶Ŀڡ\n\nʹSpringܽпĹУҪúܶSpringܰspring-corespring-beanspring-contextȣЩͨظӵģҪܶʹüظã翪ע⡢־ȡSpring BootЩҪĲṩĬãȻЩĬǿ԰޸ĵģٴSpringӦ\n\n# SpringMVCĹϵ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230415111153.png)\n\nSpring BootSpring BootʹͿʼSpringӦóס ˺ܶ롣 Ļ˺ܶิԣ˿ԱԿֲɿSpringӦó\n\nSpring MVCSpring MVCڹWebӦóWeb MVCܡ ڸֹܵļ һHTTPWebӦó򿪷ܡ\n\n\n\nSpring BootSpring MVCڲͬĿĶڡ Spring BootSpring MVC֮Ҫ\n\n\n\n| Spring Boot | Spring MVC |\n| --- | --- |\n| Spring BootʹúĬֵSpringӦó | Spring MVCSpring»ģͼWebܡ |\n| ṩĬSpringֵ֧Ŀܡ | ṩڹWebӦóļ͹ܡ |\n| ֶá | Ҫֶйá |\n| Ҫ | Ǳġ |\n| 룬װһԪС | ֱָÿ |\n| ˿ʱ䲢ʡ | ʵͬĿҪʱ䡣 |\n\n# ΢SpringCloudĹϵ\n\nSpring BootSpringĳԱһȫµĿܣĿǾܼ򵥺ͿٵĿSpringӦó򣬼áΪ߿ݵʹSpringؿṩ˱΢ĿܣֻΪ΢ܵʹҲṩ˺ܺõĽּܡ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230415111929.png)\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230415111950.png)\nͼߵĻspringbootڹӦãҲǳ˵΢\n\nspringcloudڷҲǴЭ΢springbootӦó\n\nspringcloudаڶصAPISpringcloud GatewayConfig Serever·Circuit BreakerעService RegisttySleuth\n\n# ʹSpringboot8ԭ\n\n## Ŀ\n\nSpring Boot  Spring ̬ϵͳ˺ܶ͹ִĬá ڿԱòͶ뿪\n\n磬Spring MVC ͨ XML bean Զ servlet ʵ֡ ʹ Spring Bootһ򵥡 ԲҪ XML á\n\n## һнȻ\n\nSpring Boot Starters ǰǵһЩԶõ Maven  ңЩΪ Spring Boot Ӧóṩܡ Ҫݿӣ һ Ϣͨ͵ʼ Spring Boot һС\n\nڼе Spring ģ飬һΪݡ һЩҲͨǵģṩ Spring ֧֡ ûЩΪԱòά XML á Ӧʹ Spring Boot һԭ\n\n## Ƕʽ\n\nSpring Boot ΪǶʽ TomcatJetty  Undertow ṩ伴õ֧֡ ԱͲصڴͳӦ÷в Web Ӧó ͨʵԽһַ ʵյõһκ JAR һе JAR ļ ʱJAR 㹻ĿΪӦó\n\nǶʽȤʱ Spring Boot Ӧó JAR תΪ WARǲ𵽴ͳ\n\n## IDE  Spring Boot ֧\n\nҪ IDE ṩ Spring Boot ֧֡ 磬IntelliJ IDEA Ultimate Ϊ Spring Boot Ŀṩ˳ɫĴɺ͵ܡ֮⣬VSCode  Eclipse Ҳṩ˷ḻĹ֧֡\n\n## ù\n\nSpring Boot ṩԣءָͿ伴õע ЩԣԱԱá 磬ִ˵ȹʹӦó״̬سΪܡ 磬\n\n Prometheus ĹռӦóָ\n\n Kubernetes  Openshift ʹþԺͻԾȽ˵㡣\n\nֻԻͨ /actuator/logging ˵㼴ɸ־¼\n\n⣬ԱʹԼԶ彡˵Щִ˵㡣\n\n## 伴õ JUnit ֧\n\nĬ£ Spring Boot Ŀ JUnit 5 ⣬Spring Boot ṩ@SpringBootTest עҪʱʼġ ԿԱֻҪд ǲٵĲĸ spring ġ\n\n磬ԶɵĲԽǷȷء\n\n````\n@SpringBootTest\nclass SpringBootDerbyDatabaseApplicationTests {\n\n     @\n     void contextLoads() {\n     }\n\n}\n````\n\n## Spring Profiles\n\nSpring Profiles  spring Boot һǿԣڸӦóеĲͬ ʹļضû ضʹòͬʱܻó\n````\n@Profile(value = {\"prod\",\"uat\"})\nclass RabbitMQConfig {\n\n// \n\n}\n````\n\nĴУĽھ prod  uat ΪļĻС\n\n## ִͲѡ\n\nÿṩ˶ַʽӦó ֮ǰ˵Ӧó JAR  WAR ļ ͨһЩúͲԴ伴õĸ docker \n\nֹͣ Spring Boot Ӧóǳ򵥡 ⣬ͨĲ轫Щ JAR ļΪ linux  JAR ļΪ FAT jarǰӦóص ʹò̲ôӡ ʵϣЩκװ Java 8 ߰汾ĻС\n\n# ο\n\n[https://spring.io/](https://spring.io/)\n\n[https://pdai.tech/md/spring/springboot/springboot-x-overview.html](https://pdai.tech/md/spring/springboot/springboot-x-overview.html)\n\n[https://springhow.com/why-use-spring-boot/](https://springhow.com/why-use-spring-boot/)\n\n[https://dzone.com/articles/why-springboot](https://dzone.com/articles/why-springboot)\n\n[https://scand.com/company/blog/pros-and-cons-of-using-spring-boot/](https://scand.com/company/blog/pros-and-cons-of-using-spring-boot/)\n\n[https://cloud.tencent.com/developer/article/1620255](https://cloud.tencent.com/developer/article/1620255)\n\n[https://www.yiibai.com/spring-boot/spring-vs-spring-boot-vs-spring-mvc.html](https://www.yiibai.com/spring-boot/spring-vs-spring-boot-vs-spring-mvc.html)"
  },
  {
    "path": "docs/Spring全家桶/SpringBoot/SpringBoot的基本使用.md",
    "content": "# ٹSpringBootӦ\n\n̳springboot㹻ȨҲ㹻򵥡\n\n##  һ hello world \n\nһġHello WorldκӵĶ˵㡣Ը֣ԸѺõķʽӦ\n\n## Ҫ\n\n1һֵIDE,ѡ IntelliJ IDEASpring ToolsVisual Studio Code  Eclipse ȵȡ\n\n2JDKڰ汾Ļ8-17ǲѡ\n\n3ȻﻹҪmavenpomҲҪmavenmavenideaԴˡǻڽĲֽнܣҪЩ\n\n## һһµSpring BootĿ\n\nʹ[start.spring.io](http://start.spring.io/)һwebĿڡdependenciesԻӡwebĻͼʾ\n\nɡť zip ļѹϵһļС\n\n![Start.spring.io ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/quickstart-1.png)\n\n[start.spring.io](http://start.spring.io/)Ŀ[Spring Boot](https://spring.io/projects/spring-boot)һSpring׼ӦóڲҪ̫á Spring Boot  Spring Ŀеķʽ\n\nѡʹmavenΪߣĿpomļpomļӵ\n\n````  \n<?xml version=\"1.0\" encoding=\"UTF-8\"?>  \n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"  \n   xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd\">   <modelVersion>4.0.0</modelVersion>   <parent>      <groupId>org.springframework.boot</groupId>      <artifactId>spring-boot-starter-parent</artifactId>      <version>3.0.5</version>      <relativePath/> <!-- lookup parent from repository -->   </parent>   <groupId>com.example</groupId>   <artifactId>demo</artifactId>   <version>0.0.1-SNAPSHOT</version>   <name>demo</name>   <description>Demo project for Spring Boot</description>   <properties>      <java.version>17</java.version>   </properties>   <dependencies>      <dependency>         <groupId>org.springframework.boot</groupId>         <artifactId>spring-boot-starter-web</artifactId>      </dependency>  \n      <dependency>         <groupId>org.springframework.boot</groupId>         <artifactId>spring-boot-starter-test</artifactId>         <scope>test</scope>      </dependency>   </dependencies>  \n   <build>      <plugins>         <plugin>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-maven-plugin</artifactId>         </plugin>      </plugins>   </build>  \n</project>  \n````  \n\n\n## ڶĴ\n\n IDE дĿ `src/main/java/com/example/demo` ļҵ `DemoApplication.java` ļ\n\nͨʾĶⷽעļݡԸƲճֱӼ롣\n```  \npackage com.example.demo;  \nimport org.springframework.boot.SpringApplication;  \nimport org.springframework.boot.autoconfigure.SpringBootApplication;  \nimport org.springframework.web.bind.annotation.GetMapping;  \nimport org.springframework.web.bind.annotation.RequestParam;  \nimport org.springframework.web.bind.annotation.RestController;  \n  \n@SpringBootApplication  \n@RestController  \npublic class DemoApplication {  \n    public static void main(String[] args) {      SpringApplication.run(DemoApplication.class, args);    }    @GetMapping(\"/hello\")    public String hello(@RequestParam(value = \"name\", defaultValue = \"World\") String name) {      return String.format(\"Hello %s!\", name);    }}  \n  \n```  \n\n Spring Boot дһ򵥵ġHello World Web д롣\n\nӵ`hello()`ּڻȡһΪnameStringȻ󽫴˲еĵ`\"Hello\"`\n\nζнΪAmyӦǡHello Amy\n\n`@RestController` ע͸ Spring˴һ˵㣬ö˵Ӧ Web Ͽá   \n@GetMapping(/hello)  Spring ʹǵ hello() Ӧ͵ http://localhost:8080/hello ַ\n\n@RequestParam  Spring һֵڣĬʹõʡWorld\n\n## \n\nǹг򡣴УնˣӵĿļļС\n\nǿͨӦó\n\n**MacOS/Linux:**\n\n```  \nCOPY./gradlew bootRun  \n  \n```  \n\n**Windows:**\n\n```  \nCOPY.\\gradlew.bat bootRun  \n  \n```  \n\nӦûῴһЩ˷ǳƵ  \n![Quick Start On Start.spring.io](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/quickstart-2.png)\n\nиǣSpringӦѾʼˡ Spring Boot Ƕʽ Apache Tomcat 䵱ڼlocalhost˿ڡ8080ϵ\n\nڶĵַ`http://localhost:8080/hello`\n\nӦõõһѺõĻӦ  \n![Quick Start On Start.spring.io](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/quickstart-3.png)\n\n# ܽ\n˼򵥣һSpringBootӦþôˣ㲻ҪļǶķ  \nֻҪһ࣬ͿʵһSpringBootӦá\n\nҲΪʲôspringbootٹһ΢Ϊʵ̫ˡ  \nȻʵʿҪõspringbootĹܺԣǽڽ½չܡ\n\n# ο\nhttps://spring.io/quickstart"
  },
  {
    "path": "docs/Spring全家桶/SpringBoot/SpringBoot的配置文件管理.md",
    "content": "## 1.SpringBootùı\n\nΪʵֿٴͿĿSpringbootܴspringbootĿԽĿֱӴjarУԼװTomcatһַݵĲʽ\n\nĿķʽһjar𣬼ļ͵jarͻ\n\nһĿйУҪĶļĻҪ´\n\nĿҪͬһ̨ʱԵjarͬĵĿjar100Mܾռ99M˷Դ˷ԼĿЧʡ\n\nĿļȡjarͳһĿЧֽԼ˷ĴģͬʱĿάҲǷǳģĶļ·Ϳˣ¹\n\nǾʵַ\n\n### 1.1 **ļͳһ**\n\n```\n\t- springbootļ\n\t- Springbootȡļapplication.propertiesȼΪ\n\t- JarͬĿ¼configĿ¼\n\t- JarͬĿ¼\n\t- classPath(resourcesĿ¼)configĿ¼\n\t- classpathĿ¼\n\n```\n\nspringbootĬȥԼĺļȼһȼķʽĿʱͨķʽָĿغļ\njava Cjar -Dspring.config.location=xxx/xxx/xxxx.properties xxxx.jar\n\nSpring Bootȼߵλҵãôȼ͵\n\n### 1.2 **Դļ**\n\nSpringbootļѾܹȡjarйˣǻһЩҵϵļԴļԴļFTPϢȣquartzʱ־ļȥȡȷڴõ\n\n֪SpringbootĿͨעⷽʽȡļҲͨעⷽʽĿܹõjarⲿļģͼ\n![ͼƬ](https://img-blog.csdnimg.cn/20190121170400864.png)\n\n@PropertySourcevalueֵһclasspathconfigĿ¼µԴļڶǸspring.profiles.path̬ȡĿ¼spring.profiles.pathںļԶһֵļͳһļ·ignoreResourceNotFound=true趨ǰһ·ûҵļݵڶ·ȥҡ\n\nǻֱӸ·FileSystemResourceȥһļʵͼ\n![ͼƬ](https://img-blog.csdnimg.cn/20190121170338934.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0dlZUxvb25n,size_16,color_FFFFFF,t_70)\n\nԭƣںļԶͳһĿ¼·ļ\n\nlogback־ļطʽ£\n![ͼƬ](https://img-blog.csdnimg.cn/20190121170306979.png)\n\nһʵַ˼·\n\n```\n\t- springbootļﶨһspring.profiles.pathֵָļͳһõĿ¼ļҲǷ\n\n\t- ļļĵطҲӦûȡspring.profiles.path̬ظ·µļ\n\n\t- Pom.xmlļ޸Ĵģ飬ļųǴjarǲļģοĵڵ3\n\n\t- jarʱָͨصĺļΪspring.profiles.pathµĺļ\n\n```\n**ͳһ**\n\nͨjarԴjarҲԷĿjarͬĿ¼µlibĿ¼ǿԸ޸pom.xmlʵ֣οĵڵ3\n\n****\n\n```\n<build>\n\t\t<resources>\n\t\t\t<resource>\n\t\t\t\t<directory>src/main/java</directory>\n\t\t\t\t<includes>\n\t\t\t\t\t<include>**/*.properties</include>\n\t\t\t\t\t<include>**/*.xml</include>\n\t\t\t\t</includes>\n\t\t\t\t<filtering>true</filtering>\n\t\t\t</resource>\n\t\t\t<resource>\n\t\t\t\t<directory>src/main/resources</directory>\n\t\t\t\t<!ʱųļ-->\n                                <excludes>\n\t\t\t\t\t<exclude>**/*.properties</exclude>\n\t\t\t\t\t<exclude>**/*.xml</exclude>\n\t\t\t\t\t<exclude>**/*.yml</exclude>\n\t\t\t\t</excludes>\n\t\t\t\t<filtering>false</filtering>\n\t\t\t</resource>\n\t\t</resources>\n\t\t<plugins>\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.apache.maven.plugins</groupId>\n\t\t\t\tmaven-compiler-plugin\n\t\t\t\t<configuration>\n\t\t\t\t\t<source>1.8</source>\n\t\t\t\t\t<target>1.8</target>\n\t\t\t\t\t<fork>true</fork>\n\t\t\t\t\t<skip>true</skip>\n\t\t\t\t\t<executable>\n\t\t\t\t\t\tC:/Program Files/Java/jdk1.8.0_161/bin/javac.exe\n\t\t\t\t\t</executable>\n\t\t\t\t</configuration>\n\t\t\t</plugin>\n\t\t\t<plugin>\n\t\t\t\tmaven-jar-plugin\n\t\t\t\t<configuration>\n\t\t\t\t\t\n\t\t\t\t\t\t<manifest>\n\t\t\t\t\t\t\ttrue\n\t\t\t\t\t\t\t<classpathPrefix>lib/</classpathPrefix>\n\t\t\t\t\t\t\t<useUniqueVersions>false</useUniqueVersions>\n\t\t\t\t\t\t\t<mainClass>com.xrq.demo.Application</mainClass>\n\t\t\t\t\t\t</manifest>\n\t\t\t\t\t\t<manifestEntries>\n\t\t\t\t\t\t\t<Class-Path>./</Class-Path>\n\t\t\t\t\t\t</manifestEntries>\n\t\t\t\t\t\n\t\t\t\t\t<excludes>\n\t\t\t\t\t\t<exclude>*.properties</exclude>\n\t\t\t\t\t\t<exclude>*.yml</exclude>\n\t\t\t\t\t\t<exclude>*.xml</exclude>\n\t\t\t\t\t\t<exclude>config/**</exclude>\n\t\t\t\t\t</excludes>\n\t\t\t\t</configuration>\n\t\t\t</plugin>\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.apache.maven.plugins</groupId>\n\t\t\t\tmaven-dependency-plugin\n\t\t\t\t<executions>\n\t\t\t\t\t<execution>\n\t\t\t\t\t\t<id>copy</id>\n\t\t\t\t\t\t<phase>package</phase>\n\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t<goal>copy-dependencies</goal>\n\t\t\t\t\t\t</goals>\n\t\t\t\t\t\t<configuration>\n\t\t\t\t\t\t\t<outputDirectory>\n\t\t\t\t\t\t\t\t${project.build.directory}/lib\n\t\t\t\t\t\t\t</outputDirectory>\n\t\t\t\t\t\t</configuration>\n\t\t\t\t\t</execution>\n\t\t\t\t</executions>\n\t\t\t</plugin>\n\t\t</plugins>\n\t</build>\n\n```\n\nĺpom.xmlbuildģ󣬾Ϳͨmvn package mvn installǵjar\n\n1.  **Ŀshellűд**\n    ԶshellűʵĿֹͣ״̬\n\n```\n#!/bin/bash \n#滻ΪԼִг, \nAPP_NAME=demo1-0.0.1-SNAPSHOT.jar \nJVM=\"-server -Xms512m -Xmx512m -XX:PermSize=64M -XX:MaxNewSize=128m -XX:MaxPermSize=128m -Djava.awt.headless=true -XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled\"\nAPPFILE_PATH=\"-Dspring.config.location=/usr/local/demo/config/application-demo1.properties\"\n#ʹ˵,ʾ \nusage() { \necho \"Usage: sh ִнű.sh [start|stop|restart|status]\" \nexit 1 \n} \n#Ƿ \nis_exist(){ \npid=`ps -ef|grep $APP_NAME|grep -v grep|awk '{print $2}' ` \n#ڷ1,ڷ0 \nif [ -z \"${pid}\" ]; then \nreturn 1 \nelse \nreturn 0 \nfi \n} \n# \nstart(){ \nis_exist \nif [ $? -eq \"0\" ]; then \necho \"${APP_NAME} is already running. pid=${pid} .\" \nelse \nnohup java $JVM -jar $APPFILE_PATH $APP_NAME > /dev/null 2>&1 \nfi\n} \n#ֹͣ \nstop(){ \nis_exist \nif [ $? -eq \"0\" ]; then \nkill -9 $pid \nelse \necho \"${APP_NAME} is not running\" \nfi \n} \n#״̬ \nstatus(){ \nis_exist \nif [ $? -eq \"0\" ]; then \necho \"${APP_NAME} is running. Pid is ${pid}\" \nelse \necho \"${APP_NAME} is NOT running.\" \nfi \n} \n# \nrestart(){ \nstop \nstart \n} \n#,ѡִжӦ,ִʹ˵ \ncase \"$1\" in \n\"start\") \nstart \n;; \n\"stop\") \nstop \n;; \n\"status\") \nstatus \n;; \n\"restart\") \nrestart \n;; \n*) \nusage \n;; \nesac\n\n```\n\n****\n\nlinux½ļУǴõĿjarȥjarͬĿ¼½configlibļУֱļ͵ȥṹͼ*.shΪԼдĿshellű\n\n![ͼƬ](https://img-blog.csdnimg.cn/20190121170223384.png)\n\nconfigڵspringbootļapplication-demo1.propertiesļ\n\nspring.profiles.pathĳɵǰļڵĿ¼Ϊ/usr/local/demo/config\n\n*.shű޸APPFILE_PATHֵ\n\nAPPFILE_PATH=\"-Dspring.config.location=/usr/local/demo/config/application-demo1.properties\"\n\n**Ŀ**\n\njarĿ¼ִ\n\nsh [demo1.sh](http://demo1.sh/) start Ŀ\n\nsh [demo1.sh](http://demo1.sh/) stop ֹͣĿ\n\nsh [demo1.sh](http://demo1.sh/) restartĿ\n\nsh [demo1.sh](http://demo1.sh/) statusĿ״̬\n\n## 2\\. ⲿ\n\n\n\n\n\nSpring Boot㽫ⲿͿڲͬĻʹͬӦó롣 ʹøⲿԴJava properties ļYAMLļв\n\n\n\n\n\nֵͨʹ `@Value` עֱעBeanҲͨSpring  `Environment` ʣͨ `@ConfigurationProperties` [󶨵](https://springdoc.cn/spring-boot/features.html#features.external-config.typesafe-configuration-properties)\n\n\n\n\n\nSpring Boot ʹһǳر `PropertySource` ˳ּдֵ  property source ԸǰԴжֵ ˳ǡ\n\n\n\n\n\n1.  Ĭԣͨ `SpringApplication.setDefaultProperties` ָ\n\n2.  @Configuration ϵ [`@PropertySource`](https://docs.spring.io/spring-framework/docs/6.0.5/javadoc-api/org/springframework/context/annotation/PropertySource.html) ע⡣ע⣬Դֱapplication contextˢʱŻᱻӵСĳЩ˵Ѿ̫ˣ `logging.*`  `spring.main.*` ˢ¿ʼǰѾȡˡ\n\n3.  ݣ `application.properties` ļ\n\n4.  `RandomValuePropertySource`ֻ `random.*` ԡ\n\n5.  ϵͳ\n\n6.  Java System properties (`System.getProperties()`).\n\n7.  `java:comp/env` е JNDI ԡ\n\n8.  `ServletContext` init parameters.\n\n9.  `ServletConfig` init parameters.\n\n10.   `SPRING_APPLICATION_JSON` ԣǶ뻷ϵͳеJSON\n\n11.  в\n\n12.  ڲе `properties` ԡ [`@SpringBootTest`](https://docs.spring.io/spring-boot/docs/3.1.0-SNAPSHOT/api/org/springframework/boot/test/context/SpringBootTest.html) Ͳעпã[ڲӦóһضƬ](https://springdoc.cn/spring-boot/features.html#features.testing.spring-boot-applications.autoconfigured-tests)\n\n13.  еhttps://docs.spring.io/spring-framework/docs/6.0.5/javadoc-api/org/springframework/test/context/TestPropertySource.html[`@TestPropertySource`] ע.\n\n14.  devtoolsڻ״̬ʱ`$HOME/.config/spring-boot` Ŀ¼µ[Devtoolsȫ](https://springdoc.cn/spring-boot/using.html#using.devtools.globalsettings)\n\n\n\n\n\nļ˳ǡ\n\n\n\n\n\n1.  jarд[Application properties](https://springdoc.cn/spring-boot/features.html#features.external-config.files)application.properties  YAML\n\n2.  jarд [ض Profile application properties](https://springdoc.cn/spring-boot/features.html#features.external-config.files.profile-specific)`application-{profile}.properties`  YAML\n\n3.  jar֮[Application properties](https://springdoc.cn/spring-boot/features.html#features.external-config.files)ԣapplication.propertiesYAML\n\n4.  jar֮[ض Profile application properties](https://springdoc.cn/spring-boot/features.html#features.external-config.files.profile-specific) `application-{profile}.properties` YAML\n\n\n\n\n\n|  | Ӧóмʹһָʽ ͬһط `.properties`  `.yml` ʽļ`.properties` ȡ |\n| --- | --- |\n\n\n\n\n\nΪṩһӣ㿪һ `@Component`ʹһ `name` ԣʾ\n\n\n\n\n\n\n\nJava\n\nKotlin\n\n\n\n\n\n```\n@Component\npublic class MyBean {\n\n    @Value(\"${name}\")\n    private String name;\n\n    // ...\n\n}\n\n```\n\n\n\n\n\n\n\nӦóclasspath磬jarУһ `application.properties` ļΪ `name` ṩһĬֵһµĻʱjar֮ṩһ `application.properties` ļ `name` һԵĲԣһضв磬`java -jar app.jar --name=\"Spring\"`\n\n\n\n\n\n|  | `env`  `configprops` ˵ȷһΪʲôһضֵʱǳáʹ˵ֵ \"[](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints)\" ֡ |\n| --- | --- |\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/features.html#features.external-config.command-line-args)2.1\\. \n\n\n\nĬ£`SpringApplication` Ὣκѡ `--` ͷĲ `--server.port=9000` תΪ `property` ӵSpring `Environment` С ǰڻļԴ\n\n\n\n\n\n㲻ϣԱӵ `Environment` Уͨ `SpringApplication.setAddCommandLineProperties(false)` ǡ\n\n\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/features.html#features.external-config.application-json)2.2\\. JSON Application Properties\n\n\n\nϵͳƣζЩƲʹá Ϊ˰⣬Spring Boot㽫һԿΪһһJSONṹ\n\n\n\n\n\nӦóʱκ `spring.application.json`  `SPRING_APPLICATION_JSON` Խӵ `Environment` С\n\n\n\n\n\n磬`SPRING_APPLICATION_JSON` Կ UN*X shell Ϊṩ\n\n\n\n\n\n\n\n```\n$ SPRING_APPLICATION_JSON='{\"my\":{\"name\":\"test\"}}' java -jar myapp.jar\n```\n\n\n\n\n\n\n\nǰУSpring `Environment` յõ `my.name=test`\n\n\n\n\n\nͬJSONҲΪһϵͳṩ\n\n\n\n\n\n\n\n```\n$ java -Dspring.application.json='{\"my\":{\"name\":\"test\"}}' -jar myapp.jar\n```\n\n\n\n\n\n\n\nͨʹһвṩJSON\n\n\n\n\n\n\n\n```\n$ java -jar myapp.jar --spring.application.json='{\"my\":{\"name\":\"test\"}}'\n```\n\n\n\n\n\n\n\nҪһӦ÷УҲʹһΪ `java:comp/env/spring.application.json` JNDI\n\n\n\n\n\n|  | JSONе `null` ֵӵɵԴУ `PropertySourcesPropertyResolver`  `null` Ϊȱʧֵ ζJSON `null` ֵԵͽԴԡ |\n| --- | --- |\n\n\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/features.html#features.external-config.files)2.3\\. ⲿ Application Properties\n\n\n\nӦóʱSpring BootԶλҵ `application.properties`  `application.yaml` ļ\n\n\n\n\n\n1.  classpath\n\n\n\n    1.  classpath ·\n\n    2.  classpath µ `/config` \n\n\n\n2.  ǰĿ¼\n\n\n\n    1.  ǰĿ¼\n\n    2.  ǰĿ¼µ `config/` Ŀ¼\n\n    3.  `config/` Ŀ¼ֱĿ¼\n\n\n\n\n\n\n\nбȼ򣨽ϵĿֵǽĿֵ صļΪ `PropertySources` ӵSpring `Environment` С\n\n\n\n\n\n㲻ϲ `application` Ϊļƣָͨ `spring.config.name` лһļơ 磬ΪѰ `myproject.properties`  `myproject.yaml` ļ԰·ʽӦó\n\n\n\n\n\n\n\n```\n$ java -jar myproject.jar --spring.config.name=myproject\n```\n\n\n\n\n\n\n\nҲͨʹ `spring.config.location` һȷλá ԽһŷָбаһҪλá\n\n\n\n\n\nʾָͬļ\n\n\n\n\n\n\n\n```\n$ java -jar myproject.jar --spring.config.location=\\\n    optional:classpath:/default.properties,\\\n    optional:classpath:/override.properties\n```\n\n\n\n\n\n\n\n|  |  [ļǿѡ](https://springdoc.cn/spring-boot/features.html#features.external-config.files.optional-prefix)ҿǲڵģôʹ `optional:` ǰ׺ |\n| --- | --- |\n\n\n\n\n\n|  | `spring.config.name`, `spring.config.location`,  `spring.config.extra-location` ȷЩļ뱻ء Ǳ뱻Ϊԣͨǲϵͳϵͳԣв |\n| --- | --- |\n\n\n\n\n\n `spring.config.location` Ŀ¼ļӦ `/` β ʱǽ `spring.config.name` ɵƣȻ󱻼ء  `spring.config.location` ָļֱӵ롣\n\n\n\n\n\n|  | Ŀ¼ļλֵҲչԼ[ضļ](https://springdoc.cn/spring-boot/features.html#features.external-config.files.profile-specific)磬 `spring.config.location`  `classpath:myconfig.properties`Ҳᷢʵ `classpath:myconfig-<profile>.properties` ļء |\n| --- | --- |\n\n\n\n\n\nڴ£ӵÿ `spring.config.location` һļĿ¼ λǰǱ˳ģλÿԸǰλõֵ\n\n\n\n\n\nһӵλãʹضļҪṩһʾԱSpring Boot֪Ӧη顣һλһλõļϣЩλöΪͬһ磬classpathλ÷飬ȻⲿλáһλڵĿӦ `;` ָϸڼ [ָ profile](https://springdoc.cn/spring-boot/features.html#features.external-config.files.profile-specific) ֵӡ\n\n\n\n\n\nͨʹ `spring.config.location` õλȡĬλá 磬 `spring.config.location` Ϊ `optional:classpath:/custom-config/,optional:file:./custom-config/` ǵλü¡\n\n\n\n\n\n1.  `optional:classpath:custom-config/`\n\n2.  `optional:file:./custom-config/`\n\n\n\n\n\nϲӶλã滻ǣʹ `spring.config.extra-location`  ӸλüصԿԸĬλõԡ 磬 `spring.config.extra-location` Ϊ `optional:classpath:/custom-config/,optional:file:./custom-config/` ǵλü¡\n\n\n\n\n\n1.  `optional:classpath:/;optional:classpath:/config/`\n\n2.  `optional:file:./;optional:file:./config/;optional:file:./config/*/`\n\n3.  `optional:classpath:custom-config/`\n\n4.  `optional:file:./custom-config/`\n\n\n\n\n\nһļָĬֵȻһļѡԵظЩֵ һĬλõ `application.properties`  `spring.config.name` ѡbasenameΪӦóṩĬֵ ȻЩĬֵʱλһԶλõĲͬļǡ\n\n\n\n\n\n|  | ʹûϵͳԣϵͳʹþָļʹ»ߴ棨磬 `SPRING_CONFIG_NAME`  `spring.config.name`  μ[ӻ](https://springdoc.cn/spring-boot/features.html#features.external-config.typesafe-configuration-properties.relaxed-binding.environment-variables) ˽顣 |\n| --- | --- |\n\n\n\n\n\n|  | ӦóservletӦ÷УôJNDIԣ `java:comp/env` УservletĳʼԴ滷ϵͳԣ֮һ |\n| --- | --- |\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/features.html#features.external-config.files.optional-prefix)2.3.1\\. ѡλ(Optional Locations)\n\n\n\nĬ£ָλòʱSpring Boot׳һ `ConfigDataLocationNotFoundException` Ӧó޷\n\n\n\n\n\nָһλã㲻Ǵڣʹ `optional:` ǰ׺ `spring.config.locationspring.config.extra-location` ʹǰ׺Ҳ [`spring.config.import`](https://springdoc.cn/spring-boot/features.html#features.external-config.files.importing) ʹá\n\n\n\n\n\n磬`spring.config.import` ֵΪ `optional:file:./myconfig.properties` Ӧóʹ `myconfig.properties` ļʧ\n\n\n\n\n\nе `ConfigDataLocationNotFoundExceptions` ʼռӦóʹ `spring.config.on-not-found` ԡ ʹ `SpringApplication.setDefaultProperties(..)` ʹϵͳ/ֵΪ `ignore`\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/features.html#features.external-config.files.wildcard-locations)2.3.2\\. ַͨ\n\n\n\nһļλһ·а `*` ַͱΪһͨλá ͨڼʱչˣֱӵĿ¼Ҳ顣 ͨλKubernetesжԵԴĻرá\n\n\n\n\n\n磬һЩRedisúһЩMySQLã÷ֿͬʱҪֶһ `application.properties` ļС\n\n\n\n\n\nܻᵼ `application.properties` ļڲͬλã `/config/redis/application.properties`  `/config/mysql/application.properties`  £һͨλ `config/*/` ļ\n\n\n\n\n\nĬ£Spring Boot `config/*/` Ĭλá ζjar֮ `/config` Ŀ¼Ŀ¼ᱻ\n\n\n\n\n\n `spring.config.location`  `spring.config.extra-location` ʹͨλá\n\n\n\n\n\n|  | ͨλñֻһ `*`  `*/` βĿ¼λã `*/<filename>` ļλá ͨλýļľ·ĸ˳ |\n| --- | --- |\n\n\n\n\n\n|  | ͨλֻⲿĿ¼á 㲻 `classpath:` λʹͨ |\n| --- | --- |\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/features.html#features.external-config.files.profile-specific)2.3.3\\. ضļProfile Specific Files\n\n\n\n `application` ļSpring Bootʹ `application-{profile}` profileضļ 磬Ӧó򼤻Ϊ `prod` ļ`spring.profiles.active=prod`ʹYAMLļô `application.yml`  `application-prod.yml` ǡ\n\n\n\n\n\nضļ(`profiles`)׼ `application.properties` λͬضļڷضļ ָ˼ļʤĲԡ 磬ļ `prod,live`  `spring.profiles.active` ָģ`application-prod.properties` еֵԱ `application-live.properties` еֵǡ\n\n\n\n\n\n|  | ʤĲ[location group](https://springdoc.cn/spring-boot/features.html#features.external-config.files.location-groups) `spring.config.location`  `classpath:/cfg/,classpath:/ext/`  `classpath:/cfg/;classpath:/ext/` ͬĸǹ磬 `prod,live` ˵ǿļ /cfg  application-live.properties/ext  application-live.properties  application-prod.properties һ `spring.config.location` Ϊ `classpath:/cfg/,classpath:/ext/` ʱǻ `/ext` ļ֮ǰ `/cfg` ļ1.  `/cfg/application-live.properties`        2.  `/ext/application-prod.properties`        3.  `/ext/application-live.properties`         `classpath:/cfg/;classpath:/ext/` ʱ `;` ָͬһ `/cfg`  `/ext` 1.  `/ext/application-prod.properties`        2.  `/cfg/application-live.properties`        3.  `/ext/application-live.properties`         |\n| --- | --- |\n\n\n\n\n\n`Environment` һĬϵļĬΪ `[default]` ûûļͻʹЩļ 仰˵ûȷļôͻῼ `application-default` ԡ\n\n\n\n\n\n|  | ļֻһΡ Ѿֱ[](https://springdoc.cn/spring-boot/features.html#features.external-config.files.importing)һļضļôᱻڶε롣 |\n| --- | --- |\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/features.html#features.external-config.files.importing)2.3.4\\. \n\n\n\napplication properties пʹ `spring.config.import` Դطݡ ڱʱΪļĶļ\n\n\n\n\n\n磬 classpath `application.properties` ļݡ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nspring.application.name=myapp\nspring.config.import=optional:file:./dev.properties\n\n```\n\n\n\n\n\n\n\n⽫뵱ǰĿ¼µ `dev.properties` ļļ  `dev.properties` еֵڴļ У`dev.properties` Խ `spring.application.name` ¶Ϊһֵͬ\n\n\n\n\n\nһֻᱻһΣٴΡ һproperties/yamlļڵĵļб˳򲢲Ҫ 磬ӲͬĽ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nspring.config.import=my.properties\nmy.property=value\n\n```\n\n\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmy.property=value\nspring.config.import=my.properties\n\n```\n\n\n\n\n\n\n\nУ`my.properties` ļֵڴ䵼ļ\n\n\n\n\n\nһһ `spring.config.import` ¿ָλá λýǱ˳򱻴ĵ뽫ȴ\n\n\n\n\n\n|  | ʵʱ[ضļı](https://springdoc.cn/spring-boot/features.html#features.external-config.files.profile-specific)Ҳǵ롣 ӽ `my.properties` Լκ `my-<profile>.properties` 塣 |\n| --- | --- |\n\n\n\n\n\n|  | Spring Boot ṩ˿ɲεAPIֲָ֧ͬλõַ Ĭ£ԵJava PropertiesYAML [](https://springdoc.cn/spring-boot/features.html#features.external-config.files.configtree) jarṩ֧֣ҪǱļ 磬ⲿ洢ConsulApache ZooKeeperNetflix ArchaiusNacos֧ԼλãʵԼüأ `org.springframework.boot.context.config` е `ConfigDataLocationResolver`  `ConfigDataLoader` ࡣ |\n| --- | --- |\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/features.html#features.external-config.files.importing-extensionless)2.3.5\\. չļ\n\n\n\nЩƽ̨Ϊװļvolume mounted filesļչ ҪЩչļҪSpring BootһʾԱ֪μǡ ͨչʾڷһ㡣\n\n\n\n\n\n磬һ `/etc/config/myconfig` ļϣyamlʽ롣 ķ `application.properties` е\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nspring.config.import=file:/etc/config/myconfig[.yaml]\n\n```\n\n\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/features.html#features.external-config.files.configtree)2.3.6\\. ʹConfiguration Trees\n\n\n\nƽ̨KubernetesӦóʱ㾭Ҫȡƽ̨ṩֵ ڴĿĲټȱ㣬رֵ secret ġ\n\n\n\n\n\nΪƽ̨㽫ӳ䵽صݾ 磬Kubernetes Ծ [`ConfigMaps`](https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/#populate-a-volume-with-data-stored-in-a-configmap)  [`Secrets`](https://kubernetes.io/docs/concepts/configuration/secret/#using-secrets-as-files-from-a-pod)\n\n\n\n\n\nʹֳ volume ģʽ\n\n\n\n\n\n1.  һļһԣͨдYAML\n\n2.  ļдһĿ¼УļΪ keyݳΪ value\n\n\n\n\n\nڵһʹ `spring.config.import` ֱӵYAMLļ[](https://springdoc.cn/spring-boot/features.html#features.external-config.files.importing) ڵڶҪʹ `configtree:` ǰ׺ԱSpring Boot֪ҪļΪԹ\n\n\n\n\n\nٸӣһ£KubernetesѾvolume\n\n\n\n\n\n\n\n etc/\n  config/\n    myapp/\n      username\n      password \n\n\n\n\n\n\n\n`username` ļݽһֵ `password` ݽһ secret\n\n\n\n\n\nҪЩԣ `application.properties`  `application.yaml` ļݡ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nspring.config.import=optional:configtree:/etc/config/\n\n```\n\n\n\n\n\n\n\nȻԴ `Environment` Գ淽ʽʻע `myapp.username`  `myapp.password` ԡ\n\n\n\n\n\n|  | µļйơ УΪ˷Ϊ `username`  `password`Խ `spring.config.import` Ϊ `optional:configtree:/etc/config/myapp`  |\n| --- | --- |\n\n\n\n\n\n|  | еŵļҲᱻȷӳ䡣 磬У`/etc/config` Ϊ `myapp.username` ļ `Environment` е `myapp.username`  |\n| --- | --- |\n\n\n\n\n\n|  | ֵԱ󶨵ַ `String`  `byte[]` ͣȡԤڵݡ |\n| --- | --- |\n\n\n\n\n\nжҪͬһļе룬ʹͨݷʽ κ `/*/` β `configtree:` λýֱӵļΪ\n\n\n\n\n\n磬volume\n\n\n\n\n\n\n\n etc/\n  config/\n    dbconfig/\n      db/\n        username\n        password\n    mqconfig/\n      mq/\n        username\n        password \n\n\n\n\n\n\n\nʹ `configtree:/etc/config/*/` Ϊλá\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nspring.config.import=optional:configtree:/etc/config/*/\n\n```\n\n\n\n\n\n\n\n⽫ `db.username``db.password``mq.username`  `mq.password` ԡ\n\n\n\n\n\n|  | ʹͨصĿ¼ǰĸ˳еġ Ҫһͬ˳ôӦðÿλΪһĵг |\n| --- | --- |\n\n\n\n\n\nҲDocker secret Docker swarmsecretķȨʱsecretᱻװصС 磬һΪ `db.password` secret `/run/secrets/` λã· `db.password` Springá\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nspring.config.import=optional:configtree:/run/secrets/\n\n```\n\n\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/features.html#features.external-config.files.property-placeholders)2.3.7\\. ռλ\n\n\n\n`application.properties`  `application.yml` еֵʹʱͨе `Environment` ˣԲοǰֵ磬ϵͳԻ򻷾 ׼ `${name}` ռλ﷨һֵκεط ռλҲָһĬֵʹ `:` ָĬֵƣ `${name:default}` \n\n\n\n\n\nʾ˴ĬֵͲĬֵռλʹ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\napp.name=MyApp\napp.description=${app.name} is a Spring Boot application written by ${username:Unknown}\n\n```\n\n\n\n\n\n\n\n `username` ûطã`app.description` ֵ `MyApp is a Spring Boot application written by Unknown`\n\n\n\n\n\n|  | ӦʼʹռλеƵĹ淶ʽʹСдĸkebab-caseǡ ⽫Spring Bootʹ[ɰ](https://springdoc.cn/spring-boot/features.html#features.external-config.typesafe-configuration-properties.relaxed-binding) `@ConfigurationProperties` ʱͬ߼磬`${demo.item-price}`  `application.properties` ļлȡ `demo.item-price`  `demo.itemPrice` ʽԣԼϵͳлȡ `DEMO_ITEMPRICE`   `${demo.itemPrice}` Ļ `demo.item-price`  `DEMO_ITEMPRICE` Ͳᱻǡ |\n| --- | --- |\n\n\n\n\n\n|  | ҲʹּSpring BootԵ short 塣 μ_[howto.html](https://springdoc.cn/spring-boot/howto.html#howto.properties-and-configuration.short-command-line-arguments)_ķ |\n| --- | --- |\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/features.html#features.external-config.files.multi-document)2.3.8\\. ʹöĵļWorking with Multi-Document Files\n\n\n\nSpring Boot㽫һļֳɶ߼ļÿļǶӵġ ļǰ˳ģϵ¡ ļԸǰļжԡ\n\n\n\n\n\n `application.yml` ļʹñ׼YAMLĵ﷨ ַ`---`һļĽһļĿʼ\n\n\n\n\n\n磬ļ߼ĵ\n\n\n\n\n\n\n\n```\nspring:\n  application:\n    name: \"MyApp\"\n---\nspring:\n  application:\n    name: \"MyCloudApp\"\n  config:\n    activate:\n      on-cloud-platform: \"kubernetes\"\n```\n\n\n\n\n\n\n\n `application.properties` ļһ `#---`  `!---` עͱļķָ\n\n\n\n\n\n\n\n```\nspring.application.name=MyApp\n#---\nspring.application.name=MyCloudApp\nspring.config.activate.on-cloud-platform=kubernetes\n```\n\n\n\n\n\n\n\n|  | properties ļķָκǰհףұַ ָǰвͬעǰ׺ |\n| --- | --- |\n\n\n\n\n\n|  | ĵļͨ뼤һʹã `spring.config.activated.on-profile` [һ](https://springdoc.cn/spring-boot/features.html#features.external-config.files.activation-properties) |\n| --- | --- |\n\n\n\n\n\n|  | ĵļͨʹ `@PropertySource`  `@TestPropertySource` עء |\n| --- | --- |\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/features.html#features.external-config.files.activation-properties)2.3.9\\. ԣActivation Properties\n\n\n\nʱֻĳЩʱһضǺõġ 磬һЩֻضļʱء\n\n\n\n\n\nʹ `spring.config.activation.*` ؼһļ\n\n\n\n\n\n¡\n\n\n\n<caption>Table 2\\. activation properties</caption><colgroup><col><col></colgroup>\n|  | ˵ |\n| --- | --- |\n| `on-profile` | һ֮ƥļʽʹļڻ״ָ̬ļʱЧ |\n| `on-cloud-platform` | ⵽ `CloudPlatform`ʹļڻ״̬ƽ̨״̬Ч |\n\n\n\n磬ָڶļֻKubernetesʱЧֻ prod  staging ļڻ״̬ʱЧ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmyprop=always-set\n#---\nspring.config.activate.on-cloud-platform=kubernetes\nspring.config.activate.on-profile=prod | staging\nmyotherprop=sometimes-set\n\n```\n\n\n\n\n\n\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/features.html#features.external-config.encrypting)2.4\\. ԣEncrypting Properties\n\n\n\nSpring BootûΪֵṩκ֧֣ṩHookm޸Spring `Environment` аֵ `EnvironmentPostProcessor` ӿӦóǰ `Environment` μ[howto.html](https://springdoc.cn/spring-boot/howto.html#howto.application.customize-the-environment-or-application-context)˽顣\n\n\n\n\n\nҪһְȫķʽ洢ƾ֤룬 [Spring Cloud Vault](https://cloud.spring.io/spring-cloud-vault/) Ŀṩ˶ [HashiCorp Vault](https://www.vaultproject.io/)д洢ⲿõ֧֡\n\n\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/features.html#features.external-config.yaml)2.5\\. ʹ YAML\n\n\n\n[YAML](https://yaml.org/) JSONĳֲָݵķʽ ֻҪclasspath [SnakeYAML](https://github.com/snakeyaml/snakeyaml) ⣬`SpringApplication` ͻԶ֧YAMLΪpropertiesƷ\n\n\n\n\n\n|  | ʹ StarterSnakeYAML `spring-boot-starter` Զṩ |\n| --- | --- |\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/features.html#features.external-config.yaml.mapping-to-properties)2.5.1\\. YAMLӳ䵽Properties\n\n\n\nYAML ĵҪֲʽתΪ Spring `Environment` һʹõıƽṹ 磬YAMLĵ\n\n\n\n\n\n\n\n```\nenvironments:\n  dev:\n    url: \"https://dev.example.com\"\n    name: \"Developer Setup\"\n  prod:\n    url: \"https://another.example.com\"\n    name: \"My Cool App\"\n```\n\n\n\n\n\n\n\nΪ˴ `Environment` зЩԣǽƽʾ\n\n\n\n\n\n\n\n```\nenvironments.dev.url=https://dev.example.com\nenvironments.dev.name=Developer Setup\nenvironments.prod.url=https://another.example.com\nenvironments.prod.name=My Cool App\n```\n\n\n\n\n\n\n\nͬأYAMLебҲҪбƽ ǱʾΪ `[index]` key 磬YAML\n\n\n\n\n\n\n\n```\nmy:\n servers:\n - \"dev.example.com\"\n - \"another.example.com\"\n```\n\n\n\n\n\n\n\nǰӽתΪԡ\n\n\n\n\n\n\n\n```\nmy.servers[0]=dev.example.com\nmy.servers[1]=another.example.com\n```\n\n\n\n\n\n\n\n|  | ʹ `[index]` ŵԿʹSpring Boot `Binder` 󶨵Java `List`  `Set`  ϸڼ [Ͱȫ](https://springdoc.cn/spring-boot/features.html#features.external-config.typesafe-configuration-properties) ֡ |\n| --- | --- |\n\n\n\n\n\n|  | YAMLļͨʹ `@PropertySource`  `@TestPropertySource` עء ԣҪַʽֵ£Ҫʹһ properties ļ |\n| --- | --- |\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/features.html#features.external-config.yaml.directly-loading)2.5.2\\. ֱӼYAML\n\n\n\nSpring Frameworkṩ࣬YAMLĵ `YamlPropertiesFactoryBean` YAMLΪ `Properties` أ`YamlMapFactoryBean` YAMLΪ `Map` ء\n\n\n\n\n\nYAMLΪSpring `PropertySource` Ҳʹ `YamlPropertySourceLoader` ࡣ\n\n\n\n\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/features.html#features.external-config.random-values)2.6\\. ֵ\n\n\n\nThe `RandomValuePropertySource` is useful for injecting random values (for example, into secrets or test cases). It can produce integers, longs, uuids, or strings, as shown in the following example:\n\n\n\n\n\n`RandomValuePropertySource` עֵã磬ע԰ ԲIntegerLongUUIDStringʾ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmy.secret=${random.value}\nmy.number=${random.int}\nmy.bignumber=${random.long}\nmy.uuid=${random.uuid}\nmy.number-less-than-ten=${random.int(10)}\nmy.number-in-range=${random.int[1024,65536]}\n\n```\n\n\n\n\n\n\n\n`random.int*` ﷨ `OPEN value (,max) CLOSE` `OPEN,CLOSE` κַ `value,max`  ṩ `max`ô `value` Сֵ `max` ֵռ\n\n\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/features.html#features.external-config.system-environment)2.7\\. ϵͳ\n\n\n\nSpring Boot֧Ϊһǰ׺ ϵͳвͬҪSpring BootӦóͺá ϵͳԵǰ׺ֱ `SpringApplication` á\n\n\n\n\n\n磬㽫ǰ׺Ϊ `input`  `remote.timeout` ϵͳҲΪ `input.remote.timeout`\n\n\n\n\n\n\n\n### [](https://springdoc.cn/spring-boot/features.html#features.external-config.typesafe-configuration-properties)2.8\\. Ͱȫ\n\n\n\nʹ `@Value(\"${property}\")` עעʱ鷳رǵҪԻǷֲġ Spring BootṩһִԵǿ͵Bean֤Ӧóá\n\n\n\n\n\n|  | μ[`@Value` Ͱȫ֮](https://springdoc.cn/spring-boot/features.html#features.external-config.typesafe-configuration-properties.vs-value-annotation) |\n| --- | --- |\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/features.html#features.external-config.typesafe-configuration-properties.java-bean-binding)2.8.1\\. JavaBean ԰\n\n\n\nʾ԰һ˱׼JavaBeanԵbean\n\n\n\n\n\n\n\nJava\n\nKotlin\n\n\n\n\n\n```\n@ConfigurationProperties(\"my.service\")\npublic class MyProperties {\n\n    private boolean enabled;\n\n    private InetAddress remoteAddress;\n\n    private final Security security = new Security();\n\n    // getters / setters...\n\n    public static class Security {\n\n        private String username;\n\n        private String password;\n\n        private List<String> roles = new ArrayList<>(Collections.singleton(\"USER\"));\n\n        // getters / setters...\n\n    }\n\n}\n\n```\n\n\n\n\n\n\n\nǰPOJOԡ\n\n\n\n\n\n*   `my.service.enabled`ĬֵΪ`false`\n\n*   `my.service.remote-address`Ϳ`String`ǿṩ\n\n*   `my.service.security.username`һǶ׵ `security` ɸԵƾ رǣȫûʹͣ `SecurityProperties`\n\n*   `my.service.security.password`.\n\n*   `my.service.security.role`һ `String` ļϣĬΪ `USER`\n\n\n\n\n\n|  | ӳ䵽Spring Bootпõ `@ConfigurationProperties` ԣͨpropertiesļYAMLļƽãЩǹAPI౾ getters/setters ζſֱʹãһ仰SpringҲͨgetter/setterЩpublicֵģã |\n| --- | --- |\n\n\n\n\n\n|  | һĬϵ޲ι캯gettersetterͨǱģΪͨ׼Java Beans property descriptorJavaʡʵֵģSpring MVCһ £ʡsetter*   Map, ֻҪǱʼҪһgetterһҪһsetterΪǿԱͻ䡣        *   Collectionarray ͨͨYAMLʹõŷֵָԣʡ ںһ£һsetterǱġ ǽΪһsetter ʼһϣȷǲɱģǰӣ        *   Ƕ׵POJOԱʼǰе `Security` ֶΣͲҪsetter ðͨʹĬϹ캯ʱʵҪһsetter        ЩʹProject LombokԶgettersetter ȷLombokΪκضĹ캯ΪԶʵֻǱ׼Java Beanԣֶ֧Ծ̬Եİ󶨡 |\n| --- | --- |\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/features.html#features.external-config.typesafe-configuration-properties.constructor-binding)2.8.2\\. 캯\n\n\n\nһڵӿòɱķʽдʾ\n\n\n\n\n\n\n\nJava\n\nKotlin\n\n\n\n\n\n```\n@ConfigurationProperties(\"my.service\")\npublic class MyProperties {\n\n    // fields...\n\n    public MyProperties(boolean enabled, InetAddress remoteAddress, Security security) {\n        this.enabled = enabled;\n        this.remoteAddress = remoteAddress;\n        this.security = security;\n    }\n\n    // getters...\n\n    public static class Security {\n\n        // fields...\n\n        public Security(String username, String password, @DefaultValue(\"USER\") List<String> roles) {\n            this.username = username;\n            this.password = password;\n            this.roles = roles;\n        }\n\n        // getters...\n\n    }\n\n}\n\n```\n\n\n\n\n\n\n\nУΨһġ캯ĴζӦʹøù캯а󶨡 ζŰҵһϣ󶨵ĲĹ캯 ж캯ʹ `@ConstructorBinding` עָʹĸ캯й캯󶨡 ҪΪһֻһ캯ѡ񲻰󶨹캯ù캯 `@Autowired` ע⡣ 캯󶨿 `Record` һʹá ļ¼ж캯ûбҪʹ `@ConstructorBinding`\n\n\n\n\n\n캯Ƕ׳Աе `Security`Ҳͨ乹캯󶨡\n\n\n\n\n\nĬֵڹ캯Recordʹ `@DefaultValue` ָ ת񽫱Ӧڽע `String` ֵǿתΪȱʧԵĿ͡\n\n\n\n\n\nοǰӣû԰󶨵 `Security`  `MyProperties` ʵһ `security` ͵ `null` ֵ Ϊʹһ null  `Security` ʵʹû֮󶨣ʹKotlinʱ⽫Ҫ `Security`  `username`  `password` Ϊ nullableΪûĬֵʹһյ `@DefaultValue` ע⡣\n\n\n\n\n\n\n\nJava\n\nKotlin\n\n\n\n\n\n```\npublic MyProperties(boolean enabled, InetAddress remoteAddress, @DefaultValue Security security) {\n    this.enabled = enabled;\n    this.remoteAddress = remoteAddress;\n    this.security = security;\n}\n\n```\n\n\n\n\n\n\n\n|  | Ҫʹù캯󶨣ʹ `@EnableConfigurationProperties` ɨá 㲻ܶͨSpringƴBeanʹù캯󶨣 `@Component` Beanͨʹ `@Bean` Beanͨʹ `@Import` صBean |\n| --- | --- |\n\n\n\n\n\n|  | Ҫԭʹù캯󶨣 `-parameters` ࡣʹ Spring Boot  Gradle ʹ Maven  `spring-boot-starter-parent`⽫Զá |\n| --- | --- |\n\n\n\n\n\n|  | 齫 `java.util.Optional`  `@ConfigurationProperties` һʹãΪҪΪһʹá ˣʺע롣 Ϊ͵Աһ£ȷʵһ `Optional` ԣûֵ`null` һյ `Optional` 󶨡 |\n| --- | --- |\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/features.html#features.external-config.typesafe-configuration-properties.enabling-annotated-types)2.8.3\\.  @ConfigurationProperties \n\n\n\nSpring Bootṩ˰ `@ConfigurationProperties` ͲעΪBeanĻʩ Ļԣɨ裬乤ʽɨơ\n\n\n\n\n\nʱ `@ConfigurationProperties` עܲʺɨ裬磬ڿԼԶûǡ Щ£ʹ `@EnableConfigurationProperties` עָҪб עκ `@Configuration` ϣʾ\n\n\n\n\n\n\n\nJava\n\nKotlin\n\n\n\n\n\n```\n@Configuration(proxyBeanMethods = false)\n@EnableConfigurationProperties(SomeProperties.class)\npublic class MyConfiguration {\n\n}\n\n```\n\n\n\n\n\n\n\n\n\nJava\n\nKotlin\n\n\n\n\n\n```\n@ConfigurationProperties(\"some.properties\")\npublic class SomeProperties {\n\n}\n\n```\n\n\n\n\n\n\n\nҪʹɨ裬application `@ConfigurationPropertiesScan` ע⡣ ͨӵ `@SpringBootApplication` עmainУҲԱӵκ `@Configuration` ϡ Ĭ£ɨעڵİʼԶɨԲο¡\n\n\n\n\n\n\n\nJava\n\nKotlin\n\n\n\n\n\n```\n@SpringBootApplication\n@ConfigurationPropertiesScan({ \"com.example.app\", \"com.example.another\" })\npublic class MyApplication {\n\n}\n\n```\n\n\n\n\n\n\n\n|  |  `@ConfigurationProperties` Beanʹɨͨ `@EnableConfigurationProperties` עʱBeanһƣ`<prefix>-<fqn>` `<prefix>`  `@ConfigurationProperties` עָĻǰ׺ `<fqn>` Beanȫ޶ơ עûṩκǰ׺ֻʹBeanȫ޶ơ `com.example.app` У `SomeProperties` ӵ bean  `some.properties-com.example.app.SomeProperties` |\n| --- | --- |\n\n\n\n\n\nǽ `@ConfigurationProperties` ֻ environmentرǲעBean ڱ߽ǰʹ setter עṩκ `*Aware` ӿڣ `EnvironmentAware` Ҫ `Environment` ȻʹùעBeanBean `@Component` ע⣬ʹûJavaBean԰󶨡\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/features.html#features.external-config.typesafe-configuration-properties.using-annotated-types)2.8.4\\. ʹ @ConfigurationProperties \n\n\n\n÷ʽ `SpringApplication` ⲿYAMLϵرãʾ\n\n\n\n\n\n\n\n```\nmy:\n  service:\n    remote-address: 192.168.1.1\n    security:\n      username: \"admin\"\n      roles:\n      - \"USER\"\n      - \"ADMIN\"\n```\n\n\n\n\n\n\n\nҪʹ `@ConfigurationProperties` BeanBeanͬķʽעǣʾ\n\n\n\n\n\n\n\nJava\n\nKotlin\n\n\n\n\n\n```\n@Service\npublic class MyService {\n\n    private final MyProperties properties;\n\n    public MyService(MyProperties properties) {\n        this.properties = properties;\n    }\n\n    public void openConnection() {\n        Server server = new Server(this.properties.getRemoteAddress());\n        server.start();\n        // ...\n    }\n\n    // ...\n\n}\n\n```\n\n\n\n\n\n\n\n|  | ʹ `@ConfigurationProperties` ԪļЩļԱIDEԵġԶȫܡ [¼](https://springdoc.cn/spring-boot/configuration-metadata.html#appendix.configuration-metadata) |\n| --- | --- |\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/features.html#features.external-config.typesafe-configuration-properties.third-party-configuration)2.8.5\\. \n\n\n\nʹ `@ConfigurationProperties` עһ֮⣬㻹ڹ `@Bean` ʹ ԰󶨵֮ĵʱرá\n\n\n\n\n\nҪ `Environment` һBeanBeanע `@ConfigurationProperties` ʾ\n\n\n\n\n\n\n\nJava\n\nKotlin\n\n\n\n\n\n```\n@Configuration(proxyBeanMethods = false)\npublic class ThirdPartyConfiguration {\n\n    @Bean\n    @ConfigurationProperties(prefix = \"another\")\n    public AnotherComponent anotherComponent() {\n        return new AnotherComponent();\n    }\n\n}\n\n```\n\n\n\n\n\n\n\nκ `another` ǰ׺JavaBeanԶᱻӳ䵽 `AnotherComponent` Beanϣ䷽ʽǰ `SomeProperties` ӡ\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/features.html#features.external-config.typesafe-configuration-properties.relaxed-binding)2.8.6\\. ɵİ\n\n\n\nSpring Bootڽ `Environment` ԰󶨵 `@ConfigurationProperties` beanʱʹһЩɵĹ `Environment` ƺbean֮䲻Ҫȫƥ䡣 ãӰۺŷָƣ磬 `context-path` 󶨵 `contextPath` ʹдƣ磬`PORT` 󶨵 `port` \n\n\n\n\n\nʾһӣ `@ConfigurationProperties` ࡣ\n\n\n\n\n\n\n\nJava\n\nKotlin\n\n\n\n\n\n```\n@ConfigurationProperties(prefix = \"my.main-project.person\")\npublic class MyPersonProperties {\n\n    private String firstName;\n\n    public String getFirstName() {\n        return this.firstName;\n    }\n\n    public void setFirstName(String firstName) {\n        this.firstName = firstName;\n    }\n\n}\n\n```\n\n\n\n\n\n\n\nϵĴ˵µƶʹá\n\n\n\n<caption>Table 3\\. relaxed binding</caption><colgroup><col><col></colgroup>\n| Property | Note |\n| --- | --- |\n| `my.main-project.person.first-name` | Kebab 񣨶̺߸ `.properties`  `.yml` ļʹá |\n| `my.main-project.person.firstName` | ׼շ﷨ |\n| `my.main-project.person.first_name` | »ߣһ `.properties`  `.yml` ļʽ |\n| `MY_MAINPROJECT_PERSON_FIRSTNAME` | дʽʹϵͳʱʹôдʽ |\n\n\n\n|  | ע `prefix` ֵ __ kebabСд `-` ָ `my.main-project.person`  |\n| --- | --- |\n\n\n\n<caption>Table 4\\. ÿԴĿɰ󶨹</caption><colgroup><col><col><col></colgroup>\n| Դ | 򵥵 | б |\n| --- | --- | --- |\n| Properties ļ | շ, kebab , » | ʹ `[ ]` 򶺺ŷֵָı׼б﷨ |\n| YAML ļ | շ, kebab , » | ׼YAMLб﷨򶺺ŷֵָ |\n|  | д»Ϊָ( [ӻ](https://springdoc.cn/spring-boot/features.html#features.external-config.typesafe-configuration-properties.relaxed-binding.environment-variables)). | Numeric values surrounded by underscores (see [ӻ](https://springdoc.cn/spring-boot/features.html#features.external-config.typesafe-configuration-properties.relaxed-binding.environment-variables)) |\n| ϵͳԣSystem properties | շ, kebab , » | ʹ `[ ]` 򶺺ŷֵָı׼б﷨ |\n\n\n\n|  | ǽ飬ڿܵ£ӦСдkebabʽ洢 `my.person.first-name=Rod`  |\n| --- | --- |\n\n\n\n\n\n##### [](https://springdoc.cn/spring-boot/features.html#features.external-config.typesafe-configuration-properties.relaxed-binding.maps)Map\n\n\n\n󶨵 `Map` ʱҪʹһŷţԱ㱣ԭʼ `key` ֵ keyûб `[ ]` κηĸ֡`-`  `.` ַɾ\n\n\n\n\n\n磬ǽ԰󶨵һ `Map<String,String>`\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmy.map.[/key1]=value1\nmy.map.[/key2]=value2\nmy.map./key3=value3\n```\n\n\n\n\n\n\n\n|  | YAMLļҪŰʹkeyȷ |\n| --- | --- |\n\n\n\n\n\nԽ󶨵һ `Map` `/key1``/key2`  `key3` Ϊmapkey бѾ `key3` ɾΪûбŰ\n\n\n\n\n\n󶨵ֵʱ `.` ļҪ `[]`  ֵöٺ `java.lang` еͣ `Object`   `a.b=c` 󶨵 `Map<String, String>` е `.` һ `{\"a.b\"=\"c\"}` EntryMap κͣ `key`  `.` Ҫʹŷš 磬 `a.b=c` 󶨵 `Map<String, Object>` һ `{\"a\"={\"b\"=\"c\"}` entryMap `[a.b]=c` һ `{\"a.b\"=\"c\"}` entry Map\n\n\n\n\n\n\n\n##### [](https://springdoc.cn/spring-boot/features.html#features.external-config.typesafe-configuration-properties.relaxed-binding.environment-variables)ӻ\n\n\n\n磬Linux shellֻܰĸ`a`  `z`  `A`  `Z` ֣ `0`  `9` »ַ `_`  չUnix shellҲôдĸ\n\n\n\n\n\nSpring Bootɵİ󶨹ΪܵЩƼݡ\n\n\n\n\n\nҪ淶ʽתΪƣѭЩ\n\n\n\n\n\n*   »ߣ`_`滻㣨`.`\n\n*   ɾκۺţ`-`\n\n*   תΪдĸ\n\n\n\n\n\n磬 `spring.main.log-startup-info` һΪ `SPRING_MAIN_LOGSTARTUPINFO` Ļ\n\n\n\n\n\nҲڰ󶨵бListʱʹá Ҫ󶨵һ `List`ڱУԪرţӦ»߰\n\n\n\n\n\n磬 `my.service[0].other` ʹһΪ `MY_SERVICE_0_OTHER` Ļ\n\n\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/features.html#features.external-config.typesafe-configuration-properties.merging-complex-types)2.8.7\\. ϲӵ\n\n\n\nListڶطʱǵ滻list\n\n\n\n\n\n磬һ `MyPojo`  `name`  `description` ĬΪ `null` Ӵ `MyProperties` б¶һ `MyPojo` б\n\n\n\n\n\n\n\nJava\n\nKotlin\n\n\n\n\n\n```\n@ConfigurationProperties(\"my\")\npublic class MyProperties {\n\n    private final List<MyPojo> list = new ArrayList<>();\n\n    public List<MyPojo> getList() {\n        return this.list;\n    }\n\n}\n\n```\n\n\n\n\n\n\n\ná\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmy.list[0].name=my name\nmy.list[0].description=my description\n#---\nspring.config.activate.on-profile=dev\nmy.list[0].name=my another name\n\n```\n\n\n\n\n\n\n\n `dev` ļδ`MyProperties.list` һ `MyPojo` Ŀ֮ǰ Ȼ `dev` ļ`list` ȻֻһĿname Ϊ `my another name`descriptionΪ `null` òбӵڶ `MyPojo` ʵҲϲĿ\n\n\n\n\n\nһ `List` ڶļбָʱʹþȼǸֻǸ ӡ\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmy.list[0].name=my name\nmy.list[0].description=my description\nmy.list[1].name=another name\nmy.list[1].description=another description\n#---\nspring.config.activate.on-profile=dev\nmy.list[0].name=my another name\n\n```\n\n\n\n\n\n\n\nǰУ `dev` ļǼģ`MyProperties.list`  _һ_ `MyPojo` Ŀname  `my another name`description `null` YAMLŷָбYAMLбȫбݡ\n\n\n\n\n\n `Map` ԣôӶԴȡֵа󶨡 ȻڶԴеͬһԣʹþȼǸ Ӵ `MyProperties` ¶һ `Map<String, MyPojo>`\n\n\n\n\n\n\n\nJava\n\nKotlin\n\n\n\n\n\n```\n@ConfigurationProperties(\"my\")\npublic class MyProperties {\n\n    private final Map<String, MyPojo> map = new LinkedHashMap<>();\n\n    public Map<String, MyPojo> getMap() {\n        return this.map;\n    }\n\n}\n\n```\n\n\n\n\n\n\n\ná\n\n\n\n\n\n\n\nProperties\n\nYaml\n\n\n\n\n\n```\nmy.map.key1.name=my name 1\nmy.map.key1.description=my description 1\n#---\nspring.config.activate.on-profile=dev\nmy.map.key1.name=dev name 1\nmy.map.key2.name=dev name 2\nmy.map.key2.description=dev description 2\n\n```\n\n\n\n\n\n\n\n `dev` ļûм`MyProperties.map` һkeyΪ `key1` ĿnameΪ `my name 1` descriptionΪ `my description 1`  Ȼ `dev` ļ`map` ĿkeyΪ `key1` nameΪ `dev name 1`descriptionΪ `my description 1`  `key2`nameΪ `dev name 2`descriptionΪ `dev description 2`\n\n\n\n\n\n|  | ǰĺϲԴԣļ |\n| --- | --- |\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/features.html#features.external-config.typesafe-configuration-properties.conversion)2.8.8\\. ԣPropertiesת\n\n\n\nSpring Boot `@ConfigurationProperties` Beanʱͼⲿapplication propertiesǿƸΪȷ͡ ҪԶתṩһ `ConversionService` beanBeanΪ `conversionService` ԶԱ༭ͨ `CustomEditorConfigurer` beanԶ `Converters` Beanʹ `@ConfigurationPropertiesBinding` ע⣩\n\n\n\n\n\n|  | BeanӦóڵڱģȷ `ConversionService` ʹõϵ ͨ£Ҫκϵڴʱûȫʼ Զ `ConversionService` Ҫkeys coercionֻ `@ConfigurationPropertiesBinding` ޶Զת |\n| --- | --- |\n\n\n\n\n\n##### [](https://springdoc.cn/spring-boot/features.html#features.external-config.typesafe-configuration-properties.conversion.durations)תΪ Duration\n\n\n\nSpring BootԱʱרŵ֧֡ 㹫һ `java.time.Duration` ԣapplication propertiesе¸ʽͿá\n\n\n\n\n\n*   ͨ `long` ʹúΪĬϵλָ `@DurationUnit` \n\n*   ׼ISO-8601ʽ [ `java.time.Duration` ʹ](https://docs.oracle.com/javase/17/docs/api/java/time/Duration.html#parse-java.lang.CharSequence-)\n\n*   һ׶ĸʽֵ͵λϵģ`10s` ʾ10룩\n\n\n\n\n\n뿼ӡ\n\n\n\n\n\n\n\nJava\n\nKotlin\n\n\n\n\n\n```\n@ConfigurationProperties(\"my\")\npublic class MyProperties {\n\n    @DurationUnit(ChronoUnit.SECONDS)\n    private Duration sessionTimeout = Duration.ofSeconds(30);\n\n    private Duration readTimeout = Duration.ofMillis(1000);\n\n    // getters / setters...\n\n}\n\n```\n\n\n\n\n\n\n\nҪָһ30ĻỰʱ `30`  `PT30S`  `30s` ǵȼ۵ġ ȡʱΪ500msκһʽָ `500`, `PT0.5S`  `500ms`.\n\n\n\n\n\nҲʹֵ֧ʱ䵥λ\n\n\n\n\n\n*   `ns` \n\n*   `us` ΢\n\n*   `ms` \n\n*   `s` \n\n*   `m` \n\n*   `h` Сʱ\n\n*   `d` \n\n\n\n\n\nĬϵλǺ룬ʹ `@DurationUnit` дʾ\n\n\n\n\n\nϲʹù캯󶨣ͬԿԱ¶ʾ\n\n\n\n\n\n\n\nJava\n\nKotlin\n\n\n\n\n\n```\n@ConfigurationProperties(\"my\")\npublic class MyProperties {\n\n    // fields...\n\n    public MyProperties(@DurationUnit(ChronoUnit.SECONDS) @DefaultValue(\"30s\") Duration sessionTimeout,\n            @DefaultValue(\"1000ms\") Duration readTimeout) {\n        this.sessionTimeout = sessionTimeout;\n        this.readTimeout = readTimeout;\n    }\n\n    // getters...\n\n}\n\n```\n\n\n\n\n\n\n\n|  | Ҫһ `Long` ԣǺ룬ȷ嵥λʹ `@DurationUnit`  ṩһ͸·ͬʱָ֧ḻĸʽ |\n| --- | --- |\n\n\n\n\n\n\n\n##### [](https://springdoc.cn/spring-boot/features.html#features.external-config.typesafe-configuration-properties.conversion.periods)תΪڼ䣨Period\n\n\n\ndurationSpring Bootʹ `java.time.Period` ͡ ¸ʽapplication propertiesʹá\n\n\n\n\n\n*   һ `int` ʾʹΪĬϵλָ `@PeriodUnit` \n\n*   ׼ISO-8601ʽ [ `java.time.Period` ʹ](https://docs.oracle.com/javase/17/docs/api/java/time/Period.html#parse-java.lang.CharSequence-)\n\n*   һ򵥵ĸʽֵ͵λϵģ `1y3d` ʾ13죩\n\n\n\n\n\n֧м򵥵ĵλʽ\n\n\n\n\n\n*   `y` \n\n*   `m` \n\n*   `w` \n\n*   `d` \n\n\n\n\n\n|  | `java.time.Period` ʵϴδ洢һݷʽζ 7족 |\n| --- | --- |\n\n\n\n\n\n\n\n##### [](https://springdoc.cn/spring-boot/features.html#features.external-config.typesafe-configuration-properties.conversion.data-sizes)תΪݴСData Sizes\n\n\n\nSpring Frameworkһ `DataSize` ֵֽͣΪλС 㹫һ `DataSize` ԣapplication propertiesе¸ʽͿá\n\n\n\n\n\n*   һ `long` ʾʹֽΪĬϵλָ `@DataSizeUnit`\n\n*   һ׶ĸʽֵ͵λϵģ`10MB` ζ10ֽڣ\n\n\n\n\n\nӡ\n\n\n\n\n\n\n\nJava\n\nKotlin\n\n\n\n\n\n```\n@ConfigurationProperties(\"my\")\npublic class MyProperties {\n\n    @DataSizeUnit(DataUnit.MEGABYTES)\n    private DataSize bufferSize = DataSize.ofMegabytes(2);\n\n    private DataSize sizeThreshold = DataSize.ofBytes(512);\n\n    // getters/setters...\n\n}\n\n```\n\n\n\n\n\n\n\nҪָһ10ֽڣMbĻС `10`  `10MB` ǵȼ۵ġ 256ֽڵĴСֵָΪ `256`  `256B`\n\n\n\n\n\nҲʹЩֵ֧ĵλ\n\n\n\n\n\n*   `B` ֽ\n\n*   `KB` KB\n\n*   `MB` MB\n\n*   `GB` GB\n\n*   `TB` TB\n\n\n\n\n\nĬϵλֽڣʹ `@DataSizeUnit` дʾ\n\n\n\n\n\nϲʹù캯󶨣ͬԿԱ¶ʾ\n\n\n\n\n\n\n\nJava\n\nKotlin\n\n\n\n\n\n```\n@ConfigurationProperties(\"my\")\npublic class MyProperties {\n\n    // fields...\n\n    public MyProperties(@DataSizeUnit(DataUnit.MEGABYTES) @DefaultValue(\"2MB\") DataSize bufferSize,\n            @DefaultValue(\"512B\") DataSize sizeThreshold) {\n        this.bufferSize = bufferSize;\n        this.sizeThreshold = sizeThreshold;\n    }\n\n    // getters...\n\n}\n\n```\n\n\n\n\n\n\n\n|  | һ `Long` ԣȷ嵥λʹ `@DataSizeUnit`ֽڡ ṩһ͸·ͬʱָ֧ḻĸʽ |\n| --- | --- |\n\n\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/features.html#features.external-config.typesafe-configuration-properties.validation)2.8.9\\. @ConfigurationProperties У\n\n\n\nֻҪʹSpring `@Validated` ע⣬Spring Bootͻ᳢֤ `@ConfigurationProperties` ࡣ ֱʹJSR-303 `jakarta.validation` Լע⡣ Ҫһ㣬ȷclasspathһݵJSR-303ʵ֣ȻԼעӵֶУʾ\n\n\n\n\n\n\n\nJava\n\nKotlin\n\n\n\n\n\n```\n@ConfigurationProperties(\"my.service\")\n@Validated\npublic class MyProperties {\n\n    @NotNull\n    private InetAddress remoteAddress;\n\n    // getters/setters...\n\n}\n\n```\n\n\n\n\n\n\n\n|  | Ҳͨ configuration properties  `@Bean` ע `@Validated` ֤ |\n| --- | --- |\n\n\n\n\n\nΪȷΪǶ׵Դ֤ʹûҵԣصֶα `@Valid` ע͡ ӽǰ `MyProperties` Ļϡ\n\n\n\n\n\n\n\nJava\n\nKotlin\n\n\n\n\n\n```\n@ConfigurationProperties(\"my.service\")\n@Validated\npublic class MyProperties {\n\n    @NotNull\n    private InetAddress remoteAddress;\n\n    @Valid\n    private final Security security = new Security();\n\n    // getters/setters...\n\n    public static class Security {\n\n        @NotEmpty\n        private String username;\n\n        // getters/setters...\n\n    }\n\n}\n\n```\n\n\n\n\n\n\n\nҲͨһΪ `configurationPropertiesValidator` beanһԶSpring `Validator` `@Bean` ӦñΪ `static` ֤Ӧóڵڴģ `@Bean` Ϊ̬BeanĴҪʵ `@Configuration` ࡣ Աʵκ⡣\n\n\n\n\n\n|  | `spring-boot-actuator` ģһ¶ `@ConfigurationProperties` Bean Ķ˵㡣 ͨ `/actuator/configprops` ʹӦJMX˵㡣 \"[](https://springdoc.cn/spring-boot/actuator.html#actuator.endpoints)\"֡ |\n| --- | --- |\n\n\n\n\n\n\n\n#### [](https://springdoc.cn/spring-boot/features.html#features.external-config.typesafe-configuration-properties.vs-value-annotation)2.8.10\\. @ConfigurationProperties vs. @Value\n\n\n\n`@Value` עһĵܣṩͰȫͬĹܡ ±ܽ `@ConfigurationProperties`  `@Value` ֵ֧Ĺܡ\n\n\n\n<colgroup><col><col><col></colgroup>\n|  | `@ConfigurationProperties` | `@Value` |\n| --- | --- | --- |\n| [ɰ](https://springdoc.cn/spring-boot/features.html#features.external-config.typesafe-configuration-properties.relaxed-binding) | Yes |  ( [ע](https://springdoc.cn/spring-boot/features.html#features.external-config.typesafe-configuration-properties.vs-value-annotation.note)) |\n| [֧ Meta-data](https://springdoc.cn/spring-boot/configuration-metadata.html#appendix.configuration-metadata) | Yes | No |\n| `SpEL` ʽ | No | Yes |\n\n\n\n|  | ȷʵʹ `@Value`ǽʹƵĹ淶ʽʹСдĸkebab-caseơ ⽫Spring Bootʹ [ɰ](https://springdoc.cn/spring-boot/features.html#features.external-config.typesafe-configuration-properties.relaxed-binding) `@ConfigurationProperties` ʱͬ߼磬`@Value(\"${demo.item-price}\")`  `application.properties` ļлȡ `demo.item-price`  `demo.itemPrice` ʽԼϵͳлȡ `DEMO_ITEMPRICE`  `@Value(\"${demo.itemPrice}\")` 棬`demo.item-price`  `DEMO_ITEMPRICE` ᱻǡ |\n| --- | --- |\n\n\n\n\n\nΪԼһüǽ㽫Ƿһ `@ConfigurationProperties` עPOJOС ΪṩṹġͰȫĶԽע뵽ԼbeanС\n\n\n\n\n\nӦ[application property](https://springdoc.cn/spring-boot/features.html#features.external-config.files) ļ `SpEL` ʽڽЩļenvironmentʱᱻ Ȼ `@Value` дһ `SpEL` ʽ Ӧóļֵһ `SpEL` ʽڱ `@Value` ʱ\n\n\n\n\n\n\n\n\n\n\n\n\n\n## [](https://springdoc.cn/spring-boot/features.html#features.profiles)\n\n"
  },
  {
    "path": "docs/Spring全家桶/SpringBoot/SpringBoot自带的热部署工具.md",
    "content": "# SpringBoot - Ȳdevtools\n\n> SpringBootУÿд޸ĶҪٵԣܱȽϷʱ䣻SpringBootŶԴṩspring-boot-devtoolsdevtoolsͼԵЧʡ@pdai\n\n*   [SpringBoot - Ȳdevtools](#springboot%E5%85%A5%E9%97%A8---%E9%85%8D%E7%BD%AE%E7%83%AD%E9%83%A8%E7%BD%B2devtools%E5%B7%A5%E5%85%B7)\n    *   [׼֪ʶ](#%E5%87%86%E5%A4%87%E7%9F%A5%E8%AF%86%E7%82%B9)\n        *   [ʲôȲȼأ](#%E4%BB%80%E4%B9%88%E6%98%AF%E7%83%AD%E9%83%A8%E7%BD%B2%E5%92%8C%E7%83%AD%E5%8A%A0%E8%BD%BD)\n        *   [ʲôLiveLoad](#%E4%BB%80%E4%B9%88%E6%98%AFliveload)\n    *   [devtoolsʵȲ](#%E9%85%8D%E7%BD%AEdevtools%E5%AE%9E%E7%8E%B0%E7%83%AD%E9%83%A8%E7%BD%B2)\n        *   [POM](#pom%E9%85%8D%E7%BD%AE)\n        *   [IDEA](#idea%E9%85%8D%E7%BD%AE)\n        *   [application.yml](#applicationyml%E9%85%8D%E7%BD%AE)\n        *   [ʹLiveLoad](#%E4%BD%BF%E7%94%A8liveload)\n    *   [һ](#%E8%BF%9B%E4%B8%80%E6%AD%A5%E7%90%86%E8%A7%A3)\n        *   [devtoolԭΪλԶ](#devtool%E7%9A%84%E5%8E%9F%E7%90%86%E4%B8%BA%E4%BD%95%E4%BC%9A%E8%87%AA%E5%8A%A8%E9%87%8D%E5%90%AF)\n        *   [devtoolǷᱻJar](#devtool%E6%98%AF%E5%90%A6%E4%BC%9A%E8%A2%AB%E6%89%93%E5%8C%85%E8%BF%9Bjar)\n        *   [devtoolΪλĬϽûѡ](#devtool%E4%B8%BA%E4%BD%95%E4%BC%9A%E9%BB%98%E8%AE%A4%E7%A6%81%E7%94%A8%E7%BC%93%E5%AD%98%E9%80%89%E9%A1%B9)\n        *   [devtoolǷԸSpringbootӦȫֵã](#devtool%E6%98%AF%E5%90%A6%E5%8F%AF%E4%BB%A5%E7%BB%99%E6%89%80%E6%9C%89springboot%E5%BA%94%E7%94%A8%E5%81%9A%E5%85%A8%E5%B1%80%E7%9A%84%E9%85%8D%E7%BD%AE)\n        *   [Ҳdevtoolʲôѡ](#%E5%A6%82%E6%9E%9C%E6%88%91%E4%B8%8D%E7%94%A8devtool%E8%BF%98%E6%9C%89%E4%BB%80%E4%B9%88%E9%80%89%E6%8B%A9)\n    *   [ʾԴ](#%E7%A4%BA%E4%BE%8B%E6%BA%90%E7%A0%81)\n    *   [ο](#%E5%8F%82%E8%80%83%E6%96%87%E7%AB%A0)\n\n## [#](#׼֪ʶ) ׼֪ʶ\n\n### [#](#ʲôȲȼ) ʲôȲȼأ\n\n> ȲȼӦеʱԶ£¼ػ滻classȣӦõһPSspring-boot-devtoolsṩķҲҪģֶֻʵԶضѡ\n\nϸϣҪȲȼ, JavaĿԣ\n\n*   **Ȳ**\n\n    *   ڷʱ²Ŀ\n    *   ֱ¼Ӧãַʽͷڴ棬ȼظӸɾףͬʱҲʱ䡣\n*   **ȼ**\n\n    *   ʱ¼classӶӦá\n    *   ȼصʵԭҪ[javaػ](/md/java/jvm/java-jvm-classload.html)ʵַʽԸΪʱһ̨̣߳ʱļļʱ仯ʱˣ롣\n    *   ԱȷƣʱȡϢ̬ͨĵıΪ ȼʱͨ¼ظıϢֱӸıΪ\n\n### [#](#ʲôliveload) ʲôLiveLoad\n\nLiveLoadṩͻԶظµĹߣΪLiveLoadLiveload֣ devtoolsѾLiveLoadǿwebӦãԶˢ£ ʱԿLiveLoad.\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230416205152.png)\n\nͬһʱֻһLiveReload ʼӦó֮ǰȷûLiveReloadСIDEӦóֻеһӦó֧LiveReload\n\n## [#](#devtoolsʵȲ) devtoolsʵȲ\n\n> ͨʵԶʽȲ\n\n### [#](#pom) POM\n\nspring-boot-devtools\n\n\n\n```\n<dependencies>\n    <dependency>\n        <groupId>org.springframework.boot</groupId>\n        spring-boot-devtools\n        <optional>true</optional> <!-- Էֹdevtoolsݵģ -->\n    </dependency>\n</dependencies>\n\n```\n\n\n\n### [#](#idea) IDEA\n\n> ʹIDEAߣַͨʽ\n\n*   ʽһ **κʱֶ£Ctrl+F9**\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230416205320.png)\nҲ`mvn compile`봥£\n\n*   ʽ **IDEA迪ʱ룬Զ**\n\n**1**\n\nFile->Setting->Build,Execution,Deployment->Compile\n\nѡMake project automatically\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230416205335.png)\n\n**2**\n\nݼctrl+alt+shift+/\n\nѡRegistry\n\nѡcompiler.automake.allow.when.app.running\n\n°汾IDEAFile->setting->Advanced Setttingsĵһã\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230416205405.png)\n### [#](#application-yml) application.yml\n\n\n\n```\nspring:\n  devtools:\n    restart:\n      enabled: true  #ÿȲ\n      additional-paths: src/main/java #Ŀ¼\n      exclude: WEB-INF/**\n  thymeleaf:\n    cache: false #ʹThymeleafģ棬رջ\n\n```\n\n\n\n### [#](#ʹliveload) ʹLiveLoad\n\nspring-boot-devtoolsģ**ǶʽLiveReload**Դʱڴˢ¡ LiveReloadչ֧ChromeFirefoxSafariԴlivereload.comء\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230416205417.png)\nߴأfirefox:\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230416205426.png)\n\nװ֮󣬿ͨͼ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230416205442.png)\n\n㲻ӦóʱLiveReloadԽspring.devtools.livereload.enabledΪfalse \n\nͬһʱֻһLiveReload ʼӦó֮ǰȷûLiveReloadСIDEӦóֻеһӦó֧LiveReload\n\n## [#](#һ) һ\n\n> ȻһЩ߻ʹdevtoolߣǺܹģ¼⣬һ⡣@pdai\n\n### [#](#devtoolԭ-ΪλԶ) devtoolԭΪλԶ\n\n> ΪʲôͬӦãΪʲôֶǽʹspring-boot-devtoolsȲ\n\nspring-boot-devtoolsʹClassLoaderһClassLoaderزᷢĵࣨjarһClassLoaderrestart ClassLoaderػĵࣨԶࣩ\n\n̨һ**ļ̣߳File Watcher****Ŀ¼еļ䶯ʱ ԭrestart ClassLoader¼µrestart ClassLoader**\n\nΪļ䶯󣬵jar¼أֻԶ࣬صȽ٣ȽϿ졣\n\nҲΪʲôͬӦãΪʲôֶʹspring-boot-devtoolsȲ\n\nԶмҪע:\n\n*   **Զ¼־**\n\n¼ʲô־\n\nͨ¹ر\n\n\n\n```\nspring:\n  devtools:\n    restart:\n      log-condition-evaluation-delta: false\n\n```\n\n\n\n*   **ųһЩҪԶԴ**\n\nĳЩԴڸʱһҪĬ£ıԴ/META-INF/maven/META-INF/resources/resources/static/public/templatesȷᴥֳװҪԶЩųʹøspring.devtools.restart.excludeԡ磬Ҫų/static/public㽫ԣ\n\n\n\n```\nspring:\n  devtools:\n    restart:\n      exclude: \"static/**,public/**\"\n\n```\n\n\n\nҪЩĬֵųøspring.devtools.restart.additional-excludeԡ\n\n*   **Զ**\n\nͨʹʵֵġڴӦóַЧܺáǣʱᵼ⡣\n\nĬ£IDE еκδĿʹáأκγ.jarļʹáء㴦һģĿҲÿģ鶼뵽 IDE УҪԶһЩΪˣԴһMETA-INF/spring-devtools.propertiesļ\n\nspring-devtools.propertiesļ԰restart.excludeΪǰ׺restart.includeincludeԪӦñߵĿԼexcludeҪӦ롰BaseĿԵֵӦ·ʽģʽʾʾ\n\n\n\n```\nrestart:\n  exclude:\n    companycommonlibs: \"/mycorp-common-[\\\\w\\\\d-\\\\.]+\\\\.jar\"\n  include:\n    projectcommon: \"/mycorp-myproj-[\\\\w\\\\d-\\\\.]+\\\\.jar\"\n\n```\n\n\n\nصϢ[´ڴ](https://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.devtools)鿴\n\n### [#](#devtoolǷᱻjar) devtoolǷᱻJar\n\n> devtoolԭ˵ӦֻڿԵʱʹãjarʱǲҪģSpring᲻JAR\n\n*   **Ĭ£ᱻJAR**\n\nдӦóʱԱ**Զ**ͨ java -jarʱᱻΪǡӦá\n\n*   **Զ̵Ӧ**\n\n_ãֻεлʹ SSL бʱӦ_\n\n£devtoolҲ߱Զ̵ԵԶ̿ͻӦóּڴ IDE СҪorg.springframework.boot.devtools.RemoteSpringApplicationʹӵԶĿͬ·СӦóΨһӵԶ URL\n\n磬ʹ Eclipse  Spring Toolsһmy-appѲ Cloud Foundry ΪĿִ²\n\n1.  ѡRun Configurations?Run˵\n2.  һµJava Applicationá\n3.  my-appĿ\n4.  ʹorg.springframework.boot.devtools.RemoteSpringApplicationΪࡣ\n5.  https://myapp.cfapps.ioProgram argumentsκԶ URL\n\nеԶ̿ͻ˿б\n\n\n\n```\n  .   ____          _                                              __ _ _\n /\\\\ / ___'_ __ _ _(_)_ __  __ _          ___               _      \\ \\ \\ \\\n( ( )\\___ | '_ | '_| | '_ \\/ _` |        | _ \\___ _ __  ___| |_ ___ \\ \\ \\ \\\n \\\\/  ___)| |_)| | | | | || (_| []::::::[]   / -_) '  \\/ _ \\  _/ -_) ) ) ) )\n  '  |____| .__|_| |_|_| |_\\__, |        |_|_\\___|_|_|_\\___/\\__\\___|/ / / /\n =========|_|==============|___/===================================/_/_/_/\n :: Spring Boot Remote :: 2.5.4\n\n2015-06-10 18:25:06.632  INFO 14938 --- [           main] o.s.b.devtools.RemoteSpringApplication   : Starting RemoteSpringApplication on pwmbp with PID 14938 (/Users/pwebb/projects/spring-boot/code/spring-boot-project/spring-boot-devtools/target/classes started by pwebb in /Users/pwebb/projects/spring-boot/code)\n2015-06-10 18:25:06.671  INFO 14938 --- [           main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@2a17b7b6: startup date [Wed Jun 10 18:25:06 PDT 2015]; root of context hierarchy\n2015-06-10 18:25:07.043  WARN 14938 --- [           main] o.s.b.d.r.c.RemoteClientConfiguration    : The connection to http://localhost:8080 is insecure. You should use a URL starting with 'https://'.\n2015-06-10 18:25:07.074  INFO 14938 --- [           main] o.s.b.d.a.OptionalLiveReloadServer       : LiveReload server is running on port 35729\n2015-06-10 18:25:07.130  INFO 14938 --- [           main] o.s.b.devtools.RemoteSpringApplication   : Started RemoteSpringApplication in 0.74 seconds (JVM running for 1.105)\n\n```\n\n\n\n### [#](#devtoolΪλĬϽûѡ) devtoolΪλĬϽûѡ\n\n> Spring Boot ֵ֧һЩ**ʹû**磬ģ滺ѱģԱظģļ⣬Spring MVC ṩ̬ԴʱӦ HTTP ͷ\n\nȻ**зǳ棬ڿпܻʵ䷴**ʹ޷ոӦóĸġԭ spring-boot-devtools ĬϽûѡ\n\nThymeleaf ṩspring.thymeleaf.cacheģĻ棬ʹspring-boot-devtoolsģʱǲҪֶЩԵģΪspring-boot-devtoolsԶá\n\nôԶЩأDevToolsPropertyDefaultsPostProcessorҵӦĬá\n\n\n\n```\npublic class DevToolsPropertyDefaultsPostProcessor implements EnvironmentPostProcessor {\n\n\tstatic {\n\t\tMap<String, Object> properties = new HashMap<>();\n\t\tproperties.put(\"spring.thymeleaf.cache\", \"false\");\n\t\tproperties.put(\"spring.freemarker.cache\", \"false\");\n\t\tproperties.put(\"spring.groovy.template.cache\", \"false\");\n\t\tproperties.put(\"spring.mustache.cache\", \"false\");\n\t\tproperties.put(\"server.servlet.session.persistent\", \"true\");\n\t\tproperties.put(\"spring.h2.console.enabled\", \"true\");\n\t\tproperties.put(\"spring.web.resources.cache.period\", \"0\");\n\t\tproperties.put(\"spring.web.resources.chain.cache\", \"false\");\n\t\tproperties.put(\"spring.template.provider.cache\", \"false\");\n\t\tproperties.put(\"spring.mvc.log-resolved-exception\", \"true\");\n\t\tproperties.put(\"server.error.include-binding-errors\", \"ALWAYS\");\n\t\tproperties.put(\"server.error.include-message\", \"ALWAYS\");\n\t\tproperties.put(\"server.error.include-stacktrace\", \"ALWAYS\");\n\t\tproperties.put(\"server.servlet.jsp.init-parameters.development\", \"true\");\n\t\tproperties.put(\"spring.reactor.debug\", \"true\");\n\t\tPROPERTIES = Collections.unmodifiableMap(properties);\n\t}\n\n```\n\n\n\nȻ㲻뱻ӦԱspring-boot-devtoolsĬã ͨspring.devtools.add-propertiesfalseapplication.ymlС\n\n### [#](#devtoolǷԸspringbootӦȫֵ) devtoolǷԸSpringbootӦȫֵã\n\n> ͨspring-boot-devtools.ymlļӵ$HOME/.config/spring-bootĿ¼**ȫ devtools **\n\nӵЩļκԶʹ devtools Spring Boot Ӧó磬ҪΪʼʹôļҪӵspring-boot-devtoolsļУ\n\n\n\n```\nspring:\n  devtools:\n    restart:\n      trigger-file: \".reloadtrigger\"\n\n```\n\n\n\n### [#](#Ҳdevtool-ʲôѡ) Ҳdevtoolʲôѡ\n\n> Ҳdevtoolʲôѡ\n\n**ʵʵĿУҲȥʹdevtool**, Ϊ\n\n*   devtoolʽȻ滻JRebelǣշѵģ\n*   ҪĻһȨ\n    *   ԶĿֶûʲô̫ôֶ\n    *   £**ڲ޸Ļ߾̬Դ޸**IDEAǿͨRebuildCtrl + Shift + F9ȸ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230416211745.png)\n\n*   ⻹һspring loaded ʵ޸ļȲ𣬾ɿ[githubַ´ڴ](https://github.com/spring-projects/spring-loaded)ϵ˵\n\n## [#](#ʾԴ) ʾԴ\n\nhttps://github.com/realpdai/tech-pdai-spring-demos\n\n## [#](#ο) ο\n\nhttps://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.devtools\n\nhttps://liayun.blog.csdn.net/article/details/116541775\n\n* * *\n\nȨ@pdai ԭӣhttps://pdai.tech/md/spring/springboot/springboot-x-hello-devtool.html"
  },
  {
    "path": "docs/Spring全家桶/SpringBoot/SpringBoot集成Swagger实现API文档自动生成.md",
    "content": "\n\nĿ¼\n\n*   [SwaggerĽ](https://www.cnblogs.com/progor/p/13297904.html#swagger%E7%9A%84%E4%BB%8B%E7%BB%8D)\n    *   [ŵȱ](https://www.cnblogs.com/progor/p/13297904.html#%E4%BC%98%E7%82%B9%E4%B8%8E%E7%BC%BA%E7%82%B9)\n*   [swagger](https://www.cnblogs.com/progor/p/13297904.html#%E6%B7%BB%E5%8A%A0swagger)\n    *   [1.](https://www.cnblogs.com/progor/p/13297904.html#1%E6%B7%BB%E5%8A%A0%E4%BE%9D%E8%B5%96%E5%8C%85)\n    *   [2.Swagger:](https://www.cnblogs.com/progor/p/13297904.html#2%E9%85%8D%E7%BD%AEswagger)\n    *   [3.](https://www.cnblogs.com/progor/p/13297904.html#3%E6%B5%8B%E8%AF%95)\n*   [](https://www.cnblogs.com/progor/p/13297904.html#%E5%9C%BA%E6%99%AF)\n    *   [ӿ](https://www.cnblogs.com/progor/p/13297904.html#%E5%AE%9A%E4%B9%89%E6%8E%A5%E5%8F%A3%E7%BB%84)\n    *   [ӿ](https://www.cnblogs.com/progor/p/13297904.html#%E5%AE%9A%E4%B9%89%E6%8E%A5%E5%8F%A3)\n    *   [ӿ](https://www.cnblogs.com/progor/p/13297904.html#%E5%AE%9A%E4%B9%89%E6%8E%A5%E5%8F%A3%E8%AF%B7%E6%B1%82%E5%8F%82%E6%95%B0)\n        *   [һʵࡣ](https://www.cnblogs.com/progor/p/13297904.html#%E5%9C%BA%E6%99%AF%E4%B8%80%E8%AF%B7%E6%B1%82%E5%8F%82%E6%95%B0%E6%98%AF%E5%AE%9E%E4%BD%93%E7%B1%BB)\n        *   [Ƿʵࡣ](https://www.cnblogs.com/progor/p/13297904.html#%E5%9C%BA%E6%99%AF%E4%BA%8C%E8%AF%B7%E6%B1%82%E5%8F%82%E6%95%B0%E6%98%AF%E9%9D%9E%E5%AE%9E%E4%BD%93%E7%B1%BB)\n    *   [ӿӦ](https://www.cnblogs.com/progor/p/13297904.html#%E5%AE%9A%E4%B9%89%E6%8E%A5%E5%8F%A3%E5%93%8D%E5%BA%94)\n        *   [Ӧʵࣺ](https://www.cnblogs.com/progor/p/13297904.html#%E5%93%8D%E5%BA%94%E6%98%AF%E5%AE%9E%E4%BD%93%E7%B1%BB)\n        *   [ӦǷʵࣺ](https://www.cnblogs.com/progor/p/13297904.html#%E5%93%8D%E5%BA%94%E6%98%AF%E9%9D%9E%E5%AE%9E%E4%BD%93%E7%B1%BB)\n*   [Swagger UIǿ](https://www.cnblogs.com/progor/p/13297904.html#swagger-ui%E5%A2%9E%E5%BC%BA)\n    *   [UIԱȣ](https://www.cnblogs.com/progor/p/13297904.html#ui%E5%AF%B9%E6%AF%94)\n    *   [ʹ](https://www.cnblogs.com/progor/p/13297904.html#%E4%BD%BF%E7%94%A8)\n    *   [ŵ](https://www.cnblogs.com/progor/p/13297904.html#%E4%BC%98%E7%82%B9)\n*   [Spring Securityע](https://www.cnblogs.com/progor/p/13297904.html#%E6%95%B4%E5%90%88spring-security%E6%B3%A8%E6%84%8F)\n*   [tokenĴ](https://www.cnblogs.com/progor/p/13297904.html#%E5%AF%B9%E4%BA%8Etoken%E7%9A%84%E5%A4%84%E7%90%86)\n*   [Swaggerİȫ](https://www.cnblogs.com/progor/p/13297904.html#swagger%E7%9A%84%E5%AE%89%E5%85%A8%E7%AE%A1%E7%90%86)\n\n\n\n* * *\n\n# SwaggerĽ\n\n?ܳԹдһӿںԼȥӿĵ޸Ľӿں޸Ľӿĵ֮϶ᷢһǾ޸ĵߴĵǹ˾ѽӿĵдӿҪúܽ?дĵͿ۹ʣĹп©ģswaggerһдӿڵʱԶɽӿĵĶֻҪѭĹ淶дһЩӿڵ˵ע⼴ɡ\n\n## ŵȱ\n\n?ŵ㣺\n\n*   ԶĵֻҪڽӿʹעбעɶӦĽӿĵ\n*   ԶĵǶ̬ɵģ޸˽ӿڣĵҲԶӦ޸ģҲעĻͲᷢ޸˽ӿڣȴǸ½ӿĵ\n*   ֧ߵԣswaggerṩߵýӿڵĹܡ\n\n?ȱ㣺\n\n*   ܴʱܰ㴦е顣ֻṩһ򵥵ߵԣ洢ĲʹPostmanYAPIִ֧ûĹܡ\n*   ҪѭһЩ淶淶ġ˵ܻ᷵һjsonݣݿһMapʽģôǴʱܱעMapʽķݵÿֶε˵һʵĻǿͨעֶμ˵Ҳ˵swaggerƼʹGETʽύݵʱʹBodyƼʹqueryheader·Ȼֻߵԡ\n*   ûнӿĵ¹Ȼһӿڸ֮󣬿ܲľɰĽӿϢ㡰ܡ뿴ɰĽӿϢЩҶȸ·ʱܻľɰĽӿڡôʱֻɺȥûעˣԿԿǽӿĵµʱע;ɰģȻд°ġȻͨӿĵԱȡ\n*   ȻJavaʵвģͣpo,dto,voȣģ͵ΪһЩһû¼ʱֻҪusername,passwordȨ޵ʱҪȨޱϢʹUserʵĻĵоͻԶ˶ϢҪģʵ࣬¼ʱһLoginFormҪû-Ȩ޵ϢʱʹUserࡣȻˣswagger֮ʹžͻôˡ\n\n?ȱдеܻ࣬swaggerе󡣵ʵҪǹ淶⣬淶ʱֻĴ淶ԣͼʼˣǰʲôӿڵĲʹһ࣬swaggerҪֿĳֲĴ淶ԡ\n\n?ע´ʾSpring BootԲο[swagger-demo](https://github.com/alprogor/swagger-demo)\n\n* * *\n\n# swagger\n\n?ȽswaggerҲϽôʹãġٽ⡣\n\n## 1.\n\n?ע⣬ǰѾspring bootweb\n\nƴ\n\n```\n        <dependency>\n            <groupId>io.springfox</groupId>\n            springfox-swagger2\n            <version>2.9.2</version>\n        </dependency>\n        <dependency>\n            <groupId>io.springfox</groupId>\n            springfox-swagger-ui\n            <version>2.9.2</version>\n        </dependency>\n\n```\n\n## 2.Swagger:\n\nҪʹswaggerǱswaggerãҪһswagger࣬ΪSwaggerConfig.java\n\nƴ\n\n```\npackage com.example.config;\n\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport springfox.documentation.builders.ApiInfoBuilder;\nimport springfox.documentation.builders.PathSelectors;\nimport springfox.documentation.builders.RequestHandlerSelectors;\nimport springfox.documentation.service.ApiInfo;\nimport springfox.documentation.spi.DocumentationType;\nimport springfox.documentation.spring.web.plugins.Docket;\nimport springfox.documentation.swagger2.annotations.EnableSwagger2;\n\n@Configuration // \n@EnableSwagger2 //swagger\npublic class SwaggerConfig {\n    @Bean\n    public Docket createRestApi() {\n        return new Docket(DocumentationType.SWAGGER_2)  // DocumentationType.SWAGGER_2 ̶ģswagger2\n//                .groupName(\"ֲʽϵͳ\") // öĵʱôҪgroupNameʶ\n                .apiInfo(apiInfo()) // APIϢ\n                .select() // select()һApiSelectorBuilderʵ,ƽӿڱswaggerĵ\n                .apis(RequestHandlerSelectors.basePackage(\"com.example.controller\")) // ָɨĸµĽӿ\n                .paths(PathSelectors.any())// ѡеAPI,ֻΪAPIĵ\n                .build();\n    }\n\n    /**\n     * ڶAPIϢеAPIܱ⡢汾\n     * @return\n     */\n    private ApiInfo apiInfo() {\n        return new ApiInfoBuilder()\n                .title(\"XXĿAPI\") //  ԶAPI\n                .description(\"XXĿSwaggerAPI\") // API\n                .termsOfServiceUrl(\"\") // ڶ\n                .version(\"1.0\") // 汾\n                .build(); //\n    }\n}\n\n```\n\n## 3.\n\nǵSpring BootĿĬ8080˿ڣ㲻һע޸ĺurl`http://localhost:8080/swagger-ui.html`\nȻͿԿһµĽ棬ʱûýӿݣʾ`No operations defined in spec!`\n\n![20200711013419](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20200711013419.png)\n\n?ǽζӿڣԼswagger UIеݡ\n\n* * *\n\n# \n\n## ӿ\n\nӿʱӦǷģҴ󲿷ֶһcontrollerеģûصĽӿӦöUserControllerУôͬҵʱӦö/ֲͬĽӿ顣ӿʹ`@Api`֡\n磺\n\nƴ\n\n```\n@Api(tags = \"ɫ\") //  tagsԵ֡\n@RestController\npublic class RoleController {\n}\n\n```\n\n\n\nƴ\n\n```\n@Api(tags = \"û\") //  tagsԵ֡\n@RestController\npublic class UserController {\n}\n\n```\n\n?Ҳɻtags飬ͺһЩıǩһʹñǩࡣ\n?Controller£ӿ飩ûнӿڣôswagger uiǲʾģеĻͻʾ\n![20200712022545](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20200712022545.png)\n\n## ӿ\n\nʹ`@Api`עһController֮нӿڣôͻĬĵûԶ˵\n\nƴ\n\n```\n@Api(tags = \"û\")\n@RestController\npublic class UserController {\n    // ע⣬swaggerҪʹ@RequestMapping\n    // Ϊ@RequestMapping֧ʽswaggerΪӿ7ʽĽӿĵ\n    @GetMapping(\"/info\") \n    public String info(String id){\n        return \"aaa\";\n    }\n}\n\n```\n\n![20200711015840](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20200711015840.png)\n\n![20200711020149](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20200711020149.png)\n\nǿʹ`@ApiOperation`ӿڣ磺\n\nƴ\n\n```\n    @ApiOperation(value = \"û\",notes = \"ûnotes\")\n    @GetMapping(\"/test\")\n    public String test(String id){\n        return \"test\";\n    }\n\n```\n\n![20200711021112](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20200711021112.png)\n\n\n*   valueԵǽӿڵļ\n*   notesӿڵ\n*   tagsԶⶨӿ飬ӿѾ`@Api(tags = \"û\")`ӿڻֵˡûУԶʹtags`tags = \"ɫ\"`ýɫҲӿĵ\n\n## ӿ\n\nʹ`@ApiOperation`ӿڣʵȱٽӿ˵Ƿֳ\n?עһ£**GETʽswaggerƼʹbodyʽ**ҲǲϣGETʽʱʹjsonform-dataȷʽݣʱʹ·url(?ȻPOSTMANֵ֧)ӿڴݵjsonform-dataʽģʹPOSTʽá\n\n### һʵࡣ\n\nʱҪʹ`@ApiModel`עʵ࣬ȻڽӿжΪʵ༴ɣ\n\n*   @ApiModel\n    *   \n        *   valueʵ\n        *   descriptionʵ˵\n*   @ApiModelPropertyֶε塣\n    *   \n        *   valueֶ˵\n        *   exampleʾExample ValueĬֵãֶΪstringʱ򣬴ʱʾĬֵΪ\"\".\n        *   nameµֶɵֶ\n        *   allowableValuesֵ÷Χ`{1,2,3}`ֻȡֵ`[1,5]`ȡ15ֵ`(1,5)`15ֵ15ʹinfinity-infinityֵ`[1, infinity]`СֵΪ1ֵ\n        *   requiredֶǷĬfalse,\n        *   hiddenֶΣĬfalseҪҪʹtrueΪֶĬ϶ʾû`@ApiModelProperty`\n\nƴ\n\n```\n// ʹ@ApiModelע\n@ApiModel(value=\"û¼\",description=\"û¼\")\npublic class LoginForm {\n    // ʹApiModelPropertyעֶԡ\n    @ApiModelProperty(value = \"û\",required = true,example = \"root\")\n    private String username;\n    @ApiModelProperty(value = \"\",required = true,example = \"123456\")\n    private String password;\n\n    // ˴ʡθֵʱҪgetter,setter,swaggerҲҪ\n}\n\n```\n\nΣ\n\nƴ\n\n```\n    @ApiOperation(value = \"¼ӿ\",notes = \"¼ӿڵ˵\")\n    @PostMapping(\"/login\")\n    public LoginForm login(@RequestBody LoginForm loginForm){\n        return loginForm;\n    }\n\n```\n\nЧ\n\n![20200711181038](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20200711181038.png)\n\n### Ƿʵࡣ\n\n**˵һΣGETʽswaggerƼʹbodyʽݣȻSpring MVCԶװGETǲҪʹform-datajsonȷʽݲʹPostmanԽӿڣswagger߲ǲ֧**\nڷʵʹ`@ApiImplicitParams``@ApiImplicitParam`\n`@ApiImplicitParams`ڷͷϣ`@ApiImplicitParam``@ApiImplicitParams`棬һ`@ApiImplicitParam`Ӧһ\n`@ApiImplicitParam`\n\n*   name֣Ҳֶε,ӿڵӦ**ӦҲɣԿ**\n*   value\n*   requiredעǷ\n*   paramTypepath,query,body,form,headerȷʽڶڷʵʱ򣬳õֻpath,query,headerbodyformǲõġbodyڶɢֻjsonĽӿ`form-data`,`x-www-form-urlencoded`ʱܲʹswaggerҳAPIԣں潲BootstrapUIswaggerǿеԣBootstrapUIswaggerָ֧`form-data``x-www-form-urlencoded`\n\nʾһURL\n\nƴ\n\n```\n    // ʹURL query\n    @ApiOperation(value = \"¼ӿ2\",notes = \"¼ӿڵ˵2\")\n    @ApiImplicitParams({\n            @ApiImplicitParam(name = \"username\",//\n                    value = \"û\",//\n                    required = true,//Ƿ봫\n                    //paramTypeͣpath,query,body,form,header\n                    paramType = \"query\"\n                    )\n            ,\n            @ApiImplicitParam(name = \"password\",//\n                    value = \"\",//\n                    required = true,//Ƿ봫\n                    paramType = \"query\"\n                    )\n    })\n    @PostMapping(value = \"/login2\")\n    public LoginForm login2(String username,String password){\n        System.out.println(username+\":\"+password);\n        LoginForm loginForm = new LoginForm();\n        loginForm.setUsername(username);\n        loginForm.setPassword(password);\n        return loginForm;\n    }\n\n```\n\nʾURL·\n\nƴ\n\n```\n    // ʹ·\n    @PostMapping(\"/login3/{id1}/{id2}\")\n    @ApiOperation(value = \"¼ӿ3\",notes = \"¼ӿڵ˵3\")\n    @ApiImplicitParams({\n            @ApiImplicitParam(name = \"id1\",//\n                    value = \"û\",//\n                    required = true,//Ƿ봫\n                    //paramTypeͣpath,query,body,form,header\n                    paramType = \"path\"\n            )\n            ,\n            @ApiImplicitParam(name = \"id2\",//\n                    value = \"\",//\n                    required = true,//Ƿ봫\n                    paramType = \"path\"\n            )\n    })\n    public String login3(@PathVariable Integer id1,@PathVariable Integer id2){\n        return id1+\":\"+id2;\n    }\n\n```\n\nʾheader\n\nƴ\n\n```\n    // headerݲ\n    @PostMapping(\"/login4\")\n    @ApiOperation(value = \"¼ӿ4\",notes = \"¼ӿڵ˵4\")\n    @ApiImplicitParams({\n            @ApiImplicitParam(name = \"username\",//\n                    value = \"û\",//\n                    required = true,//Ƿ봫\n                    //paramTypeͣpath,query,body,form,header\n                    paramType = \"header\"\n            )\n            ,\n            @ApiImplicitParam(name = \"password\",//\n                    value = \"\",//\n                    required = true,//Ƿ봫\n                    paramType = \"header\"\n            )\n    })\n    public String login4( @RequestHeader String username,\n                          @RequestHeader String password){\n        return username+\":\"+password;\n    }\n\n```\n\nʾģļϴ\n\nƴ\n\n```\n    // ļϴʱҪ@ApiParam÷@ApiImplicitParamһ@ApiParamڲ\n    // ҲԲע⣬swaggerԶ˵\n    @ApiOperation(value = \"ϴļ\",notes = \"ϴļ\")\n    @PostMapping(value = \"/upload\")\n    public String upload(@ApiParam(value = \"ͼƬļ\", required = true)MultipartFile uploadFile){\n        String originalFilename = uploadFile.getOriginalFilename();\n\n        return originalFilename;\n    }\n\n    // ļϴʱ**swaggerֻܲԵļϴ**\n    @ApiOperation(value = \"ϴļ\",notes = \"ϴļ\")\n    @PostMapping(value = \"/upload2\",consumes = \"multipart/*\", headers = \"content-type=multipart/form-data\")\n    public String upload2(@ApiParam(value = \"ͼƬļ\", required = true,allowMultiple = true)MultipartFile[] uploadFile){\n        StringBuffer sb = new StringBuffer();\n        for (int i = 0; i < uploadFile.length; i++) {\n            System.out.println(uploadFile[i].getOriginalFilename());\n            sb.append(uploadFile[i].getOriginalFilename());\n            sb.append(\",\");\n        }\n        return sb.toString();\n    }\n\n    // ļв\n    @ApiOperation(value = \"ļв\",notes = \"ļв\")\n    @PostMapping(value = \"/upload3\")\n    @ApiImplicitParams({\n            @ApiImplicitParam(name = \"name\",\n                    value = \"ͼƬ\",\n                    required = true\n            )\n    })\n    public String upload3(@ApiParam(value = \"ͼƬļ\", required = true)MultipartFile uploadFile,\n                          String name){\n        String originalFilename = uploadFile.getOriginalFilename();\n\n        return originalFilename+\":\"+name;\n    }\n\n```\n\n## ӿӦ\n\nӿӦǷ鿴ӿĵܹ֪ӿڷصݵ塣\n\n### Ӧʵࣺ\n\nǰڶӿʱᵽʹ`@ApiModel`ע࣬ӿڷ࣬ôϵ˵ҲΪӦ˵\n\nƴ\n\n```\n    // ر@ApiModelע\n    @ApiOperation(value = \"ʵӦ\",notes = \"ΪʵĽӿ\")\n    @PostMapping(\"/role1\")\n    public LoginForm role1(@RequestBody LoginForm loginForm){\n        return loginForm;\n    }\n\n```\n\n![20200712000406](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20200712000406.png)\n\n### ӦǷʵࣺ\n\nswagger޷ԷʵӦϸ˵ֻܱעӦϢͨ`@ApiResponses``@ApiResponse`ʵֵġ\n`@ApiResponses``@ApiResponse``@ApiModel`һʹá\n\nƴ\n\n```\n    // ͵,ʱֶעͣʵswaggerƼʹʵ\n    @ApiOperation(value = \"ʵ\",notes = \"ʵ\")\n    @ApiResponses({\n            @ApiResponse(code=200,message = \"óɹ\"),\n            @ApiResponse(code=401,message = \"Ȩ\" )\n    }\n    )\n    @PostMapping(\"/role2\")\n    public String role2(){\n        return \" {\\n\" +\n                \" name:\\\"㶫\\\",\\n\" +\n                \"     citys:{\\n\" +\n                \"         city:[\\\"\\\",\\\"\\\",\\\"麣\\\"]\\n\" +\n                \"     }\\n\" +\n                \" }\";\n    }\n\n```\n\n![20200712013503](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20200712013503.png)\n\n* * *\n\n# Swagger UIǿ\n\nܻUIǺܺÿһЩṩһЩSwagger UIǿȽе`swagger-bootstrap-ui``swagger-bootstrap-ui`Ϊ\n\n## UIԱȣ\n\n![20200712013653](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20200712013653.png)\n\n![20200712013723](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20200712013723.png)\n\n## ʹ\n\n1.\n\nƴ\n\n```\n        <!--swagger-->\n        <dependency>\n            <groupId>io.springfox</groupId>\n            springfox-swagger2\n            <version>2.9.2</version>\n        </dependency>\n        <dependency>\n            <groupId>io.springfox</groupId>\n            springfox-swagger-ui\n            <version>2.9.2</version>\n        </dependency>\n        <!-- swagger-bootstrap-ui-->\n        <dependency>\n            <groupId>com.github.xiaoymin</groupId>\n            swagger-bootstrap-ui\n            <version>1.8.7</version>\n        </dependency>\n\n```\n\n2.swaggerע`@EnableSwaggerBootstrapUI`:\n\nƴ\n\n```\n@Configuration // \n@EnableSwagger2 //swagger\n@EnableSwaggerBootstrapUI // SwaggerBootstrapUI\npublic class SwaggerConfig {\n    // ʡ\n}\n\n```\n\n3.API`http://localhost:8080/doc.html`ԤbootstarpSwagger UI档\n\n## ŵ\n\n1.?ÿһ\n\n2.˵ˣBootstrapUIswaggerָ֧`form-data``x-www-form-urlencoded`\n![20200712024858](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20200712024858.png)\n\n3.ָ֧ƵAPIĵ͵ȫAPIĵ\n![20200712025020](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20200712025020.png)\n\n![20200712025044](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20200712025044.png)\n\n* * *\n\n# Spring Securityע\n\nSpring BootSpring SecuritySwaggerʱҪص·ͷе·עǷ¼·\n\nƴ\n\n```\n.antMatchers(\"/swagger**/**\").permitAll()\n.antMatchers(\"/webjars/**\").permitAll()\n.antMatchers(\"/v2/**\").permitAll()\n.antMatchers(\"/doc.html\").permitAll() // bootstarpSwagger UI棬һ\n\n```\n\n* * *\n\n# tokenĴ\n\nswaggerֻ֧˼򵥵ĵԣһЩӿڣǲԵʱҪtokenϢдheaderУĿǰûԶͷĵط\n?һ\nʹSwagger BootstrapUIôڡĵȫֲheader\n\n?swaggerȫֲã\n\nƴ\n\n```\n        //жȫֲ˵ͷ\n        ParameterBuilder parameterBuilder = new ParameterBuilder();\n        List<Parameter> parameters = new ArrayList<Parameter>();\n        parameterBuilder.name(\"authorization\").description(\"\")\n                .modelRef(new ModelRef(\"string\")).parameterType(\"header\").required(false).build();\n        parameters.add(parameterBuilder.build());\n        return new Docket(DocumentationType.SWAGGER_2)  // DocumentationType.SWAGGER_2 ̶ģswagger2\n                .apiInfo(apiInfo()) // APIϢ\n                .select() // select()һApiSelectorBuilderʵ,ƽӿڱswaggerĵ\n                .apis(RequestHandlerSelectors.basePackage(\"com.example.controller\")) // ָɨĸµĽӿ\n                .paths(PathSelectors.any())// ѡеAPI,ֻΪAPIĵ\n                .build().globalOperationParameters(parameters);\n\n```\n\n?ʹ`@ApiImplicitParams`עһͷ磺\n\nƴ\n\n```\n    // ҪĲǱõҪ,Ȩtoken\n    @PostMapping(\"/login6\")\n    @ApiOperation(value = \"tokenĽӿ\",notes = \"tokenĽӿ\")\n    @ApiImplicitParams({\n            @ApiImplicitParam(name = \"authorization\",//\n                    value = \"Ȩtoken\",//\n                    required = true,//Ƿ봫\n                    paramType = \"header\"\n            )\n            ,\n            @ApiImplicitParam(name = \"username\",//\n                    value = \"û\",//\n                    required = true,//Ƿ봫\n                    paramType = \"query\"\n            )\n    })\n    public String login6(String username){\n        return username;\n    }\n\n```\n\n* * *\n\n# Swaggerİȫ\n\n1.Ȩ޹ԸswaggerȨ޹Ҫswaggerҳû룬Щspring securityshiroˣﲻ\n\n2.ǲʽпԷʣʽйرSwaggerԶãͲswaggerҳˡʹ`@Profile({\"dev\",\"test\"})`עֻdevtestSwaggerԶá\nȻSpring Bootļ޸ĵǰprofile`spring.profiles.active=release`֮󣬴ʱ޷`http://localhost:8080/swagger-ui.html`\n\n* * *\n\n\n\nߣ[progor](https://www.cnblogs.com/progor/)\nΪԭתע\n\n"
  },
  {
    "path": "docs/Spring全家桶/SpringBoot/Spring常见注解使用指南(包含Spring+SpringMVC+SpringBoot).md",
    "content": "# Springע\n## 1 \n\nǶ֪SpringĵԾIOC+AOPIOCԭʵһSpringSpring Beanʵ\nDIҲע룬ΪҪĵĺĻ⣬עעĸҪȷ֪ġ\nǰϰxmlļһbeanڣǸʹעʹDIĹ\n\n\nǿʹ org.springframework.beans.factory.annotation  org.springframework.context.annotation еע Spring DI Ĺܡ\n\nͨЩΪSpring ע͡ǽڱ̳жлعˡ\n\n\n## 2 DIע\n\n### 2.1 @Autowired\n\nǿʹ @Autowired  Spring Ҫע\nǿԽע빹캯setter ֶעһʹá\nConstructor injection:\n\n**ע**\n\n````\nclass Car {\n    Engine engine;\n\n    @Autowired\n    Car(Engine engine) {\n        this.engine = engine;\n    }\n}\n````\n\n**Setterע**\n````\nclass Car {\n    Engine engine;\n\n    @Autowired\n    void setEngine(Engine engine) {\n        this.engine = engine;\n    }\n}\n````\n**ע**\n````\nclass Car {\n    @Autowired\n    Engine engine;\n}\n````\n\n@Autowired һΪ required ĲĬֵΪ true\n\nҲʵ bean ʱ Spring Ϊ Ϊ true ʱ׳쳣κݡ\n\nע⣬ʹù캯ע룬й캯ǿԵġ\n\n 4.3 汾ʼǲҪʽʹ @Autowired ע⹹캯캯\n\n### 2.2 @Bean\n\n@Bean ʵ Spring bean Ĺ\n\n```\n@Bean\nEngine engine() {\n    return new Engine();\n}\n````\n\nҪ͵ʵʱSpring Щ\n\nɵ bean 빤ͬ Բͬķʽǿʹôע͵ƻֵֵǲƵı\n\n````\n@Bean(\"engine\")\nEngine getEngine() {\n    return new Engine();\n}\n````\nһַǳbeanʽΪܶbeanһʼڴﶨõģҪʱа蹹\n\nǿɵͶBeanҲԸԶbeanơ\n\nע⣬@Bean ע͵ķ@Configuration С\n\n### 2.3 @Qualifier\n\nʹ@Qualifier @Autowired ṩҪڲȷʹõbean id bean ơ\n\n磬 bean ʵͬĽӿڣ\n````\nclass Bike implements Vehicle {}\n\nclass Car implements Vehicle {}\n\n````\n\n Spring Ҫעһ Vehicle beanԶƥ䶨 £ǿʹ @Qualifier עʽṩ bean ơ\n\n**ע**\n````\n@Autowired\nBiker(@Qualifier(\"bike\") Vehicle vehicle) {\nthis.vehicle = vehicle;\n}\n````\n\n**Setterע**\n\n````\n@Autowired\nvoid setVehicle(@Qualifier(\"bike\") Vehicle vehicle) {\nthis.vehicle = vehicle;\n}\n````\n:\n\n````\n@Autowired\n@Qualifier(\"bike\")\nvoid setVehicle(Vehicle vehicle) {\nthis.vehicle = vehicle;\n````\n**ע**\n\n````\n@Autowired\n@Qualifier(\"bike\")\nVehicle vehicle;\n````\nעǿƽõĲ࣬ǵһӿжʵʱͻᾭó\n\n### 2.4 @Required\n\n@Required  setter ϱҪͨ XML \n````\n@Required\nvoid setColor(String color) {\nthis.color = color;\n}\n````\nxml\n````\n<bean class=\"com.baeldung.annotations.Bike\">\n    <property name=\"color\" value=\"green\" />\n</bean>\n````\n򣬽׳ BeanInitializationException\nǳټ÷֪һ¾\n\n### 2.5 @Value\nǿʹ @Value ֵע bean 빹캯setter ֶעݡ\n\nҲǷǳõһע⣬ΪǺܶʱҪapplication.propertiesļȡֵ\n\n**ע**\n````\nEngine(@Value(\"8\") int cylinderCount) {\nthis.cylinderCount = cylinderCount;\n}\n````\n\n**setterע**\n\n````\n@Autowired\nvoid setCylinderCount(@Value(\"8\") int cylinderCount) {\nthis.cylinderCount = cylinderCount;\n}\n````\n\n:\n````\n\n@Value(\"8\")\nvoid setCylinderCount(int cylinderCount) {\nthis.cylinderCount = cylinderCount;\n}\n````\n\n**ע**\n````\n@Value(\"8\")\nint cylinderCount;\n````\n\nȻע뾲ֵ̬ûõġ ˣǿ @Value ʹռλַⲿԴжֵ .properties  .yaml ļС\n````\n\nengine.fuelType=petrol\n````\n\nǿͨ·ʽע engine.fuelType ֵ\n\n````\n@Value(\"${engine.fuelType}\")\nString fuelType;\n````\n\nʹ SpEL ʹ@Value ߼ʾǹ@Value ҵ\n\n### 2.6 @DependsOn\nǿʹôע Spring ע bean ֮ǰʼ bean ͨΪԶģ bean ֮ʽϵ\n\nֻʽʱҪע⣬JDBCػ߾̬ʼ\n\nǿָ bean Ƶʹ @DependsOn ע͵ֵҪһ bean Ƶ飺\n\n````\n@DependsOn(\"engine\")\nclass Car implements Vehicle {}\n````\nAlternatively, if we define a bean with the @Bean annotation, the factory method should be annotated with @DependsOn:\n````\n@Bean\n@DependsOn(\"fuel\")\nEngine engine() {\nreturn new Engine();\n}\n````\n### 2.7 @Lazy\nسʼǵ bean ʱʹ @Lazy Ĭ£Spring Ӧóĵ/ʱеشе bean\n\nǣЩҪʱ beanӦóʱ\n\nעΪǷȷλöͬ ǿ԰ڣ\n\nһ @Bean ע͵ bean ӳٷã˴ bean\n@Configuration а@Bean ܵӰ\n\nһ @Component ࣬ @Configuration ࣬ bean ӳٳʼ\n\n@Autowired 캯setter ֶΣӳټͨ\n\n````\n@Configuration\n@Lazy\nclass VehicleFactoryConfig {\n\n    @Bean\n    @Lazy(false)\n    Engine engine() {\n        return new Engine();\n    }\n}\n````\nͬһõע⡣\n\nάһдbeanĿʱᷢкܶbeanܶǰʹõģһҪʼʱͽгʼ԰ǽʡܶʱܡ\n\n\n### 2.8 @Lookup\nͬһȽõע\n\n@Lookup  Spring ǵʱط͵ʵ\n\nϣSpring Ǵע͵ķʹǷķͺͲΪ BeanFactory#getBean Ĳ\n\n@Lookup ڣ\n\nԭ bean עԼbean Provider\n\nɾӵһԭ Spring beanôǼ⣺\n\nǵĵ Spring bean ηЩԭ Spring bean\n\nڣProvider ϶һַʽ @Lookup ĳЩͨá\n\nҪעǣspringĬʹõĵbeanҪעԭbeanǲҪĶ⹤\n\nȣǴһԭ beanԺǽע뵽 bean У\n````\n@Component\n@Scope(\"prototype\")\npublic class SchoolNotification {\n// ... prototype-scoped state\n}\n````\nʹ@Lookupǿͨ bean ȡ SchoolNotification ʵ\n\n````\n@Component\npublic class StudentServices {\n\n    // ... member variables, etc.\n\n    @Lookup\n    public SchoolNotification getNotification() {\n        return null;\n    }\n\n    // ... getters and setters\n}\n````\nUsing @Lookup, we can get an instance of SchoolNotification through our singleton bean:\n````\n@Test\npublic void whenLookupMethodCalled_thenNewInstanceReturned() {\n// ... initialize context\nStudentServices first = this.context.getBean(StudentServices.class);\nStudentServices second = this.context.getBean(StudentServices.class);\n\n    assertEquals(first, second); \n    assertNotEquals(first.getNotification(), second.getNotification()); \n}\n````\nע⣬ StudentServices Уǽ getNotification Ϊ\n\nΪ Spring ͨ beanFactory.getBean(StudentNotification.class) ˸÷ǿԽա\n\n\n### 2.9 @Primary\nʱҪͬ͵bean Щ£ע뽫ɹΪ Spring ֪Ҫĸ bean\n\nѾ˴ѡ@Qualifier нߵ㲢ָ bean ơ\n\nȻʱҪһض beanҪ bean\n\nǿʹ@Primary @Primary õbeanunqualifiedעϱѡ\n\n````\n@Component\n@Primary\nclass Car implements Vehicle {}\n\n@Component\nclass Bike implements Vehicle {}\n\n@Component\nclass Driver {\n@Autowired\nVehicle vehicle;\n}\n\n@Component\nclass Biker {\n@Autowired\n@Qualifier(\"bike\")\nVehicle vehicle;\n}\n````\nǰʾУҪ ˣ Driver УSpring עһ Car bean Ȼ Biker bean Уֶ vehicle ֵһ Bike Ϊqualifiedġ\n\n### 2.10 @Scope\n\nͨӦ˵beanscopeĬ϶ǵģʵspring beanֶֶ֧÷ΧŲͬڡ\n\nʹ@Scope @Component @Bean ķΧ ǵԭ͡󡢻ỰglobalSession һЩԶ巶Χ\n\nӦöֵΪ\n````\nsingleton\nprototype\nrequest\nsession\napplication\nwebsocket\n````\n\n\n````\n@Component\n@Scope(\"prototype\")\nclass Engine {}\n````\nǿһЩrequestsessionwebsocketbeanͨӦǺصģô洢ûϢsession֮bean\n\nôʹãҪľ峡ѡˣһܴĻ⣬ȲչˣԺڵܡ\n\n## 3 ע\nǿʹñעӦóġ\n\n\n### 3.1 @Profile\n\nϣ Spring ضļڻ״̬ʱʹ@Component @Bean ǿʹ@Profile бǡ\n\nǿʹע͵ֵļƣ\n\nͨעòͬá\n\n\n````\npublic interface DatasourceConfig {\npublic void setup();\n}\n````\n\nǿã\n\n````\n@Component\n@Profile(\"dev\")\npublic class DevDatasourceConfig implements DatasourceConfig {\n@Override\npublic void setup() {\nSystem.out.println(\"Setting up datasource for DEV environment. \");\n}\n}\n````\nã\n\n````\n@Component\n@Profile(\"production\")\npublic class ProductionDatasourceConfig implements DatasourceConfig {\n@Override\npublic void setup() {\nSystem.out.println(\"Setting up datasource for PRODUCTION environment. \");\n}\n}\n````\nȻҲʹxml͵ļbean\n\nxml\n````\n<beans profile=\"local\">\n    <bean id=\"localDatasourceConfig\" \n      class=\"org.test.profiles.LocalDatasourceConfig\" />\n</beans>\n````\n### 3.2 @Import\n\nǿʹض @Configuration ࣬ʹôעɨ衣 ǿΪЩṩ@Import ֵ\n\n˵Ҫõ@ConfigurationעbeanôspringӦñҪɨ赽Ŀ¼ǡûжԸ·ɨ裬ֻʹ·µĵ࣬ôǾͿʹ@Importˡ\n\nע⻹Ƿǳõģһ\n\n````\n@Import(VehiclePartSupplier.class)\nclass VehicleFactoryConfig {}\n\n@Configuration\nclass VehiclePartSupplier{\n}\n````\n\n### 3.3 @ImportResource\n\n˵һ bean.xml ļҪ beans.xml ж bean 뵽 Spring Boot Уβأ\n\n1.Spring ʽļ bean.xml ˴ٸʾ˵ xml һ helloServiceʾ\n````\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<beans xmlns=\"http://www.springframework.org/schema/beans\"\nxmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\nxsi:schemaLocation=\"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd\">\n\n    <!-- HelloService xmlķʽ,ע뵽-->\n    <bean id=\"helloService\" class=\"com.demo.springboot.service.HelloService\"></bean>\n</beans>\n````\n2.ʹ@ImportResourceע⣬ xml \n````\n/**\n * Spring BootûSpringļԼдļҲԶʶ\n * SpringļЧصSpring \n * ʹ@ImportResourceע⣬עһ(˴)\n */\n@SpringBootApplication\n@ImportResource(locations = {\"classpath:beans.xml\"})\npublic class BootApplication {\n\n    public static void main(String[] args) {\n        // SpringӦ         \n        SpringApplication.run(BootApplication.class,args);\n\n    }\n}\n\n````\n### 3.4 @PropertySource\nͨע⣬ǿΪӦóöļ\n\n@PropertySource עṩһַԻƣڽ PropertySource ӵ Spring  Environment У @Configuration һʹá\n\nʹ @Value ȥöԣ磺@Value(\"testbean.name\")ҲָĬֵ磺@Value(\"testbean.name:defaultValue\")\n\n÷ʾ\n\nһļapp.properties\n````\ntestbean.name=myTestBean\n````\n @Configuration ʹ @PropertySource  app.properties ø Environment  PropertySources ϡ\n````\n@Configuration\n@PropertySource(\"classpath:/com/myco/app.properties\")\npublic class AppConfig {\n\n    @Autowired\n    Environment env;\n \n    @Bean\n    public TestBean testBean() {\n        TestBean testBean = new TestBean();\n        testBean.setName(env.getProperty(\"testbean.name\"));\n        return testBean;\n    }\n}\n````\n\nע⣺ʹ @Autowired  Environment ע뵽УȻ testBean() ʹá\nУ testBean.getName() ءmyTestBeanַ\n\n@PropertySource  Java 8 ظעԣζǿαһࣺ\n\n````\n@Configuration\n@PropertySource(\"classpath:/annotations.properties\")\n@PropertySource(\"classpath:/vehicle-factory.properties\")\nclass VehicleFactoryConfig {}\n````\n\n### 3.5 @PropertySources\n÷ͬϣֻһǿʹעָ@PropertySource ã\n````\n@Configuration\n@PropertySources({\n@PropertySource(\"classpath:/annotations.properties\"),\n@PropertySource(\"classpath:/vehicle-factory.properties\")\n})\nclass VehicleFactoryConfig {}\n````\nע⣬ Java 8 ǿͨظע͹ʵͬĹܡ\n\n## 4.\n\nڱУǿ Spring ע͵ĸ ǿ bean ӺӦģԼΪɨࡣ\n\nspringϵеĳע⻹кܶ࣬һƪ²ȫǣ©ӭ䡣\n\n# Spring Beanע\n\n## 1 \nڱ̳Уǽڶ岻ͬ bean  Spring bean ע͡\n\nмַ Spring  bean ȣǿʹ XML ǡ ǻʹ@Bean ע bean\n\nǿʹ org.springframework.stereotype еע֮һǸ࣬ಿɨ衣\n\n## 2 @ComponentScan\nǾʹõһע⣬ǵӦУʱһɨеİرǵҪɨⲿjarеbeanʱǳá\n\nԼSpringBootApplicationϣҲԼ@configurationעϵ\n\nɨ裬Spring Զɨе bean\n\n@ComponentScan ʹעɨЩࡣ\n\nǿֱʹ basePackages  value ֮һָƣvalue  basePackages ı\n\n````\n@Configuration\n@ComponentScan(basePackages = \"com.baeldung.annotations\")\nclass VehicleFactoryConfig {}\n````\n⣬ǿʹ basePackageClasses ָеࣺ\n\n````\n@Configuration\n@ComponentScan(basePackageClasses = VehicleFactoryConfig.class)\nclass VehicleFactoryConfig {}\n````\n\n飬ǿΪÿṩ\n\nδָɨ跢ڴ @ComponentScan עͬһС\n\n@ComponentScan  Java 8 ظעԣζǿαһࣺ\n\n````\n@Configuration\n@ComponentScan(basePackages = \"com.baeldung.annotations\")\n@ComponentScan(basePackageClasses = VehicleFactoryConfig.class)\nclass VehicleFactoryConfig {}\n````\n\nߣǿʹ @ComponentScans ָ @ComponentScan ã\n\n````\n@Configuration\n@ComponentScans({\n@ComponentScan(basePackages = \"com.baeldung.annotations\"),\n@ComponentScan(basePackageClasses = VehicleFactoryConfig.class)\n})\n````\n````\nclass VehicleFactoryConfig {\n}\n````\nʹ XML ʱɨͬ򵥣\n\n````\n<context:component-scan base-package=\"com.baeldung\"/>\n````\n\n### 3 @Component\n\n@Component ༶ע⡣ ɨڼ䣬Spring Framework Զʹ@Component עࣺ\n````\n@Component\nclass CarUtility {\n// ...\n}\n````\n\nĬ£ bean ʵͬĸСд ⣬ǿʹôע͵Ŀѡֵָͬơ\n\n@Repository@Service@Configuration @Controller Ǵ@Component ע⣬ǹͬbean Ϊ\n\nSpring ɨԶǡ\n\nͨ˵ǻmvcӦǻõע⣬ڷwebӦиؿʹ@componentעbean\n\n### 4 @Repository\n\nDAO or Repository classes usually represent the database access layer in an application, and should be annotated with @Repository:\n````\n@Repository\nclass VehicleRepository {\n// ...\n}\n````\nʹôע͵һŵԶ־쳣ת ʹó־Կܣ Hibernateʱʹ @Repository ע͵׳ı쳣ԶתΪ Spring  DataAccessExeption ࡣ\n\nҪ쳣תҪԼ PersistenceExceptionTranslationPostProcessor bean\n````\n@Bean\npublic PersistenceExceptionTranslationPostProcessor exceptionTranslation() {\nreturn new PersistenceExceptionTranslationPostProcessor();\n}\n````\nע⣬ڴ£Spring Զִ衣\n\nͨ XML ã\n````\n<bean class=\n\"org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor\"/>\n````\n\n### 5 @Service\nӦóҵ߼ͨפڷУǽʹ@Service עָʾһڸò㣺\n\n````\n@Service\npublic class VehicleService {\n// ...    \n}\n````\n### 6 @Controller\n@Controller һ༶ע⣬ Spring Framework Ϊ Spring MVC еĿ\n\nspring@Controller עbeanܶ飬ǻSpringMVCص\n\n````\n@Controller\npublic class VehicleController {\n// ...\n}\n\n````\n## 7 @Configuration\n\n԰@Bean ע͵ bean 巽\n````\n@Configuration\nclass VehicleFactoryConfig {\n\n    @Bean\n    Engine engine() {\n        return new Engine();\n    }\n\n}\n````\n## 8 AOPע\nʹ Spring עʱ״һ㣬оض͵ΪĿꡣ\n\n磬 DAO 㷽ִʱ䡣 ǽ·棨ʹ AspectJ עͣ @Repository ͣ\n\n```\n@Aspect\n@Component\npublic class PerformanceAspect {\n@Pointcut(\"within(@org.springframework.stereotype.Repository *)\")\npublic void repositoryClassMethods() {};\n\n    @Around(\"repositoryClassMethods()\")\n    public Object measureMethodExecutionTime(ProceedingJoinPoint joinPoint) \n      throws Throwable {\n        long start = System.nanoTime();\n        Object returnValue = joinPoint.proceed();\n        long end = System.nanoTime();\n        String methodName = joinPoint.getSignature().getName();\n        System.out.println(\n          \"Execution of \" + methodName + \" took \" + \n          TimeUnit.NANOSECONDS.toMillis(end - start) + \" ms\");\n        return returnValue;\n    }\n}\n````\n\nڴʾУǴһ㣬ƥʹ@Repository ע͵ез Ȼʹ@Around ֪ͨλǸ㣬ȷطõִʱ䡣\n\n⣬ʹַǿΪÿӦó־¼ܹƺΪ\n\nȻˣaspectJע⻹ܶ࣬棬δҲᵥдĽܡ\n\n## 9 \n\nڱУǼ Spring עͲǸԴ͡\n\nǻѧϰʹɨҵע͵ࡣ\n\n˽Щעε¸ɾֲԼӦóע֮ķ롣 ǻʹøСΪǲҪֶʽ bean\n# SpringMVCע\n## 1. \nڱ̳Уǽ̽ org.springframework.web.bind.annotation е Spring Web ע͡\n\n## 2. @RequestMapping\n\n򵥵˵@RequestMapping @Controller ڲ򷽷 ʹã\n\n·ƺֵӳ䵽ĸ URL\nݵ HTTP \nparams HTTP Ĵڡڻֵ\nheaders HTTP ͷĴڡڻֵ\nģ÷ HTTP Щý\nproduces÷ HTTP ӦЩý\nһ򵥵ʾ\n\n````\n@Controller\nclass VehicleController {\n\n    @RequestMapping(value = \"/vehicles/home\", method = RequestMethod.GET)\n    String home() {\n        return \"home\";\n    }\n}\n````\n༶ӦôעͣǿΪ @Controller ед򷽷ṩĬá Ψһ Spring ʹ÷øǵḽ·ֵ URL\n\n磬úЧһģ\n\n````\n@Controller\n@RequestMapping(value = \"/vehicles\", method = RequestMethod.GET)\nclass VehicleController {\n\n    @RequestMapping(\"/home\")\n    String home() {\n        return \"home\";\n    }\n}\n````\n\n⣬@GetMapping@PostMapping@PutMapping@DeleteMapping @PatchMapping @RequestMapping Ĳͬ壬HTTP ѷֱΪGETPOSTPUTDELETE PATCH\n\nЩ Spring 4.3 汾ʼá\n\n## 3 @RequestBody\n\nǼ@RequestBody HTTP ӳ䵽һ\n\n````\n@PostMapping(\"/save\")\nvoid saveVehicle(@RequestBody Vehicle vehicle) {\n// ...\n}\n````\nлԶģȡ͡\n\n## 4 @PathVariable\n˵˵@PathVariable\n\nעָʾ󶨵 URI ģ ǿʹ @RequestMapping עָ URI ģ壬ʹ @PathVariable 󶨵ģ岿֮һ\n\nǿʹƻֵʵһ㣺\n\n````\n@RequestMapping(\"/{id}\")\nVehicle getVehicle(@PathVariable(\"id\") long id) {\n// ...\n}\n````\nģвֵ뷽ƥ䣬ǾͲעָ\n\n````\n@RequestMapping(\"/{id}\")\nVehicle getVehicle(@PathVariable long id) {\n// ...\n}\n````\n⣬ǿͨĲΪ false ·Ϊѡ\n\n````\n@RequestMapping(\"/{id}\")\nVehicle getVehicle(@PathVariable(required = false) long id) {\n// ...\n}\n````\n## 5. @RequestParam\nWe use @RequestParam for accessing HTTP request parameters:\n````\n@RequestMapping\nVehicle getVehicleByParam(@RequestParam(\"id\") long id) {\n// ...\n}\n````\n @PathVariable עͬѡ\n\nЩ֮⣬ Spring зûֵΪֵʱǿʹ @RequestParam ָעֵ ΪˣǱ defaultValue \n\nṩĬֵʽ required Ϊ false\n````\n@RequestMapping(\"/buy\")\nCar buyCar(@RequestParam(defaultValue = \"5\") int seatCount) {\n// ...\n}\n````\n˲֮⣬ǻԷ HTTP 󲿷֣cookie ͱͷ\n\nǿԷֱʹע@CookieValue @RequestHeader ǡ\n\n\n## 6. Response Handling Annotations\nڽĲУǽ Spring MVC в HTTP Ӧע͡\n\n### 6.1 @ResponseBody\n@ResponseBody 򷽷Spring ὫĽΪӦ\n\n````\n@ResponseBody\n@RequestMapping(\"/hello\")\nString hello() {\nreturn \"Hello World!\";\n}\n````\nעע @Controller ࣬򷽷ʹ\n\n### 6.2 @ExceptionHandler\n\nʹôעͣǿһԶ򷽷 򷽷׳κָ쳣ʱSpring ô˷\n\n쳣Ϊݸ\n````\n@ExceptionHandler(IllegalArgumentException.class)\nvoid onIllegalArgumentException(IllegalArgumentException exception) {\n// ...\n}\n````\n\n### 6.3 @ResponseStatus\nʹôעͶ򷽷עָͣӦ HTTP ״̬ ǿʹ code  value ״̬롣\n\n⣬ǿʹ reason ṩԭ\n\nҲԽ@ExceptionHandler һʹã\n\n@ExceptionHandler(IllegalArgumentException.class)\n@ResponseStatus(HttpStatus.BAD_REQUEST)\nvoid onIllegalArgumentException(IllegalArgumentException exception) {\n// ...\n}\n\nй HTTP Ӧ״̬ĸϢʱġ\n\n## 7  Webע\nһЩעͲֱӹ HTTP Ӧ ڽĲУǽġ\n\n### 7.1 @Controller\nǿʹ@Controller һSpring MVC  йظϢǹ Spring Bean Annotations ¡\n\n### 7.2 @RestController\n@RestController @Controller @ResponseBody\n\nˣǵЧģ\n\n````\n@Controller\n@ResponseBody\nclass VehicleRestController {\n// ...\n}\n````\n\n````\n@RestController\nclass VehicleRestController {\n// ...\n}\n````\n### 7.3 @ModelAttribute\nͨע⣬ǿͨṩģͼѾ MVC @Controller ģеԪأ\n\n````\n@PostMapping(\"/assemble\")\nvoid assembleVehicle(@ModelAttribute(\"vehicle\") Vehicle vehicleInModel) {\n// ...\n}\n````\n@PathVariable @RequestParam һͬƣǲָģͼ\n\n````\n@PostMapping(\"/assemble\")\nvoid assembleVehicle(@ModelAttribute Vehicle vehicle) {\n// ...\n}\n````\n⣬@ModelAttributeһ;עһSpringԶķֵӵģУ\n\n````\n@ModelAttribute(\"vehicle\")\nVehicle getVehicle() {\n// ...\n}\n````\nǰһǲָģͼSpring Ĭʹ÷ƣ\n````\n@ModelAttribute\nVehicle vehicle() {\n// ...\n}\n````\n Spring 򷽷֮ǰ @ModelAttribute ע͵ķ\n\nй @ModelAttribute ĸϢıġ\n\n### 7.4 @CrossOrigin\n@CrossOrigin Ϊע͵򷽷ÿͨţ\n\n````\n@CrossOrigin\n@RequestMapping(\"/hello\")\nString hello() {\nreturn \"Hello World!\";\n}\n````\nһ࣬е򷽷\n\nǿʹôע͵Ĳ΢ CORS Ϊ\n\nйϸϢʱġ\n\n# SpringBootע\n## 1 \nSpring Boot ͨԶùʹ Spring øס\n\nڱٽ̳Уǽ̽ org.springframework.boot.autoconfigure  org.springframework.boot.autoconfigure.condition еע⡣\n\n## 2 @SpringBootApplication\nʹע Spring Boot Ӧóࣺ\n\n@SpringBootApplication\nʹע Spring Boot Ӧóࣺ\n````\n@SpringBootApplication\nclass VehicleFactoryApplication {\n\n    public static void main(String[] args) {\n        SpringApplication.run(VehicleFactoryApplication.class, args);\n    }\n}\n````\n@SpringBootApplication װ@Configuration@EnableAutoConfiguration @ComponentScan ע⼰Ĭԡ\n\n## 3 @EnableAutoConfiguration\n\n@EnableAutoConfiguration˼壬Զá ζ Spring Boot ·вԶ bean ԶӦǡ\n\nע⣬Ǳ뽫ע@Configuration һʹã\n\n````\n@Configuration\n@EnableAutoConfiguration\nclass VehicleFactoryConfig {}\n````\n\n## 4 @ConfigurationԼ\n\n@Configurationãעϣspring(Ӧ)\n\nspringbeanʹõxmlļһbeanspringbootУΪãspringṩ@Configurationһע\n\n൱ڰѸΪspringxmlļе<beans>\n\n@ConfigurationעУʹ@BeanעעķصͶֱעΪbean\n\n@ConfigureעĶ£\n````\n@Target(ElementType.TYPE)\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\n@Component\npublic @interface Configuration {\nString value() default \"\";\n}\n````\nӶײǺ@Component @Configuration к @Component ácontext:component-scan/@ComponentScanܴ@Configurationעࡣ\n\nͨǱдԶԶʱϣ Spring ʹǡ ǿͨеעʵһ㡣\n\nǿԽ˲еעͷ@Configuration @Bean ϡ\n\nڽĲУǽֻÿĻ ˽Ϣƪ¡\n\n### 4.1 @ConditionalOnClass and @ConditionalOnMissingClass\nһжϵע⣬Ҫ֪ܶʱǰbeanģǸⲿjarǷмغжϵġ\n\nʱ򣬾ҪⲿǷǷǷظbean\n\nʹЩעͲе/ڣSpring ʹñǵԶ bean\n\n````\n@Configuration\n@ConditionalOnClass(DataSource.class)\nclass MySQLAutoconfiguration {\n//...\n}\n````\n\n### 4.2 @ConditionalOnBean and @ConditionalOnMissingBean\n\nҪض bean Ĵڻ򲻴ʱǿʹЩעͣ\n\nһעЩͬΪǵжbean\n\n````\n@Bean\n@ConditionalOnBean(name = \"dataSource\")\nLocalContainerEntityManagerFactoryBean entityManagerFactory() {\n// ...\n}\n````\n### 4.3 @ConditionalOnProperty\nͨע⣬ǿԶԵֵ\n\nҪע⣬ֵԴapplication.propertiesļе\n\n````\n@Bean\n@ConditionalOnProperty(\nname = \"usemysql\",\nhavingValue = \"local\"\n)\nDataSource dataSource() {\n// ...\n}\n````\n\n### 4.4 @ConditionalOnResource\n\nǿ Spring ڴضԴʱʹö壺\n˼壬ҪclasspathԴļʱŽмأҲǺܳõһע⡣\n\n````\n\n@ConditionalOnResource(resources = \"classpath:mysql.properties\")\nProperties  ditionalProperties() {\n// ...\n}\n````\n\n### 4.5 @ConditionalOnWebApplication and @ConditionalOnNotWebApplication\nעͨںwebǿȫ޹\n\nʹЩעͣǿԸݵǰӦóǷ Web Ӧó\n````\n\n@ConditionalOnWebApplication\nHealthCheckController healthCheckController() {\n// ...\n}\n````\n\n### 4.6 @ConditionalExpression\nspringbootΪ뵽ע⻹ҪôɴԼдӦûɣ\n\nǿڸӵʹע⡣  SpEL ʽΪʱSpring ʹñǵĶ壺\n\n````\n@Bean\n@ConditionalOnExpression(\"${usemysql} && ${mysqlserver == 'local'}\")\nDataSource dataSource() {\n// ...\n}\n````\n\n### 4.7 @Conditional\nʲô⣿\nspringbootҲṩʲôʽˣֱûдһжtruefalse\n\nڸӵǿԴһԶࡣ Ǹ Spring Զ @Conditional һʹã\n\n````\n@Conditional(HibernateCondition.class)\nProperties  ditionalProperties() {\n//...\n}\n````\n\n## 5 ܽ\nڱУǸ΢Զù̲ΪԶԶ bean ṩ\n\n# ο\nhttps://www.baeldung.com/spring-annotations\n"
  },
  {
    "path": "docs/Spring全家桶/SpringBoot/基于SpringBoot中的开源监控工具SpringBootAdmin.md",
    "content": "Spring Boot Admin(SBA)һԴĿڹͼ Spring Boot ӦóӦóͨ http ķʽ Spring Cloud ֻעᵽ SBA УȻͿʵֶ Spring Boot ĿĿӻͲ鿴ˡ\n\nSpring Boot Admin Լ Spring Boot ȺĿṩϸĽ (Health)ϢڴϢJVM ϵͳͻԡϢ־úͲ鿴ʱ鿴Spring Boot 鿴͹ȹܡһʹðɡ\n\nյչʾЧ£\n\n[![](https://s5.51cto.com/oss/202201/14/5d142e8c6b544f7b981b3eff8099b3d8.png)](https://s5.51cto.com/oss/202201/14/5d142e8c6b544f7b981b3eff8099b3d8.png)\n\n## 1.SBAض\n\nҪһ Spring Boot Admin Ŀغ͹ǵ Spring Boot Ŀķʽʹͨ Spring Boot Ŀƣ岽¡ʹ Idea һ Spring Boot Ŀ\n\n[![](https://s5.51cto.com/oss/202201/14/d97c492785db6ff2ded49175184ceda9.png)](https://s5.51cto.com/oss/202201/14/d97c492785db6ff2ded49175184ceda9.png)\n\n[![](https://s3.51cto.com/oss/202201/14/8bb1f0389b95e174b56ac01ba313ec7b.png)](https://s3.51cto.com/oss/202201/14/8bb1f0389b95e174b56ac01ba313ec7b.png)\n\nҪע⣬Ҫ Spring Boot Admin(Server)˿֧֣ܵͼʾ\n\n[![](https://s4.51cto.com/oss/202201/14/122e9f0726fde8ac0c8936c79ef12f5f.png)](https://s4.51cto.com/oss/202201/14/122e9f0726fde8ac0c8936c79ef12f5f.png)\n\nҲǴ Spring Boot ĿҪҪĿ֧֣\n\n\n\n<button data-clipboard-target=\"#code_id_0\"></button>\n\n```\n<dependency> \n   <groupId>org.springframework.boot</groupId> \n   spring-boot-starter-web \n</dependency> \n<dependency> \n  <groupId>de.codecentric</groupId> \n  spring-boot-admin-starter-server \n</dependency> \n\n```\n\n\n\n\n\n<dependency></dependency>\n\n### 1.1 SBA\n\nĿ֮ҪϿ SBA \n\n\n\n<button data-clipboard-target=\"#code_id_1\"></button>\n\n```\nimport de.codecentric.boot.admin.server.config.EnableAdminServer; \nimport org.springframework.boot.SpringApplication; \nimport org.springframework.boot.autoconfigure.SpringBootApplication; \n\n@EnableAdminServer // Ӵд \n@SpringBootApplication  \npublic class SbaserverApplication { \n    public static void main(String[] args) { \n        SpringApplication.run(SbaserverApplication.class, args); \n    } \n} \n\n```\n\n\n\n\n\n### 1.2 SBA˿ں\n\n application.properties һĿĶ˿ںžͿֱˣõĶ˿ں 9001\n\n\n\n<button data-clipboard-target=\"#code_id_2\"></button>\n\n```\nserver.port=9001 \n\n```\n\n\n\n\n\nPSö˿ںŵҪĿΪ˲ Spring Boot Ŀͻ SBA ǵ˲Ժԡ\n\nĿ֮󣬾ͿԿ SBA ҳˣͼʾ\n\n[![](https://s5.51cto.com/oss/202201/14/20418ce88230b20b234f9e9c15e07f47.png)](https://s5.51cto.com/oss/202201/14/20418ce88230b20b234f9e9c15e07f47.png)\n\nʱ SBA лûκҪصĿٴһ Spring Boot Ŀ뵽 SBA мغ͹ɡ\n\n## 2.һͨSpringBootĿ\n\nȣҪһͨ Spring Boot ĿĴͲʾˡ Spring Boot Ŀ֮Ҫ Spring Boot ĿҪ SBA ͻ˿֧֣ܵҲ pom.xml ݣ\n\n<dependency></dependency>\n\n\n\n<button data-clipboard-target=\"#code_id_3\"></button>\n\n```\n<dependency> \n  <groupId>de.codecentric</groupId> \n  spring-boot-admin-starter-client \n</dependency> \n\n```\n\n\n\n\n\nȻ application.properties ļ SBA ˵ַҲǵһ SBA Ŀĵַ£\n\n\n\n<button data-clipboard-target=\"#code_id_4\"></button>\n\n```\n# ǰĿ˿ں \nserver.port=8080 \n# Spring Boot Admin ط˵ַ \nspring.boot.admin.client.url=http://localhost:9001 \n\n```\n\n\n\n\n\nСspring.boot.admin.client.urlΪ SBA صַ\n\n## 3.SpringBootAdmin\n\nϢ֮󣬴ʱ鿴 Spring Boot Admin ҳобص Spring Boot Ŀˣͼʾ\n\n[![](https://s5.51cto.com/oss/202201/14/c010770a5cdfe5fad0ad1e8f0c3b07dc.png)](https://s5.51cto.com/oss/202201/14/c010770a5cdfe5fad0ad1e8f0c3b07dc.png)\n\nҲԵӦǽ鿴 Spring Boot Admin бص Spring Boot Ŀͼʾ\n\n[![](https://s4.51cto.com/oss/202201/14/d88e33f87e116000f9717e8c19c43cc4.png)](https://s4.51cto.com/oss/202201/14/d88e33f87e116000f9717e8c19c43cc4.png)\n\nӦýҳ棬ͼʾ\n\n[![](https://s2.51cto.com/oss/202201/14/4c3609840ea5cb45779eafbc2b260324.png)](https://s2.51cto.com/oss/202201/14/4c3609840ea5cb45779eafbc2b260324.png)\n\n[![](https://s4.51cto.com/oss/202201/14/2912ed434d97f8dd49c27ce73252d34c.png)](https://s4.51cto.com/oss/202201/14/2912ed434d97f8dd49c27ce73252d34c.png)\n\n¼־а Spring Boot ״̬չʾ(UP ΪOFFLINE Ϊ쳣)ͷʱ䣬ͼʾ\n\n[![](https://s5.51cto.com/oss/202201/14/5792a62fbcafe6978bfe3bd26cf1e3ab.png)](https://s5.51cto.com/oss/202201/14/5792a62fbcafe6978bfe3bd26cf1e3ab.png)\n\n## 4.SpringBoot쳣\n\nֶѱص Spring Boot Ŀֹ֮ͣ Spring Boot Admin оͿԲ鿴һӦѾͣˣͼʾ\n\n[![](https://s5.51cto.com/oss/202201/14/47569a3fe09e62b2364c26bdbd7da4bc.png)](https://s5.51cto.com/oss/202201/14/47569a3fe09e62b2364c26bdbd7da4bc.png)\n\nҲͨ¼־鿴 Spring Boot 崻ľʱ䣬ͼʾ\n\n[![](https://s2.51cto.com/oss/202201/14/b63f631561fa646f85ccf3e1e4321939.png)](https://s2.51cto.com/oss/202201/14/b63f631561fa646f85ccf3e1e4321939.png)\n\n## 5.ò鿴\n\nͨǿԿص Spring Boot ѡǱȽٵģôܲ鿴ļ?Ҫ⣬Ҫڱص Spring Boot Ŀ spring-boot-starter-actuator ֧֣ܵ鿴мòУչʾЧ£\n\n[![](https://s4.51cto.com/oss/202201/14/03938ac0bded4487b6720fc4657f9e99.png)](https://s4.51cto.com/oss/202201/14/03938ac0bded4487b6720fc4657f9e99.png)\n\nһЩ\n\n### 5.1 actuator֧\n\nڱص Spring Boot Ŀ actuator ֧֣Ҳ pom.xml ã\n\n<dependency></dependency>\n\n\n\n<button data-clipboard-target=\"#code_id_5\"></button>\n\n```\n<dependency> \n    <groupId>org.springframework.boot</groupId> \n    spring-boot-starter-actuator \n</dependency> \n\n```\n\n\n\n\n\nֶ Maven (Զ룬˲ɺ)\n\n### 5.2 ÿм\n\nڱص Spring Boot Ŀã\n\n\n\n<button data-clipboard-target=\"#code_id_6\"></button>\n\n```\n#  \nmanagement.endpoints.web.exposure.include=* \n\n```\n\n\n\n\n\nϵǿżѡ֮ Spring Boot ĿȻˢ Spring Boot Admin ļչʾˣͼʾ\n\n[![](https://s6.51cto.com/oss/202201/14/2cc24e05bc6185ba1869872db5a864a5.png)](https://s6.51cto.com/oss/202201/14/2cc24e05bc6185ba1869872db5a864a5.png)\n\n### 5.3 ĿԤ\n\n Spring Boot м֮ͨ SBA ͿԲ鿴ˣ\n\n*   ʱ䡢ۼʱ;\n*   ̺߳ռõ CPU Դ;\n*   Ϣմͻʱ;\n*   JVM ߳תڴתͶӦļ;\n*   Բ鿴 Spring Boot Ŀе־;\n*   鿴 Spring Boot Ŀܼ;\n*   鿴 Spring Boot лϢ;\n*   鿴 Spring Boot Ϣ;\n*   鿴 Spring Boot еĶʱ;\n*   鿴͹ Spring Boot Ŀел档\n\nǼҪҳĽͼһ\n\n### 5.3.1 鿴л\n\n[![](https://s5.51cto.com/oss/202201/14/a9db77b1b0e378450086edd1ab438df5.png)](https://s5.51cto.com/oss/202201/14/a9db77b1b0e378450086edd1ab438df5.png)\n\n[![](https://s2.51cto.com/oss/202201/14/17604f4f5eb388a2a6c08f55e1e050ac.png)](https://s2.51cto.com/oss/202201/14/17604f4f5eb388a2a6c08f55e1e050ac.png)\n\n### 5.3.2 鿴ʱ\n\n[![](https://s5.51cto.com/oss/202201/14/113d94b9bd488e239967915aededc89c.png)](https://s5.51cto.com/oss/202201/14/113d94b9bd488e239967915aededc89c.png)\n\n### 5.3.3 Ŀ־\n\n[![](https://s3.51cto.com/oss/202201/14/8da60846eecbdbd9472ae6dbf17d951f.png)](https://s3.51cto.com/oss/202201/14/8da60846eecbdbd9472ae6dbf17d951f.png)\n\nǿͨ Spring Boot Admin ̬Ŀе־\n\n### 5.3.4 JVM̺߳ڴ鿴\n\n[![](https://s2.51cto.com/oss/202201/14/d3f98228a8b19675475c863457821034.png)](https://s2.51cto.com/oss/202201/14/d3f98228a8b19675475c863457821034.png)\n\n### 5.3.5 鿴SpringBootл\n\n[![](https://s5.51cto.com/oss/202201/14/ae811102080c26b11135be50cd889710.png)](https://s5.51cto.com/oss/202201/14/ae811102080c26b11135be50cd889710.png)\n\nȻǻԶЩɾ\n\n## 6.鿴Ŀʵʱ־\n\nҪ鿴Ŀе־Ϣһǰǰ㱻ص Spring Boot Ŀ־ı·־ļֻеһ Spring Boot ĿŻὫ־浽ϣͨ SBA 鿴õ־· Spring Boot  application.properties ļã\n\n\n\n<button data-clipboard-target=\"#code_id_7\"></button>\n\n```\n# ־· \nlogging.file.path=C:\\\\work\\\\log \n\n```\n\n\n\n\n\n֮ Spring Boot ĿȻˢ SBA ҳ棬չʾЧ£\n\n[![](https://s6.51cto.com/oss/202201/14/3f03c6402cc8a2532ed45ab43be156ac.png)](https://s6.51cto.com/oss/202201/14/3f03c6402cc8a2532ed45ab43be156ac.png)\n\nʱǾͿԲ鿴ʵʱ־ϢˣȻҲʱ־ҪĻ\n\n## ܽ\n\nSpring Boot Admin(SBA)һԴĿڹͼ Spring Boot ӦóṩϸĽ (Health)ϢڴϢJVM ϵͳͻԡϢ־úͲ鿴ʱ鿴Spring Boot 鿴͹ȹܡ\n\nҪһ SBA һ Spring Boot Ŀص Spring Boot ĿҪ SBA Client ֧֣ܵ actuator ܺӦãͿʵֶ Spring Boot Ŀˡ"
  },
  {
    "path": "docs/Spring全家桶/SpringBoot/给你一份SpringBoot知识清单.md",
    "content": "# 目录\n* [一、抛砖引玉：探索Spring IoC容器](#一、抛砖引玉：探索spring-ioc容器)\n    * [1.1、Spring IoC容器](#11、spring-ioc容器)\n    * [1.2、Spring容器扩展机制](#12、spring容器扩展机制)\n* [二、夯实基础：JavaConfig与常见Annotation](#二、夯实基础：javaconfig与常见annotation)\n    * [2.1、JavaConfig](#21、javaconfig)\n    * [2.2、@ComponentScan](#22、componentscan)\n    * [2.3、@Import](#23、import)\n    * [2.4、@Conditional](#24、conditional)\n    * [2.5、@ConfigurationProperties与@EnableConfigurationProperties](#25、configurationproperties与enableconfigurationproperties)\n* [三、削铁如泥：SpringFactoriesLoader详解](#三、削铁如泥：springfactoriesloader详解)\n* [四、另一件武器：Spring容器的事件监听机制](#四、另一件武器：spring容器的事件监听机制)\n    * [Spring容器内的事件监听机制](#spring容器内的事件监听机制)\n* [五、出神入化：揭秘自动配置原理](#五、出神入化：揭秘自动配置原理)\n* [六、启动引导：Spring Boot应用启动的秘密](#六、启动引导：spring-boot应用启动的秘密)\n    * [6.1 SpringApplication初始化](#61-springapplication初始化)\n    * [6.2 Spring Boot启动流程](#62-spring-boot启动流程)\n* [参考文章](#参考文章)\n\n\n本文转自互联网，侵删\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章将同步到我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《Spring和SpringMVC源码分析》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从spring基础入手，一步步地学习spring基础和springmvc的框架知识，并上手进行项目实战，spring框架是每一个Java工程师必须要学习和理解的知识点，进一步来说，你还需要掌握spring甚至是springmvc的源码以及实现原理，才能更完整地了解整个spring技术体系，形成自己的知识框架。\n\n后续还会有springboot和springcloud的技术专题，陆续为大家带来，敬请期待。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供部分知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->  \n<!-- more -->  \n\n\n> 1.  预警：本文非常长，建议先mark后看，也许是最后一次写这么长的文章\n> 2.  说明：前面有4个小节关于Spring的基础知识，分别是：IOC容器、JavaConfig、事件监听、SpringFactoriesLoader详解，它们占据了本文的大部分内容，虽然它们之间可能没有太多的联系，但这些知识对于理解Spring Boot的核心原理至关重要，如果你对Spring框架烂熟于心，完全可以跳过这4个小节。正是因为这个系列的文章是由这些看似不相关的知识点组成，因此取名知识清单。\n\n转自：https://www.jianshu.com/p/83693d3d0a65\n\n在过去两三年的Spring生态圈，最让人兴奋的莫过于Spring Boot框架。或许从命名上就能看出这个框架的设计初衷：快速的启动Spring应用。因而Spring Boot应用本质上就是一个基于Spring框架的应用，它是Spring对“约定优先于配置”理念的最佳实践产物，它能够帮助开发者更快速高效地构建基于Spring生态圈的应用。\n\n那Spring Boot有何魔法？**自动配置**、**起步依赖**、**Actuator**、**命令行界面(CLI)**是Spring Boot最重要的4大核心特性，其中CLI是Spring Boot的可选特性，虽然它功能强大，但也引入了一套不太常规的开发模型，因而这个系列的文章仅关注其它3种特性。如文章标题，本文是这个系列的第一部分，将为你打开Spring Boot的大门，重点为你剖析其启动流程以及自动配置实现原理。要掌握这部分核心内容，理解一些Spring框架的基础知识，将会让你事半功倍。\n\n## 一、抛砖引玉：探索Spring IoC容器\n\n如果有看过`SpringApplication.run()`方法的源码，Spring Boot冗长无比的启动流程一定会让你抓狂，透过现象看本质，SpringApplication只是将一个典型的Spring应用的启动流程进行了扩展，因此，透彻理解Spring容器是打开Spring Boot大门的一把钥匙。\n\n### 1.1、Spring IoC容器\n\n可以把Spring IoC容器比作一间餐馆，当你来到餐馆，通常会直接招呼服务员：点菜！至于菜的原料是什么？如何用原料把菜做出来？可能你根本就不关心。IoC容器也是一样，你只需要告诉它需要某个bean，它就把对应的实例（instance）扔给你，至于这个bean是否依赖其他组件，怎样完成它的初始化，根本就不需要你关心。\n\n作为餐馆，想要做出菜肴，得知道菜的原料和菜谱，同样地，IoC容器想要管理各个业务对象以及它们之间的依赖关系，需要通过某种途径来记录和管理这些信息。`BeanDefinition`对象就承担了这个责任：容器中的每一个bean都会有一个对应的BeanDefinition实例，该实例负责保存bean对象的所有必要信息，包括bean对象的class类型、是否是抽象类、构造方法和参数、其它属性等等。当客户端向容器请求相应对象时，容器就会通过这些信息为客户端返回一个完整可用的bean实例。\n\n原材料已经准备好（把BeanDefinition看着原料），开始做菜吧，等等，你还需要一份菜谱，`BeanDefinitionRegistry`和`BeanFactory`就是这份菜谱，BeanDefinitionRegistry抽象出bean的注册逻辑，而BeanFactory则抽象出了bean的管理逻辑，而各个BeanFactory的实现类就具体承担了bean的注册以及管理工作。它们之间的关系就如下图：\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/175724-911dc459ef9cec28.png)\n\n\n\nBeanFactory、BeanDefinitionRegistry关系图（来自：Spring揭秘）\n\n\n\n`DefaultListableBeanFactory`作为一个比较通用的BeanFactory实现，它同时也实现了BeanDefinitionRegistry接口，因此它就承担了Bean的注册管理工作。从图中也可以看出，BeanFactory接口中主要包含getBean、containBean、getType、getAliases等管理bean的方法，而BeanDefinitionRegistry接口则包含registerBeanDefinition、removeBeanDefinition、getBeanDefinition等注册管理BeanDefinition的方法。\n\n下面通过一段简单的代码来模拟BeanFactory底层是如何工作的：\n\n```  \n// 默认容器实现  \nDefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory();  \n// 根据业务对象构造相应的BeanDefinition  \nAbstractBeanDefinition definition = new RootBeanDefinition(Business.class,true);  \n// 将bean定义注册到容器中  \nbeanRegistry.registerBeanDefinition(\"beanName\",definition);  \n// 如果有多个bean，还可以指定各个bean之间的依赖关系  \n// ........  \n  \n// 然后可以从容器中获取这个bean的实例  \n// 注意：这里的beanRegistry其实实现了BeanFactory接口，所以可以强转，  \n// 单纯的BeanDefinitionRegistry是无法强制转换到BeanFactory类型的  \nBeanFactory container = (BeanFactory)beanRegistry;  \nBusiness business = (Business)container.getBean(\"beanName\");  \n  \n```  \n\n这段代码仅为了说明BeanFactory底层的大致工作流程，实际情况会更加复杂，比如bean之间的依赖关系可能定义在外部配置文件(XML/Properties)中、也可能是注解方式。Spring IoC容器的整个工作流程大致可以分为两个阶段：\n\n①、容器启动阶段\n\n容器启动时，会通过某种途径加载`Configuration MetaData`。除了代码方式比较直接外，在大部分情况下，容器需要依赖某些工具类，比如：`BeanDefinitionReader`，BeanDefinitionReader会对加载的`Configuration MetaData`进行解析和分析，并将分析后的信息组装为相应的BeanDefinition，最后把这些保存了bean定义的BeanDefinition，注册到相应的BeanDefinitionRegistry，这样容器的启动工作就完成了。这个阶段主要完成一些准备性工作，更侧重于bean对象管理信息的收集，当然一些验证性或者辅助性的工作也在这一阶段完成。\n\n来看一个简单的例子吧，过往，所有的bean都定义在XML配置文件中，下面的代码将模拟BeanFactory如何从配置文件中加载bean的定义以及依赖关系：\n\n```  \n// 通常为BeanDefinitionRegistry的实现类，这里以DeFaultListabeBeanFactory为例  \nBeanDefinitionRegistry beanRegistry = new DefaultListableBeanFactory(); // XmlBeanDefinitionReader实现了BeanDefinitionReader接口，用于解析XML文件  \nXmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReaderImpl(beanRegistry);  \n// 加载配置文件  \nbeanDefinitionReader.loadBeanDefinitions(\"classpath:spring-bean.xml\");  \n  \n// 从容器中获取bean实例  \nBeanFactory container = (BeanFactory)beanRegistry;  \nBusiness business = (Business)container.getBean(\"beanName\");  \n  \n```  \n\n②、Bean的实例化阶段\n\n经过第一阶段，所有bean定义都通过BeanDefinition的方式注册到BeanDefinitionRegistry中，当某个请求通过容器的getBean方法请求某个对象，或者因为依赖关系容器需要隐式的调用getBean时，就会触发第二阶段的活动：容器会首先检查所请求的对象之前是否已经实例化完成。如果没有，则会根据注册的BeanDefinition所提供的信息实例化被请求对象，并为其注入依赖。当该对象装配完毕后，容器会立即将其返回给请求方法使用。\n\nBeanFactory只是Spring IoC容器的一种实现，如果没有特殊指定，它采用采用延迟初始化策略：只有当访问容器中的某个对象时，才对该对象进行初始化和依赖注入操作。而在实际场景下，我们更多的使用另外一种类型的容器：`ApplicationContext`，它构建在BeanFactory之上，属于更高级的容器，除了具有BeanFactory的所有能力之外，还提供对事件监听机制以及国际化的支持等。它管理的bean，在容器启动时全部完成初始化和依赖注入操作。\n\n### 1.2、Spring容器扩展机制\n\nIoC容器负责管理容器中所有bean的生命周期，而在bean生命周期的不同阶段，Spring提供了不同的扩展点来改变bean的命运。在容器的启动阶段，`BeanFactoryPostProcessor`允许我们在容器实例化相应对象之前，对注册到容器的BeanDefinition所保存的信息做一些额外的操作，比如修改bean定义的某些属性或者增加其他信息等。\n\n如果要自定义扩展类，通常需要实现`org.springframework.beans.factory.config.BeanFactoryPostProcessor`接口，与此同时，因为容器中可能有多个BeanFactoryPostProcessor，可能还需要实现`org.springframework.core.Ordered`接口，以保证BeanFactoryPostProcessor按照顺序执行。Spring提供了为数不多的BeanFactoryPostProcessor实现，我们以`PropertyPlaceholderConfigurer`来说明其大致的工作流程。\n\n在Spring项目的XML配置文件中，经常可以看到许多配置项的值使用占位符，而将占位符所代表的值单独配置到独立的properties文件，这样可以将散落在不同XML文件中的配置集中管理，而且也方便运维根据不同的环境进行配置不同的值。这个非常实用的功能就是由PropertyPlaceholderConfigurer负责实现的。\n\n根据前文，当BeanFactory在第一阶段加载完所有配置信息时，BeanFactory中保存的对象的属性还是以占位符方式存在的，比如`${jdbc.mysql.url}`。当PropertyPlaceholderConfigurer作为BeanFactoryPostProcessor被应用时，它会使用properties配置文件中的值来替换相应的BeanDefinition中占位符所表示的属性值。当需要实例化bean时，bean定义中的属性值就已经被替换成我们配置的值。当然其实现比上面描述的要复杂一些，这里仅说明其大致工作原理，更详细的实现可以参考其源码。\n\n与之相似的，还有`BeanPostProcessor`，其存在于对象实例化阶段。跟BeanFactoryPostProcessor类似，它会处理容器内所有符合条件并且已经实例化后的对象。简单的对比，BeanFactoryPostProcessor处理bean的定义，而BeanPostProcessor则处理bean完成实例化后的对象。BeanPostProcessor定义了两个接口：\n\n```  \npublic interface BeanPostProcessor {  \n    // 前置处理  \n    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;    // 后置处理  \n    Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;}  \n  \n```  \n\n为了理解这两个方法执行的时机，简单的了解下bean的整个生命周期：\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/175724-0d50d67d980161b4.png)\n\n\n\nBean的实例化过程（来自：Spring揭秘）\n\n\n\n`postProcessBeforeInitialization()`方法与`postProcessAfterInitialization()`分别对应图中前置处理和后置处理两个步骤将执行的方法。这两个方法中都传入了bean对象实例的引用，为扩展容器的对象实例化过程提供了很大便利，在这儿几乎可以对传入的实例执行任何操作。注解、AOP等功能的实现均大量使用了`BeanPostProcessor`，比如有一个自定义注解，你完全可以实现BeanPostProcessor的接口，在其中判断bean对象的脑袋上是否有该注解，如果有，你可以对这个bean实例执行任何操作，想想是不是非常的简单？\n\n再来看一个更常见的例子，在Spring中经常能够看到各种各样的Aware接口，其作用就是在对象实例化完成以后将Aware接口定义中规定的依赖注入到当前实例中。比如最常见的`ApplicationContextAware`接口，实现了这个接口的类都可以获取到一个ApplicationContext对象。当容器中每个对象的实例化过程走到BeanPostProcessor前置处理这一步时，容器会检测到之前注册到容器的ApplicationContextAwareProcessor，然后就会调用其postProcessBeforeInitialization()方法，检查并设置Aware相关依赖。看看代码吧，是不是很简单：\n\n```  \n// 代码来自：org.springframework.context.support.ApplicationContextAwareProcessor  \n// 其postProcessBeforeInitialization方法调用了invokeAwareInterfaces方法  \nprivate void invokeAwareInterfaces(Object bean) {  \n    if (bean instanceof EnvironmentAware) {        ((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());    }    if (bean instanceof ApplicationContextAware) {        ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);    }    // ......}  \n  \n```  \n\n最后总结一下，本小节内容和你一起回顾了Spring容器的部分核心内容，限于篇幅不能写更多，但理解这部分内容，足以让您轻松理解Spring Boot的启动原理，如果在后续的学习过程中遇到一些晦涩难懂的知识，再回过头来看看Spring的核心知识，也许有意想不到的效果。也许Spring Boot的中文资料很少，但Spring的中文资料和书籍有太多太多，总有东西能给你启发。\n\n## 二、夯实基础：JavaConfig与常见Annotation\n\n### 2.1、JavaConfig\n\n我们知道`bean`是Spring IOC中非常核心的概念，Spring容器负责bean的生命周期的管理。在最初，Spring使用XML配置文件的方式来描述bean的定义以及相互间的依赖关系，但随着Spring的发展，越来越多的人对这种方式表示不满，因为Spring项目的所有业务类均以bean的形式配置在XML文件中，造成了大量的XML文件，使项目变得复杂且难以管理。\n\n后来，基于纯Java Annotation依赖注入框架`Guice`出世，其性能明显优于采用XML方式的Spring，甚至有部分人认为，`Guice`可以完全取代Spring（`Guice`仅是一个轻量级IOC框架，取代Spring还差的挺远）。正是这样的危机感，促使Spring及社区推出并持续完善了`JavaConfig`子项目，它基于Java代码和Annotation注解来描述bean之间的依赖绑定关系。比如，下面是使用XML配置方式来描述bean的定义：\n\n```  \n<bean id=\"bookService\" class=\"cn.moondev.service.BookServiceImpl\"></bean>  \n  \n```  \n\n而基于JavaConfig的配置形式是这样的：\n\n```  \n@Configuration  \npublic class MoonBookConfiguration {  \n  \n    // 任何标志了@Bean的方法，其返回值将作为一个bean注册到Spring的IOC容器中  \n    // 方法名默认成为该bean定义的id  \n    @Bean    public BookService bookService() {        return new BookServiceImpl();    }}  \n  \n```  \n\n如果两个bean之间有依赖关系的话，在XML配置中应该是这样：\n\n```  \n<bean id=\"bookService\" class=\"cn.moondev.service.BookServiceImpl\">  \n    <property name=\"dependencyService\" ref=\"dependencyService\"/></bean>  \n  \n<bean id=\"otherService\" class=\"cn.moondev.service.OtherServiceImpl\">  \n    <property name=\"dependencyService\" ref=\"dependencyService\"/></bean>  \n  \n<bean id=\"dependencyService\" class=\"DependencyServiceImpl\"/>  \n  \n```  \n\n而在JavaConfig中则是这样：\n\n```  \n@Configuration  \npublic class MoonBookConfiguration {  \n  \n    // 如果一个bean依赖另一个bean，则直接调用对应JavaConfig类中依赖bean的创建方法即可  \n    // 这里直接调用dependencyService()  \n    @Bean    public BookService bookService() {        return new BookServiceImpl(dependencyService());    }  \n    @Bean    public OtherService otherService() {        return new OtherServiceImpl(dependencyService());    }  \n    @Bean    public DependencyService dependencyService() {        return new DependencyServiceImpl();    }}  \n  \n```  \n\n你可能注意到这个示例中，有两个bean都依赖于dependencyService，也就是说当初始化bookService时会调用`dependencyService()`，在初始化otherService时也会调用`dependencyService()`，那么问题来了？这时候IOC容器中是有一个dependencyService实例还是两个？这个问题留着大家思考吧，这里不再赘述。\n\n### 2.2、@ComponentScan\n\n`@ComponentScan`注解对应XML配置形式中的`<context:component-scan>`元素，表示启用组件扫描，Spring会自动扫描所有通过注解配置的bean，然后将其注册到IOC容器中。我们可以通过`basePackages`等属性来指定`@ComponentScan`自动扫描的范围，如果不指定，默认从声明`@ComponentScan`所在类的`package`进行扫描。正因为如此，SpringBoot的启动类都默认在`src/main/java`下。\n\n### 2.3、@Import\n\n`@Import`注解用于导入配置类，举个简单的例子：\n\n```  \n@Configuration  \npublic class MoonBookConfiguration {  \n    @Bean    public BookService bookService() {        return new BookServiceImpl();    }}  \n  \n```  \n\n现在有另外一个配置类，比如：`MoonUserConfiguration`，这个配置类中有一个bean依赖于`MoonBookConfiguration`中的bookService，如何将这两个bean组合在一起？借助`@Import`即可：\n\n```  \n@Configuration  \n// 可以同时导入多个配置类，比如：@Import({A.class,B.class})  \n@Import(MoonBookConfiguration.class)  \npublic class MoonUserConfiguration {  \n    @Bean    public UserService userService(BookService bookService) {        return new BookServiceImpl(bookService);    }}  \n  \n```  \n\n需要注意的是，在4.2之前，`@Import`注解只支持导入配置类，但是在4.2之后，它支持导入普通类，并将这个类作为一个bean的定义注册到IOC容器中。\n\n### 2.4、@Conditional\n\n`@Conditional`注解表示在满足某种条件后才初始化一个bean或者启用某些配置。它一般用在由`@Component`、`@Service`、`@Configuration`等注解标识的类上面，或者由`@Bean`标记的方法上。如果一个`@Configuration`类标记了`@Conditional`，则该类中所有标识了`@Bean`的方法和`@Import`注解导入的相关类将遵从这些条件。\n\n在Spring里可以很方便的编写你自己的条件类，所要做的就是实现`Condition`接口，并覆盖它的`matches()`方法。举个例子，下面的简单条件类表示只有在`Classpath`里存在`JdbcTemplate`类时才生效：\n\n```  \npublic class JdbcTemplateCondition implements Condition {  \n  \n    @Override    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {        try {        conditionContext.getClassLoader().loadClass(\"org.springframework.jdbc.core.JdbcTemplate\");            return true;        } catch (ClassNotFoundException e) {            e.printStackTrace();        }        return false;    }}  \n  \n```  \n\n当你用Java来声明bean的时候，可以使用这个自定义条件类：\n\n```  \n@Conditional(JdbcTemplateCondition.class)  \n@Service  \npublic MyService service() {  \n    ......}  \n  \n```  \n\n这个例子中只有当`JdbcTemplateCondition`类的条件成立时才会创建MyService这个bean。也就是说MyService这bean的创建条件是`classpath`里面包含`JdbcTemplate`，否则这个bean的声明就会被忽略掉。\n\n`Spring Boot`定义了很多有趣的条件，并把他们运用到了配置类上，这些配置类构成了`Spring Boot`的自动配置的基础。`Spring Boot`运用条件化配置的方法是：定义多个特殊的条件化注解，并将它们用到配置类上。下面列出了`Spring Boot`提供的部分条件化注解：\n\n| 条件化注解 | 配置生效条件 |  \n| --- | --- |  \n| @ConditionalOnBean | 配置了某个特定bean |  \n| @ConditionalOnMissingBean | 没有配置特定的bean |  \n| @ConditionalOnClass | Classpath里有指定的类 |  \n| @ConditionalOnMissingClass | Classpath里没有指定的类 |  \n| @ConditionalOnExpression | 给定的Spring Expression Language表达式计算结果为true |  \n| @ConditionalOnJava | Java的版本匹配特定指或者一个范围值 |  \n| @ConditionalOnProperty | 指定的配置属性要有一个明确的值 |  \n| @ConditionalOnResource | Classpath里有指定的资源 |  \n| @ConditionalOnWebApplication | 这是一个Web应用程序 |  \n| @ConditionalOnNotWebApplication | 这不是一个Web应用程序 |  \n\n### 2.5、@ConfigurationProperties与@EnableConfigurationProperties\n\n当某些属性的值需要配置的时候，我们一般会在`application.properties`文件中新建配置项，然后在bean中使用`@Value`注解来获取配置的值，比如下面配置数据源的代码。\n\n```  \n// jdbc config  \njdbc.mysql.url=jdbc:mysql://localhost:3306/sampledb  \njdbc.mysql.username=root  \njdbc.mysql.password=123456  \n......  \n  \n// 配置数据源  \n@Configuration  \npublic class HikariDataSourceConfiguration {  \n  \n    @Value(\"jdbc.mysql.url\")    public String url;    @Value(\"jdbc.mysql.username\")    public String user;    @Value(\"jdbc.mysql.password\")    public String password;  \n    @Bean    public HikariDataSource dataSource() {        HikariConfig hikariConfig = new HikariConfig();        hikariConfig.setJdbcUrl(url);        hikariConfig.setUsername(user);        hikariConfig.setPassword(password);        // 省略部分代码  \n        return new HikariDataSource(hikariConfig);    }}  \n  \n```  \n\n使用`@Value`注解注入的属性通常都比较简单，如果同一个配置在多个地方使用，也存在不方便维护的问题（考虑下，如果有几十个地方在使用某个配置，而现在你想改下名字，你改怎么做？）。对于更为复杂的配置，Spring Boot提供了更优雅的实现方式，那就是`@ConfigurationProperties`注解。我们可以通过下面的方式来改写上面的代码：\n\n```  \n@Component  \n//  还可以通过@PropertySource(\"classpath:jdbc.properties\")来指定配置文件  \n@ConfigurationProperties(\"jdbc.mysql\")  \n// 前缀=jdbc.mysql，会在配置文件中寻找jdbc.mysql.*的配置项  \npulic class JdbcConfig {  \n    public String url;    public String username;    public String password;}  \n  \n@Configuration  \npublic class HikariDataSourceConfiguration {  \n  \n    @AutoWired    public JdbcConfig config;  \n    @Bean    public HikariDataSource dataSource() {        HikariConfig hikariConfig = new HikariConfig();        hikariConfig.setJdbcUrl(config.url);        hikariConfig.setUsername(config.username);        hikariConfig.setPassword(config.password);        // 省略部分代码  \n        return new HikariDataSource(hikariConfig);    }}  \n  \n```  \n\n`@ConfigurationProperties`对于更为复杂的配置，处理起来也是得心应手，比如有如下配置文件：\n\n```  \n#App  \napp.menus[0].title=Home  \napp.menus[0].name=Home  \napp.menus[0].path=/  \napp.menus[1].title=Login  \napp.menus[1].name=Login  \napp.menus[1].path=/login  \n  \napp.compiler.timeout=5  \napp.compiler.output-folder=/temp/  \n  \napp.error=/error/  \n  \n```  \n\n可以定义如下配置类来接收这些属性\n\n```  \n@Component  \n@ConfigurationProperties(\"app\")  \npublic class AppProperties {  \n  \n    public String error;    public List<Menu> menus = new ArrayList<>();    public Compiler compiler = new Compiler();  \n    public static class Menu {        public String name;        public String path;        public String title;    }  \n    public static class Compiler {        public String timeout;        public String outputFolder;    }}  \n  \n```  \n\n`@EnableConfigurationProperties`注解表示对`@ConfigurationProperties`的内嵌支持，默认会将对应Properties Class作为bean注入的IOC容器中，即在相应的Properties类上不用加`@Component`注解。\n\n## 三、削铁如泥：SpringFactoriesLoader详解\n\nJVM提供了3种类加载器：`BootstrapClassLoader`、`ExtClassLoader`、`AppClassLoader`分别加载Java核心类库、扩展类库以及应用的类路径(`CLASSPATH`)下的类库。JVM通过双亲委派模型进行类的加载，我们也可以通过继承`java.lang.classloader`实现自己的类加载器。\n\n何为双亲委派模型？当一个类加载器收到类加载任务时，会先交给自己的父加载器去完成，因此最终加载任务都会传递到最顶层的BootstrapClassLoader，只有当父加载器无法完成加载任务时，才会尝试自己来加载。\n\n采用双亲委派模型的一个好处是保证使用不同类加载器最终得到的都是同一个对象，这样就可以保证Java 核心库的类型安全，比如，加载位于rt.jar包中的`java.lang.Object`类，不管是哪个加载器加载这个类，最终都是委托给顶层的BootstrapClassLoader来加载的，这样就可以保证任何的类加载器最终得到的都是同样一个Object对象。查看ClassLoader的源码，对双亲委派模型会有更直观的认识：\n\n```  \nprotected Class<?> loadClass(String name, boolean resolve) {  \n    synchronized (getClassLoadingLock(name)) {    // 首先，检查该类是否已经被加载，如果从JVM缓存中找到该类，则直接返回  \n    Class<?> c = findLoadedClass(name);    if (c == null) {        try {            // 遵循双亲委派的模型，首先会通过递归从父加载器开始找，  \n            // 直到父类加载器是BootstrapClassLoader为止  \n            if (parent != null) {                c = parent.loadClass(name, false);            } else {                c = findBootstrapClassOrNull(name);            }        } catch (ClassNotFoundException e) {}        if (c == null) {            // 如果还找不到，尝试通过findClass方法去寻找  \n            // findClass是留给开发者自己实现的，也就是说  \n            // 自定义类加载器时，重写此方法即可  \n           c = findClass(name);        }    }    if (resolve) {        resolveClass(c);    }    return c;    }}  \n  \n```  \n\n但双亲委派模型并不能解决所有的类加载器问题，比如，Java 提供了很多服务提供者接口(`Service Provider Interface`，SPI)，允许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JNDI、JAXP 等，这些SPI的接口由核心类库提供，却由第三方实现，这样就存在一个问题：SPI 的接口是 Java 核心库的一部分，是由BootstrapClassLoader加载的；SPI实现的Java类一般是由AppClassLoader来加载的。BootstrapClassLoader是无法找到 SPI 的实现类的，因为它只加载Java的核心库。它也不能代理给AppClassLoader，因为它是最顶层的类加载器。也就是说，双亲委派模型并不能解决这个问题。\n\n线程上下文类加载器(`ContextClassLoader`)正好解决了这个问题。从名称上看，可能会误解为它是一种新的类加载器，实际上，它仅仅是Thread类的一个变量而已，可以通过`setContextClassLoader(ClassLoader cl)`和`getContextClassLoader()`来设置和获取该对象。如果不做任何的设置，Java应用的线程的上下文类加载器默认就是AppClassLoader。在核心类库使用SPI接口时，传递的类加载器使用线程上下文类加载器，就可以成功的加载到SPI实现的类。线程上下文类加载器在很多SPI的实现中都会用到。但在JDBC中，你可能会看到一种更直接的实现方式，比如，JDBC驱动管理`java.sql.Driver`中的`loadInitialDrivers()`方法中，你可以直接看到JDK是如何加载驱动的：\n\n```  \nfor (String aDriver : driversList) {  \n    try {        // 直接使用AppClassLoader  \n        Class.forName(aDriver, true, ClassLoader.getSystemClassLoader());    } catch (Exception ex) {        println(\"DriverManager.Initialize: load failed: \" + ex);    }}  \n  \n```  \n\n其实讲解线程上下文类加载器，最主要是让大家在看到`Thread.currentThread().getClassLoader()`和`Thread.currentThread().getContextClassLoader()`时不会一脸懵逼，这两者除了在许多底层框架中取得的ClassLoader可能会有所不同外，其他大多数业务场景下都是一样的，大家只要知道它是为了解决什么问题而存在的即可。\n\n类加载器除了加载class外，还有一个非常重要功能，就是加载资源，它可以从jar包中读取任何资源文件，比如，`ClassLoader.getResources(String name)`方法就是用于读取jar包中的资源文件，其代码如下：\n\n```  \npublic Enumeration<URL> getResources(String name) throws IOException {  \n    Enumeration<URL>[] tmp = (Enumeration<URL>[]) new Enumeration<?>[2];    if (parent != null) {        tmp[0] = parent.getResources(name);    } else {        tmp[0] = getBootstrapResources(name);    }    tmp[1] = findResources(name);    return new CompoundEnumeration<>(tmp);}  \n  \n```  \n\n是不是觉得有点眼熟，不错，它的逻辑其实跟类加载的逻辑是一样的，首先判断父类加载器是否为空，不为空则委托父类加载器执行资源查找任务，直到BootstrapClassLoader，最后才轮到自己查找。而不同的类加载器负责扫描不同路径下的jar包，就如同加载class一样，最后会扫描所有的jar包，找到符合条件的资源文件。\n\n类加载器的`findResources(name)`方法会遍历其负责加载的所有jar包，找到jar包中名称为name的资源文件，这里的资源可以是任何文件，甚至是.class文件，比如下面的示例，用于查找Array.class文件：\n\n```  \n// 寻找Array.class文件  \npublic static void main(String[] args) throws Exception{  \n    // Array.class的完整路径  \n    String name = \"java/sql/Array.class\";    Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(name);    while (urls.hasMoreElements()) {        URL url = urls.nextElement();        System.out.println(url.toString());    }}  \n  \n```  \n\n运行后可以得到如下结果：\n\n```  \n$JAVA_HOME/jre/lib/rt.jar!/java/sql/Array.class  \n  \n```  \n\n根据资源文件的URL，可以构造相应的文件来读取资源内容。\n\n看到这里，你可能会感到挺奇怪的，你不是要详解`SpringFactoriesLoader`吗？上来讲了一堆ClassLoader是几个意思？看下它的源码你就知道了：\n\n```  \npublic static final String FACTORIES_RESOURCE_LOCATION = \"META-INF/spring.factories\";  \n// spring.factories文件的格式为：key=value1,value2,value3  \n// 从所有的jar包中找到META-INF/spring.factories文件  \n// 然后从文件中解析出key=factoryClass类名称的所有value值  \npublic static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {  \n    String factoryClassName = factoryClass.getName();    // 取得资源文件的URL  \n    Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));    List<String> result = new ArrayList<String>();    // 遍历所有的URL  \n    while (urls.hasMoreElements()) {        URL url = urls.nextElement();        // 根据资源文件URL解析properties文件  \n        Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));        String factoryClassNames = properties.getProperty(factoryClassName);        // 组装数据，并返回  \n        result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));    }    return result;}  \n  \n```  \n\n有了前面关于ClassLoader的知识，再来理解这段代码，是不是感觉豁然开朗：从`CLASSPATH`下的每个Jar包中搜寻所有`META-INF/spring.factories`配置文件，然后将解析properties文件，找到指定名称的配置后返回。需要注意的是，其实这里不仅仅是会去ClassPath路径下查找，会扫描所有路径下的Jar包，只不过这个文件只会在Classpath下的jar包中。来简单看下`spring.factories`文件的内容吧：\n\n```  \n// 来自 org.springframework.boot.autoconfigure下的META-INF/spring.factories  \n// EnableAutoConfiguration后文会讲到，它用于开启Spring Boot自动配置功能  \norg.springframework.boot.autoconfigure.EnableAutoConfiguration=\\  \norg.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\\  \norg.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\\  \norg.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration\\  \n  \n```  \n\n执行`loadFactoryNames(EnableAutoConfiguration.class, classLoader)`后，得到对应的一组`@Configuration`类，  \n我们就可以通过反射实例化这些类然后注入到IOC容器中，最后容器里就有了一系列标注了`@Configuration`的JavaConfig形式的配置类。\n\n这就是`SpringFactoriesLoader`，它本质上属于Spring框架私有的一种扩展方案，类似于SPI，Spring Boot在Spring基础上的很多核心功能都是基于此，希望大家可以理解。\n\n## 四、另一件武器：Spring容器的事件监听机制\n\n过去，事件监听机制多用于图形界面编程，比如：**点击**按钮、在文本框**输入**内容等操作被称为事件，而当事件触发时，应用程序作出一定的响应则表示应用监听了这个事件，而在服务器端，事件的监听机制更多的用于异步通知以及监控和异常处理。Java提供了实现事件监听机制的两个基础类：自定义事件类型扩展自`java.util.EventObject`、事件的监听器扩展自`java.util.EventListener`。来看一个简单的实例：简单的监控一个方法的耗时。\n\n首先定义事件类型，通常的做法是扩展EventObject，随着事件的发生，相应的状态通常都封装在此类中：\n\n```  \npublic class MethodMonitorEvent extends EventObject {  \n    // 时间戳，用于记录方法开始执行的时间  \n    public long timestamp;  \n    public MethodMonitorEvent(Object source) {        super(source);    }}  \n  \n```  \n\n事件发布之后，相应的监听器即可对该类型的事件进行处理，我们可以在方法开始执行之前发布一个begin事件，在方法执行结束之后发布一个end事件，相应地，事件监听器需要提供方法对这两种情况下接收到的事件进行处理：\n\n```  \n// 1、定义事件监听接口  \npublic interface MethodMonitorEventListener extends EventListener {  \n    // 处理方法执行之前发布的事件  \n    public void onMethodBegin(MethodMonitorEvent event);    // 处理方法结束时发布的事件  \n    public void onMethodEnd(MethodMonitorEvent event);}  \n// 2、事件监听接口的实现：如何处理  \npublic class AbstractMethodMonitorEventListener implements MethodMonitorEventListener {  \n  \n    @Override    public void onMethodBegin(MethodMonitorEvent event) {        // 记录方法开始执行时的时间  \n        event.timestamp = System.currentTimeMillis();    }  \n    @Override    public void onMethodEnd(MethodMonitorEvent event) {        // 计算方法耗时  \n        long duration = System.currentTimeMillis() - event.timestamp;        System.out.println(\"耗时：\" + duration);  \n    }}  \n  \n```  \n\n事件监听器接口针对不同的事件发布实际提供相应的处理方法定义，最重要的是，其方法只接收MethodMonitorEvent参数，说明这个监听器类只负责监听器对应的事件并进行处理。有了事件和监听器，剩下的就是发布事件，然后让相应的监听器监听并处理。通常情况，我们会有一个事件发布者，它本身作为事件源，在合适的时机，将相应的事件发布给对应的事件监听器：\n\n```  \npublic class MethodMonitorEventPublisher {  \n  \n    private List<MethodMonitorEventListener> listeners = new ArrayList<MethodMonitorEventListener>();  \n    public void methodMonitor() {        MethodMonitorEvent eventObject = new MethodMonitorEvent(this);        publishEvent(\"begin\",eventObject);        // 模拟方法执行：休眠5秒钟  \n        TimeUnit.SECONDS.sleep(5);        publishEvent(\"end\",eventObject);  \n    }  \n    private void publishEvent(String status,MethodMonitorEvent event) {        // 避免在事件处理期间，监听器被移除，这里为了安全做一个复制操作  \n        List<MethodMonitorEventListener> copyListeners = ➥ new ArrayList<MethodMonitorEventListener>(listeners);        for (MethodMonitorEventListener listener : copyListeners) {            if (\"begin\".equals(status)) {                listener.onMethodBegin(event);            } else {                listener.onMethodEnd(event);            }        }    }  \n    public static void main(String[] args) {        MethodMonitorEventPublisher publisher = new MethodMonitorEventPublisher();        publisher.addEventListener(new AbstractMethodMonitorEventListener());        publisher.methodMonitor();    }    // 省略实现  \n    public void addEventListener(MethodMonitorEventListener listener) {}    public void removeEventListener(MethodMonitorEventListener listener) {}    public void removeAllListeners() {}  \n```  \n\n对于事件发布者（事件源）通常需要关注两点：\n\n1.  在合适的时机发布事件。此例中的methodMonitor()方法是事件发布的源头，其在方法执行之前和结束之后两个时间点发布MethodMonitorEvent事件，每个时间点发布的事件都会传给相应的监听器进行处理。在具体实现时需要注意的是，事件发布是顺序执行，为了不影响处理性能，事件监听器的处理逻辑应尽量简单。\n2.  事件监听器的管理。publisher类中提供了事件监听器的注册与移除方法，这样客户端可以根据实际情况决定是否需要注册新的监听器或者移除某个监听器。如果这里没有提供remove方法，那么注册的监听器示例将一直被MethodMonitorEventPublisher引用，即使已经废弃不用了，也依然在发布者的监听器列表中，这会导致隐性的内存泄漏。\n\n#### Spring容器内的事件监听机制\n\nSpring的ApplicationContext容器内部中的所有事件类型均继承自`org.springframework.context.AppliationEvent`，容器中的所有监听器都实现`org.springframework.context.ApplicationListener`接口，并且以bean的形式注册在容器中。一旦在容器内发布ApplicationEvent及其子类型的事件，注册到容器的ApplicationListener就会对这些事件进行处理。\n\n你应该已经猜到是怎么回事了。\n\nApplicationEvent继承自EventObject，Spring提供了一些默认的实现，比如：`ContextClosedEvent`表示容器在即将关闭时发布的事件类型，`ContextRefreshedEvent`表示容器在初始化或者刷新的时候发布的事件类型......\n\n容器内部使用ApplicationListener作为事件监听器接口定义，它继承自EventListener。ApplicationContext容器在启动时，会自动识别并加载EventListener类型的bean，一旦容器内有事件发布，将通知这些注册到容器的EventListener。\n\nApplicationContext接口继承了ApplicationEventPublisher接口，该接口提供了`void publishEvent(ApplicationEvent event)`方法定义，不难看出，ApplicationContext容器担当的就是事件发布者的角色。如果有兴趣可以查看`AbstractApplicationContext.publishEvent(ApplicationEvent event)`方法的源码：ApplicationContext将事件的发布以及监听器的管理工作委托给`ApplicationEventMulticaster`接口的实现类。在容器启动时，会检查容器内是否存在名为applicationEventMulticaster的ApplicationEventMulticaster对象实例。如果有就使用其提供的实现，没有就默认初始化一个SimpleApplicationEventMulticaster作为实现。\n\n最后，如果我们业务需要在容器内部发布事件，只需要为其注入ApplicationEventPublisher依赖即可：实现ApplicationEventPublisherAware接口或者ApplicationContextAware接口(Aware接口相关内容请回顾上文)。\n\n## 五、出神入化：揭秘自动配置原理\n\n典型的Spring Boot应用的启动类一般均位于`src/main/java`根路径下，比如`MoonApplication`类：\n\n```  \n@SpringBootApplication  \npublic class MoonApplication {  \n  \n    public static void main(String[] args) {        SpringApplication.run(MoonApplication.class, args);    }}  \n  \n```  \n\n其中`@SpringBootApplication`开启组件扫描和自动配置，而`SpringApplication.run`则负责启动引导应用程序。`@SpringBootApplication`是一个复合`Annotation`，它将三个有用的注解组合在一起：\n\n```  \n@Target(ElementType.TYPE)  \n@Retention(RetentionPolicy.RUNTIME)  \n@Documented  \n@Inherited  \n@SpringBootConfiguration  \n@EnableAutoConfiguration  \n@ComponentScan(excludeFilters = {  \n        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })public @interface SpringBootApplication {  \n    // ......}  \n  \n```  \n\n`@SpringBootConfiguration`就是`@Configuration`，它是Spring框架的注解，标明该类是一个`JavaConfig`配置类。而`@ComponentScan`启用组件扫描，前文已经详细讲解过，这里着重关注`@EnableAutoConfiguration`。\n\n`@EnableAutoConfiguration`注解表示开启Spring Boot自动配置功能，Spring Boot会根据应用的依赖、自定义的bean、classpath下有没有某个类 等等因素来猜测你需要的bean，然后注册到IOC容器中。那`@EnableAutoConfiguration`是如何推算出你的需求？首先看下它的定义：\n\n```  \n@Target(ElementType.TYPE)  \n@Retention(RetentionPolicy.RUNTIME)  \n@Documented  \n@Inherited  \n@AutoConfigurationPackage  \n@Import(EnableAutoConfigurationImportSelector.class)  \npublic @interface EnableAutoConfiguration {  \n    // ......}  \n  \n```  \n\n你的关注点应该在`@Import(EnableAutoConfigurationImportSelector.class)`上了，前文说过，`@Import`注解用于导入类，并将这个类作为一个bean的定义注册到容器中，这里它将把`EnableAutoConfigurationImportSelector`作为bean注入到容器中，而这个类会将所有符合条件的@Configuration配置都加载到容器中，看看它的代码：\n\n```  \npublic String[] selectImports(AnnotationMetadata annotationMetadata) {  \n    // 省略了大部分代码，保留一句核心代码  \n    // 注意：SpringBoot最近版本中，这句代码被封装在一个单独的方法中  \n    // SpringFactoriesLoader相关知识请参考前文  \n    List<String> factories = new ArrayList<String>(new LinkedHashSet<String>(        SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, this.beanClassLoader)));  \n}  \n  \n```  \n\n这个类会扫描所有的jar包，将所有符合条件的@Configuration配置类注入的容器中，何为符合条件，看看`META-INF/spring.factories`的文件内容：\n\n```  \n// 来自 org.springframework.boot.autoconfigure下的META-INF/spring.factories  \n// 配置的key = EnableAutoConfiguration，与代码中一致  \norg.springframework.boot.autoconfigure.EnableAutoConfiguration=\\  \norg.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\\  \norg.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\\  \norg.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration\\  \n.....  \n  \n```  \n\n以`DataSourceAutoConfiguration`为例，看看Spring Boot是如何自动配置的：\n\n```  \n@Configuration  \n@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })  \n@EnableConfigurationProperties(DataSourceProperties.class)  \n@Import({ Registrar.class, DataSourcePoolMetadataProvidersConfiguration.class })  \npublic class DataSourceAutoConfiguration {  \n}  \n  \n```  \n\n分别说一说：\n\n*   `@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })`：当Classpath中存在DataSource或者EmbeddedDatabaseType类时才启用这个配置，否则这个配置将被忽略。\n*   `@EnableConfigurationProperties(DataSourceProperties.class)`：将DataSource的默认配置类注入到IOC容器中，DataSourceproperties定义为：\n\n```  \n// 提供对datasource配置信息的支持，所有的配置前缀为：spring.datasource  \n@ConfigurationProperties(prefix = \"spring.datasource\")  \npublic class DataSourceProperties  {  \n    private ClassLoader classLoader;    private Environment environment;    private String name = \"testdb\";    ......}  \n  \n```  \n\n*   `@Import({ Registrar.class, DataSourcePoolMetadataProvidersConfiguration.class })`：导入其他额外的配置，就以`DataSourcePoolMetadataProvidersConfiguration`为例吧。\n\n```  \n@Configuration  \npublic class DataSourcePoolMetadataProvidersConfiguration {  \n  \n    @Configuration    @ConditionalOnClass(org.apache.tomcat.jdbc.pool.DataSource.class)    static class TomcatDataSourcePoolMetadataProviderConfiguration {        @Bean        public DataSourcePoolMetadataProvider tomcatPoolDataSourceMetadataProvider() {            .....        }    }  ......}  \n  \n```  \n\nDataSourcePoolMetadataProvidersConfiguration是数据库连接池提供者的一个配置类，即Classpath中存在`org.apache.tomcat.jdbc.pool.DataSource.class`，则使用tomcat-jdbc连接池，如果Classpath中存在`HikariDataSource.class`则使用Hikari连接池。\n\n这里仅描述了DataSourceAutoConfiguration的冰山一角，但足以说明Spring Boot如何利用条件话配置来实现自动配置的。回顾一下，`@EnableAutoConfiguration`中导入了EnableAutoConfigurationImportSelector类，而这个类的`selectImports()`通过SpringFactoriesLoader得到了大量的配置类，而每一个配置类则根据条件化配置来做出决策，以实现自动配置。\n\n整个流程很清晰，但漏了一个大问题：`EnableAutoConfigurationImportSelector.selectImports()`是何时执行的？其实这个方法会在容器启动过程中执行：`AbstractApplicationContext.refresh()`，更多的细节在下一小节中说明。\n\n## 六、启动引导：Spring Boot应用启动的秘密\n\n### 6.1 SpringApplication初始化\n\nSpringBoot整个启动流程分为两个步骤：初始化一个SpringApplication对象、执行该对象的run方法。看下SpringApplication的初始化流程，SpringApplication的构造方法中调用initialize(Object[] sources)方法，其代码如下：\n\n```  \nprivate void initialize(Object[] sources) {  \n     if (sources != null && sources.length > 0) {         this.sources.addAll(Arrays.asList(sources));     }     // 判断是否是Web项目  \n     this.webEnvironment = deduceWebEnvironment();     setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));     setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));     // 找到入口类  \n     this.mainApplicationClass = deduceMainApplicationClass();}  \n  \n```  \n\n初始化流程中最重要的就是通过SpringFactoriesLoader找到`spring.factories`文件中配置的`ApplicationContextInitializer`和`ApplicationListener`两个接口的实现类名称，以便后期构造相应的实例。`ApplicationContextInitializer`的主要目的是在`ConfigurableApplicationContext`做refresh之前，对ConfigurableApplicationContext实例做进一步的设置或处理。ConfigurableApplicationContext继承自ApplicationContext，其主要提供了对ApplicationContext进行设置的能力。\n\n实现一个ApplicationContextInitializer非常简单，因为它只有一个方法，但大多数情况下我们没有必要自定义一个ApplicationContextInitializer，即便是Spring Boot框架，它默认也只是注册了两个实现，毕竟Spring的容器已经非常成熟和稳定，你没有必要来改变它。\n\n而`ApplicationListener`的目的就没什么好说的了，它是Spring框架对Java事件监听机制的一种框架实现，具体内容在前文Spring事件监听机制这个小节有详细讲解。这里主要说说，如果你想为Spring Boot应用添加监听器，该如何实现？\n\nSpring Boot提供两种方式来添加自定义监听器：\n\n*   通过`SpringApplication.addListeners(ApplicationListener<?>... listeners)`或者`SpringApplication.setListeners(Collection<? extends ApplicationListener<?>> listeners)`两个方法来添加一个或者多个自定义监听器\n*   既然SpringApplication的初始化流程中已经从`spring.factories`中获取到`ApplicationListener`的实现类，那么我们直接在自己的jar包的`META-INF/spring.factories`文件中新增配置即可：\n\n```  \norg.springframework.context.ApplicationListener=\\  \ncn.moondev.listeners.xxxxListener\\  \n  \n```  \n\n关于SpringApplication的初始化，我们就说这么多。\n\n### 6.2 Spring Boot启动流程\n\nSpring Boot应用的整个启动流程都封装在SpringApplication.run方法中，其整个流程真的是太长太长了，但本质上就是在Spring容器启动的基础上做了大量的扩展，按照这个思路来看看源码：\n\n```  \npublic ConfigurableApplicationContext run(String... args) {  \n        StopWatch stopWatch = new StopWatch();        stopWatch.start();        ConfigurableApplicationContext context = null;        FailureAnalyzers analyzers = null;        configureHeadlessProperty();        // ①  \n        SpringApplicationRunListeners listeners = getRunListeners(args);        listeners.starting();        try {            // ②  \n            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);            ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);            // ③  \n            Banner printedBanner = printBanner(environment);            // ④  \n            context = createApplicationContext();            // ⑤  \n            analyzers = new FailureAnalyzers(context);            // ⑥  \n            prepareContext(context, environment, listeners, applicationArguments,printedBanner);            // ⑦   \n            refreshContext(context);  \n            // ⑧  \n            afterRefresh(context, applicationArguments);            // ⑨  \n            listeners.finished(context, null);            stopWatch.stop();            return context;        }        catch (Throwable ex) {            handleRunFailure(context, listeners, analyzers, ex);            throw new IllegalStateException(ex);        }    }  \n```  \n\n① 通过SpringFactoriesLoader查找并加载所有的`SpringApplicationRunListeners`，通过调用starting()方法通知所有的SpringApplicationRunListeners：应用开始启动了。SpringApplicationRunListeners其本质上就是一个事件发布者，它在SpringBoot应用启动的不同时间点发布不同应用事件类型(ApplicationEvent)，如果有哪些事件监听者(ApplicationListener)对这些事件感兴趣，则可以接收并且处理。还记得初始化流程中，SpringApplication加载了一系列ApplicationListener吗？这个启动流程中没有发现有发布事件的代码，其实都已经在SpringApplicationRunListeners这儿实现了。\n\n简单的分析一下其实现流程，首先看下SpringApplicationRunListener的源码：\n\n```  \npublic interface SpringApplicationRunListener {  \n  \n    // 运行run方法时立即调用此方法，可以用户非常早期的初始化工作  \n    void starting();  \n    // Environment准备好后，并且ApplicationContext创建之前调用  \n    void environmentPrepared(ConfigurableEnvironment environment);  \n    // ApplicationContext创建好后立即调用  \n    void contextPrepared(ConfigurableApplicationContext context);  \n    // ApplicationContext加载完成，在refresh之前调用  \n    void contextLoaded(ConfigurableApplicationContext context);  \n    // 当run方法结束之前调用  \n    void finished(ConfigurableApplicationContext context, Throwable exception);  \n}  \n  \n```  \n\nSpringApplicationRunListener只有一个实现类：`EventPublishingRunListener`。①处的代码只会获取到一个EventPublishingRunListener的实例，我们来看看starting()方法的内容：\n\n```  \npublic void starting() {  \n    // 发布一个ApplicationStartedEvent  \n    this.initialMulticaster.multicastEvent(new ApplicationStartedEvent(this.application, this.args));}  \n  \n```  \n\n顺着这个逻辑，你可以在②处的`prepareEnvironment()`方法的源码中找到`listeners.environmentPrepared(environment);`即SpringApplicationRunListener接口的第二个方法，那不出你所料，`environmentPrepared()`又发布了另外一个事件`ApplicationEnvironmentPreparedEvent`。接下来会发生什么，就不用我多说了吧。\n\n② 创建并配置当前应用将要使用的`Environment`，Environment用于描述应用程序当前的运行环境，其抽象了两个方面的内容：配置文件(profile)和属性(properties)，开发经验丰富的同学对这两个东西一定不会陌生：不同的环境(eg：生产环境、预发布环境)可以使用不同的配置文件，而属性则可以从配置文件、环境变量、命令行参数等来源获取。因此，当Environment准备好后，在整个应用的任何时候，都可以从Environment中获取资源。\n\n总结起来，②处的两句代码，主要完成以下几件事：\n\n*   判断Environment是否存在，不存在就创建（如果是web项目就创建`StandardServletEnvironment`，否则创建`StandardEnvironment`）\n*   配置Environment：配置profile以及properties\n*   调用SpringApplicationRunListener的`environmentPrepared()`方法，通知事件监听者：应用的Environment已经准备好\n\n③、SpringBoot应用在启动时会输出这样的东西：\n\n```  \n  .   ____          _            __ _ _ /\\\\ / ___'_ __ _ _(_)_ __  __ _ \\ \\ \\ \\( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\  \n \\\\/  ___)| |_)| | | | | || (_| |  ) ) ) )  '  |____| .__|_| |_|_| |_\\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot ::        (v1.5.6.RELEASE)  \n```  \n\n如果想把这个东西改成自己的涂鸦，你可以研究以下Banner的实现，这个任务就留给你们吧。\n\n④、根据是否是web项目，来创建不同的ApplicationContext容器。\n\n⑤、创建一系列`FailureAnalyzer`，创建流程依然是通过SpringFactoriesLoader获取到所有实现FailureAnalyzer接口的class，然后在创建对应的实例。FailureAnalyzer用于分析故障并提供相关诊断信息。\n\n⑥、初始化ApplicationContext，主要完成以下工作：\n\n*   将准备好的Environment设置给ApplicationContext\n*   遍历调用所有的ApplicationContextInitializer的`initialize()`方法来对已经创建好的ApplicationContext进行进一步的处理\n*   调用SpringApplicationRunListener的`contextPrepared()`方法，通知所有的监听者：ApplicationContext已经准备完毕\n*   将所有的bean加载到容器中\n*   调用SpringApplicationRunListener的`contextLoaded()`方法，通知所有的监听者：ApplicationContext已经装载完毕\n\n⑦、调用ApplicationContext的`refresh()`方法，完成IoC容器可用的最后一道工序。从名字上理解为刷新容器，那何为刷新？就是插手容器的启动，联系一下第一小节的内容。那如何刷新呢？且看下面代码：\n\n```  \n// 摘自refresh()方法中一句代码  \ninvokeBeanFactoryPostProcessors(beanFactory);  \n  \n```  \n\n看看这个方法的实现：\n\n```  \nprotected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {  \n    PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());    ......}  \n  \n```  \n\n获取到所有的`BeanFactoryPostProcessor`来对容器做一些额外的操作。BeanFactoryPostProcessor允许我们在容器实例化相应对象之前，对注册到容器的BeanDefinition所保存的信息做一些额外的操作。这里的getBeanFactoryPostProcessors()方法可以获取到3个Processor：\n\n```  \nConfigurationWarningsApplicationContextInitializer$ConfigurationWarningsPostProcessor  \nSharedMetadataReaderFactoryContextInitializer$CachingMetadataReaderFactoryPostProcessor  \nConfigFileApplicationListener$PropertySourceOrderingPostProcessor  \n  \n```  \n\n不是有那么多BeanFactoryPostProcessor的实现类，为什么这儿只有这3个？因为在初始化流程获取到的各种ApplicationContextInitializer和ApplicationListener中，只有上文3个做了类似于如下操作：\n\n```  \npublic void initialize(ConfigurableApplicationContext context) {  \n    context.addBeanFactoryPostProcessor(new ConfigurationWarningsPostProcessor(getChecks()));}  \n  \n```  \n\n然后你就可以进入到`PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors()`方法了，这个方法除了会遍历上面的3个BeanFactoryPostProcessor处理外，还会获取类型为`BeanDefinitionRegistryPostProcessor`的bean：`org.springframework.context.annotation.internalConfigurationAnnotationProcessor`，对应的Class为`ConfigurationClassPostProcessor`。`ConfigurationClassPostProcessor`用于解析处理各种注解，包括：@Configuration、@ComponentScan、@Import、@PropertySource、@ImportResource、@Bean。当处理`@import`注解的时候，就会调用<自动配置>这一小节中的`EnableAutoConfigurationImportSelector.selectImports()`来完成自动配置功能。其他的这里不再多讲，如果你有兴趣，可以查阅参考资料6。\n\n⑧、查找当前context中是否注册有CommandLineRunner和ApplicationRunner，如果有则遍历执行它们。\n\n⑨、执行所有SpringApplicationRunListener的finished()方法。\n\n这就是Spring Boot的整个启动流程，其核心就是在Spring容器初始化并启动的基础上加入各种扩展点，这些扩展点包括：ApplicationContextInitializer、ApplicationListener以及各种BeanFactoryPostProcessor等等。你对整个流程的细节不必太过关注，甚至没弄明白也没有关系，你只要理解这些扩展点是在何时如何工作的，能让它们为你所用即可。\n\n整个启动流程确实非常复杂，可以查询参考资料中的部分章节和内容，对照着源码，多看看，我想最终你都能弄清楚的。言而总之，Spring才是核心，理解清楚Spring容器的启动流程，那Spring Boot启动流程就不在话下了。\n\n## 参考文章\n[1][王福强 著；SpringBoot揭秘：快速构建微服务体系; 机械工业出版社, 2016](https://link.jianshu.com/?t=http%3A%2F%2Funion-click.jd.com%2Fjdc%3Fd%3D4jESQ9)  \n[2][王福强 著；Spring揭秘; 人民邮件出版社, 2009](https://link.jianshu.com/?t=http%3A%2F%2Funion-click.jd.com%2Fjdc%3Fd%3DyzfgeF)  \n[3][Craig Walls 著；丁雪丰 译；Spring Boot实战；中国工信出版集团 人民邮电出版社，2016](https://link.jianshu.com/?t=http%3A%2F%2Funion-click.jd.com%2Fjdc%3Fd%3DAQ6oHO)  \n[4][深入探讨 Java 类加载器](https://link.jianshu.com/?t=https%3A%2F%2Fwww.ibm.com%2Fdeveloperworks%2Fcn%2Fjava%2Fj-lo-classloader%2F):[https://www.ibm.com/developerworks/cn/java/j-lo-classloader/](https://link.jianshu.com/?t=https%3A%2F%2Fwww.ibm.com%2Fdeveloperworks%2Fcn%2Fjava%2Fj-lo-classloader%2F)  \n[5][spring boot实战：自动配置原理分析](https://link.jianshu.com/?t=http%3A%2F%2Fblog.csdn.net%2Fliaokailin%2Farticle%2Fdetails%2F49559951):[http://blog.csdn.net/liaokailin/article/details/49559951](https://link.jianshu.com/?t=http%3A%2F%2Fblog.csdn.net%2Fliaokailin%2Farticle%2Fdetails%2F49559951)  \n[6][spring boot实战：Spring boot Bean加载源码分析](https://link.jianshu.com/?t=http%3A%2F%2Fblog.csdn.net%2Fliaokailin%2Farticle%2Fdetails%2F49107209)：[http://blog.csdn.net/liaokailin/article/details/49107209](https://link.jianshu.com/?t=http%3A%2F%2Fblog.csdn.net%2Fliaokailin%2Farticle%2Fdetails%2F49107209)\n\n"
  },
  {
    "path": "docs/Spring全家桶/SpringBoot源码解析/@SpringBootApplication注解.md",
    "content": "springboot ϻעһע⣺`@SpringBootApplication`˽Դ עá\n\n`@SpringBootApplication` £\n\n```\n@Target(ElementType.TYPE)\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\n@Inherited\n@SpringBootConfiguration\n@EnableAutoConfiguration\n@ComponentScan(excludeFilters = { \n        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),\n        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })\npublic @interface SpringBootApplication {\n\n    /**\n     * ԶװҪų࣬ @EnableAutoConfiguration\n     */\n    @AliasFor(annotation = EnableAutoConfiguration.class)\n    Class<?>[] exclude() default {};\n\n    /**\n     *  ԶװҪų @EnableAutoConfiguration\n     */\n    @AliasFor(annotation = EnableAutoConfiguration.class)\n    String[] excludeName() default {};\n\n    /**\n     * ɨİ @ComponentScan\n     */\n    @AliasFor(annotation = ComponentScan.class, attribute = \"basePackages\")\n    String[] scanBasePackages() default {};\n\n    /**\n     * ɨclassclassڵİᱻɨ裬 @ComponentScan\n     */\n    @AliasFor(annotation = ComponentScan.class, attribute = \"basePackageClasses\")\n    Class<?>[] scanBasePackageClasses() default {};\n\n    /**\n     * Ƿ @Bean  @Configuration\n     */\n    @AliasFor(annotation = Configuration.class)\n    boolean proxyBeanMethods() default true;\n\n}\n\n```\n\n1.  `@SpringBootApplication` һע⣬ `@SpringBootConfiguration``@EnableAutoConfiguration``@ComponentScan` עĹܣ\n2.  `@SpringBootApplication` ҲṩһЩԣЩע⡣\n\nע÷ֱʲô\n\n### 1. `@SpringBootConfiguration`\n\n `@SpringBootConfiguration`£\n\n```\n@Target(ElementType.TYPE)\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\n@Configuration\npublic @interface SpringBootConfiguration {\n\n    @AliasFor(annotation = Configuration.class)\n    boolean proxyBeanMethods() default true;\n\n}\n\n```\n\nעȽϼ򵥣 `@Configuration`Ȼһ `proxyBeanMethods()` `@Configuration`ˣ`@SpringBootConfiguration` ûʲôֻǽ `@Configuration` ʹ `@Configuration` Ĺܡ\n\n `@Configuration` springܱ spring ʶΪ `Component` `proxyBeanMethods != false` ʱᱻ spring Ϊ `Full` ࣬ںе `@Bean` ʱ cglib ⷽݣɲο [ConfigurationClassPostProcessor @Bean ע](https://my.oschina.net/funcy/blog/4492878).\n\n### 2. `@EnableAutoConfiguration`\n\n`@EnableAutoConfiguration` Ҫ Զװ书ܣ£\n\n```\n@Target(ElementType.TYPE)\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\n@Inherited\n// Զװİ\n@AutoConfigurationPackage\n// Զװ\n@Import(AutoConfigurationImportSelector.class)\npublic @interface EnableAutoConfiguration {\n\n    String ENABLED_OVERRIDE_PROPERTY = \"spring.boot.enableautoconfiguration\";\n\n    /**\n     * жųԶװ\n     */\n    Class<?>[] exclude() default {};\n\n    /**\n     * жųԶװ\n     */\n    String[] excludeName() default {};\n\n}\n\n```\n\nӴпԿ\n\n1.  ע `@AutoConfigurationPackage` עĹܣעָԶװİ\n2.  עͨ `@Import` עһ `AutoConfigurationImportSelector`ԶװĹؼ\n3.  עṩãųָԶװ࣬Ըų (`Class` )ҲԸ (`.`) ų\n\nע `@AutoConfigurationPackage`  `AutoConfigurationImportSelector`\n\n#### 2.1 `@AutoConfigurationPackage`\n\n`@AutoConfigurationPackage` ָԶװİ£\n\n```\n@Target(ElementType.TYPE)\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\n@Inherited\n@Import(AutoConfigurationPackages.Registrar.class)\npublic @interface AutoConfigurationPackage {\n\n}\n\n```\n\nעݷǳ򵥣ʹ `@Import` ע `AutoConfigurationPackages.Registrar`ݣ\n\n```\npublic abstract class AutoConfigurationPackages {\n\n    private static final String BEAN = AutoConfigurationPackages.class.getName();\n\n    static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {\n\n        /**\n         *  ImportBeanDefinitionRegistrar Ĵspring registerBeanDefinitions() ע\n         */\n        @Override\n        public void registerBeanDefinitions(AnnotationMetadata metadata, \n                    BeanDefinitionRegistry registry) {\n            register(registry, new PackageImport(metadata).getPackageName());\n        }\n\n        @Override\n        public Set<Object> determineImports(AnnotationMetadata metadata) {\n            return Collections.singleton(new PackageImport(metadata));\n        }\n\n    }\n\n    /**\n     * ע\n     * 1\\.  beanFacotry а BEAN򽫴İӵ BEAN Ӧ BeanDefinition Ĺ췽ֵϣ\n     * 2\\.  beanFacotry в BEAN򴴽 beanDefinitionòֵȻעᵽ beanFacotry\n     * עᵽbeanFacotryеbeanΪBasePackages\n     */\n    public static void register(BeanDefinitionRegistry registry, String... packageNames) {\n        if (registry.containsBeanDefinition(BEAN)) {\n            BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);\n            // bean  BasePackages췽 BasePackages(String... names)ȡԭĹֵ\n            ConstructorArgumentValues constructorArguments \n                    = beanDefinition.getConstructorArgumentValues();\n            // ԭĹֵԼ packageNames ͳһӵ췽ĵ0ֵ\n            constructorArguments.addIndexedArgumentValue(0, \n                    addBasePackages(constructorArguments, packageNames));\n        }\n        else {\n            GenericBeanDefinition beanDefinition = new GenericBeanDefinition();\n            // BeanClassΪBasePackages.class\n            beanDefinition.setBeanClass(BasePackages.class);\n            // ù췽Ĳֵ\n            beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);\n            beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);\n            registry.registerBeanDefinition(BEAN, beanDefinition);\n        }\n    }\n\n    /**\n     * packageName İװ\n     * packageName ǴڵİPackageImportĹ췽лȡ\n     */\n    private static final class PackageImport {\n\n        private final String packageName;\n\n        PackageImport(AnnotationMetadata metadata) {\n            // ȡڰ\n            this.packageName = ClassUtils.getPackageName(metadata.getClassName());\n        }\n\n        String getPackageName() {\n            return this.packageName;\n        }\n\n        // ʡ equals/toString/hashCode \n        ...\n\n    }\n\n    /**\n     * ע⵽ beanFactory е\n     * һListṹɨ·\n     */\n    static final class BasePackages {\n        // ɨ·ﱣ\n        private final List<String> packages;\n\n        private boolean loggedBasePackageInfo;\n\n        BasePackages(String... names) {\n            List<String> packages = new ArrayList<>();\n            for (String name : names) {\n                if (StringUtils.hasText(name)) {\n                    packages.add(name);\n                }\n            }\n            this.packages = packages;\n        }\n\n        // ʡһЩ\n        ...\n    }\n\n}\n\n```\n\nе㳤߼ӣ£\n\n1.  `AutoConfigurationPackages.Registrar` ʵ `ImportBeanDefinitionRegistrar``registerBeanDefinitions(...)`  spring ע `BasePackages`ע߼ `AutoConfigurationPackages#register` У\n2.  `AutoConfigurationPackages#register` ע߼ΪжǷע `BasePackages`עˣͽǰڵİӵ `BasePackages` Ĺ췽ֵУʹ `BeanDefinition`ù췽ĲֵȻעᵽ spring У\n\n#### 2.2 `AutoConfigurationImportSelector`\n\n`AutoConfigurationImportSelector` ǴԶõĹؼ£\n\n```\npublic class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,\n\n    ...\n\n}\n\n```\n\n`AutoConfigurationImportSelector` ʵ `DeferredImportSelector`һ `ImportSelector` ࣬ȼ ( `@ComponentScan``@Component``@Bean``@Configuration`  `@Import` ע⴦֮ٴ) `AutoConfigurationImportSelector` лᴦԶļַ̣ͨʽԶ spring С\n\n spring  `@Import` ĴԲο [ConfigurationClassPostProcessor ֮ @Import ע](https://my.oschina.net/funcy/blog/4678152).\n\n `AutoConfigurationImportSelector` ȡԶ̣ںоľͲչˡ\n\n### 3. `@ComponentScan`\n\nעشѾϤˣָ˰ɨ·ָɨİЩ [ConfigurationClassPostProcessor ֮ @ComponentScan ע](https://my.oschina.net/funcy/blog/4836178)һѾϸˣͲٷˡ\n\nעʹõ 2 ࣺ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-19d14d3d8262eead434d5ca09369e1789d5.png)\n\n#### 3.1 `TypeExcludeFilter`\n\nʾڽаɨʱųһЩ࣬£\n\n```\npublic class TypeExcludeFilter implements TypeFilter, BeanFactoryAware {\n\n    private BeanFactory beanFactory;\n\n    private Collection<TypeExcludeFilter> delegates;\n\n    @Override\n    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {\n        this.beanFactory = beanFactory;\n    }\n\n    @Override\n    public boolean match(MetadataReader metadataReader, \n            MetadataReaderFactory metadataReaderFactory) throws IOException {\n        if (this.beanFactory instanceof ListableBeanFactory \n                && getClass() == TypeExcludeFilter.class) {\n            // getDelegates() ȡǰе TypeExcludeFilter ʵ\n            // ̳ TypeExcludeFilterԶƥ\n            for (TypeExcludeFilter delegate : getDelegates()) {\n                if (delegate.match(metadataReader, metadataReaderFactory)) {\n                    return true;\n                }\n            }\n        }\n        return false;\n    }\n\n    private Collection<TypeExcludeFilter> getDelegates() {\n        Collection<TypeExcludeFilter> delegates = this.delegates;\n        if (delegates == null) {\n            delegates = ((ListableBeanFactory) this.beanFactory)\n                    .getBeansOfType(TypeExcludeFilter.class).values();\n            this.delegates = delegates;\n        }\n        return delegates;\n    }\n\n    ....\n\n```\n\nӴҪųһЩ ࣬ǿ̳ `TypeExcludeFilter` ࣬Ȼд `match(...)` жƥ߼\n\n#### 3.1 `AutoConfigurationExcludeFilter`\n\n`AutoConfigurationExcludeFilter` ųԶ࣬Ҳ˵spring ڽаɨʱɨԶ࣬£\n\n```\npublic class AutoConfigurationExcludeFilter implements TypeFilter, BeanClassLoaderAware {\n\n    private ClassLoader beanClassLoader;\n\n    private volatile List<String> autoConfigurations;\n\n    @Override\n    public void setBeanClassLoader(ClassLoader beanClassLoader) {\n        this.beanClassLoader = beanClassLoader;\n    }\n\n    @Override\n    public boolean match(MetadataReader metadataReader, \n            MetadataReaderFactory metadataReaderFactory) throws IOException {\n        // isConfiguration(...)ǰǷ @Configuration \n        // isAutoConfiguration(...)ǰǷΪԶ\n        return isConfiguration(metadataReader) && isAutoConfiguration(metadataReader);\n    }\n\n    private boolean isConfiguration(MetadataReader metadataReader) {\n        return metadataReader.getAnnotationMetadata().isAnnotated(Configuration.class.getName());\n    }\n\n    private boolean isAutoConfiguration(MetadataReader metadataReader) {\n        // ȡеԶ࣬ȻжϵǰǷ\n        return getAutoConfigurations().contains(metadataReader.getClassMetadata().getClassName());\n    }\n\n    protected List<String> getAutoConfigurations() {\n        if (this.autoConfigurations == null) {\n            this.autoConfigurations = SpringFactoriesLoader\n                    .loadFactoryNames(EnableAutoConfiguration.class, this.beanClassLoader);\n        }\n        return this.autoConfigurations;\n    }\n\n}\n\n```\n\nҪ `match(...)` ƥΪ\n\n1.   `@Configuration` ǣ\n2.  Զࡣ\n\nspring Ͳɨ账\n\nʲôԶأ `isAutoConfiguration(...)` ԿжǷΪԶϣspringboot ʹ `SpringFactoriesLoader` ࣬ȻжϴǷΪ֮һԿԶಢаɨ\n\n `SpringFactoriesLoader` μ࣬»ϸ\n\n### 4\\. ܽ\n\nҪ `@SpringBootApplication` Ĺܣܽ£\n\n1.  `@SpringBootApplication` һע⣬ `@SpringBootConfiguration``@EnableAutoConfiguration``@ComponentScan` עĹܣͬʱṩһЩãҲ 3 ע⣻\n2.  `@SpringBootConfiguration`  `Configuration` עĹܣ\n3.  `@EnableAutoConfiguration` ǿԶװĹؼע⣬б `@AutoConfigurationPackage`Ὣ `@SpringBootApplication` ǵڵİװ `BasePackages`Ȼעᵽ spring У`@EnableAutoConfiguration` ͨ `@Import` ע `AutoConfigurationImportSelector`ὫǰĿֵ֧Զӵ spring У\n4.  `@ComponentScan` ˰ɨ· `excludeFilters` ֵųɨ裬springboot ָ `TypeExcludeFilter`ǿԼ̳иų ͬʱҲָ `AutoConfigurationExcludeFilter`  `Filter` ųԶ࣬Ҳ˵Զ಻а\n\n* * *\n\n_ԭӣ[https://my.oschina.net/funcy/blog/4870882](https://my.oschina.net/funcy/blog/4870882) ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע_"
  },
  {
    "path": "docs/Spring全家桶/SpringBoot源码解析/SpringBootWeb应用（一）：servlet组件的注册流程.md",
    "content": " springboot УҪע servlet `servlet``filter``listener`ôأspringboot ĵΪṩ 3 ַľ 3 ַԴʵ֡\n\n### 1\\. ע᷽ʽ\n\n#### 1.1 ʹ `XxxRegistrationBean` ע\n\nspringboot ṩ͵ `RegistrationBean`  servlet עᣬֱ `ServletRegistrationBean``FilterRegistrationBean``ServletListenerRegistrationBean`Ǽʾǵ÷\n\n```\n/**\n * ׼һservlet\n */\npublic class MyServlet extends HttpServlet {\n\n    @Override\n    protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException {\n        // һЩ\n        ...\n    }\n}\n\n/**\n * ע\n */\n@Bean\npublic ServletRegistrationBean registerServlet() {\n    ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(\n            new MyServlet(), \"/myServlet\");\n    // һЩò\n    servletRegistrationBean.setXxx();\n    ...\n    return servletRegistrationBean;\n}\n\n```\n\nṩ `servlet` ע᷽ʽҪע `filter``listener`ֻʹöӦ `RegistrationBean` ɣͲչʾˡ\n\n#### 1.2 ʹ servlet עע\n\n `Servlet 3.0`servlet ṩ 3 ע `servlet` ע᣺\n\n*   `@WebServlet`:  `servlet` ע\n*   `@WebFilter`:  `filter` ע\n*   `@WebListener`:  `listener` ע\n\n `servlet` עΪ `@WebServlet`:\n\n```\n@Target({ElementType.TYPE})\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\npublic @interface WebServlet {\n    String name() default \"\";\n\n    String[] value() default {};\n\n    String[] urlPatterns() default {};\n\n    int loadOnStartup() default -1;\n\n    WebInitParam[] initParams() default {};\n\n    boolean asyncSupported() default false;\n\n    String smallIcon() default \"\";\n\n    String largeIcon() default \"\";\n\n    String description() default \"\";\n\n    String displayName() default \"\";\n}\n\n```\n\nԿ`@WebServlet` ֶ֧ãָ servlet ơӳ url ָҲṩһʾ\n\n```\n@WebServlet(name = \"myServlet\", urlPatterns = \"/myServlet\")\npublic class JavaServlet extends HttpServlet {\n\n    @Override\n    protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException {\n        // һЩ\n        ...\n    }\n\n}\n\n```\n\n󣬻ҪһҪĲǾʹ `@ServletComponentScan` ɨ蹦ܣ\n\n```\n// ʹ @ServletComponentScan  servlet ɨ蹦\n@ServletComponentScan\n@SpringBootApplication\npublic class MyApplication {\n    public static void main(String[] args) {\n        ...\n    }\n}\n\n```\n\n#### 1.3 `ServletContextInitializer` ע\n\nʹַʽעᣬҪʵ `ServletContextInitializer` ӿڣ\n\n```\n/**\n * ׼һservlet\n */\npublic class MyServlet extends HttpServlet {\n\n    @Override\n    protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException {\n        // һЩ\n        ...\n    }\n}\n\n/**\n * ʵ ServletContextInitializer \n */\n@Component\npublic class ServletConfig implements ServletContextInitializer {\n\n    @Override\n    public void onStartup(ServletContext servletContext) {\n        // ʹ servletContext ע\n        ServletRegistration initServlet = servletContext.addServlet(\"myServlet\", MyServlet.class);\n        // ԽһЩ\n        initServlet.addMapping(\"/myServlet\");\n    }\n\n}\n\n```\n\nʹַʽעᣬҪʵ `ServletContextInitializer`Ȼд `ServletContextInitializer#onStartup`  `ServletContextInitializer#onStartup` ʹ `ServletContext` עᡣ`ServletContext`  servlet ṩעռ࣬ʹ `RegistrationBean` עᣬʹ `@ServletComponentScan` ɨעᣬնͨ `ServletContext` עᵽ servlet С\n\n### 2\\. Դʵ\n\n˽ʹú󣬽Ǿ Դ뿴Щʵ֡\n\n#### 2.1 `@ServletComponentScan` ɨ\n\nֱӽ `@ServletComponentScan`\n\n```\n@Target(ElementType.TYPE)\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\n@Import(ServletComponentScanRegistrar.class)\npublic @interface ServletComponentScan {\n    ...\n}\n\n```\n\nע `@Import` ע⣬һࣺ`ServletComponentScanRegistrar`ǿ྿ɶ\n\n```\n/**\n * ʵImportBeanDefinitionRegistrar\n * ע ServletComponentRegisteringPostProcessor\n */\nclass ServletComponentScanRegistrar implements ImportBeanDefinitionRegistrar {\n\n    private static final String BEAN_NAME = \"servletComponentRegisteringPostProcessor\";\n\n    @Override\n    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, \n            BeanDefinitionRegistry registry) {\n        Set<String> packagesToScan = getPackagesToScan(importingClassMetadata);\n        if (registry.containsBeanDefinition(BEAN_NAME)) {\n            updatePostProcessor(registry, packagesToScan);\n        }\n        else {\n            // ע BeanFactoryPostProcessor\n            addPostProcessor(registry, packagesToScan);\n        }\n    }\n\n    /**\n     * ע BeanFactoryPostProcessor\n     * ע ServletComponentRegisteringPostProcessor\n     */\n    private void addPostProcessor(BeanDefinitionRegistry registry, Set<String> packagesToScan) {\n        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();\n        // ServletComponentRegisteringPostProcessor: ɨ BeanFactoryPostProcessor\n        beanDefinition.setBeanClass(ServletComponentRegisteringPostProcessor.class);\n        beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(packagesToScan);\n        beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);\n        // ServletComponentScanRegistrar Ϊע ServletComponentRegisteringPostProcessor\n        registry.registerBeanDefinition(BEAN_NAME, beanDefinition);\n    }\n\n    ...\n\n}\n\n```\n\nԿʵ `ImportBeanDefinitionRegistrar`Ҫ spring ע `ServletComponentRegisteringPostProcessor`Ǽȥ `ServletComponentRegisteringPostProcessor`\n\n```\nclass ServletComponentRegisteringPostProcessor implements BeanFactoryPostProcessor, \n        ApplicationContextAware {\n\n    /**\n     * Ҫɨİ.\n     */\n    private final Set<String> packagesToScan;\n\n    /**\n     * Ҫɨİɹ췽\n     */\n    ServletComponentRegisteringPostProcessor(Set<String> packagesToScan) {\n        this.packagesToScan = packagesToScan;\n    }\n\n    /**\n     * дBeanFactoryPostProcessorķ\n     */\n    @Override\n    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) \n            throws BeansException {\n        // жǷǶ web \n        if (isRunningInEmbeddedWebServer()) {\n            // ɨɨ\n            ClassPathScanningCandidateComponentProvider componentProvider \n                    = createComponentProvider();\n            for (String packageToScan : this.packagesToScan) {\n                // аɨ\n                scanPackage(componentProvider, packageToScan);\n            }\n        }\n    }\n\n    ...\n\n}\n\n```\n\nԿ`ServletComponentRegisteringPostProcessor` ʵ `BeanFactoryPostProcessor`д `BeanFactoryPostProcessor#postProcessBeanFactory` дɨɨǰǴɨ `ClassPathScanningCandidateComponentProvider`Ȼٽɨ衣\n\nɨĴ `createComponentProvider()`\n\n```\n//  handler\nprivate static final List<ServletComponentHandler> HANDLERS;\n\nstatic {\n    List<ServletComponentHandler> servletComponentHandlers = new ArrayList<>();\n    servletComponentHandlers.add(new WebServletHandler());\n    servletComponentHandlers.add(new WebFilterHandler());\n    servletComponentHandlers.add(new WebListenerHandler());\n    HANDLERS = Collections.unmodifiableList(servletComponentHandlers);\n}\n\n/**\n * ɨ\n */\nprivate ClassPathScanningCandidateComponentProvider createComponentProvider() {\n    // \n    ClassPathScanningCandidateComponentProvider componentProvider \n            = new ClassPathScanningCandidateComponentProvider(false);\n    componentProvider.setEnvironment(this.applicationContext.getEnvironment());\n    componentProvider.setResourceLoader(this.applicationContext);\n    for (ServletComponentHandler handler : HANDLERS) {\n        // ù˹\n        componentProvider.addIncludeFilter(handler.getTypeFilter());\n    }\n    return componentProvider;\n}\n\n```\n\n`createComponentProvider()` УǴɨȻһЩԣžù˹ص¹˹ãЩ `WebServletHandler`/`WebFilterHandler`/`WebListenerHandler`  `getTypeFilter()` ṩ\n\n`getTypeFilter()` λһ󷽷У\n\n```\nabstract class ServletComponentHandler {\n\n    private final TypeFilter typeFilter;\n\n    /**\n     * ע⣬תΪ AnnotationTypeFilter \n     */\n    protected ServletComponentHandler(Class<? extends Annotation> annotationType) {\n        this.typeFilter = new AnnotationTypeFilter(annotationType);\n        ...\n    }\n\n    /**\n     *  TypeFilter\n     */\n    TypeFilter getTypeFilter() {\n        return this.typeFilter;\n    }\n    ...\n}\n\n```\n\n `ServletComponentHandler` УһԱ `typeFilter`ڹ췽дעֵת `AnnotationTypeFilter`Ȼֵ `typeFilter` `getTypeFilter()` صľ `typeFilter`\n\n˽ `typeFilter` Դļʵࣺ\n\n```\n/**\n * WebFilterHandler 췽Ĳ WebFilter\n */\nclass WebFilterHandler extends ServletComponentHandler {\n    WebFilterHandler() {\n        super(WebFilter.class);\n    }\n    ...\n}\n\n/**\n * WebListenerHandler 췽Ĳ WebListener\n */\nclass WebListenerHandler extends ServletComponentHandler {\n    WebListenerHandler() {\n        super(WebListener.class);\n    }\n    ...\n}\n\n/**\n * WebServletHandler 췽Ĳ WebServlet\n */\nclass WebServletHandler extends ServletComponentHandler {\n    WebServletHandler() {\n        super(WebServlet.class);\n    }\n    ...\n}\n\n```\n\nɴ˾ˣ`createComponentProvider()` õ `ClassPathScanningCandidateComponentProvider` ֻ 3 עࣺ\n\n*   `@WebFilter`\n*   `@WebListener`\n*   `@WebServlet`\n\nǼɨ̣Ϊ `ServletComponentRegisteringPostProcessor#scanPackage`:\n\n```\nprivate void scanPackage(ClassPathScanningCandidateComponentProvider componentProvider, \n        String packageToScan) {\n    for (BeanDefinition candidate : componentProvider.findCandidateComponents(packageToScan)) {\n        if (candidate instanceof AnnotatedBeanDefinition) {\n            // õ BeanDefinition\n            for (ServletComponentHandler handler : HANDLERS) {\n                handler.handle(((AnnotatedBeanDefinition) candidate),\n                        (BeanDefinitionRegistry) this.applicationContext);\n            }\n        }\n    }\n}\n\n```\n\nھɨ̣`ClassPathScanningCandidateComponentProvider#findCandidateComponents` ͬ spring İɨ̻һ£Ͳչϸˣǰص `BeanDefinition` ĴϣҲ `ServletComponentHandler#handle` \n\n```\nvoid handle(AnnotatedBeanDefinition beanDefinition, BeanDefinitionRegistry registry) {\n    //  annotationType ǹ췽дע⣬@WebFilter@WebListener \n    Map<String, Object> attributes = beanDefinition.getMetadata()\n            .getAnnotationAttributes(this.annotationType.getName());\n    // ж϶ӦעǷڣ\n    if (attributes != null) {\n        doHandle(attributes, beanDefinition, registry);\n    }\n}\n\n```\n\nڴɨõ `BeanDefinition` ʱȱе `handler`(`WebServletHandler`/`WebFilterHandler`/`WebListenerHandler`)Ȼ `ServletComponentHandler#handle` д `ServletComponentHandler#handle` УֻǷڶӦעǷڣʹ `AnnotatedBeanDefinition#getMetadata` ȡӦעϢǷ `doHandler()` \n\nô `doHandler()` ʲôأǽ `WebServletHandler#doHandle`\n\n```\npublic void doHandle(Map<String, Object> attributes, AnnotatedBeanDefinition beanDefinition,\n        BeanDefinitionRegistry registry) {\n    // ע ServletRegistrationBean Ӧ BeanDefinition\n    BeanDefinitionBuilder builder = BeanDefinitionBuilder\n            .rootBeanDefinition(ServletRegistrationBean.class);\n    builder.addPropertyValue(\"asyncSupported\", attributes.get(\"asyncSupported\"));\n    builder.addPropertyValue(\"initParameters\", extractInitParameters(attributes));\n    builder.addPropertyValue(\"loadOnStartup\", attributes.get(\"loadOnStartup\"));\n    // ȡ servlet ƣָƣʹָƣûָʹbean\n    String name = determineName(attributes, beanDefinition);\n    builder.addPropertyValue(\"name\", name);\n    builder.addPropertyValue(\"servlet\", beanDefinition);\n    builder.addPropertyValue(\"urlMappings\", extractUrlPatterns(attributes));\n    builder.addPropertyValue(\"multipartConfig\", determineMultipartConfig(beanDefinition));\n    registry.registerBeanDefinition(name, builder.getBeanDefinition());\n}\n\n```\n\nԿҪǴ `Servlet` ã spring ע `ServletRegistrationBean` Ӧ `beanDefinition`\n\n `Handler`  `doHandle()` Ҳ࣬ spring ע `beanDefinition` ͬͲϸˡ\n\nܽ⼸ע spring ע `beanDefinition`\n\n*   `@WebServlet`: ע `ServletRegistrationBean` Ӧ `beanDefinition`\n*   `@WebFilter`: ע `FilterRegistrationBean` Ӧ `beanDefinition`\n*   `@WebListener`: ע `ServletListenerRegistrationBean` Ӧ `beanDefinition`\n\nʹ `XxxRegistrationBean` עʱֶ `XxxRegistrationBean`Ȼͨ `@Bean` עעᵽ spring Уʹ `@WebServlet`/`@WebFilter`/`@WebListener` һȦҲǻص `XxxRegistrationBean`\n\n#### 2.2 `XxxRegistrationBean` ע\n\nʹ `XxxRegistrationBean` עᣬʹ `@ServletComponentScan` ɨעᣬնõ `XxxRegistrationBean` Ӧ beanǾ̽Щ bean עᵽ servlet еġ\n\nӴϿ`ServletRegistrationBean``FilterRegistrationBean`  `ServletListenerRegistrationBean`  `ServletContextInitializer` ӿڵʵ࣬`ServletRegistrationBean` ļ̳нṹ£\n\n![ͼƬ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-0397d56cfb328683ae349d0822db2320231.png)\n\n`ServletContextInitializer` ֻһ `onStartup(...)`\n\n```\n@FunctionalInterface\npublic interface ServletContextInitializer {\n\n    /**\n     *  servletContextǽ Servletfilterlistener ע\n     */\n    void onStartup(ServletContext servletContext) throws ServletException;\n\n}\n\n```\n\nڽ `servlet` ע᷽ʽʱᵽͨʵ `ServletContextInitializer`д `onStartup()` ʵ `servlet` עᣬ `XxxRegistrationBean` ĵײʵҲôġ\n\n `ServletRegistrationBean` `onStartup(...)` ɶ\n\n`ServletRegistrationBean` ûд `onStartup(...)` ֱӼ̳ `RegistrationBean`:\n\n```\npublic final void onStartup(ServletContext servletContext) throws ServletException {\n    // ȡϢ \n    String description = getDescription();\n    // ǷעᣬĬΪtrue\n    if (!isEnabled()) {\n        logger.info(StringUtils.capitalize(description) + \" was not registered (disabled)\");\n        return;\n    }\n    // ע\n    register(description, servletContext);\n}\n\n```\n\nӣȻȡһϢȻжǷעᣬžǽעˡֱӲ鿴ע `DynamicRegistrationBean#register`:\n\n```\nprotected final void register(String description, ServletContext servletContext) {\n    D registration = addRegistration(description, servletContext);\n    if (registration == null) {\n        logger.info(...);\n        return;\n    }\n    // \n    configure(registration);\n}\n\n```\n\nҪ£ע `servlet` 봦ãע `ServletRegistrationBean#addRegistration` :\n\n```\nprotected ServletRegistration.Dynamic addRegistration(String description, \n        ServletContext servletContext) {\n    String name = getServletName();\n    // ע\n    return servletContext.addServlet(name, this.servlet);\n}\n\n```\n\nעǱȽϼ򵥵ģֱӵ `ServletContext#addServlet` С\n\n鿴ô `ServletRegistrationBean#configure` \n\n```\nprotected void configure(ServletRegistration.Dynamic registration) {\n    // ø\n    super.configure(registration);\n    // urlMapping\n    String[] urlMapping = StringUtils.toStringArray(this.urlMappings);\n    if (urlMapping.length == 0 && this.alwaysMapUrl) {\n        urlMapping = DEFAULT_MAPPINGS;\n    }\n    if (!ObjectUtils.isEmpty(urlMapping)) {\n        registration.addMapping(urlMapping);\n    }\n    // loadOnStartup\n    registration.setLoadOnStartup(this.loadOnStartup);\n    // һЩ\n    if (this.multipartConfig != null) {\n        registration.setMultipartConfig(this.multipartConfig);\n    }\n}\n\n```\n\nǵ˸ķȻôˣҪǴ `urlMapping`  `loadOnStartup`Ͳˡ\n\n `super.configure(...)` ɶ `DynamicRegistrationBean#configure`:\n\n```\n/**\n * ҲǴһЩ\n */\nprotected void configure(D registration) {\n    registration.setAsyncSupported(this.asyncSupported);\n    // óʼ\n    if (!this.initParameters.isEmpty()) {\n        registration.setInitParameters(this.initParameters);\n    }\n}\n\n```\n\nҪ˳ʼá\n\nķ`ServletRegistrationBean`  `onStartup(...)` Ҫ\n\n1.   `servlet` ӵ\n2.   `servlet` \n\n`FilterRegistrationBean`  `ServletListenerRegistrationBean` עƣͲ˵ˡ\n\n#### 2.3 `ServletContextInitializer#onStartup` ִ\n\nעѾˣ `ServletContextInitializer#onStartup` ִеġעһ̱Ƚϸӣ漰 tomcat ̣ⲿֻעص룬һ̡\n\n tomcat Ϊһϵеĵ׷٣ `TomcatStarter` еģ£\n\n```\nclass TomcatStarter implements ServletContainerInitializer {\n\n    private final ServletContextInitializer[] initializers;\n\n    TomcatStarter(ServletContextInitializer[] initializers) {\n        this.initializers = initializers;\n    }\n\n    @Override\n    public void onStartup(Set<Class<?>> classes, ServletContext servletContext) \n            throws ServletException {\n        try {\n            for (ServletContextInitializer initializer : this.initializers) {\n                // ִ ServletContextInitializer#onStartup\n                initializer.onStartup(servletContext);\n            }\n        }\n        catch (Exception ex) {\n            this.startUpException = ex;\n            ...\n        }\n    }\n\n    ...\n\n}\n\n```\n\n`TomcatStarter`  springboot ṩ࣬ʵ `ServletContainerInitializer` `ServletContextInitializer``ServletContainerInitializer`  tomcat ṩģ tomcat ʱִ `ServletContainerInitializer#onStartup` `servlt 3.0` 淶\n\nô `TomcatStarter` ӵ tomcat еأȻ `servlt 3.0` 淶ͨ `spi` ɨ赽 `ServletContainerInitializer` ʵ֣ԲģΪ tomcat ͨ `spi` ɨõ `TomcatStarter` ʵĳԱ `initializers` ޷ֵˣӵ tomcat ǰ`TomcatStarter` Ҫʵ `initializers` Ҫֵ\n\nεԣ `TomcatStarter`  `TomcatServletWebServerFactory#configureContext` ӵ tomcat ģؼ:\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-6625407ce4d1dad8de736db00db124a2a99.png)\n\nԿ`initializers` 뵽 `TomcatStarter` Ĺ췽Уõ `TomcatStarter` ʵֶӵ tomcat ˡ\n\nô `initializers` ȡأʵϣǵ `XxxRegistrationBean` Ҫ spring УҪȡĻֻҪ `beanFactory.getBeansOfType(...)` Ϳˣ`ServletContextInitializerBeans#addServletContextInitializerBean(String, ServletContextInitializer, ListableBeanFactory)` Ǹµģ\n\n```\nprivate void addServletContextInitializerBeans(ListableBeanFactory beanFactory) {\n    for (Class<? extends ServletContextInitializer> initializerType : this.initializerTypes) {\n        // ȡ ServletContextInitializer: getOrderedBeansOfType(beanFactory, initializerType)\n        for (Entry<String, ? extends ServletContextInitializer> initializerBean \n                : getOrderedBeansOfType(beanFactory, initializerType)) {\n            addServletContextInitializerBean(initializerBean.getKey(), \n                initializerBean.getValue(), beanFactory);\n        }\n    }\n}\n\n/**\n * Ӳ\n */\nprivate void addServletContextInitializerBean(String beanName, \n        ServletContextInitializer initializer, ListableBeanFactory beanFactory) {\n    //  ServletRegistrationBean\n    if (initializer instanceof ServletRegistrationBean) {\n        Servlet source = ((ServletRegistrationBean<?>) initializer).getServlet();\n        addServletContextInitializerBean(Servlet.class, beanName, initializer, \n                beanFactory, source);\n    }\n    //  FilterRegistrationBean\n    else if (initializer instanceof FilterRegistrationBean) {\n        Filter source = ((FilterRegistrationBean<?>) initializer).getFilter();\n        addServletContextInitializerBean(Filter.class, beanName, initializer, \n                beanFactory, source);\n    }\n    //  DelegatingFilterProxyRegistrationBean\n    else if (initializer instanceof DelegatingFilterProxyRegistrationBean) {\n        String source = ((DelegatingFilterProxyRegistrationBean) initializer).getTargetBeanName();\n        addServletContextInitializerBean(Filter.class, beanName, initializer, beanFactory, source);\n    }\n    //  ServletListenerRegistrationBean\n    else if (initializer instanceof ServletListenerRegistrationBean) {\n        EventListener source = ((ServletListenerRegistrationBean<?>) initializer).getListener();\n        addServletContextInitializerBean(EventListener.class, beanName, initializer, \n                beanFactory, source);\n    }\n    else {\n        //  ServletContextInitializer Bean\n        addServletContextInitializerBean(ServletContextInitializer.class, beanName, \n                initializer, beanFactory, initializer);\n    }\n}\n\n```\n\n### 3\\. ܽ\n\nķ springboot ע servlet ̣\n\n1.   `Servlet` Ϊ 3 ע᷽ʽʹ `XxxRegistrationBean` עᡢʹ `servlet` ע (`@WebServlet`/`@WebFilter`/`@WebListener`) עᣬԼʵ `ServletContextInitializer` ӿֶע᣻\n2.   `@ServletComponentScan` עɨ\n3.   `ServletRegistrationBean` Ϊ˽ `ServletRegistrationBean` עᵽ servlet \n4.   `ServletContainerInitializer#onStartup` ִ\n\n* * *\n\n_ԭӣ[https://my.oschina.net/funcy/blog/4951050](https://my.oschina.net/funcy/blog/4951050) ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע_"
  },
  {
    "path": "docs/Spring全家桶/SpringBoot源码解析/SpringBootWeb应用（二）：WebMvc装配过程.md",
    "content": "ƽʱУspringboot е web ĿĽ springboot Զ springMvc Ŀ̡\n\n### 1\\. springMvc Զװ\n\nspringMvc ԶװΪ\n\n```\n@Configuration(proxyBeanMethods = false)\n// װ\n@ConditionalOnWebApplication(type = Type.SERVLET)\n@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })\n// ûԶWebMvc࣬ʹñ\n@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)\n// װ˳\n@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)\n@AutoConfigureAfter({ \n        // DispatcherServlet Զװ\n        DispatcherServletAutoConfiguration.class, \n        // ̳߳صԶװ\n        TaskExecutionAutoConfiguration.class,\n        // jsr 303 ֤ܵԶװ\n        ValidationAutoConfiguration.class })\npublic class WebMvcAutoConfiguration {\n    ...\n}\n\n```\n\n `@AutoConfigureAfter` עУ`WebMvcAutoConfiguration` Ҫ `DispatcherServletAutoConfiguration``TaskExecutionAutoConfiguration``ValidationAutoConfiguration` װ֮װ䣬Що£\n\n*   `DispatcherServletAutoConfiguration``DispatcherServlet` Զװ\n*   `TaskExecutionAutoConfiguration`ִʵǴһ̳߳\n*   `ValidationAutoConfiguration`jsr 303 ֤Զװ䣬֤ `@NotNull``@NotEmpty` ע֤\n\n 3 У springMvc йصֻ `DispatcherServletAutoConfiguration`ʶһ\n\n```\n@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)\n@Configuration(proxyBeanMethods = false)\n@ConditionalOnWebApplication(type = Type.SERVLET)\n@ConditionalOnClass(DispatcherServlet.class)\n// Ҫ ServletWebServerFactoryAutoConfiguration Զװɺ\n@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)\npublic class DispatcherServletAutoConfiguration {\n    ...\n}\n\n```\n\n`DispatcherServletAutoConfiguration` Ҫ `ServletWebServerFactoryAutoConfiguration` ԶװɲŽװ䣬ʲôأ͸£Ǵ servlet `tomcat`, `jetty`, `undertow` ȣɵģ `ServletWebServerFactoryAutoConfiguration`\n\n```\n@Configuration(proxyBeanMethods = false)\n@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)\n@ConditionalOnClass(ServletRequest.class)\n@ConditionalOnWebApplication(type = Type.SERVLET)\n@EnableConfigurationProperties(ServerProperties.class)\n// һЩ\n@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,\n        // 3 web \n        ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,\n        ServletWebServerFactoryConfiguration.EmbeddedJetty.class,\n        ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })\npublic class ServletWebServerFactoryAutoConfiguration {\n    ...\n}\n\n```\n\nԿû `@AutoConfigureAfter` עˣ springMvc Զװ࣬ǵķʹ࿪ʼ\n\nܽϼװ˳\n\n1.  `ServletWebServerFactoryAutoConfiguration`\n2.  `DispatcherServletAutoConfiguration`\n3.  `WebMvcAutoConfiguration`\n\nǵķҲ˳һЩԶװࡣ\n\n### 2. `ServletWebServerFactoryAutoConfiguration` Զװ\n\n`ServletWebServerFactoryAutoConfiguration` £\n\n```\n@Configuration(proxyBeanMethods = false)\n@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)\n@ConditionalOnClass(ServletRequest.class)\n@ConditionalOnWebApplication(type = Type.SERVLET)\n@EnableConfigurationProperties(ServerProperties.class)\n// һЩ\n@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,\n        // 3 web \n        ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,\n        ServletWebServerFactoryConfiguration.EmbeddedJetty.class,\n        ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })\npublic class ServletWebServerFactoryAutoConfiguration {\n    ...\n}\n\n```\n\n `BeanPostProcessorsRegistrar``EmbeddedTomcat``EmbeddedJetty``EmbeddedUndertow`һɣ\n\n#### 2.1 `BeanPostProcessorsRegistrar`\n\n`BeanPostProcessorsRegistrar`  `ServletWebServerFactoryAutoConfiguration` ڲ࣬£\n\n```\npublic static class BeanPostProcessorsRegistrar \n        implements ImportBeanDefinitionRegistrar, BeanFactoryAware {\n\n    ...\n\n    /**\n     *  ImportBeanDefinitionRegistrar ķ\n     */\n    @Override\n    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,\n            BeanDefinitionRegistry registry) {\n        if (this.beanFactory == null) {\n            return;\n        }\n        // ע\n        registerSyntheticBeanIfMissing(registry, \"webServerFactoryCustomizerBeanPostProcessor\",\n                WebServerFactoryCustomizerBeanPostProcessor.class);\n        registerSyntheticBeanIfMissing(registry, \"errorPageRegistrarBeanPostProcessor\",\n                ErrorPageRegistrarBeanPostProcessor.class);\n    }\n\n    /**\n     * ע\n     */\n    private void registerSyntheticBeanIfMissing(BeanDefinitionRegistry registry, \n            String name, Class<?> beanClass) {\n        if (ObjectUtils.isEmpty(this.beanFactory.getBeanNamesForType(beanClass, true, false))) {\n            RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass);\n            beanDefinition.setSynthetic(true);\n            registry.registerBeanDefinition(name, beanDefinition);\n        }\n    }\n}\n\n```\n\nҪ spring עࣺ`WebServerFactoryCustomizerBeanPostProcessor``ErrorPageRegistrarBeanPostProcessor`ǷֱǸɶ\n\n##### 1. `WebServerFactoryCustomizerBeanPostProcessor`\n\n`WebServerFactoryCustomizerBeanPostProcessor` Ĵ£\n\n```\npublic class WebServerFactoryCustomizerBeanPostProcessor \n        implements BeanPostProcessor, BeanFactoryAware {\n\n    ...\n\n    @Override\n    public Object postProcessBeforeInitialization(Object bean, String beanName) \n            throws BeansException {\n        // bean  WebServerFactory Ŵ\n        if (bean instanceof WebServerFactory) {\n            postProcessBeforeInitialization((WebServerFactory) bean);\n        }\n        return bean;\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {\n        LambdaSafe.callbacks(WebServerFactoryCustomizer.class, getCustomizers(), webServerFactory)\n                .withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)\n                // \n                .invoke((customizer) -> customizer.customize(webServerFactory));\n    }\n\n    /**\n     * ȡ WebServerFactoryCustomizerõһɱ List\n     */\n    private Collection<WebServerFactoryCustomizer<?>> getCustomizers() {\n        if (this.customizers == null) {\n            this.customizers = new ArrayList<>(getWebServerFactoryCustomizerBeans());\n            this.customizers.sort(AnnotationAwareOrderComparator.INSTANCE);\n            this.customizers = Collections.unmodifiableList(this.customizers);\n        }\n        return this.customizers;\n    }\n\n    /**\n     * ȡ beanFactory е WebServerFactoryCustomizer\n     */\n    private Collection<WebServerFactoryCustomizer<?>> getWebServerFactoryCustomizerBeans() {\n        return (Collection) this.beanFactory.getBeansOfType(\n                WebServerFactoryCustomizer.class, false, false).values();\n    }\n\n}\n\n```\n\n `WebServerFactory` Զõģʵ `BeanPostProcessor` Ҫע `postProcessBeforeInitialization(...)` \n\n `WebServerFactoryCustomizerBeanPostProcessor`  `postProcessBeforeInitialization(...)` Уǰ bean  `WebServerFactory`Ȼȡ `beanFactory` Ϊ `WebServerFactoryCustomizer`  beanȻЩ `WebServerFactoryCustomizer` õ `WebServerFactory`bean С\n\nҪԶ `Tomcat` ã\n\n```\n@Component\npublic class MyCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {\n\n    @Override\n    public void customize(TomcatServletWebServerFactory factory) {\n        factory.setPort(8091);\n        factory.setContextPath(\"/\");\n        // \n        ...\n    }\n\n}\n\n```\n\n `ServletWebServerFactoryAutoConfiguration` ṩ `WebServerFactoryCustomizer`:\n\n```\npublic class ServletWebServerFactoryAutoConfiguration {\n\n    @Bean\n    public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(\n            ServerProperties serverProperties) {\n        return new ServletWebServerFactoryCustomizer(serverProperties);\n    }\n\n    @Bean\n    @ConditionalOnClass(name = \"org.apache.catalina.startup.Tomcat\")\n    public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(\n            ServerProperties serverProperties) {\n        return new TomcatServletWebServerFactoryCustomizer(serverProperties);\n    }\n\n    ...\n\n}\n\n```\n\nӷö `ServerProperties`\n\n```\n@ConfigurationProperties(prefix = \"server\", ignoreUnknownFields = true)\npublic class ServerProperties {\n    ...\n}\n\n```\n\n `ConfigurationProperties` ע⣬`prefix` Ϊ \"server\"ö `server` ͷֵ֧£\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-586d95875e2eeefd295643c3e6acf0091e1.png)\n\n磬ҪĶ˿ڣֻ `application.properties` üɣ\n\n```\nserver.port=8080\n\n```\n\nãͲˡ\n\n##### 2. `ErrorPageRegistrarBeanPostProcessor`\n\n`ErrorPageRegistrarBeanPostProcessor` Ĵ£\n\n```\npublic class ErrorPageRegistrarBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware {\n\n    /**\n     * BeanPostProcessorķbeanʼǰִ\n     */\n    @Override\n    public Object postProcessBeforeInitialization(Object bean, String beanName) \n            throws BeansException {\n        if (bean instanceof ErrorPageRegistry) {\n            //  bean  ErrorPageRegistry\n            postProcessBeforeInitialization((ErrorPageRegistry) bean);\n        }\n        return bean;\n    }\n\n    private void postProcessBeforeInitialization(ErrorPageRegistry registry) {\n        for (ErrorPageRegistrar registrar : getRegistrars()) {\n            // עҳ\n            registrar.registerErrorPages(registry);\n        }\n    }\n\n    private Collection<ErrorPageRegistrar> getRegistrars() {\n        if (this.registrars == null) {\n            // ȡеĴҳ\n            this.registrars = new ArrayList<>(this.beanFactory.getBeansOfType(\n                    ErrorPageRegistrar.class, false, false).values());\n            this.registrars.sort(AnnotationAwareOrderComparator.INSTANCE);\n            this.registrars = Collections.unmodifiableList(this.registrars);\n        }\n        return this.registrars;\n    }\n\n    ...\n}\n\n```\n\nҪע `postProcessBeforeInitialization` ̣\n\n1.   bean  `ErrorPageRegistry`е 2 \n2.  ȡ beanFactory  `ErrorPageRegistrar` ͵ beanе 3 \n3.   `registrar.registerErrorPages(registry)` дҳ\n\nҪ˵\n\n*   `ErrorPageRegistrar`ҳע `ErrorPageRegistry` ɻࣩ\n*   `ErrorPageRegistry`עࣨʵʸɻࣩ\n\nҪԶҳʵ `ErrorPageRegistry` ӿڣ\n\n```\n@Component\npublic class MyErrorPage implements ErrorPageRegistrar {\n\n    /**\n     * עҳ\n     */\n    @Override\n    public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {\n        // յõ ErrorPageRegistry#addErrorPages ע\n        errorPageRegistry.addErrorPages(new ErrorPage(\"/error/page\"));\n    }\n}\n\n```\n\n#### 2.2 `EmbeddedTomcat`\n\n `ServletWebServerFactoryConfiguration.EmbeddedTomcat` ࣺ\n\n```\n@Configuration(proxyBeanMethods = false)\nclass ServletWebServerFactoryConfiguration {\n\n    @Configuration(proxyBeanMethods = false)\n    // ע⣬ʱ\n    @ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })\n    // ע ServletWebServerFactoryǿʵ tomcat װ\n    @ConditionalOnMissingBean(value = ServletWebServerFactory.class, \n        search = SearchStrategy.CURRENT)\n    public static class EmbeddedTomcat {\n\n        @Bean\n        public TomcatServletWebServerFactory tomcatServletWebServerFactory(\n                ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,\n                ObjectProvider<TomcatContextCustomizer> contextCustomizers,\n                ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {\n            TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();\n            // һЩ\n            factory.getTomcatConnectorCustomizers()\n                    .addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));\n            factory.getTomcatContextCustomizers()\n                    .addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));\n            factory.getTomcatProtocolHandlerCustomizers()\n                    .addAll(protocolHandlerCustomizers.orderedStream()\n                    .collect(Collectors.toList()));\n            return factory;\n        }\n\n    }\n\n    ...\n}\n\n```\n\nҪǷ `TomcatServletWebServerFactory` beanעһЩ `connectorCustomizers``contextCustomizers``protocolHandlerCustomizers` ȲԶãЩǴ `BeanPostProcessorsRegistrar` ġ\n\nиطҪһ£ʹ springboot ṩ `TomcatServletWebServerFactory`ǿԼʵ `TomcatServletWebServerFactory`\n\n```\n@Bean\npublic ServletWebServerFactory servletWebServerFactory() {\n    TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();\n    // Զĸ\n    ...\n    return tomcat;\n}\n\n```\n\n֮springboot ṩ `tomcatServletWebServerFactory` Ͳᴦˡ\n\n `EmbeddedJetty``EmbeddedUndertow` `EmbeddedTomcat` ĴƣͲ˵ˡ\n\n### 3. `DispatcherServletAutoConfiguration`\n\n `DispatcherServletAutoConfiguration`ؼ£\n\n```\npublic class DispatcherServletAutoConfiguration {\n\n    public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = \"dispatcherServlet\";\n\n    public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME \n            = \"dispatcherServletRegistration\";\n\n    @Configuration(proxyBeanMethods = false)\n    @Conditional(DefaultDispatcherServletCondition.class)\n    @ConditionalOnClass(ServletRegistration.class)\n    @EnableConfigurationProperties({ HttpProperties.class, WebMvcProperties.class })\n    protected static class DispatcherServletConfiguration {\n\n        /**\n         *  DispatcherServlet.\n         * @param httpProperties http.\n         * @param webMvcProperties webMvc .\n         * @return ض\n         */\n        @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)\n        public DispatcherServlet dispatcherServlet(HttpProperties httpProperties, \n                WebMvcProperties webMvcProperties) {\n            DispatcherServlet dispatcherServlet = new DispatcherServlet();\n            dispatcherServlet.setDispatchOptionsRequest(\n                    webMvcProperties.isDispatchOptionsRequest());\n            dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());\n            dispatcherServlet.setThrowExceptionIfNoHandlerFound(\n                    webMvcProperties.isThrowExceptionIfNoHandlerFound());\n            dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());\n            dispatcherServlet.setEnableLoggingRequestDetails(httpProperties.isLogRequestDetails());\n            return dispatcherServlet;\n        }\n\n        /**\n         * ļϴ.\n         * @param resolver .\n         * @return ֵ.\n         */\n        @Bean\n        @ConditionalOnBean(MultipartResolver.class)\n        @ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)\n        public MultipartResolver multipartResolver(MultipartResolver resolver) {\n            return resolver;\n        }\n\n    }\n\n    /**\n     *  DispatcherServletRegistrationBean\n     * ὫdispatcherServletעᵽservlet\n     */\n    @Configuration(proxyBeanMethods = false)\n    @Conditional(DispatcherServletRegistrationCondition.class)\n    @ConditionalOnClass(ServletRegistration.class)\n    @EnableConfigurationProperties(WebMvcProperties.class)\n    @Import(DispatcherServletConfiguration.class)\n    protected static class DispatcherServletRegistrationConfiguration {\n\n        @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)\n        @ConditionalOnBean(value = DispatcherServlet.class, \n                name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)\n        public DispatcherServletRegistrationBean dispatcherServletRegistration(\n                DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties, \n                ObjectProvider<MultipartConfigElement> multipartConfig) {\n            //  DispatcherServletRegistrationBeanὫdispatcherServletעᵽservlet\n            DispatcherServletRegistrationBean registration = \n                    new DispatcherServletRegistrationBean(dispatcherServlet, \n                    webMvcProperties.getServlet().getPath());\n            registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);\n            registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());\n            multipartConfig.ifAvailable(registration::setMultipartConfig);\n            return registration;\n        }\n\n    }\n\n    ...\n\n}\n\n```\n\nҪע 3  bean\n\n*   `dispatcherServlet`springMvc ڣurl ɴ˽룬Ȼת `requestMapping`\n*   `multipartResolver`ļϴ\n*   `dispatcherServletRegistration` `dispatcherServlet` עᣬὫ `dispatcherServlet` עᵽ servlet У springboot ע servlet ݣԲο [springboot web Ӧ֮ servlet ע](https://my.oschina.net/funcy/blog/4951050)\n\n### 4. `WebMvcAutoConfiguration`\n\n `WebMvcAutoConfiguration`:\n\n```\n@Configuration(proxyBeanMethods = false)\n...\n// ûԶWebMvc࣬ʹñ\n@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)\n...\npublic class WebMvcAutoConfiguration {\n    ...\n}\n\n```\n\n`WebMvcAutoConfiguration` иעҪע£\n\n```\n@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)\n\n```\n\nд `WebMvcAutoConfiguration` ֻ `WebMvcConfigurationSupport` ͵ bean ʱŻԶװ䡣\n\n`WebMvcConfigurationSupport` Ǹɶأ [springmvc demo  @EnableWebMvc ע](https://my.oschina.net/funcy/blog/4696657)һУᵽ springMvc ַʽ\n\n*   ʹ `@EnableWebMvc` ע\n*   ̳ `WebMvcConfigurationSupport` \n\nַն spring Ϊ `WebMvcConfigurationSupport`  bean `springboot` ĿУǽֲ֮һô `WebMvcConfigurationSupport``WebMvcAutoConfiguration` ԶװͲִˡ\n\n `WebMvcAutoConfiguration` װ bean\n\n#### 4.1 `WebMvcAutoConfigurationAdapter`\n\n`WebMvcAutoConfigurationAdapter`  `WebMvcAutoConfiguration` ڲ࣬£\n\n```\n@Configuration(proxyBeanMethods = false)\n//  EnableWebMvcConfiguration\n@Import(EnableWebMvcConfiguration.class)\n@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })\n@Order(0)\npublic static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {\n    ...\n}\n\n```\n\nʵ `WebMvcConfigurer` `EnableWebMvcConfiguration``WebMvcConfigurer`  springMvc ãֻҪджӦķɣ`EnableWebMvcConfiguration`   webMvc áҲ `WebMvcAutoConfiguration` ڲ࣬ɶ\n\n```\n@Configuration(proxyBeanMethods = false)\npublic static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration \n        implements ResourceLoaderAware {\n    ...\n}\n\n```\n\nԿ `DelegatingWebMvcConfiguration` ࣬ `DelegatingWebMvcConfiguration` ǸɶأҲĶ壺\n\n```\n@Configuration(proxyBeanMethods = false)\npublic class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {\n    ...\n}\n\n```\n\n`DelegatingWebMvcConfiguration`  springMvc ṩģԿʵ `WebMvcConfigurationSupport`ˣspringboot ͨ `@Import(EnableWebMvcConfiguration.class)` ķʽ spring  `WebMvcConfigurationSupport` ͵ bean\n\nʵϣ`DelegatingWebMvcConfiguration`  `@EnableWebMvc`  bean\n\n```\n@Retention(RetentionPolicy.RUNTIME)\n@Target(ElementType.TYPE)\n@Documented\n//  DelegatingWebMvcConfiguration\n@Import(DelegatingWebMvcConfiguration.class)\npublic @interface EnableWebMvc {\n    ...\n}\n\n```\n\nspringbot ֱ `DelegatingWebMvcConfiguration`  `EnableWebMvcConfiguration`ȻһЩԶõġ\n\n `EnableWebMvcConfiguration` Щɶһٷ `WebMvcAutoConfigurationAdapter`  bean\n\n```\n/**\n * http Ϣת.\n */\n@Override\npublic void configureMessageConverters(List<HttpMessageConverter<?>> converters) {\n    this.messageConvertersProvider.ifAvailable(\n            (customConverters) -> converters.addAll(customConverters.getConverters()));\n}\n\n/**\n * ͼ.\n */\n@Bean\n@ConditionalOnMissingBean\npublic InternalResourceViewResolver defaultViewResolver() {\n    InternalResourceViewResolver resolver = new InternalResourceViewResolver();\n    resolver.setPrefix(this.mvcProperties.getView().getPrefix());\n    resolver.setSuffix(this.mvcProperties.getView().getSuffix());\n    return resolver;\n}\n\n@Bean\n@ConditionalOnBean(View.class)\n@ConditionalOnMissingBean\npublic BeanNameViewResolver beanNameViewResolver() {\n    BeanNameViewResolver resolver = new BeanNameViewResolver();\n    resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);\n    return resolver;\n}\n\n@Bean\n@ConditionalOnBean(ViewResolver.class)\n@ConditionalOnMissingBean(name = \"viewResolver\", value = ContentNegotiatingViewResolver.class)\npublic ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {\n    ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();\n    resolver.setContentNegotiationManager(beanFactory.getBean(ContentNegotiationManager.class));\n    resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);\n    return resolver;\n}\n\n/**\n * ʻ.\n */\n@Bean\n@ConditionalOnMissingBean\n@ConditionalOnProperty(prefix = \"spring.mvc\", name = \"locale\")\npublic LocaleResolver localeResolver() {\n    if (this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {\n        return new FixedLocaleResolver(this.mvcProperties.getLocale());\n    }\n    AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();\n    localeResolver.setDefaultLocale(this.mvcProperties.getLocale());\n    return localeResolver;\n}\n\n/**\n * ̬Դӳ\n */\n@Override\npublic void addResourceHandlers(ResourceHandlerRegistry registry) {\n    if (!this.resourceProperties.isAddMappings()) {\n        logger.debug(\"Default resource handling disabled\");\n        return;\n    }\n    // ӳwebjars\n    Duration cachePeriod = this.resourceProperties.getCache().getPeriod();\n    CacheControl cacheControl = this.resourceProperties.getCache()\n            .getCachecontrol().toHttpCacheControl();\n    if (!registry.hasMappingForPattern(\"/webjars/**\")) {\n        customizeResourceHandlerRegistration(registry.addResourceHandler(\"/webjars/**\")\n                .addResourceLocations(\"classpath:/META-INF/resources/webjars/\")\n                .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));\n    }\n    // ӳ侲̬Դ·\n    String staticPathPattern = this.mvcProperties.getStaticPathPattern();\n    if (!registry.hasMappingForPattern(staticPathPattern)) {\n        customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)\n                // staticLocations ĬΪ classpath:[/META-INF/resources/,\n                // /resources/, /static/, /public/]\n                .addResourceLocations(getResourceLocations(\n                    this.resourceProperties.getStaticLocations()))\n                .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));\n    }\n}\n\n```\n\nԿ`WebMvcAutoConfigurationAdapter`  bean:\n\n1.  http Ϣת\n2.  ͼ\n3.  ʻ\n\nص `httpϢת`ʹ springMvc ʱͨ `Controller` еķϱ `@ResponseBody`زͻתΪ json `httpϢת`Ĺˡspringboot Ĭϵ jaon  `jackson`ʵ `JacksonAutoConfiguration` УװΪ `HttpMessageConverter` Ĵ `JacksonHttpMessageConvertersConfiguration`Ͳչˡ\n\n ķУγ `WebMvcProperties` ãǸɶ\n\n```\n@ConfigurationProperties(prefix = \"spring.mvc\")\npublic class WebMvcProperties {\n    ...\n}\n\n```\n\nԣǸ Կǰ׺Ϊ `spring.mvc`ֵ֧£\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-062f490afbb74e496f50f4cad4bd13ede82.png)\n\n#### 4.2 `EnableWebMvcConfiguration`\n\n `EnableWebMvcConfiguration` Զã\n\n```\npublic static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration \n        implements ResourceLoaderAware {\n\n    /**\n     * \n     */\n    @Bean\n    @Override\n    public RequestMappingHandlerAdapter requestMappingHandlerAdapter(\n            @Qualifier(\"mvcContentNegotiationManager\") \n                ContentNegotiationManager contentNegotiationManager,\n            @Qualifier(\"mvcConversionService\") FormattingConversionService conversionService,\n            @Qualifier(\"mvcValidator\") Validator validator) {\n        RequestMappingHandlerAdapter adapter = super.requestMappingHandlerAdapter(\n                contentNegotiationManager, conversionService, validator);\n        adapter.setIgnoreDefaultModelOnRedirect(this.mvcProperties == null \n                || this.mvcProperties.isIgnoreDefaultModelOnRedirect());\n        return adapter;\n    }\n\n    /**\n     * ·ӳ\n     */\n    @Bean\n    @Primary\n    @Override\n    public RequestMappingHandlerMapping requestMappingHandlerMapping(\n            @Qualifier(\"mvcContentNegotiationManager\") \n                    ContentNegotiationManager contentNegotiationManager,\n            @Qualifier(\"mvcConversionService\") FormattingConversionService conversionService,\n            @Qualifier(\"mvcResourceUrlProvider\") ResourceUrlProvider resourceUrlProvider) {\n        // Must be @Primary for MvcUriComponentsBuilder to work\n        return super.requestMappingHandlerMapping(contentNegotiationManager, conversionService,\n                resourceUrlProvider);\n    }\n\n    /**\n     * ӭҳ\n     */\n    @Bean\n    public WelcomePageHandlerMapping welcomePageHandlerMapping(\n            ApplicationContext applicationContext, \n            FormattingConversionService mvcConversionService, \n            ResourceUrlProvider mvcResourceUrlProvider) {\n        WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(\n                new TemplateAvailabilityProviders(applicationContext), applicationContext, \n                getWelcomePage(), this.mvcProperties.getStaticPathPattern());\n        welcomePageHandlerMapping.setInterceptors(getInterceptors(\n                mvcConversionService, mvcResourceUrlProvider));\n        return welcomePageHandlerMapping;\n    }\n\n    /**\n     * mvcãڸʽĴ\n     */\n    @Bean\n    @Override\n    public FormattingConversionService mvcConversionService() {\n        WebConversionService conversionService \n            = new WebConversionService(this.mvcProperties.getDateFormat());\n        addFormatters(conversionService);\n        return conversionService;\n    }\n\n    /**\n     * У\n     */\n    @Bean\n    @Override\n    public Validator mvcValidator() {\n        if (!ClassUtils.isPresent(\"javax.validation.Validator\", getClass().getClassLoader())) {\n            return super.mvcValidator();\n        }\n        return ValidatorAdapter.get(getApplicationContext(), getValidator());\n    }\n\n    /**\n     * 쳣\n     */\n    @Override\n    protected ExceptionHandlerExceptionResolver createExceptionHandlerExceptionResolver() {\n        if (this.mvcRegistrations != null \n                && this.mvcRegistrations.getExceptionHandlerExceptionResolver() != null) {\n            return this.mvcRegistrations.getExceptionHandlerExceptionResolver();\n        }\n        return super.createExceptionHandlerExceptionResolver();\n    }\n\n    @Override\n    protected void extendHandlerExceptionResolvers(\n                List<HandlerExceptionResolver> exceptionResolvers) {\n        super.extendHandlerExceptionResolvers(exceptionResolvers);\n        if (this.mvcProperties.isLogResolvedException()) {\n            for (HandlerExceptionResolver resolver : exceptionResolvers) {\n                if (resolver instanceof AbstractHandlerExceptionResolver) {\n                    ((AbstractHandlerExceptionResolver) resolver).setWarnLogCategory(\n                            resolver.getClass().getName());\n                }\n            }\n        }\n    }\n    ...\n}\n\n```\n\nԭ springMvc·ӳ䡢У顢쳣ȡ\n\n### 5\\. ܽ\n\n1.  `webMvc` װʱװ `ServletWebServerFactoryAutoConfiguration`װ `DispatcherServletAutoConfiguration`װ `WebMvcAutoConfiguration`\n2.  `ServletWebServerFactoryAutoConfiguration`  servlet װ䣬`DispatcherServletAutoConfiguration`  `DispatcherServlet` װ䣬`WebMvcAutoConfiguration`  `webMvc` Ϣתͼ̬Դӳȣװ\n3.   spring  `WebMvcConfigurationSupport` `WebMvcAutoConfiguration` װִ\n4.  `servlet`  `servlet` Ϊǰ׺`webMvc`  `spring.mvc` Ϊǰ׺ǿļһΪ `application.properties/application.yml`н\n\n* * *\n\n_ԭӣ[https://my.oschina.net/funcy/blog/4921595](https://my.oschina.net/funcy/blog/4921595) ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע_"
  },
  {
    "path": "docs/Spring全家桶/SpringBoot源码解析/SpringBoot启动流程（一）：准备SpringApplication.md",
    "content": "̽ springboot ģʹõ demo λ?[gitee/funcy](https://gitee.com/funcy/spring-boot/tree/v2.2.2.RELEASE_learn/spring-boot-project/spring-boot-learn/src/main/java/org/springframework/boot/learn/autoconfigure/demo01).\n\n## 1\\. ?`Demo01Application#main(...)`?ʼ\n\nspringboot ʽǳ򵥣һУ\n\n```\n@SpringBootApplication\npublic class Demo01Application {\n\n    public static void main(String[] args) {\n        // һоspringboot\n        SpringApplication.run(Demo01Application.class, args);\n    }\n}\n\n```\n\nȻǾͽʲô\n\n```\npublic class SpringApplication {\n    ...\n    // primarySource Ǵ Demo01Application.class\n    // args  main() Ĳ\n    public static ConfigurableApplicationContext run(\n            Class<?> primarySource, String... args) {\n        //  primarySource װ飬 run(...) \n        return run(new Class<?>[] { primarySource }, args);\n    }\n\n    // primarySources Ǵ Demo01Application.class װɵ飬\n    // args  main() Ĳ\n    public static ConfigurableApplicationContext run(\n            Class<?>[] primarySources, String[] args) {\n        // ￪ʼ\n        return new SpringApplication(primarySources).run(args);\n    }\n    ...\n}\n\n```\n\nͨһ׷ȥ?`SpringApplication#run(Class<?>[], String[])`?ؼ£\n\n```\nreturn new SpringApplication(primarySources).run(args);\n\n```\n\nҪԲ֣\n\n*   췽`SpringApplication#SpringApplication(Class<?>...)`\n*   ʵ`SpringApplication#run(String...)`\n\n springboot ˣǾ\n\n## 2\\. ?`SpringApplication``SpringApplication#SpringApplication(Class<?>...)`\n\n```\npublic class SpringApplication {\n    public SpringApplication(Class<?>... primarySources) {\n        // \n        this(null, primarySources);\n    }\n\n    /**\n     * յõĹ췽\n     * resourceLoader Ϊ null\n     * primarySources Ϊ Demo01Application.class\n     */\n    @SuppressWarnings({ \"unchecked\", \"rawtypes\" })\n    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {\n        // 1\\. resourceLoaderõԱֵΪnull\n        this.resourceLoader = resourceLoader;\n        Assert.notNull(primarySources, \"PrimarySources must not be null\");\n        // 2\\. primarySourcesõԱֵΪ Demo01Application.class\n        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));\n        // 3\\. ǰ web ӦͣREACTIVENONESERVLET\n        this.webApplicationType = WebApplicationType.deduceFromClasspath();\n        // 4\\. óʼgetSpringFactoriesInstances META-INF/spring.factories лȡ\n        setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));\n        // 5\\. ügetSpringFactoriesInstances META-INF/spring.factories лȡ\n        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));\n        // 6\\. ذmain()class\n        this.mainApplicationClass = deduceMainApplicationClass();\n    }\n}\n\n```\n\n»ǱȽģݶڴעˣЩҪչǾ\n\n### 2.1 ȡǰ web Ӧͣ`WebApplicationType.deduceFromClasspath()`\n\n`WebApplicationType.deduceFromClasspath()`?ƶϵǰĿʲô͵ģ£\n\n```\npublic enum WebApplicationType {\n    //  web Ӧ\n    NONE,\n\n    // servlet ͵ web Ӧ\n    SERVLET,\n\n    // reactive ͵ web Ӧ\n    REACTIVE;\n\n    ...\n\n    private static final String[] SERVLET_INDICATOR_CLASSES = { \n            \"javax.servlet.Servlet\",\n            \"org.springframework.web.context.ConfigurableWebApplicationContext\" };\n\n    private static final String WEBMVC_INDICATOR_CLASS \n            = \"org.springframework.web.servlet.DispatcherServlet\";\n\n    private static final String WEBFLUX_INDICATOR_CLASS \n            = \"org.springframework.web.reactive.DispatcherHandler\";\n\n    private static final String JERSEY_INDICATOR_CLASS \n            = \"org.glassfish.jersey.servlet.ServletContainer\";\n\n    static WebApplicationType deduceFromClasspath() {\n        // classpath н WEBFLUX \n        if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) \n                && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)\n                && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {\n            return WebApplicationType.REACTIVE;\n        }\n        // classpath  SERVLET \n        for (String className : SERVLET_INDICATOR_CLASSES) {\n            if (!ClassUtils.isPresent(className, null)) {\n                return WebApplicationType.NONE;\n            }\n        }\n        // Ĭ web Ϊ SERVLET\n        // Ҳ˵ͬʱ WEBFLUX  SERVLET ࣬շص SERVLET\n        return WebApplicationType.SERVLET;\n    }\n\n    ...\n}\n\n```\n\nԿspringboot Ŀͣ`NONE`( web Ӧ)`SERVLET`(`servlet`?͵ web Ӧ)`REACTIVE`(`reactive`?͵ web Ӧ)`WebApplicationType.deduceFromClasspath()`?ִ£\n\n1.  ?`classpath`?н?`WEBFLUX`?࣬ǰĿ?`reactive`?͵ web Ӧãأ\n2.  ?`classpath`?в?`SERVLET`?࣬ǰĿ web Ӧãأ\n3.  㣬ǰĿ?`servlet`?͵ web Ӧá\n\n demo ?`spring-boot-starter-web`?˵ǰĿ?`servlet`?͵ web Ӧá\n\n### 2.2 óʼ`setInitializers(...)`\n\n£\n\n```\nsetInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));\n\n```\n\nдΪ:\n\n*   ȡ?`ApplicationContextInitializer``getSpringFactoriesInstances(ApplicationContextInitializer.class)`\n*   óʼ`setInitializers(...)`\n\nȡ?`ApplicationContextInitializer`?̣£\n\n```\npublic class SpringApplication {\n    ...\n\n    // type Ϊ ApplicationContextInitializer.class\n    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {\n        return getSpringFactoriesInstances(type, new Class<?>[] {});\n    }\n\n    /**\n     * type Ϊ ApplicationContextInitializer.class\n     * parameterTypes Ϊ ew Class<?>[] {}\n     * args Ϊ null\n     */\n    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, \n            Class<?>[] parameterTypes, Object... args) {\n        ClassLoader classLoader = getClassLoader();\n        //  META-INF/spring.factories \n        Set<String> names = new LinkedHashSet<>(\n                SpringFactoriesLoader.loadFactoryNames(type, classLoader));\n        // ʵʹõķ\n        List<T> instances = createSpringFactoriesInstances(\n                type, parameterTypes, classLoader, args, names);\n        // 򣬱Ƚϵ @Order ע⣬ʵֵ Orderd ӿ\n        AnnotationAwareOrderComparator.sort(instances);\n        return instances;\n    }\n    ...\n}\n\n```\n\nϴȽϼ򵥣ȴ?`META-INF/spring.factories`?ȡݣȻʹ÷ʵٷء\n\n?`SpringFactoriesLoader.loadFactoryNames(...)`ջ?`META-INF/spring.factories`?ݣ`META-INF/spring.factories`?Ϊһļkey Ϊ type÷ϸԲο?[springboot Զװ֮Զװ](https://my.oschina.net/funcy/blog/4870868)\n\nջжٸ?`ApplicationContextInitializer`?ؽأͨԣһ 7 \n\n![](https://oscimg.oschina.net/oscnet/up-53f764fefeb0c55fcfef6e34634805162f5.png)\n\n 7 ?`ApplicationContextInitializer`˵£\n\n*   `ConfigurationWarningsApplicationContextInitializer` IOC һЩĴ\n*   `ContextIdApplicationContextInitializer` Spring Ӧĵ ID\n*   `DelegatingApplicationContextInitializer`?`application.properties`??`context.initializer.classes`?õ\n*   `RSocketPortInfoApplicationContextInitializer`?`RSocketServer`?ʵʹõļ˿д뵽?`Environment`?\n*   `ServerPortInfoApplicationContextInitializer` servlet ʵʹõļ˿д뵽?`Environment`?\n*   `SharedMetadataReaderFactoryContextInitializer`һ?`SpringBoot`??`ConfigurationClassPostProcessor`?õ?`CachingMetadataReaderFactory`?\n*   `ConditionEvaluationReportLoggingListener`?`ConditionEvaluationReport`?д־\n\nȡ?`ApplicationContextInitializer`?`setInitializers(...)`?\n\n```\npublic class SpringApplication {\n    ...\n    public void setInitializers(\n            Collection<? extends ApplicationContextInitializer<?>> initializers) {\n        this.initializers = new ArrayList<>(initializers);\n    }\n    ...\n}\n\n```\n\nһ׼?`setter`?ľֻóԱ\n\n### 2.3 ü`setListeners(...)`\n\nüĴ£\n\n```\nsetListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));\n\n```\n\nʽϿͬ?`Initializer`?һҲȴ?`META-INF/spring.factories`?м?`ApplicationListener`ȻӵԱУֱӿܻȡЩ?`listener`\n\n![](https://oscimg.oschina.net/oscnet/up-0440eb21c69a75686850a1b44eb9f1287c8.png)\n\nԿһԻȡ 11 ?`listener`Щ?`listener`?£\n\n*   `ClearCachesApplicationListener`ӦļɺԻ\n*   `ParentContextCloserApplicationListener`˫ӦĵĹر¼ԼӦд\n*   `CloudFoundryVcapEnvironmentPostProcessor`?`CloudFoundry`?ṩ֧\n*   `FileEncodingApplicationListener`ϵͳļӦûǷһ£ϵͳļӦûı벻ֹͬӦ\n*   `AnsiOutputApplicationListener`?`spring.output.ansi.enabled`??`AnsiOutput`\n*   `ConfigFileApplicationListener`ӳЩԼλöȡļ\n*   `DelegatingApplicationListener`¼ת?`application.properties`?õ?`context.listener.classes`?ļ\n*   `ClasspathLoggingApplicationListener`Ի¼?`ApplicationEnvironmentPreparedEvent`?Ӧʧ¼?`ApplicationFailedEvent`?Ӧ\n*   `LoggingApplicationListener`?`LoggingSystem`ʹ?`logging.config`?ָûȱʡ\n*   `LiquibaseServiceLocatorApplicationListener`ʹһԺ?`SpringBoot`?ִ jar Ϲİ汾滻?`LiquibaseServiceLocator`\n*   `BackgroundPreinitializer`ʹһ̨߳̾紥һЩʱĳʼ\n\n?`SpringApplication#setListeners`\n\n```\npublic void setListeners(Collection<? extends ApplicationListener<?>> listeners) {\n    this.listeners = new ArrayList<>(listeners);\n}\n\n```\n\nҲһ׼?`setter`?\n\n### 2.4 ƶࣺ`deduceMainApplicationClass()`\n\nν࣬ǰ?`main(String[])`Ҳǵǰ spring Ӧõ࣬`SpringApplication#deduceMainApplicationClass`?£\n\n```\nprivate Class<?> deduceMainApplicationClass() {\n    try {\n        // ȡջ\n        StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();\n        // ջ main\n        for (StackTraceElement stackTraceElement : stackTrace) {\n            if (\"main\".equals(stackTraceElement.getMethodName())) {\n                return Class.forName(stackTraceElement.getClassName());\n            }\n        }\n    }\n    catch (ClassNotFoundException ex) {\n        // Swallow and continue\n    }\n    return null;\n}\n\n```\n\nҪͨ?`new RuntimeException().getStackTrace()`?ȡջȻõ?`main`?࣬õĵջ£\n\n![](https://oscimg.oschina.net/oscnet/up-8c04487e6b05f583e7d45b83c293634f42a.png)\n\nԿ`main()`?Ͱڵջˡ\n\n### 2.5 ܽ\n\nҪǽ?`SpringApplication`?Ĵ̣ص¼㣺\n\n1.  ƶϵǰ web ӦͣNONE, SERVLET,REACTIVE\n2.  óʼ`ApplicationContextInitializer`\n3.  ü`ApplicationListener`\n4.  ƶࡣ\n\n![](https://oscimg.oschina.net/oscnet/up-e9a43f1c523c0f19d37e4741580ed32ca08.png)\n\n* * *\n\n_ԭӣ[https://my.oschina.net/funcy/blog/4877610](https://my.oschina.net/funcy/blog/4877610)?߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע_"
  },
  {
    "path": "docs/Spring全家桶/SpringBoot源码解析/SpringBoot启动流程（三）：准备IOC容器.md",
    "content": "һƪܽ springboot £\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-07a6b491fbe69b8dcbd41e59a8543f06671.png)\n\nģǼĲ衣\n\n### 3.8  ioc \n\n ioc Ĵ£\n\n```\nConfigurableApplicationContext context = null;\n....\n// applicationContext\ncontext = createApplicationContext();\n\n```\n\nǽ `SpringApplication#createApplicationContext` \n\n```\n/** Ĭϵ ApplicationContext */\npublic static final String DEFAULT_CONTEXT_CLASS = \"org.springframework.context.\"\n        + \"annotation.AnnotationConfigApplicationContext\";\n\n/** servlet Ӧõĵ ApplicationContext */\npublic static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = \"org.springframework.boot.\"\n        + \"web.servlet.context.AnnotationConfigServletWebServerApplicationContext\";\n\n/** Reactive Ӧõ ApplicationContext */\npublic static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = \"org.springframework.\"\n        + \"boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext\";\n\nprotected ConfigurableApplicationContext createApplicationContext() {\n    Class<?> contextClass = this.applicationContextClass;\n    if (contextClass == null) {\n        try {\n            // Ӧͬ\n            switch (this.webApplicationType) {\n            case SERVLET:\n                // ʹõ AnnotationConfigServletWebServerApplicationContext\n                contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);\n                break;\n            case REACTIVE:\n                contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);\n                break;\n            default:\n                // Ĭʹõ AnnotationConfigApplicationContext\n                contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);\n            }\n        }\n        catch (ClassNotFoundException ex) {\n            throw new IllegalStateException(...);\n        }\n    }\n    // ʹ÷ʵ\n    return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);\n}\n\n```\n\nҪǸӦͬ `ApplicationContext`ʹ÷ķʵӦͶӦ `ApplicationContext` £\n\n1.  `servlet` Ӧã`AnnotationConfigServletWebServerApplicationContext`\n2.  `reactive` Ӧã`AnnotationConfigReactiveWebServerApplicationContext`\n3.  ϶ǣ`AnnotationConfigApplicationContext`\n\nǰӦõ `servlet`˴ `ApplicationContext`  `AnnotationConfigReactiveWebServerApplicationContext`Ĺ췽\n\n```\npublic class AnnotationConfigServletWebServerApplicationContext \n        extends ServletWebServerApplicationContext implements AnnotationConfigRegistry {\n\n    //  BeanDefinition ע\n    private final AnnotatedBeanDefinitionReader reader;\n\n    // ɨ\n    private final ClassPathBeanDefinitionScanner scanner;\n\n    ...\n\n    public AnnotationConfigServletWebServerApplicationContext() {\n        this.reader = new AnnotatedBeanDefinitionReader(this);\n        this.scanner = new ClassPathBeanDefinitionScanner(this);\n    }\n\n    ...\n}\n\n```\n\n`AnnotationConfigServletWebServerApplicationContext` Ĺ췽ǱȽϼ򵥵ģֻԣͲ˵ˡҲҪĿԶһ㣬丸Ĺ췽 `GenericApplicationContext` Ĺ췽ҵôһ䣺\n\n```\npublic GenericApplicationContext() {\n    this.beanFactory = new DefaultListableBeanFactory();\n}\n\n```\n\nд봴 `DefaultListableBeanFactory` 丳ֵ `beanFactory` `ApplicationContext` ʹõ `beanFactory`  `DefaultListableBeanFactory`\n\n### 3.9 ׼ ioc \n\n ioc 󣬽žǶһЩ׼£\n\n```\npublic class SpringApplication {\n\n    ...\n\n    private void prepareContext(ConfigurableApplicationContext context, \n            ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, \n            ApplicationArguments applicationArguments, Banner printedBanner) {\n        // õӦûõIOC\n        context.setEnvironment(environment);\n        // һЩ\n        postProcessApplicationContext(context);\n        // ӦInitializerгʼ\n        applyInitializers(context);\n        //  SpringApplicationRunListenerscontextPrepared\n        // ڴ׼ApplicationContext֮󣬵ڼ֮ǰ\n        listeners.contextPrepared(context);\n        // ӡ־\n        if (this.logStartupInfo) {\n            logStartupInfo(context.getParent() == null);\n            logStartupProfileInfo(context);\n        }\n        // ȡbeanFactory\n        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();\n        // вΪbeanעᵽbeanFactory\n        beanFactory.registerSingleton(\"springApplicationArguments\", applicationArguments);\n        //  banner ΪbeanעᵽbeanFactory\n        if (printedBanner != null) {\n            beanFactory.registerSingleton(\"springBootBanner\", printedBanner);\n        }\n        if (beanFactory instanceof DefaultListableBeanFactory) {\n            // ǷbeanϢ\n            ((DefaultListableBeanFactory) beanFactory)\n                    .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);\n        }\n        // \n        if (this.lazyInitialization) {\n            context.addBeanFactoryPostProcessor(\n                    new LazyInitializationBeanFactoryPostProcessor());\n        }\n        // ȡԴ\n        Set<Object> sources = getAllSources();\n        Assert.notEmpty(sources, \"Sources must not be empty\");\n        //  class\n        load(context, sources.toArray(new Object[0]));\n        // ¼\n        listeners.contextLoaded(context);\n    }\n}\n\n```\n\n׼Ĳ軹ǺģҪݶڴнעͣһЩ΢չ¡\n\n#### 1\\.  `Environment`\n\nòĴΪ\n\n```\nprivate void prepareContext(ConfigurableApplicationContext context, \n        ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, \n        ApplicationArguments applicationArguments, Banner printedBanner) {\n    // õӦûõIOC\n    context.setEnvironment(environment);\n    ...\n}\n\n```\n\n `environment` ǰ洴 `environment`ioc Ҳʹò\n\n```\npublic class AnnotationConfigServletWebServerApplicationContext extends ... {\n\n    ...\n\n    @Override\n    public void setEnvironment(ConfigurableEnvironment environment) {\n        // øķ\n        super.setEnvironment(environment);\n        // Ҳenvironmentõ\n        this.reader.setEnvironment(environment);\n        this.scanner.setEnvironment(environment);\n    }\n\n    ....\n}\n\n```\n\n#### 2\\.  ioc Ĳ\n\n `postProcessApplicationContext(context);` Ĺ\n\n```\npublic class SpringApplication {\n\n    ...\n\n    protected void postProcessApplicationContext(ConfigurableApplicationContext context) {\n        //  beanNameGenerator bean ƣﴫnull\n        if (this.beanNameGenerator != null) {\n            context.getBeanFactory().registerSingleton(\n                    AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,\n                    this.beanNameGenerator);\n        }\n        //  resourceLoaderزΪnullifĴ벻ִ\n        if (this.resourceLoader != null) {\n            if (context instanceof GenericApplicationContext) {\n                ((GenericApplicationContext) context).setResourceLoader(this.resourceLoader);\n            }\n            if (context instanceof DefaultResourceLoader) {\n                ((DefaultResourceLoader) context).setClassLoader(\n                        this.resourceLoader.getClassLoader());\n            }\n        }\n        // ִ\n        if (this.addConversionService) {\n            // תStringתNumber\n            context.getBeanFactory().setConversionService(\n                    ApplicationConversionService.getSharedInstance());\n        }\n    }\n\n    ...\n\n}\n\n```\n\nⲿ־ `ApplicationContext` ļԣǵ demo У`beanNameGenerator`  `resourceLoader`  `null`鶼Уеľֻ룺\n\n```\n context.getBeanFactory().setConversionService(\n        ApplicationConversionService.getSharedInstance());\n\n```\n\n`ConversionService` ǰҲᵽҪвתġ\n\n#### 3\\. Ӧóʼ`applyInitializers(context)`\n\n`SpringApplication#applyInitializers` £\n\n```\npublic class SpringApplication {\n\n    ...\n\n    @SuppressWarnings({ \"rawtypes\", \"unchecked\" })\n    protected void applyInitializers(ConfigurableApplicationContext context) {\n        // getInitializers()ȡеĳʼ\n        for (ApplicationContextInitializer initializer : getInitializers()) {\n            Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),\n                    ApplicationContextInitializer.class);\n            Assert.isInstanceOf(requiredType, context, \"Unable to call initializer.\");\n            // һinitializerinitialize(...)\n            initializer.initialize(context);\n        }\n    }\n\n}\n\n```\n\nǺģǻȡе `Initializer`Ȼ `initialize(...)` \n\nﻹҪ£`getInitializers()` ôȡ `Initializer` أش£\n\n```\npublic class SpringApplication {\n\n    private List<ApplicationContextInitializer<?>> initializers;\n\n    ...\n\n    // ڹ췽õ initializers\n    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {\n        ...\n\n        // óʼgetSpringFactoriesInstances META-INF/spring.factories лȡ\n        setInitializers((Collection) getSpringFactoriesInstances(\n                ApplicationContextInitializer.class));\n\n        ...\n    }\n\n    // ȡ Initializer Ĳлȡ\n    public Set<ApplicationContextInitializer<?>> getInitializers() {\n        return asUnmodifiableOrderedSet(this.initializers);\n    }\n\n    ...\n\n```\n\n˴ˣǰ `SpringApplication` Ĺ췽ʱᵽ springboot  `META-INF/spring.factories` ȡõ `Initializer`õ `initializers` ԣʹ `Initializer` ĵطˡ\n\n#### 4\\. ȡԴ\n\n£\n\n```\nprivate void prepareContext(ConfigurableApplicationContext context, \n        ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, \n        ApplicationArguments applicationArguments, Banner printedBanner) {\n    ...\n    // ȡԴ\n    Set<Object> sources = getAllSources();\n    ...\n}\n\n```\n\n `getAllSources()` \n\n```\n//  primarySources setУȻsetתΪɱset\npublic Set<Object> getAllSources() {\n    Set<Object> allSources = new LinkedHashSet<>();\n    if (!CollectionUtils.isEmpty(this.primarySources)) {\n        allSources.addAll(this.primarySources);\n    }\n    // sources Ϊգifִ\n    if (!CollectionUtils.isEmpty(this.sources)) {\n        allSources.addAll(this.sources);\n    }\n    return Collections.unmodifiableSet(allSources);\n}\n\n```\n\nܼ򵥣һ `primarySources`  `set` УȻ `set` תΪɱ `set`ء\n\n `primarySources` ɶأҪص `SpringApplication` Ĺ췽ˣ\n\n```\npublic SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {\n    ...\n    //  primarySources\n    Assert.notNull(primarySources, \"PrimarySources must not be null\");\n    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));\n    ...\n}\n\n```\n\nȻϣ `primarySources`  `main` дģ\n\n```\n@SpringBootApplication\npublic class Demo01Application {\n\n    public static void main(String[] args) {\n        // Demo01Application.class  primarySources\n        SpringApplication.run(Demo01Application.class, args);\n    }\n\n}\n\n```\n\nǴ `Demo01Application.class`  `primarySources`\n\nˣ`getAllSources()` һ setset ֻһԪأ`Demo01Application.class`Ҳ֤ͨ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-29dd8056dc457c56e6fe41020516d02fe80.png)\n\n#### 5\\. Դ\n\nĴ£\n\n```\nprivate void prepareContext(ConfigurableApplicationContext context, \n        ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, \n        ApplicationArguments applicationArguments, Banner printedBanner) {\n    ...\n    //  class\n    load(context, sources.toArray(new Object[0]));\n    ...\n}\n\n```\n\n `SpringApplication#load` \n\n```\npublic class SpringApplication {\n    ...\n\n    protected void load(ApplicationContext context, Object[] sources) {\n        // һBeanDefinitionļ\n        BeanDefinitionLoader loader = createBeanDefinitionLoader(\n                getBeanDefinitionRegistry(context), sources);\n        // ǰ beanNameGenerator Ϊ null\n        if (this.beanNameGenerator != null) {\n            loader.setBeanNameGenerator(this.beanNameGenerator);\n        }\n        // ǰ resourceLoader Ϊ null\n        if (this.resourceLoader != null) {\n            loader.setResourceLoader(this.resourceLoader);\n        }\n        // ǰ environment Ϊ null\n        // ǰ洴environmentûиֵԱ\n        if (this.environment != null) {\n            loader.setEnvironment(this.environment);\n        }\n        loader.load();\n    }\n    ...\n}\n\n```\n\nǴһ `BeanDefinitionLoader` ʵȡ `source`  `BeanDefinitionLoader` Ĺ췽д뵽ʵУȻԸ `loader` һϵеãٵ `load()` Ҫ˵ǣȻǰ洴 `environment` `this.environment` Ϊ `null`ԭǰ洴 `environment` ûиֵ `this.environment`\n\nǼ `BeanDefinitionLoader#load()`\n\n```\nclass BeanDefinitionLoader {\n\n    ...\n\n    int load() {\n        int count = 0;\n        //  sources оֻһԪأDemo01Application.class\n        for (Object source : this.sources) {\n            count += load(source);\n        }\n        return count;\n    }\n\n    // ز\n    private int load(Object source) {\n        Assert.notNull(source, \"Source must not be null\");\n        // Class \n        if (source instanceof Class<?>) {\n            // source Ϊ Demo01Application.classҪע\n            return load((Class<?>) source);\n        }\n        if (source instanceof Resource) {\n            return load((Resource) source);\n        }\n        if (source instanceof Package) {\n            return load((Package) source);\n        }\n        if (source instanceof CharSequence) {\n            return load((CharSequence) source);\n        }\n        throw new IllegalArgumentException(\"Invalid source type \" + source.getClass());\n    }\n\n    // Class ͵ļز\n    private int load(Class<?> source) {\n        //  grouovy Եģù\n        if (isGroovyPresent() && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {\n            GroovyBeanDefinitionSource loader \n                    = BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class);\n            load(loader);\n        }\n        // Ƿ @Component ע\n        if (isComponent(source)) {\n            //  BeanDefinition 󣬲עᵽspring\n            this.annotatedReader.register(source);\n            return 1;\n        }\n        return 0;\n    }\n    ...\n\n```\n\nڴ `BeanDefinitionLoader` ʵʱͨ乹췽 `sources`  `BeanDefinitionLoader` ʵУ`sources` оֻһԪأ`Demo01Application.class`ֻע `Class` ԴļؾͿˣյ `BeanDefinitionLoader#load(java.lang.Class<?>)` ÷ĲΪжϴ `Class` Ƿ `@Component`оͽעᵽ ioc С\n\nô `Demo01Application.class` Ƿ `@Component` עأеģصñȽע㼶£\n\n```\n@SpringBootApplication\npublic class Demo01Application {\n    ...\n}\n\n```\n\n `@SpringBootApplication`\n\n```\n...\n@SpringBootConfiguration\n...\npublic @interface SpringBootApplication {\n    ...\n}\n\n```\n\n `@SpringBootApplication`\n\n```\n...\n@Configuration\npublic @interface SpringBootConfiguration {\n    ...\n}\n\n```\n\n `@Configuration`\n\n```\n...\n@Component\npublic @interface Configuration {\n    ...\n}\n\n```\n\nزۣ `@Configuration` עҵ `@Component`.\n\n `this.annotatedReader.register(source)` עľ [spring ](https://my.oschina.net/funcy/blog/4527454)УѾˣͲٷˡ\n\nƪľ͵ˣƪʣµ̡\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-fba95ddd84c68cdd1757f060ab131478a3c.png)\n\n* * *\n\n_ԭӣ[https://my.oschina.net/funcy/blog/4884127](https://my.oschina.net/funcy/blog/4884127) ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע_"
  },
  {
    "path": "docs/Spring全家桶/SpringBoot源码解析/SpringBoot启动流程（二）：准备运行环境.md",
    "content": "ģǼĽ`SpringApplication#run(String...)`\n\n## 3.`springboot`У`SpringApplication#run(String...)`\n\n£\n\n```\npublic ConfigurableApplicationContext run(String... args) {\n    // 1\\.  StopWatch ʵʵǸʱͳspringbootʱ\n    StopWatch stopWatch = new StopWatch();\n    stopWatch.start();\n    // ׼յApplicationContextԼһ쳣\n    ConfigurableApplicationContext context = null;\n    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();\n    // 2\\. һϵͳԣjava.awt.headlessjava.awt.headlessģʽϵͳһģʽ\n    // ϵͳȱʾ豸̻Щ¿ʹøģʽ\n    configureHeadlessProperty();\n    // 3\\. ȡҲǴ META-INF/spring.factories лȡ\n    SpringApplicationRunListeners listeners = getRunListeners(args);\n    // starting()״runʱáڷǳڵĳʼ׼ʱ֮ǰ\n    // 4\\. ¼\n    listeners.starting();\n    try {\n        // װĲ\n        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);\n        // 5\\. \n        ConfigurableEnvironment environment \n                = prepareEnvironment(listeners, applicationArguments);\n        // 6\\.  spring.beaninfo.ignore򽫸ýϵͳ\n        configureIgnoreBeanInfo(environment);\n        // 7\\. banner\n        Banner printedBanner = printBanner(environment);\n        // 8\\. applicationContext\n        context = createApplicationContext();\n        // 󱨸Զصӿ\n        exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,\n                new Class[] { ConfigurableApplicationContext.class }, context);\n        // 9\\. ׼ģһϵеֵ\n        prepareContext(context, environment, listeners, applicationArguments, printedBanner);\n        // 10\\.  AbstractApplicationContext.refreshspring\n        refreshContext(context);\n        // 11\\. ˢºĴ\n        afterRefresh(context, applicationArguments);\n        stopWatch.stop();\n        if (this.logStartupInfo) {\n            new StartupInfoLogger(this.mainApplicationClass)\n                .logStarted(getApplicationLog(), stopWatch);\n        }\n        // 12\\. ¼\n        listeners.started(context);\n        // 13\\.  runnerʵ ApplicationRunnerCommandLineRunner Ľӿ\n        callRunners(context, applicationArguments);\n    }\n    catch (Throwable ex) {\n        handleRunFailure(context, ex, exceptionReporters, listeners);\n        throw new IllegalStateException(ex);\n    }\n    try {\n        // 14\\. ¼\n        listeners.running(context);\n    }\n    catch (Throwable ex) {\n        handleRunFailure(context, ex, exceptionReporters, null);\n        throw new IllegalStateException(ex);\n    }\n    return context;\n}\n\n```\n\n£\n\n![](https://oscimg.oschina.net/oscnet/up-07a6b491fbe69b8dcbd41e59a8543f06671.png)\n\nص 13 ̡\n\n### 3.1`stopWatch`ʱ\n\nһʼspringboot ʹ`stopWatch`ʵȻ`StopWatch#start()`ʱܣûɶ˵ģǸʱ springboot ʱ־еʱʱõģ\n\n![](https://oscimg.oschina.net/oscnet/up-70a9e95e6c1208288334341bdb54bd59c17.png)\n\n### 3.2 `java.awt.headless`ֵ\n\n`SpringApplication#configureHeadlessProperty`ش£\n\n```\npublic class SpringApplication {\n\n    private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = \"java.awt.headless\";\n\n    ...\n\n    private boolean headless = true;\n\n    public void setHeadless(boolean headless) {\n        this.headless = headless;\n    }\n    ...\n\n    private void configureHeadlessProperty() {\n        //  java.awt.headless ֵõϵͳ\n        System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,\n                System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, \n                Boolean.toString(this.headless)));\n    }\n    ...\n}\n\n```\n\nǽ`java.awt.headless`ֵõϵͳó`true`ʾ`java.awt.headless`ģʽôǸɶģʽأ˵ģʽϵͳȱʾ豸̻ģʽһ㶼¹ġ\n\n### 3.3 ȡм\n\nһǻȡмԼڼһЩ״̬룺\n\n```\n// ȡҲǴ META-INF/spring.factories лȡ\nSpringApplicationRunListeners listeners = getRunListeners(args);\n\n```\n\n`SpringApplication#getRunListeners`\n\n```\npublic class SpringApplication {\n    ...\n\n    private SpringApplicationRunListeners getRunListeners(String[] args) {\n        Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };\n        return new SpringApplicationRunListeners(logger,\n                // ȻǴMETA-INF/spring.factories лȡkey  SpringApplicationRunListener\n                getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));\n    }\n    ...\n}\n\n```\n\nԿ`SpringApplicationRunListener`ȻǴ`META-INF/spring.factories`лȡ`SpringApplicationRunListener`Ǹɶأ룺\n\n```\npublic interface SpringApplicationRunListener {\n\n    /**\n     * ״runʱáڷǳڵĳʼ\n     */\n    default void starting() {\n    }\n\n    /**\n     * ׼ûEnvironmentɣڴApplicationContext֮ǰá\n     */\n    default void environmentPrepared(ConfigurableEnvironment environment) {\n    }\n\n    /**\n     * ڴ͹ApplicationContext֮󣬵ڼ֮ǰá\n     */\n    default void contextPrepared(ConfigurableApplicationContext context) {\n    }\n\n    /**\n     * ApplicationContextѼصˢ֮ǰá\n     */\n    default void contextLoaded(ConfigurableApplicationContext context) {\n    }\n\n    /**\n     * ApplicationContextˢ£Ӧó\n     * δCommandLineRunnersApplicationRunners\n     */\n    default void started(ConfigurableApplicationContext context) {\n    }\n\n    /**\n     * з֮ǰã\n     * ˢApplicationContextCommandLineRunnersApplicationRunner\n     */\n    default void running(ConfigurableApplicationContext context) {\n    }\n\n    /**\n     * Ӧóʱʧʱá\n     */\n    default void failed(ConfigurableApplicationContext context, Throwable exception) {\n    }\n}\n\n```\n\n`SpringApplicationRunListener`һӿڣһϵеķ springboot ̣˵Ѿĵϸ壬Ҫ springboot еĳһЩ飬Ϳʵ`SpringApplicationRunListener`ȻдӦķ\n\nͨԣ springboot õм£\n\n![](https://oscimg.oschina.net/oscnet/up-3ed62d827b3bf1989af74f9c4db1fc0b9ce.png)\n\n### 3.4 м`listeners.starting()`\n\nص`SpringApplication#run(java.lang.String...)`ȡм󣬻`starting()`¼\n\n```\n// ȡ\nSpringApplicationRunListeners listeners = getRunListeners(args);\n// starting()״runʱáڷǳڵĳʼ׼ʱ֮ǰ\nlisteners.starting();\n\n```\n\n`SpringApplicationRunListeners#starting`\n\n```\nvoid starting() {\n    for (SpringApplicationRunListener listener : this.listeners) {\n        listener.starting();\n    }\n}\n\n```\n\nԿνķ¼Ǳеļһ`starting()`ˣ`this.listeners`ȡемˣ`SpringApplicationRunListener``environmentPrepared(...)``contextPrepared(...)`ȶĵ·濴˾Ͳظˡ\n\n### 3.5 ׼ʱ\n\nĴ£\n\n```\n// װĲ\nApplicationArguments applicationArguments = new DefaultApplicationArguments(args);\n// \nConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);\n\n```\n\n`SpringApplication#prepareEnvironment`\n\n```\nprivate ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,\n        ApplicationArguments applicationArguments) {\n    // ȡ򴴽\n    ConfigurableEnvironment environment = getOrCreateEnvironment();\n    // ʱ\n    configureEnvironment(environment, applicationArguments.getSourceArgs());\n    ConfigurationPropertySources.attach(environment);\n    //  SpringApplicationRunListener  environmentPrepared \n    // EnvironmentɣڴApplicationContext֮ǰ\n    listeners.environmentPrepared(environment);\n    // Ӧð\n    bindToSpringApplication(environment);\n    if (!this.isCustomEnvironment) {\n        environment = new EnvironmentConverter(getClassLoader())\n                .convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());\n    }\n    ConfigurationPropertySources.attach(environment);\n    return environment;\n}\n\n```\n\nԿֻ׼ص㽲\n\n#### 1\\. ȡ򴴽`Environment`\n\nֱӽ`SpringApplication#getOrCreateEnvironment`\n\n```\nprivate ConfigurableEnvironment getOrCreateEnvironment() {\n    if (this.environment != null) {\n        return this.environment;\n    }\n    switch (this.webApplicationType) {\n    case SERVLET:\n        return new StandardServletEnvironment();\n    case REACTIVE:\n        return new StandardReactiveWebEnvironment();\n    default:\n        return new StandardEnvironment();\n    }\n}\n\n```\n\nӴǸӦӦ`Environment`ʵǰӦ`SERVLET`ֱӿ`StandardServletEnvironment`δġ\n\n֪ java УʱȵøĹ췽ֱӽ`AbstractEnvironment`췽\n\n```\n\npublic abstract class AbstractEnvironment implements ConfigurableEnvironment {\n\n    ...\n\n    public AbstractEnvironment() {\n        customizePropertySources(this.propertySources);\n    }\n\n    ...\n}\n\n```\n\n`AbstractEnvironment`Ĺ췽У`customizePropertySources()``StandardServletEnvironment`ʵ֣\n\n```\npublic class StandardServletEnvironment extends StandardEnvironment \n        implements ConfigurableWebEnvironment {\n    public static final String SERVLET_CONTEXT_PROPERTY_SOURCE_NAME = \"servletContextInitParams\";\n    public static final String SERVLET_CONFIG_PROPERTY_SOURCE_NAME = \"servletConfigInitParams\";\n    public static final String JNDI_PROPERTY_SOURCE_NAME = \"jndiProperties\";\n\n    @Override\n    protected void customizePropertySources(MutablePropertySources propertySources) {\n        //  servletConfigInitParams\n        propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));\n        //  servletContextInitParams\n        propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));\n        if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {\n            propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));\n        }\n        // øķ\n        super.customizePropertySources(propertySources);\n    }\n\n    @Override\n    public void initPropertySources(@Nullable ServletContext servletContext, \n            @Nullable ServletConfig servletConfig) {\n        // 滻õ servletContextInitParams Ϊ servletContext\n        // 滻õ servletConfigInitParams Ϊ servletConfig\n        WebApplicationContextUtils.initServletPropertySources(\n                getPropertySources(), servletContext, servletConfig);\n    }\n\n}\n\n```\n\nԿ`StandardServletEnvironment``customizePropertySources()`ֻ˼ servlet صĲȻȥøĹ췽ˣǼ`StandardEnvironment`\n\nƺûʲôǼ׷٣Ĺ췽\n\n```\npublic class StandardEnvironment extends AbstractEnvironment {\n\n    /** ϵͳ */\n    public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = \"systemEnvironment\";\n\n    /** ϵͳ */\n    public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = \"systemProperties\";\n\n    @Override\n    protected void customizePropertySources(MutablePropertySources propertySources) {\n        // ȡϵͳԣõ System.getenv()\n        propertySources.addLast(new PropertiesPropertySource(\n                SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));\n        // ȡϵͳõ System.getProperties()\n        propertySources.addLast(new SystemEnvironmentPropertySource(\n                SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));\n    }\n\n}\n\n```\n\nԿ`StandardEnvironment``customizePropertySources()`Ҫǽϵͳϵͳӵ`Environment`Сʵϣ`Environment`аϵͳ뻷صĲҲṩһЩ`getter`ԺܷػȡЩ\n\n![](https://oscimg.oschina.net/oscnet/up-d2d69692db15146f2981db94633e7c575d5.png)\n\nǾˣ`StandardServletEnvironment`аݣ\n\n*   ϵͳԣƽʱ`System.getenv()`õĲ\n*   ϵͳƽʱ`System.getProperties()`õĲ\n*   `servlet``servletContext``servletConfig`.\n\n#### 2\\. û\n\nǼſụ̂Ҳ`SpringApplication#configureEnvironment`\n\n```\nprotected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {\n    if (this.addConversionService) {\n        // תת StringתNumberIntegerתEnum\n        ConversionService conversionService = ApplicationConversionService.getSharedInstance();\n        environment.setConversionService((ConfigurableConversionService) conversionService);\n    }\n    // ӵ environment \n    configurePropertySources(environment, args);\n    //  ActiveProfiles ֵ\n    configureProfiles(environment, args);\n}\n\n```\n\n벻࣬ؼ㶼ڴעˣҪ΢`SpringApplication#configurePropertySources`\n\n```\nprotected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {\n    MutablePropertySources sources = environment.getPropertySources();\n    // ĬԣָĬԣ\n    if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {\n        sources.addLast(new MapPropertySource(\"defaultProperties\", this.defaultProperties));\n    }\n    if (this.addCommandLineProperties && args.length > 0) {\n        String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;\n        if (sources.contains(name)) {\n            PropertySource<?> source = sources.get(name);\n            CompositePropertySource composite = new CompositePropertySource(name);\n            composite.addPropertySource(\n                    // ʱĲ\n                    new SimpleCommandLinePropertySource(\"springApplicationCommandLineArgs\", args));\n            composite.addPropertySource(source);\n            sources.replace(name, composite);\n        }\n        else {\n            sources.addFirst(new SimpleCommandLinePropertySource(args));\n        }\n    }\n}\n\n```\n\nԴĲн`SimpleCommandLinePropertySource`\n\n```\npublic class SimpleCommandLinePropertySource \n        extends CommandLinePropertySource<CommandLineArgs> {\n\n    public SimpleCommandLinePropertySource(String... args) {\n        super(new SimpleCommandLineArgsParser().parse(args));\n    }\n    ...\n}\n\n```\n\nսķ`SimpleCommandLineArgsParser#parse`\n\n```\npublic class SimpleCommandLineArgsParser {\n    public CommandLineArgs parse(String... args) {\n        CommandLineArgs commandLineArgs = new CommandLineArgs();\n        for (String arg : args) {\n            if (arg.startsWith(\"--\")) {\n                String optionText = arg.substring(2, arg.length());\n                String optionName;\n                String optionValue = null;\n                if (optionText.contains(\"=\")) {\n                    // -- ͷҰ = Ĳᱻ key/value\n                    optionName = optionText.substring(0, optionText.indexOf('='));\n                    optionValue = optionText.substring(optionText.indexOf('=')+1, optionText.length());\n                }\n                else {\n                    optionName = optionText;\n                }\n                if (optionName.isEmpty() || (optionValue != null && optionValue.isEmpty())) {\n                    throw new IllegalArgumentException(\"Invalid argument syntax: \" + arg);\n                }\n                commandLineArgs.addOptionArg(optionName, optionValue);\n            }\n            else {\n                commandLineArgs.addNonOptionArg(arg);\n            }\n        }\n        return commandLineArgs;\n    }\n\n    ...\n}\n\n```\n\nǱȽϼ򵥵ģַĴѡ\n\nspringboot ɶýأ spring Ŀʱǿָ\n\n```\njava -jar xxx.jar --a1=aaa --b1=bbb\n\n```\n\nȻǾͨ`@Value(\"${a1}\")`ȡؼԿspringboot Ѵ`--a1=aaa``--b1=bbb``a1/aaa``b1/bbb`ֵԵʽ浽`Environment`ҪõʱͿɺܷش`Environment`лȡˡ\n\nˣ׼ķ͵ˡ\n\n### 3.6 ϵͳ\n\nҪһ`spring.beaninfo.ignore`Ƿ`BeanInfo``Դ֪Ĭֵtrue`оõĲ࣬Ͳˡ\n\n### 3.7 ӡ`banner`\n\n`banner`ӡģ\n\n```\nBanner printedBanner = printBanner(environment);\n\n```\n\nҲԼ bannerϽ̳һѣdemo Ͳṩˡ\n\n`banner` springboot ̹ϵ󣬾ͲˣСֻ˽ôüɡ\n\nˣƪľ͵ˣƪǼ\n\n![](https://oscimg.oschina.net/oscnet/up-38d3824690292937a6b0cba5b081c8f8fec.png)\n\n* * *\n\n_ԭӣ[https://my.oschina.net/funcy/blog/4882417](https://my.oschina.net/funcy/blog/4882417)߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע_"
  },
  {
    "path": "docs/Spring全家桶/SpringBoot源码解析/SpringBoot启动流程（五）：完成启动.md",
    "content": "һƪܽ springboot £\n\n![img](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-07a6b491fbe69b8dcbd41e59a8543f06671.png)\n\nģǼĲ衣\n\n### 3.11 ˢºĴ\n\nˢºĴΪ `SpringApplication#afterRefresh`£\n\n```\nprotected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {\n}\n```\n\nԿһշspringboot ṩչ\n\n### 3.12  `started` ¼\n\nòķΪ `listeners.started(context)`߼ǰѾͲٷˡ\n\n### 3.13 ص\n\nķ `SpringApplication#callRunners`£\n\n```\nprivate void callRunners(ApplicationContext context, ApplicationArguments args) {\n    List<Object> runners = new ArrayList<>();\n    // ȡе ApplicationRunner  CommandLineRunner\n    runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());\n    runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());\n    // \n    AnnotationAwareOrderComparator.sort(runners);\n    // \n    for (Object runner : new LinkedHashSet<>(runners)) {\n        //  ApplicationRunner#run \n        if (runner instanceof ApplicationRunner) {\n            callRunner((ApplicationRunner) runner, args);\n        }\n        //  CommandLineRunner#run \n        if (runner instanceof CommandLineRunner) {\n            callRunner((CommandLineRunner) runner, args);\n        }\n    }\n}\n\n/**\n *  ApplicationRunner#run \n */\nprivate void callRunner(ApplicationRunner runner, ApplicationArguments args) {\n    try {\n        (runner).run(args);\n    }\n    catch (Exception ex) {\n        throw new IllegalStateException(\"Failed to execute ApplicationRunner\", ex);\n    }\n}\n\n/**\n *  CommandLineRunner#run \n */\nprivate void callRunner(CommandLineRunner runner, ApplicationArguments args) {\n    try {\n        (runner).run(args.getSourceArgs());\n    }\n    catch (Exception ex) {\n        throw new IllegalStateException(\"Failed to execute CommandLineRunner\", ex);\n    }\n}\n```\n\nʾspringboot Ϊṩӿڣ`ApplicationRunner`  `CommandLineRunner`ǿʵһЩӦʾ£\n\n```\n/**\n * ApplicationRunner ʾ\n */\n@Component\npublic class MyApplicationRunner implements ApplicationRunner {\n    @Override\n    public void run(ApplicationArguments args) throws Exception {\n        System.out.println(\"MyApplicationRunner: hello world\");\n    }\n}\n\n/**\n * CommandLineRunner ʾ\n */\n@Component\npublic class MyCommandLineRunner implements CommandLineRunner {\n    @Override\n    public void run(String... args) throws Exception {\n        System.out.println(\"MyCommandLineRunner: hello world!\");\n    }\n}\n```\n\n### 3.14 м `listeners.running(...)`\n\nͬǰ `listeners.starting()` ·һͲˡ\n\nˣĵķ͵ˣ springboot ̵ķҲˡ\n\n![img](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-9b539b547c6004c40d2b6f8bd59481b8e34.png)\n\n------\n\n*ԭӣhttps://my.oschina.net/funcy/blog/4906553 ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע*"
  },
  {
    "path": "docs/Spring全家桶/SpringBoot源码解析/SpringBoot启动流程（六）：启动流程总结.md",
    "content": "ǰ漸ƪ· springboot ̣ܽ¡\n\nһʼ `SpringApplication.run(Demo01Application.class, args);` ֣ط\n\n*   `SpringApplication#SpringApplication(...)`\n*   `SpringApplication#run(...)`\n\n springboot ̣һܽ \n\n### `SpringApplication#SpringApplication(...)`\n\n£\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-e9a43f1c523c0f19d37e4741580ed32ca08.png)\n\nУ\n\n*   `webApplicationType` ںʲô͵ `applicationContext`\n*   `Initialzers`  `META-INF/spring.factories` springboot ʱһЩʼ\n*   `Listteners` ͬ `META-INF/spring.factories`ṩ˶Էؼ springboot ִй̡\n\n### `SpringApplication#run(...)`\n\nⲿֵ£\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-07a6b491fbe69b8dcbd41e59a8543f06671.png)\n\nУ\n\n*   `getRunListener()` ȡе `Listeners`Ҳ `SpringApplication#SpringApplication(...)`  ȡ `Listeners``Listeners` ṩڶ෽ɼ springboot ̣\n*   ׼лʱ `webApplicationType` ãõӦ͵ `Environment`  õ spring Уspring ʹõ `Environment` ﴴõģ\n*    ioc ʱҲǸ `webApplicationType` Ӧ `ApplicationContext`\n*   ׼ ioc ķУ `ApplicationContext` һ `Initializers` ҲУ\n*    ioc ʱspringboot עһ shutdownhookĿرʱرղ⣬ ioc ̣springboot չлᴴ web \n*   springboot ṩ͵`ApplicationRunner``CommandLineRunner`ߵķ\n\nݽıȽϼԣҪϸ˽⣬Ķǰ¡\n\n* * *\n\n_ԭӣ[https://my.oschina.net/funcy/blog/4906588](https://my.oschina.net/funcy/blog/4906588) ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע_"
  },
  {
    "path": "docs/Spring全家桶/SpringBoot源码解析/SpringBoot启动流程（四）：启动IOC容器.md",
    "content": "һƪܽ springboot £\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-07a6b491fbe69b8dcbd41e59a8543f06671.png)\n\nģǼĲ衣\n\n### 3.10 ˢ ioc \n\n `SpringApplication#refreshContext` \n\n```\nprivate void refreshContext(ConfigurableApplicationContext context) {\n    // spring\n    refresh(context);\n    if (this.registerShutdownHook) {\n        try {\n            // ע ShutdownHook\n            context.registerShutdownHook();\n        }\n        catch (AccessControlException ex) {\n            // Not allowed in some environments.\n        }\n    }\n}\n\n```\n\n\n\n1.  `refresh(context)` spring Ҳǵ `AbstractApplicationContext#refresh` \n2.  `context.registerShutdownHook()`ע `ShutdownHook` jvm ̹رʱһЩضĲ\n\n#### 3.10.1  spring \n\n `SpringApplication#refresh`\n\n```\nprotected void refresh(ApplicationContext applicationContext) {\n    Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);\n    // spring \n    ((AbstractApplicationContext) applicationContext).refresh();\n}\n\n```\n\nܼ򵥣ж `applicationContext` ǷΪ `AbstractApplicationContext`Ȼٵ `AbstractApplicationContext#refresh()`\n\n `AbstractApplicationContext#refresh()`ǿǴ÷ spring ̡ڱĲǷ spring £ͲչˣҪ˽̵СԲο£\n\n*   [spring Դspring ̣ģǰ׼](https://my.oschina.net/funcy/blog/4633169)\n*   [spring Դspring ̣壩ִ BeanFactoryPostProcessor](https://my.oschina.net/funcy/blog/4641114)\n*   [spring Դspring ̣ע BeanPostProcessor](https://my.oschina.net/funcy/blog/4657181)\n*   [spring Դspring ̣ߣʻ¼](https://my.oschina.net/funcy/blog/4892120)\n*   [spring Դspring ̣ˣ BeanFactory ĳʼ](https://my.oschina.net/funcy/blog/4658230)\n*   [spring Դspring ̣ţ bean Ĵ](https://my.oschina.net/funcy/blog/4659524)\n*   [spring Դspring ̣ʮɵĴ](https://my.oschina.net/funcy/blog/4892555)\n\n `AbstractApplicationContext#refresh()` Уspring ṩ˼չ㣺\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-b86d0fb2e3790f63b5c6590884be9401354.png)\n\nǵǰʹõ `applicationContext` Ϊ `AnnotationConfigServletWebServerApplicationContext`ҲʹЩչ㣬ҪעЩչӦá\n\n##### 1\\. ǰ׼`prepareRefresh()`\n\nԷ֣`initPropertySources()` е£\n\n```\nAbstractApplicationContext#refresh\n |- AnnotationConfigServletWebServerApplicationContext#prepareRefresh\n  |- AbstractApplicationContext#prepareRefresh\n   |- GenericWebApplicationContext#initPropertySources\n\n```\n\nյõ `GenericWebApplicationContext#initPropertySources`\n\n```\nprotected void initPropertySources() {\n    ConfigurableEnvironment env = getEnvironment();\n    if (env instanceof ConfigurableWebEnvironment) {\n        ((ConfigurableWebEnvironment) env).initPropertySources(this.servletContext, null);\n    }\n}\n\n```\n\nȻȡ `Environment`ȻжǷΪ `ConfigurableWebEnvironment` ʵǰ**׼ʱ**ʱǵõ `Environment` Ϊ `StandardServletEnvironment` `ConfigurableWebEnvironment` ķϣȻ `ConfigurableWebEnvironment#initPropertySources`  `StandardServletEnvironment#initPropertySources`\n\n```\npublic void initPropertySources(@Nullable ServletContext servletContext, \n        @Nullable ServletConfigservletConfig) {\n    // 滻õ servletContextInitParams Ϊ servletContext\n    // 滻õ servletConfigInitParams Ϊ servletConfig\n    WebApplicationContextUtils.initServletPropertySources(getPropertySources(), \n        servletContext, servletConfig);\n}\n\n```\n\nǺܼ򵥣ֻǽ `servletContext`  `servletConfig` õ `Environment` С\n\n##### 2\\. ȡ `beanFactory`: `obtainFreshBeanFactory()`\n\nǰ `applicationContext` Ը÷չ\n\n##### 3\\. ׼ `beanFactory`: `prepareBeanFactory(beanFactory)`\n\nǰ `applicationContext` Ը÷չ\n\n##### 4\\. չ㣺`postProcessBeanFactory(beanFactory)`\n\n`AnnotationConfigServletWebServerApplicationContext` д\n\n```\n@Override\nprotected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {\n    // øķ\n    super.postProcessBeanFactory(beanFactory);\n    // аɨ裬İ\n    if (this.basePackages != null && this.basePackages.length > 0) {\n        this.scanner.scan(this.basePackages);\n    }\n    // עbeanΪ\n    if (!this.annotatedClasses.isEmpty()) {\n        this.reader.register(ClassUtils.toClassArray(this.annotatedClasses));\n    }\n}\n\n```\n\nִй£\n\n1.  ˸ķ `super.postProcessBeanFactory(beanFactory)`\n2.  аɨ裬ͨԷ֣ `basePackages` Ϊ nul\n3.  ע `annotatedClasses` `annotatedClasses` Ϊ\n\nҪ `super.postProcessBeanFactory(beanFactory)`÷ `ServletWebServerApplicationContext` У\n\n```\n@Override\nprotected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {\n    // һ BeanPostProcessor\n    beanFactory.addBeanPostProcessor(\n            new WebApplicationContextServletContextAwareProcessor(this));\n    //  ServletContextAware Զע\n    beanFactory.ignoreDependencyInterface(ServletContextAware.class);\n    // ע web bean ķΧעrequestsessionglobalSession\n    registerWebApplicationScopes();\n}\n\n```\n\nݱȽϼ򵥣Ҫע `BeanPostProcessor` Լע `web bean` ÷ΧҪ `WebApplicationContextServletContextAwareProcessor` ã£\n\n```\npublic class WebApplicationContextServletContextAwareProcessor \n        extends ServletContextAwareProcessor {\n\n    private final ConfigurableWebApplicationContext webApplicationContext;\n\n    public WebApplicationContextServletContextAwareProcessor(\n            ConfigurableWebApplicationContext webApplicationContext) {\n        Assert.notNull(webApplicationContext, \"WebApplicationContext must not be null\");\n        this.webApplicationContext = webApplicationContext;\n    }\n\n    /**\n     * ȡ ServletContext\n     */\n    @Override\n    protected ServletContext getServletContext() {\n        ServletContext servletContext = this.webApplicationContext.getServletContext();\n        return (servletContext != null) ? servletContext : super.getServletContext();\n    }\n\n    /**\n     * ȡ ServletConfig\n     */\n    @Override\n    protected ServletConfig getServletConfig() {\n        ServletConfig servletConfig = this.webApplicationContext.getServletConfig();\n        return (servletConfig != null) ? servletConfig : super.getServletConfig();\n    }\n\n}\n\n```\n\nƺûʲôٸ࣬Ǹ `BeanPostProcessor`Ҫע `postProcessBeforeInitialization()`  `postProcessAfterInitialization()` \n\n```\npublic class ServletContextAwareProcessor implements BeanPostProcessor {\n\n    ...\n\n    public Object postProcessBeforeInitialization(Object bean, \n            String beanName) throws BeansException {\n        //  ServletContext\n        if (getServletContext() != null && bean instanceof ServletContextAware) {\n            ((ServletContextAware) bean).setServletContext(getServletContext());\n        }\n        //  ServletConfig\n        if (getServletConfig() != null && bean instanceof ServletConfigAware) {\n            ((ServletConfigAware) bean).setServletConfig(getServletConfig());\n        }\n        return bean;\n    }\n\n    @Override\n    public Object postProcessAfterInitialization(Object bean, String beanName) {\n        return bean;\n    }\n\n}\n\n```\n\nԿ `BeanPostProcessor`  `ServletContextAware`  `ServletConfigAware`  `Aware` ӿڵģ·ͬ `ApplicationAware``BeanFactoryAware` һ\n\n##### 5\\. ִ `BeanFactoryPostProcessors`: `invokeBeanFactoryPostProcessors(beanFactory)`\n\nǰ `applicationContext` Ը÷չ\n\nֵһǣУиص `BeanFactoryPostProcessor` ᱻִУ`ConfigurationClassPostProcessor`springboot Զװע `@EnableAutoConfiguration` ﴦԶװļءעҲ `ConfigurationClassPostProcessor` С\n\n##### 6\\. ע `BeanPostProcessor`: `registerBeanPostProcessors(beanFactory)`\n\nǰ `applicationContext` Ը÷չ\n\n##### 7\\. ʼ` MessageSource`(ڹʻ): `initMessageSource()`\n\nǰ `applicationContext` Ը÷չ\n\n##### 8\\. ʼ¼㲥`initApplicationEventMulticaster()`\n\nǰ `applicationContext` Ը÷չ\n\n##### 9\\. չ㣺`onRefresh()`\n\nǰ `applicationContext` Ը÷չΪ `ServletWebServerApplicationContext#onRefresh` £\n\n```\n@Override\nprotected void onRefresh() {\n    // ø෽\n    super.onRefresh();\n    try {\n        // webtomcat,jetty\n        createWebServer();\n    }\n    catch (Throwable ex) {\n        throw new ApplicationContextException(...);\n    }\n}\n\n```\n\n web дġ web Ĵ򵥣ҪжϣǺϸ˵\n\n##### 10\\. ע¼`registerListeners()`\n\nǰ `applicationContext` Ը÷չ\n\n##### 11\\. ʼ `bean`: `finishBeanFactoryInitialization(beanFactory)`\n\nǰ `applicationContext` Ը÷չ\n\n##### 12\\. : `finishRefresh()`\n\nǰ `applicationContext` Ը÷չΪ `ServletWebServerApplicationContext#finishRefresh` £\n\n```\n@Override\nprotected void finishRefresh() {\n    super.finishRefresh();\n    // web\n    WebServer webServer = startWebServer();\n    if (webServer != null) {\n        //  ServletWebServerInitializedEvent ¼\n        publishEvent(new ServletWebServerInitializedEvent(webServer, this));\n    }\n}\n\n/**\n * web\n */\nprivate WebServer startWebServer() {\n    WebServer webServer = this.webServer;\n    if (webServer != null) {\n        webServer.start();\n    }\n    return webServer;\n}\n\n```\n\nԿ web \n\n##### 13\\. : `resetCommonCaches()`\n\nǰ `applicationContext` Ը÷չ\n\n#### 3.10.2 ע `ShutdownHook`\n\n `context.registerShutdownHook()`÷ `AbstractApplicationContext#registerShutdownHook` ṩ\n\n```\npublic abstract class AbstractApplicationContext extends DefaultResourceLoader\n        implements ConfigurableApplicationContext {\n    ...\n    @Override\n    public void registerShutdownHook() {\n        if (this.shutdownHook == null) {\n            // ̵ָ߳\n            this.shutdownHook = new Thread(SHUTDOWN_HOOK_THREAD_NAME) {\n                @Override\n                public void run() {\n                    synchronized (startupShutdownMonitor) {\n                        //  ShutdownHook \n                        doClose();\n                    }\n                }\n            };\n            Runtime.getRuntime().addShutdownHook(this.shutdownHook);\n        }\n    }\n\n    /**\n     * Ĺرղ\n     */\n    protected void doClose() {\n        // Check whether an actual close attempt is necessary...\n        if (this.active.get() && this.closed.compareAndSet(false, true)) {\n            LiveBeansView.unregisterApplicationContext(this);\n\n            try {\n                // ر¼\n                publishEvent(new ContextClosedEvent(this));\n            }\n            catch (Throwable ex) {\n                logger.warn(...);\n            }\n\n            //  lifecycle  onClose() \n            if (this.lifecycleProcessor != null) {\n                try {\n                    this.lifecycleProcessor.onClose();\n                }\n                catch (Throwable ex) {\n                    logger.warn(...);\n                }\n            }\n\n            //  bean\n            destroyBeans();\n\n            // ر\n            closeBeanFactory();\n\n            // չ㣬ʵ\n            onClose();\n\n            // \n            if (this.earlyApplicationListeners != null) {\n                this.applicationListeners.clear();\n                this.applicationListeners.addAll(this.earlyApplicationListeners);\n            }\n\n            //  active ʶ\n            this.active.set(false);\n        }\n    }\n\n    ...\n\n}\n\n```\n\nԿ`context.registerShutdownHook()` ʵ `doClose()` Ĺرղر spring ĹرգעѾ൱ˣͲˡ\n\nˣͷˣ spring չ `onRefresh()`  `finishRefresh()`ǰߴ `webServer`  `webServer` \n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-81033ec78641ad875623cf452ef9cd62eb6.png)\n\n* * *\n\n_ԭӣ[https://my.oschina.net/funcy/blog/4888129](https://my.oschina.net/funcy/blog/4888129) ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע_"
  },
  {
    "path": "docs/Spring全家桶/SpringBoot源码解析/SpringBoot自动装配（一）：加载自动装配类.md",
    "content": "Զװ springboot ĺ֮һĽ̽ springboot μԶװġ\n\n [@SpringBootApplication ע](https://my.oschina.net/funcy/blog/4870882)һУᵽ springboot Զװע `@EnableAutoConfiguration`£\n\n```\n@Target(ElementType.TYPE)\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\n@Inherited\n// Զװİ\n@AutoConfigurationPackage\n// Զװ\n@Import(AutoConfigurationImportSelector.class)\npublic @interface EnableAutoConfiguration {\n\n    String ENABLED_OVERRIDE_PROPERTY = \"spring.boot.enableautoconfiguration\";\n\n    /**\n     * жųԶװ\n     */\n    Class<?>[] exclude() default {};\n\n    /**\n     * жųԶװ\n     */\n    String[] excludeName() default {};\n\n}\n\n```\n\nϴ֣\n\n1.  `@AutoConfigurationPackage`ָԶװİ\n2.  `@Import(AutoConfigurationImportSelector.class)`ԶװĴ `AutoConfigurationImportSelector`ԶװĹؼڣ\n3.  `@EnableAutoConfiguration` ԣ`@EnableAutoConfiguration` ṩԣ`exclude`  `excludeName`ųҪԶװࡣ\n\nص `AutoConfigurationImportSelector` ࡣ\n\n### 1. `AutoConfigurationImportSelector.AutoConfigurationGroup`\n\n`AutoConfigurationImportSelector` ʵ `DeferredImportSelector` `DeferredImportSelector` ķԲο [ConfigurationClassPostProcessor ֮ @Import ע](https://my.oschina.net/funcy/blog/4678152)ֱӸۣ\n\n* `DeferredImportSelector`  `ImportSelector` ӽӿڣڲһӿ `Group`ýӿڶ\n\n  ```\n  public interface DeferredImportSelector extends ImportSelector {\n      ...\n  \n      interface Group {\n  \n          /**\n           * \n           */\n          void process(AnnotationMetadata metadata, DeferredImportSelector selector);\n  \n          /**\n           * ص\n           */\n          Iterable<Entry> selectImports()\n      }\n  }\n  \n  ```\n\n  ڴ `DeferredImportSelector` ĵʱ`DeferredImportSelector.Group#process` ȵãȻٵ `DeferredImportSelector.Group#selectImports` صࣻ\n\n* `DeferredImportSelector` ָķ飬ڴʱ԰鴦ࣻ\n\n* `DeferredImportSelector` ڴʱȽఴһ `map` Уڴࣨspring Ϊ `@Component``@ComponentScan``@Import``@Configuration``@Bean` ǵࣩеĵ࣬Ҳ˵`DeferredImportSelector` ࣬עᵽ `beanFactory` кٽעᣨעǰжܷעᵽ `beanFactory`ܲעᣩ\n\n `AutoConfigurationImportSelector` Ĵ룺\n\n```\n// ʵ DeferredImportSelector\npublic class AutoConfigurationImportSelector implements DeferredImportSelector, \n        BeanClassLoaderAware,ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {\n\n    ...\n\n    /**\n     * ʵ DeferredImportSelector.Group\n     */\n    private static class AutoConfigurationGroup implements DeferredImportSelector.Group, \n            BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware {\n\n        /**\n         * 浼\n         */\n        private final List<AutoConfigurationEntry> autoConfigurationEntries = new ArrayList<>();\n\n        /**\n         * \n         */\n        @Override\n        public void process(AnnotationMetadata annotationMetadata, \n                DeferredImportSelector deferredImportSelector) {\n            Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,\n                    () -> String.format(\"Only %s implementations are supported, got %s\",\n                            AutoConfigurationImportSelector.class.getSimpleName(),\n                            deferredImportSelector.getClass().getName()));\n            // 1\\.  AutoConfigurationImportSelector#getAutoConfigurationEntry(...) \n            // Զװ\n            AutoConfigurationEntry autoConfigurationEntry = \n                ((AutoConfigurationImportSelector) deferredImportSelector)\n                    .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);\n            // 2\\. ȡ autoConfigurationEntry \n            this.autoConfigurationEntries.add(autoConfigurationEntry);\n            for (String importClassName : autoConfigurationEntry.getConfigurations()) {\n                this.entries.putIfAbsent(importClassName, annotationMetadata);\n            }\n        }\n\n        /**\n         * ص\n         */\n        @Override\n        public Iterable<Entry> selectImports() {\n            if (this.autoConfigurationEntries.isEmpty()) {\n                return Collections.emptyList();\n            }\n            // 3\\. õ\n            Set<String> allExclusions = this.autoConfigurationEntries.stream()\n                    .map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream)\n                    .collect(Collectors.toSet());\n            // 4\\.  autoConfigurationEntries תΪ LinkedHashSet\n            Set<String> processedConfigurations = this.autoConfigurationEntries.stream()\n                    .map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream)\n                    .collect(Collectors.toCollection(LinkedHashSet::new));\n            // 5\\. ȥҪ˵\n            processedConfigurations.removeAll(allExclusions);\n            // 6\\. \n            return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata())\n                    .stream().map((importClassName) -> new Entry(\n                        this.entries.get(importClassName), importClassName))\n                    .collect(Collectors.toList());\n        }\n\n        ...\n    }\n\n}\n\n```\n\nǽ `DeferredImportSelector.Group#process`  `DeferredImportSelector.Group#selectImports` ܽ:\n\n1.   `AutoConfigurationImportSelector#getAutoConfigurationEntry(...)` Զװࣻ\n2.  õԶװౣ浽 `autoConfigurationEntries` У\n3.  õ࣬Щ `@EnableAutoConfiguration`  `exclude`  `excludeName` ָģ\n4.   `autoConfigurationEntries` תΪ `LinkedHashSet`Ϊ `processedConfigurations`\n5.  ȥ `processedConfigurations` Ҫ˵ࣻ\n6.   5 õ󣬷ء\n\nǶЩؼз\n\n> ر˵`DeferredImportSelector`  `ImportSelector` ӽӿڣ`ImportSelector` ķ `selectImports(...)` `DeferredImportSelector` Ҳд˸÷\n>\n> ![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-75a2839c622af2d0f374189b2e2765a64d7.png)\n>\n> ҲǼԶװ࣬յ࣬Ҫעǣspringboot Զ****ﴦģ㣬ڷڴϵ㣬Ȼͻᷢûе\n>\n> £springboot Զ**** `AutoConfigurationImportSelector#selectImports` дģ `AutoConfigurationImportSelector.AutoConfigurationGroup#selectImports` дġ\n\n### 2\\. ȡװࣺ`AutoConfigurationImportSelector#getAutoConfigurationEntry`\n\nԶ ļشΪ\n\n```\nAutoConfigurationEntry autoConfigurationEntry = \n    ((AutoConfigurationImportSelector) deferredImportSelector)\n        .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);\n\n```\n\nôԶװģֱӽ `AutoConfigurationImportSelector#getAutoConfigurationEntry` \n\n```\nprotected AutoConfigurationEntry getAutoConfigurationEntry(\n        AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {\n    // һжǷԶװ\n    if (!isEnabled(annotationMetadata)) {\n        return EMPTY_ENTRY;\n    }\n    // ȡע\n    AnnotationAttributes attributes = getAttributes(annotationMetadata);\n    // 1\\. غѡԶ\n    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);\n    // 2\\. ȥأתsetתlist\n    configurations = removeDuplicates(configurations);\n    // 3\\. ȥҪų࣬ʵǴ@EnableAutoConfigurationexcludeexcludeName\n    Set<String> exclusions = getExclusions(annotationMetadata, attributes);\n    checkExcludedClasses(configurations, exclusions);\n    configurations.removeAll(exclusions);\n    // 4\\. ˲ҪԶװ\n    configurations = filter(configurations, autoConfigurationMetadata);\n    // 5\\.  AutoConfigurationImportEvent ¼\n    fireAutoConfigurationImportEvents(configurations, exclusions);\n    // 6\\. շصֵ\n    return new AutoConfigurationEntry(configurations, exclusions);\n}\n\n```\n\nǳҪ˻ȡԶװȫò£\n\n1. غѡԶװ࣬springboot Զװλ `classpath` µ `META-INF/spring.factories` ļУkey Ϊ `org.springframework.boot.autoconfigure.EnableAutoConfiguration`Ǻϸ\n\n2. ȥظԶװ࣬һصõԶװܻظȥظ࣬ȥʽҲǳ򵥣springboot ֻת `Set`ת `List`\n\n3. ȥų࣬ǰᵽ `@EnableAutoConfiguration` ͨ `exclude`  `excludeName` ָҪų࣬һԵģ\n\n4. ˲ҪԶװ࣬ݱ˵ԣֲûɹˣ\n\n   ǰ 124 \n\n   ![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-773695c0161c8126f239c2e66529fc8a394.png)\n\n   ˺ 124 \n\n   ![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-7144d971f729b5dfbddfeff2d3319c7705c.png)\n\n5.  `AutoConfigurationImportEvent` ¼\n\n6.  3 õų 4 õԶװװ `AutoConfigurationEntry` ء\n\nעһд룺\n\n```\n// 6\\. շصֵ\nreturn new AutoConfigurationEntry(configurations, exclusions);\n\n```\n\n `configurations`  `exclusions`  `AutoConfigurationEntry` Ĺ췽 `AutoConfigurationEntry`\n\n```\nprotected static class AutoConfigurationEntry {\n    // Զװ\n    private final List<String> configurations;\n\n    // ҪųԶװ\n    private final Set<String> exclusions;\n\n    /**\n     * 췽߽иֵ\n     */\n    AutoConfigurationEntry(Collection<String> configurations, Collection<String> exclusions) {\n        this.configurations = new ArrayList<>(configurations);\n        this.exclusions = new HashSet<>(exclusions);\n    }\n\n    ...\n}\n\n```\n\nЩɼշص `AutoConfigurationEntry` ݣ\n\n*   `configurations`Զװ࣬ѾȥҪų\n*   `exclusions`ͨ `@EnableAutoConfiguration` ָҪų\n\nԶװĻȡˣغѡԶװ̡\n\n### 3\\. غѡԶװ\n\nԶװļλ `AutoConfigurationImportSelector#getCandidateConfigurations`£\n\n```\nprotected List<String> getCandidateConfigurations(AnnotationMetadata metadata, \n        AnnotationAttributes attributes) {\n    // õ spring ṩķSpringFactoriesLoader.loadFactoryNames(...)\n    // getSpringFactoriesLoaderFactoryClass() صEnableAutoConfiguration\n    List<String> configurations = SpringFactoriesLoader\n            .loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());\n    Assert.notEmpty(configurations, \"...\");\n    return configurations;\n}\n\nprotected Class<?> getSpringFactoriesLoaderFactoryClass() {\n    return EnableAutoConfiguration.class;\n}\n\n```\n\n `SpringFactoriesLoader#loadFactoryNames`\n\n```\npublic final class SpringFactoriesLoader {\n\n    public static final String FACTORIES_RESOURCE_LOCATION = \"META-INF/spring.factories\";\n\n    public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {\n        // õ factoryTypeName  org.springframework.boot.autoconfigure.EnableAutoConfiguration\n        String factoryTypeName = factoryType.getName();\n        return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());\n    }\n\n    /**\n     * мأص META-INF/spring.factories е\n     */\n    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {\n        MultiValueMap<String, String> result = cache.get(classLoader);\n        if (result != null) {\n            return result;\n        }\n\n        try {\n            //  META-INF/spring.factories \n            Enumeration<URL> urls = (classLoader != null ?\n                    classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :\n                    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));\n            result = new LinkedMultiValueMap<>();\n            while (urls.hasMoreElements()) {\n                URL url = urls.nextElement();\n                UrlResource resource = new UrlResource(url);\n                //  META-INF/spring.factories תΪ Properties \n                Properties properties = PropertiesLoaderUtils.loadProperties(resource);\n                for (Map.Entry<?, ?> entry : properties.entrySet()) {\n                    String factoryTypeName = ((String) entry.getKey()).trim();\n                    // StringUtils.commaDelimitedListToStringArray(...) ŷָΪ\n                    for (String factoryImplementationName : \n                                StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {\n                        result.add(factoryTypeName, factoryImplementationName.trim());\n                    }\n                }\n            }\n            cache.put(classLoader, result);\n            return result;\n        }\n        catch (IOException ex) {\n            throw new IllegalArgumentException(\"Unable to load factories from location [\" +\n                    FACTORIES_RESOURCE_LOCATION + \"]\", ex);\n        }\n    }\n    ...\n}\n\n```\n\nԿص `classpath` µ `META-INF/spring.factories` ļע⣺ļܻжλڲͬ jar С\n\nspringboot Դ `META-INF/spring.factories` λ `spring-boot-autoconfigure` ģ£\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-ace5e83645626966eae1e62a50752f2417d.png)\n\nһ `spring.factories`\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-d7de9ecd19345f0dc77cc304843c588fe4d.png)\n\nļ࣬ `key-value` ʽ棬ֵ֮ʹ , ֿᵽԶװ key  `org.springframework.boot.autoconfigure.EnableAutoConfiguration`Ӧ `value` ǳ࣬Ͳչʾˡ\n\nһ֮Զװͱעᵽ spring ˡעʱص spring еĻ `BeanDefinition`ҪΪ spring beanþ `ConditionalOnBean``ConditionalOnClass` עĿ飬ЩǺٷ\n\n### 4\\. ȡԶװĴ\n\nٻص `AutoConfigurationImportSelector.AutoConfigurationGroup`ڵ 1 ܽ£\n\n1.   `AutoConfigurationImportSelector#getAutoConfigurationEntry(...)` Զװࣻ\n2.  õԶװౣ浽 `autoConfigurationEntries` У\n3.  õ࣬Щ `@EnableAutoConfiguration`  `exclude`  `excludeName` ָģ\n4.   `autoConfigurationEntries` תΪ `LinkedHashSet`Ϊ `processedConfigurations`\n5.  ȥ `processedConfigurations` Ҫ˵ࣻ\n6.   5 õ󣬷ء\n\nϵ 2  3 ڣԶļع̣Ĳ衣\n\nŴ룬ǻֽᷢĲ趼Ƚϼ򵥣Ҳһ˵°ɡ\n\n*    2 õԶװֻ࣬ǵ `List#add(...)` õ `autoConfigurationEntry` 浽 `autoConfigurationEntries`ṹ `AutoConfigurationGroup` ĳԱ `AutoConfigurationImportSelector.AutoConfigurationGroup#selectImports` лõ\n\n*    3 õеĹ࣬ùǱ `autoConfigurationEntries`Ȼͨ `autoConfigurationEntry#getExclusions` õ ǰҲᵽ`autoConfigurationEntry` ֻԱ`configurations`(ȥųԶװ)  `exclusions`(ͨ `@EnableAutoConfiguration` ָų)\n\n*    4  `List` תΪ `LinkedHashSet`\n\n*    5 еԶװٽһȥųĲųĶеų࣬ӦǻͬһĿж `@EnableAutoConfiguration` һ `@EnableAutoConfiguration` עų `A``B` ࣬ڶ `@EnableAutoConfiguration` עų `C``D` ࣬ų `A``B``C``D` ĸࣻ\n\n*    6 һҪ˳Զװעᵽ `beanFactory` е˳`AutoConfigureOrder``@AutoConfigureAfter`  `@AutoConfigureBefore` ﴦģݣԲο [springboot Զװ֮Զװ˳](https://my.oschina.net/funcy/blog/4921594).\n\nЩԶװĻȡˡ\n\n### 5\\. ԶԶװ\n\n˽Զװļع̺ҲԶһԶװࡣ\n\n1.  ׼һԶװ\n\n```\n@Configuration\npublic class MyAutoConfiguration {\n\n    @Bean\n    public Object object() {\n        System.out.println(\"create object\");\n        return new Object();\n    }\n}\n\n```\n\nܼ򵥣һ `@Configuration` ࣬ʹ `@Bean` עⴴһ beanڴ bean Ĺл ӡ \"create object\"\n\n1.  ׼ `META-INF/spring.factories` £\n\n```\n# Auto Configure\norg.springframework.boot.autoconfigure.EnableAutoConfiguration=\\\norg.springframework.boot.learn.autoconfigure.demo01.configure.MyAutoConfiguration\n\n```\n\n1.  \n\n```\n@SpringBootApplication\npublic class AutoconfigureDemo01Application {\n\n    public static void main(String[] args) {\n        SpringApplication.run(AutoconfigureDemo01Application.class, args);\n    }\n\n}\n\n```\n\nн£\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-9337a7ac4ce4ff7d71e69bc952bfac30b12.png)\n\nԿ`create object` ɹӡˡ\n\n `bean` ͨɨ贴ģԶװ䵼أͨԵķʽԶװõࣺ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-27bea49ddeae2f0f720ea338914cc443aec.png)\n\nԿ`MyAutoConfiguration` Զװбˡ\n\nע⵽`MyAutoConfiguration`  `@Configuration` ע⣬ ô sping ɨ赽ģԶװõأ\n\n[springboot Դ@SpringBootApplication ע](https://my.oschina.net/funcy/blog/4870882)һУᵽ `SpringBootApplication` עе `@ComponentScan` ָһ`AutoConfigurationExcludeFilter`Զװ࣬ǿĿǰΪֹ `beanFactory` Щ `beanName`\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-1725008451f5516ab540bcc3ae13d1f37ee.png)\n\nԿû `MyAutoConfiguration`˴ʱûɨ `beanFactory` С\n\nȻҲ԰ `MyAutoConfiguration`  `@Configuration` עȥͲˡ\n\n### 6\\. ܽ\n\nĴ `@EnableAutoConfiguration` עԶװļ̣ `AutoConfigurationImportSelector#getAutoConfigurationEntry` Уռص `META-INF/spring.factories` ļ key  `org.springframework.boot.autoconfigure.EnableAutoConfiguration` ࡣ\n\nõԶװspring ὫעᵽУʱǻһ `BeanDefinition`ҪΪ spring beanþ `ConditionalOnBean``ConditionalOnClass` עĿ飬ЩǺٷ\n\n* * *\n\n_ԭӣ[https://my.oschina.net/funcy/blog/4870868](https://my.oschina.net/funcy/blog/4870868) ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע_"
  },
  {
    "path": "docs/Spring全家桶/SpringBoot源码解析/SpringBoot自动装配（三）：自动装配顺序.md",
    "content": " [springboot Զװ֮ע⣨һ](https://my.oschina.net/funcy/blog/4918863)һУڷ `@ConditionalOnBean/@ConditionalOnMissingBean` עжʱٷǿҽԶװʹע⣬ `@ConditionalOnBean/@ConditionalOnMissingBean` ǵҪָ֮ʼ springboot Զװ˳أĽо¡\n\n### 1\\. springboot ԶװĹ\n\nҪȷǣֵ̽`Զװ˳`ָ `class` עᵽ `beanFactory` ˳springboot ԶװĴ¹£\n\n1.  Զװ࣬ [springboot Զװ֮Զװ](https://my.oschina.net/funcy/blog/4870868) һѷ\n2.  ԶװǱĽҪݣ\n3.  Զװ࣬ÿԶװһ²\n    1.  עжϵǰԶװǷװ\n    2.  ǰԶװװעᵽ `beanFactory` С\n\nٻص `@ConditionalOnBean/@ConditionalOnMissingBean`Զװࣺ\n\n```\n// AԶװ\n@Configuration\npublic class A {\n    @Bean\n    @ConditionalOnMissingBean(\"b1\")\n    public A1 a1() {\n        return new A1();\n    }\n}\n\n// BԶװ\n@Configuration\npublic class B {\n    @Bean\n    public B1 b1() {\n        return new b1();\n    }\n}\n\n```\n\n`a1`  `b1` ͬԶװгʼ `a1` ֻ `b1` ʱŻʼܽ springboot ԶװĲ裬ֻҪָ `b1`  `a1` ֮ǰʼͲ쳣ˡ\n\nôԶװ˳ָأ\n\n### 2\\. Զװ˳ע\n\nspringboot ΪṩԶװֶΣ\n\n*   Զװ˳ `@AutoConfigOrder`\n*   Զװ˳ `@AutoConfigureBefore`  `@AutoConfigureAfter`\n\nעԶװˣ`@AutoConfigOrder` ָװ˳ͬ spring ṩ `@Order` ƣ`@AutoConfigureBefore`  `@AutoConfigureAfter` ָ `class`ʾĸ `class` ֮ǰ֮װ䡣\n\nصʾǿָװ˳\n\n```\n// AԶװ\n@Configuration\n// B.class֮Զװ\n@AutoConfigureAfter(B.class)\npublic class A {\n    @Bean\n    @ConditionalOnMissingBean(\"b1\")\n    public A1 a1() {\n    ...\n    }\n}\n\n// BԶװ\n@Configuration\npublic class B {\n    ...\n}\n\n```\n\n### 3\\. Զװ\n\nǰᵽ`@AutoConfigOrder``@AutoConfigureBefore`  `@AutoConfigureAfter` ԿԶװװ˳ôأ [springboot Զװ֮Զװ](https://my.oschina.net/funcy/blog/4870868) һУܽ˻ȡԶװĲ 6 \n\n1.   `AutoConfigurationImportSelector#getAutoConfigurationEntry(...)` Զװࣻ\n2.  õԶװౣ浽 `autoConfigurationEntries` У\n3.  õ࣬Щ `@EnableAutoConfiguration`  `exclude`  `excludeName` ָģ\n4.   `autoConfigurationEntries` תΪ `LinkedHashSet`Ϊ `processedConfigurations`\n5.  ȥ `processedConfigurations` Ҫ˵ࣻ\n6.   5 õ󣬷ء\n\nԶװڵ 6 Ӧķ `AutoConfigurationImportSelector.AutoConfigurationGroup#sortAutoConfigurations`£\n\n```\nprivate List<String> sortAutoConfigurations(Set<String> configurations,\n        AutoConfigurationMetadata autoConfigurationMetadata) {\n    // ȴ AutoConfigurationSorter \n    // Ȼ AutoConfigurationSorter.getInPriorityOrder \n    return new AutoConfigurationSorter(getMetadataReaderFactory(), autoConfigurationMetadata)\n            .getInPriorityOrder(configurations);\n}\n\n```\n\nĴΪ\n\n1.   `AutoConfigurationSorter` \n2.   `AutoConfigurationSorter.getInPriorityOrder` \n\n `AutoConfigurationSorter` Ĵ\n\n```\nclass AutoConfigurationSorter {\n\n    private final MetadataReaderFactory metadataReaderFactory;\n\n    private final AutoConfigurationMetadata autoConfigurationMetadata;\n\n    /**\n     * 췽\n     * ֻǶԴĲиֵǸֵΪԱ\n     */\n    AutoConfigurationSorter(MetadataReaderFactory metadataReaderFactory,\n            AutoConfigurationMetadata autoConfigurationMetadata) {\n        Assert.notNull(metadataReaderFactory, \"MetadataReaderFactory must not be null\");\n        this.metadataReaderFactory = metadataReaderFactory;\n        this.autoConfigurationMetadata = autoConfigurationMetadata;\n    }\n\n    ...\n\n}\n\n```\n\nԿ`AutoConfigurationSorter` Ĺ췽ûʲôʵԵĲĹؼÿ `AutoConfigurationSorter.getInPriorityOrder` ÷Ĵ£\n\n```\nList<String> getInPriorityOrder(Collection<String> classNames) {\n    // 1\\.  classNames װ AutoConfigurationClasses\n    AutoConfigurationClasses classes = new AutoConfigurationClasses(this.metadataReaderFactory,\n            this.autoConfigurationMetadata, classNames);\n    List<String> orderedClassNames = new ArrayList<>(classNames);\n    // 2\\. \n    Collections.sort(orderedClassNames);\n    // 3\\. ʹ @AutoConfigureOrder \n    orderedClassNames.sort((o1, o2) -> {\n        int i1 = classes.get(o1).getOrder();\n        int i2 = classes.get(o2).getOrder();\n        return Integer.compare(i1, i2);\n    });\n    // 4\\. ʹ @AutoConfigureBefore@AutoConfigureAfter \n    orderedClassNames = sortByAnnotation(classes, orderedClassNames);\n    return orderedClassNames;\n}\n\n```\n\nӴ ִв£\n\n1.   `classNames` װ `AutoConfigurationClasses`\n2.  \n3.  ʹ `@AutoConfigureOrder` \n4.  ʹ `@AutoConfigureBefore``@AutoConfigureAfter` \n\n򹲽 3 ΣǶ `orderedClassNames` һǰȵǰҲ˵ûָ `@AutoConfigureOrder``@AutoConfigureBefore` ע⣬ͻʹ\n\nǾ⼸ɡ\n\n### 4\\.  `classNames` װ `AutoConfigurationClasses`\n\nòλ `AutoConfigurationSorter.AutoConfigurationClasses#AutoConfigurationClasses` £\n\n```\nprivate static class AutoConfigurationClasses {\n\n    // \n    private final Map<String, AutoConfigurationClass> classes = new HashMap<>();\n\n    /**\n     * 췽\n     */\n    AutoConfigurationClasses(MetadataReaderFactory metadataReaderFactory,\n            AutoConfigurationMetadata autoConfigurationMetadata, Collection<String> classNames) {\n        // з\n        addToClasses(metadataReaderFactory, autoConfigurationMetadata, classNames, true);\n    }\n\n    /**\n     * ࣬ǽװ AutoConfigurationClassӵΪ classes  Map \n     * classNames ȥųԶװ\n     */\n    private void addToClasses(MetadataReaderFactory metadataReaderFactory,\n            AutoConfigurationMetadata autoConfigurationMetadata, Collection<String> classNames, \n            boolean required) {\n        for (String className : classNames) {\n            if (!this.classes.containsKey(className)) {\n                //  className װ AutoConfigurationClass\n                AutoConfigurationClass autoConfigurationClass = new AutoConfigurationClass(\n                        className, metadataReaderFactory, autoConfigurationMetadata);\n                boolean available = autoConfigurationClass.isAvailable();\n                // @AutoConfigureBefore  @AutoConfigureAfter ǵ required Ϊ false\n                if (required || available) {\n                    this.classes.put(className, autoConfigurationClass);\n                }\n                if (available) {\n                    // ݹ\n                    addToClasses(metadataReaderFactory, autoConfigurationMetadata,\n                            autoConfigurationClass.getBefore(), false);\n                    addToClasses(metadataReaderFactory, autoConfigurationMetadata,\n                            autoConfigurationClass.getAfter(), false);\n                }\n            }\n        }\n    }\n    ...\n}\n\n```\n\nϴ\n\n*   `AutoConfigurationClasses` һԱ`classes` `Map``key`  `String`Ҳ `className``value`  `AutoConfigurationClass`Ҳ `className` İࣩ;\n*   `AutoConfigurationClasses` Ĺ췽 `addToClasses(...)` ÷ `classNames`װ `AutoConfigurationClass` ٱ浽 `classes` С\n\nڷ `addToClasses(...)` ľ߼ǰ `AutoConfigurationClass` Ǹɶ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-fd9c1a4c335951391a33f86a86fe89ff496.png)\n\nԿ `AutoConfigurationClass` İװһ `@AutoConfigureBefore`  `@AutoConfigureAfter` ָ࣬Լṩ˸ `@AutoConfigureOrder` `@AutoConfigureBefore``@AutoConfigureAfter` صһЩ\n\nٻعͷ `addToClasses(...)` ִ̣÷ִ£\n\n1.   `classNames`ÿһ `className`Ĳ\n2.   `AutoConfigurationClass` `className`\n3.   `AutoConfigurationSorter.AutoConfigurationClass#isAvailable` õ `available`\n4.  ж `available`  `required` ֵһΪ tureͽӵ `classes`\n5.   `available` Ϊ `true`ݹ鴦 `className`  `@AutoConfigureBefore`  `@AutoConfigureAfter` ָࡣ\n\n̿ŲӣмȥҪ \n\n1.  `AutoConfigurationSorter.AutoConfigurationClass#isAvailable`жϵǰ `class` Ƿ\n2.  `AutoConfigurationSorter.AutoConfigurationClass#getBefore`ȡ `class`ǰ `class` ҪЩ `class` ֮ǰ\n3.  `AutoConfigurationSorter.AutoConfigurationClass#getAfter`ȡ `class`ǰ `class` ҪЩ `class` ֮\n\nһһ⼸\n\n#### 4.1 `AutoConfigurationSorter.AutoConfigurationClass#isAvailable`\n\nжϵǰ `class` ǷڵǰĿ `classpath` У룺\n\n```\nboolean isAvailable() {\n    try {\n        if (!wasProcessed()) {\n            getAnnotationMetadata();\n        }\n        return true;\n    }\n    catch (Exception ex) {\n        return false;\n    }\n}\n\n```\n\n벻࣬ǵ `wasProcessed()` ٵ `getAnnotationMetadata()`Ҫעǣ`getAnnotationMetadata()` ܻ׳쳣Ƹ쳣Ҳ᷵ `false`.\n\nǼ `AutoConfigurationSorter.AutoConfigurationClass#wasProcessed` \n\n```\nprivate boolean wasProcessed() {\n    return (this.autoConfigurationMetadata != null\n        // ж META-INF/spring-autoconfigure-metadata.properties ļǷڸ\n        && this.autoConfigurationMetadata.wasProcessed(this.className));\n}\n\n```\n\nҪ `AutoConfigurationMetadataLoader.PropertiesAutoConfigurationMetadata#wasProcessed` жϣ\n\n```\n@Override\npublic boolean wasProcessed(String className) {\n    // ж properties ǷڶӦ className\n    return this.properties.containsKey(className);\n}\n\n```\n\nԿж `properties` Ƿ `className``properties`  `META-INF/spring-autoconfigure-metadata.properties`ʾ£\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-069444fdaa952c6fa8c39576e776275982b.png)\n\nҪעǣļԴǲڵģڱʱдģڸļд롢ص `properties` ̣ľͲչˣṩ˼·\n\n*   ļд룺ڴʱspringboot ὫԶװһЩϢ (磬`@ConditionalOnClass` ָ `class``@ConditionalOnBean` ָ `bean``@AutoConfigureBefore`  `@AutoConfigureAfter` ָ `class` ) д뵽 `META-INF/spring-autoconfigure-metadata.properties` ļУΪ `AutoConfigureAnnotationProcessor` `javax.annotation.processing.AbstractProcessor` ࣬ `AbstractProcessor`  jdk ṩڱڶעд\n\n*   ļļأ `AutoConfigurationImportSelector.AutoConfigurationGroup#process` е `AutoConfigurationImportSelector#getAutoConfigurationEntry` ʱᴫ `AutoConfigurationMetadata`ļ `META-INF/spring-autoconfigure-metadata.properties` еݾǴص `AutoConfigurationMetadataLoader.PropertiesAutoConfigurationMetadata#properties` еģ\n\nЩɼ`AutoConfigurationMetadataLoader.PropertiesAutoConfigurationMetadata#wasProcessed` ʵϾж `META-INF/spring-autoconfigure-metadata.properties` ļǷ `className` á\n\nǻص `AutoConfigurationSorter.AutoConfigurationClass#isAvailable`һ`getAnnotationMetadata()`÷λ `AutoConfigurationSorter.AutoConfigurationClass` У£\n\n```\nprivate AnnotationMetadata getAnnotationMetadata() {\n    if (this.annotationMetadata == null) {\n        try {\n            // `className`ӦԴ className ӦԴʱ׳쳣\n            MetadataReader metadataReader = this.metadataReaderFactory\n                    .getMetadataReader(this.className);\n            this.annotationMetadata = metadataReader.getAnnotationMetadata();\n        }\n        catch (IOException ex) {\n            throw new IllegalStateException(...);\n        }\n    }\n    return this.annotationMetadata;\n}\n\n```\n\n `SimpleMetadataReaderFactory#getMetadataReader(String)`\n\n```\n@Override\n/**\n * ȡ className Ӧ .class ļ\n *  .class ļڣͱ쳣ˣIOException\n */\npublic MetadataReader getMetadataReader(String className) throws IOException {\n    try {\n        // תƣ\"classpath:xxx/xxx/Xxx.class\"\n        String resourcePath = ResourceLoader.CLASSPATH_URL_PREFIX \n                + lassUtils.convertClassNameToResourcePath(className) \n                + ClassUtils.CLASS_FILE_SUFFIX;\n        // ȡԴĬϵ resourceLoader Ϊ classLoader\n        Resource resource = this.resourceLoader.getResource(resourcePath);\n        //  resource ת MetadataReader 󣬲ھͻ׳쳣IOException\n        return getMetadataReader(resource);\n    }\n    catch (FileNotFoundException ex) {\n        // пڲٰ࣬ڲʽһ\n        int lastDotIndex = className.lastIndexOf('.');\n        if (lastDotIndex != -1) {\n            String innerClassName = className.substring(0, lastDotIndex) + '$' \n                    + className.substring(lastDotIndex + 1);\n            // תƣ\"classpath:xxx/Xxx$Xxx.class\"\n            String innerClassResourcePath = ResourceLoader.CLASSPATH_URL_PREFIX \n                    + ClassUtils.convertClassNameToResourcePath(innerClassName) \n                    + ClassUtils.CLASS_FILE_SUFFIX;\n            Resource innerClassResource = this.resourceLoader.getResource(innerClassResourcePath);\n            // жǷڣڻǻᱨ쳣ģIOException\n            if (innerClassResource.exists()) {\n                return getMetadataReader(innerClassResource);\n            }\n        }\n        throw ex;\n    }\n}\n\n```\n\nĴ£\n\n1.   `className` תΪ `classpath:xxx/xxx/Xxx.class` ʽȻȥضӦԴԴڼ `className` Ӧ`.class` ļڣ׳쳣\n2.  쳣 `catch` УΪ˷ֹ `className` ڲ࣬Ὣ `className` תΪ `classpath:xxx/Xxx$Xxx.class` ʽȻټһԴԴڣֱӷأ쳣ף\n\nǾˣ`getAnnotationMetadata()` жϵǰ `className` Ӧ`.class` Ŀ `classpath` ·Ƿڡ\n\nܽ£\n\n*   `AutoConfigurationSorter.AutoConfigurationClass#wasProcessed`ǰ `className` Ƿ `META-INF/spring-autoconfigure-metadata.properties` ļ\n*   `AutoConfigurationSorter.AutoConfigurationClass#isAvailable`ǰ `className` Ӧ`.class` ļǷ\n\nյĽۣ`AutoConfigurationSorter.AutoConfigurationClass#isAvailable` жϵǰ `className` Ӧ`.class` ļĿ `classpath` ·.\n\n#### 4.2 `AutoConfigurationSorter.AutoConfigurationClass#getBefore/getAfter`\n\n `AutoConfigurationSorter.AutoConfigurationClass` `getAfter()`  `getBefore()`\n\n```\nSet<String> getBefore() {\n    if (this.before == null) {\n        this.before = (wasProcessed() \n            //  `META-INF/spring-autoconfigure-metadata.properties` ļУֱӻȡֵ\n            ? this.autoConfigurationMetadata.getSet(this.className, \"AutoConfigureBefore\", \n                    Collections.emptySet()) \n            //  @AutoConfigureBefore עϻȡ\n            : getAnnotationValue(AutoConfigureBefore.class));\n    }\n    return this.before;\n}\n\nSet<String> getAfter() {\n    if (this.after == null) {\n        this.after = (wasProcessed() \n            //  `META-INF/spring-autoconfigure-metadata.properties` ļУֱӻȡֵ\n            ? this.autoConfigurationMetadata.getSet(this.className, \"AutoConfigureAfter\", \n                    Collections.emptySet()) \n            //  @AutoConfigureAfter עϻȡ\n            : getAnnotationValue(AutoConfigureAfter.class));\n    }\n    return this.after;\n}\n\n/**\n *  @AutoConfigureBefore/@AutoConfigureAfter עлȡֵvalue  name ֵָ\n */\nprivate Set<String> getAnnotationValue(Class<?> annotation) {\n    Map<String, Object> attributes = getAnnotationMetadata()\n            .getAnnotationAttributes(annotation.getName(), true);\n    if (attributes == null) {\n        return Collections.emptySet();\n    }\n    Set<String> value = new LinkedHashSet<>();\n    Collections.addAll(value, (String[]) attributes.get(\"value\"));\n    Collections.addAll(value, (String[]) attributes.get(\"name\"));\n    return value;\n}\n\n```\n\nڴʽһ£ȿ `getBefore()` ̣\n\n1.  ǰ `className`  `META-INF/spring-autoconfigure-metadata.properties` ļУֱȡֵǰҲᵽspringboot ڱʱһЩעϢд뵽 `META-INF/spring-autoconfigure-metadata.properties` ļУ\n\n2.   1 ɹӵǰ `class`  `@AutoConfigureBefore` ȡֵ\n\n`getAfter()`  `getBefore()` ̻һ£Ͳˡ\n\n### 5\\. ʹ `@AutoConfigureOrder` \n\nǻص `AutoConfigurationSorter#getInPriorityOrder`  `@AutoConfigureOrder` ̣\n\n```\nList<String> getInPriorityOrder(Collection<String> classNames) {\n    ...\n    orderedClassNames.sort((o1, o2) -> {\n        int i1 = classes.get(o1).getOrder();\n        int i2 = classes.get(o2).getOrder();\n        return Integer.compare(i1, i2);\n    });\n    ...\n}\n\n```\n\nʹõ `List#sort``sort(...)` ĲΪ `Comparator`ָ򡣴Ӵͨ `getOrder()` ȡǰ˳ʹõ `Integer` ıȽϹ `getOrder()` ĹؼԾ͵ķ `AutoConfigurationSorter.AutoConfigurationClass#getOrder`£\n\n```\nprivate int getOrder() {\n    // ж META-INF/spring-autoconfigure-metadata.properties ļǷڵǰ className\n    if (wasProcessed()) {\n        // ڣʹļָ˳򣬷ʹĬ˳\n        return this.autoConfigurationMetadata.getInteger(this.className, \n                \"AutoConfigureOrder\", AutoConfigureOrder.DEFAULT_ORDER);\n    }\n    // ڵȡ @AutoConfigureOrder עָ˳\n    Map<String, Object> attributes = getAnnotationMetadata()\n            .getAnnotationAttributes(AutoConfigureOrder.class.getName());\n    //  @AutoConfigureOrder δãʹĬ˳\n    return (attributes != null) ? (Integer) attributes.get(\"value\") \n            : AutoConfigureOrder.DEFAULT_ORDER;\n}\n\n```\n\nǱȽϼ򵥵ģǻȡ `@AutoConfigureOrder` עָ˳û `@AutoConfigureOrder` ע⣬ʹĬ˳Ĭ˳ `AutoConfigureOrder.DEFAULT_ORDER` ֵΪ 0\n\n### 6\\. ʹ `@AutoConfigureBefore``@AutoConfigureAfter` \n\nĵ `@AutoConfigureBefore`  `@AutoConfigureAfter` עˣӦķΪ `AutoConfigurationSorter#sortByAnnotation`£\n\n```\n/**\n * \n * ʵֻ׼һЩݣɻ doSortByAfterAnnotation(...)\n */\nprivate List<String> sortByAnnotation(AutoConfigurationClasses classes, List<String> classNames) {\n    // Ҫ className\n    List<String> toSort = new ArrayList<>(classNames);\n    toSort.addAll(classes.getAllNames());\n    // õ className\n    Set<String> sorted = new LinkedHashSet<>();\n    // е className\n    Set<String> processing = new LinkedHashSet<>();\n    while (!toSort.isEmpty()) {\n        // ķ\n        doSortByAfterAnnotation(classes, toSort, sorted, processing, null);\n    }\n    // ڼ sorted У classNames еԪؽᱻƳ\n    sorted.retainAll(classNames);\n    return new ArrayList<>(sorted);\n}\n\n/**\n * ķ\n */\nprivate void doSortByAfterAnnotation(AutoConfigurationClasses classes, List<String> toSort, \n        Set<String> sorted, Set<String> processing, String current) {\n    if (current == null) {\n        current = toSort.remove(0);\n    }\n    // ʹ processing жǷѭȽϣ磬A after B B  after A\n    processing.add(current);\n    // classes.getClassesRequestedAfterǰ className ҪЩ className ִ֮\n    for (String after : classes.getClassesRequestedAfter(current)) {\n        Assert.state(!processing.contains(after),\n                \"AutoConfigure cycle detected between \" + current + \" and \" + after);\n        if (!sorted.contains(after) && toSort.contains(after)) {\n            // ݹ\n            doSortByAfterAnnotation(classes, toSort, sorted, processing, after);\n        }\n    }\n    processing.remove(current);\n    // ӵ\n    sorted.add(current);\n}\n\n```\n\n`AutoConfigurationSorter#sortByAnnotation` ṩ˱ݵĽṹ `AutoConfigurationSorter#doSortByAfterAnnotation` ķ̫ö£\n\n1.  ҵǰ `className` ҪЩ `className` ֮װ䣬䱣Ϊ `afterClasses`Ҳ˵`afterClasses` еÿһ `className` Ҫڵǰ `className` ֮ǰװ䣻\n\n2.   `afterClasses`ÿһ `className` `afterClasses`ݹȥѭȽϵ£ձȻһ `className` `afterClasses` ΪգͰ `className` 뵽ĽṹС\n\nȡ `afterClasses` ĲΪ `AutoConfigurationSorter.AutoConfigurationClasses#getClassesRequestedAfter`£\n\n```\nSet<String> getClassesRequestedAfter(String className) {\n    // ǰࣺȡЩִ֮Уǻȡ @AutoConfigureAfter עָ\n    Set<String> classesRequestedAfter = new LinkedHashSet<>(get(className).getAfter());\n    // ࣺҪǰִе\n    this.classes.forEach((name, autoConfigurationClass) -> {\n        if (autoConfigurationClass.getBefore().contains(className)) {\n            classesRequestedAfter.add(name);\n        }\n    });\n    return classesRequestedAfter;\n}\n\n```\n\nӴ `afterClasses` ݣ\n\n*   ȡЩװ֮װ䣬ǻȡ `@AutoConfigureAfter` עָ\n*   ȡЩҪڵǰװ֮ǰװ\n\n### 7\\. `@ConditionalOnBean/@ConditionalOnMissingBean`\n\nǰᵽ `@ConditionalOnBean/@ConditionalOnMissingBean` Ŀӣ˽Զװ˳󣬾ܺܺùЩˣ\n\n1.   `bean` Զװࣺܿӷʽǣʹ `@AutoConfigureBefore` / `@AutoConfigureAfter`  `@AutoConfigureOrder` ָ˳򣬱֤עе `bean` װ伴ɣ\n2.  һͨ `spring bean`һԶװࣺעе `bean` ͨ spring beanһԶװ࣬²ôԶװĴ `DeferredImportSelector` ࣬Զװͨ `spring bean` ֮֮ עе `bean` Զװ࣬һͨ `spring bean`һҪʹã\n3.  ͨ `spring bean`ޱܿӷ`spring bean` עᵽ `beanFactory` ˳򲻿ɿأʹã\n\n### 8\\. ܽ\n\nܽԶװװ˳Ҫݣ\n\n1.  Զװ`AutoConfigurationImportSelector.AutoConfigurationGroup#sortAutoConfigurations`\n2.  ָԶװװ˳ʹ `@AutoConfigureBefore` / `@AutoConfigureAfter`  `@AutoConfigureOrder`\n3.  ʽ֣ǣ\n    1.   className  `String` ṩ\n    2.   `@AutoConfigureOrder` ֵָ `Integer` ṩ\n    3.   `@AutoConfigureBefore` / `@AutoConfigureAfter`  ҪעǣʽȺУĽΪ˳\n4.   `@ConditionalOnBean/@ConditionalOnMissingBean` ָܿϣ\n    1.   `bean` Զװࣺܿӷʽǣʹ `@AutoConfigureBefore` / `@AutoConfigureAfter`  `@AutoConfigureOrder` ָ˳򣬱֤עе `bean` װ伴ɣ\n    2.  һͨ `spring bean`һԶװࣺעе `bean` Ϊͨ `spring bean`\n    3.  ɿأʹá\n\n* * *\n\n_ԭӣ[https://my.oschina.net/funcy/blog/4921594](https://my.oschina.net/funcy/blog/4921594) ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע_"
  },
  {
    "path": "docs/Spring全家桶/SpringBoot源码解析/SpringBoot自动装配（二）：条件注解.md",
    "content": "### 1\\. ע⼰ж\n\n [springboot Զװ֮Զװ](https://my.oschina.net/funcy/blog/4870868)һУǷ springboot  `META-INF/spring.factories` ļжԶװ࣬صЩԶװЩе bean һʼ𣿲ǣǿڶӦ Bean ɷʹ**ע**Ƿгʼ\n\nspringboot ṩע£\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-0e8d27c887fb6ca142672cad1c60e9de207.png)\n\nоٲ£\n\n| ע              | ע                                                     | ˵                                      |\n| --------------------- | ------------------------------------------------------------ | --------------------------------------------- |\n| class ע        | `@ConditionalOnClass`/`@ConditionalOnMissingClass`           | ָ** / ȱʧ**ʱʼ bean      |\n| bean ע         | `@ConditionalOnBean`/`@ConditionalOnMissingBean`             | ָ bean ** / ȱʧ**ʱʼ bean  |\n| ע          | `@ConditionalOnProperty`                                     | ָԴڳʼ bean                 |\n| Resource ע     | `@ConditionalOnResource`                                     | ָԴڳʼ bean                 |\n| Web Ӧע      | `@ConditionalOnWebApplication` / `@ConditionalOnNotWebApplication` | ǰӦ**Ϊ / Ϊ** web Ӧʱʼ bean |\n| spring ʽע | `@ConditionalOnExpression`                                   | ʽΪ true ʱʼ bean           |\n\nǽ `@ConditionalOnClass` עݣ\n\n```\n...\n@Conditional(OnClassCondition.class)\npublic @interface ConditionalOnClass {\n    ...\n}\n\n```\n\nԿ`ConditionalOnClass`  `@Conditional` עĹܣ `OnClassCondition.class`\n\n `@Conditional` עԲο [ConfigurationClassPostProcessor ֮ @Conditional ע](https://my.oschina.net/funcy/blog/4873444)ֱ˵ `@Conditional` ʹ÷ʽ\n\n1. `@Conditional`  spring ע⣻\n\n2. `@Conditional` ṩһ `value`Ϊ `Class` `Condition` ࣺ\n\n   ```\n   Class<? extends Condition>[] value();\n   \n   ```\n\n3. `Condition` һӿڣһ `matches(...)` \n\n   ```\n   public interface Condition {\n   \n       boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);\n   \n   }\n   \n   ```\n\n   ֻ `matches(...)`  `true` ʱ`ConfigurationClassPostProcessor` ŻὫӦ bean עᵽ `beanFactory`  `beanDefinitionMap` .\n\nܽ `@Conditional` ʹ÷ʽǾˣ`OnClassCondition.class`  `Condition` ࣬ `matches(...)` ҪͬעĴʽҲƣܽעжࣺ\n\n| ע              | ע                                                     | ж                  |\n| --------------------- | ------------------------------------------------------------ | --------------------------- |\n| class ע        | `@ConditionalOnClass`/`@ConditionalOnMissingClass`           | `OnClassCondition`          |\n| bean ע         | `@ConditionalOnBean`/`@ConditionalOnMissingBean`             | `OnBeanCondition`           |\n| ע          | `@ConditionalOnProperty`                                     | `OnPropertyCondition`       |\n| Resource ע     | `@ConditionalOnResource`                                     | `OnResourceCondition`       |\n| Web Ӧע      | `@ConditionalOnWebApplication` / `@ConditionalOnNotWebApplication` | `OnWebApplicationCondition` |\n| spring ʽע | `@ConditionalOnExpression`                                   | `OnExpressionCondition`     |\n\nĿľͺȷˣҪЩעж߼ֻҪӦж `matches(...)` Ϳˡ\n\n### 2. `SpringBootCondition#matches`\n\n `OnClassCondition#matches`  `SpringBootCondition`ط£\n\n```\npublic abstract class SpringBootCondition implements Condition {\n\n    private final Log logger = LogFactory.getLog(getClass());\n\n    @Override\n    public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {\n        String classOrMethodName = getClassOrMethodName(metadata);\n        try {\n            // ȡƥ\n            ConditionOutcome outcome = getMatchOutcome(context, metadata);\n            // ӡһ־\n            logOutcome(classOrMethodName, outcome);\n            // ¼ķΪ¼һжϼ¼\n            recordEvaluation(context, classOrMethodName, outcome);\n            // ﷵսtrue  false\n            return outcome.isMatch();\n        }\n        catch (NoClassDefFoundError ex) {\n            throw new IllegalStateException(...);\n        }\n        catch (RuntimeException ex) {\n            throw new IllegalStateException(...);\n        }\n    }\n\n    /**\n     * Ǹʽʵ\n     */\n    public abstract ConditionOutcome getMatchOutcome(\n        ConditionContext context, AnnotatedTypeMetadata metadata);\n\n    ...\n\n}\n\n```\n\n`SpringBootCondition`  `matches(...)` ؼУ\n\n```\n...\nConditionOutcome outcome = getMatchOutcome(context, metadata);\n...\nreturn outcome.isMatch();\n\n```\n\n `SpringBootCondition`  `getMatchOutcome(...)` Ǹ󷽷߼ṩ`OnClassCondition` ʵ֮һʵϣж඼ `SpringBootCondition` ࣬Ǿֱӽ `getMatchOutcome(...)` ˡ\n\n`getMatchOutcome(...)` صĽ `ConditionOutcome` `ConditionOutcome` Ǹɶ\n\n```\npublic class ConditionOutcome {\n\n    private final boolean match;\n\n    private final ConditionMessage message;\n\n    /**\n     * 췽\n     */\n    public ConditionOutcome(boolean match, String message) {\n        this(match, ConditionMessage.of(message));\n    }\n\n    /**\n     * 췽\n     */\n    public ConditionOutcome(boolean match, ConditionMessage message) {\n        Assert.notNull(message, \"ConditionMessage must not be null\");\n        this.match = match;\n        this.message = message;\n    }\n\n    /**\n     * ƥĽ\n     */\n    public boolean isMatch() {\n        return this.match;\n    }\n\n    ...\n}\n\n```\n\nӴװȽϽģڲԣ`match`  `message`:\n\n*   `match`  `boolean`ƥɹʧܵıʶ\n*   `message`  `ConditionMessage`ʾƥ˵\n\n `ConditionMessage`:\n\n```\npublic final class ConditionMessage {\n\n    private String message;\n\n    private ConditionMessage() {\n        this(null);\n    }\n\n    private ConditionMessage(String message) {\n        this.message = message;\n    }\n\n    ...\n}\n\n```\n\nһԣ`message`Ƕ˵Ϣİװ\n\n### 3. `@ConditionalOnClass`: `OnClassCondition#getMatchOutcome`\n\n `OnClassCondition` ƥ߼ֱӽ `getMatchOutcome` \n\n```\npublic ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {\n    ClassLoader classLoader = context.getClassLoader();\n    ConditionMessage matchMessage = ConditionMessage.empty();\n    // 1\\.  @ConditionalOnClass ע\n    List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);\n    if (onClasses != null) {\n        // 1.1 ж\n        List<String> missing = filter(onClasses, ClassNameFilter.MISSING, classLoader);\n        if (!missing.isEmpty()) {\n            // 1.2 ؽƥ\n            return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)\n                    .didNotFind(\"required class\", \"required classes\").items(Style.QUOTE, missing));\n        }\n        matchMessage = matchMessage.andCondition(ConditionalOnClass.class)\n                .found(\"required class\", \"required classes\")\n                .items(Style.QUOTE, filter(onClasses, ClassNameFilter.PRESENT, classLoader));\n    }\n\n    // 2\\.  @ConditionalOnMissingClass ע\n    List<String> onMissingClasses = getCandidates(metadata, ConditionalOnMissingClass.class);\n    if (onMissingClasses != null) {\n        // 2.1 ж\n        List<String> present = filter(onMissingClasses, ClassNameFilter.PRESENT, classLoader);\n        if (!present.isEmpty()) {\n            // 2.2 ؽƥ\n            return ConditionOutcome.noMatch(ConditionMessage\n                    .forCondition(ConditionalOnMissingClass.class)\n                    .found(\"unwanted class\", \"unwanted classes\").items(Style.QUOTE, present));\n        }\n        matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class)\n                .didNotFind(\"unwanted class\", \"unwanted classes\")\n                .items(Style.QUOTE, filter(onMissingClasses, ClassNameFilter.MISSING, classLoader));\n    }\n    // 󷵻ƥĽ\n    return ConditionOutcome.match(matchMessage);\n}\n\n```\n\nͬʱ `@ConditionalOnClass`  `@ConditionalOnMissingClass` ע⣬̼ƣעж϶ͨ `FilteringSpringBootCondition#filter` £\n\n```\nprotected final List<String> filter(Collection<String> classNames, ClassNameFilter classNameFilter,\n        ClassLoader classLoader) {\n    if (CollectionUtils.isEmpty(classNames)) {\n        return Collections.emptyList();\n    }\n    List<String> matches = new ArrayList<>(classNames.size());\n    for (String candidate : classNames) {\n        // ƥ\n        if (classNameFilter.matches(candidate, classLoader)) {\n            matches.add(candidate);\n        }\n    }\n    return matches;\n}\n\n```\n\nɴ˿ɼ `classNameFilter` ˹ؼ\n\n*    `@ConditionalOnClass` ʱ`classNameFilter` Ϊ `ClassNameFilter.MISSING`\n*    `@ConditionalOnMissingClass` ʱ`classNameFilter` Ϊ `ClassNameFilter.PRESENT`\n\nǽ `ClassNameFilter` һ̽ `FilteringSpringBootCondition` ࣬£\n\n```\nabstract class FilteringSpringBootCondition extends SpringBootCondition\n        implements AutoConfigurationImportFilter, BeanFactoryAware, BeanClassLoaderAware {\n    ...\n    /**\n     *  classLoader ڣ ClassLoader#loadClass \n     *  Class#forName \n     */\n    protected static Class<?> resolve(String className, ClassLoader classLoader) \n            throws ClassNotFoundException {\n        if (classLoader != null) {\n            return classLoader.loadClass(className);\n        }\n        return Class.forName(className);\n    }\n\n    /**\n     * ƥ\n     */\n    protected enum ClassNameFilter {\n\n        PRESENT {\n\n            @Override\n            public boolean matches(String className, ClassLoader classLoader) {\n                return isPresent(className, classLoader);\n            }\n\n        },\n\n        MISSING {\n\n            @Override\n            public boolean matches(String className, ClassLoader classLoader) {\n                return !isPresent(className, classLoader);\n            }\n\n        };\n\n        abstract boolean matches(String className, ClassLoader classLoader);\n\n        /**\n         * Class Ƿ\n         * ͨʱ쳣жǷڣδ׳쳣ʾ\n         */\n        static boolean isPresent(String className, ClassLoader classLoader) {\n            if (classLoader == null) {\n                classLoader = ClassUtils.getDefaultClassLoader();\n            }\n            try {\n                // ͨ쳣жǷڸclass\n                resolve(className, classLoader);\n                return true;\n            }\n            catch (Throwable ex) {\n                return false;\n            }\n        }\n    }\n    ...\n}\n\n```\n\nǾˣж `Class` Ƿڣspring ͨ `ClassLoader.load(String)`  `Class.forName(String)` 쳣ģ׳쳣ͱ `Class` ڡ\n\nܽ `@ConditionalOnClass`/`@ConditionalOnMissingClass` Ĵʽ**ߵĴ඼Ϊ `OnClassCondition`ͨ `ClassLoader.load(String)`  `Class.forName(String)` 쳣ж `Class` Ƿڣ׳쳣ͱ `Class` **\n\n### 4. `@ConditionalOnBean`: `OnBeanCondition#getMatchOutcome`\n\n `@ConditionalOnBean`  ֱӽ `OnBeanCondition#getMatchOutcome`\n\n```\npublic ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {\n    ConditionMessage matchMessage = ConditionMessage.empty();\n    MergedAnnotations annotations = metadata.getAnnotations();\n    //  @ConditionalOnBean\n    if (annotations.isPresent(ConditionalOnBean.class)) {\n        Spec<ConditionalOnBean> spec = new Spec<>(context, metadata, \n                annotations, ConditionalOnBean.class);\n        // ƥ\n        MatchResult matchResult = getMatchingBeans(context, spec);\n        // עж\n        if (!matchResult.isAllMatched()) {\n            String reason = createOnBeanNoMatchReason(matchResult);\n            return ConditionOutcome.noMatch(spec.message().because(reason));\n        }\n        matchMessage = spec.message(matchMessage).found(\"bean\", \"beans\").items(Style.QUOTE,\n                matchResult.getNamesOfAllMatches());\n    }\n\n    //  @ConditionalOnSingleCandidate\n    if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) {\n        Spec<ConditionalOnSingleCandidate> spec \n                = new SingleCandidateSpec(context, metadata, annotations);\n        // ƥ\n        MatchResult matchResult = getMatchingBeans(context, spec);\n        // עж\n        if (!matchResult.isAllMatched()) {\n            return ConditionOutcome.noMatch(spec.message().didNotFind(\"any beans\").atAll());\n        }\n        else if (!hasSingleAutowireCandidate(context.getBeanFactory(), \n                matchResult.getNamesOfAllMatches(), spec.getStrategy() == SearchStrategy.ALL)) {\n            return ConditionOutcome.noMatch(spec.message().didNotFind(\"a primary bean from beans\")\n                    .items(Style.QUOTE, matchResult.getNamesOfAllMatches()));\n        }\n        matchMessage = spec.message(matchMessage).found(\"a primary bean from beans\")\n                .items(Style.QUOTE, matchResult.getNamesOfAllMatches());\n    }\n\n    //  @ConditionalOnMissingBean\n    if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {\n        Spec<ConditionalOnMissingBean> spec = new Spec<>(context, metadata, annotations,\n                ConditionalOnMissingBean.class);\n        // ƥ\n        MatchResult matchResult = getMatchingBeans(context, spec);\n        // עж\n        if (matchResult.isAnyMatched()) {\n            String reason = createOnMissingBeanNoMatchReason(matchResult);\n            return ConditionOutcome.noMatch(spec.message().because(reason));\n        }\n        matchMessage = spec.message(matchMessage).didNotFind(\"any beans\").atAll();\n    }\n    return ConditionOutcome.match(matchMessage);\n}\n\n```\n\nԿһעƥ䣺`@ConditionalOnBean``@ConditionalOnSingleCandidate`  `@ConditionalOnMissingBean`߶ͬһ `getMatchingBeans(...)` ȡƥȻʹ `matchResult.isAllMatched()`  `matchResult.isAnyMatched()` յĽжϡ\n\n#### `OnBeanCondition#getMatchingBeans`\n\n`getMatchingBeans(...)` Ĵ£\n\n```\nprotected final MatchResult getMatchingBeans(ConditionContext context, Spec<?> spec) {\n    ClassLoader classLoader = context.getClassLoader();\n    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();\n    boolean considerHierarchy = spec.getStrategy() != SearchStrategy.CURRENT;\n    Set<Class<?>> parameterizedContainers = spec.getParameterizedContainers();\n    if (spec.getStrategy() == SearchStrategy.ANCESTORS) {\n        BeanFactory parent = beanFactory.getParentBeanFactory();\n        Assert.isInstanceOf(ConfigurableListableBeanFactory.class, parent,\n                \"Unable to use SearchStrategy.ANCESTORS\");\n        beanFactory = (ConfigurableListableBeanFactory) parent;\n    }\n    MatchResult result = new MatchResult();\n    // 1\\. ȡ ignoreTypeֻ @ConditionalOnMissingBean \n    Set<String> beansIgnoredByType = getNamesOfBeansIgnoredByType(classLoader, beanFactory, \n            considerHierarchy, spec.getIgnoredTypes(), parameterizedContainers);\n\n    // 2\\.  types\n    for (String type : spec.getTypes()) {\n        Collection<String> typeMatches = getBeanNamesForType(classLoader, considerHierarchy, \n                beanFactory, type, parameterizedContainers);\n        typeMatches.removeAll(beansIgnoredByType);\n        if (typeMatches.isEmpty()) {\n            result.recordUnmatchedType(type);\n        }\n        else {\n            result.recordMatchedType(type, typeMatches);\n        }\n    }\n\n    // 3\\. ϵע @ConditionalOnMissingBean \n    for (String annotation : spec.getAnnotations()) {\n        Set<String> annotationMatches = getBeanNamesForAnnotation(classLoader, beanFactory, \n                annotation, considerHierarchy);\n        annotationMatches.removeAll(beansIgnoredByType);\n        if (annotationMatches.isEmpty()) {\n            result.recordUnmatchedAnnotation(annotation);\n        }\n        else {\n            result.recordMatchedAnnotation(annotation, annotationMatches);\n        }\n    }\n\n    // 4\\.  beanName\n    for (String beanName : spec.getNames()) {\n        if (!beansIgnoredByType.contains(beanName) && containsBean(beanFactory, beanName, \n                considerHierarchy)) {\n            result.recordMatchedName(beanName);\n        }\n        else {\n            result.recordUnmatchedName(beanName);\n        }\n    }\n    return result;\n}\n\n```\n\nҪ˵ǣᴦ 3 עƥ`@ConditionalOnBean``@ConditionalOnSingleCandidate`  `@ConditionalOnMissingBean`£\n\n1.  ȡ `ignoreType`ֻ `@ConditionalOnMissingBean` \n2.   `types` ƥ\n3.  ע⣨ϵע⣩ƥ ֻ `@ConditionalOnMissingBean` \n4.   `beanName` ƥ\n\nϲľϸڣľͲչˣṩ̣\n\n1.  ȡ `ignoreType`\n    1.  ʹ `ListableBeanFactory#getBeanNamesForType(Class, boolean, boolean)` ȡе `ignoreType`  `beanName`\n    2.  Ϊ `beansIgnoredByType`( `Set<String>`)\n2.   `types` ƥ\n    1.  ʹ `ListableBeanFactory#getBeanNamesForType(Class, boolean, boolean)` ȡе `type` Ӧ `beanName`Ϊ `typeMatches`\n    2.   `typeMatches` еֵȥ `ignoreType`\n    3.  жϵڶõ `typeMatches`Ϊգǰ `Type` 浽 `unmatchedTypes` У򱣴浽 `matchedTypes`  `namesOfAllMatches` \n3.  עƥ\n    1.  ʹ `ListableBeanFactory#getBeanNamesForAnnotation` ȡе `annotation` Ӧ `beanName`Ϊ `annotationMatches`\n    2.   `annotationMatches` еֵȥ `ignoreType`\n    3.  жϵڶõ `annotationMatches`Ϊգǰ `Annotation` 浽 `unmatchedAnnotations` У򱣴浽 `matchedAnnotations`  `namesOfAllMatches` \n4.   `beanName` ƥ\n    1.  ж `beansIgnoredByType` Ƿ `beanName`\n    2.  ʹ `BeanFactory#containsBean` жи `beanName`\n    3.   2 Ϊ `false`ڶΪ `true`򽫵ǰ `beanName` 뵽 `matchedNames`  `namesOfAllMatches`򱣴浽 `unmatchedNames` \n\nõ `matchedTypes``unmatchedNames` ݺ`matchResult.isAllMatched()`  `matchResult.isAnyMatched()` յжϽжЩṹǷգ\n\n```\nboolean isAllMatched() {\n    return this.unmatchedAnnotations.isEmpty() && this.unmatchedNames.isEmpty()\n            && this.unmatchedTypes.isEmpty();\n}\n\nboolean isAnyMatched() {\n    return (!this.matchedAnnotations.isEmpty()) || (!this.matchedNames.isEmpty())\n            || (!this.matchedTypes.isEmpty());\n}\n\n```\n\n`@ConditionalOnBean`/`@ConditionalOnMissingBean` Ĺؼʹ `ListableBeanFactory#getBeanNamesForType`  `BeanFactory#containsBean` ж `beanName``beanType` Ƿˡ\n\nʹ `@ConditionalOnBean`/`@ConditionalOnMissingBean` ʱһҪرע⣺עִʱ spring  `ConfigurationClassPostProcessor` еģȷе˵ڽ `bean` 뵽 `beanFactory`  `beanDefinitionMap` ֮ǰжϵģӵ `beanDefinitionMap` УͲӡ͵һ⣺ `@ConditionalOnBean`/`@ConditionalOnMissingBean`  `bean` ڸ `bean` ֮뵽 `beanDefinitionMap` УпܳУ˵\n\nࣺ\n\n```\n@Component\n@ConditionalOnMissingBean(\"b\")\npublic class A {\n\n}\n\n@Component\npublic class B {\n\n}\n\n```\n\n `A`  `B`  `@Component` spring beanȻ `A` ע `@ConditionalOnMissingBean(\"b\")` `b` ʱ`A` ŽгʼЩǰᣬ\n\n1.   `b` ӵ `beanDefinitionMap` Уڽ `a` ӵ `beanDefinitionMap` ʱ `b` ѾˣǾͲˣǵԤڣ\n2.   `a` ȱʱ `beanDefinitionMap` вû `b` `a` ӵ `beanDefinitionMap` Уٴ `b``b` Ҳᱻӵ `beanDefinitionMap`һ`a`  `b` ͬʱ `beanDefinitionMap` Уնᱻʼ spring beanǵԤڲ\n\nô springboot νأ `@ConditionalOnBean`/`@ConditionalOnMissingBean` ˵\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-30df67cb01c73a6b201695298aad14fd0a5.png)\n\n΢£\n\nֻƥ䵽ĿǰΪֹӦóе bean ˣǿҽԶʹáѡ bean ҪһԶ´ȷʹôڴ֮С\n\nݣҵĽ£\n\n*    `@ConditionalOnBean`/`@ConditionalOnMissingBean`  `bean` 뵽 `beanDefinitionMap` һ̣ƥĿǰΪֹ `beanDefinitionMap` Ѵڵ bean֮ bean ǣпУԲοٵ `a`  `b` \n*   ǿҽԶʹ `@ConditionalOnBean`/`@ConditionalOnMissingBean` ע⣬Ҳ˵ԶʹõĻȷƥ\n*    `a`  `b`  `a`  `b` ֱλڲͬԶУô `a` Ҫ `b` ֮ص `beanDefinitionMap` Уͨ `@AutoConfigureAfter``@AutoConfigureBefore``@AutoConfigureOrder` עָ\n\nԶļ˳򣬺ɡ\n\nƪľȵˣƪʣµע⡣\n\n* * *\n\n springboot עĵڶƪܽ springboot ļܽ᣺\n\n| ע              | ע                                                     | ж                  |\n| --------------------- | ------------------------------------------------------------ | --------------------------- |\n| class ע        | `@ConditionalOnClass`/`@ConditionalOnMissingClass`           | `OnClassCondition`          |\n| bean ע         | `@ConditionalOnBean`/`@ConditionalOnMissingBean`             | `OnBeanCondition`           |\n| ע          | `@ConditionalOnProperty`                                     | `OnPropertyCondition`       |\n| Resource ע     | `@ConditionalOnResource`                                     | `OnResourceCondition`       |\n| Web Ӧע      | `@ConditionalOnWebApplication` / `@ConditionalOnNotWebApplication` | `OnWebApplicationCondition` |\n| spring ʽע | `@ConditionalOnExpression`                                   | `OnExpressionCondition`     |\n\nļжϡ\n\n### 5. `@ConditionalOnProperty``OnPropertyCondition#getMatchOutcome`\n\n `@ConditionalOnProperty` Ĵ `OnPropertyCondition#getMatchOutcome` \n\n```\nclass OnPropertyCondition extends SpringBootCondition {\n\n    @Override\n    public ConditionOutcome getMatchOutcome(ConditionContext context, \n            AnnotatedTypeMetadata metadata) {\n        // ȡ @ConditionalOnProperty ֵ\n        List<AnnotationAttributes> allAnnotationAttributes = annotationAttributesFromMultiValueMap(\n                metadata.getAllAnnotationAttributes(ConditionalOnProperty.class.getName()));\n        List<ConditionMessage> noMatch = new ArrayList<>();\n        List<ConditionMessage> match = new ArrayList<>();\n        for (AnnotationAttributes annotationAttributes : allAnnotationAttributes) {\n            //  determineOutcome(...) нжϣעcontext.getEnvironment()\n            ConditionOutcome outcome = determineOutcome(annotationAttributes, \n                    context.getEnvironment());\n            (outcome.isMatch() ? match : noMatch).add(outcome.getConditionMessage());\n        }\n        if (!noMatch.isEmpty()) {\n            return ConditionOutcome.noMatch(ConditionMessage.of(noMatch));\n        }\n        return ConditionOutcome.match(ConditionMessage.of(match));\n    }\n\n    ...\n\n}\n\n```\n\nǱȽϼ򵥵ģǻȡ `@ConditionalOnProperty` ֵٵ `determineOutcome(...)` дٽ `OnPropertyCondition#determineOutcome` \n\n```\n/**\n * \n * ע⣺resolver ĵ Environment applicationContext е Environment\n */\nprivate ConditionOutcome determineOutcome(AnnotationAttributes annotationAttributes, \n        PropertyResolver resolver) {\n    Spec spec = new Spec(annotationAttributes);\n    List<String> missingProperties = new ArrayList<>();\n    List<String> nonMatchingProperties = new ArrayList<>();\n    // \n    spec.collectProperties(resolver, missingProperties, nonMatchingProperties);\n    // жϽ\n    if (!missingProperties.isEmpty()) {\n        return ConditionOutcome.noMatch(ConditionMessage\n            .forCondition(ConditionalOnProperty.class, spec)\n            .didNotFind(\"property\", \"properties\").items(Style.QUOTE, missingProperties));\n    }\n    // жϽ\n    if (!nonMatchingProperties.isEmpty()) {\n        return ConditionOutcome.noMatch(ConditionMessage\n            .forCondition(ConditionalOnProperty.class, spec)\n            .found(\"different value in property\", \"different value in properties\")\n            .items(Style.QUOTE, nonMatchingProperties));\n    }\n    // жϽ\n    return ConditionOutcome.match(ConditionMessage\n        .forCondition(ConditionalOnProperty.class, spec).because(\"matched\"));\n}\n\n/**\n * \n */\nprivate void collectProperties(PropertyResolver resolver, List<String> missing, \n        List<String> nonMatching) {\n    for (String name : this.names) {\n        String key = this.prefix + name;\n        // resolver  environment\n        // properties жϾж environment ûӦ\n        if (resolver.containsProperty(key)) {\n            if (!isMatch(resolver.getProperty(key), this.havingValue)) {\n                nonMatching.add(name);\n            }\n        }\n        else {\n            if (!this.matchIfMissing) {\n                missing.add(name);\n            }\n        }\n    }\n}\n\n```\n\nԿ`@ConditionalOnProperty` ͨж `environment` Ƿижϵġ\n\n### 6. `@ConditionalOnResource``OnResourceCondition#getMatchOutcome`\n\n `@ConditionalOnResource` Ĵһʹã\n\n```\n@Bean\n@ConditionalOnResource(resources = \"classpath:config.properties\")\npublic Config config() {\n    return config;\n}\n\n```\n\nʾ `classpath` д `config.properties` ʱ`config` Żᱻʼ springbean\n\nٽ `OnResourceCondition#getOutcomes` \n\n```\n@Override\npublic ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {\n    MultiValueMap<String, Object> attributes = metadata\n            .getAllAnnotationAttributes(ConditionalOnResource.class.getName(), true);\n    // ȡ ResourceLoader\n    ResourceLoader loader = context.getResourceLoader();\n    List<String> locations = new ArrayList<>();\n    collectValues(locations, attributes.get(\"resources\"));\n    Assert.isTrue(!locations.isEmpty(),\n            \"@ConditionalOnResource annotations must specify at least one resource location\");\n    List<String> missing = new ArrayList<>();\n    // жԴǷ\n    for (String location : locations) {\n        // location пռλﴦ\n        String resource = context.getEnvironment().resolvePlaceholders(location);\n        // ж resource Ƿ\n        if (!loader.getResource(resource).exists()) {\n            missing.add(location);\n        }\n    }\n    // \n    if (!missing.isEmpty()) {\n        return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnResource.class)\n                .didNotFind(\"resource\", \"resources\").items(Style.QUOTE, missing));\n    }\n    return ConditionOutcome.match(ConditionMessage.forCondition(ConditionalOnResource.class)\n            .found(\"location\", \"locations\").items(locations));\n}\n\n```\n\nͨ `OnResourceCondition#getOutcomes` ȡ `ResourceLoader`ͨԷʽֵǰ `ResourceLoader` Ϊ `AnnotationConfigServletWebServerApplicationContext`\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-8f1de99f757eca7aeb0307b06b28d1020d2.png)\n\nȡ `ResourceLoader` 󣬵 `ResourceLoader#getResource(String)` ȡԴȻ `Resource#exists` жԴǷڣƥ\n\n̵Ĺؼ `ResourceLoader#getResource(String)`÷Ĵ뵽 `GenericApplicationContext#getResource` \n\n```\n@Override\npublic Resource getResource(String location) {\n    if (this.resourceLoader != null) {\n        return this.resourceLoader.getResource(location);\n    }\n    return super.getResource(location);\n}\n\n```\n\n `this.resourceLoader` Ϊ `null`븸ķ `DefaultResourceLoader#getResource`\n\n```\npublic Resource getResource(String location) {\n    Assert.notNull(location, \"Location must not be null\");\n    for (ProtocolResolver protocolResolver : getProtocolResolvers()) {\n        Resource resource = protocolResolver.resolve(location, this);\n        if (resource != null) {\n            return resource;\n        }\n    }\n    // /ͷԴ\n    if (location.startsWith(\"/\")) {\n        return getResourceByPath(location);\n    }\n    else if (location.startsWith(CLASSPATH_URL_PREFIX)) {\n        // classpathͷԴ\n        return new ClassPathResource(\n            location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());\n    }\n    else {\n        try {\n            // ϶㣬ʹ url \n            URL url = new URL(location);\n            return (ResourceUtils.isFileURL(url) \n                ? new FileUrlResource(url) : new UrlResource(url));\n        }\n        catch (MalformedURLException ex) {\n            // url⣬ջ getResourceByPath(...) \n            return getResourceByPath(location);\n        }\n    }\n}\n\n/**\n * ͨ·õ Resource\n */\nprotected Resource getResourceByPath(String path) {\n    return new ClassPathContextResource(path, getClassLoader());\n}\n\n```\n\nԿ`DefaultResourceLoader#getResource` ͨж `location` ǰ׺õ 4  `Resource`\n\n*   `ClassPathContextResource`\n*   `FileUrlResource`\n*   `UrlResource`\n\nõ `Resource` 󣬽žжϸ `Resource` Ƿˣ `ClassPathContextResource#exist` ÷ `ClassPathResource#exists`\n\n```\n/**\n * ж Resource Ƿ\n */\n@Override\npublic boolean exists() {\n    return (resolveURL() != null);\n}\n\n/**\n * Դܻȡ򷵻ԴӦurl򷵻null\n */\n@Nullable\nprotected URL resolveURL() {\n    if (this.clazz != null) {\n        // ʹõǰ class Ӧ classLoader ȡ\n        return this.clazz.getResource(this.path);\n    }\n    else if (this.classLoader != null) {\n        // ʹָ classLoader ȡ\n        return this.classLoader.getResource(this.path);\n    }\n    else {\n        // ȡϵͳȡ\n        return ClassLoader.getSystemResource(this.path);\n    }\n}\n\n```\n\nӴԿͨ `classLoader` ȡļ `url`ͨжļ `url` ǷΪ `null` ж `resource` Ƿڡ\n\n `FileUrlResource` жϣʵ `FileUrlResource`  `UrlResource`  `exist()`  `AbstractFileResolvingResource#exists`ͳһͿˣ÷£\n\n```\npublic boolean exists() {\n    try {\n        URL url = getURL();\n        if (ResourceUtils.isFileURL(url)) {\n            // ļֱжļǷ\n            return getFile().exists();\n        }\n        else {\n            // ʹļ\n            URLConnection con = url.openConnection();\n            customizeConnection(con);\n            HttpURLConnection httpCon =\n                    (con instanceof HttpURLConnection ? (HttpURLConnection) con : null);\n            // httpжϿӷص״̬\n            if (httpCon != null) {\n                int code = httpCon.getResponseCode();\n                if (code == HttpURLConnection.HTTP_OK) {\n                    return true;\n                }\n                else if (code == HttpURLConnection.HTTP_NOT_FOUND) {\n                    return false;\n                }\n            }\n            //  contentLengthLong 0Ҳtrue\n            if (con.getContentLengthLong() > 0) {\n                return true;\n            }\n            if (httpCon != null) {\n                httpCon.disconnect();\n                return false;\n            }\n            else {\n                getInputStream().close();\n                return true;\n            }\n        }\n    }\n    catch (IOException ex) {\n        return false;\n    }\n}\n\n```\n\nǱļֱʹ `File#exists()` жļǷڣжļǷڣжϷʽͲϸ˵ˡ\n\nܵ˵springboot  `@ConditionalOnResource` жϻЩӵģܽ£\n\n1.   `classpath` ļͨ `classloader` ȡļӦ `url` ǷΪ `null` жļǷڣ\n2.  ͨļֱ `File#exists()` жļǷڣ\n3.  ļȴһӣжļǷڡ\n\n### 7. `@ConditionalOnWebApplication``OnWebApplicationCondition#getMatchOutcome`\n\n `@ConditionalOnWebApplication` Ĵ `OnWebApplicationCondition#getOutcomes` \n\n```\n@Override\nprotected ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,\n        AutoConfigurationMetadata autoConfigurationMetadata) {\n    ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];\n    for (int i = 0; i < outcomes.length; i++) {\n        String autoConfigurationClass = autoConfigurationClasses[i];\n        if (autoConfigurationClass != null) {\n            // \n            outcomes[i] = getOutcome(autoConfigurationMetadata.get(autoConfigurationClass, \n                \"ConditionalOnWebApplication\"));\n        }\n    }\n    return outcomes;\n}\n\n/**\n * \n * springbootֵ֧web֣SERVLETREACTIVE\n */\nprivate ConditionOutcome getOutcome(String type) {\n    if (type == null) {\n        return null;\n    }\n    ConditionMessage.Builder message = ConditionMessage\n            .forCondition(ConditionalOnWebApplication.class);\n    // ָ SERVLET\n    if (ConditionalOnWebApplication.Type.SERVLET.name().equals(type)) {\n        if (!ClassNameFilter.isPresent(SERVLET_WEB_APPLICATION_CLASS, getBeanClassLoader())) {\n            return ConditionOutcome.noMatch(\n                message.didNotFind(\"servlet web application classes\").atAll());\n        }\n    }\n    // ָ REACTIVE\n    if (ConditionalOnWebApplication.Type.REACTIVE.name().equals(type)) {\n        if (!ClassNameFilter.isPresent(REACTIVE_WEB_APPLICATION_CLASS, getBeanClassLoader())) {\n            return ConditionOutcome.noMatch(\n                message.didNotFind(\"reactive web application classes\").atAll());\n        }\n    }\n    // ûָweb\n    if (!ClassNameFilter.isPresent(SERVLET_WEB_APPLICATION_CLASS, getBeanClassLoader())\n            && !ClassUtils.isPresent(REACTIVE_WEB_APPLICATION_CLASS, getBeanClassLoader())) {\n        return ConditionOutcome.noMatch(\n            message.didNotFind(\"reactive or servlet web application classes\").atAll());\n    }\n    return null;\n}\n\n```\n\nܼ򵥣߼Ϊ `@ConditionalOnWebApplication` ָͣж϶ӦǷڣжϷʽ `@ConditionalOnClass` жǷһ£ͶӦ£\n\n*   Servlet`org.springframework.web.context.support.GenericWebApplicationContext`\n*   Reactive`org.springframework.web.reactive.HandlerResult`\n\n### 8. `@ConditionalOnExpression``OnExpressionCondition#getMatchOutcome`\n\n `@ConditionalOnExpression` Ĵ `OnExpressionCondition#getOutcomes` \n\n```\n/**\n * ƥ\n */\n@Override\npublic ConditionOutcome getMatchOutcome(ConditionContext context, \n        AnnotatedTypeMetadata metadata) {\n    // ȡʽ\n    String expression = (String) metadata.getAnnotationAttributes(\n            ConditionalOnExpression.class.getName()).get(\"value\");\n    expression = wrapIfNecessary(expression);\n    ConditionMessage.Builder messageBuilder = ConditionMessage\n            .forCondition(ConditionalOnExpression.class, \"(\" + expression + \")\");\n    // ռλ\n    expression = context.getEnvironment().resolvePlaceholders(expression);\n    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();\n    if (beanFactory != null) {\n        // ʽֵ\n        boolean result = evaluateExpression(beanFactory, expression);\n        return new ConditionOutcome(result, messageBuilder.resultedIn(result));\n    }\n    return ConditionOutcome.noMatch(messageBuilder.because(\"no BeanFactory available.\"));\n}\n\n/**\n * ʽֵ\n */\nprivate Boolean evaluateExpression(ConfigurableListableBeanFactory beanFactory, \n        String expression) {\n    BeanExpressionResolver resolver = beanFactory.getBeanExpressionResolver();\n    if (resolver == null) {\n        resolver = new StandardBeanExpressionResolver();\n    }\n    // ʽֵ\n    BeanExpressionContext expressionContext = new BeanExpressionContext(beanFactory, null);\n    Object result = resolver.evaluate(expression, expressionContext);\n    return (result != null && (boolean) result);\n}\n\n```\n\nԿspringboot ͨ `BeanExpressionResolver#evaluate` ʽ spring ʽľͲչˡ\n\nˣspring עķ͵ˣҪ˵ǣspringboot  ע⣺\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-0e8d27c887fb6ca142672cad1c60e9de207.png)\n\nЩעжϷʽ뱾ĵķʽƣͲһһзˡ\n\n* * *\n\n_ԭӣ[https://my.oschina.net/funcy/blog/4921590](https://my.oschina.net/funcy/blog/4921590) ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע_"
  },
  {
    "path": "docs/Spring全家桶/SpringCloud/SpringCloudConfig.md",
    "content": "ڷֲʽ΢ϵͳУзж벻ļ֧֣Щļͨɸй properties  yml ʽڸ΢·£ application.properties  application.yml ȡ\n\nֽļɢڸеĹʽ⣺\n\n*   **Ѷȴ**ļɢڸ΢УԹ\n*   **ȫԵ**øԴ뱣ڴУй©\n*   **ʱЧԲ**΢е޸ĺ󣬱񣬷޷Ч\n*   ****޷ֶ֧̬־ءܿء\n\nΪ˽Щ⣬ͨǶʹĶýͳһϿԴкܶ࣬ٶȵ DisconfԱ diamond360  QConfЯ̵ Apollo ȶǽġSpring Cloud ҲԼķֲʽģǾ Spring Cloud Config\n\n## Spring Cloud Config\n\nSpring Cloud Config  Spring Cloud ŶӿĿΪ΢ܹи΢ṩлⲿ֧֡\n\n򵥵˵ǣSpring Cloud Config Խ΢ļд洢һⲿĴ洢ֿϵͳ Git SVN ȣУõͳһָ֧΢С\nSpring Cloud Config ֣\n\n*   Config ServerҲΪֲʽģһе΢ӦãòֿⲢΪͻṩȡϢϢͽϢķʽӿڡ\n*   Config Clientָ΢ܹеĸ΢ͨ Config Server ýй Config Sever лȡͼϢ\n\nSpring Cloud Config Ĭʹ Git 洢Ϣʹ Spirng Cloud Config ÷Ȼֶ֧΢õİ汾ǿʹ Git ͻ˹߷ضݽйͷʡ Git ⣬Spring Cloud Config ṩ˶洢ʽ֧֣ SVNػļϵͳȡ\n\n## Spring Cloud Config ԭ\n\nSpring Cloud Config ԭͼ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1019425240-0.png)\nͼ1Spring Cloud Config ԭ\n\nSpring Cloud Config £\n\n1.  άԱύļԶ̵ Git ֿ⡣\n2.  Config ˣֲʽģòֿ Git Config ͻ˱¶ȡõĽӿڡ\n3.  Config ͻͨ Config ˱¶Ľӿڣȡòֿеá\n4.  Config ͻ˻ȡϢַ֧С\n\n## Spring Cloud Config ص\n\nSpring Cloud Config ص㣺\n\n*   Spring Cloud Config  Spring Cloud Ŷӿ˵ Spring ׶ӣܹ Spring ̬ϵ޷켯ɡ\n*   Spring Cloud Config ΢ļд洢һⲿĴ洢ֿϵͳ GitУͳһ\n*   Spring Cloud Config Ľ REST ӿڵʽ¶΢Է΢ȡ\n*   ΢ͨ Spring Cloud Config ͳһȡԼϢ\n*   ÷仯ʱ΢Ҫɸ֪õı仯ԶȡӦá\n*   һӦÿж翪devԣtestprodȵȣԱͨ Spring Cloud Config ԲͬĸýйܹȷӦڻǨƺȻ֧С\n\nǾͨʵʾ Spring Cloud Config ʹá\n\n##  Config \n\n1\\.  Github ϴһΪ springcloud-config Ĳֿ⣨Repositoryȡòֿĵַ Github վڹû˵ȶܴܿڼػ⣬ǿ[](https://gitee.com/)ִиò\n\n2\\. ڸ spring-cloud-demo2 £һΪ micro-service-cloud-config-center-3344  Spring Boot ģ飬 pom.xml  Spring Cloud Config ¡\n\n\n\n\n\n```\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <parent>\n        <artifactId>spring-cloud-demo2</artifactId>\n        <groupId>net.biancheng.c</groupId>\n        <version>0.0.1-SNAPSHOT</version>\n    </parent>\n    <groupId>net.biancheng.c</groupId>\n    <artifactId>micro-service-cloud-config-center-3344</artifactId>\n    <version>0.0.1-SNAPSHOT</version>\n    <name>micro-service-cloud-config-center-3344</name>\n    <description>Demo project for Spring Boot</description>\n    <properties>\n        <java.version>1.8</java.version>\n    </properties>\n\n    <dependencies>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <!--ķ-->\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            <artifactId>spring-cloud-config-server</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-web</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-maven-plugin</artifactId>\n            </plugin>\n        </plugins>\n    </build>\n</project>\n\n```\n\n\n\n\n\n3\\.  micro-service-cloud-config-center-3344 ·/resources Ŀ¼£һΪ application.yml ļ¡\n\n\n\n\n\n```\n\nserver:\n  port: 3344 #˿ں\nspring:\n  application:\n    name: spring-cloud-config-center #\n  cloud:\n    config:\n      server:\n        git:\n          # Git ַhttps://gitee.com/java-mohan/springcloud-config.git\n          # ƣgiteeַ uri: https://github.com/javmohan/springcloud-config.git  (github վʽʹ gitee)\n          uri: https://gitee.com/java-mohan/springcloud-config.git\n          #ֿ\n          search-paths:\n            - springcloud-config\n          force-pull: true\n          # GitֿΪֿ⣬Բдû룬˽вֿҪд\n          # username: ********\n          # password: ********\n      #֧\n      label: master\n\neureka:                                            \n  client: #ͻעᵽ eureka б\n    service-url: \n      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/  #עᵽ Eureka Ⱥ\n```\n\n\n\n\n\n4\\.  micro-service-cloud-config-center-3344 ϣʹ @EnableConfigServer ע⿪ Spring Cloud Config Ĺܣ¡\n\n\n\n\n\n```\npackage net.biancheng.c;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.cloud.config.server.EnableConfigServer;\nimport org.springframework.cloud.netflix.eureka.EnableEurekaClient;\n\n@SpringBootApplication\n@EnableEurekaClient\n@EnableConfigServer\npublic class MicroServiceCloudConfigCenter3344Application {\n\n    public static void main(String[] args) {\n        SpringApplication.run(MicroServiceCloudConfigCenter3344Application.class, args);\n    }\n\n}\n\n```\n\n\n\n\n\n5\\. ½һΪ config-dev.yml ļϴ springcloud-config ֿ master ֧£config-dev.yml ¡\n\n```\nconfig:\n  info: c.biancheng.net\n  version: 1.0\n```\n\n\n6\\. עģȺ micro-service-cloud-config-center-3344ʹʡhttp://localhost:3344/master/config-dev.ymlͼ\n\n![Spring Cloud Config ļ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1019423313-2.png)\nͼ2ļ\n\nSpring Cloud Config 涨һļʹ±\n\n| ʹ                                  | ʾ                   |\n| ----------------------------------------- | ---------------------- |\n| /{application}/{profile}[/{label}]        | /config/dev/master     |\n| /{application}-{profile}.{suffix}         | /config-dev.yml        |\n| /{label}/{application}-{profile}.{suffix} | /master/config-dev.yml |\n\nʹڸ˵¡\n\n*   {application}Ӧƣļƣ config-dev\n*   {profile}һĿͨпdev汾ԣtest汾prod汾ļ application-{profile}.yml ʽ֣ application-dev.ymlapplication-test.ymlapplication-prod.yml ȡ\n*   {label}Git ֧Ĭ master ֧ĬϷ֧µļʱòʡԣڶַʷʽ\n*   {suffix}ļĺ׺ config-dev.yml ĺ׺Ϊ yml\n\nͨ׹ϾֱӶļзʡ\n\n7\\. Ϸʡhttp://localhost:3344/config-dev.ymlͼ\n\n![Spring Cloud Config ļ2](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1019422606-3.png)\nͼ3Spring Cloud Config ļ\n\n8\\. Ϸʡhttp://localhost:3344/config/dev/master¡\n\n```\n{\"name\":\"config\",\"profiles\":[\"dev\"],\"label\":\"master\",\"version\":\"9caafcc3498e04147463482f8b29e925e8afcc3a\",\"state\":null,\"propertySources\":[{\"name\":\"https://gitee.com/java-mohan/springcloud-config.git/config-dev.yml\",\"source\":{\"config.info\":\"c.biancheng.net\",\"config.version\":1.0}}]}\n```\n\nԴǾ˶ Spring Cloud Config ˵ĴͲԡ\n\n##  Config ͻ\n\n1\\. ڸ spring-cloud-demo2 £һΪ micro-service-cloud-config-client-3355  Spring Boot ģ飬 pom.xml  Spring Cloud Config ͻ˵¡\n\n\n\n\n\n```\n\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <parent>\n        <artifactId>spring-cloud-demo2</artifactId>\n        <groupId>net.biancheng.c</groupId>\n        <version>0.0.1-SNAPSHOT</version>\n    </parent>\n    <groupId>net.biancheng.c</groupId>\n    <artifactId>micro-service-cloud-config-client-3355</artifactId>\n    <version>0.0.1-SNAPSHOT</version>\n    <name>micro-service-cloud-config-client-3355</name>\n    <description>Demo project for Spring Boot</description>\n    <properties>\n        <java.version>1.8</java.version>\n    </properties>\n    <dependencies>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-web</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-devtools</artifactId>\n            <scope>runtime</scope>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>org.projectlombok</groupId>\n            <artifactId>lombok</artifactId>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n\n        <!--Spring Cloud Config ͻ-->\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            <artifactId>spring-cloud-starter-config</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-maven-plugin</artifactId>\n                <configuration>\n                    <excludes>\n                        <exclude>\n                            <groupId>org.projectlombok</groupId>\n                            <artifactId>lombok</artifactId>\n                        </exclude>\n                    </excludes>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n</project>\n```\n\n\n\n\n\n2\\.  micro-service-cloud-config-client-3355 ·/resources Ŀ¼£һΪ bootstrap.yml ļ¡\n\n\n\n\n\n```\n#bootstrap.yml ϵͳģȼ application.yml ⲿò\nserver:\n  port: 3355 #˿ں\nspring:\n  application:\n    name: spring-cloud-config-client #\n  cloud:\n    config:\n      label: master #֧\n      name: config  #ļƣconfig-dev.yml е config\n      profile: dev  #  config-dev.yml е dev\n      #ﲻҪ http:// ޷ȡ\n      uri: http://localhost:3344 #Spring Cloud Config ˣģַ\n\neureka:\n  client: #ͻעᵽ eureka б\n    service-url:\n      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/  #עᵽ Eureka Ⱥ\n```\n\n\n\n\n\n3\\.  net.biancheng.c.controller £һΪ ConfigClientController ࣬ͨȡļеã¡\n\n\n\n\n\n```\n\npackage net.biancheng.c.controller;\n\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\n//ȡָļݣչʾҳ\n@RestController\npublic class ConfigClientController {\n    @Value(\"${server.port}\")\n    private String serverPort;\n\n    @Value(\"${config.info}\")\n    private String configInfo;\n\n    @Value(\"${config.version}\")\n    private String configVersion;\n\n    @GetMapping(value = \"/getConfig\")\n    public String getConfig() {\n        return \"info\" + configInfo + \"<br/>version\" + configVersion + \"<br/>port\" + serverPort;\n    }\n}\n```\n\n\n\n\n\n4\\.  micro-service-cloud-config-client-3355 ϣʹ @EnableEurekaClient ע⿪ Eureka ͻ˹ܣ¡\n\n\n\n\n\n```\n\npackage net.biancheng.c;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.cloud.netflix.eureka.EnableEurekaClient;\n\n@SpringBootApplication\n@EnableEurekaClient\npublic class MicroServiceCloudConfigClient3355Application {\n\n    public static void main(String[] args) {\n        SpringApplication.run(MicroServiceCloudConfigClient3355Application.class, args);\n    }\n\n}\n\n```\n\n\n\n\n\n5\\.  micro-service-cloud-config-client-3355ʹʡhttp://localhost:3355/getConfig,ͼ\n\n![Config ͻ˻ȡϢ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1019422604-4.png)\nͼ4Spring Cloud Config ͻ˻ȡϢ\n\n6\\. ļ config-dev.yml  config.version ֵ޸Ϊ 2.0¡\n\n```\nconfig:\n  info: c.biancheng.net\n  version: 2.0\n```\n\n\n7\\.  Eureka עģȺ micro-service-cloud-config-center-3344 ʹʡhttp://localhost:3344/master/config-dev.ymlͼ\n\n![Ļȡ޸ĺļ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/10194255O-5.png)\nͼ5Ļȡ޸ĺļ\n\nͼ 6 ԿѾɹػȡ޸ĺá\n\n8\\. ٴηʡhttp://localhost:3355/getConfigͨ Spring Cloud Config ͻ˻ȡ޸ĺϢͼ\n\n![Config ͻ˻ȡϢ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1019422604-4.png)\nͼ6Spring Cloud Config ͻ˻ȡ޸ĺϢ\n\n9\\.  micro-service-cloud-config-client-3355ٴʹ÷ʡhttp://localhost:3355/getConfigͼ\n\n![Config ͻ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1019425023-7.png)\nͼ7 Spring Cloud Config ͻ˻ȡ\n\nͨʵǿԵõ 2 ۣ\n\n*   øºSpring Cloud Config ˣServerֱӴ Git ֿлȡµá\n*    Spring Cloud Config ͻˣClient޷ͨ Spring Cloud Config ˻ȡµϢ\n\n## ֶˢ\n\nΪ˽ Config ͻ޷ȡõ⣬ǾͶ micro-service-cloud-config-client-3355 и죬첽¡\n\n1\\.  micro-service-cloud-config-client-3355  pom.xml  Spring Boot actuator ģ顣\n\n\n\n\n\n```\n\n\n<dependency>\n    <groupId>org.springframework.boot</groupId>\n    <artifactId>spring-boot-starter-actuator</artifactId>\n</dependency>\n\n```\n\n\n\n\n\n2\\. ļ bootstrap.yml ãⱩ¶ Spring Boot actuator ļؽڵ㡣\n\n\n\n\n\n```\n\n# Spring Boot 2.50 actuator ˴Ľڵ㣬ֻ¶ health ڵ㣬ã*Ϊ˿еĽڵ\nmanagement:\n  endpoints:\n    web:\n      exposure:\n        include: \"*\"   # * yaml ļڹؼ֣Ҫ\n\n```\n\n\n\n\n\n\n3\\.  ConfigClientController ʹ @RefreshScope ע⿪ˢ£¡\n\n\n\n\n\n```\n\npackage net.biancheng.c.controller;\n\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.cloud.context.config.annotation.RefreshScope;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\n// ȡָļݣչʾҳ\n@RefreshScope //Ϊöֶ̬Ļȡµgit ã actuator ؼ RefreshScope\n@RestController\npublic class ConfigClientController {\n    @Value(\"${server.port}\")\n    private String serverPort;\n\n    @Value(\"${config.info}\")\n    private String configInfo;\n\n    @Value(\"${config.version}\")\n    private String configVersion;\n\n    @GetMapping(value = \"/getConfig\")\n    public String getConfig() {\n        return \"info\" + configInfo + \"<br/> version\" + configVersion + \"<br/>port\" + serverPort;\n    }\n}\n```\n\n\n\n\n\n4\\.  micro-service-cloud-config-client-3355Ȼļ config-dev.yml е config.version ޸Ϊ 3.0¡\n\n```\nconfig:\n  info: c.biancheng.net\n  version: 3.0\n```\n\n\n5\\. ʹٴηʡhttp://localhost:3355/getConfigͼ\n\n![Config ͻ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1019425023-7.png)\nͼ8 Spring Cloud Config ͻ˺ȡ\n\nͼ 9 ԿʹǶ Spring Cloud Config ͻ˽˸죬ҲȻ޷ֱӻȡá\n\n6\\. дڣʹһ POST ˢ Spring Cloud Config 3355 ͻˣ֪ͨͻļѾ޸ģҪȥá\n\n```curl -X POST \"http://localhost:3355/actuator/refresh\"```\n\n7. ʹٴηʡhttp://localhost:3355/getConfigͼ\n\n![ֶˢ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/101942C62-9.png)\nͼ9ֶˢ¿ͻ\n\n#### ֶˢõ\n\nʵУͨ Config ͻˣ˿ںţ3355 Spring Boot actuator õı仯ʹǿڲ Config ͻ˵»ȡãԭͼ\n\n![Spring Cloud Config ֶˢ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1019424S8-10.png)\nͼ10Spring Cloud Congfig ֶˢ\n\nַʽȻ Config ͻ˲ܻȡõ⣬һȴҲǾֻҪòֿе÷ı䣬Ҫǰ Config ͻֶ POST ֪ͨȡá\n\n֪ν Config ͻʵһһķ΢ܹУһϵͳʮʮΪĳһļ޸Ķʮ΢ POST Ȼǲġ\n\nôûСһ֪ͨЧķʽأǿ϶ġSpring Cloud Config  Bus ͿʵõĶ̬ˢ¡\n\n## Config+Bus ʵõĶ̬ˢ\n\nSpring Cloud Bus ֱΪϢߣܹͨϢ RabbitMQKafka ȣ΢ܹеĸʵֹ㲥״̬ġ¼͵ȹܣʵ΢֮ͨŹܡ\n\nĿǰ Spring Cloud Bus ֧ϢRabbitMQ  Kafka\n\n#### Spring Cloud Bus Ļԭ\n\nSpring Cloud Bus ʹһϢһϢ TopicĬΪspringCloudBus Topic еϢᱻзʵѡеһˢʱSpring Cloud Bus Ϣ浽 Topic У Topic ķյϢԶѡ\n\n#### Spring Cloud Bus ̬ˢõԭ\n\n Spring Cloud Bus ƿʵֺܶ๦ܣ Spring Cloud Config ʵõĶ̬ˢ¾͵Ӧó֮һ\n\n Git ֿе÷˸ı䣬ֻҪĳһ񣨼ȿ Config ˣҲ Config ͻˣһ POST Spring Cloud Bus ͿͨϢ֪ͨȡãʵõĶ̬ˢ¡\n\nSpring Cloud Bus ̬ˢõĹԭͼʾ\n\n![bus+config ̬ˢ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/101942GY-11.png)\nͼ11Bus+Config ʵõĶ̬ˢ\n\nͼ 12 Spring Cloud Bus ʵõĶ̬ˢҪ²:\n\n1.   Git ֿе÷ıάԱ Config ˷һ POST ·Ϊ/actuator/refresh\n2.  Config ˽յ󣬻Ὣת Spring Cloud Bus\n3.  Spring Cloud Bus ӵϢ󣬻֪ͨ Config ͻˡ\n4.  Config ͻ˽յ֪ͨ Config ȡá\n5.   Config ͻ˶ȡµá\n\n#### Spring Cloud Bus ̬ˢãȫֹ㲥\n\n RabbitMQ Ϊʾʹ Config+Bus ʵõĶ̬ˢ¡\n\n1\\.  micro-service-cloud-config-center-3344  pom.xml У Spring Boot actuator ģ Spring Cloud Bus ¡\n\n\n\n\n\n```\n\n<!--ϢߣBus RabbitMQ ֧-->\n<dependency>\n    <groupId>org.springframework.cloud</groupId>\n    <artifactId>spring-cloud-starter-bus-amqp</artifactId>\n</dependency>\n<!--Spring Boot actuator ģ-->\n<dependency>\n    <groupId>org.springframework.boot</groupId>\n    <artifactId>spring-boot-starter-actuator</artifactId>\n</dependency>\n\n```\n\n\n\n\n\n2\\.  micro-service-cloud-config-center-3344 ļ application.yml У RabbitMQ  Spring Boot actuator ã¡\n\n\n\n\n\n```\n##### RabbitMQ ã15672 web Ķ˿ڣ5672  MQ ķʶ˿###########\nspring:\n  rabbitmq:\n    host: 127.0.0.1\n    port: 5672\n    username: guest\n    password: guest\n\n# Spring Boot 2.50 actuator ˴Ľڵ㣬ֻ¶ heath ڵ㣬ã*Ϊ˿еĽڵ\nmanagement:\n  endpoints:\n    web:\n      exposure:\n        include: 'bus-refresh'\n\n```\n\n\n\n\n\n3\\.  micro-service-cloud-config-client-3355  pom.xml У Spring Cloud Bus ¡\n\n\n\n\n\n```\n\n\n1.  <!--ϢߣBus RabbitMQ ֧-->\n2.  <dependency>\n3.  <groupId>org.springframework.cloud</groupId>\n4.  spring-cloud-starter-bus-amqp\n5.  </dependency>\n\n```\n\n\n\n\n\n4.  micro-service-cloud-config-client-3355 ļ bootstrap.yml á\n\n\n\n\n\n```\n\n<!--ϢߣBus RabbitMQ ֧-->\n<dependency>\n    <groupId>org.springframework.cloud</groupId>\n    <artifactId>spring-cloud-starter-bus-amqp</artifactId>\n</dependency>\n\n```\n\n\n\n\n\n5\\.  micro-service-cloud-config-client-3355½һΪ micro-service-cloud-config-client-bus-3366  Spring Boot ģ飨˿ںΪ 3366ļ bootstrap.yml á\n\n\n\n\n\n```\n\n#bootstrap.yml ϵͳģȼ application.yml ⲿò\nserver:\n  port: 3366  #˿ںΪ 3366\nspring:\n  application:\n    name: spring-cloud-config-client-bus\n\n  cloud:\n    config:\n      label: master #֧\n      name: config  #ļƣconfig-dev.yml е config\n      profile: dev  #ļĺ׺  config-dev.yml е dev\n      #ﲻҪ http:// ޷ȡ\n      uri: http://localhost:3344 #spring cloud ĵַ\n\n##### RabbitMQ ã15672 web Ķ˿ڣ5672  MQ ķʶ˿###########\n  rabbitmq:\n    host: 127.0.0.1\n    port: 5672\n    username: guest\n    password: guest\n###################### eureka  ####################\neureka:\n  client: #ͻעᵽ eureka б\n    service-url:\n      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/  #עᵽ Eureka Ⱥ\n\n# Spring Boot 2.50 actuator ˴Ľڵ㣬ֻ¶ heath ڵ㣬ã*Ϊ˿еĽڵ\nmanagement:\n  endpoints:\n    web:\n      exposure:\n        include: \"*\"   # * yaml ļڹؼ֣Ҫ\n\n```\n\n\n\n\n\n6\\.  micro-service-cloud-config-center-3344micro-service-cloud-config-client-3355ʹʡhttp://localhost:3355/getConfigͼ\n\n![Bus ̬ø](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1019422S4-12.png)\nͼ12Spring Cloud Bus ̬ˢ\n\n7\\.  micro-service-cloud-config-client-bus-3366ʹʡhttp://localhost:3366/getConfigͼ\n\n![Bus ̬](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/10194254S-13.png)\nͼ13Spring Cloud Bus ̬ˢ\n\n8.  ļ config-dev.yml е config.version ޸Ϊ 4.0¡\n\n```\nconfig:\n  info: c.biancheng.net\n  version: 4.0\n```\n\n\n9\\. дڣʹ micro-service-cloud-config-center-3344Config Serverһ POST ˢá\n\n```curl -X POST \"http://localhost:3344/actuator/bus-refresh\"```\n\n10. ʹٴηʡhttp://localhost:3355/getConfigͼ\n\n![bus ̬ˢ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1019423453-14.png)\nͼ14Spring Cloud Bus ̬ˢ\n\n11. ʹٴηʡhttp://localhost:3366/getConfigͼ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/10194222Y-15.png)\nͼ15Spring Cloud Bus ̬ˢ\n\n> ע⣺ʹ Spring Cloud Bus ʱ뱣֤ Bus ӵϢ RabbitMQѾȷװ\n\n#### Spring Cloud Bus ̬ˢã֪ͨ\n\nν֪ͨǲ֪ͨе Config ͻˣǸֻ֪ͨĳһ Config ͻˡ\n\nʹ Spring Cloud Bus ʵֶ֪ͨķʮּ򵥣ֻҪڷ POST ʱʹ¸ʽɡ\n\n```http://{hostname}:{port}/actuator/bus-refresh/{destination}```\n\n˵£\n\n*   {hostname} ʾ Config ˵ַȿҲ IP ַ\n*   {port}ʾ Config ˵Ķ˿ں.\n*   {destination}ʾҪ֪ͨ Config ͻˣ΢񣩣 Config ͻ˵ķspring.application.name+˿ںţserver.portɣֻ֪ͨ micro-service-cloud-config-client-3355 ˢãȡֵΪ spring-cloud-config-client:3355\n\nǾͨһ򵥵ʵʾ Spring Cloud Bus ̬ˢµĶ֪ͨ\n\n1\\. ļ config-dev.yml е config.version ޸Ϊ 5.0¡\n\n```\nconfig:\n  info: c.biancheng.net\n  version: 5.0\n```\n\n\n2\\. дڣʹ micro-service-cloud-config-center-3344 һ POST \n\n```curl -X POST \"http://localhost:3344/actuator/bus-refresh/spring-cloud-config-client:3355\"```\n\n3\\. ʹʡhttp://localhost:3355/getConfigͼ\n\n![Bus ֪ͨ 3355](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/10194233O-16.png)\nͼ16Spring Cloud Bus ֪ͨ\n\n4\\. ʹٴηʡhttp://localhost:3366/getConfigͼ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/10194222Y-15.png)\nͼ17Spring Cloud Bus ֪ͨ\n\n\n# ο\nhttps://lijunyi.xyz/docs/SpringCloud/SpringCloud.html#_2-2-x-%E5%88%86%E6%94%AF\nhttps://mp.weixin.qq.com/s/2jeovmj77O9Ux96v3A0NtA\nhttps://juejin.cn/post/6931922457741770760\nhttps://github.com/D2C-Cai/herring\nhttp://c.biancheng.net/springcloud\nhttps://github.com/macrozheng/springcloud-learning"
  },
  {
    "path": "docs/Spring全家桶/SpringCloud/SpringCloudConsul.md",
    "content": "Spring Cloud Consul Ϊ SpringBoot Ӧṩ Consul֧֣ConsulȿΪעʹãҲΪʹãĽ÷ϸܡ\n\n# Consul \n\nConsulHashiCorp˾ƳĿԴṩ΢ϵͳеķġߵȹܡЩеÿһԸҪʹãҲһʹԹȫλķ֮Consulṩһķ\n\nSpring Cloud Consul ԣ\n\n- ַ֧ConsulΪעʱ΢еӦÿConsulעԼҿԴConsulȡӦϢ\n- ֿ֧ͻ˸⣺RibbonSpring Cloud LoadBalancer\n- ֧ZuulZuulΪʱԴConsulעͷӦã\n- ֲַ֧ʽùConsulΪʱʹüֵ洢Ϣ\n- ֿ֧ߣ΢ϵͳͨ Control Bus ַ¼Ϣ\n\n# ʹConsulΪע\n\n# װConsul\n\n- ǴӹConsulַhttps://www.consul.io/downloads.html\n\n\n\n- ɺֻһexeļ˫У\n- Բ鿴汾ţ\n\n\n\n    consul --version\n\n\n\n- 鿴汾Ϣ£\n\n\n\n    Consul v1.6.1\n    Protocol 2 spoken by default, understands 2 to 3 (agent will automatically use protocol >2 when speaking to compatible agents)\n\n\n- ʹÿģʽ\n\n\n\n    consul agent -dev \n\n\n\n\n\n\n- ͨµַԷConsulҳhttp://localhost:8500\n\n\n\n# ӦעᵽConsul\n\nͨuser-serviceribbon-serviceʾ·ע뷢ֵĹܣҪǽӦԭEurekaעָ֧ΪConsulע֧֡\n\n- consul-user-serviceģconsul-ribbon-serviceģ飻\n- ޸ԭEurekaעֵᷢΪConsulģSpringBoot Actuator\n\n\n\n    <dependency>\n        <groupId>org.springframework.cloud</groupId>\n        spring-cloud-starter-consul-discovery\n    </dependency>\n    <dependency>\n        <groupId>org.springframework.boot</groupId>\n        spring-boot-starter-actuator\n    </dependency>\n\n\n\n\n- ޸ļapplication.ymlEurekaעᷢøΪConsulģ\n\n\n\n    server:\n      port: 8206\n    spring:\n      application:\n        name: consul-user-service\n      cloud:\n        consul: #Consulעᷢ\n          host: localhost\n          port: 8500\n          discovery:\n            service-name: ${spring.application.name}\n\n\n\n\n- consul-user-serviceһconsul-ribbon-serviceConsulҳϿԿϢ\n\n\n\n# ؾ⹦\n\nconsul-user-serviceconsul-ribbon-serviceĬϻȥĽӿڣǵconsul-ribbon-serviceĽӿʾ¸ؾ⹦ܡ\n\nεýӿڣhttp://localhost:8308/user/1 Էconsul-user-serviceĿ̨ӡϢ\n\n\n\n    2019-10-20 10:39:32.580  INFO 12428 --- [io-8206-exec-10] c.macro.cloud.controller.UserController  : idȡûϢûΪmacro\n\n\n\n\n# ʹConsulΪ\n\nͨconsul-config-clientģ飬ConsulϢʾùĹܡ\n\n# consul-config-clientģ\n\n- pom.xml\n\n\n\n    <dependency>\n        <groupId>org.springframework.cloud</groupId>\n        spring-cloud-starter-consul-config\n    </dependency>\n    <dependency>\n        <groupId>org.springframework.cloud</groupId>\n        spring-cloud-starter-consul-discovery\n    </dependency>\n\n\n\n- ļapplication.ymlõdevã\n\n\n\n    spring:\n      profiles:\n        active: dev\n\n\n\n\n- ļbootstrap.ymlҪǶConsulùܽã\n\n\n\n    server:\n      port: 9101\n    spring:\n      application:\n        name: consul-config-client\n      cloud:\n        consul:\n          host: localhost\n          port: 8500\n          discovery:\n            serviceName: consul-config-client\n          config:\n            enabled: true #ǷĹ\n            format: yaml #ֵĸʽ\n            prefix: config #Ŀ¼\n            profile-separator: ':' #õķָ\n            data-key: data #key֣ConsulK/V洢ô洢ڶӦKV\n\n\n- ConfigClientControllerConsulлȡϢ\n\n\n\n    /**\n     * Created by macro on 2019/9/11.\n     */\n    @RestController\n    @RefreshScope\n    public class ConfigClientController {\n    \n        @Value(\"${config.info}\")\n        private String configInfo;\n    \n        @GetMapping(\"/configInfo\")\n        public String getConfigInfo() {\n            return configInfo;\n        }\n    }\n\n\n# Consul\n\n- consulô洢keyΪ:\n\n\n\n    config/consul-config-client:dev/data\n\n\n\n\n\n- consulô洢valueΪ\n\n\n\n    config:\n      info: \"config info for dev\"\n\n\n\n\n- 洢Ϣͼ£\n\n\n\n- consul-config-clientýӿڲ鿴Ϣhttp://localhost:9101/configInfo\n\n\n\n    config info for dev\n\n\n\n\n# ConsulĶ̬ˢ\n\nֻҪ޸ConsulеϢٴεò鿴õĽӿڣͻᷢѾˢ¡ʹSpring Cloud ConfigʱҪýӿڣͨSpring Cloud BusˢáConsulʹԴControl Bus ʵһ¼ݻƣӶʵ˶̬ˢ¹ܡ\n\n# ʹõģ\n\n\n\n    springcloud-learning\n     consul-config-client -- ʾconsulΪĵconsulͻ\n     consul-user-service -- עᵽconsulṩUserCRUDӿڵķ\n     consul-service -- עᵽconsulribbonòԷ\n\n\n\n\n# ĿԴַ\n\nhttps://github.com/macrozheng/springcloud-learning\n# ο\nhttps://lijunyi.xyz/docs/SpringCloud/SpringCloud.html#_2-2-x-%E5%88%86%E6%94%AF\nhttps://mp.weixin.qq.com/s/2jeovmj77O9Ux96v3A0NtA\nhttps://juejin.cn/post/6931922457741770760\nhttps://github.com/D2C-Cai/herring\nhttp://c.biancheng.net/springcloud\nhttps://github.com/macrozheng/springcloud-learning"
  },
  {
    "path": "docs/Spring全家桶/SpringCloud/SpringCloudEureka.md",
    "content": "Eureka һԴڹϣʻ㣬ǡˡ˼Eureka  Netflix ˾һԴķע뷢\n\nSpring Cloud  Eureka  Netflix еԴ RibbonFeign Լ Hystrix ȣһϽ Spring Cloud Netflix ģУϺȫΪ Spring Cloud Netflix Eureka\n\nEureka  Spring Cloud Netflix ģģ飬 Spring Cloud  Netflix Eureka ĶηװҪ Spring Cloud ķע뷢ֹܡ\n\nSpring Cloud ʹ Spring Boot ˼Ϊ Eureka ԶãԱֻҪע⣬ܽ Spring Boot ΢ɵ Eureka ϡ\n\n## Eureka \n\nEureka  CSClient/Serverͻ/ ܹ\n\n*   **Eureka Server**Eureka עģҪṩעṦܡ΢ʱὫԼķעᵽ Eureka ServerEureka Server άһ÷б洢עᵽ Eureka Server Ŀ÷ϢЩ÷ Eureka Server Ĺֱۿ\n*   **Eureka Client**Eureka ͻˣָͨ΢ϵͳи΢Ҫں Eureka Server н΢ӦEureka Client  Eureka Server ĬΪ 30 룩 Eureka Server ڶûнյĳ Eureka Client Eureka Server ӿ÷бƳĬ 90 룩\n\n> עָһζʱ͵ԶϢöԷ֪ԼȷӵЧԡ󲿷 CS ܹӦó򶼲ƣ˺Ϳͻ˶Էͨǿͻ˷жϿͻǷߡ\n\n## Eureka ע뷢\n\nEureka ʵַע뷢ֵԭͼʾ\n\n![Eureka ע뷢](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1010305209-0.png)\nͼ1Eureka ԭͼ\n\nͼй漰 3 ɫ\n\n*   **עģRegister Service**һ Eureka Serverṩעͷֹܡ\n*   **ṩߣProvider Service**һ Eureka ClientṩԼṩķעᵽעģԹ߷֡\n*   **ߣConsumer Service**һ Eureka ClientѷԴӷעĻȡбķ\n\nEureka ʵַע뷢ֵ£\n\n1.  һ Eureka Server Ϊעģ\n2.  ṩ Eureka Client ʱѵǰϢԷspring.application.nameķʽעᵽעģ\n3.   Eureka Client ʱҲעע᣻\n4.  ߻ȡһݿ÷ббаעᵽעĵķϢṩߺϢ\n5.  ڻ˿÷б󣬷ͨ HTTP ϢмԶ̵÷ṩṩķ\n\nעģEureka ServerݵĽɫʮҪǷṩߺͷ֮ṩֻнԼķעᵽעĲſܱߵãҲֻͨעĻȡ÷б󣬲ܵķ\n\n## ʾ 1\n\n棬ͨһչʾ Eureka ʵַע뷢ֵġ\n\n#### 1\\. ̣Maven Project\n\nڱУ漰 Spring Boot ΢Ϊ˷ǲ Maven Ķ Module ṹһ Project  Module̡\n\nһΪ spring-cloud-demo2  Maven  Ȼڸ̵ pom.xml ʹ dependencyManagement  Spring Cloud İ汾¡\n\n\n\n\n\n```\n\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <packaging>pom</packaging>\n    <modules>\n        <module>micro-service-cloud-api</module>\n    </modules>\n    <parent>\n        <groupId>org.springframework.boot</groupId>\n        <artifactId>spring-boot-starter-parent</artifactId>\n        <version>2.3.6.RELEASE</version>\n        <relativePath/> <!-- lookup parent from repository -->\n    </parent>\n    <groupId>net.biancheng.c</groupId>\n    <artifactId>spring-cloud-demo2</artifactId>\n    <version>0.0.1-SNAPSHOT</version>\n    <properties>\n        <maven.compiler.source>8</maven.compiler.source>\n        <maven.compiler.target>8</maven.compiler.target>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <maven.compiler.source>1.8</maven.compiler.source>\n        <maven.compiler.target>1.8</maven.compiler.target>\n        <junit.version>4.12</junit.version>\n        <log4j.version>1.2.17</log4j.version>\n        <lombok.version>1.16.18</lombok.version>\n    </properties>\n    <dependencyManagement>\n        <dependencies>\n            <!--ʹ dependencyManagement  Spring Cloud İ汾\n            ڵ Module  Spring Cloud ʱͲİ汾Ϣ\n            ֤ Spring Cloud һ-->\n            <dependency>\n                <groupId>org.springframework.cloud</groupId>\n                <artifactId>spring-cloud-dependencies</artifactId>\n                <version>Hoxton.SR12</version>\n                <type>pom</type>\n                <scope>import</scope>\n            </dependency>\n        </dependencies>\n    </dependencyManagement>\n    <build>\n        <finalName>microservicecloud</finalName>\n        <resources>\n            <resource>\n                <directory>src/main/resources</directory>\n                <filtering>true</filtering>\n            </resource>\n        </resources>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-resources-plugin</artifactId>\n                <configuration>\n                    <delimiters>\n                        <delimit>$</delimit>\n                    </delimiters>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n</project>\n\n```\n\n\n\n\n\n#### 2\\. ģ飨Maven Module\n\n1) £һΪ micro-service-cloud-api  Maven Modulemicro-service-cloud-api pom.xml ¡\n\n\n\n\n\n```\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>spring-cloud-demo2</artifactId>\n        <groupId>net.biancheng.c</groupId>\n        <version>0.0.1-SNAPSHOT</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>micro-service-cloud-api</artifactId>\n    <properties>\n        <maven.compiler.source>8</maven.compiler.source>\n        <maven.compiler.target>8</maven.compiler.target>\n    </properties>\n    <dependencies>\n        <dependency>\n            <groupId>org.projectlombok</groupId>\n            <artifactId>lombok</artifactId>\n        </dependency>\n    </dependencies>\n</project>\n\n```\n\n\n\n\n\n> עmicro-service-cloud-api ̵Ĺģ飬һЩģ鹲еݣʵࡢࡢȡģҪʹùģеʱֻҪ pom.xml 빫ģɡ\n\n2)  micro-service-cloud-api  net.biancheng.c.entity £һΪ Dept ʵ࣬¡\n\n\n\n\n\n```\n\npackage net.biancheng.c.entity;\n\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport lombok.experimental.Accessors;\n\nimport java.io.Serializable;\n\n@NoArgsConstructor //޲ι캯\n@Data // ṩgetsetequalshashCodecanEqualtoString \n@Accessors(chain = true)\npublic class Dept implements Serializable {\n    private Integer deptNo;\n    private String deptName;\n    private String dbSource;\n}\n\n```\n\n\n\n\n\n#### 3\\. ע\n\n1) ´һΪ micro-service-cloud-eureka-7001  Spring Boot Module Ϊעģ pom.xml \n\n\n\n\n\n```\n\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <!--̵̳ POM-->\n    <parent>\n        <artifactId>spring-cloud-demo2</artifactId>\n        <groupId>net.biancheng.c</groupId>\n        <version>0.0.1-SNAPSHOT</version>\n    </parent>\n    <groupId>net.biancheng.c</groupId>\n    <artifactId>micro-service-cloud-eureka-7001</artifactId>\n    <version>0.0.1-SNAPSHOT</version>\n    <name>micro-service-cloud-eureka-7001</name>\n    <description>Demo project for Spring Boot</description>\n\n    <properties>\n        <java.version>1.8</java.version>\n    </properties>\n\n    <dependencies>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-web</artifactId>\n        </dependency>\n        <!--Ϊע Eureka Server -->\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>\n        </dependency>\n        <!--devtools  lombok Ϊģ飬ʵѡ-->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-devtools</artifactId>\n            <scope>runtime</scope>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>org.projectlombok</groupId>\n            <artifactId>lombok</artifactId>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-maven-plugin</artifactId>\n                <configuration>\n                    <excludes>\n                        <exclude>\n                            <groupId>org.projectlombok</groupId>\n                            <artifactId>lombok</artifactId>\n                        </exclude>\n                    </excludes>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n</project>\n```\n\n\n\n\n\n2)  micro-service-cloud-eureka-7001 ·/resouces Ŀ¼£һļ application.yml¡\n\n\n\n\n\n```\nserver:\n  port: 7001  # Module Ķ˿ں\n\neureka:\n  instance:\n    hostname: localhost #eureka˵ʵƣ\n\n  client:\n    register-with-eureka: false #falseʾעעԼ\n    fetch-registry: false #falseʾԼ˾עģҵְάʵҪȥ\n    service-url:\n      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ #ע\n```\n\n\n\n\n\n3)  micro-service-cloud-eureka-7001 ʹ @EnableEurekaServer ע⿪עĹܣעᣬ¡\n\n\n\n\n\n```\n\npackage net.biancheng.c;\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;\n@SpringBootApplication\n@EnableEurekaServer // Eureka server,΢ע\npublic class MicroServiceCloudEureka7001Application {\n    public static void main(String[] args) {\n        SpringApplication.run(MicroServiceCloudEureka7001Application.class, args);\n    }\n}\n\n```\n\n\n\n\n\n4)  micro-service-cloud-eureka-7001ʹ Eureka עҳַΪhttp://localhost:7001/ͼ\n\n![Eureka Server 7001 ע](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1010306360-1.png)\n\nͼ2Eureka 7001 ע\n\n#### 4\\. ṩ\n\n1) ´һΪ micro-service-cloud-provider-dept-8001  Spring Boot Module pom.xml \n\n\n\n\n\n```\n\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <!--븸pom-->\n    <parent>\n        <artifactId>spring-cloud-demo2</artifactId>\n        <groupId>net.biancheng.c</groupId>\n        <version>0.0.1-SNAPSHOT</version>\n    </parent>\n\n    <groupId>net.biancheng.c</groupId>\n    <artifactId>micro-service-cloud-provider-dept-8001</artifactId>\n    <version>0.0.1-SNAPSHOT</version>\n    <name>micro-service-cloud-provider-dept-8001</name>\n    <description>Demo project for Spring Boot</description>\n\n    <properties>\n        <java.version>1.8</java.version>\n    </properties>\n    <dependencies>\n        <!--Spring Boot Web-->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-web</artifactId>\n        </dependency>\n        <!--devtools -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-devtools</artifactId>\n            <scope>runtime</scope>\n            <optional>true</optional>\n        </dependency>\n        <!--Spring Boot -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <!--빫ģ-->\n        <dependency>\n            <groupId>net.biancheng.c</groupId>\n            <artifactId>micro-service-cloud-api</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n        <!--junit -->\n        <dependency>\n            <groupId>junit</groupId>\n            <artifactId>junit</artifactId>\n            <version>4.12</version>\n        </dependency>\n        <!--mysql -->\n        <dependency>\n            <groupId>mysql</groupId>\n            <artifactId>mysql-connector-java</artifactId>\n            <version>5.1.49</version>\n        </dependency>\n        <!--logback ־-->\n        <dependency>\n            <groupId>ch.qos.logback</groupId>\n            <artifactId>logback-core</artifactId>\n        </dependency>\n        <!-- mybatis -->\n        <dependency>\n            <groupId>org.mybatis.spring.boot</groupId>\n            <artifactId>mybatis-spring-boot-starter</artifactId>\n            <version>2.2.0</version>\n        </dependency>\n        <!-- ޸ĺЧȲ -->\n        <dependency>\n            <groupId>org.springframework</groupId>\n            <artifactId>springloaded</artifactId>\n            <version>1.2.8.RELEASE</version>\n        </dependency>\n        <!-- Eureka Client עᵽ Eureka Server-->\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>\n        </dependency>\n        <!-- Spring Boot ģ-->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-actuator</artifactId>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <!--mybatisԶɴ-->\n            <plugin>\n                <groupId>org.mybatis.generator</groupId>\n                <artifactId>mybatis-generator-maven-plugin</artifactId>\n                <version>1.4.0</version>\n                <configuration>\n                    <configurationFile>src/main/resources/mybatis-generator/generatorConfig.xml</configurationFile>\n                    <verbose>true</verbose>\n                    <!-- Ƿ񸲸ǣtrueʾ滻ɵJAVAļfalse򲻸 -->\n                    <overwrite>true</overwrite>\n                </configuration>\n                <dependencies>\n                    <!--mysql-->\n                    <dependency>\n                        <groupId>mysql</groupId>\n                        <artifactId>mysql-connector-java</artifactId>\n                        <version>5.1.49</version>\n                    </dependency>\n                    <dependency>\n                        <groupId>org.mybatis.generator</groupId>\n                        <artifactId>mybatis-generator-core</artifactId>\n                        <version>1.4.0</version>\n                    </dependency>\n                </dependencies>\n            </plugin>\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-maven-plugin</artifactId>\n            </plugin>\n        </plugins>\n    </build>\n</project>\n\n```\n\n\n\n\n\n2)  micro-service-cloud-provider-dept-8001 ·/resources Ŀ¼£ļ application.yml¡\n\n\n\n\n\n```\n\nserver:\n  port: 8001 #˿ں\nspring:\n  application:\n    name: microServiceCloudProviderDept  #΢ƣⱩ©΢ƣʮҪ\n################################################## JDBC ͨ  ##########################################\n  datasource:\n    username: root        #ݿ½û\n    password: root        #ݿ½\n    url: jdbc:mysql://127.0.0.1:3306/bianchengbang_jdbc       #ݿurl\n    driver-class-name: com.mysql.jdbc.Driver                  #ݿ\n\n###############################  spring.config.import=configserver:##################\n#  cloud:\n#    config:\n#      enabled: false\n###################################### MyBatis  ######################################\nmybatis:\n  # ָ mapper.xml λ\n  mapper-locations: classpath:mybatis/mapper/*.xml\n  #ɨʵλ,ڴ˴ָɨʵİ mapper.xml оͿԲдʵȫ·\n  type-aliases-package: net.biancheng.c.entity\n  configuration:\n    #ĬϿշԲø\n    map-underscore-to-camel-case: true\n########################################### Spring cloud Զƺ ip ַ###############################################\neureka:\n  client: #ͻעᵽ eureka б\n    service-url:\n      defaultZone: http://eureka7001.com:7001/eureka  #ַ 7001ע application.yml б¶עַ 棩\n\n  instance:\n    instance-id: spring-cloud-provider-8001 #ԶϢ\n    prefer-ip-address: true  #ʾ· ip ַ\n########################################## spring cloud ʹ Spring Boot actuator Ϣ###################################\n# Spring Boot 2.50 actuator ˴Ľڵ㣬ֻ¶ heath ڵ㣬ã*Ϊ˿еĽڵ\nmanagement:\n  endpoints:\n    web:\n      exposure:\n        include: \"*\"   # * yaml ļڹؼ֣Ҫ\ninfo:\n  app.name: micro-service-cloud-provider-dept\n  company.name: c.biancheng.net\n  build.aetifactId: @project.artifactId@\n  build.version: @project.version@\n```\n\n\n\n\n\n3)  net.biancheng.c.mapper ´һΪ DeptMapper Ľӿڣ¡\n\n\n\n\n\n```\n\npackage net.biancheng.c.mapper;\nimport net.biancheng.c.entity.Dept;\nimport org.apache.ibatis.annotations.Mapper;\nimport java.util.List;\n@Mapper\npublic interface DeptMapper {\n    //ȡ\n    Dept selectByPrimaryKey(Integer deptNo);\n    //ȡеȫ\n    List<Dept> GetAll();\n}\n\n```\n\n\n\n\n\n4)  resources/mybatis/mapper/ Ŀ¼£һΪ DeptMapper.xml  MyBatis ӳļ¡\n\n\n\n\n\n```\n\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"net.biancheng.c.mapper.DeptMapper\">\n    <resultMap id=\"BaseResultMap\" type=\"net.biancheng.c.entity.Dept\">\n        <id column=\"dept_no\" jdbcType=\"INTEGER\" property=\"deptNo\"/>\n        <result column=\"dept_name\" jdbcType=\"VARCHAR\" property=\"deptName\"/>\n        <result column=\"db_source\" jdbcType=\"VARCHAR\" property=\"dbSource\"/>\n    </resultMap>\n\n    <sql id=\"Base_Column_List\">\n        dept_no\n        , dept_name, db_source\n    </sql>\n\n    <select id=\"selectByPrimaryKey\" parameterType=\"java.lang.Integer\" resultMap=\"BaseResultMap\">\n        select\n        <include refid=\"Base_Column_List\"/>\n        from dept\n        where dept_no = #{deptNo,jdbcType=INTEGER}\n    </select>\n    <select id=\"GetAll\" resultType=\"net.biancheng.c.entity.Dept\">\n        select *\n        from dept;\n    </select>\n</mapper>\n\n```\n\n\n\n\n\n5)  net.biancheng.c.service ´һΪ DeptService Ľӿڣ¡\n\n\n\n\n\n```\n\n\npackage net.biancheng.c.service;\nimport net.biancheng.c.entity.Dept;\nimport java.util.List;\npublic interface DeptService {\n    Dept get(Integer deptNo);\n    List<Dept> selectAll();\n}\n\n```\n\n\n\n\n\n6)  net.biancheng.c.service.impl ´ DeptService ӿڵʵ DeptServiceImpl¡\n\n\n\n\n\n```\npackage net.biancheng.c.service.impl;\n\nimport net.biancheng.c.entity.Dept;\nimport net.biancheng.c.mapper.DeptMapper;\nimport net.biancheng.c.service.DeptService;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Service;\n\nimport java.util.List;\n\n@Service(\"deptService\")\npublic class DeptServiceImpl implements DeptService {\n    @Autowired\n    private DeptMapper deptMapper;\n\n    @Override\n    public Dept get(Integer deptNo) {\n        return deptMapper.selectByPrimaryKey(deptNo);\n    }\n\n    @Override\n    public List<Dept> selectAll() {\n        return deptMapper.GetAll();\n    }\n}\n\n```\n\n\n\n\n\n7)  net.biancheng.c.controller ´һΪ DeptController  Controller ࣬¡\n\n\n\n\n\n```\n\npackage net.biancheng.c.controller;\n\nimport lombok.extern.slf4j.Slf4j;\nimport net.biancheng.c.entity.Dept;\nimport net.biancheng.c.service.DeptService;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.util.List;\n\n/**\n* ṩߵĿƲ\n* author:c c.biancheng.net\n*/\n@RestController\n@Slf4j\npublic class DeptController {\n    @Autowired\n    private DeptService deptService;\n  \n    @Value(\"${server.port}\")\n    private String serverPort;\n\n    @RequestMapping(value = \"/dept/get/{id}\", method = RequestMethod.GET)\n    public Dept get(@PathVariable(\"id\") int id) {\n        return deptService.get(id);\n    }\n\n    @RequestMapping(value = \"/dept/list\", method = RequestMethod.GET)\n    public List<Dept> list() {\n        return deptService.selectAll();\n    }\n}\n```\n\n\n\n\n\n8)  micro-service-cloud-provider-dept-8001 ϣʹ @EnableEurekaClient ע⿪ Eureka ͻ˹ܣעᵽעģEureka Server¡\n\n\n\n\n\n```\n\n\npackage net.biancheng.c;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.cloud.netflix.eureka.EnableEurekaClient;\n\n@SpringBootApplication\n@EnableEurekaClient // Spring cloud Eureka ͻˣԶעᵽ Eureka Server ע\npublic class MicroServiceCloudProviderDept8001Application {\n\n    public static void main(String[] args) {\n        SpringApplication.run(MicroServiceCloudProviderDept8001Application.class, args);\n    }\n}\n\n```\n\n\n\n\n\n9)  micro-service-cloud-eureka-7001  micro-service-cloud-provider-dept-8001ʹٴ Eureka עҳhttp://localhost:7001/ͼ\n\n![Eureka Client עᵽע](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1010304436-2.png)\nͼ3ṩעᵽע\n\nͼ 3 Կ Instances currently registered with Eureka עᵽ Eureka Server ʵѡѾһϢѾзעᵽ Eureka Server ˡ\n\nInstances currently registered with Eureka ѡаݣ\n\n*   ApplicationMICROSERVICECLOUDPROVIDERDEPTȡֵΪ micro-service-cloud-provider-dept-8001 ļ application.yml  spring.application.name ȡֵ\n*   Status UP (1) - spring-cloud-provider-8001UP ʾߣ (1) ʾмȺзspring-cloud-provider-8001  micro-service-cloud-provider-dept-8001 ļ application.yml  eureka.instance.instance-id ȡֵ\n\n10)  MySQL  bianchengbang_jdbc ݿִ SQL׼ݡ\n\n```\nDROP TABLE IF EXISTS `dept`;\nCREATE TABLE `dept` (\n  `dept_no` int NOT NULL AUTO_INCREMENT,\n  `dept_name` varchar(255) DEFAULT NULL,\n  `db_source` varchar(255) DEFAULT NULL,\n  PRIMARY KEY (`dept_no`)\n) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;\n\n\nINSERT INTO `dept` VALUES ('1', '', 'bianchengbang_jdbc');\nINSERT INTO `dept` VALUES ('2', '²', 'bianchengbang_jdbc');\nINSERT INTO `dept` VALUES ('3', '', 'bianchengbang_jdbc');\nINSERT INTO `dept` VALUES ('4', 'г', 'bianchengbang_jdbc');\nINSERT INTO `dept` VALUES ('5', 'ά', 'bianchengbang_jdbc');\n```\n\n11) ʹʡhttp://localhost:8001/dept/listͼ\n\n![Eureka Client 8001 ݿ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1010301b2-3.png)\nͼ4ṩṩݿ\n\n## Eureka Server Ⱥ\n\n΢ܹУһϵͳʮʮɣЩȫעᵽͬһ Eureka Server Уͼпܵ Eureka Server 򲻿ظյϵͳֱ̱ӵİ취ǲ Eureka Server Ⱥ\n\n֪ Eureka ʵַע뷢ʱһ漰 3 ɫעġṩԼߣɫֹȷ˾ְʵ Eureka Уз񶼼ǷҲǷṩߣע Eureka Server Ҳ⡣\n\nڴעʱ application.yml 漰ã\n\n\n\n\n\n```\n\n\neureka:\n  client:\n    register-with-eureka: false  #false ʾעעԼ\n    fetch-registry: false  #falseʾԼ˾עģְάʵҪȥ\n```\n\n\n\n\n\nõԭ micro-service-cloud-eureka-7001 ԼǷעģעǲܽԼעᵽԼϵģעǿԽԼΪķעעԼġ\n\nٸӣ Eureka Server ֱΪ A  BȻ A ܽԼעᵽ A ϣB ҲܽԼעᵽ B ϣ A ǿΪһԼעᵽ B ϵģͬ B ҲԽԼעᵽ A ϡ\n\nͿγһ黥ע Eureka Server Ⱥṩ߷ע Eureka Server ʱEureka Server ὫתȺ֮ Eureka Server ϣʵ Eureka Server ֮ķͬ\n\nͨͬ߿ڼȺеһ̨ Eureka Server ϻȡṩṩķʹȺеĳעķϣȻԴӼȺе Eureka Server лȡϢãᵼϵͳ̱ Eureka Server Ⱥĸ߿ԡ\n\n## ʾ 2\n\nʾ 1 ĻϽչһӵ 3  Eureka Server ʵļȺ\n\n1\\.  micro-service-cloud-eureka-7001 Ĵ̣ٴ Eureka Servermicro-service-cloud-eureka-7002  micro-service-cloud-eureka-7003ʱ 3  Eureka Server  Maven 뻹öһģһġ\n\n2\\. ޸ micro-service-cloud-eureka-7001micro-service-cloud-eureka-7002micro-service-cloud-eureka-7003  application.yml ã \n\nmicro-service-cloud-eureka-7001  application.yml ¡\n\n\n\n\n\n```\nserver:\n  port: 7001  #˿ں\neureka:\n  instance:\n    hostname: eureka7001.com #eureka˵ʵ\n  client:\n    register-with-eureka: false #false ʾעעԼ\n    fetch-registry: false #false ʾԼ˾עģҵְάʵҪȥ\n    service-url:\n      #defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ #\n      defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/ #Ⱥ ǰ Eureka Server עᵽ 7003  7003 ϣγһ黥ע Eureka Server Ⱥ\n```\n\n\n\n\n\nmicro-service-cloud-eureka-7002  application.yml ¡\n\n\n\n\n\n```\n\nserver:\n  port: 7002 #˿ں\n\neureka:\n  instance:\n    hostname: eureka7002.com #Eureka Server ʵ\n\n  client:\n    register-with-eureka: false #false ʾעעԼ\n    fetch-registry: false  #false ʾԼ˾עģҵְάʵҪȥ\n    service-url:\n      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7003.com:7003/eureka/ # Eureka Server עᵽ 7001  7003 \n```\n\n\n\n\n\nmicro-service-cloud-eureka-7003  application.yml ¡\n\n\n\n\n\n```\n\nserver:\n  port: 7003 #˿ں\n\neureka:\n  instance:\n    hostname: eureka7003.com #Eureka Server ʵ\n\n  client:\n    register-with-eureka: false #false ʾעעԼ\n    fetch-registry: false  #false ʾԼ˾עģҵְάʵҪȥ\n    service-url:\n      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/ # Eureka Server עᵽ 7001  7002 \n```\n\n\n\n\n\n3\\. ڱش Eureka Server ȺҪ޸ıص host ļWindows ϵͳĵ C:/Windows/System/drivers/etc/hosts ޸ģMac ϵͳĵҪ vim/etc/hosts ޸ģ޸¡\n\n```\n#Spring Cloud eureka Ⱥ\n127.0.0.1 eureka7001.com\n127.0.0.1 eureka7002.com\n127.0.0.1 eureka7003.com\n```\n\n\n4\\. ޸ micro-service-cloud-provider-dept-8001ṩߣļ application.yml  eureka.client.service-url.defaultZone ȡֵעᵽ Eureka Server Ⱥϣ¡\n\n\n\n\n\n```\n\neureka:\n  client: #ͻעᵽ eureka б\n    service-url:\n      #defaultZone: http://eureka7001.com:7001/eureka  #ַ 7001 ע application.yml б¶עַ 棩\n      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/  #עᵽ Eureka Server Ⱥ\n```\n\n\n\n\n\n5\\.  micro-service-cloud-eureka-7001ʹʡhttp://eureka7001.com:7001/ͼ\n\n![Eureka Ⱥ 7001](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1010302b3-4.png)\n\nͼ5Eureka Server Ⱥ -7001\n\nͼԿ ṩߣmicro-service-cloud-provider-dept-8001ķѾעᵽ Eureka Server 7001 DS Replicas ѡҲʾ˼Ⱥе Eureka ServerEureka Server 7002  Eureka Server 7003\n\n6\\.  micro-service-cloud-eureka-7002ʹʡhttp://eureka7002.com:7002/ͼ\n\n![Eureka Ⱥ 7002](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/101030E36-5.png)\nͼ6Eureka Server Ⱥ -7002\n\n\nͼԿ ṩߣmicro-service-cloud-provider-dept-8001ṩķѾעᵽ Eureka Server 7002 DS Replicas ѡҲʾ˼Ⱥе Eureka ServerEureka Server 7001  Eureka Server 7003\n\n7.  micro-service-cloud-eureka-7003ʹʡhttp://eureka7003.com:7003/ͼ\n\n![Eureka Ⱥ 7003](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1010306034-6.png)\nͼ7Eureka Server Ⱥ -7003\n\nͼԿ ṩߣmicro-service-cloud-provider-dept-8001ṩķѾעᵽ Eureka Server 7003 DS Replicas ѡҲʾ˼Ⱥе Eureka ServerEureka Server 7001  Eureka Server 7002\n\nԴǾ Eureka Server ȺĴʹá\n\n## Eureka ұ\n\nڱصԻ Eureka ĳʱEureka עĺпܻͼʾĺɫ档\n\n![Eureka ұ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/10103014a-7.png)\nͼ8Eureka ұʾ\n\n\nʵϣǴ Eureka ұƶֵġĬ£ Eureka Server һʱڣĬΪ 90 룩ûнյĳṩߣEureka ClientͻὫṩṩķӷעƳ ߾Ҳ޷ӷעлȡˣ޷ø÷\n\nʵʵķֲʽ΢ϵͳУķEureka ClientҲпܻϣӳ١١ӵԭ򣩶޷ Eureka Server ͨѶʱ Eureka Server Ϊûн󽫽ķӷбƳȻǲġ Eureka ұƾġ\n\nν Eureka ұơ˼ǡš Eureka Server һʱûнյ Eureka Client ô Eureka Server ͻῪұģʽе Eureka Client עϢֱӴӷעƳһָЩ Eureka Client ṩķ񻹿Լѡ\n\nϣEureka ұһӦ쳣İȫʩļܹѧǣͬʱ΢񣨽ķͲķ񶼻ᱣҲäĿƳκνķͨ Eureka ұƣ Eureka Server ȺӵĽ׳ȶ\n\n> Eureka ұҲڱ׶ˡ Eureka ұƴڼ䣬ṩṩķ⣬ô߾ͺ׻ȡѾڵķֵʧܵʱǿͨͻ˵ݴ⣬ο [Spring Cloud Netflix Ribbon](http://c.biancheng.net/springcloud/ribbon.html)  [Spring Cloud Netflix Hystrix](http://c.biancheng.net/springcloud/hystrix.html)\n\nĬ£Eureka ұǿģҪرգҪļá\n\n\n\n\n\n```\n\n\neureka:\nserver:\nenable-self-preservation: false # false ر Eureka ұƣĬǿ,һ㲻޸\n\n```\n\n\n\n\n\n## ʾ 3\n\nͨһʵ֤ Eureka ұơ\n\n1\\.  micro-service-cloud-eureka-7001 ļ application.yml ãر Eureka ұơ\n\n\n\n\n\n```\n\n\neureka:\nserver:\nenable-self-preservation: false # false ر Eureka ұƣĬǿ,һ㲻޸\n\n```\n\n\n\n\n\n2\\. Ⱥе micro-service-cloud-eureka-7002  micro-service-cloud-eureka-7002 κ޸ģǵұǿġ\n\n3\\.  Eureka Server ȺԼ micro-service-cloud-provider-dept-8001ʹʡhttp://eureka7001.com:7001/ͼ\n\n![Eureka ұ 7001](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/101030J48-8.png)\nͼ9Eureka رұ\n\nͼ 8 Կݣ\n\n*    DS Replicas ѡ˺ɫϢTHE SELF PRESERVATION MODE IS TURNED OFF. THIS MAY NOT PROTECT INSTANCE EXPIRY IN CASE OF NETWORK/OTHER PROBLEMS.ָϢʾ Eureka ұģʽѹرա\n*   micro-service-cloud-provider-dept-8001 ṩķѾעᵽ Eureka Server С\n\n4\\. ʹʡhttp://eureka7002.com:7002/ͼ\n\n![Eureka ұ 7002](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1010301938-9.png)\nͼ10Eureka ұ\n\nͼ 9 Կmicro-service-cloud-provider-dept-8001 ṩķҲѾעᵽǰ Eureka Server У DS Replicas ѡϷûκξʾ\n\n5\\. ر micro-service-cloud-provider-dept-8001ȴӣٴηʡhttp://eureka7001.com:7001/ͼ\n\n![Eureka ұ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/10103014J-10.png)\nͼ11Eureka رұ-2\n\nͼ 10  ǿԿݣ\n\n*    DS Replicas ѡ˺ɫϢRENEWALS ARE LESSER THAN THE THRESHOLD. THE SELF PRESERVATION MODE IS TURNED OFF. THIS MAY NOT PROTECT INSTANCE EXPIRY IN CASE OF NETWORK/OTHER PROBLEMS.ָϢʾ Eureka ұģʽѹرգѾзƳ\n*   micro-service-cloud-provider-dept-8001 ṩķѾӷбƳ\n\n6\\. ٴηʡhttp://eureka7002.com:7002/ͼ\n\n![Eureka ұƿ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1010301210-11.png)\nͼ12Eureka ұЧ\n\nͼ 11  Կݣ\n\n*    DS Replicas ѡ˺ɫϢEMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.ָϢ Eureka ұƴڿ״̬Ѿ\n*   micro-service-cloud-provider-dept-8001 ķϢȻ Eureka Server עУδƳ\n\n# ο\nhttps://lijunyi.xyz/docs/SpringCloud/SpringCloud.html#_2-2-x-%E5%88%86%E6%94%AF\nhttps://mp.weixin.qq.com/s/2jeovmj77O9Ux96v3A0NtA\nhttps://juejin.cn/post/6931922457741770760\nhttps://github.com/D2C-Cai/herring\nhttp://c.biancheng.net/springcloud\nhttps://github.com/macrozheng/springcloud-learning"
  },
  {
    "path": "docs/Spring全家桶/SpringCloud/SpringCloudGateway.md",
    "content": "΢ܹУһϵͳɶ΢ɣЩܲڲͬͬͬ¡£ͻˣֻߵȣҪֱЩ񣬾Ҫ֪ǾĵַϢ IP ַ˿ںŵȡ\n\nֿͻֱķʽ⣺\n\n*   ڶʱͻҪάķַڿͻ˵Ƿǳӵġ\n*   ĳЩ¿ܻڿ⡣\n*   ֤Ѷȴÿ΢Ҫ֤\n\nǿͨ API Щ⣬ʲô API ء\n\n## API \n\nAPI һڿͻ˺΢֮ķǿ API дһЩҵܵ߼Ȩ֤ء桢·ɵȡ\n\nAPI ؾ΢ϵͳһϵͳΨһڡͻ˻Ƚ͵ API أȻ API ظıʶϢת΢ʵ\n\n![](http://c.biancheng.net/uploads/allimg/211210/101P46212-0.png)\nͼ1ַʷʽԱ\n\nڷڶࡢӶȽϸߡģȽϴϵͳ˵ʹ API ؾºô\n\n*   ͻͨ API ΢񽻻ʱͻֻҪ֪ API صַɣҪάķַ˿ͻ˵Ŀ\n*   ͻֱ API ͨţܹٿͻĽ\n*   ͻ˵ķ϶Ƚ͡\n*   ʡܣû顣\n*   API ػṩ˰ȫءˡ桢ƷԼص API ܡ\n\n API ʵַҪ 5 ֣\n\n*   Spring Cloud Gateway\n*   Spring Cloud Netflix Zuul\n*   Kong\n*   Nginx+Lua\n*   Traefik\n\nڣǾͶ Spring Cloud Gateway ϸܡ\n\n## Spring Cloud Gateway```\n\nSpring Cloud Gateway  Spring Cloud Ŷӻ Spring 5.0Spring Boot 2.0  Project Reactor ȼĸ API \n\nSpring Cloud Gateway ּṩһּ򵥶Ч; APIΪṩйע㣬磺ȫԣ/ָ͵ԡ```\n\n> Spring Cloud Gateway ǻ WebFlux ʵֵģ WebFlux ܵײʹ˸ܵ Reactor ģʽͨſ Netty\n\n#### Spring Cloud Gateway ĸ\n\nSpring Cloud GateWay ҪĹܾ·תڶתʱҪ漰ĸ±\n\n| ĸ |  |\n| --- | --- |\n| Route·ɣ | ģ顣һ IDһĿ URIһԣPredicateһFilterɡ |\n| Predicateԣ | ·תжǿͨ Predicate ```HTTP ƥ䣬ʽ·ͷȣƥɹתӦķ |\n| Filter | ǿʹغ޸ģʹĵӦٴ |\n\n> ע⣺ Route  Predicate ͬʱ\n\n#### Spring Cloud Gateway \n\nSpring Cloud Gateway ԣ\n\n*    Spring Framework 5Project Reactor  Spring Boot 2.0 \n*   ܹƥ·ɡ\n*   predicatesԣ  filtersض·ɵġ\n*    Hystrix ۶\n*    Spring Cloud DiscoveryClientֿͻˣ\n*   ڱдԺ͹\n*   ܹƵʡ\n*   ܹд·\n\n## Gateway Ĺ\n\nSpring Cloud Gateway ͼ\n\n![Spring Cloud Gateway ](http://c.biancheng.net/uploads/allimg/211210/101P45T2-1.png)\nͼ2Spring Cloud Gateway \n\nSpring Cloud Gateway ˵£\n\n1.  ͻ˽͵```Spring Cloud Gateway ϡ\n2.  Spring Cloud Gateway ͨ```Gateway Handler Mapping ҵƥ·ɣ䷢͸ Gateway Web Handler\n3.  Gateway Web Handler```ָͨĹFilter ChainתʵʵķڵУִҵ߼Ӧ\n4.  ֮߷ֿΪܻת֮ǰpre֮postִҵ߼\n5.  Filterתǰغ޸ģУ顢ȨУ顢ء־ԼЭתȡ\n6.  Ӧؿͻ֮ǰӦغٴ޸ӦݻӦͷ־صȡ\n7.  Ӧԭ·ظͻˡ\n\n֮ܶͻ˷͵ Spring Cloud Gateway Ҫͨһƥܶλķڵ㡣ڽתдĹǰpre  postǻԶӦһЩϸơ\n\nPredicate ·ɵƥ Filter ǶӦоϸƵĹߡԪأټĿ URIͿʵһ·ˡ\n\n## Predicate \n\nSpring Cloud Gateway ͨ```Predicate ʵ Route ·ɵƥ򡣼򵥵˵Predicate ·תжֻ Predicate ŻᱻתָķϽд\n\nʹ Predicate Ҫע 3 㣺\n\n*   Route · Predicate ԵĶӦϵΪһԶࡱһ·ɿ԰ͬԡ\n*   һҪתָ·ϣͱͬʱƥ·ϵжԡ\n*   һͬʱ·ɵĶʱֻᱻ׸ɹƥ·ת\n\n![](http://c.biancheng.net/uploads/allimg/211210/101P42B6-2.png)\nͼ3Predicate ƥ\n\n Predicate ±ת URI Ϊ http://localhost:8001\n\n|  | ʾ | ˵ |\n| --- | --- | --- |\n| Path | - Path=/dept/list/**``` | · /dept/list/** ƥʱܱת```http://localhost:8001 ϡ |\n| Before | - Before=2021-10-20T11:47:34.255+08:00[Asia/Shanghai] | ```2021  10  20  11 ʱ 47  34.255 ֮ǰ󣬲Żᱻת```http://localhost:8001 ϡ |\n| After | - After=2021-10-20T11:47:34.255+08:00[Asia/Shanghai] |  2021  10  20  11 ʱ 47  34.255 ֮󣬲Żᱻת```http://localhost:8001 ϡ |\n| Between | - Between=2021-10-20T15:18:33.226+08:00[Asia/Shanghai],2021-10-20T15:23:33.226+08:00[Asia/Shanghai] |  2021  10  20  15 ʱ 18  33.226   2021  10  20  15 ʱ 23  33.226 ֮󣬲Żᱻת```http://localhost:8001 ϡ |\n| Cookie | - Cookie=name,c.biancheng.net | Я```Cookie  Cookie Ϊ```name=c.biancheng.net 󣬲Żᱻת http://localhost:8001 ϡ |\n| Header | - Header=X-Request-Id,\\d+ | ͷЯ X-Request-Id ֵΪ󣬲Żᱻת http://localhost:8001 ϡ |\n| Method | - Method=GET | ֻ GET Żᱻת http://localhost:8001 ϡ |\n\n#### ʾ\n\nǾͨһʵʾ Predicate ʹõġ\n\n1\\. ڸ```spring-cloud-demo2 ´һΪ```micro-service-cloud-gateway-9527  Spring Boot ģ飬 pom.xml ¡\n\n\n\n\n\n```\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <parent>\n        <artifactId>spring-cloud-demo2</artifactId>\n        <groupId>net.biancheng.c</groupId>\n        <version>0.0.1-SNAPSHOT</version>\n    </parent>\n\n    <groupId>net.biancheng.c</groupId>\n    <artifactId>micro-service-cloud-gateway-9527</artifactId>\n    <version>0.0.1-SNAPSHOT</version>\n    <name>micro-service-cloud-gateway-9527</name>\n    <description>Demo project for Spring Boot</description>\n    <properties>\n        <java.version>1.8</java.version>\n    </properties>\n\n    <dependencies>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n\n        <!--رע⣺ gateway طв spring-boot-starter-web ᱨ-->\n        <!-- Spring cloud gateway -->\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            <artifactId>spring-cloud-starter-gateway</artifactId>\n        </dependency>\n        <!--Eureka ͻ-->\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-devtools</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.projectlombok</groupId>\n            <artifactId>lombok</artifactId>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-maven-plugin</artifactId>\n            </plugin>\n        </plugins>\n    </build>\n</project>\n```\n\n\n\n\n\n2\\. ```micro-service-cloud-gateway-9527 ·/resources Ŀ¼£½һļ application.yml¡\n\n\n\n\n\n```\nserver:\n  port: 9527  #˿ں\nspring:\n  application:\n    name: microServiceCloudGateway\n  cloud:\n    gateway: #·\n      routes:\n        # micro-service-cloud-provider-dept-8001 ṩķ¶ͻˣֻͻ˱¶ API صĵַ 9527\n        - id: provider_dept_list_routh   #· id,ûй̶򣬵ΨһӦ\n          uri: http://localhost:8001          #ƥṩ·ɵַ\n          predicates:\n            #Ƕѡȫ\n            - Path=/dept/list/**               #ԣ·ƥ ע⣺Path  P Ϊд\n            - Method=GET #ֻʱ GET ʱܷ\n\neureka:\n  instance:\n    instance-id: micro-service-cloud-gateway-9527\n    hostname: micro-service-cloud-gateway\n  client:\n    fetch-registry: true\n    register-with-eureka: true\n    service-url:\n      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/\n```\n\n\n\n\n\nУ spring.cloud.gateway.routes ʹ predicates ԣ\n\n```\n- Path=/dept/list/**            \n- Method=GET\n```\n\nֻеⲿͻˣ͵ micro-service-cloud-gateway-9527  HTTP ͬʱеĶʱŻᱻתָķУ http://localhost:80013\\. ```micro-service-cloud-gateway-9527 ϣʹ @EnableEurekaClient ע⿪ Eureka ͻ˹ܣ¡\n\n\n\n\n\n```\npackage net.biancheng.c;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.cloud.netflix.eureka.EnableEurekaClient;\n\n@SpringBootApplication\n@EnableEurekaClient\npublic class MicroServiceCloudGateway9527Application {\n\n    public static void main(String[] args) {\n        SpringApplication.run(MicroServiceCloudGateway9527Application.class, args);\n    }\n}\n\n```\n\n\n\n\n\n4\\.  Eureka עģȺmicro-service-cloud-provider-dept-8001 Լ```micro-service-cloud-gateway-9527ʹʡhttp://localhost:9527/dept/listͼ\n\n![Spring Cloud ](http://c.biancheng.net/uploads/allimg/211210/101P43419-3.png)\nͼ4Spring Cloud Gateway ·ת\n\n## Spring Cloud Gateway ̬·\n\nĬ£Spring Cloud Gateway ݷעģ Eureka ServerάķбԷspring.application.nameΪ·̬·ɽתӶʵֶ̬·ɹܡ\n\nǿļУ Route  uri ַ޸Ϊʽ\n\n```lb://service-name```\n\n˵£\n\n*   lburi Э飬ʾ Spring Cloud Gateway ĸؾ⹦ܡ\n*   service-nameSpring Cloud Gateway ȡ΢ַ\n\n#### ʾ\n\nǾͨһʵչʾ Spring Cloud Gateway ʵֶ̬·ɵġ\n\n1\\. ޸```micro-service-cloud-gateway-9527  application.yml ãʹעе΢̬·ɽת¡\n\n\n\n\n\n```\nserver:\n  port: 9527 #˿ں\n \nspring:\n  application:\n    name: microServiceCloudGateway  #עעķ\n   \n  cloud:\n    gateway: #·\n      discovery:\n        locator:\n          enabled: true #ĬֵΪ trueĬϿעĶ̬·ɵĹܣ΢·\n\n      routes:\n        # micro-service-cloud-provider-dept-8001 ṩķ¶ͻˣֻͻ˱¶ API صĵַ 9527\n        - id: provider_dept_list_routh   #· id,ûй̶򣬵ΨһӦ\n          uri: lb://MICROSERVICECLOUDPROVIDERDEPT #̬·ɣʹ÷ľ˿   http://eureka7001.com:9527/dept/list\n\n          predicates:\n            #Ƕѡȫ\n            - Path=/dept/list/**    #ԣ·ƥ ע⣺Path  P Ϊд\n            - Method=GET #ֻʱ GET ʱܷ\n\neureka:\n  instance:\n    instance-id: micro-service-cloud-gateway-9527\n    hostname: micro-service-cloud-gateway\n  client:\n    fetch-registry: true\n    register-with-eureka: true\n    service-url:\n      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/\n```\n\n\n\n\n\n2\\.  Eureka עģȺṩ߼Ⱥmicro-service-cloud-provider-dept-8001/8002/8003Լ```micro-service-cloud-gateway-9527\n\n3\\. зʡhttp://localhost:9527/dept/listͼ\n\n![Gateway ̬·](http://c.biancheng.net/uploads/allimg/211210/101P46240-4.gif)\nͼ5Spring Cloud ʵֶ̬·\n\n## Filter \n\nͨ£ڰȫĿǣṩķһУ߼û½״̬У顢ǩУȡ\n\n΢ܹУϵͳɶ΢ɣЩҪЩУ߼ʱǾͿԽЩУ߼д Spring Cloud Gateway  Filter С\n\n#### Filter ķ\n\nSpring Cloud Gateway ṩ͵ĹԶӦоϸơ\n\n|  | ˵ |\n| --- | --- |\n| Pre  | ֹת΢֮ǰԶغ޸ģУ顢ȨУ顢ء־ԼЭתȲ |\n| Post  | ֹ΢ӦԶӦغٴ޸ӦݻӦͷ־صȡ |\n\n÷Χ֣Spring Cloud gateway  Filter ԷΪ 2 ࣺ\n\n*   GatewayFilterӦڵ·ɻһ·ϵĹ\n*   GlobalFilterӦе·ϵĹ\n\n#### GatewayFilter ع\n\nGatewayFilter  Spring Cloud Gateway ṩһӦڵһ·ϵĹԶԵ·ɻһ·ϴʹӦأʵһЩҵ޹صĹܣ½״̬У顢ǩУ顢ȨУ顢־صȡ\n\nGatewayFilter ļ application.ymlед Predicate ƣʽ¡\n\n\n\n\n\n```\n\nspring:\n  cloud:\n    gateway: \n      routes:\n        - id: xxxx\n          uri: xxxx\n          predicates:\n            - Path=xxxx\n          filters:\n            - AddRequestParameter=X-Request-Id,1024 #ƥͷһͷΪ X-Request-Id ֵΪ 1024\n            - PrefixPath=/dept #·ǰ /dept\n            \n```\n\n\n\n\n\nSpring Cloud Gateway ˶ 31  GatewayFilter±о˼ֳõعʹʾ\n\n| ·ɹ |  |  | ʹʾ |\n| --- | --- | --- | --- |\n| AddRequestHeader | ```ش󣬲һָͷ | nameҪӵͷ keyvalueҪӵͷ value | - AddRequestHeader=my-request-header,1024 |\n| AddRequestParameter | ش󣬲һָ | nameҪӵ keyvalueҪӵ value | - AddRequestParameter=my-request-param,c.biancheng.net |\n| AddResponseHeader | ӦӦһָӦͷ | nameҪӵӦͷ keyvalueҪӵӦͷ value | - AddResponseHeader=my-response-header,c.biancheng.net |\n| PrefixPath | ش󣬲·һָǰ׺ | ```prefixҪӵ·ǰ׺ | - PrefixPath=/consumer |\n| PreserveHostHeader | תʱֿͻ˵ Host Ϣ䣬Ȼݵṩ΢С |  | - PreserveHostHeader |\n| RemoveRequestHeader | ƳͷָĲ | nameҪƳͷ key | - RemoveRequestHeader=my-request-header |\n| RemoveResponseHeader | ƳӦͷָĲ | nameҪƳӦͷ | - RemoveResponseHeader=my-response-header |\n| RemoveRequestParameter | Ƴָ | nameҪƳ | - RemoveRequestParameter=my-request-param |\n| RequestSize | ĴСʱ᷵ 413 Payload Too Large | maxSizeĴС | - name: RequestSize`````````args:```````````````maxSize: 5000000 |\n\n#### ʾ\n\nͨһʵʾ GatewayFilter ã¡\n\n1\\. ```micro-service-cloud-gateway-9527  application.yml һ̬·ɣ¡\n\n\n\n\n\n```\nspring:\n  cloud:\n    gateway: \n      routes:\n        - id: xxxx\n          uri: xxxx\n          predicates:\n            - Path=xxxx\n          filters:\n            - AddRequestParameter=X-Request-Id,1024 #ƥͷһͷΪ X-Request-Id ֵΪ 1024\n            - PrefixPath=/dept #·ǰ /dept\n            \n\n```\n\n\n\n\n\n2\\. ```micro-service-cloud-gateway-9527ʹʡhttp://eureka7001.com:9527/get/1ͼ\n\n![Gateway ·ɹ](http://c.biancheng.net/uploads/allimg/211210/101P4J58-5.png)\nͼ6·ɹʾ\n\n#### GlobalFilter ȫֹ\n\nGlobalFilter һе·ϵȫֹͨǿʵһЩͳһҵܣȨ֤IP Ƶȡĳ·ƥʱôе GlobalFilter ͸·õ GatewayFilter ϳһ\n\nSpring Cloud Gateway Ϊṩ˶Ĭϵ GlobalFilterת·ɡؾصȫֹʵʵĿУͨǶԶһЩԼ GlobalFilter ȫֹҵ󣬶ֱʹ Spring Cloud``` Config ṩЩĬϵ GlobalFilter\n\n> Ĭϵȫֹϸݣο```[Spring Cloud ](https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#global-filters)\n\nǾͨһʵʾ£Զ GlobalFilter ȫֹ\n\n1\\. ```net.biancheng.c.filter £½һΪ```MyGlobalFilter ȫֹ࣬¡\n\n\n\n\n\n```\nserver:\n  port: 9527 #˿ں\n \nspring:\n  application:\n    name: microServiceCloudGateway  #עעķ\n   \n  cloud:\n    gateway: #·\n      discovery:\n        locator:\n          enabled: true #ĬֵΪ trueĬϿעĶ̬·ɵĹܣ΢·\n\n      routes:\n        # micro-service-cloud-provider-dept-8001 ṩķ¶ͻˣֻͻ˱¶ API صĵַ 9527\n        - id: provider_dept_list_routh   #· id,ûй̶򣬵ΨһӦ\n          uri: lb://MICROSERVICECLOUDPROVIDERDEPT #̬·ɣʹ÷ľ˿   http://eureka7001.com:9527/dept/list\n\n          predicates:\n            #Ƕѡȫ\n            - Path=/dept/list/**    #ԣ·ƥ ע⣺Path  P Ϊд\n            - Method=GET #ֻʱ GET ʱܷ\n\neureka:\n  instance:\n    instance-id: micro-service-cloud-gateway-9527\n    hostname: micro-service-cloud-gateway\n  client:\n    fetch-registry: true\n    register-with-eureka: true\n    service-url:\n      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/\n\n```\n\n\n\n\n\n2\\. ```micro-service-cloud-gateway-9527ʹʡhttp://eureka7001.com:9527/dept/listǻַᷢʱ 406 󣬿̨¡\n\n```2021-10-21 16:25:39.450  INFO 19116 --- [ctor-http-nio-4] net.biancheng.c.filter.MyGlobalFilter    : Thu Oct 21 16:25:39 CST 2021Զȫֹ MyGlobalFilter\n2021-10-21 16:25:39.451  INFO 19116 --- [ctor-http-nio-4] net.biancheng.c.filter.MyGlobalFilter    :  uname Ϊ null\n```\n\n3\\. ʹʡhttp://eureka7001.com:9527/dept/list?uname=123,ͼ\n\n![Զȫع](http://c.biancheng.net/uploads/allimg/211210/101P43096-6.png)\nͼ7Զȫع\n\n# ο\nhttps://lijunyi.xyz/docs/SpringCloud/SpringCloud.html#_2-2-x-%E5%88%86%E6%94%AF\nhttps://mp.weixin.qq.com/s/2jeovmj77O9Ux96v3A0NtA\nhttps://juejin.cn/post/6931922457741770760\nhttps://github.com/D2C-Cai/herring\nhttp://c.biancheng.net/springcloud\nhttps://github.com/macrozheng/springcloud-learning"
  },
  {
    "path": "docs/Spring全家桶/SpringCloud/SpringCloudHystrix.md",
    "content": "΢ܹУһӦɶɣЩ֮໥ϵ۸ӡ\n\nһ΢ϵͳд ABCDEF ȶǵϵͼ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/101623H11-0.png)\n\nͼ1ϵ\n\nͨ£һûҪϲɡͼ 1 ʾз񶼴ڿ״̬ʱ 1 Ҫ ADEF ĸɣ 2 Ҫ BED ɣ 3 Ҫ÷ CFED ĸɡ\n\n E ϻӳʱ\n\n1.  ʹз񶼿ãڷ E Ĳãôû 123 ᴦ״̬ȴ E Ӧڸ߲ĳ£ᵼ߳ԴڶʱѸĴ\n2.  ڷ E  BD Լ F Ҳᴦ߳״̬ȴ E ӦЩĲá\n3.  BD  F ķ A ͷ C Ҳᴦ߳״̬Եȴ D ͷ F Ӧ· A ͷ C Ҳá\n\nϹ̿Կ΢ϵͳһֹʱϻŷĵ·ϵͳзӣյ΢ϵͳ̱ǡѩЧӦΪ˷ֹ¼ķ΢ܹˡ۶һϵзݴͱơ\n\n## ۶\n\n۶Circuit BreakerһԴѧеĵ·֪ʶǵ·ֹʱѸжϵԴԱ·İȫ\n\n΢۶ Martin Fowler  [Circuit Breake](https://martinfowler.com/bliki/CircuitBreaker.html)rһѧе۶ƣ΢ܹе۶ܹĳϺ÷һԤڵġɴĽӦFallBackǳʱĵȴ׳÷޷쳣ͱ֤˷÷̲߳ᱻʱ䡢Ҫռã΢ϵͳеӣֹϵͳѩЧӦķ\n\n## Spring Cloud Hystrix\n\nSpring Cloud Hystrix һķݴ뱣Ҳ Spring Cloud Ҫ֮һ\n\nSpring Cloud Hystrix ǻ Netflix ˾ĿԴ Hystrix ʵֵģṩ۶ܣܹЧֲֹʽ΢ϵͳгϣ΢ϵͳĵԡSpring Cloud Hystrix з񽵼۶ϡ̸߳롢󻺴桢ϲԼʵʱϼصǿܡ\n\n> Hystrix [h?st'r?ks]ĺǺıϳ˼̣ʹӵǿұ Spring Cloud Hystrix Ϊһݴ뱣Ҳ÷ӵұҲ˽ϷΪ硱\n\n΢ϵͳУHystrix ܹʵĿ꣺\n\n*   **߳Դ**ֹĹϺľϵͳе߳Դ\n*   **ʧܻ**ĳ˹ϣ÷÷һֱȴֱӷʧܡ\n*   **ṩFallBack**ʧܺṩһƺõĽͨһ׷ʧܺ󼴵ø÷\n*   **ֹɢ**ʹ۶ϻƣֹɢ\n*   **ع**ṩ۶ϼ Hystrix Dashboardʱ۶״̬\n\n## Hystrix 񽵼\n\nHystrix ṩ˷񽵼ܣܹ֤ǰϵӰ죬߷Ľ׳ԡ\n\n񽵼ʹó 2 ֣\n\n*   ڷѹʱʵҵһЩҪķвԵز򵥴ӶͷŷԴԱ֤ķ\n*   ĳЩ񲻿ʱΪ˱ⳤʱȴɷ񿨶ٻѩЧӦִбõĽ߼̷һѺõʾԱҵӰ졣\n\nǿͨд HystrixCommand  getFallBack()  HystrixObservableCommand  resumeWithFallback() ʹַ֧񽵼\n\nHystrix 񽵼 FallBack ȿԷڷ˽УҲԷڿͻ˽С\n\nHystrix ³½з񽵼\n\n*   쳣\n*   ʱ\n*   ۶ڴ״̬\n*   ̳߳Դľ\n\n## ʾ1\n\nǾͨһֱʾ Hystrix ˷񽵼Ϳͻ˷񽵼\n\n#### ˷񽵼\n\n1\\.  spring-cloud-demo2 ´һΪ micro-service-cloud-provider-dept-hystrix-8004 ķṩߣ pom.xml \n\n\n\n\n\n```\n\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <!--pom-->\n    <parent>\n        <artifactId>spring-cloud-demo2</artifactId>\n        <groupId>net.biancheng.c</groupId>\n        <version>0.0.1-SNAPSHOT</version>\n    </parent>\n\n    <groupId>net.biancheng.c</groupId>\n    <artifactId>micro-service-cloud-provider-dept-hystrix-8004</artifactId>\n    <version>0.0.1-SNAPSHOT</version>\n    <name>micro-service-cloud-provider-dept-hystrix-8004</name>\n    <description>Demo project for Spring Boot</description>\n    <properties>\n        <java.version>1.8</java.version>\n    </properties>\n\n    <dependencies>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-web</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-devtools</artifactId>\n            <scope>runtime</scope>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>org.projectlombok</groupId>\n            <artifactId>lombok</artifactId>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <!-- Spring Boot ļģ-->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-actuator</artifactId>\n        </dependency>\n        <!-- eureka ͻ-->\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>\n        </dependency>\n        <!--hystrix -->\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-maven-plugin</artifactId>\n                <configuration>\n                    <excludes>\n                        <exclude>\n                            <groupId>org.projectlombok</groupId>\n                            <artifactId>lombok</artifactId>\n                        </exclude>\n                    </excludes>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n</project>\n\n```\n\n\n\n\n\n2\\. · /resources Ŀ¼һļ application.yml¡\n\n\n\n\n\n```\n\nspring:\n  application:\n    name: microServiceCloudProviderDeptHystrix  #΢ƣⱩ©΢ƣʮҪ\n\nserver:\n  port: 8004\n########################################### Spring cloud Զƺ ip ַ###############################################\neureka:\n  client: #ͻעᵽ eureka б\n    service-url:\n      #defaultZone: http://eureka7001:7001/eureka  #ַ 7001ע application.yml б¶עַ 棩\n      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/  #עᵽ Eureka Ⱥ\n  instance:\n    instance-id: spring-cloud-provider-8004 #ԶϢ\n    prefer-ip-address: true  #ʾ· ip ַ\n#####################spring cloud ʹ Spring Boot actuator Ϣ###########################################\n# Spring Boot 2.50 actuator ˴Ľڵ㣬ֻ¶ heath ڵ㣬ã*Ϊ˿еĽڵ\nmanagement:\n  endpoints:\n    web:\n      exposure:\n        include: \"*\"   # * yaml ļڹؼ֣Ҫ\ninfo:\n  app.name: micro-service-cloud-provider-dept-hystrix\n  company.name: c.biancheng.net\n  build.aetifactId: @project.artifactId@\n  build.version: @project.version@\n\n```\n\n\n\n\n\n3\\.  net.biancheng.c.service ´һΪ  DeptService Ľӿڣ¡\n\n\n\n\n\n```\n\npackage net.biancheng.c.service;\n\npublic interface DeptService {\n\n    // hystrix ۶ʾ ok\n    public String deptInfo_Ok(Integer id);\n\n    //hystrix ۶ʱ\n    public String deptInfo_Timeout(Integer id);\n}\n\n```\n\n\n\n\n\n4\\.  net.biancheng.c.service.impl £ DeptService ӿڵʵ DeptServiceImpl¡\n\n\n\n\n\n```\npackage net.biancheng.c.service.impl;\n\nimport com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;\nimport com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;\nimport net.biancheng.c.service.DeptService;\nimport org.springframework.stereotype.Service;\n\nimport java.util.concurrent.TimeUnit;\n\n@Service(\"deptService\")\npublic class DeptServiceImpl implements DeptService {\n\n    @Override\n    public String deptInfo_Ok(Integer id) {\n        return \"̳߳أ\" + Thread.currentThread().getName() + \"  deptInfo_Ok,id:   \" + id;\n    }\n\n    //һ÷ʧܲ׳쳣Ϣ󣬻Զ  @HystrixCommand עע fallbackMethod ָķ\n    @HystrixCommand(fallbackMethod = \"dept_TimeoutHandler\",\n            commandProperties =\n                    //涨 5 ھͲУ 5 ͱָķ\n                    {@HystrixProperty(name = \"execution.isolation.thread.timeoutInMilliseconds\", value = \"5000\")})\n    @Override\n    public String deptInfo_Timeout(Integer id) {\n        int outTime = 6;\n        try {\n            TimeUnit.SECONDS.sleep(outTime);\n        } catch (InterruptedException e) {\n            e.printStackTrace();\n        }\n        return \"̳߳أ\" + Thread.currentThread().getName() + \"  deptInfo_Timeout,id:   \" + id + \"  ʱ: \" + outTime;\n    }\n\n    // ֹϺ󣬵ø÷Ѻʾ\n    public String dept_TimeoutHandler(Integer id) {\n       return  \"CϵͳæԺԣ\"+\"̳߳أ\" + Thread.currentThread().getName() + \"  deptInfo_Timeout,id:   \" + id;\n    }\n}\n\n```\n\n\n\n\n\nǿԿ deptInfo_Timeout() ʹ @HystrixCommand ע⣬ע˵£\n\n*    fallbackMethod ָ\n*    execution.isolation.thread.timeoutInMilliseconds óʱʱķֵֵڿУִн\n\n5.   net.biancheng.c.controller ´һΪ DeptController  Controller ࣬¡\n\n\n\n\n\n```\n\npackage net.biancheng.c.controller;\n\nimport lombok.extern.slf4j.Slf4j;\nimport net.biancheng.c.service.DeptService;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.web.bind.annotation.*;\n\n@RestController\n@Slf4j\npublic class DeptController {\n    @Autowired\n    private DeptService deptService;\n    @Value(\"${server.port}\")\n    private String serverPort;\n\n    @RequestMapping(value = \"/dept/hystrix/ok/{id}\")\n    public String deptInfo_Ok(@PathVariable(\"id\") Integer id) {\n        String result = deptService.deptInfo_Ok(id);\n        log.info(\"˿ںţ\" + serverPort + \" result:\" + result);\n        return result + \"   ˿ںţ\" + serverPort;\n    }\n\n    // Hystrix ʱ\n    @RequestMapping(value = \"/dept/hystrix/timeout/{id}\")\n    public String deptInfo_Timeout(@PathVariable(\"id\") Integer id) {\n        String result = deptService.deptInfo_Timeout(id);\n        log.info(\"˿ںţ\" + serverPort + \" result:\" + result);\n        return result + \"   ˿ںţ\" + serverPort;\n    }\n\n}\n\n```\n\n\n\n\n\n6\\.  micro-service-cloud-provider-dept-hystrix-8004 ϣʹ @EnableCircuitBreaker ע⿪۶ܣ¡\n\n\n\n\n\n```\n\n\npackage net.biancheng.c;\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;\nimport org.springframework.cloud.netflix.eureka.EnableEurekaClient;\n\n@SpringBootApplication\n@EnableEurekaClient // Eureka ͻ˹\n@EnableCircuitBreaker //۶\npublic class MicroServiceCloudProviderDeptHystrix8004Application {\n\n    public static void main(String[] args) {\n        SpringApplication.run(MicroServiceCloudProviderDeptHystrix8004Application.class, args);\n    }\n\n}\n```\n\n\n\n\n\n7\\. עģEureka ServerȺ micro-service-cloud-provider-dept-hystrix-8004ʹʡhttp://eureka7001.com:8004/dept/hystrix/ok/1ͼ\n\n![Hystrix ok](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1016234615-1.png)\nͼ2Hystrix \n\n8\\. ʹʡhttp://eureka7001.com:8004/dept/hystrix/timeout/1ͼ\n\n![Hystrix 񽵼](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/101623FR-2.png)\nͼ3Hystrix ˷񽵼\n\n#### ͻ˷񽵼\n\nͨ£Ƕڿͻ˽з񽵼ͻ˵õķ˵ķ񲻿ʱͻֱӽз񽵼̱߳ʱ䡢Ҫռá\n\nͻ˷񽵼¡\n\n1\\.  micro-service-cloud-consumer-dept-feign  pom.xml  Hystrix ¡\n\n\n\n\n\n```\n\n\n<!--hystrix -->\n<dependency>\n    <groupId>org.springframework.cloud</groupId>\n    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>\n</dependency>\n```\n\n\n\n\n\n2.  micro-service-cloud-consumer-dept-feign  application.yml ãͻ˵ Hystrix ܡ\n\n\n\n\n\n```\n\nfeign:\n  hystrix:\n    enabled: true #ͻ hystrix\n\n```\n\n\n\n\n\n3\\.  net.biancheng.c.service £һΪ DeptHystrixService ķ󶨽ӿڣ micro-service-cloud-provider-dept-hystrix-8004 ṩķӿڽа󶨣¡\n\n\n\n\n\n```\n\npackage net.biancheng.c.service;\n\nimport org.springframework.cloud.openfeign.FeignClient;\nimport org.springframework.stereotype.Component;\nimport org.springframework.web.bind.annotation.PathVariable;\nimport org.springframework.web.bind.annotation.RequestMapping;\n\n@Component\n@FeignClient(value = \"MICROSERVICECLOUDPROVIDERDEPTHYSTRIX\")\npublic interface DeptHystrixService {\n    @RequestMapping(value = \"/dept/hystrix/ok/{id}\")\n    public String deptInfo_Ok(@PathVariable(\"id\") Integer id);\n\n    @RequestMapping(value = \"/dept/hystrix/timeout/{id}\")\n    public String deptInfo_Timeout(@PathVariable(\"id\") Integer id);\n}\n```\n\n\n\n\n\n4\\.  net.biancheng.c.controller ´һΪ HystrixController_Consumer  Controller ¡\n\n\n\n\n\n```\npackage net.biancheng.c.controller;\n\nimport com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;\nimport com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;;\nimport lombok.extern.slf4j.Slf4j;\nimport net.biancheng.c.service.DeptHystrixService;\nimport org.springframework.web.bind.annotation.PathVariable;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport javax.annotation.Resource;\n\n@Slf4j\n@RestController\npublic class HystrixController_Consumer {\n\n    @Resource\n    private DeptHystrixService deptHystrixService;\n\n    @RequestMapping(value = \"/consumer/dept/hystrix/ok/{id}\")\n    public String deptInfo_Ok(@PathVariable(\"id\") Integer id) {\n        return deptHystrixService.deptInfo_Ok(id);\n    }\n\n    //ڿͻ˽н\n    @RequestMapping(value = \"/consumer/dept/hystrix/timeout/{id}\")\n    @HystrixCommand(fallbackMethod = \"dept_TimeoutHandler\") //ΪָרĻ˷\n    public String deptInfo_Timeout(@PathVariable(\"id\") Integer id) {\n        String s = deptHystrixService.deptInfo_Timeout(id);\n        log.info(s);\n        return s;\n    }\n\n    // deptInfo_Timeout ר fallback \n    public String dept_TimeoutHandler(@PathVariable(\"id\") Integer id) {\n        log.info(\"deptInfo_Timeout ѱ\");\n        return \"CϵͳæԺԣͻ deptInfo_Timeout רĻ˷\";\n    }\n}\n\n```\n\n\n\n\n\n5\\. ļ appliction.yml ãڿͻʱʱ䡣\n\n\n\n\n\n```\n######################### Ribbon ͻ˳ʱ ###################################\nribbon:\n  ReadTimeout: 6000 #õʱ䣬״£õʱ\n  ConnectionTimeout: 6000 #Ӻ󣬷ȡԴʱ\n######################ʱʱ##########################\nhystrix:\n  command:\n    default:\n      execution:\n        isolation:\n          thread:\n            timeoutInMilliseconds: 7000\n####################þ巽ʱʱ Ϊ 3 ########################\n    DeptHystrixService#deptInfo_Timeout(Integer):\n      execution:\n        isolation:\n          thread:\n            timeoutInMilliseconds: 3000\n\n```\n\n\n\n\n\nļĳʱʱʱҪע 2 㣺\n\n1Hystrix Ϊ󣨷óʱʱ䣨λΪ룩ʱ򴥷ȫֵĻ˷д\n\n```hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=mmm```\n\n2Hystrix Ϊĳضķ󣨷óʱʱ䣬ʽ£\n\n```hystrix.command.xxx#yyy(zzz).execution.isolation.thread.timeoutInMilliseconds=mmm```\n\nʽ˵£\n\n*   xxxΪ÷񷽷ƣͨΪ󶨽ӿڵƣ DeptHystrixService ӿڡ\n*   yyy񷽷 deptInfo_Timeout() \n*   zzzڵĲͣ IntegerString ȵ\n*   mmmҪõĳʱʱ䣬λΪ루1  =1000 룩\n\n6\\.  micro-service-cloud-consumer-dept-feign ϣʹ @EnableHystrix ע⿪ͻ Hystrix ܣ¡\n\n\n\n\n\n```\n\npackage net.biancheng.c;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.cloud.netflix.hystrix.EnableHystrix;\nimport org.springframework.cloud.openfeign.EnableFeignClients;\n\n@SpringBootApplication\n@EnableFeignClients // OpenFeign \n@EnableHystrix // Hystrix\npublic class MicroServiceCloudConsumerDeptFeignApplication {\n\n    public static void main(String[] args) {\n        SpringApplication.run(MicroServiceCloudConsumerDeptFeignApplication.class, args);\n    }\n}\n\n```\n\n\n\n\n\n7\\. ޸ micro-service-cloud-provider-dept-hystrix-8004  DeptServiceImpl Ĵ룬 deptInfo_Timeout() ʱ޸Ϊ 4 루Сڳʱʱ 5 룩Ա֤¡\n\n\n\n\n\n```\n//һ÷ʧܲ׳쳣Ϣ󣬻Զ  @HystrixCommand עע fallbackMethod ָķ\n@HystrixCommand(fallbackMethod = \"dept_TimeoutHandler\",\n        commandProperties =\n                //涨 5 ھͲУ 5 ͱָķ\n                {@HystrixProperty(name = \"execution.isolation.thread.timeoutInMilliseconds\", value = \"5000\")})\n@Override\npublic String deptInfo_Timeout(Integer id) {\n    int outTime = 4;\n    try {\n        TimeUnit.SECONDS.sleep(outTime);\n    } catch (InterruptedException e) {\n        e.printStackTrace();\n    }\n    return \"̳߳أ\" + Thread.currentThread().getName() + \"  deptInfo_Timeout,id:   \" + id + \"  ʱ: \" + outTime;\n}\n\n```\n\n\n\n\n\n8\\.  micro-service-cloud-provider-dept-hystrix-8004  micro-service-cloud-consumer-dept-feignʹʡhttp://eureka7001.com:8004/dept/hystrix/timeout/1ֱӵ÷˵ deptInfo_Timeout() ͼ\n\n![Hystrix ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1016231059-3.png)\nͼ4Hystrix \n\n9\\. ʹʡhttp://eureka7001.com/consumer/dept/hystrix/timeout/1ͼ\n\n![Hystrix ͻ˷񽵼](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/101623C94-4.png)\nͼ5Hystrix ͻ˷񽵼\n\nͼ 5 ԿڷĺʱΪ 4 룬˿ͻΪָĳʱʱ 3  ˸÷񱻽ָĻ˷\n\n## ȫֽ\n\nͨķʽʵַ񽵼ʱҪҵ񷽷ý⼫пܻɴļ͡Ϊ˽⣬ǻΪҵ񷽷ָһȫֵĻ˷岽¡\n\n1\\.  HystrixController_Consumer ϱע @DefaultProperties ע⣬ͨ defaultFallback ָһȫֵĽ¡\n\n\n\n\n\n```\n\n\n@Slf4j\n@RestController\n@DefaultProperties(defaultFallback = \"dept_Global_FallbackMethod\") //ȫֵķ񽵼\npublic class HystrixController_Consumer {\n\n}\n\n```\n\n\n\n\n\n2\\.  HystrixController_Consumer УһΪ dept_Global_FallbackMethod ȫֻط¡\n\n\n\n\n\n```\n\n\n/**\n * ȫֵ fallback \n * ˷ hystrix ִзͬ\n * @DefaultProperties(defaultFallback = \"dept_Global_FallbackMethod\") ע⣬󷽷ʹ @HystrixCommand ע\n */\npublic String dept_Global_FallbackMethod() {\n    return \"CгϵͳæԺԣͻȫֻ˷,\";\n}\n\n```\n\n\n\n\n\n> **ע**FallBackӦҵ񷽷ͬһУ޷Ч\n\n3\\. еҵ񷽷϶ע @HystrixCommand ע⣬ǽ deptInfo_Timeout() ϵ @HystrixCommand(fallbackMethod = \"dept_TimeoutHandler\") ޸Ϊ @HystrixCommand ɣ¡\n\n\n\n\n\n```\n\n\n//ڿͻ˽н\n@RequestMapping(value = \"/consumer/dept/hystrix/timeout/{id}\")\n@HystrixCommand\npublic String deptInfo_Timeout(@PathVariable(\"id\") Integer id) {\n    String s = deptHystrixService.deptInfo_Timeout(id);\n    log.info(s);\n    return s;\n}\n```\n\n\n\n\n\n> **ע**ȫֽȼϵֻͣҵ񷽷ûָ併ʱ񽵼ʱŻᴥȫֻ˷ҵ񷽷ָԼĻ˷ôڷ񽵼ʱֱֻӴԼĻ˷ȫֻ˷\n\n4\\.  micro-service-cloud-consumer-dept-feignʹʡhttp://eureka7001.com/consumer/dept/hystrix/timeout/1ͼ\n\n![Hystrix ȫֻ˷](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/101623J11-5.png)\nͼ6ȫֻ˷\n\n## ߼\n\nҵ񷽷ָĽȫֽǶҵ񷽷ͬһвЧҵ߼뽵߼϶ȼߡ\n\nǶҵ߼뽵߼н¡\n\n1\\.  micro-service-cloud-consumer-dept-feign  net.biancheng.c.service £½ DeptHystrixService ӿڵʵ DeptHystrixFallBackServiceͳһΪ DeptHystrixService еķṩ񽵼 ¡\n\n\n\n\n\n```\n\npackage net.biancheng.c.service;\nimport org.springframework.stereotype.Component;\n/**\n* Hystrix 񽵼\n* ߼\n*/\n@Component\npublic class DeptHystrixFallBackService implements DeptHystrixService {\n    @Override\n    public String deptInfo_Ok(Integer id) {\n        return \"--------------------CϵͳæԺԣ˷-----------------------\";\n    }\n    @Override\n    public String deptInfo_Timeout(Integer id) {\n        return \"--------------------CϵͳæԺԣ˷-----------------------\";\n    }\n}\n\n```\n\n\n\n\n\n> **ע**ʽ Spring вЧõķʽϱע @Component ע⡣\n\n2\\. ڷ󶨽ӿ DeptHystrixService ע @FeignClient ע fallback ԣֵΪ DeptHystrixFallBackService.class¡\n\n\n\n\n\n```\npackage net.biancheng.c.service;\nimport org.springframework.cloud.openfeign.FeignClient;\nimport org.springframework.stereotype.Component;\nimport org.springframework.web.bind.annotation.PathVariable;\nimport org.springframework.web.bind.annotation.RequestMapping;\n@Component\n@FeignClient(value = \"MICROSERVICECLOUDPROVIDERDEPTHYSTRIX\", fallback = DeptHystrixFallBackService.class)\npublic interface DeptHystrixService {\n    @RequestMapping(value = \"/dept/hystrix/ok/{id}\")\n    public String deptInfo_Ok(@PathVariable(\"id\") Integer id);\n    @RequestMapping(value = \"/dept/hystrix/timeout/{id}\")\n    public String deptInfo_Timeout(@PathVariable(\"id\") Integer id);\n}\n\n```\n\n\n\n\n\n3\\.  micro-service-cloud-consumer-dept-feignȻرշ micro-service-cloud-provider-dept-hystrix-8004ʹʡhttp://eureka7001.com/consumer/dept/hystrix/ok/1ͼ\n\n![Hystrix ߼](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1016233063-6.png)\nͼ7Hystrix ˷\n\n## Hystrix ۶\n\n۶ϻΪӦѩЧӦֵһ΢·ơ\n\n΢ϵͳеĳ΢񲻿ûӦʱ̫ʱΪ˱ϵͳԣ۶ʱжԸ÷ĵãٷһѺõĴӦ۶״̬õģھһʱ۶ٴμ΢Ƿָָָ·\n\n#### ۶״̬\n\n۶ϻ漰۶״̬\n\n*   ۶Ϲر״̬Closedʱ۶ڹر״̬÷ضԷеá\n*   ۶Ͽ״̬OpenĬ£ڹ̶ʱڽӿڵóʴﵽһֵ 50%۶۶Ͽ״̬۶״̬󣬺Ը÷ĵöᱻжϣ۶ִбصĽFallBack\n*   ۶״̬Half-Open ۶Ͽһʱ֮۶۶״̬ڰ۶״̬£۶᳢Իָ÷Էĵãø÷񣬲óɹʡɹʴﵽԤڣ˵ѻָ۶ر״̬ɹԾɺܵͣ½۶Ͽ״̬\n\n۶״̬֮תϵͼ\n\n![۶״̬ת](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/10162355X-7.png)\nͼ8۶״̬ת\n\n#### Hystrix ʵ۶ϻ\n\n Spring Cloud У۶ϻͨ Hystrix ʵֵġHystrix ΢õ״ʧܵõһʱ 5 ʧ 20 Σͻ۶ϻơ\n\nHystrix ʵַ۶ϵĲ£\n\n1.  ĵóʴﵽ򳬹 Hystix 涨ıʣĬΪ 50%۶۶Ͽ״̬\n2.  ۶۶Ͽ״̬Hystrix һʱ䴰ʱ䴰ڣ÷Ľ߼ʱ䵱ҵ߼ԭҵ߼á\n3.  ٴεø÷ʱֱӵý߼ٵطʧӦԱϵͳѩ\n4.  ʱ䴰ںHystrix ۶ת̬Էԭҵ߼еãóɹʡ\n5.  óɹʴﵽԤڣ˵ѻָHystrix ۶Ϲر״̬ԭҵ߼ָ Hystrix ½۶Ͽ״̬ʱ䴰¼ʱظ 2  5 \n\n#### ʾ\n\nǾͨһʵ֤ Hystrix ʵ۶ϻƵġ\n\n1\\.  micro-service-cloud-provider-dept-hystrix-8004 е DeptService ӿһ deptCircuitBreaker() ¡\n\n\n\n\n\n```\n\npackage net.biancheng.c.service;\npublic interface DeptService {\n    // hystrix ۶ʾ ok\n    public String deptInfo_Ok(Integer id);\n   \n    //hystrix ۶ʱ\n    public String deptInfo_Timeout(Integer id);\n    // Hystrix ۶ϻư\n    public String deptCircuitBreaker(Integer id);\n}\n\n```\n\n\n\n\n\n2\\.  DeptService ӿڵʵ DeptServiceImpl  deptCircuitBreaker() ķʵּ˷¡\n\n\n\n\n\n```\n//Hystrix ۶ϰ\n@Override\n@HystrixCommand(fallbackMethod = \"deptCircuitBreaker_fallback\", commandProperties = {\n        //² HystrixCommandProperties Ĭ\n        @HystrixProperty(name = \"circuitBreaker.enabled\", value = \"true\"), //Ƿ۶\n    @HystrixProperty(name = \"metrics.rollingStats.timeInMilliseconds\",value = \"1000\"), //ͳʱ䴰\n        @HystrixProperty(name = \"circuitBreaker.requestVolumeThreshold\", value = \"10\"), //ͳʱ䴰\n        @HystrixProperty(name = \"circuitBreaker.sleepWindowInMilliseconds\", value = \"10000\"), //ʱ䴰\n        @HystrixProperty(name = \"circuitBreaker.errorThresholdPercentage\", value = \"60\"), //ͳʱ䴰ڣʧʴﵽ 60% ʱ۶״̬\n})\npublic String deptCircuitBreaker(Integer id) {\n    if (id < 0) {\n        // id Ϊʱ׳쳣ý\n        throw new RuntimeException(\"cid Ǹ\");\n    }\n    String serialNum = IdUtil.simpleUUID();\n    return Thread.currentThread().getName() + \"\\t\" + \"óɹˮΪ\" + serialNum;\n}\n\n//deptCircuitBreaker Ľ\npublic String deptCircuitBreaker_fallback(Integer id) {\n    return \"cid Ǹ,Ժ!\\t id:\" + id;\n}\n\n```\n\n\n\n\n\nϴУ漰 4  Hystrix ۶ϻصҪ 4 ĺ±\n\n|                                      |                                                          |\n| ---------------------------------------- | ------------------------------------------------------------ |\n| metrics.rollingStats.timeInMilliseconds  | ͳʱ䴰                                                 |\n| circuitBreaker.sleepWindowInMilliseconds | ʱ䴰۶Ͽ״̬һʱ۶Զ۶״̬ʱͱΪߴڡ |\n| circuitBreaker.requestVolumeThreshold    | ֵͳʱ䴰ڣ뵽һHystrix ſܻὫ۶򿪽۶Ͽת̬ ֵHystrix ֵĬΪ 20ζͳʱ䴰ڣô 20 Σʹе󶼵ó۶Ҳ򿪡 |\n| circuitBreaker.errorThresholdPercentage  | ٷֱֵͳʱ䴰ڳֵóʳһı۶Ż򿪽۶Ͽת̬ǴٷֱֵٷֱֵΪ 50ͱʾٷֱΪ 50% 30 εã 15 η˴󣬼 50% Ĵٷֱȣʱ۶ͻ򿪡 |\n\n3\\.  DeptController һ deptCircuitBreaker() ṩ񣬴¡\n\n\n\n\n\n```\n\n// Hystrix ۶\n@RequestMapping(value = \"/dept/hystrix/circuit/{id}\")\npublic String deptCircuitBreaker(@PathVariable(\"id\") Integer id){\n    String result = deptService.deptCircuitBreaker(id);\n    log.info(\"result:\"+result);\n    return result;\n}\n\n```\n\n\n\n\n\n4\\.  micro-service-cloud-provider-dept-hystrix-8004ʹʡhttp://eureka7001.com:8004/dept/hystrix/circuit/1ͼ\n\n![Hystrix ʵ۶ϻ ȷ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1016233Q8-8.png)\nͼ9Hystrix ʵ۶ϻ ȷʾ\n\n5\\. Σôֵʡhttp://eureka7001.com:8004/dept/hystrix/circuit/-2ʹóʴڴٷֱȷֵͼ\n\n![Hystrix ʵ۶ϻ ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/10162332D-9.png)\nͼ10Hystrix ʵ۶ϻ \n\n6\\. ½޸ΪΪ 3ʹʡhttp://eureka7001.com:8004/dept/hystrix/circuit/3ͼ\n\n![Hystrix ۶Ͽת̨](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1016235592-10.png)\nͼ11Hystrix ۶Ͽ״̬\n\nͨͼ 11 Կ۶Ͽ״̬£ʹǴĲѾõȻ߼\n\n7\\. ʡhttp://eureka7001.com:8004/dept/hystrix/circuit/3ͼ\n\n![Hystrix ۶Ϲر](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1016231R0-11.gif)\nͼ12Hystrix ۶Ϲر״̬\n\nͨͼ 12 ԿȷһʺHystrix ۶Ϲر״̬\n\n## Hystrix ϼ\n\nHystrix ṩ׼ʵʱĵüأHystrix DashboardܣHystrix ؼ¼ͨ Hystrix ִϢͳƱʽչʾûÿִɹʧȡ\n\nǾͨһʵ Hystrix Dashboard micro-service-cloud-provider-dept-hystrix-8004 \n\n1\\. ڸ½һΪ micro-service-cloud-consumer-dept-hystrix-dashboard-9002 ģ飬 pom.xml \n\n\n\n\n\n```\n\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <parent>\n        <artifactId>spring-cloud-demo2</artifactId>\n        <groupId>net.biancheng.c</groupId>\n        <version>0.0.1-SNAPSHOT</version>\n    </parent>\n    <groupId>net.biancheng.c</groupId>\n    <artifactId>micro-service-cloud-consumer-dept-hystrix-dashboard-9002</artifactId>\n    <version>0.0.1-SNAPSHOT</version>\n    <name>micro-service-cloud-consumer-dept-hystrix-dashboard-9002</name>\n    <description>Demo project for Spring Boot</description>\n    <properties>\n        <java.version>1.8</java.version>\n    </properties>\n    <dependencies>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter</artifactId>\n        </dependency>\n        <!--Spring Boot -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <!--hystrix-dashboard ص-->\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>\n        </dependency>\n        <!-- Spring Boot ļģ-->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-actuator</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-devtools</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.projectlombok</groupId>\n            <artifactId>lombok</artifactId>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-maven-plugin</artifactId>\n            </plugin>\n        </plugins>\n    </build>\n</project>\n\n```\n\n\n\n\n\n2\\.  micro-service-cloud-consumer-dept-hystrix-dashboard-9002  application.yml á\n\n\n\n\n\n```\n\nserver:\n  port: 9002  #˿ں\n\n#http://eureka7001.com:9002/hystrix ۶ҳ\n# localhost:8004//actuator/hystrix.stream صַ\nhystrix:\n  dashboard:\n    proxy-stream-allow-list:\n      - \"localhost\"\n\n```\n\n\n\n\n\n3\\.  micro-service-cloud-consumer-dept-hystrix-dashboard-9002  @EnableHystrixDashboard ע⣬ Hystrix عܣ¡\n\n\n\n\n\n```\n\npackage net.biancheng.c;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;\n\n@SpringBootApplication\n@EnableHystrixDashboard\npublic class MicroServiceCloudConsumerDeptHystrixDashboard9002Application {\n\n    public static void main(String[] args) {\n        SpringApplication.run(MicroServiceCloudConsumerDeptHystrixDashboard9002Application.class, args);\n    }\n\n}\n\n```\n\n\n\n\n\n4\\.  micro-service-cloud-provider-dept-hystrix-8004  net.biancheng.c.config £һΪ HystrixDashboardConfig ࣬¡\n\n\n\n\n\n```\n\npackage net.biancheng.c.config;\n\nimport com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;\nimport org.springframework.boot.web.servlet.ServletRegistrationBean;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n@Configuration\npublic class HystrixDashboardConfig {\n    /**\n     *  Hystrix dashboard ؽ\n     * @return\n     */\n    @Bean\n    public ServletRegistrationBean getServlet() {\n        HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();\n        ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);\n        registrationBean.setLoadOnStartup(1);\n        registrationBean.addUrlMappings(\"/actuator/hystrix.stream\");//·\n        registrationBean.setName(\"hystrix.stream\");\n        return registrationBean;\n    }\n\n}\n\n```\n\n\n\n\n\n5\\.  micro-service-cloud-consumer-dept-hystrix-dashboard-9002ʹʡhttp://eureka7001.com:9002/hystrixͼ\n\n![Hystrix ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1016234440-12.png)\nͼ13Hystrix ҳ\n\n6\\.  micro-service-cloud-provider-dept-hystrix-8004Ϣ Hystrix ҳУͼ\n\n![Hystrix Ϣ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1016232636-13.png)\nͼ14Hystrix Ϣ\n\n7\\. · Monitor Stream ťת Hystrix  micro-service-cloud-provider-dept-hystrix-8004 ļҳ棬ͼ\n\n![Hystrix  8004](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1016231229-14.png)\nͼ15Hystrix ΢\n\n8\\. ʹηʡhttp://eureka7001.com:8004/dept/hystrix/circuit/1 http://eureka7001.com:8004/dept/hystrix/circuit/-1鿴 Hystrix ҳ棬ͼ\n\n![Hystrix  8004 ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/10162345J-15.png)\nͼ16Hystrix ط\n\n# ο\nhttps://lijunyi.xyz/docs/SpringCloud/SpringCloud.html#_2-2-x-%E5%88%86%E6%94%AF\nhttps://mp.weixin.qq.com/s/2jeovmj77O9Ux96v3A0NtA\nhttps://juejin.cn/post/6931922457741770760\nhttps://github.com/D2C-Cai/herring\nhttp://c.biancheng.net/springcloud\nhttps://github.com/macrozheng/springcloud-learning"
  },
  {
    "path": "docs/Spring全家桶/SpringCloud/SpringCloudLoadBalancer.md",
    "content": "ǰãҰMall΢汾ȫ ͨGatewayصʱ򣬳Service Unavailable⡣ŲԭʱΪؾRibbonˣΪNetflixԴһRibbonѽά״̬ƼʹõLoadbalancerǾLoadbalancerʹã\n\n# LoadBalancer\n\nLoadBalancerSpring CloudٷṩĸؾRibbonʹ÷ʽRibbonݣԴRibbonƽɡ\n\n# ʹ\n\nLoadBalancerĻʹãǽʹNacosΪעģͨnacos-loadbalancer-servicenacos-user-service໥ʾ\n\n# ؾ\n\nǽʹRestTemplateʾLoadBalancerĸؾ⹦ܡ\n\n- nacos-loadbalancer-serviceģpom.xmlļLoadBalancer\n\n\n\n    <dependency>\n        <groupId>org.springframework.cloud</groupId>\n        spring-cloud-starter-loadbalancer\n    </dependency>\n\n\n\n\n\n- Ȼ󴴽Java࣬RestTemplateͬʱʹ@LoadBalancedע⸳为ؾ\n\n\n\n    /**\n     * RestTemplate\n     * Created by macro on 2019/8/29.\n     */\n    @Configuration\n    public class RestTemplateConfig {\n    \n        @Bean\n        @ConfigurationProperties(prefix = \"rest.template.config\")\n        public HttpComponentsClientHttpRequestFactory customHttpRequestFactory() {\n            return new HttpComponentsClientHttpRequestFactory();\n        }\n    \n        @Bean\n        @LoadBalanced\n        public RestTemplate restTemplate() {\n            return new RestTemplate(customHttpRequestFactory());\n        }\n    }\n\n\n\n\n\n- application.ymlпʹԶöRestTemplateĵóʱã\n\n\n\n    rest:\n      template:\n        config: # RestTemplateóʱ\n          connectTimeout: 5000\n          readTimeout: 5000\n\n\n\n\n- ȻControllerʹRestTemplateԶ̵ã\n\n\n\n    /**\n     * Created by macro on 2019/8/29.\n     */\n    @RestController\n    @RequestMapping(\"/user\")\n    public class UserLoadBalancerController {\n        @Autowired\n        private RestTemplate restTemplate;\n        @Value(\"${service-url.nacos-user-service}\")\n        private String userServiceUrl;\n    \n        @GetMapping(\"/{id}\")\n        public CommonResult getUser(@PathVariable Long id) {\n            return restTemplate.getForObject(userServiceUrl + \"/user/{1}\", CommonResult.class, id);\n        }\n    \n        @GetMapping(\"/getByUsername\")\n        public CommonResult getByUsername(@RequestParam String username) {\n            return restTemplate.getForObject(userServiceUrl + \"/user/getByUsername?username={1}\", CommonResult.class, username);\n        }\n    \n        @GetMapping(\"/getEntityByUsername\")\n        public CommonResult getEntityByUsername(@RequestParam String username) {\n            ResponseEntity<CommonResult> entity = restTemplate.getForEntity(userServiceUrl + \"/user/getByUsername?username={1}\", CommonResult.class, username);\n            if (entity.getStatusCode().is2xxSuccessful()) {\n                return entity.getBody();\n            } else {\n                return new CommonResult(\"ʧ\", 500);\n            }\n        }\n    \n        @PostMapping(\"/create\")\n        public CommonResult create(@RequestBody User user) {\n            return restTemplate.postForObject(userServiceUrl + \"/user/create\", user, CommonResult.class);\n        }\n    \n        @PostMapping(\"/update\")\n        public CommonResult update(@RequestBody User user) {\n            return restTemplate.postForObject(userServiceUrl + \"/user/update\", user, CommonResult.class);\n        }\n    \n        @PostMapping(\"/delete/{id}\")\n        public CommonResult delete(@PathVariable Long id) {\n            return restTemplate.postForObject(userServiceUrl + \"/user/delete/{1}\", null, CommonResult.class, id);\n        }\n    }\n\n\n\n\n- nacos-user-serviceѾʵЩӿڣṩnacos-loadbalancer-serviceԶ̵ã\n\n\n\n- Ȼһnacos-loadbalancer-servicenacos-user-serviceʱNacosлʾ·\n\n\n\n- ʱͨnacos-loadbalancer-serviceýӿڽвԣᷢnacos-user-serviceӡ־Ϣʹõѯԣʵַhttp://localhost:8308/user/1\n\n\n\n# ʽ\n\nȻLoadBalancerʹRestTemplateԶ̵ãʹOpenFeignʽãǾ¡\n\n- nacos-loadbalancer-serviceģpom.xmlļOpenFeign\n\n\n\n    <dependency>\n        <groupId>org.springframework.cloud</groupId>\n        spring-cloud-starter-openfeign\n    </dependency>\n\n\n\n\n\n- ȻOpenFeignĿͻ˽ӿҪõķӿԼ÷ʽ\n\n\n\n    /**\n     * Created by macro on 2019/9/5.\n     */\n    @FeignClient(value = \"nacos-user-service\")\n    public interface UserService {\n        @PostMapping(\"/user/create\")\n        CommonResult create(@RequestBody User user);\n    \n        @GetMapping(\"/user/{id}\")\n        CommonResult<User> getUser(@PathVariable Long id);\n    \n        @GetMapping(\"/user/getByUsername\")\n        CommonResult<User> getByUsername(@RequestParam String username);\n    \n        @PostMapping(\"/user/update\")\n        CommonResult update(@RequestBody User user);\n    \n        @PostMapping(\"/user/delete/{id}\")\n        CommonResult delete(@PathVariable Long id);\n    }\n\n\n\n- ControllerʹOpenFeignĿͻ˽ӿԶ̷\n\n\n\n    /**\n     * Created by macro on 2019/8/29.\n     */\n    @RestController\n    @RequestMapping(\"/userFeign\")\n    public class UserFeignController {\n        @Autowired\n        private UserService userService;\n    \n        @GetMapping(\"/{id}\")\n        public CommonResult getUser(@PathVariable Long id) {\n            return userService.getUser(id);\n        }\n    \n        @GetMapping(\"/getByUsername\")\n        public CommonResult getByUsername(@RequestParam String username) {\n            return userService.getByUsername(username);\n        }\n    \n        @PostMapping(\"/create\")\n        public CommonResult create(@RequestBody User user) {\n            return userService.create(user);\n        }\n    \n        @PostMapping(\"/update\")\n        public CommonResult update(@RequestBody User user) {\n            return userService.update(user);\n        }\n    \n        @PostMapping(\"/delete/{id}\")\n        public CommonResult delete(@PathVariable Long id) {\n            return userService.delete(id);\n        }\n    }\n\n\n\n\n\n- OpenFeignĳʱõĻapplication.ymlݣ\n\n\n\n    feign:\n      client:\n        config:\n          default: # Feignóʱ\n            connectTimeout: 5000\n            readTimeout: 5000\n\n\n\n- ͨԽӿڵԶ̷񣬷ֿãʵַhttp://localhost:8308/userFeign/1\n\n\n\n# ʵ\n\nLoadBalancerΪܣÿʱȥȡʵбǽʵб˱ػ档\n\nĬϵĻʱΪ35sΪ˼ٷ񲻿ûᱻѡĿԣǿԽá\n\n\n\n    spring:\n      cloud:\n        loadbalancer:\n          cache: # ؾ⻺\n            enabled: true # \n            ttl: 5s # ûʱ\n            capacity: 256 # ûС\n\n\n\n\n# HTTPת\n\nÿԶ̵дԶͷĻLoadBalancerRequestTransformerͨԶԭʼһת\n\n- ҪúLoadBalancerRequestTransformerBeanʵǽServiceInstanceinstanceId뵽ͷX-InstanceIdУ\n\n\n\n    /**\n     * LoadBalancer\n     * Created by macro on 2022/7/26.\n     */\n    @Configuration\n    public class LoadBalancerConfig {\n        @Bean\n        public LoadBalancerRequestTransformer transformer() {\n            return new LoadBalancerRequestTransformer() {\n                @Override\n                public HttpRequest transformRequest(HttpRequest request, ServiceInstance instance) {\n                    return new HttpRequestWrapper(request) {\n                        @Override\n                        public HttpHeaders getHeaders() {\n                            HttpHeaders headers = new HttpHeaders();\n                            headers.putAll(super.getHeaders());\n                            headers.add(\"X-InstanceId\", instance.getInstanceId());\n                            return headers;\n                        }\n                    };\n                }\n            };\n        }\n    }\n\n\n\n\n\n- Ȼ޸nacos-user-serviceеĴ룬ӡȡͷX-InstanceIdϢ\n\n\n\n    /**\n     * Created by macro on 2019/8/29.\n     */\n    @RestController\n    @RequestMapping(\"/user\")\n    public class UserController {\n        @GetMapping(\"/{id}\")\n        public CommonResult<User> getUser(@PathVariable Long id) {\n            User user = userService.getUser(id);\n            LOGGER.info(\"idȡûϢûΪ{}\", user.getUsername());\n            ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();\n            HttpServletRequest request = servletRequestAttributes.getRequest();\n            String instanceId = request.getHeader(\"X-InstanceId\");\n            if (StrUtil.isNotEmpty(instanceId)) {\n                LOGGER.info(\"ȡԶͷ:X-InstanceId={}\", instanceId);\n            }\n            return new CommonResult<>(user);\n        }\n    }\n\n- ʽӿڽвԣnacos-user-servicęӡ־ԶͷѾɹˣʵַhttp://localhost:8308/user/1\n\n\n\n    2022-07-26 15:05:19.920  INFO 14344 --- [nio-8206-exec-5] c.macro.cloud.controller.UserController  : idȡûϢûΪmacro\n    2022-07-26 15:05:19.921  INFO 14344 --- [nio-8206-exec-5] c.macro.cloud.controller.UserController  : ȡԶͷ:X-InstanceId=192.168.3.227#8206#DEFAULT#DEFAULT_GROUP@@nacos-user-service\n\n\n\n# ܽ\n\nͨLoadBalancerһʵǿԷ֣ʹLoadBalancerRibbonʵҪһЩ÷ʽ֮ͬǰʹùRibbonĻϿ޷лLoadBalancer\n\n# ο\n\nٷĵhttps://docs.spring.io/spring-cloud-commons/docs/current/reference/html/#spring-cloud-loadbalancer\n\n# ĿԴַ\n\nhttps://github.com/macrozheng/springcloud-learning/tree/master/nacos-loadbalancer-service\n\n# ο\nhttps://lijunyi.xyz/docs/SpringCloud/SpringCloud.html#_2-2-x-%E5%88%86%E6%94%AF\nhttps://mp.weixin.qq.com/s/2jeovmj77O9Ux96v3A0NtA\nhttps://juejin.cn/post/6931922457741770760\nhttps://github.com/D2C-Cai/herring\nhttp://c.biancheng.net/springcloud\nhttps://github.com/macrozheng/springcloud-learning"
  },
  {
    "path": "docs/Spring全家桶/SpringCloud/SpringCloudOpenFeign.md",
    "content": "Netflix Feign  Netflix ˾һʵָؾͷõĿԴSpring Cloud  Netflix еԴ EurekaRibbon Լ Hystrix ȣһϽ Spring Cloud Netflix ģУϺȫΪ Spring Cloud Netflix Feign\n\nFeign  [Ribbon](http://c.biancheng.net/springcloud/ribbon.html) ˼ɣ Ribbon άһݿ÷嵥ͨ Ribbon ʵ˿ͻ˵ĸؾ⡣\n\nFeign һʽ RestTemplate Ļ˽һķװͨ FeignֻҪһӿڲͨעм򵥵ã Dao ӿ Mapper עһʵֶ HTTP ӿڵİ󶨡\n\nͨ FeignǿñطһԶ̷񣬶ȫоڽԶ̵á\n\nFeign ֶ֧ע⣬ Feign ԴעԼ JAX-RS עȣź Feign ֧ Spring MVC ע⣬ɻ Spring û㡣\n\n2019  Netflix ˾ Feign ʽͣά״̬ Spring ٷƳһΪ OpenFeign Ϊ Feign \n\n## OpenFeign\n\nOpenFeign ȫ Spring Cloud OpenFeign Spring ٷƳһʽ븺ؾĳ־Ϊͣά״̬ Feign\n\nOpenFeign  Spring Cloud  Feign Ķηװ Feign йܣ Feign Ļ˶ Spring MVC ע֧֣ @RequestMapping@GetMapping  @PostMapping ȡ\n\n#### OpenFeign ע\n\nʹ OpenFegin Զ̷ʱע±\n\n| ע                | ˵                                                         |\n| ------------------- | ------------------------------------------------------------ |\n| @FeignClient        | ע֪ͨ OpenFeign  @RequestMapping עµĽӿڽн̬ͨķʽʵ࣬ʵָؾͷá |\n| @EnableFeignClients | עڿ OpenFeign ܣ Spring Cloud ӦʱOpenFeign ɨ @FeignClient עĽӿڣɴעᵽ Spring С |\n| @RequestMapping     | Spring MVC ע⣬ Spring MVC ʹøעӳָͨControllerԴЩ URL ൱ Servlet  web.xml á |\n| @GetMapping         | Spring MVC ע⣬ӳ GET һע⣬൱ @RequestMapping(method = RequestMethod.GET)  |\n| @PostMapping        | Spring MVC ע⣬ӳ POST һע⣬൱ @RequestMapping(method = RequestMethod.POST)  |\n\n> Spring Cloud Finchley ϰ汾һʹ OpenFeign Ϊ OpenFeign  2019  Feign ͣάƳģ˴ 2019 꼰ԺĿʹõĶ OpenFeign 2018 ǰĿһʹ Feign\n\n## Feign VS OpenFeign\n\nǾԱ Feign  OpenFeign ͬ\n\n#### ͬ\n\nFeign  OpenFegin ͬ㣺\n\n*   Feign  OpenFeign  Spring Cloud µԶ̵ú͸ؾ\n*   Feign  OpenFeign һʵַԶ̵ú͸ؾ⡣\n*   Feign  OpenFeign  Ribbon ˼ɣ Ribbon ά˿÷嵥ͨ Ribbon ʵ˿ͻ˵ĸؾ⡣\n*   Feign  OpenFeign ڷߣͻˣ󶨽ӿڲͨעķʽãʵԶ̷ĵá\n\n#### ͬ\n\nFeign  OpenFeign ²ͬ\n\n*   Feign  OpenFeign ͬFeign Ϊ spring-cloud-starter-feign OpenFeign Ϊ spring-cloud-starter-openfeign\n*   Feign  OpenFeign ֵ֧עⲻͬFeign ֧ Feign ע JAX-RS ע⣬֧ Spring MVC ע⣻OpenFeign ֧ Feign ע JAX-RS ע⣬֧ Spring MVC ע⡣\n\n## OpenFeign ʵԶ̷\n\nǾͨһʵʾͨ OpenFeign ʵԶ̷õġ\n\n1\\.  spring-cloud-demo2 ´һΪ micro-service-cloud-consumer-dept-feign  Spring Boot ģ飬 pom.xml \n\n\n\n\n\n```\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <parent>\n        <artifactId>spring-cloud-demo2</artifactId>\n        <groupId>net.biancheng.c</groupId>\n        <version>0.0.1-SNAPSHOT</version>\n    </parent>\n\n    <groupId>net.biancheng.c</groupId>\n    <artifactId>micro-service-cloud-consumer-dept-feign</artifactId>\n    <version>0.0.1-SNAPSHOT</version>\n    <name>micro-service-cloud-consumer-dept-feign</name>\n    <description>Demo project for Spring Boot</description>\n    <properties>\n        <java.version>1.8</java.version>\n    </properties>\n    <dependencies>\n        <dependency>\n            <groupId>net.biancheng.c</groupId>\n            <artifactId>micro-service-cloud-api</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-web</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.projectlombok</groupId>\n            <artifactId>lombok</artifactId>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <!--Eureka Client -->\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>\n        </dependency>\n        <!-- Ribbon -->\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>\n        </dependency>\n        <!-- OpenFeign -->\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            <artifactId>spring-cloud-starter-openfeign</artifactId>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-maven-plugin</artifactId>\n                <configuration>\n                    <excludes>\n                        <exclude>\n                            <groupId>org.projectlombok</groupId>\n                            <artifactId>lombok</artifactId>\n                        </exclude>\n                    </excludes>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n</project>\n\n```\n\n\n\n\n\n2\\.  micro-service-cloud-consumer-dept-feign µ· /resources Ŀ¼£һ application.yml¡\n\n\n\n\n\n```\n\nserver:\n  port: 80\neureka:\n  client:\n    register-with-eureka: false #߿Բעע\n    service-url:\n      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/\n    fetch-registry: true  #߿ͻҪȥ\n```\n\n\n\n\n\n3\\.  net.biancheng.c.service ´һΪ DeptFeignService Ľӿڣڸýӿʹ @FeignClient עʵֶԷӿڵİ󶨣¡\n\n\n\n\n\n```\npackage net.biancheng.c.service;\n\nimport net.biancheng.c.entity.Dept;\nimport org.springframework.cloud.openfeign.FeignClient;\nimport org.springframework.stereotype.Component;\nimport org.springframework.web.bind.annotation.PathVariable;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestMethod;\n\nimport java.util.List;\n\n//Ϊڵһ\n@Component\n// ṩṩķƣ application.name\n@FeignClient(value = \"MICROSERVICECLOUDPROVIDERDEPT\")\npublic interface DeptFeignService {\n    //Ӧṩߣ800180028003Controller жķ\n    @RequestMapping(value = \"/dept/get/{id}\", method = RequestMethod.GET)\n    public Dept get(@PathVariable(\"id\") int id);\n\n    @RequestMapping(value = \"/dept/list\", method = RequestMethod.GET)\n    public List<Dept> list();\n}\n\n```\n\n\n\n\n\nڱд󶨽ӿʱҪע 2 㣺\n\n*    @FeignClient עУvalue ԵȡֵΪṩߵķṩļapplication.yml spring.application.name ȡֵ\n*   ӿжÿṩߣ micro-service-cloud-provider-dept-8001 ȣ Controller ķ񷽷Ӧ\n\n4\\.  net.biancheng.c.controller £һΪ DeptController_Consumer  Controller ࣬¡\n\n\n\n\n\n```\n\npackage net.biancheng.c.controller;\n\nimport net.biancheng.c.entity.Dept;\nimport net.biancheng.c.service.DeptFeignService;\nimport org.springframework.web.bind.annotation.PathVariable;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport javax.annotation.Resource;\nimport java.util.List;\n\n@RestController\npublic class DeptController_Consumer {\n    \n    @Resource\n    private DeptFeignService deptFeignService;\n\n    @RequestMapping(value = \"/consumer/dept/get/{id}\")\n    public Dept get(@PathVariable(\"id\") Integer id) {\n        return deptFeignService.get(id);\n    }\n\n    @RequestMapping(value = \"/consumer/dept/list\")\n    public List<Dept> list() {\n        return deptFeignService.list();\n    }\n}\n```\n\n\n\n\n\n5\\.  @EnableFeignClients ע⿪ OpenFeign ܣ¡\n\n\n\n\n\n```\n\npackage net.biancheng.c;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.cloud.openfeign.EnableFeignClients;\n\n@SpringBootApplication\n@EnableFeignClients // OpenFeign \npublic class MicroServiceCloudConsumerDeptFeignApplication {\n\n    public static void main(String[] args) {\n        SpringApplication.run(MicroServiceCloudConsumerDeptFeignApplication.class, args);\n    }\n}\n```\n\n\n\n\n\nSpring Cloud ӦʱOpenFeign ɨ @FeignClient עĽӿɴע˵ Spring С\n\n6\\. עļȺṩԼ micro-service-cloud-consumer-dept-feignɺʹʡhttp://eureka7001.com/consumer/dept/listͼ\n\n![OpenFeign ʵַ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1014296212-0.png)\nͼ1OpenFeign ʵԶ̷\n\n7\\. ηʡhttp://eureka7001.com/consumer/dept/listͼ\n\n![OpenFeign Ĭϸؾ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1014294062-1.gif)\nͼ2OpenFeign ؾ\n\nͼ 2 Կ OpenFeign  RibbonҲʵ˿ͻ˵ĸؾ⣬ĬϸؾΪѯԡ\n\n## OpenFeign ʱ\n\nOpenFeign ͻ˵ĬϳʱʱΪ 1 ӣ˴ʱ䳬 1 ͻᱨΪ˱Ҫ OpenFeign ͻ˵ĳʱʱпơ\n\nǾͨһʵʾ OpenFeign νгʱƵġ\n\n1\\. еķṩߣˣ DeptController һӦʱΪ 5 ķ񣬴¡\n\n\n\n\n\n```\n\n//ʱ,÷ӦʱΪ 5 \n@RequestMapping(value = \"/dept/feign/timeout\")\npublic String DeptFeignTimeout() {\n    //ͣ 5 \n    try {\n        TimeUnit.SECONDS.sleep(5);\n    } catch (InterruptedException e) {\n        e.printStackTrace();\n    }\n    return serverPort;\n}\n\n```\n\n\n\n\n\n2\\.  micro-service-cloud-consumer-dept-feign  DeptFeignService ӿ´룬󶨷˸ոӵĳʱ\n\n\n\n\n\n```\n\n@RequestMapping(value = \"/dept/feign/timeout\")\npublic String DeptFeignTimeout();\n\n```\n\n\n\n\n\n3\\.  micro-service-cloud-consumer-dept-feign  DeptController_Consumer ´롣\n\n\n\n\n\n```\n\n@RequestMapping(value = \"/consumer/dept/feign/timeout\")\npublic String DeptFeignTimeout() {\n    // openFeign-ribbon ͻһĬϵȴһӣʱͻᱨ\n    return deptFeignService.DeptFeignTimeout();\n}\n\n```\n\n\n\n\n\n4\\. зṩߣʹηʡhttp://eureka7001.com:8001/dept/feign/timeouthttp://eureka7001.com:8002/dept/feign/timeout͡http://eureka7001.com:8003/dept/feign/timeoutȷзṩṩĳʱʹãͼ\n\n![ṩ߳ʱ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/10142a102-2.png)\nͼ3ṩߵĳʱ\n\n5\\.  micro-service-cloud-consumer-dept-feignʹʡhttp://eureka7001.com/consumer/dept/feign/timeoutͼ\n\n![OpenFeign ʱ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1014293G1-3.png)\nͼ4OpenFeign ʱ\n\n6\\.  micro-service-cloud-consumer-dept-feign  application.yml ãʱʱΪ 6 롣\n\n\n\n\n\n```\n\n\nribbon:\n  ReadTimeout: 6000 #õʱ䣬״£õʱ\n  ConnectionTimeout: 6000 #Ӻ󣬷ȡԴʱ\n\n```\n\n\n\n\n\n> ע OpenFeign  Ribbon Լؾڵײ㶼 Ribbon ʵֵģ OpenFeign ʱҲͨ Ribbon ʵֵġ\n\n7\\. ٴ micro-service-cloud-consumer-dept-feignʹʡhttp://eureka7001.com/consumer/dept/feign/timeoutͼ\n\n![OpenFeign ʱ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/10142942D-4.png)\nͼ5OpenFeign ʱ\n\n## OpenFeign ־ǿ\n\nOpenFeign ṩ־ӡܣǿͨõ־˽ϸڡ\n\nFeign Ϊÿһ FeignClient ṩһ feign.Logger ʵͨԶ OpenFeign 󶨽ӿڵĵмء\n\nOpenFeign ־ӡܵĿʽȽϼ򵥣Ǿͨһʵʾ\n\n1\\.  micro-service-cloud-consumer-dept-feign  application.yml ݡ\n\n\n\n\n\n```\n\n\nlogging:\n  level:\n    #feign ־ʲôļظýӿ\n    net.biancheng.c.service.DeptFeignService: debug\n```\n\n\n\n\n\n˵£\n\n*   net.biancheng.c.service.DeptFeignService ǿ @FeignClient עĽӿڣ󶨽ӿڣҲֻò·ʾظ·µз󶨽ӿ\n*   debugʾýӿڵ־\n\nõĺǣOpenFeign  debug  net.biancheng.c.service.DeptFeignService ӿڡ\n\n2.  net.biancheng.c. config ´һΪ ConfigBean ࣬¡\n\n\n\n\n\n```\n\npackage net.biancheng.c.config;\nimport feign.Logger;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n@Configuration\npublic class ConfigBean {\n    /**\n     * OpenFeign ־ǿ\n     *  OpenFeign ¼Щ\n     */\n    @Bean\n    Logger.Level feginLoggerLevel() {\n        return Logger.Level.FULL;\n    }\n}\n\n```\n\n\n\n\n\nõͨõ Logger.Level  OpenFeign ¼Щ־ݡ\n\nLogger.Level ľ弶£\n\n*   NONE¼κϢ\n*   BASIC¼󷽷URL ԼӦ״ִ̬ʱ䡣\n*   HEADERS˼¼ BASIC Ϣ⣬¼ӦͷϢ\n*   FULL¼ӦϸͷϢ塢Ԫݵȵȡ\n\n3\\.  micro-service-cloud-consumer-dept-feignʹʡhttp://eureka7001.com/consumer/dept/list̨¡\n\n```\n2021-10-12 14:33:07.408 DEBUG 13388 --- [p-nio-80-exec-2] n.biancheng.c.service.DeptFeignService   : [DeptFeignService#list] ---> GET http://MICROSERVICECLOUDPROVIDERDEPT/dept/list HTTP/1.1\n2021-10-12 14:33:07.408 DEBUG 13388 --- [p-nio-80-exec-2] n.biancheng.c.service.DeptFeignService   : [DeptFeignService#list] ---> END HTTP (0-byte body)\n2021-10-12 14:33:07.983 DEBUG 13388 --- [p-nio-80-exec-2] n.biancheng.c.service.DeptFeignService   : [DeptFeignService#list] <--- HTTP/1.1 200 (574ms)\n2021-10-12 14:33:07.983 DEBUG 13388 --- [p-nio-80-exec-2] n.biancheng.c.service.DeptFeignService   : [DeptFeignService#list] connection: keep-alive\n2021-10-12 14:33:07.983 DEBUG 13388 --- [p-nio-80-exec-2] n.biancheng.c.service.DeptFeignService   : [DeptFeignService#list] content-type: application/json\n2021-10-12 14:33:07.983 DEBUG 13388 --- [p-nio-80-exec-2] n.biancheng.c.service.DeptFeignService   : [DeptFeignService#list] date: Tue, 12 Oct 2021 06:33:07 GMT\n2021-10-12 14:33:07.983 DEBUG 13388 --- [p-nio-80-exec-2] n.biancheng.c.service.DeptFeignService   : [DeptFeignService#list] keep-alive: timeout=60\n2021-10-12 14:33:07.983 DEBUG 13388 --- [p-nio-80-exec-2] n.biancheng.c.service.DeptFeignService   : [DeptFeignService#list] transfer-encoding: chunked\n2021-10-12 14:33:07.983 DEBUG 13388 --- [p-nio-80-exec-2] n.biancheng.c.service.DeptFeignService   : [DeptFeignService#list]\n2021-10-12 14:33:07.991 DEBUG 13388 --- [p-nio-80-exec-2] n.biancheng.c.service.DeptFeignService   : [DeptFeignService#list] [{\"deptNo\":1,\"deptName\":\"\",\"dbSource\":\"bianchengbang_jdbc\"},{\"deptNo\":2,\"deptName\":\"²\",\"dbSource\":\"bianchengbang_jdbc\"},{\"deptNo\":3,\"deptName\":\"\",\"dbSource\":\"bianchengbang_jdbc\"},{\"deptNo\":4,\"deptName\":\"г\",\"dbSource\":\"bianchengbang_jdbc\"},{\"deptNo\":5,\"deptName\":\"ά\",\"dbSource\":\"bianchengbang_jdbc\"}]\n2021-10-12 14:33:07.991 DEBUG 13388 --- [p-nio-80-exec-2] n.biancheng.c.service.DeptFeignService   : [DeptFeignService#list] <--- END HTTP (341-byte body)```\n```\n# ο\nhttps://lijunyi.xyz/docs/SpringCloud/SpringCloud.html#_2-2-x-%E5%88%86%E6%94%AF\nhttps://mp.weixin.qq.com/s/2jeovmj77O9Ux96v3A0NtA\nhttps://juejin.cn/post/6931922457741770760\nhttps://github.com/D2C-Cai/herring\nhttp://c.biancheng.net/springcloud\nhttps://github.com/macrozheng/springcloud-learning"
  },
  {
    "path": "docs/Spring全家桶/SpringCloud/SpringCloudRibbon.md",
    "content": "Spring Cloud Ribbon һ׻ Netflix Ribbon ʵֵĿͻ˸ؾͷùߡ\n\nNetflix Ribbon  Netflix ˾ĿԴҪṩͻ˵ĸؾ㷨ͷáSpring Cloud  Netflix еԴ EurekaFeign Լ Hystrix ȣһϽ Spring Cloud Netflix ģУϺȫΪ Spring Cloud Netflix Ribbon\n\nRibbon  Spring Cloud Netflix ģģ飬 Spring Cloud  Netflix Ribbon ĶηװͨǿԽ REST ģ壨RestTemplateתΪͻ˸ؾķá\n\nRibbon  Spring Cloud ϵġҪ֮һȻֻһ͵Ŀܣ Eureka ServerעģҪ𣬵ÿһʹ Spring Cloud ΢С\n\nSpring Cloud ΢֮ĵãAPI صתݣʵ϶ͨ Spring Cloud Ribbon ʵֵģҪܵ [OpenFeign](http://c.biancheng.net/springcloud/open-feign.html) Ҳǻʵֵġ\n\n## ؾ\n\nκһϵͳУؾⶼһʮҪҲòȥʵʩݣϵͳ߲ѹͷݵҪֶ֮һ\n\nؾ⣨Load Balance 򵥵˵ǽûƽ̯䵽УԴﵽչǿݴĿԺԵĿġ\n\nĸؾⷽʽ֣\n\n*   ˸ؾ\n*   ͻ˸ؾ\n\n#### ˸ؾ\n\n˸ؾĸؾⷽʽ乤ԭͼ\n\n![˸ؾ⹤ԭ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/10122164F-0.png)\nͼ1˸ؾ⹤ԭ\n\n˸ؾڿͻ˺ͷ֮佨һĸؾ÷ȿӲ豸 F5Ҳ Nginxؾάһݿ÷嵥Ȼͨɾϵķ˽ڵ㣬Ա֤嵥езڵ㶼ǿʵġ\n\nͻ˷ʱ󲻻ֱӷ͵˽дȫؾɸؾĳ㷨ѯȣάĿ÷嵥ѡһˣȻת\n\n˸ؾص㣺\n\n*   Ҫһĸؾ\n*   ؾڿͻ˷еģ˿ͻ˲֪ĸṩķ\n*   ÷嵥洢ڸؾϡ\n\n#### ͻ˸ؾ\n\nڷ˸ؾ⣬ͻ˷ھһȽСڵĸ\n\nͻ˸ؾĹԭͼ\n\n![ͻ˸ؾԭ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1012212Q4-1.png)\n\nͼ2ͻ˸ؾ⹤ԭ\n\n\nͻ˸ؾǽؾ߼Դʽװͻϣؾλڿͻˡͻͨעģ Eureka ServerȡһݷṩĿ÷嵥˷嵥󣬸ؾڿͻ˷ǰͨؾ㷨ѡһʵٽзʣԴﵽؾĿģ\n\nͻ˸ؾҲҪȥά嵥ЧԣҪϷעһɡ\n\nͻ˸ؾص㣺\n\n*   ؾλڿͻˣҪһؾ\n*   ؾڿͻ˷ǰеģ˿ͻ֪ĸṩķ\n*   ͻ˶άһݿ÷嵥嵥ǴӷעĻȡġ\n\nRibbon һ HTTP  TCP Ŀͻ˸ؾǽ Ribbon  Eureka һʹʱRibbon  Eureka ServerעģлȡбȻͨؾԽ̯ṩߣӶﵽؾĿġ\n\n#### ˸ؾ VS ͻ˸ؾ\n\nǾԱ£˸ؾͿͻ˸ؾ⵽ʲô±\n\n| ͬ                       | ˸ؾ                                               | ͻ˸ؾ                                               |\n| ---------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |\n| ǷҪؾ   | Ҫڿͻ˺ͷ֮佨һĸؾ       | ؾ߼Դʽװͻϣ˲Ҫؾ |\n| ǷҪע         | Ҫעġ                                         | Ҫעġڿͻ˸ؾУеĿͻ˺ͷ˶Ҫṩķעᵽעϡ |\n| ÷嵥洢λ       | ÷嵥洢λڿͻ֮ĸؾϡ | еĿͻ˶άһݿ÷嵥Щ嵥ǴӷעĻȡġ |\n| ؾʱ               | Ƚ͵ؾȻɸؾͨؾ㷨ڶ֮ѡһзʣڷٽиؾ㷨䡣򵥵˵ǣȷٽиؾ⡣ | ڷǰλڿͻ˵ķؾ Ribbonͨؾ㷨ѡһȻзʡ򵥵˵ǣȽиؾ⣬ٷ |\n| ͻǷ˽ṩϢ | ڸؾڿͻ˷еģ˿ͻ˲֪ĸṩķ | ؾڿͻ˷ǰеģ˿ͻ֪ĸṩķ |\n\n## Ribbon ʵַ\n\nRibbon  RestTemplateRest ģ壩ʹãʵ΢֮ĵá\n\nRestTemplate  Spring еһѵ REST ܡRestTemplate ʵ˶ HTTP ķװṩһģ廯ķ÷ͨSpring ӦÿԺܷضԸ͵ HTTP зʡ\n\nRestTemplate Ը͵ HTTP ṩӦķд HEADGETPOSTPUTDELETE ͵ HTTP 󣬷ֱӦ RestTemplate е headForHeaders()getForObject()postForObject()put() Լ delete() \n\nͨһ򵥵ʵʾ Ribbon ʵַõġ\n\n1\\.  spring-cloud-demo2 £һΪ micro-service-cloud-consumer-dept-80 ΢񣬲 pom.xml ¡\n\n\n\n\n\n```\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <!---->\n    <parent>\n        <artifactId>spring-cloud-demo2</artifactId>\n        <groupId>net.biancheng.c</groupId>\n        <version>0.0.1-SNAPSHOT</version>\n    </parent>\n\n    <groupId>net.biancheng.c</groupId>\n    <artifactId>micro-service-cloud-consumer-dept-80</artifactId>\n    <version>0.0.1-SNAPSHOT</version>\n    <name>micro-service-cloud-consumer-dept-80</name>\n    <description>Demo project for Spring Boot</description>\n    <properties>\n        <java.version>1.8</java.version>\n    </properties>\n\n    <dependencies>\n        <!--ģ-->\n        <dependency>\n            <groupId>net.biancheng.c</groupId>\n            <artifactId>micro-service-cloud-api</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n        <!--Spring Boot Web-->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-web</artifactId>\n        </dependency>\n        <!--lombok-->\n        <dependency>\n            <groupId>org.projectlombok</groupId>\n            <artifactId>lombok</artifactId>\n            <optional>true</optional>\n        </dependency>\n        <!--Spring Boot -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <!--Spring Cloud Eureka ͻ-->\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>\n        </dependency>\n        <!--Spring Cloud Ribbon -->\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-maven-plugin</artifactId>\n                <configuration>\n                    <excludes>\n                        <exclude>\n                            <groupId>org.projectlombok</groupId>\n                            <artifactId>lombok</artifactId>\n                        </exclude>\n                    </excludes>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n</project>\n\n```\n\n\n\n\n\n2. · /resource Ŀ¼£½һļ application.yml¡\n\n\n\n\n\n```\nserver:\n  port: 80 #˿ں\n\n############################################# Spring Cloud Ribbon ؾ##########################\neureka:\n  client:\n    register-with-eureka: false #΢ΪߣҪԼעᵽע\n    fetch-registry: true  #΢ΪߣҪע\n    service-url:\n      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/ #עļȺ\n```\n\n\n\n\n\n3\\.  net.biancheng.c.config £һΪ ConfigBean ࣬ RestTemplate ע뵽У¡\n\n\n\n\n\n```\n\n\npackage net.biancheng.c.config;\n\nimport com.netflix.loadbalancer.IRule;\n\nimport com.netflix.loadbalancer.RetryRule;\nimport org.springframework.cloud.client.loadbalancer.LoadBalanced;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.web.client.RestTemplate;\n// ע\n@Configuration\npublic class ConfigBean {\n    \n    @Bean // RestTemplate ע뵽\n    @LoadBalanced //ڿͻʹ RestTemplate ʱؾ⣨Ribbon\n    public RestTemplate getRestTemplate() {\n        return new RestTemplate();\n    }\n}\n\n```\n\n\n\n\n\n4\\.  net.biancheng.c.controller £һΪ DeptController_Consumer  Controller Controller жڵ÷ṩķ񣬴¡\n\n\n\n\n\n```\npackage net.biancheng.c.controller;\n\nimport net.biancheng.c.entity.Dept;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.web.bind.annotation.PathVariable;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\nimport org.springframework.web.client.RestTemplate;\n\nimport java.util.List;\n\n@RestController\npublic class DeptController_Consumer {\n    //private static final String REST_URL_PROVIDER_PREFIX = \"http://localhost:8001/\"; ַʽֱ÷񷽵ķûõ Spring Cloud\n\n    //΢̣ͨ΢ȡõַ\n    private static final String REST_URL_PROVIDER_PREFIX = \"http://MICROSERVICECLOUDPROVIDERDEPT\"; // ʹעᵽ Spring Cloud Eureka עеķ񣬼 application.name\n\n    @Autowired\n    private RestTemplate restTemplate; //RestTemplate һּ򵥱ݵķ restful ģ࣬ Spring ṩڷ Rest Ŀͻģ幤߼ṩ˶ֱݷԶ HTTP ķ\n\n    //ȡָϢ\n    @RequestMapping(value = \"/consumer/dept/get/{id}\")\n    public Dept get(@PathVariable(\"id\") Integer id) {\n        return restTemplate.getForObject(REST_URL_PROVIDER_PREFIX + \"/dept/get/\" + id, Dept.class);\n    }\n    //ȡб\n    @RequestMapping(value = \"/consumer/dept/list\")\n    public List<Dept> list() {\n        return restTemplate.getForObject(REST_URL_PROVIDER_PREFIX + \"/dept/list\", List.class);\n    }\n}\n```\n\n\n\n\n\n5\\.  micro-service-cloud-consumer-dept-80 ϣʹ @EnableEurekaClient ע Eureka ͻ˹ܣ¡\n\n\n\n\n\n```\n\npackage net.biancheng.c;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.cloud.netflix.eureka.EnableEurekaClient;\n\n@SpringBootApplication\n@EnableEurekaClient\npublic class MicroServiceCloudConsumerDept80Application {\n\n    public static void main(String[] args) {\n        SpringApplication.run(MicroServiceCloudConsumerDept80Application.class, args);\n    }\n}\n\n```\n\n\n\n\n\n6\\. ע micro-service-cloud-eureka-7001ṩ micro-service-cloud-provider-dept-8001 Լ micro-service-cloud-consumer-dept-80\n\n7\\. ʹʡhttp://eureka7001.com:80/consumer/dept/listͼ\n\n![Ribbon ʵַ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/101221CG-2.png)\nͼ3Ribbon ʵַ\n\n## Ribbon ʵָؾ\n\nRibbon һͻ˵ĸؾ Eureka ʹɵʵֿͻ˵ĸؾ⡣Ribbon ȴ Eureka ServerעģȥȡбȻͨؾԽ̯ˣӶﵽؾĿġ\n\nSpring Cloud Ribbon ṩһ IRule ӿڣýӿҪ帺ؾԣ 7 Ĭʵ࣬ÿһʵ඼һָؾԡ\n\n|  | ʵ                    | ؾ                                                 |\n| ---- | ------------------------- | ------------------------------------------------------------ |\n| 1    | RoundRobinRule            | ѯԣһ˳ѡȡʵ           |\n| 2    | RandomRule                | ѡȡһʵ                                         |\n| 3    | RetryRule                 |  RoundRobinRuleѯĲȡȡķʵΪ null ѾʧЧָʱ֮ڲϵؽԣʱȡĲԻ RoundRobinRule жĲԣָʱȻûȡʵ򷵻 null  |\n| 4    | WeightedResponseTimeRule  | WeightedResponseTimeRule  RoundRobinRule һ࣬ RoundRobinRule ĹܽչƽӦʱ䣬зʵȨأӦʱԽ̵ķʵȨԽߣѡеĸԽ󡣸ʱͳϢ㣬ʹѯԣϢ㹻ʱл WeightedResponseTimeRule |\n| 5    | BestAvailableRule         | ̳ ClientConfigEnabledRoundRobinRuleȹ˵ϻʧЧķʵȻѡ񲢷Сķʵ |\n| 6    | AvailabilityFilteringRule | ȹ˵ϻʧЧķʵȻѡ񲢷Сķʵ |\n| 7    | ZoneAvoidanceRule         | ĬϵĸؾԣۺжϷzoneܺͷserverĿԣѡʵûĻ£òѯRandomRuleơ |\n\nǾͨһʵ֤£Ribbon Ĭʹʲôѡȡʵġ\n\n1.  MySQL ݿִ SQL 䣬׼ݡ\n\n```\nDROP DATABASE IF EXISTS spring_cloud_db2;\nCREATE DATABASE spring_cloud_db2 CHARACTER SET UTF8;\n\nUSE spring_cloud_db2;\n\nDROP TABLE IF EXISTS `dept`;\nCREATE TABLE `dept` (\n  `dept_no` int NOT NULL AUTO_INCREMENT,\n  `dept_name` varchar(255) DEFAULT NULL,\n  `db_source` varchar(255) DEFAULT NULL,\n  PRIMARY KEY (`dept_no`)\n) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;\n\nINSERT INTO `dept` VALUES ('1', '', DATABASE());\nINSERT INTO `dept` VALUES ('2', '²', DATABASE());\nINSERT INTO `dept` VALUES ('3', '', DATABASE());\nINSERT INTO `dept` VALUES ('4', 'г', DATABASE());\nINSERT INTO `dept` VALUES ('5', 'ά', DATABASE());\n\n#############################################################################################\n\nDROP DATABASE IF EXISTS spring_cloud_db3;\n\nCREATE DATABASE spring_cloud_db3 CHARACTER SET UTF8;\n\nUSE spring_cloud_db3;\n\nDROP TABLE IF EXISTS `dept`;\nCREATE TABLE `dept` (\n  `dept_no` int NOT NULL AUTO_INCREMENT,\n  `dept_name` varchar(255) DEFAULT NULL,\n  `db_source` varchar(255) DEFAULT NULL,\n  PRIMARY KEY (`dept_no`)\n) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;\n\nINSERT INTO `dept` VALUES ('1', '', DATABASE());\nINSERT INTO `dept` VALUES ('2', '²', DATABASE());\nINSERT INTO `dept` VALUES ('3', '', DATABASE());\nINSERT INTO `dept` VALUES ('4', 'г', DATABASE());\nINSERT INTO `dept` VALUES ('5', 'ά', DATABASE());```\n\n2\\. ο micro-service-cloud-provider-dept-8001ٴ΢ Moudle micro-service-cloud-provider-dept-8002  micro-service-cloud-provider-dept-8003\n\n3\\.  micro-service-cloud-provider-dept-8002  application.yml У޸Ķ˿ںšݿϢԼԶϢeureka.instance.instance-id޸ĵ¡\n```\n\n\n\n\n```\n\nserver:\n  port: 8002  #˿ں޸Ϊ 8002\n\nspring:\n  application:\n    name: microServiceCloudProviderDept  #΢ƣ޸ģ micro-service-cloud-provider-dept-8001 ñһ\n\n  datasource:\n    username: root        #ݿ½û\n    password: root        #ݿ½\n    url: jdbc:mysql://127.0.0.1:3306/spring_cloud_db2       #ݿurl\n    driver-class-name: com.mysql.jdbc.Driver                  #ݿ\n\neureka:\n  client: #ͻעᵽ eureka б\n    service-url:\n     #defaultZone: http://eureka7001:7001/eureka  #ַ 7001ע application.yml б¶עַ 棩\n      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/  #עᵽ Eureka Ⱥ\n  instance:\n    instance-id: spring-cloud-provider-8002 #޸ԶķϢ\n    prefer-ip-address: true  #ʾ· ip ַ\n\n```\n\n\n\n\n\n4\\.  micro-service-cloud-provider-dept-8003  application.yml У޸Ķ˿ںԼݿϢ޸ĵ¡\n\n\n\n\n\n```\n\nserver:\n  port: 8003  #˿ں޸Ϊ 8003\n\nspring:\n  application:\n    name: microServiceCloudProviderDept  #΢ƣ޸ģ micro-service-cloud-provider-dept-8001 ñһ\n\n  datasource:\n    username: root        #ݿ½û\n    password: root        #ݿ½\n    url: jdbc:mysql://127.0.0.1:3306/spring_cloud_db3      #ݿurl\n    driver-class-name: com.mysql.jdbc.Driver                  #ݿ\n\neureka:\n  client: #ͻעᵽ eureka б\n    service-url:\n     #defaultZone: http://eureka7001:7001/eureka  #ַ 7001ע application.yml б¶עַ 棩\n      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/  #עᵽ Eureka Ⱥ\n  instance:\n    instance-id: spring-cloud-provider-8003 #ԶϢ\n    prefer-ip-address: true  #ʾ· ip ַ\n\n```\n\n\n\n\n\n5.   micro-service-cloud-eureka-7001/7002/7003עļȺmicro-service-cloud-provider-dept-8001/8002/8003ṩ߼ȺԼ micro-service-cloud-consumer-dept-80ߣ\n\n6\\. ʹʡhttp://eureka7001.com/consumer/dept/get/1,ͼ\n\n![Ribbon Ĭϸؾ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1012215133-3.gif)\nͼ4Ribbon Ĭϸؾ\n\nͨͼ 4  dbSource ֶȡֵı仯ԿSpring Cloud Ribbon ĬʹѯԽиؾ⡣\n\n## лؾ\n\nSpring Cloud Ribbon ĬʹѯѡȡʵҲԸлؾԡ\n\nлؾԵķܼ򵥣ֻҪڷߣͻˣУ IRule ʵע뵽мɡ\n\nǾͨһʵʾлؾĲԡ\n\n1\\.  micro-service-cloud-consumer-dept-80  ConfigBean ´룬ؾлΪ RandomRule\n\n\n\n\n\n```\n\n\n@Bean\npublic IRule myRule() {\n    // RandomRule Ϊ\n    return  new RandomRule();\n}\n\n```\n\n\n\n\n\n2\\.  micro-service-cloud-consumer-dept-80ʹʡhttp://eureka7001.com/consumer/dept/get/1ͼ\n\n![лؾΪ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1012214B8-4.gif)\nͼ5лؾΪ\n\nͨͼ 5  dbSource ֶȡֵı仯ԿѾؾлΪ RandomRule\n\n## Ƹؾ\n\nͨ£Ribbon ṩЩĬϸؾǿǵģҪǻԸƸؾԡ\n\nǾʾζƸؾԡ\n\n1\\.  micro-service-cloud-consumer-dept-80 ½һ net.biancheng.myrule ڸð´һΪ MyRandomRule ࣬¡\n\n\n\n\n\n```\n\npackage net.biancheng.myrule;\n\nimport com.netflix.client.config.IClientConfig;\nimport com.netflix.loadbalancer.AbstractLoadBalancerRule;\nimport com.netflix.loadbalancer.ILoadBalancer;\nimport com.netflix.loadbalancer.Server;\n\nimport java.util.List;\n\n/**\n*  Ribbon ؾ\n*/\npublic class MyRandomRule extends AbstractLoadBalancerRule {\n    private int total = 0;            // ܹõĴĿǰҪÿ̨5\n    private int currentIndex = 0;    // ǰṩĻ\n\n    public Server choose(ILoadBalancer lb, Object key) {\n        if (lb == null) {\n            return null;\n        }\n        Server server = null;\n\n        while (server == null) {\n            if (Thread.interrupted()) {\n                return null;\n            }\n            //ȡЧķʵб\n            List<Server> upList = lb.getReachableServers();\n            //ȡеķʵб\n            List<Server> allList = lb.getAllServers();\n\n            //ûκεķʵ򷵻 null\n            int serverCount = allList.size();\n            if (serverCount == 0) {\n                return null;\n            }\n            //ƣÿʵֻڵ 3 ֮󣬲Żķʵ\n            if (total < 3) {\n                server = upList.get(currentIndex);\n                total++;\n            } else {\n                total = 0;\n                currentIndex++;\n                if (currentIndex >= upList.size()) {\n                    currentIndex = 0;\n                }\n            }\n            if (server == null) {\n                Thread.yield();\n                continue;\n            }\n            if (server.isAlive()) {\n                return (server);\n            }\n            server = null;\n            Thread.yield();\n        }\n        return server;\n    }\n\n    @Override\n    public Server choose(Object key) {\n        return choose(getLoadBalancer(), key);\n    }\n\n    @Override\n    public void initWithNiwsConfig(IClientConfig clientConfig) {\n        // TODO Auto-generated method stub\n    }\n}\n\n```\n\n\n\n\n\n2\\.  net.biancheng.myrule ´һΪ MySelfRibbonRuleConfig ࣬ǶƵĸؾʵע뵽У¡\n\n\n\n\n\n```\npackage net.biancheng.myrule;\n\nimport com.netflix.loadbalancer.IRule;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n/**\n*  Ribbon ؾԵ\n* Զ Ribbon ؾ  net.biancheng.c Ӱ\n* е Ribbon ͻ˶øòԣ޷ﵽ⻯ƵĿ\n*/\n@Configuration\npublic class MySelfRibbonRuleConfig {\n    @Bean\n    public IRule myRule() {\n        //Զ Ribbon ؾ\n        return new MyRandomRule(); //Զ壬ѡĳһ΢ִ\n    }\n}\n\n```\n\n\n\n\n\n3\\. ޸λ net.biancheng.c µ࣬ڸʹ @RibbonClient עǶƵĸؾЧ¡\n\n\n\n\n\n```\n\n\npackage net.biancheng.c;\n\nimport net.biancheng.myrule.MySelfRibbonRuleConfig;\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.cloud.netflix.eureka.EnableEurekaClient;\nimport org.springframework.cloud.netflix.ribbon.RibbonClient;\n\n@SpringBootApplication\n@EnableEurekaClient\n//Զ Ribbon ؾʹ RibbonClient ע⣬ڸ΢ʱԶȥԶ Ribbon ࣬ӶЧ\n// name ΪҪƸؾԵ΢ƣapplication name\n// configuration ΪƵĸؾԵ࣬\n// ҹٷĵȷ಻ ComponentScan ע⣨SpringBootApplication עа˸ע⣩µİӰУԶ帺ؾ಻ net.biancheng.c Ӱ\n@RibbonClient(name = \"MICROSERVICECLOUDPROVIDERDEPT\", configuration = MySelfRibbonRuleConfig.class)\npublic class MicroServiceCloudConsumerDept80Application {\n\n    public static void main(String[] args) {\n        SpringApplication.run(MicroServiceCloudConsumerDept80Application.class, args);\n    }\n}\n\n```\n\n\n\n\n\n4\\.  micro-service-cloud-consumer-dept-80ʹʡhttp://eureka7001.com/consumer/dept/get/1ͼ\n\n![Ƹؾ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/10122152E-5.gif)\nͼ6Ƹؾ\n\nͨͼ 6  dbSource ֶȡֵı仯ԿǶƵĸؾѾЧ\n\n# ο\nhttps://lijunyi.xyz/docs/SpringCloud/SpringCloud.html#_2-2-x-%E5%88%86%E6%94%AF\nhttps://mp.weixin.qq.com/s/2jeovmj77O9Ux96v3A0NtA\nhttps://juejin.cn/post/6931922457741770760\nhttps://github.com/D2C-Cai/herring\nhttp://c.biancheng.net/springcloud\nhttps://github.com/macrozheng/springcloud-learning"
  },
  {
    "path": "docs/Spring全家桶/SpringCloud/SpringCloudSleuth.md",
    "content": "> Spring Cloud Sleuth ǷֲʽϵͳиٷõĹߣֱ۵չʾһĵụ̀Ľ÷ϸܡ\n\n## [#](https://www.macrozheng.com/cloud/sleuth.html#spring-cloud-sleuth-%E7%AE%80%E4%BB%8B)Spring Cloud Sleuth \n\nǵϵͳԽԽӴ󣬸ĵùϵҲԽԽӡͻ˷һʱ󾭹շ˽ÿһпܷӳٻ󣬴ӶʧܡʱǾҪ·ٹǣõķ·⡣\n\n## [#](https://www.macrozheng.com/cloud/sleuth.html#%E7%BB%99%E6%9C%8D%E5%8A%A1%E6%B7%BB%E5%8A%A0%E8%AF%B7%E6%B1%82%E9%93%BE%E8%B7%AF%E8%B7%9F%E8%B8%AA)·\n\n> ǽͨuser-serviceribbon-service֮ķʾùܣǵribbon-serviceĽӿʱribbon-serviceͨRestTemplateuser-serviceṩĽӿڡ\n\n*   ȸuser-serviceribbon-service·ٹ֧֣ܵ\n\n*   user-serviceribbon-service\n\n\n\n```\n<dependency>\n    <groupId>org.springframework.cloud</groupId>\n    spring-cloud-starter-zipkin\n</dependency>\n\n```\n\n\n\n\n*   ޸application.ymlļռ־zipkin-serverʵַ\n\n\n\n```\nspring:\n  zipkin:\n    base-url: http://localhost:9411\n  sleuth:\n    sampler:\n      probability: 0.1 #Sleuthĳռ\n\n```\n\n\n\n## [#](https://www.macrozheng.com/cloud/sleuth.html#%E6%95%B4%E5%90%88zipkin%E8%8E%B7%E5%8F%96%E5%8F%8A%E5%88%86%E6%9E%90%E6%97%A5%E5%BF%97)Zipkinȡ־\n\n> ZipkinTwitterһԴĿȡͷSpring Cloud Sleuth в·־ṩWebֱ۵ز鿴·Ϣ\n\n*   SpringBoot 2.0ϰ汾ѾҪдzipkin-serverǿԴӸõַzipkin-serverhttps://repo1.maven.org/maven2/io/zipkin/java/zipkin-server/2.12.9/zipkin-server-2.12.9-exec.jar\n\n*   ɺʹzipkin-server\n\n\n\n```\njava -jar zipkin-server-2.12.9-exec.jar\n\n```\n\n1\n\n\n\n\n*   Zipkinҳʵַhttp://localhost:9411\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/springcloud_sleuth_01.625f37c3.png)\n\n*   eureka-severribbon-serviceuser-service\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/springcloud_sleuth_02.baf7b77c.png)\n\n*   εãSleuthΪռribbon-serviceĽӿ[http://localhost:8301/user/1open in new window](http://localhost:8301/user/1) 鿴ZipkinҳѾ·Ϣˣ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/springcloud_sleuth_03.a71d1cf1.png)\n\n*   鿴ֱ۵ؿ·ͨÿĺʱ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/springcloud_sleuth_04.d13e3d99.png)\n\n## [#](https://www.macrozheng.com/cloud/sleuth.html#%E4%BD%BF%E7%94%A8elasticsearch%E5%AD%98%E5%82%A8%E8%B7%9F%E8%B8%AA%E4%BF%A1%E6%81%AF)ʹElasticsearch洢Ϣ\n\n> ǰzipkin-serverһ¾ͻָᷢոյĴ洢ĸϢȫʧˣɼǴ洢ڴеģʱҪϢ洢Դ洢ElasticsearchΪʾ¸ùܡ\n\n### [#](https://www.macrozheng.com/cloud/sleuth.html#%E5%AE%89%E8%A3%85elasticsearch)װElasticsearch\n\n*   Elasticsearch6.2.2zipѹָĿ¼صַ[https://www.elastic.co/cn/downloads/past-releases/elasticsearch-6-2-2open in new window](https://www.elastic.co/cn/downloads/past-releases/elasticsearch-6-2-2)\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/arch_screen_25.48daf958.png)\n\n*   binĿ¼µelasticsearch.batElasticsearch\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/arch_screen_27.ba3cb8e0.png)\n\n### [#](https://www.macrozheng.com/cloud/sleuth.html#%E4%BF%AE%E6%94%B9%E5%90%AF%E5%8A%A8%E5%8F%82%E6%95%B0%E5%B0%86%E4%BF%A1%E6%81%AF%E5%AD%98%E5%82%A8%E5%88%B0elasticsearch)޸Ϣ洢Elasticsearch\n\n*   ʹУͿ԰ѸϢ洢ElasticsearchȥˣҲᶪʧ\n\n\n\n```\n# STORAGE_TYPEʾ洢 ES_HOSTSʾESķʵַ\njava -jar zipkin-server-2.12.9-exec.jar --STORAGE_TYPE=elasticsearch --ES_HOSTS=localhost:9200 \n\n```\n\n\n*   ֮Ҫuser-serviceribbon-serviceЧεribbon-serviceĽӿ[http://localhost:8301/user/1open in new window](http://localhost:8301/user/1)\n\n*   װElasticsearchĿӻKibanaĻԿѾ洢˸Ϣ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/springcloud_sleuth_05.9929ce6a.png)\n\n### [#](https://www.macrozheng.com/cloud/sleuth.html#%E6%9B%B4%E5%A4%9A%E5%90%AF%E5%8A%A8%E5%8F%82%E6%95%B0%E5%8F%82%E8%80%83)ο\n\nhttps://github.com/openzipkin/zipkin/tree/master/zipkin-server#elasticsearch-storage\n\n## [#](https://www.macrozheng.com/cloud/sleuth.html#%E4%BD%BF%E7%94%A8%E5%88%B0%E7%9A%84%E6%A8%A1%E5%9D%97)ʹõģ\n\n\n\n```\nspringcloud-learning\n eureka-server -- eurekaע\n user-service -- ṩUserCRUDӿڵķ\n ribbon-service -- ribbonòԷ\n\n```\n\n\n\n\n## [#](https://www.macrozheng.com/cloud/sleuth.html#%E9%A1%B9%E7%9B%AE%E6%BA%90%E7%A0%81%E5%9C%B0%E5%9D%80)ĿԴַ\n\n[https://github.com/macrozheng/springcloud-learning](https://github.com/macrozheng/springcloud-learning)\n\n# ο\nhttps://lijunyi.xyz/docs/SpringCloud/SpringCloud.html#_2-2-x-%E5%88%86%E6%94%AF\nhttps://mp.weixin.qq.com/s/2jeovmj77O9Ux96v3A0NtA\nhttps://juejin.cn/post/6931922457741770760\nhttps://github.com/D2C-Cai/herring\nhttp://c.biancheng.net/springcloud\nhttps://github.com/macrozheng/springcloud-learning"
  },
  {
    "path": "docs/Spring全家桶/SpringCloud/SpringCloudZuul.md",
    "content": "Spring Cloud Zuul Spring Cloud Netflix Ŀĺ֮һΪ΢ܹеAPIʹãֶ֧̬·˹ܣĽ÷ϸܡ\n\n# Zuul\n\nAPIΪ΢ܹеķṩͳһķڣͻͨAPIططAPIصĶģʽеģʽ൱΢ܹе棬пͻ˵ķʶͨ·ɼˡʵ·ɡؾ⡢Уˡݴۺϵȹܡ\n\n#һzuul-proxyģ\n\nǴһzuul-proxyģʾzuulĳùܡ\n\n#pom.xml\n\n\n\n    <dependency>\n        <groupId>org.springframework.cloud</groupId>\n        spring-cloud-starter-netflix-eureka-client\n    </dependency>\n    <dependency>\n        <groupId>org.springframework.cloud</groupId>\n        spring-cloud-starter-netflix-zuul\n    </dependency>\n\n\n\n\n# application.ymlн\n\n\n\n    server:\n      port: 8801\n    spring:\n      application:\n        name: zuul-proxy\n    eureka:\n      client:\n        register-with-eureka: true\n        fetch-registry: true\n        service-url:\n          defaultZone: http://localhost:8001/eureka/\n\n\n#@EnableZuulProxyעZuulAPIع\n\n\n\n    @EnableZuulProxy\n    @EnableDiscoveryClient\n    @SpringBootApplication\n    public class ZuulProxyApplication {\n    \n        public static void main(String[] args) {\n            SpringApplication.run(ZuulProxyApplication.class, args);\n        }\n    \n    }\n\n\n\n# ù\n\n# ط\n\nͨeureka-serveruser-servicefeign-servicezuul-proxyʾZuulĳùܣעʾ¡\n\n\n\n# ·ɹ\n\n- ǿͨ޸application.ymlе·ɹǽƥ/userService/**·ɵuser-serviceȥƥ/feignService/**·ɵfeign-serviceȥ\n\n\n\n    zuul:\n      routes: #·\n        user-service:\n          path: /userService/**\n        feign-service:\n          path: /feignService/**\n\n\n\n- http://localhost:8801/userService/user/1open in new windowԷ·ɵuser-serviceˣ\n- http://localhost:8801/feignService/user/1open in new windowԷ·ɵfeign-serviceˡ\n\n# Ĭ·ɹ\n\n- ZuulEurekaʹãʵ·ɵԶãԶõ·ԷΪƥ·൱ã\n\n\n\n    zuul:\n      routes: #·\n        user-service:\n          path: /user-service/**\n        feign-service:\n          path: /feign-service/**\n\n\n\n- http://localhost:8801/user-service/user/1open in new windowͬ·ɵuser-serviceˣ\n- http://localhost:8801/feign-service/user/1open in new windowͬ·ɵfeign-serviceˡ\n- ʹĬϵ·ɹ򣬿Ĭ·ã\n\n\n\n    zuul:\n      ignored-services: user-service,feign-service #رĬ·\n\n\n\n\n\n# ؾ⹦\n\nεhttp://localhost:8801/user-service/user/1open in new windowвԣԷ82018202user-serviceӡϢ\n\n\n\n    2019-10-05 10:31:58.738  INFO 11520 --- [nio-8202-exec-5] c.macro.cloud.controller.UserController  : idȡûϢûΪmacro\n    2019-10-05 10:32:00.356  INFO 11520 --- [nio-8202-exec-6] c.macro.cloud.controller.UserController  : idȡûϢûΪmacro\n\n\n\n# ÷ǰ׺\n\nǿͨ·ǰ׺˴/proxyǰ׺Ҫhttp://localhost:8801/proxy/user-service/user/1open in new windowܷʵuser-serviceеĽӿڡ\n\n\n\n    zuul:\n      prefix: /proxy #·ǰ׺\n\n\n\n\n# Header˼ضHost\n\n- Zuul·ʱĬϻ˵һЩеͷϢÿԷֹ·ʱCookieAuthorizationĶʧ\n\n\n\n    zuul:\n      sensitive-headers: Cookie,Set-Cookie,Authorization #ùеͷϢΪվͲ\n\n\n\n\n- Zuul·ʱhostͷϢÿԽ\n\n\n\n    zuul:\n      add-host-header: true #Ϊtrueضǻhostͷ\n\n\n\n# 鿴·Ϣ\n\nǿͨSpringBoot Actuator鿴Zuulе·Ϣ\n\n- pom.xml\n\n\n\n    <dependency>\n        <groupId>org.springframework.boot</groupId>\n        spring-boot-starter-actuator\n    </dependency>\n\n\n\n\n\n\n- ޸application.yamlļ鿴·ɵĶ˵㣺\n\n\n\n    management:\n      endpoints:\n        web:\n          exposure:\n            include: 'routes'\n\n\n\n\n\n- ͨhttp://localhost:8801/actuator/routesopen in new window鿴·Ϣ\n\n\n\n- ͨhttp://localhost:8801/actuator/routes/detailsopen in new window鿴ϸ·Ϣ\n\n\n\n# \n\n·ZuulĹܣ·ɹܸⲿתķʵȥʵͳһڵĻ˹ܸ̽жĴУ˼ۺϵĻ\n\n# \n\nZuul¼ֵ͵Ĺ͡\n\n- pre·ɵĿǰִУȨУ顢ӡ־ȹܣ\n- routing·ɵĿʱִУʹApache HttpClientNetflix RibbonͷԭʼHTTPĵط\n- post·ɵĿִУĿӦͷϢռͳݵȹܣ\n- error׶ηʱִС\n\n# \n\nͼһHTTP󵽴APIغڸֲͬ͵ĹתĹ̡\n\n\n\n# Զ\n\nԶһʾ¹á\n\n# PreLogFilter̳ZuulFilter\n\nһǰù·ɵĿǰӡ־\n\n\n\n    /**\n     * Created by macro on 2019/9/9.\n     */\n    @Component\n    public class PreLogFilter extends ZuulFilter {\n        private Logger LOGGER = LoggerFactory.getLogger(this.getClass());\n    \n        /**\n         * ͣpreroutingposterror֡\n         */\n        @Override\n        public String filterType() {\n            return \"pre\";\n        }\n    \n        /**\n         * ִ˳ֵԽСȼԽߡ\n         */\n        @Override\n        public int filterOrder() {\n            return 1;\n        }\n    \n        /**\n         * Ƿйˣtrueִйˡ\n         */\n        @Override\n        public boolean shouldFilter() {\n            return true;\n        }\n    \n        /**\n         * ԶĹ߼shouldFilter()trueʱִС\n         */\n        @Override\n        public Object run() throws ZuulException {\n            RequestContext requestContext = RequestContext.getCurrentContext();\n            HttpServletRequest request = requestContext.getRequest();\n            String host = request.getRemoteHost();\n            String method = request.getMethod();\n            String uri = request.getRequestURI();\n            LOGGER.info(\"Remote host:{},method:{},uri:{}\", host, method, uri);\n            return null;\n        }\n    }\n\n\n\n# ʾ\n\nӹǷhttp://localhost:8801/user-service/user/1open in new window£ӡ־\n\n\n\n    2019-10-05 15:13:10.232  INFO 11040 --- [nio-8801-exec-7] com.macro.cloud.filter.PreLogFilter      : Remote host:0:0:0:0:0:0:0:1,method:GET,uri:/user-service/user/1\n\n\n\n\n\n\n# Ĺ\n\n                      \t \tȼ \t                                  \n    ServletDetectionFilter \tpre  \t-3  \t⵱ǰͨDispatcherServletеĻZuulServletдġ\n    Servlet30WrapperFilter \tpre  \t-2  \tԭʼHttpServletRequestаװ             \n    FormBodyWrapperFilter  \tpre  \t-1  \tContent-TypeΪapplication/x-www-form-urlencodedmultipart/form-dataװFormBodyRequestWrapper\n    DebugFilter            \troute\t1   \tzuul.debug.requestǷӡdebug־  \n    PreDecorationFilter    \troute\t5   \tԵǰԤԱִк                     \n    RibbonRoutingFilter    \troute\t10  \tͨRibbonHystrixʵ󣬲зء  \n    SimpleHostRoutingFilter\troute\t100 \tֻrouteHostĽдֱʹHttpClientrouteHostӦַת\n    SendForwardFilter      \troute\t500 \tֻforward.toĽдбת      \n    SendErrorFilter        \tpost \t0   \tڲ쳣ʱĻдӦ          \n    SendResponseFilter     \tpost \t1000\tĵӦϢ֯ɹӦݡ\n\n# ù\n\n- ǿԶԹнõãøʽ£\n\n\n\n    zuul:\n      filterClassName:\n        filter:\n          disable: true \n\n\n\n- ǽPreLogFilterʾã\n\n\n\n    zuul:\n      PreLogFilter:\n        pre:\n          disable: true \n\n\n\n#RibbonHystrix֧\n\nZuulԶRibbonHystrixZuulиؾͷݴǿͨRibbonHystrixZuulеӦܡ\n\n- ʹHystrix·תʱHystrixCommandִгʱʱ䣺\n\n\n\n    hystrix:\n      command: #ڿHystrixCommandΪ\n        default:\n          execution:\n            isolation:\n              thread:\n                timeoutInMilliseconds: 1000 #HystrixCommandִеĳʱʱ䣬ִгʱз񽵼\n\n\n- ʹRibbon·תʱӼĳʱʱ䣺\n\n\n\n    ribbon: #ȫ\n      ConnectTimeout: 1000 #ӳʱʱ䣨룩\n      ReadTimeout: 3000 #ʱʱ䣨룩\n\n\n#\n\n\n\n    zuul:\n      routes: #·\n        user-service:\n          path: /userService/**\n        feign-service:\n          path: /feignService/**\n      ignored-services: user-service,feign-service #رĬ·\n      prefix: /proxy #·ǰ׺\n      sensitive-headers: Cookie,Set-Cookie,Authorization #ùеͷϢΪվͲ\n      add-host-header: true #Ϊtrueضǻhostͷ\n      retryable: true # رԻ\n      PreLogFilter:\n        pre:\n          disable: false #Ƿù\n\n\n#ʹõģ\n\n\n\n    springcloud-learning\n     eureka-server -- eurekaע\n     user-service -- ṩUserCRUDӿڵķ\n     feign-service -- feignòԷ\n     zuul-proxy -- zuulΪصĲԷ\n\n\n#ĿԴַ\n\nhttps://github.com/macrozheng/springcloud-learning\n# ο\nhttps://lijunyi.xyz/docs/SpringCloud/SpringCloud.html#_2-2-x-%E5%88%86%E6%94%AF\nhttps://mp.weixin.qq.com/s/2jeovmj77O9Ux96v3A0NtA\nhttps://juejin.cn/post/6931922457741770760\nhttps://github.com/D2C-Cai/herring\nhttp://c.biancheng.net/springcloud"
  },
  {
    "path": "docs/Spring全家桶/SpringCloud/SpringCloud概述.md",
    "content": "Spring Cloud һ Spring Boot ʵֵ΢ܡSpring Cloud Դ Spring Ҫ Pivotal  Netflix ˾ṩά\n\n΢ĻУ˾̷΢ܹУԲֵͬĸĽͿԴܡ\n\n*   ****ͰͿԴ Dubbo ͵չ DubboXNetflix  Eureka Լ Apache  Consul ȡ\n*   **ֲʽù**ٶȵ DisconfNetflix  Archaius360  QConfЯ̵ Apollo Լ Spring Cloud  Config ȡ\n*   **** Elastic-JobLinkedIn  Azkaban Լ Spring Cloud  Task ȡ\n*   **** HydraSpring Cloud  Sleuth Լ Twitter  Zipkin ȡ\n*   ****\n\nЩ΢ܻ 2 ص㣺\n\n*   ͬһ΢⣬˾Ľͬ\n*   һ΢ֻܻܽ΢еĳһĳ⣬Ϊ\n\n£һ΢ֲʽ΢ϵͳҪЩĽѡʹǲòľǰڵĵСԼʵϡ\n\nSpring Cloud Ϊֲʽ΢ϵͳġȫͰĳһżһϵ΢ܵ򼯺ϡϳġ֤΢ͨ Spring Boot ˼ٷװεиӵúʵԭΪԱṩһ׼׶ײάķֲʽϵͳ߰\n\nSpring Cloud а spring-cloud-configspring-cloud-bus Ƚ 20 Ŀṩ˷ء·ɡؾ⡢·ظ١ֲʽϢСùĽ\n\nSpring Cloud һõĿܣһ΢淶 2 ʵ֣\n\n*   һʵ֣Spring Cloud Netflix\n*   ڶʵ֣Spring Cloud Alibaba\n\nǽܵ Spring Cloud ָ Spring Cloud ĵһʵ֡\n\n## Spring Cloud \n\nSpring Cloud  Spring Cloud GatewaySpring Cloud ConfigSpring Cloud Bus Ƚ 20 Щṩ˷ء·ɡؾ⡢۶ظ١ֲʽϢСùĽ\n\nSpring Cloud ĳ±ʾ\n\n| Spring Cloud             |                                                          |\n| ---------------------------- | ------------------------------------------------------------ |\n| Spring Cloud Netflix Eureka  | Spring Cloud Netflix еķעġע뷢ֻƵʵ֡ |\n| Spring Cloud Netflix Ribbon  | Spring Cloud  Netflix еķúͿͻ˸ؾ     |\n| Spring Cloud Netflix Hystrix | ˳ơ硱Spring Cloud Netflix ݴΪгֵӳٺ͹ṩǿݴ |\n| Spring Cloud Netflix Feign   |  Ribbon  Hystrix ʽ                |\n| Spring Cloud Netflix Zuul    | Spring Cloud Netflix еṩ·ɡʹ˵ȹܡ |\n| Spring Cloud Gateway         | һ Spring 5.0Spring Boot 2.0  Project Reactor ȼؿܣʹ Filter ķʽṩصĻܣ簲ȫ/ָȡ |\n| Spring Cloud Config          | Spring Cloud ùߣ֧ʹ Git 洢ݣʵӦõⲿ洢֧ڿͻ˶ýˢ¡ܡܵȲ |\n| Spring Cloud Bus             | Spring Cloud ¼ϢߣҪڼȺд¼״̬仯ԴĴ綯̬ˢá |\n| Spring Cloud Stream          | Spring Cloud Ϣм Apache Kafka  RabbitMQ ϢмͨΪм㣬ʵӦóϢм֮ĸ롣ͨӦó¶ͳһ Channel ͨʹӦóҪٿǸֲͬϢмʵ֣ɵطͺͽϢ |\n| Spring Cloud Sleuth          | Spring Cloud ֲʽ·ܹ Twitter  Zipkin |\n\n> עNetflix һƵվǹϵĴģ΢Ľܳʵߣ΢̳Netflix ĿԴѾģֲʽ΢񻷾о˶ʵս֤ҿɿ\n\n## Spring Boot  Spring Cloud ϵ\n\nSpring Boot  Spring Cloud  Spring һԱ΢񿪷жʮҪĽɫ֮ȴҲϵ\n\n#### 1\\. Spring Boot  Spring Cloud ֹͬ\n\nSpring Boot һ Spring ĿٿܣܹѸٴ Web ̡΢񿪷УSpring Boot רעڿ١ؿ΢\n\nSpring Cloud ΢ܹµһվʽSpring Cloud רעȫ΢Э仰˵Spring Cloud ൱΢Ĵܼң Spring Boot һ΢Ϊṩù֡··ɡ΢¼ߡ߾ѡԼֲʽỰȷ\n\n#### 2\\. Spring Cloud ǻ Spring Boot ʵֵ\n\nSpring Cloud ǻ Spring Boot ʵֵġ Spring Boot ƣSpring Cloud ҲΪṩһϵ StarterЩ Starter  Spring Cloud ʹ Spring Boot ˼Ը΢ܽٷװĲЩ΢иӵúʵԭʹԱܹ١ʹ Spring Cloud һ׷ֲʽ΢ϵͳ\n\n#### 3\\. Spring Boot  Spring Cloud ͬ\n\nSpring Boot һĿܣ Spring Boot ١\n\nSpring Cloud һϵ΢ܼļ壬ÿҪһStarter POMҪһ Spring  Cloud Ҫ\n\n#### 4\\. Spring Cloud  Spring Boot \n\nSpring Boot Ҫ Spring CloudֱӴɶеĹ̻ģ顣\n\nSpring Cloud ǻ Spring Boot ʵֵģ̻ܶģ飬 Spring Boot С\n\n> ע⣺Ȼ Spring Boot ܹڿ΢񣬵߱Э΢ֻһ΢ٿܣ΢ܡ\n\n## Spring Cloud 汾\n\nSpring Cloud ĿЩĿǶݸº͵ģԶάԼķ汾š\n\nΪ˱ Spring Cloud İ汾Ŀİ汾ŻSpring Cloud ûвóְ汾ţͨ·ʽ汾Ϣ\n\n<pre>{version.name} .{version.number}</pre>\n\nSpring Cloud 汾Ϣ˵£\n\n*   **version.name**汾Ӣ׶صվվĸ˳򣨼 A  ZӦ Spring Cloud İ汾˳һ汾Ϊ Angelڶ汾Ϊ BrixtonӢȻ CamdenDalstonEdgwareFinchleyGreenwichHoxton ȡ\n*   **version.number**汾ţÿһ汾 Spring Cloud ڸݻ۵һش BUG ޸ʱͻᷢһservice releases汾 SRX 汾 X Ϊһ֣ Hoxton.SR8 ͱʾ Hoxton ĵ 8  Release 汾\n\n## Spring Cloud 汾ѡ\n\nʹ Spring Boot + Spring Cloud ΢񿪷ʱҪĿ Spring Boot İ汾 Spring Cloud 汾벻Ĵ\n\nSpring Boot  Spring Cloud İ汾Ӧϵ±ο[ ](https://spring.io/projects/spring-cloud)[Spring Cloud ](http://spring.io/projects/spring-cloud)\n\n| Spring Cloud        | Spring Boot                                    |\n| ------------------- | ---------------------------------------------- |\n| 2020.0.x Ilford | 2.4.x, 2.5.x  Spring Cloud 2020.0.3 ʼ |\n| Hoxton              | 2.2.x, 2.3.x  Spring Cloud SR5 ʼ      |\n| Greenwich           | 2.1.x                                          |\n| Finchley            | 2.0.x                                          |\n| Edgware             | 1.5.x                                          |\n| Dalston             | 1.5.x                                          |\n\n> ע⣺Spring Cloud ٷѾֹͣ DalstonEdgwareFinchley  Greenwich İ汾¡\n\nϱչʾİ汾Ӧϵ֮⣬ǻʹ [https://start.spring.io/actuator/info](https://start.spring.io/actuator/info)ȡ Spring Cloud  Spring Boot İ汾ӦϵJSON 棩\n\n\n\n\n\n\n\n\n\n\n````\n\n{\n   \n    \"bom-ranges\":{\n        \n        \"spring-cloud\":{\n            \"Hoxton.SR12\":\"Spring Boot >=2.2.0.RELEASE and <2.4.0.M1\",\n            \"2020.0.4\":\"Spring Boot >=2.4.0.M1 and <2.5.6-SNAPSHOT\",\n            \"2020.0.5-SNAPSHOT\":\"Spring Boot >=2.5.6-SNAPSHOT and <2.6.0-M1\",\n            \"2021.0.0-M1\":\"Spring Boot >=2.6.0.M1 and <2.6.0-SNAPSHOT\",\n            \"2021.0.0-SNAPSHOT\":\"Spring Boot >=2.6.0-SNAPSHOT\"\n        },\n        \n    },\n \n}\n\n````\n# ο\nhttps://lijunyi.xyz/docs/SpringCloud/SpringCloud.html#_2-2-x-%E5%88%86%E6%94%AF\nhttps://mp.weixin.qq.com/s/2jeovmj77O9Ux96v3A0NtA\nhttps://juejin.cn/post/6931922457741770760\nhttps://github.com/D2C-Cai/herring\nhttp://c.biancheng.net/springcloud"
  },
  {
    "path": "docs/Spring全家桶/SpringCloudAlibaba/SpringCloudAlibabaNacos.md",
    "content": "Nacos ӢȫΪ Dynamic Naming and Configuration ServiceһɰͰŶʹ Java ԿĿԴĿ\n\nNacos һڰԭӦõĶ̬֡úͷƽ̨ο [Nacos ](https://nacos.io/zh-cn/index.html)\n\nNacos  3 ɣ\n\n| ɲ | ȫ              |                                                          |\n| -------- | ----------------- | ------------------------------------------------------------ |\n| Na       | naming/nameServer | עģ Spring Cloud Eureka Ĺơ          |\n| co       | configuration     | ģ Spring Cloud Config+Spring Cloud Bus Ĺơ |\n| s        | service           | 񣬱ʾ Nacos ʵֵķעĺĶԷΪĵġ |\n\nǿԽ Nacos ɷעĺĵ壬滻 [Eureka](http://c.biancheng.net/springcloud/eureka.html) Ϊעģʵַע뷢֣滻 [Spring Cloud Config](http://c.biancheng.net/springcloud/config.html) ΪģʵõĶ̬ˢ¡\n\nNacos Ϊעľʮꡰ˫ʮһĺ忼飬мáȶɿ׿Խŵ㣬԰ûݡ׵ع͹΢Ӧá\n\nNacos ּ֧͡񡱵ķ֡ú͹\n\n*   [Kubernetes Service](https://kubernetes.io/docs/concepts/services-networking/service/)\n*   [gRPC ](https://grpc.io/docs/what-is-grpc/core-concepts#service-definition)& [Dubbo RPC Service](https://dubbo.apache.org/zh/)\n*   Spring Cloud RESTful Service\n\n## Nacos \n\nNacos ṩһϵмõԣܹǿٵʵֶ̬֡õȹܡ\n\n#### \n\nNacos ֻ֧ DNS  RPC ķ֡ṩʹԭ SDKOpenAPI һ Agent TODO  Nacos ע󣬷߿ Nacos ͨ DNS TODO  HTTP&API ҡַ\n\n#### 񽡿\n\nNacos ṩԷʵʱ飬ֹܹ͵ʵϡNacos ṩһǱ̣ܹǸݽ״̬ĿԼ\n\n#### ̬÷\n\n̬÷ĻⲿͶ̬ķʽлӦúͷá\n\n̬ñʱ²ӦúͷҪùøӸЧݡ\n\nĻʵ״̬ø򵥣÷赯չøס\n\nNacos ṩһõ UI ǹзӦõáNacos ṩð汾١˿ȸһعԼͻø״̬ڵһϵп伴õùԣǸȫйñͽñķա\n\n#### ̬ DNS \n\nNacos ṩ˶̬ DNS ܹǸ׵ʵָؾ⡢Լļ DNS \n\nNacos ṩһЩ򵥵 DNS APIs TODO԰ǹĹͿõ IP:PORT б\n\n#### Ԫݹ\n\nNacos Ǵ΢ƽ̨ӽǹĵзԪݣڡľ̬Ľ״̬·ɼȫԡ SLA Լ metrics ͳݡ\n\n## Nacos \n\n Eureka ƣNacos Ҳ CSClient/Serverͻ/ܹ±\n\n\n|                                                          |                                                          |                                                          |\n| ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |\n| Nacos Server                                                 | Nacos ˣ Eureka Server ͬNacos Server ɰͰŶʹ Java Աд Nacos Server صַûûֻҪֱزмɡ | Nacos Server Ϊעģ Nacos Client ʵַע뷢֡ |\n| Nacos Server Ϊģ Nacos Client ڲ£ʵõĶ̬ˢ¡ |                                                              |                                                              |\n| Nacos Client                                                 | Nacos ͻˣָͨ΢ܹеĸûԼʹöԱд | Nacos Client ͨ spring-cloud-starter-alibaba-nacos-discoveryڷעģNacos Serverʵַע뷢֡ |\n| Nacos Client ͨ spring-cloud-starter-alibaba-nacos-configģNacos ServerʵõĶ̬ˢ¡ |                                                              |                                                              |\n\n## Nacos ע\n\nNacos ΪעĿʵַע뷢֣ͼ\n\n![Nacos ע뷢](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1022563360-0.png)\nͼ1Nacos ע뷢\n\nͼ 1 й漰 3 ɫ\n\n*   עģRegister Serviceһ Nacos ServerΪṩߺͷṩעͷֹܡ\n*   ṩߣProvider Serviceһ Nacos ClientڶԼṩķעᵽעģԹ߷ֺ͵á\n*   ߣConsumer Serviceһ Nacos ClientѷԴӷעĻȡбķ\n\nNacos ʵַע뷢ֵ£\n\n1.   Nacos ٷṩҳУ Nacos Server С\n2.  ṩ Nacos Client ʱѷԷspring.application.nameķʽעᵽעģNacos Server\n3.   Nacos Client ʱҲὫԼķעᵽעģ\n4.  עͬʱӷעĻȡһݷעбϢбаעᵽעϵķϢṩߺϢ\n5.  ڻȡ˷ṩߵϢ󣬷ͨ HTTP ϢмԶ̵÷ṩṩķ\n\n#### װ Nacos Server\n\n Nacos 2.0.3 Ϊʾΰװ Nacos Server¡\n\n1\\. ʹ [Nacos Server ҳ](https://github.com/alibaba/nacos/releases/tag/2.0.3)ҳ· nacos-server-2.0.3.zipͼ\n\n![Nacos ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1022562c6-1.png)\nͼ2Nacos Server \n\n2\\. ɺ󣬽ѹ nacos-server-2.0.3.zipĿ¼ṹ¡\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1022563922-2.png)\nͼ3Nacos Server Ŀ¼ṹ\n\nNacos Server ¸Ŀ¼˵£\n\n*   binڴ Nacos Ŀִ\n*   confڴ Nacos ļ\n*   targetڴ Nacos Ӧõ jar \n\n3\\. дڣת Nacos Server װĿ¼ bin £ִԵģʽ Nacos Server\n\nstartup.cmd -m standalone\n\n4\\. Nacos Server ־¡\n\n\"nacos is starting with standalone\"\n\n\n         ,--.\n       ,--.'|\n\n   ,--,:  : |                                           Nacos 2.0.3\n,`--.'`|  ' :                       ,---.               Running in stand alone mode, All function modules\n|   :  :  | |                      '   ,'\\   .--.--.    Port: 8848\n:   |   \\ | :  ,--.--.     ,---.  /   /   | /  /    '   Pid: 27512\n|   : '  '; | /       \\   /     \\.   ; ,. :|  :  /`./   Console: http://192.168.3.138:8848/nacos/index.html\n'   ' ;.    ;.--.  .-. | /    / ''   | |: :|  :  ;_\n|   | | \\   | \\__\\/: . ..    ' / '   | .; : \\  \\    `.      https://nacos.io\n'   : |  ; .' ,\" .--.; |'   ; :__|   :    |  `----.   \\\n|   | '`--'  /  /  ,.  |'   | '.'|\\   \\  /  /  /`--'  /\n'   : |     ;  :   .'   \\   :    : `----'  '--'.     /\n;   |.'     |  ,     .-./\\   \\  /            `--'---'\n'---'        `--`---'     `----'\n````\n2021-11-08 16:16:38,877 INFO Bean 'org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler@5ab9b447' of type [org.springframework.security.access.expression.method\n.DefaultMethodSecurityExpressionHandler] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)\n2021-11-08 16:16:38,884 INFO Bean 'methodSecurityMetadataSource' of type [org.springframework.security.access.method.DelegatingMethodSecurityMetadataSource] is not eligible for getting processed by al\nl BeanPostProcessors (for example: not eligible for auto-proxying)\n2021-11-08 16:16:40,001 INFO Tomcat initialized with port(s): 8848 (http)\n2021-11-08 16:16:40,713 INFO Root WebApplicationContext: initialization completed in 14868 ms\n2021-11-08 16:16:52,351 INFO Initializing ExecutorService 'applicationTaskExecutor'\n2021-11-08 16:16:52,560 INFO Adding welcome page: class path resource [static/index.html]\n2021-11-08 16:16:54,239 INFO Creating filter chain: Ant [pattern='/**'], []\n2021-11-08 16:16:54,344 INFO Creating filter chain: any request, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@7dd611c8, org.springframework.security.web.con\ntext.SecurityContextPersistenceFilter@5c7668ba, org.springframework.security.web.header.HeaderWriterFilter@fb713e7, org.springframework.security.web.csrf.CsrfFilter@6ec7bce0, org.springframework.secur\nity.web.authentication.logout.LogoutFilter@7d9ba6c, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@158f4cfe, org.springframework.security.web.servletapi.SecurityContextHolderAwa\nreRequestFilter@6c6333cd, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@5d425813, org.springframework.security.web.session.SessionManagementFilter@13741d5a, org.springf\nramework.security.web.access.ExceptionTranslationFilter@3727f0ee]\n2021-11-08 16:16:54,948 INFO Initializing ExecutorService 'taskScheduler'\n2021-11-08 16:16:54,977 INFO Exposing 16 endpoint(s) beneath base path '/actuator'\n2021-11-08 16:16:55,309 INFO Tomcat started on port(s): 8848 (http) with context path '/nacos'\n2021-11-08 16:16:55,319 INFO Nacos started successfully in stand alone mode. use embedded storage\n````\n5\\. ʹʡhttp://localhost:8848/nacosת Nacos Server ½ҳ棬ͼ\n\n![Nacos ½ҳ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1022562045-3.png)\nͼ4Nacos Server ½ҳ\n\n6\\. ڵ½ҳ¼루Ĭ϶ nacosύťת Nacos Server ̨ҳͼ\n\n![Nacos Server ҳ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/10225633C-4.png)\nͼ5Nacos Server ̨\n\nԴˣǾ Nacos Server ءװй\n\n#### ṩ\n\nһṩߣ¡\n\n1. һΪ spring-cloud-alibaba-demo  Maven  ù̵ pom.xml ¡\n\n\n\n\n````\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <packaging>pom</packaging>\n    <parent>\n        <groupId>org.springframework.boot</groupId>\n        spring-boot-starter-parent\n        <version>2.5.6</version>\n        <relativePath/> <!-- lookup parent from repository -->\n    </parent>\n    <groupId>net.biancheng.c</groupId>\n    spring-cloud-alibaba-demo\n    <version>1.0-SNAPSHOT</version>\n\n\n    <properties>\n        <maven.compiler.source>8</maven.compiler.source>\n        <maven.compiler.target>8</maven.compiler.target>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <maven.compiler.source>1.8</maven.compiler.source>\n        <maven.compiler.target>1.8</maven.compiler.target>\n        <junit.version>4.12</junit.version>\n        <log4j.version>1.2.17</log4j.version>\n        <lombok.version>1.16.18</lombok.version>\n        <spring-cloud.version>2020.0.4</spring-cloud.version>\n    </properties>\n    <dependencyManagement>\n        <dependencies>\n            <!--Spring Cloud Alibaba İ汾Ϣ-->\n            <dependency>\n                <groupId>com.alibaba.cloud</groupId>\n                spring-cloud-alibaba-dependencies\n                <version>2021.1</version>\n                <type>pom</type>\n                <scope>import</scope>\n            </dependency>\n            <!--Spring Cloud İ汾Ϣ-->\n            <dependency>\n                <groupId>org.springframework.cloud</groupId>\n                spring-cloud-dependencies\n                <version>${spring-cloud.version}</version>\n                <type>pom</type>\n                <scope>import</scope>\n            </dependency>\n        </dependencies>\n    </dependencyManagement>\n\n</project>\n````\n\n\n\n\n> ڸù̵ pom.xml Уͨ dependencyManagement  Spring Cloud Alibaba İ汾Ϣйùµĸģ Spring Cloud Alibaba ĸʱͲҪָ汾ˡ\n\n2\\.  spring-cloud-alibaba-demo £һΪ spring-cloud-alibaba-provider-8001  Spring Boot ģ飬 pom.xml ¡\n\n\n\n\n````\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <!---->\n    <parent>\n        <groupId>net.biancheng.c</groupId>\n        <version>1.0-SNAPSHOT</version>\n        spring-cloud-alibaba-demo\n    </parent>\n\n\n    <groupId>net.biancheng.c</groupId>\n    spring-cloud-alibaba-provider-8001\n    <version>0.0.1-SNAPSHOT</version>\n    <name>spring-cloud-alibaba-provider-8001</name>\n    <description>Demo project for Spring Boot</description>\n    <properties>\n        <java.version>1.8</java.version>\n    </properties>\n    <dependencies>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            spring-boot-starter-web\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            spring-boot-devtools\n            <scope>runtime</scope>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>org.projectlombok</groupId>\n            lombok\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            spring-boot-starter-test\n            <scope>test</scope>\n        </dependency>\n        <!--Spring Cloud Alibaba Nacos discovery -->\n        <dependency>\n            <groupId>com.alibaba.cloud</groupId>\n            spring-cloud-starter-alibaba-nacos-discovery\n        </dependency>\n    </dependencies>\n    \n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                spring-boot-maven-plugin\n                <configuration>\n                    <excludes>\n                        <exclude>\n                            <groupId>org.projectlombok</groupId>\n                            lombok\n                        </exclude>\n                    </excludes>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n\n</project>\n\n````\n\n\n\n3\\.  spring-cloud-alibaba-provider-8001 ļ application.properties ã¡\n````\n#˿ں\nserver.port=8001\n#\nspring.application.name=spring-cloud-alibaba-provider\n#Nacos Server ĵַ\nspring.cloud.nacos.discovery.server-addr=127.0.0.1:8848\nmanagement.endpoints.web.exposure.include=*\n````\n\n4\\.  net.biacheng.c.controller £һΪ DeptController  Controller ࣬¡\n\n\n\n\n````\npackage net.biancheng.c.controller;\n\n\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.PathVariable;\nimport org.springframework.web.bind.annotation.RestController;\n\n@RestController\n@Slf4j\npublic class DeptController {\n    @Value(\"${server.port}\")\n    private String serverPort;\n\n    @GetMapping(value = \"/dept/nacos/{id}\")\n    public String getPayment(@PathVariable(\"id\") Integer id) {\n        return \"<h2>cʳɹ</h2>spring-cloud-alibaba-provider<br /> ˿ںţ \" + serverPort + \"<br /> Ĳ\" + id;\n    }\n\n}\n\n````\n\n\n\n5\\.  spring-cloud-alibaba-provider-8001 ϣʹ @EnableDiscoveryClient ע⿪ Nacos ֹܣ¡\n\n\n\n\n\npackage net.biancheng.c;\n\n````\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.cloud.client.discovery.EnableDiscoveryClient;\n\n@SpringBootApplication\n@EnableDiscoveryClient //ֹ\npublic class SpringCloudAlibabaProvider8001Application {\n\n    public static void main(String[] args) {\n        SpringApplication.run(SpringCloudAlibabaProvider8001Application.class, args);\n    }\n\n}\n````\n\n\n\n\n6\\.  spring-cloud-alibaba-provider-8001ʹʡhttp://localhost:8001/dept/nacos/1ͼ\n\n![ʷṩߵķ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/10225C033-5.png)\nͼ6ṩ\n\n7\\. ʹʡhttp://localhost:8848/nacos鿴µġбͼ\n\n[![Nacos б](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/10225Ab5-6.png)](http://new-local.weixueyuan.net/uploads/allimg/211108/6-21110QJ12b02.png)\nͼ7עб\n\nͼ 7 ԿǴķṩ spring-cloud-alibaba-provider-8001 ṩķѾעᵽ Nacos Server ˡ\n\n#### \n\n棬Ǿһ spring-cloud-alibaba-provider-8001 ṩķ񣬲¡\n\n1\\.  spring-cloud-alibaba-demo £һΪ spring-cloud-alibaba-consumer-nacos-8801  Spring Boot ģ飬 pom.xml \n\n\n\n````\n\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <parent>\n        <groupId>net.biancheng.c</groupId>\n        <version>1.0-SNAPSHOT</version>\n        spring-cloud-alibaba-demo\n    </parent>\n    <groupId>net.biancheng.c</groupId>\n    spring-cloud-alibaba-consumer-nacos-8081\n    <version>0.0.1-SNAPSHOT</version>\n    <name>spring-cloud-alibaba-consumer-nacos-8081</name>\n    <description>Demo project for Spring Boot</description>\n    <properties>\n        <java.version>1.8</java.version>\n    </properties>\n    <dependencies>\n        <!--SpringCloud ailibaba nacos discovery-->\n        <dependency>\n            <groupId>com.alibaba.cloud</groupId>\n            spring-cloud-starter-alibaba-nacos-discovery\n        </dependency>\n        <!-- Netflix Ribbon ͣά׶Σ°汾 Nacos discovery ѾƳ Ribbon ʱҪ loadbalancer  -->\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            spring-cloud-loadbalancer\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            spring-boot-starter-web\n        </dependency>\n\n\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            spring-boot-devtools\n            <scope>runtime</scope>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>org.projectlombok</groupId>\n            lombok\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            spring-boot-starter-test\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n    \n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                spring-boot-maven-plugin\n                <configuration>\n                    <excludes>\n                        <exclude>\n                            <groupId>org.projectlombok</groupId>\n                            lombok\n                        </exclude>\n                    </excludes>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n\n</project>\n\n````\n\n\n\n\n> ע⣺ Netflix Ribbon Ѿͣά״̬Nacos Discovery Ѿͣ˶ Ribbon ֧֣Ҫڷߵ pom.xml  spring-cloud-loadbalancer ܵ÷ṩṩķ\n\n2\\.  spring-cloud-alibaba-consumer-nacos-8801 ļ application.yml Уá\n\n\n````\n\n\nserver:\n  port: 8801  #˿ں\nspring:\n  application:\n    name: spring-cloud-alibaba-consumer #\n  cloud:\n    nacos:\n      discovery:\n        server-addr: localhost:8848  #Nacos server ĵַ\n````\n\n#ϢĬãԶãĿǲ Controller Ӳṩߵķ\n````\nservice-url:\n  nacos-user-service: http://spring-cloud-alibaba-provider #ṩߵķ\n````\n\n\n\n\n3\\.  spring-cloud-alibaba-consumer-nacos-8801 ϣʹ @EnableDiscoveryClient ע⿪ֹܣ¡\n\n\n\n\n````\npackage net.biancheng.c;\n\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.cloud.client.discovery.EnableDiscoveryClient;\n\n@SpringBootApplication\n@EnableDiscoveryClient // ע뷢ֹ\npublic class SpringCloudAlibabaConsumerNacos8801Application {\n\n    public static void main(String[] args) {\n        SpringApplication.run(SpringCloudAlibabaConsumerNacos8081Application.class, args);\n    }\n\n}\n````\n\n\n\n\n4\\.  net.biancheng.c.config ´һΪ ApplicationContextBean ࣬ʹ @LoadBalanced ע Ribbon мɿؾ⹦ܣ¡\n\n\n\n\n````\npackage net.biancheng.c.config;\n\n\nimport org.springframework.cloud.client.loadbalancer.LoadBalanced;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.web.client.RestTemplate;\n\n@Configuration\npublic class ApplicationContextBean {\n\n    @Bean\n    @LoadBalanced // Ribbon ɣؾ⹦\n    public RestTemplate getRestTemplate() {\n        return new RestTemplate();\n    }\n\n}\n\n````\n\n\n\n5\\.  net.biancheng.c.controller £һΪ DeptController_Consumer  Controller ࣬¡\n\n\n\n\n````\npackage net.biancheng.c.controller;\n\n\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.PathVariable;\nimport org.springframework.web.bind.annotation.RestController;\nimport org.springframework.web.client.RestTemplate;\n\nimport javax.annotation.Resource;\n\n@RestController\n@Slf4j\npublic class DeptController_Consumer {\n\n    @Resource\n    private RestTemplate restTemplate;\n    \n    @Value(\"${service-url.nacos-user-service}\")\n    private String serverURL; //ṩߵķ\n    \n    @GetMapping(\"/consumer/dept/nacos/{id}\")\n    public String paymentInfo(@PathVariable(\"id\") Long id) {\n        return restTemplate.getForObject(serverURL + \"/dept/nacos/\" + id, String.class);\n    }\n\n}\n````\n\n\n\n\n6\\.  spring-cloud-alibaba-consumer-nacos-8801鿴 Nacos Server ķбͼ\n\n[![ Nacos Server ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/10225621N-7.png)](http://new-local.weixueyuan.net/uploads/allimg/211109/6-21110Z91435H1.png)\nͼ8\n\n7\\. ʹʡhttp://localhost:8801/consumer/dept/nacos/1ͼ\n\n![Nacos ѷ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/10225B156-8.png)\nͼ9ߵ÷\n\n## Nacos \n\nNacos Server Ϊģ Spring Cloud ӦõⲿýͳһؼлֻҪӦõ POM ļ spring-cloud-starter-alibaba-nacos-config ʵõĻȡ붯̬ˢ¡\n\nùĽǶȿNacos ˵ Spring Cloud Config Ⱥ Nacos ʹø򵥣Ҳ١\n\nͨһʵʾ Nacos ʵõͳһͶ̬ˢµġ\n\n1\\.  spring-cloud-alibaba-demo £һΪ spring-cloud-alibaba-config-client-3377  Spring Boot ģ飬 pom.xml \n\n\n\n````\n\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <parent>\n        <groupId>net.biancheng.c</groupId>\n        <version>1.0-SNAPSHOT</version>\n        spring-cloud-alibaba-demo\n    </parent>\n    <groupId>net.biancheng.c</groupId>\n    spring-cloud-alibaba-config-client-3377\n    <version>0.0.1-SNAPSHOT</version>\n    <name>spring-cloud-alibaba-nacos-config-client-3377</name>\n    <description>Demo project for Spring Boot</description>\n    <properties>\n        <java.version>1.8</java.version>\n    </properties>\n    <dependencies>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            spring-boot-starter-web\n        </dependency>\n        <!--SpringCloud2020Ժİ汾Ĭϲ bootstrap ãҪpomʽ룺-->\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            spring-cloud-starter-bootstrap\n        </dependency>\n\n\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            spring-boot-devtools\n            <scope>runtime</scope>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>org.projectlombok</groupId>\n            lombok\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            spring-boot-starter-test\n            <scope>test</scope>\n        </dependency>\n        <!--Spring Cloud Alibaba Config -->\n        <dependency>\n            <groupId>com.alibaba.cloud</groupId>\n            spring-cloud-starter-alibaba-nacos-config\n        </dependency>\n        <!--SpringCloud ailibaba nacos ע뷢ģ -->\n        <dependency>\n            <groupId>com.alibaba.cloud</groupId>\n            spring-cloud-starter-alibaba-nacos-discovery\n        </dependency>\n        <!--Spring Boot ģ-->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            spring-boot-starter-actuator\n        </dependency>\n    </dependencies>\n    \n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                spring-boot-maven-plugin\n                <configuration>\n                    <excludes>\n                        <exclude>\n                            <groupId>org.projectlombok</groupId>\n                            lombok\n                        </exclude>\n                    </excludes>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n\n</project>\n\n````\n\n\n\n> ע⣺ʹõ Spring Cloud 2020 汾Ĭϲ bootstrapҪӦʱ bootstrap ã bootstrap.yml  bootstrap.propertiesҪ pom.xml ʽ spring-cloud-starter-bootstrap \n\n2\\.  spring-cloud-alibaba-config-client-3377 · /resources Ŀ¼£һ bootstrap.yml¡\n\n\n\n\n````\nserver:\n  port: 3377 #˿ں\nspring:\n  application:\n    name: config-client #\n  cloud:\n    nacos:\n      discovery:\n        server-addr: 127.0.0.1:8848 #Nacosעĵַ\n\n\n      config:\n        server-addr: 127.0.0.1:8848 #NacosΪĵַ\n        file-extension: yaml #ָyamlʽ\n\n````\n\n\n\n3\\.  spring-cloud-alibaba-config-client-3377 · /resources Ŀ¼£һ application.yml¡\n\n\n\n\n````\nspring:\n  profiles:\n    active: dev # dev \n\n````\n\n\n\n\n4\\.  net.biancheng.c.controller £һΪ ConfigClientController  Controller ࣬ڸʹ @RefreshScope עʵõԶ£¡\n\n\n\n\n````\npackage net.biancheng.c.controller;\n\n\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.cloud.context.config.annotation.RefreshScope;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\n@RestController\n@RefreshScope\npublic class ConfigClientController {\n\n    @Value(\"${config.info}\")\n    private String ConfigInfo;\n    \n    @GetMapping(\"/config/info\")\n    public String getConfigInfo(){\n        return ConfigInfo;\n    }\n\n}\n````\n\n\n\n\n5\\.  spring-cloud-alibaba-config-client-3377 ϣʹ @EnableDiscoveryClient ע⿪ֹܣ¡\n\n\n\n\n````\npackage net.biancheng.c;\n\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.cloud.client.discovery.EnableDiscoveryClient;\n\n@SpringBootApplication\n@EnableDiscoveryClient\npublic class SpringCloudAlibabaNacosConfigClient3377Application {\n\n    public static void main(String[] args) {\n        SpringApplication.run(SpringCloudAlibabaNacosConfigClient3377Application.class, args);\n    }\n\n}\n````\n\n\n\n\n6\\.  Nacos Server Nacos Server ̨ġùµġбУ+ť½á\n````\nData ID:        config-client-dev.yaml\n\n\nGroup  :        DEFAULT_GROUP\n\nøʽ:        YAML\n\n:      config:\n                  info: c.biancheng.net\n````\n Nacos Server Уõ dataId Data IDʽ£\n````\n${prefix}-${spring.profiles.active}.${file-extension}\n````\ndataId ʽи˵£\n\n*   ${prefix}ĬȡֵΪ΢ķļ spring.application.name ֵǿļͨ spring.cloud.nacos.config.prefix ָ\n*   ${spring.profiles.active}ʾǰӦ Profile devtestprod ȡûָ Profile ʱӦӷҲڣ dataId ĸʽ ${prefix}.${file-extension}\n*   ${file-extension}ʾݵݸʽǿļͨ spring.cloud.nacos.config.file-extension ã properties  yaml\n\n7\\.  spring-cloud-alibaba-config-client-3377ʹʡhttp://localhost:3377/config/infoͼ\n\n![Nacos Config](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/10225B3C-9.png)\nͼ10Nacos Config\n\n8\\.  Nacos Server У config-client-dev.yaml е޸ĳݡ\n````\nconfig:\n    info: this is c.biancheng.net\n````\n\n9\\. ڲ spring-cloud-alibaba-config-client-3377 £ʹٴηʡhttp://localhost:3377/config/infoͼ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1022563A7-10.png)\nͼ11Nacos Cofig\n\n## Nacos Server Ⱥ\n\nʵʵĿУһ΢ϵͳʮʮٸ΢ɡ Щȫעᵽͬһ̨ Nacos Serverͼпܵ Nacos Server Ϊظյ΢ϵͳֱ̱ӵİ취ʹ Nacos Server Ⱥ\n\nNacos Server ļȺһʮԵŵ㣬ǾǿԱϵͳĸ߿ԡڼȺУֻҪе Nacos Server ֹͣNacos Client ͻԴӼȺ Nacos Server ϻȡϢãᵼϵͳ̱ Nacos Server Ⱥĸ߿ԡ\n\nͼչʾ Nacos Server ȺĻܹ\n\n![Nacos Server Ⱥܹ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1022563543-11.png)\nͼ12Nacos Server Ⱥܹ\n\n Windows ϵͳΪʾβ Nacos Server Ⱥ\n\n1\\.  MySQL У½һΪ nacos_config ݿʵڸݿִ SQL 䡣\n````\n/******************************************/\n/*   ݿȫ = nacos_config   */\n/*    = config_info   */\n/******************************************/\nCREATE TABLE `config_info` (\n  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',\n  `data_id` varchar(255) NOT NULL COMMENT 'data_id',\n  `group_id` varchar(255) DEFAULT NULL,\n  `content` longtext NOT NULL COMMENT 'content',\n  `md5` varchar(32) DEFAULT NULL COMMENT 'md5',\n  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'ʱ',\n  `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '޸ʱ',\n  `src_user` text COMMENT 'source user',\n  `src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',\n  `app_name` varchar(128) DEFAULT NULL,\n  `tenant_id` varchar(128) DEFAULT '' COMMENT '⻧ֶ',\n  `c_desc` varchar(256) DEFAULT NULL,\n  `c_use` varchar(64) DEFAULT NULL,\n  `effect` varchar(64) DEFAULT NULL,\n  `type` varchar(64) DEFAULT NULL,\n  `c_schema` text,\n  PRIMARY KEY (`id`),\n  UNIQUE KEY `uk_configinfo_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info';\n\n\n/******************************************/\n/*   ݿȫ = nacos_config   */\n/*    = config_info_aggr   */\n/******************************************/\nCREATE TABLE `config_info_aggr` (\n  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',\n  `data_id` varchar(255) NOT NULL COMMENT 'data_id',\n  `group_id` varchar(255) NOT NULL COMMENT 'group_id',\n  `datum_id` varchar(255) NOT NULL COMMENT 'datum_id',\n  `content` longtext NOT NULL COMMENT '',\n  `gmt_modified` datetime NOT NULL COMMENT '޸ʱ',\n  `app_name` varchar(128) DEFAULT NULL,\n  `tenant_id` varchar(128) DEFAULT '' COMMENT '⻧ֶ',\n  PRIMARY KEY (`id`),\n  UNIQUE KEY `uk_configinfoaggr_datagrouptenantdatum` (`data_id`,`group_id`,`tenant_id`,`datum_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='⻧ֶ';\n\n/******************************************/\n/*   ݿȫ = nacos_config   */\n/*    = config_info_beta   */\n/******************************************/\nCREATE TABLE `config_info_beta` (\n  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',\n  `data_id` varchar(255) NOT NULL COMMENT 'data_id',\n  `group_id` varchar(128) NOT NULL COMMENT 'group_id',\n  `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',\n  `content` longtext NOT NULL COMMENT 'content',\n  `beta_ips` varchar(1024) DEFAULT NULL COMMENT 'betaIps',\n  `md5` varchar(32) DEFAULT NULL COMMENT 'md5',\n  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'ʱ',\n  `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '޸ʱ',\n  `src_user` text COMMENT 'source user',\n  `src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',\n  `tenant_id` varchar(128) DEFAULT '' COMMENT '⻧ֶ',\n  PRIMARY KEY (`id`),\n  UNIQUE KEY `uk_configinfobeta_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_beta';\n\n/******************************************/\n/*   ݿȫ = nacos_config   */\n/*    = config_info_tag   */\n/******************************************/\nCREATE TABLE `config_info_tag` (\n  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',\n  `data_id` varchar(255) NOT NULL COMMENT 'data_id',\n  `group_id` varchar(128) NOT NULL COMMENT 'group_id',\n  `tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',\n  `tag_id` varchar(128) NOT NULL COMMENT 'tag_id',\n  `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',\n  `content` longtext NOT NULL COMMENT 'content',\n  `md5` varchar(32) DEFAULT NULL COMMENT 'md5',\n  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'ʱ',\n  `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '޸ʱ',\n  `src_user` text COMMENT 'source user',\n  `src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',\n  PRIMARY KEY (`id`),\n  UNIQUE KEY `uk_configinfotag_datagrouptenanttag` (`data_id`,`group_id`,`tenant_id`,`tag_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_tag';\n\n/******************************************/\n/*   ݿȫ = nacos_config   */\n/*    = config_tags_relation   */\n/******************************************/\nCREATE TABLE `config_tags_relation` (\n  `id` bigint(20) NOT NULL COMMENT 'id',\n  `tag_name` varchar(128) NOT NULL COMMENT 'tag_name',\n  `tag_type` varchar(64) DEFAULT NULL COMMENT 'tag_type',\n  `data_id` varchar(255) NOT NULL COMMENT 'data_id',\n  `group_id` varchar(128) NOT NULL COMMENT 'group_id',\n  `tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',\n  `nid` bigint(20) NOT NULL AUTO_INCREMENT,\n  PRIMARY KEY (`nid`),\n  UNIQUE KEY `uk_configtagrelation_configidtag` (`id`,`tag_name`,`tag_type`),\n  KEY `idx_tenant_id` (`tenant_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_tag_relation';\n\n/******************************************/\n/*   ݿȫ = nacos_config   */\n/*    = group_capacity   */\n/******************************************/\nCREATE TABLE `group_capacity` (\n  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',\n  `group_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Group IDַʾȺ',\n  `quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '0ʾʹĬֵ',\n  `usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'ʹ',\n  `max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'ôСޣλΪֽڣ0ʾʹĬֵ',\n  `max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'ۺ0ʾʹĬֵ',\n  `max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'ۺݵôСޣλΪֽڣ0ʾʹĬֵ',\n  `max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'ʷ',\n  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'ʱ',\n  `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '޸ʱ',\n  PRIMARY KEY (`id`),\n  UNIQUE KEY `uk_group_id` (`group_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='ȺGroupϢ';\n\n/******************************************/\n/*   ݿȫ = nacos_config   */\n/*    = his_config_info   */\n/******************************************/\nCREATE TABLE `his_config_info` (\n  `id` bigint(64) unsigned NOT NULL,\n  `nid` bigint(20) unsigned NOT NULL AUTO_INCREMENT,\n  `data_id` varchar(255) NOT NULL,\n  `group_id` varchar(128) NOT NULL,\n  `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',\n  `content` longtext NOT NULL,\n  `md5` varchar(32) DEFAULT NULL,\n  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n  `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n  `src_user` text,\n  `src_ip` varchar(50) DEFAULT NULL,\n  `op_type` char(10) DEFAULT NULL,\n  `tenant_id` varchar(128) DEFAULT '' COMMENT '⻧ֶ',\n  PRIMARY KEY (`nid`),\n  KEY `idx_gmt_create` (`gmt_create`),\n  KEY `idx_gmt_modified` (`gmt_modified`),\n  KEY `idx_did` (`data_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='⻧';\n\n/******************************************/\n/*   ݿȫ = nacos_config   */\n/*    = tenant_capacity   */\n/******************************************/\nCREATE TABLE `tenant_capacity` (\n  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',\n  `tenant_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Tenant ID',\n  `quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '0ʾʹĬֵ',\n  `usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'ʹ',\n  `max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'ôСޣλΪֽڣ0ʾʹĬֵ',\n  `max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'ۺ',\n  `max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'ۺݵôСޣλΪֽڣ0ʾʹĬֵ',\n  `max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'ʷ',\n  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'ʱ',\n  `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '޸ʱ',\n  PRIMARY KEY (`id`),\n  UNIQUE KEY `uk_tenant_id` (`tenant_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='⻧Ϣ';\n\nCREATE TABLE `tenant_info` (\n  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',\n  `kp` varchar(128) NOT NULL COMMENT 'kp',\n  `tenant_id` varchar(128) default '' COMMENT 'tenant_id',\n  `tenant_name` varchar(128) default '' COMMENT 'tenant_name',\n  `tenant_desc` varchar(256) DEFAULT NULL COMMENT 'tenant_desc',\n  `create_source` varchar(32) DEFAULT NULL COMMENT 'create_source',\n  `gmt_create` bigint(20) NOT NULL COMMENT 'ʱ',\n  `gmt_modified` bigint(20) NOT NULL COMMENT '޸ʱ',\n  PRIMARY KEY (`id`),\n  UNIQUE KEY `uk_tenant_info_kptenantid` (`kp`,`tenant_id`),\n  KEY `idx_tenant_id` (`tenant_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='tenant_info';\n\nCREATE TABLE `users` (\n`username` varchar(50) NOT NULL PRIMARY KEY,\n`password` varchar(500) NOT NULL,\n`enabled` boolean NOT NULL\n);\n\nCREATE TABLE `roles` (\n`username` varchar(50) NOT NULL,\n`role` varchar(50) NOT NULL,\nUNIQUE INDEX `idx_user_role` (`username` ASC, `role` ASC) USING BTREE\n);\n\nCREATE TABLE `permissions` (\n    `role` varchar(50) NOT NULL,\n    `resource` varchar(255) NOT NULL,\n    `action` varchar(8) NOT NULL,\n    UNIQUE INDEX `uk_role_permission` (`role`,`resource`,`action`) USING BTREE\n);\n\nINSERT INTO users (username, password, enabled) VALUES ('nacos', '$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', TRUE);\n\nINSERT INTO roles (username, role) VALUES ('nacos', 'ROLE_ADMIN');\n````\n2\\.  Nacos Server װĿ¼µ conf ļУ cluster.conf.example Ϊ cluster.confȻڸļݡ\n\n    192.168.3.138:3333\n    192.168.3.138:4444\n    192.168.3.138:5555\n\n\n˵£\n\n*   192.168.138 Ϊص IP ַòҪд localhost  127.0.0.1 Nacos Server Ⱥܻʧܣ\n*   δ Nacos Server ȺĶ˿ڷֱΪ333344445555\n\n3\\.  config Ŀ¼µ application.properties У server.port˿ںţ޸Ϊ 3333ڸļ MySQL ݿã޸¡\n````\nserver.port=3333\n################ MySQL ݿ##################\nspring.datasource.platform=mysql\n\n\ndb.num=1\ndb.url.0=jdbc:mysql://127.0.0.1:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=Asia/Shanghai\ndb.user=root\ndb.password=root\n````\n4\\.  Nacos Server Ŀ¼Ƶ̨ϣǵĶ˿ںŷֱ޸Ϊ 4444  5555\n\n5\\.  Nginx޸ Nginx  conf Ŀ¼µ nginx.conf ã¡\n````\n#user  nobody;\nworker_processes  1;\n\n\n#error_log  logs/error.log;\n\n#error_log  logs/error.log  notice;\n\n#error_log  logs/error.log  info;\n\n#pid        logs/nginx.pid;\n\nevents {\n    worker_connections  1024;\n}\n\nhttp {\n    include       mime.types;\n    default_type  application/octet-stream;\n    sendfile        on;\n    keepalive_timeout  65;\n    upstream cluster{\n        server 127.0.0.1:3333;\n        server 127.0.0.1:4444;\n        server 127.0.0.1:5555;\n    }\n\n    server {\n        listen       1111;\n        server_name  localhost;\n        #charset koi8-r;\n        #access_log  logs/host.access.log  main;\n        location / {\n            #root   html;\n            #index  index.html index.htm;\n            proxy_pass http://cluster;\n        }\n    }\n\n}\n````\n6\\. Ⱥе Nacos Serverʱʾ Nacos Server ɹ\n````\n\"nacos is starting with cluster\"\n\n\n         ,--.\n       ,--.'|\n\n   ,--,:  : |                                           Nacos 2.0.3\n,`--.'`|  ' :                       ,---.               Running in cluster mode, All function modules\n|   :  :  | |                      '   ,'\\   .--.--.    Port: ****\n:   |   \\ | :  ,--.--.     ,---.  /   /   | /  /    '   Pid: 21592\n|   : '  '; | /       \\   /     \\.   ; ,. :|  :  /`./   Console: http://192.168.3.138:3333/nacos/index.html\n'   ' ;.    ;.--.  .-. | /    / ''   | |: :|  :  ;_\n|   | | \\   | \\__\\/: . ..    ' / '   | .; : \\  \\    `.      https://nacos.io\n'   : |  ; .' ,\" .--.; |'   ; :__|   :    |  `----.   \\\n|   | '`--'  /  /  ,.  |'   | '.'|\\   \\  /  /  /`--'  /\n'   : |     ;  :   .'   \\   :    : `----'  '--'.     /\n;   |.'     |  ,     .-./\\   \\  /            `--'---'\n'---'        `--`---'     `----'\n\n2021-11-09 16:25:00,993 INFO The server IP list of Nacos is [192.168.3.138:3333, 192.168.3.138:4444, 192.168.3.138:5555]\n\n2021-11-09 16:27:07,318 INFO Nacos is starting...\n\n2021-11-09 16:27:08,325 INFO Nacos is starting...\n\n2021-11-09 16:27:09,340 INFO Nacos is starting...\n\n2021-11-09 16:27:10,343 INFO Nacos is starting...\n\n2021-11-09 16:27:10,742 INFO Nacos started successfully in cluster mode. use external storage\n````\n7\\. Ⱥе Nacos Server ɹ˫ Nignx װĿ¼µ nginx.exe Nginx\n\n![Nginx ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/10225BO0-12.png)\nͼ13Nginx ű\n\n8\\. ʹʡhttp://localhost:1111/nacos/ɹ Nacos Server Ŀ̨˵ Nacos Ⱥɹͼ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/10225C554-13.png)\nͼ14Nacos Ⱥ\n\n9\\.  spring-cloud-alibaba-demo ģļе Nacos Server ַͳһ޸Ϊlocalhost:1111 spring-cloud-alibaba-consumer-nacos-8801 Ϊļ application.yml ¡\n\n\n\n\n````\nserver:\n  port: 8801  #˿ں\nspring:\n  application:\n    name: spring-cloud-alibaba-consumer #\n  cloud:\n    nacos:\n      discovery:\n        #server-addr: localhost:8848  # Nacos Server ĵַ\n        server-addr: localhost:1111  #Ⱥ Nacos Server ĵַ\n````\n\n#ϢĬãԶãĿǲ Controller Ӳṩߵķ\n````\nservice-url:\n  nacos-user-service: http://spring-cloud-alibaba-provider #ṩߵķ\n````\n\n\n\n\n10\\.  spring-cloud-alibaba-consumer-nacos-8801ʹʡhttp://localhost:1111/nacos鿴µġбͼ\n\n[![Nacos Ⱥ 2](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/10225A913-14.png)](http://new-local.weixueyuan.net/uploads/allimg/211109/6-2111091H412N8.png)\nͼ15עᵽ Nacos Server Ⱥ\n\n# ο\nhttps://lijunyi.xyz/docs/SpringCloud/SpringCloud.html#_2-2-x-%E5%88%86%E6%94%AF\nhttps://mp.weixin.qq.com/s/2jeovmj77O9Ux96v3A0NtA\nhttps://juejin.cn/post/6931922457741770760\nhttps://github.com/D2C-Cai/herring\nhttp://c.biancheng.net/springcloud"
  },
  {
    "path": "docs/Spring全家桶/SpringCloudAlibaba/SpringCloudAlibabaRocketMQ.md",
    "content": "1. RocketMQ \n\nRocketMQ ǰͰͿԴķֲʽϢм֧Ϣ˳ϢϢʱϢϢݵȡмڱ׼ϢмĸGroupTopicQueueȡϵͳProducerConsumerBrokerNameServerȡ\n\nRocketMQ ص\n\n- ַ֧/ģPub/Sub͵Ե㣨P2PϢģͣ\n- һпɿȽȳFIFOϸ˳򴫵ݣRocketMQ Ա֤ϸϢ˳򣬶ActiveMQ ޷֤\n- ֧PullƣPushϢģʽPush ⣬߶ Listener ص PullȨӦãӦҪĵϢ Broker ȡϢһλü¼⣨¼ᵼϢظѣ\n- һаϢĶѻRocketMQ ṩڼϢĶѻⲻص㣬صǶѻڼϢȻдӳ٣\n- ֶ֧ϢЭ飬 JMSMQTT ȣ\n- ֲʽ߿õĲܹһϢ壻RocketMQ ԭֲַ֧ʽģActiveMQ ԭڵԣ\n- ṩ docker ڸԺƼȺ\n- ṩáָͼصȹܷḻ Dashboard\n\nBroker\n\nBroker ʵ RocketMQ 洢ϢתϢBroker  RocketMQ ϵͳиմ߷Ϣ洢ͬʱΪߵȡ׼Broker Ҳ洢ϢصԪݣ顢ѽƫƺͶϢȡ\n\nBroker Server  RocketMQ ҵģ˶Ҫģ飺\n\n- ·ģ飺 Broker ʵ壬 clients ˵\n- ͻ˹ͻ(Producer/Consumer)ά Consumer  Topic Ϣ\n- 洢ṩ򵥵 API ӿڴϢ洢Ӳ̺Ͳѯܡ\n- ߿÷񣺸߿÷ṩ Master Broker  Slave Broker ֮ͬܡ\n- Ϣ񣺸ض Message key Ͷݵ Broker ϢṩϢĿٲѯ\n\nNameServer\n\nNameServer һǳ򵥵 Topic ·עģɫ Dubbo е zookeeper֧ Broker Ķ̬ע뷢֡\n\nҪܣ\n\n- Broker NameServer  Broker ȺעϢұΪ·ϢĻݡȻṩƣ Broker Ƿ񻹴\n- ·Ϣ Producer  Consumer ṩȡ Broker бÿ NameServer  Broker Ⱥ·Ϣڿͻ˲ѯĶϢȻ Producer  Conumser ͨ NameServer Ϳ֪ Broker Ⱥ·ϢӶϢͶݺѡ\n\n2. ʹ Docker ٴ RocketMQ 4.4\n\nrocketmq Ҫ broker  nameserver ǵֿȽ鷳ｫʹ docker-compose⣬Ҫһ web ӻ̨Լ mq ״̬ԼϢʹ rocketmq-consoleͬóҲʹ docker װ\n\n1.  linux ѡ񲢽Ŀ¼\n\n   mkdir rocketmq-docker\n   ƴ\n\n1.  rocketmq-docker Ŀ¼һΪ broker.conf ļ£\n\n   # Ⱥƣڵ϶ö\n   brokerClusterName = DefaultCluster\n   # brokerƣmasterslaveʹͬƣǵӹϵ\n   brokerName = broker-a\n   # 0ʾMaster0ʾͬslave\n   brokerId = 0\n   # ʾϢɾĬ賿4\n   deleteWhen = 04\n   # ڴϱϢʱλСʱ\n   fileReservedTime = 48\n   # ֵSYNC_MASTERASYNC_MASTERSLAVEͬ첽ʾMasterSlave֮ͬݵĻƣ\n   brokerRole = ASYNC_MASTER\n   # ˢ̲ԣȡֵΪASYNC_FLUSHSYNC_FLUSHʾͬˢ̺첽ˢ̣SYNC_FLUSHϢд̺ŷسɹ״̬ASYNC_FLUSHҪ\n   flushDiskType = ASYNC_FLUSH\n   # brokerڵڷipַ\n   # brokerIP1 = 192.168.138.131\n   ƴ\n\nע⣺ brokerIP1 ãĬϻΪ docker ڲIPӲϡ\n\n1.  rocketmq-docker Ŀ¼һΪ rocketmq.yaml Ľűļ\n\n\n\nrocketmq.yaml :\n\n    version: '2'\n    services:\n      namesrv:\n        image: rocketmqinc/rocketmq\n        container_name: rmqnamesrv\n        ports:\n          - 9876:9876\n        volumes:\n          - /docker/rocketmq/data/namesrv/logs:/home/rocketmq/logs\n          - /docker/rocketmq/data/namesrv/store:/home/rocketmq/store\n        command: sh mqnamesrv\n      broker:\n        image: rocketmqinc/rocketmq\n        container_name: rmqbroker\n        ports:\n          - 10909:10909\n          - 10911:10911\n          - 10912:10912\n        volumes:\n          - /docker/rocketmq/data/broker/logs:/home/rocketmq/logs\n          - /docker/rocketmq/data/broker/store:/home/rocketmq/store\n          - /docker/rocketmq/conf/broker.conf:/opt/rocketmq-4.4.0/conf/broker.conf\n        command: sh mqbroker -n namesrv:9876 -c /opt/rocketmq-4.4.0/conf/broker.conf\n        depends_on:\n          - namesrv\n        environment:\n          - JAVA_HOME=/usr/lib/jvm/jre\n      console:\n        image: styletang/rocketmq-console-ng\n        container_name: rocketmq-console-ng\n        ports:\n          - 8087:8080\n        depends_on:\n          - namesrv\n        environment:\n          - JAVA_OPTS= -Dlogging.level.root=info   -Drocketmq.namesrv.addr=rmqnamesrv:9876 \n          - Dcom.rocketmq.sendMessageWithVIPChannel=false\n    ƴ\n\n1.  broker õķǽ˿ڣʹã\n\n   firewall-cmd --zone=public --add-port=10909-10912/tcp --permanent\n   ƴ\n\n1. ִ sentinel-dashboard.yaml ű\n\n   docker-compose -f rocketmq.yaml up\n   ƴ\n\n1.  rocketmq ̨ǻᷢƵͼȻʼ϶ǿյģ\n\n   http://(װRocketMQIP):8087\n   ƴ\n\n\n\n1. ѡ Ⱥ һܿõ broker IPˣ\n\n\n\nѾ RocketMQ ˵ĲھͿȥ Spring Ŀʹÿͻˡ\n\n3.  Spring Ŀ RocketMQ ͻ\n\n1.  pom ļ\n\n            <dependency>\n                <groupId>org.apache.rocketmq</groupId>\n                rocketmq-spring-boot-starter\n                <version>2.0.4</version>\n            </dependency>\n   ƴ\n\n1.  application.yml ã\n\n   server:\n   port: 10801\n\n   spring:\n   application:\n   name: (Ŀ)-service\n\n   rocketmq:\n   name-server: (װRocketMQIP):9876\n   producer:\n   group: (Ŀ)-group\n   ƴ\n\n1. ½һϢ MessageProducer ΪϢ ߣ\n\n   @Service\n   public class MessageProducer implements CommandLineRunner {\n\n        @Resource\n        private RocketMQTemplate rocketMQTemplate;\n    \n        @Override\n        public void run(String... args) throws Exception {\n            rocketMQTemplate.send(\"test-topic-1\", MessageBuilder.withPayload(\"Hello, World! I'm from spring message\").build());\n        }\n\n   }\n   ƴ\n\n1. ½һϢ MessageListener ΪϢ ߣ\n\n   @Slf4j\n   @Service\n   @RocketMQMessageListener(topic = \"test-topic-1\", consumerGroup = \"my-consumer_test-topic-1\")\n   public class MessageListener implements RocketMQListener<String> {\n\n        @Override\n        public void onMessage(String message) {\n            log.info(\"received message: {}\", message);\n        }\n\n   }\n   ƴ\n\n1. ½һ MessageProducer ĵÿࣺ\n\n   @RestController\n   @RequestMapping\n   public class HelloController {\n\n        @Resource\n        private MessageProducer messageProducer;\n    \n        @RequestMapping(\"/message\")\n        public void message() throws Exception {\n            messageProducer.run(\"\");\n        }\n   ƴ\n\n1.  Spring Ŀһ򵥵Ϣ:\n\n   GET http://localhost:10801/message\n   Accept: */*\n   Cache-Control: no-cache\n   ƴ\n\n\n\nҲ RocketMQ Ĺ̨¿Ϣ\n\n\n\nʵսﲢûнһЩܴһе㷸Ժ\n\nTopic⣩൱һ͵Ϣ Topic1 רǷҵ Topic2 רŻȯҵ񣻶 Group飩൱ڶߺߵķ飬ǵ΢Ҳߣ Group1 Ʒ΢Group2 Ƕ΢񣬵ȻҪ߷黹߷顣\n\n- GroupΪProducerGroup  ConsumerGroup, ĳһߺߣһ˵ͬһΪ Groupͬһ Group һ˵ͺѵϢһġ\n- TopicϢ⣬һϢͣ䷢Ϣ߶ȡϢ\n- Queue: ΪдֶУһ˵дһ£һ¾ͻֺܶ⡣\n\nTopic зΪ˶ QueueʵǷ/ȡϢͨСλǷϢҪָĳдĳ QueueȡϢʱҲҪָȡĳ Queueǵ˳ϢԻǵ Queue άȱֶȫôҪ Queue СΪ1еݶ Queue \n\n\n\nߣײ˵\nӣhttps://juejin.cn/post/6930869079217717256\nԴϡ\nȨСҵתϵ߻Ȩҵתע\n\n# ο\nhttps://lijunyi.xyz/docs/SpringCloud/SpringCloud.html#_2-2-x-%E5%88%86%E6%94%AF\nhttps://mp.weixin.qq.com/s/2jeovmj77O9Ux96v3A0NtA\nhttps://juejin.cn/post/6931922457741770760\nhttps://github.com/D2C-Cai/herring\nhttp://c.biancheng.net/springcloud"
  },
  {
    "path": "docs/Spring全家桶/SpringCloudAlibaba/SpringCloudAlibabaSeata.md",
    "content": "## \n\nǶ֪ Seata һֲʽĽǾ˽һʲôǷֲʽ˽һ»֪ʶ˽һĸʲô\n\n### \n\nĲֹɡ ACID\n\n*   A(Atomic)ԭԣвҪôȫִгɹҪôȫִʧܣֲֳɹ߲ʧܵ\n*   C(Consistency)һԣִǰݿһԼûбƻ磬Сȥȡ100Ǯȡ֮ǰ600ȡ֮Ӧ400ȡ֮ǰȡ֮ΪȷֵΪһԣȡ100Ǯûм٣ҪôСҪЦˣûдﵽһԵҪ\n*   I(Isolation):ԣݿеһ㶼ǲģֻڲִй̻ţһִйвܿй̵м״̬ͨ뼶Աظ⡣\n*   D(Durability)־ԣ֮ݵĸĻᱻ־ûݿ⣬Ҳع\n\nΪ֣ͷֲʽ\n\n#### \n\nڼϵͳУȽ϶ͨϵݿݿⱾԽʵֵģΪӦҪϵݿά񣬼ݿӦöͬһԻڹϵݵֱΪ\n\n#### ֲʽ\n\nֲʽָĲߡ֧ķԴԼ߷ֱλڲͬķֲʽϵͳĲͬڵ֮ϣڲͬӦãֲʽҪ֤ЩҪôȫɹҪôȫʧܣֲʽΪ˱֤ڲͬݿݵһԡ\n\nSeata ˼·ǽıһȫɸ񣬶ACIDγһķֲʽ񣬲ֲʽǲһ\n\nֲʽϵͳһӦòΪɶķ񣬷ڷ֮ͨҪԶЭĲֲַʽϵͳڲͬķ֮ͨԶЭɵ񱻳Ϊֲʽ繩ӦϵͳУɶۼ桢Լ֪ͨȡ\n\n![ͼƬ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/6471b8f38c5920f6ace85580abd1567bc0f021.png \"ͼƬ\")\n\nͼǿԿֻҪ漰Դͻ⣬ʵʿӦҪĳ֣ȻϵͳչӦúӦ֮ȻӦ֮ķ룬΢ܹУҪMQSeata˽֮ǰ˽һ·ֲʽɣԼʵֵġ\n\n## ֲʽ\n\n#### ֲʽʲô\n\nֲʽָĲߣ֧ķԴֱλڷֲʽϵͳĲͬڵ֮ϣͨһֲʽл漰ԶԴҵϵͳĲ\n\nŻķչ֮ǰĵһĿֲʽת΢ڸ˾ѾձڣʱıѾ޷ֲʽӦõҪ˷ֲʽ֮ЭͲ⣬֮Ĳܹ񱾵һѭACIDԭ򣬳Ϊһ⣬ڴţǲϵ̽£ҵ˷ֲʽݣCAPɺBASEۡ\n\n### CAP\n\nCAPһ(C)(A)ݴ(P)ɣڷֲʽϵͳУͬʱConsistency(һ)/Availability()/Partition tolerance(ݴ) ԣֻͬʱ\n\n*   һ(C)ڷֲʽϵͳеݱݣͬһʱ̱һµԣеӦýڵʵĶͬһµݸ\n*   (A): ȺһֽڵԺ󣬼ȺܹӦͻ˵Ķд󣬶ݸ¾߱߿ԡ\n*   ݴ(P): ϵͳڹ涨ʱڲܴݵһԣͱʾҪǰҪCA֮ѡϵͳܹϵʱȻܹ֤ṩһԻ߿Եķ\n\n![ͼƬ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/a5ec7fb950052bd1116452eb0ca9f52fd4e5a5.png \"ͼƬ\")\n\nͼǿԿûȥﳵµʱȻᾭǿжϿǷ㹻㣬ۼԺҪͬϣһΪ˱֤ݵĽһԣʱˣǵϵͳҪ֤ݴԣҲǱһЩ⣬ʱ뱣֤һԣҪԡ\n\nΪ˱֤߿ԣôڸ߲£޷֤޶ʱڸӦĲɿǵĶ޷õµݣҪûӦôҲ޷֤һԣAP޷֤ǿһԵġ\n\nҪ֤߿Ҫ֤һԣõ²ʵ֣ôֻһǾҪ桢Լŵһ𣬵ȥ΢ãҲͲǷֲʽϵͳˡ\n\nڷֲʽϵͳУݴǱڵģֻһԺͿȡᣬ¾͵BASEۡ\n\n### BASE\n\nBASE  (Basically Available)״̬ (Soft state) һ (Eventually consistent) ɣǶCAPһԺͿȨĽԴڶԻϵͳֲʽʵܽᣬǻCAPݻģϵǸǼʱ޷ǿһԣÿӦöԸҵص㣬ʵķʽʹϵͳﵽһԡ\n\n1.  ãֲָʽϵͳֲԤ֪ϵʱʧֿԣﲢ˵ʾϵͳãҪΪ¼:\n\n*   Ӧʱϵʧ£һҪ0.5֮ڷظûӦĲѯڳֹϣѯӦʱ1-2롣\n*   ϵͳϵʧ£һվϽй߼ܹ˳ÿһһЩմ߷ڵʱվϹΪ˱֤ϵͳȶԣ߿ܻһʱҳʾ\n\n![ͼƬ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/a7dac0d93bc82c6c6c7076b77061ed1ebed415.png \"ͼƬ\")\n\nõ˼ǣǵĺķǿʹõģķʵĽӦʱ䣬ǽз񽵼ڵǰУͶ϶ǺķǵķϵͳڵʱֻҪ֤þУͬһӳٸߣȴ߷ȥԺڽлָ\n\n1.  ״̬״ָ̬ϵͳеݴм״̬Ϊм״̬ĴڲӰϵͳԣϵͳýڵݸ֮ͬĹ̴ʱ\n\n![ͼƬ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/610c63492ee5e0f1d30493958b54430c85a9f3.png \"ͼƬ\")\n\n״̬˼˵Ǵµʱ򣬿ۼʱʱʵ߶УܻὫϵͳŪ壬ǿݵͬӳ٣Ӱϵͳʹá\n\n1.  һԣһǿݸھһʱ֮ͬնܹﵽһһµ״̬ˣһԵıҪϵͳ֤ܹﵽһ£Ҫʵʱ֤ϵͳǿһԡ\n\n![ͼƬ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/a23602e9776d2c7bd3d507582538c32a471c8c.png \"ͼƬ\")\n\n߷Ժ󣬾һʱͬм״̬һԣ֤ݵһԡ\n\n### ׶ύ(2PC)\n\n2PC׶ύЭ飬ǽ̷Ϊ׶ΣPָ׼׶ΣCָύ׶Ρ\n\nͺñȥKCCܳԣǸպлڶۣһˣʱպиСڿܳԣʱAAҲͻ˵ֻеͬʱ򣬲ܹ򵽣һͬôͲܳԡ\n\n**׶һ**׼׶ ϰҪȽиͬ⸶ɺҪŮŮͬ⸶ɡ\n\n**׶ζ**ύ׶ ɣϰͣ˶Եܡ\n\nӾһŮ˫һ˾ܾôϰͲͣһȡǮԭ·˻ء\n\nͲɵģϰǸŮǲߣֲʽڼйϵ֧׶ύЭ飺\n\n*   ׼׶(Prepare phase)ÿ߷Prepare Ϣÿݿڱִ񣬲дصUndo/Redo ־ʱûύ\n\n> undo־Ǽ¼޸ǰݣݿع\n>\n> Redo ־Ǽ¼޸ĺݣύдļ\n\n*   ύ׶(commit phase)յ˲ߵִʧܻ߳ʱϢʱֱӸÿ߷(Rollback) Ϣյ߶ɹ(Commit) ߸ִָύ߻عͷʹõԴ\n\n#### ɹύ\n\nв߷ݣѯǷ׼ˣȴߵӦڵִ UndoRedo Ϣ־С߳ɹִYESʾִУЭߴеĲ߻÷YesӦôͻִύ\n\n![ͼƬ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/48e1a2907174a1ed035541e548755d39490441.png \"ͼƬ\")\n\n#### ʧܣ\n\nκһNoָߵȴʱ֮޷յвߵķӦôж񣬷ͻعв߽ڵ㷢 RollBack 󣬲߽յ RollBack 󣬻ڽ׶һ¼UndoϢִĻعɻع֮ͷִڼռõԴع֮Э߷ACKϢڽܵв߷ACKϢ֮жϡ\n\n![ͼƬ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/0928e8933a69a75eff4968f6989ca78ceaeb47.png \"ͼƬ\")\n\n### ׶ύ(3PC)\n\n3PC ҪΪ˽׶ύЭĵСΧǶ׶ύ2PCĸĽ汾ڵĳʱ֮⣬3PC2PC׼׶ηֳѯʣý׶βԤύ,׶ηֱΪ CanCommitPreCommitDoCommit\n\n#### CanCommit ѯ״̬\n\nCanCommit׶ Э(Coordinator)(Participant) CanCommitϢѯǷִвյϢ󣬱ʾִܹУ᷵ظЭִܹе(yes)\n\n![ͼƬ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/c9cc9e8959b9a5393cb59672bc05a9121825ff.png \"ͼƬ\")\n\nִ߲У᷵No,ͷԴ\n\n![ͼƬ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/61bf61d800a42fb1583689ccbc1b1e862fec29.png \"ͼƬ\")\n\n#### PreCommit Ԥύ\n\nPreCommit ׶Эյ߷ص״ֵ̬ΪYESô֤ǶȥִôЭ߾ͻв  PreCommit ϢЭյ PreCommitϢ󣬻ȥִбִгɹὫ񱣴浽 undoredo ٷظЭYESָִбʧܣЭNo,ֻҪЭյһִʧܣв߷жϢյϢ󣬶лع\n\n׶βߺЭ߶˳ʱƣûյЭߵϢЭûյ߷صԤִн״̬ڵȴʱ֮жϣ\n\nЭ߷PreCommitִгɹyes![ͼƬ](https://s2.51cto.com/oss/202206/18/91b004f982c531b8a641821cf1a49be547f9b3.gif \"ͼƬ\")\n\nִʧܣֻһNoЭߣЭ߻߷жϢ߻ع\n\n![ͼƬ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/996ca048361577f6ba066998a6b3ed9ac0450b.png \"ͼƬ\")\n\n#### DoCommit ύ\n\nЭյв߷ص״̬YESʱЭ߻еĲ߶ DoCommit յ DoCommit 󣬻ύ񣬵ύɹ󣬷ЭYES״̬ʾѾύˣЭյв߶YES״̬ô˱\n\n![ͼƬ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/f4ab1eb14668202d8be313dc1b754833689c37.png \"ͼƬ\")\n\nĳ߷NoϢЭ߷жϢ(abort)ǣ߻ع\n\n![ͼƬ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/42ab62f59f9e71c66f7213a6833c2c7e436193.png \"ͼƬ\")\n\n3PC2PC棬˳ʱƣ˵⣬3PCȻܽһԵ⣬ΪDoCommit׶Σ߳ʱԭ²ղЭ߷͹ жϢ(abort) ʱ󣬲߻ύ񣬱Ӧýлعύ󣬻ᵼݲһµ֣2PCȻ´ǿһԱƻ⣬ǹϻָԺܱ֤һԣ3PCȻгʱʱ䣬˿ԣһԣ粨⵼һϣ2PC3PCġ\n\n## Seata\n\nhttps://seata.io/zh-cn/docs/overview/what-is-seata.html\n\nSeata һԴķֲʽṩܺͼõķֲʽSeata Ϊûṩ ATTCCSAGA  XA ģʽΪûһվʽķֲʽ\n\n![ͼƬ](https://s6.51cto.com/oss/202206/18/22906d30493df9861728958921200a254ef5d5.gif \"ͼƬ\")![ͼƬ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/e335a42151fa17eb25e848defd449317e036d3.png \"ͼƬ\")\n\n΢ϵͳУһҵᱻֳɶģ飬ڹٷṩĽṹͼУǿԿǰҪΪģ顣\n\n*   񣺶ƷϢӻ߼ٲ\n*   񣺸ûָƷɶ\n*   ˻񣺴û˻п۳ӻ֣άַϢȵȡ\n\nڵǰܹУûѡǵƷµҪɲÿһڲӵһı֤ǰݵǿһԣɵȫһԾû취б֤ôSeataġ\n\n### Seata\n\nַhttps://seata.io/zh-cn/docs/overview/terminology.html\n\n˽Seata֮ǰ˽һ Seata ؼĸ\n\n1.  TC(Transaction Coordinator)Эߣάȫֺͷ֧״̬ȫύ߻ع\n2.  TM(Transaction Manager) ߣ ߣͬʱһRMһ֣ȫķΧʼȫύعȫ\n3.  RM(Resource Manager) Դ  ΢񣬹֧ԴTC̸ע֧ͱ֧״̬֧ύع\n\n### Seata 2PC\n\n**һ׶Σ** ҵݺͻع־¼ͬһύͷűԴ\n\n**׶Σ** ύ첽ǳٵɡعͨһ׶εĻع־з򲹳\n\nһ׶αύǰҪȷõ ȫ òȫ ύȫĳԱһΧڣΧعͷű\n\nݿⱾ뼶ύϵĻϣSeataAT ģʽĬȫָ뼶 δύ\n\nӦض£Ҫȫֵ ύ Ŀǰ Seata ķʽͨ SELECT FOR UPDATE Ĵ\n\nSeata̷ִ\n\n![ͼƬ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/c7e10e315ebb9d8effa798730cebde46ec741a.png \"ͼƬ\")\n\nÿRM ʹ DataSourceProxy ·Ŀʹ ConnectionProxy ʹԴݴĿڵһ׶ν undoҵݷһύͱֻҪҵһdudo־\n\nڵһ׶Уundo޸ǰ޸ĵֵΪع׼ڵһ׶ɾѾ֧ύˣҲͷԴ\n\nTMȫʼXIDȫIDУͨfeignýXIDηУÿ֧Լ Branch ID֧IDXIDй\n\nڵڶ׶ȫύTC֪֧ͨύ֧ڵһ׶Ѿύ˷ֻ֧Ҫɾundoɣҿ첽ִС\n\nĳһ֧쳣ˣڶ׶ȫعTC֪֧ͨ߻ع֧ͨXIDBranch-IDҵӦĻع־ͨع־ɵķSQLִУɷ֧ع֮ǰ״̬\n\n### Seata ذװ\n\nصַhttps://github.com/seata/seata/releases\n\n![ͼƬ](https://s7.51cto.com/oss/202206/18/97b6c0b520c17b43b883003d10ef8e22d933a2.gif \"ͼƬ\")![ͼƬ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/019d53972f031fdf1767425abf92f907f0f1c8.png \"ͼƬ\")\n\nѹҵconfĿ¼\n\n![ͼƬ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/88a3fa94605fb2567a64954540711876f4a6df.png \"ͼƬ\")![ͼƬ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/6998821822b0d8ed7a44318b8b3390bc421303.png \"ͼƬ\")![ͼƬ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/d157544494550bb1c865815e31ae990fbac40b.png \"ͼƬ\")\n\nseata֮ǰҪnacosʵҲܼ򵥣ֻҪnacosУ֪nacosôĿĽnacosܣ֮seatabinĿ¼seata-server.bat![ͼƬ](https://s5.51cto.com/oss/202206/18/b8605b968056145dc6c740b5c95f346a98eaae.gif \"ͼƬ\")\n\nǿ8091˿ڼnacosעȥˣͱʾseataɹˡ\n\n![ͼƬ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/96829da176e4fcca80a0230d5c14b601ac549b.png \"ͼƬ\")![ͼƬ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/c253ebd69820f7d2daa259c97f27cdc70fd160.png \"ͼƬ\")\n\n## ܽ\n\nǹڷֲʽĺseataĽܾͽˣʵڷֲʽMQʵֿɿϢһԣMQҪܣϢ͵ԭ⡣뷽ϢĿɿԡ\n\n## ǰ\n\nһǽˣڷֲʽseataĻܺʹãȤСԻعһ[˵㲻ֲ֪ʽ!](https://developer.51cto.com/article/711909.html) СũҲ˵ˣڻҹSeataйseataATTCCSAGA  XA ģʽĽܺʹãSeataзֲʽģ͵Ľܡ\n\nSeataΪģ飬ֱ TMRM  TC\n\n**TC (Transaction Coordinator) - Эߣ**άȫֺͷ֧״̬ȫύع\n\n**TM (Transaction Manager) - **ȫķΧʼȫύعȫ\n\n**RM (Resource Manager) - Դ**֧ԴTC̸ע֧ͱ֧״̬֧ύع\n\n![ͼƬ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/c1ba09f1965be00dd7b817c1942fccfa2d7d25.png \"ͼƬ\")\n\n Seata Уֲʽִ̣\n\n*   TM ֲʽTM  TC עȫ¼\n*   ҵ񳡾ݿ⡢ԴRM  TC 㱨Դ׼״̬ \n*   TM ֲʽһ׶νTM ֪ͨ TC ύ/عֲʽ񣩡\n*   TC Ϣֲʽύǻع\n*   TC ֪ͨ RM ύ/ع Դ׶ν\n\nTM  RM Ϊ Seata ĿͻҵϵͳһTC Ϊ Seata ķ˶\n\n˴洢ģʽ֧֣\n\n**file** ģʽȫỰϢڴжд־ûļroot.dataܽϸߣĬϣ\n\n**DB:**  ߿ģʽȫỰϢͨDBܲһЩ\n\n**redis** Seata-Server1.3ϰ汾֧֣ܽϸߣϢʧգҪʵʳʹá\n\n### TC\n\nʹDB߿ģʽҵconf/file.confļ\n\n![ͼƬ](https://s9.51cto.com/oss/202207/03/84e02c1536f8c1431cc833b98958c1168f8658.gif \"ͼƬ\")![ͼƬ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/d9e1e2d06e2f4e53344238a2806fbc9bdbaf7b.png \"ͼƬ\")\n\n޸еϢҵӦdbã޸еjdbcӣҪע漰global_tablebranch_tablelock_tableͬʱ mysql5mysql8ǲһġ\n\n> mysql5com.mysql.jdbc.Driver\n>\n> mysql8com.mysql.cj.jdbc.Driver\n\nַhttps://github.com/seata/seata/blob/develop/script/server/db/mysql.sql\n\n**global_table** ȫÿһȫ󣬾ͻڸñм¼ȫID\n\n**branch_table** ֧¼ÿһ֧ ID֧ĸݿϢ\n\n**lock_table** ȫ\n\núԺSeataЧ\n\n### Seata  Nacos\n\nSeata֧עNacosԼ֧Seata÷ŵNacosģNacosͳһά ߿ģʽ¾ҪNacos\n\nҵ conf/registry.conf޸registryϢ\n\n\n\n\n\n\n\n\n\n```\nregistry {\n  # file nacos eurekarediszkconsuletcd3sofa\n  type = \"nacos\"\n  nacos {\n    application = \"seata-server\" # ҪͿͻ˱һ\n    serverAddr = \"127.0.0.1:8848\"\n    group = \"SEATA_GROUP\"  # ҪͿͻ˱һ\n    namespace = \"\"\n    cluster = \"default\"\n    username = \"nacos\"\n    password = \"nacos\"\n  }\n  config {\n  # filenacos apollozkconsuletcd3\n  type = \"nacos\"\n  nacos {\n    serverAddr = \"127.0.0.1:8848\"\n    namespace = \"\"\n    group = \"SEATA_GROUP\"\n    username = \"nacos\"\n    password = \"nacos\"\n    dataId = \"seataServer.properties\"\n  }\n  ......\n}\n```\n\n\n\n\n\n\n\n\n\n\n\n޸ĺú󣬽seataеһЩϴNacosУΪȽ϶࣬Թٷṩһconfig.txtֻز޸ĳЩϴNacosмɡ\n\n**صַ**https://github.com/seata/seata/tree/develop/script/config-center\n\n޸£\n\n\n\n\n\n\n\n\n\n\n\n```\nservice.vgroupMapping.mygroup=default # \nstore.mode=db\nstore.db.driverClassName=com.mysql.cj.jdbc.Driver\nstore.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai\nstore.db.user=root\nstore.db.password=123456\n```\n\n\n\n\n\n\n\n\n\n\n\n޸ĺļԺ󣬰ļŵseataĿ¼£\n\n![ͼƬ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/b42aeca797a2bcb8fb5791e836d0f6011e9848.png \"ͼƬ\")\n\nЩö뵽NacosУҪһűִУٷѾṩá\n\n**ַΪ**https://github.com/seata/seata/blob/develop/script/config-center/nacos/nacos-config.sh\n\n½һnacos-config.shļűݸƽȥ޸congfig.txt·\n\n![ͼƬ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/b876340233c7ac1623731359ea33164becafc1.png \"ͼƬ\")![ͼƬ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/f563db6014ec0cb7f5a13952c63552c916da05.png \"ͼƬ\")\n\nļ޸ĺú󣬴gitߣnacos-config.shקмɻʹ\n\n> sh nacos-config.sh -h 127.0.0.1 -p 8848 -g SEATA_GROUP -t 88b8f583-43f9-4272-bd46-78a9f89c56e8 -u nacos -w nacos\n>\n> -hnacosַ\n>\n> -p˿ڣĬ8848\n>\n> -gseataķбơ\n>\n> -tnacosռid\n>\n> -u-wnacosû롣\n\n![ͼƬ](https://s3.51cto.com/oss/202207/03/b685868586971f9c3b1490e3fcf50041fc2f6b.gif \"ͼƬ\")![ͼƬ](https://s4.51cto.com/oss/202207/03/965180d92863b00defe5550f051ade40ac60e8.gif \"ͼƬ\")![ͼƬ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/764bfaf385a67b25f732035cde02cc76bbcd20.png \"ͼƬ\")\n\n![ͼƬ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/110a73f54f3f0e565cd21630cf0e0cf4b80ee2.png \"ͼƬ\")\n\nĸִʧܣΪredisĹϵԺԣӰʹáԿNacosкܶ˵ɹseataɹ8091˿ڣʾǰùѾ׼ɡ\n\n![ͼƬ](https://s7.51cto.com/oss/202207/03/573560212aa718ea82c317737eefc492908086.gif \"ͼƬ\")![ͼƬ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/f71535950882c017bbd05116c54c16ccb20f0e.png \"ͼƬ\")\n\n### Seataģʽ\n\nSeata ȫĿܣҪΪ¼\n\n1.  TM  TC (Begin)ύ(Commit)ع(Rollback)ȫ\n2.  TMѴȫXID󶨵֧ϡ\n3.  RMTCעᣬѷ֧XIDȫС\n4.  RMѷִ֧нϱTC\n5.  TCͷ֧ύBranch Commit֧عBranch RollbackRM\n\n![ͼƬ](https://s9.51cto.com/oss/202207/03/410c3b93872ba8a55a9532876f3afbe1853d88.gif \"ͼƬ\")![ͼƬ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/a30dd41045c8db4e0c5757095c6d1ab52fd57e.png \"ͼƬ\")\n\nSeata  ȫ ̣Ϊ׶Σ\n\n*   ִн׶ ִз֧񣬲ִ֤н_ɻعģRollbackable__־ûģDurable_\n*   ɽ׶Σ ִн׶ γɵľ飬Ӧͨ TM ȫύع TCTC  RM  ֧  Commit  Rollback\n\nSeata νģʽָ Seata ȫµ ֧ Ϊģʽ׼ȷؽӦý ֧ģʽ\n\nͬ ģʽ  ֧ ʹòͬķʽﵽȫ׶εĿꡣش⣺\n\n*   ִн׶ ִв ֤ ִн_ɻعģRollbackable__־ûģDurable_\n*   ɽ׶Σյ TC ֧ύع\n\nATģʽΪ\n\n*   ִн׶Σ\n\n*   ɻع SQL ¼ع־\n*   ־ûع־ҵ SQL ͬһύݿ\n\n*   ɽ׶Σ\n*   ֧ύ첽ɾع־¼\n*   ֧عݻع־з򲹳\n\nͽͷϷSeataĴģʽĽܡ\n\n## Seata-XAģʽ\n\nSeata 1.2.0 汾µģͣXAģʽʵ˶XAЭ֧֡XAģʽҪȥ\n\n*   XAģʽʲô\n*   Ϊʲô֧XA\n*   XAģʽʵֺʹá\n\n### XAģʽ\n\nҪ֪XAģʲôXA 淶 90 ͱڽֲʽ⣬ҲķֲʽΪҪݿڲҲ֧XAģʽģMYSQLXAģʽǿһԵص㣬ݿռʱȽϳܱȽϵ͡\n\nXAģʽ׶ύ\n\n1.  һ׶νעᣬעᵽTCУִSQL䡣\n2.  ڶ׶TCж֪ͨύع\n3.  ڵһڶ׶ιУһֱռݿܱȽϵͣҪôһύҪôһعʵǿһԡ\n\nATģʽTCCSAGAЩģʽԴXA淶ĳЩҵ񳡾޷㡣\n\n### ʲôXAЭ\n\nXA淶X/OPEN֯ķֲʽDTPDistributed Transaction Processing׼XA淶ȫ;ֲԴ֮ĽӿڣXA淶ĿԴ(ݿ⣬Ӧ÷Ϣеȣͬһзʣʹ ACID ԿԽӦóЧ\n\nXA 淶 ʹ׶ύ2PCTwo-Phase Commit֤ԴͬʱύعκضΪXA淶类ԼеݿⶼжXA淶֧֡\n\nֲʽDTPģͶĽɫ£\n\n*   APӦó򣬿ΪʹDTPֲʽĳ綩񡢿\n*   RMԴΪĲߣһָһݿʵMySqlͨԴԸݿпƣԴŷ֧\n*   TMЭ͹ȫ񣬹ʵڣЭRMȫֲָʽУҪݿ⹲ͬһһȫ\n\n![ͼƬ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/85cb437444a4debfdbb835519a1b2fdd16ee7b.png \"ͼƬ\")\n\nDTPģʽTMRM֮ͨѶĽӿڹ淶XAΪݿṩ2PCӿЭ飬ݿXAЭʵֵ2PCֳΪXA\n\nӦó(AP)жͿ⣬Ӧó(AP)ͨTM֪ͨ(RM)Ϳ(RM)пۼɶʱRMûύ񣬶Դ\n\nTMյִϢһRMִʧܣֱRMҲͻع񣬻عϣͷԴ\n\nTMյִϢRMȫɹRMύύϣͷԴ\n\n![ͼƬ](https://s7.51cto.com/oss/202207/03/d264d0e03f64cdd85f1600518e2db31ee0b8a5.gif \"ͼƬ\")![ͼƬ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/8298749403719fbb05d917a501c3dd3b1cc449.png \"ͼƬ\")\n\nֲʽͨЭXA淶ִʾ\n\n![ͼƬ](https://s7.51cto.com/oss/202207/03/3609cd043edf05941832252b7e7ce838b95837.gif \"ͼƬ\")![ͼƬ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/9235cb212a65d21b72549719fdc8b7e4cdd340.png \"ͼƬ\")\n\nһAPRM1,RM2JDBCӡ\n\nڶAP֪ͨȫIDRM1RM2עᵽȫID\n\nִж׶Эеĵһ׶prepare\n\nĲprepare󣬾ύع\n\nǶXAԣһȫԴʧˣôζTMղ֧ôݣһֱӶҲSeataҪص⡣\n\nSeataķֲʽܹУԴ(ݾ֡Ϣ)ȶXAЭ֧֣XAЭĻ֧\n\n![ͼƬ](https://s2.51cto.com/oss/202207/03/a93391c180ce2293be8525a483594c701ebcbd.gif \"ͼƬ\")![ͼƬ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/52b22c57810685deea7412204aa069e494f863.png \"ͼƬ\")\n\n*   ִн׶Σ\n\n*   ɻعҵSQLXA֧нУԴXAЭ֧֤ɻع\n*   ־ûZA֧Ժִ XA prepareͬԴXAЭ֧֤־û\n\n*   ɽ׶Σ\n\n### XAڵ\n\n*   ֧ύִXA֧commit\n*   ֧عִXA֧rollback\n\nSeata Ѿ֧ģʽAT\\TCC\\SAGAǲ񣬲ƹ Դ ֮(Ҫôм棬ҪôӦò)Դڷֲʽ޸֪ģֶڷֲʽ޸֪һԵ⣬޷ȫһԡ\n\nһ¼ڲУ80ۼΪ60ʱֿԱѯݽ60֮Ϊ쳣ععԭ80ôʱԱ60ʵݣм״̬ǲڵݡ\n\nͲͬXAЭҪԴ ṩԹ淶Э֧֣ΪԴֲ֪ʽУԴԱ֤ӽǶݵķЧԣȫݵһԡ\n\n### XAģʽʹ\n\n**ٷ**https://github.com/seata/seata-samples\n\n**Ŀ**seata-samples\n\n**ҵʼ**business-xastock-xaorder-xa˺ŷaccount-xa\n\nĿԺҵĿΪseata-xaĿ¼вݿӣòݿ⣬ֻҪ޸ĹٷĵݿϢɡ\n\nȹע business-xaĿĹעBusinessService.purchase()\n\n\n\n\n\n\n\n\n\n\n```\n@GlobalTransactional\n    public void purchase(String userId, String commodityCode, int orderCount, boolean rollback) {\n        String xid = RootContext.getXID();\n        LOGGER.info(\"New Transaction Begins: \" + xid);       \n        //ÿ\n        String result = stockFeignClient.deduct(commodityCode, orderCount);\n        if (!SUCCESS.equals(result)) {\n            throw new RuntimeException(\"ʧ,ع!\");\n        }\n        //ɶ\n        result = orderFeignClient.create(userId, commodityCode, orderCount);\n        if (!SUCCESS.equals(result)) {\n            throw new RuntimeException(\"ʧ,ع!\");\n        }\n        if (rollback) {\n            throw new RuntimeException(\"Force rollback ... \");\n        }\n    }\n```\n\n\n\n\n\n\n\n\n\n\n\nʵַ֮ǰֻ࣬Ҫorder-xa(OrderService.create)Ϊ(int i = 1/0;)\n\n\n\n\n\n\n\n\n\n\n\n```\npublic void create(String userId, String commodityCode, Integer count) {\n        String xid = RootContext.getXID();\n        LOGGER.info(\"create order in transaction: \" + xid);\n        int i = 1/0;\n        // ܼ = (count) * Ʒ(100)\n        int orderMoney = count * 100;\n        // ɶ\n        jdbcTemplate.update(\"insert order_tbl(user_id,commodity_code,count,money) values(?,?,?,?)\",\n            new Object[] {userId, commodityCode, count, orderMoney});\n        // ˻ۼ\n        String result = accountFeignClient.reduce(userId, orderMoney);\n        if (!SUCCESS.equals(result)) {\n            throw new RuntimeException(\"Failed to call Account Service. \");\n        }\n\n    }\n```\n\n\n\n\n\n\n\n\n\n\n\nһԽXAģʽATģʽתOrderXADataSourceConfiguration.dataSource\n\n\n\n\n\n\n\n\n\n\n```\n@Bean(\"dataSourceProxy\")\n    public DataSource dataSource(DruidDataSource druidDataSource) {\n        // DataSourceProxy for AT mode\n        // return new DataSourceProxy(druidDataSource);\n        // DataSourceProxyXA for XA mode\n        return new DataSourceProxyXA(druidDataSource);\n    }\n```\n\n\n\n\n\n\n\n\n\n\n\nĸ񣬷ʵַ http://localhost:8084/purchase\n\n![ͼƬ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/d82378852259a962ae041377e8433a31eb8602.png \"ͼƬ\")\n\nǿбȻȥӦݿݣûзģ˵ǵXAģʽЧˣdubugȥĿʱ򣬵ݸĵʱݿʵҲûм¼ģΪXAǿһԣֻеԺ󣬲Żеݡ\n\nXAģʽļ룬SeataȫһԳµȱڣγATTCCSagaXA Ĵģʽİͼгֲʽ\n\nXAATҵģTCCSagaһҵġ\n\n## Seata-ATģʽ\n\nһATģʽATģʽһûķֲʽĽATģʽ£ûֻעԼҵSQLûҵSQLΪһ׶ΣSeataܻԶж׶ύͻع\n\n**׶ύЭݱ䣺**\n\n*   һ׶Σҵݺͻع־¼ͬһύͷűԴ\n*   ׶Σ\n    ύ첽ǳٵɡ\n    عͨһ׶εĻع־з򲹳\n\n#### ATģʽҪص\n\n1.  һԡ\n2.  ܽXAߡ\n3.  ֻڵһ׶λȡڵһ׶νύͷ\n\nһ׶УSeata ҵSQL ȽSQL壬ҵҪҵݣݱǰ¼ undo logȻִ ҵSQL ݣ֮ٴα redo logЩڱݿɣ֤һ׶εԭԡ\n\nһ׶Σ׶αȽϼ򵥣Ļعύ֮ǰһ׶бûͨôִȫֻعִȫύعõľһ׶μ¼ undo Log ͨع¼ɷSQLִУɷ֧ĻعȻɺͷԴɾ־\n\nAT̷Ϊ׶ΣҪ߼ȫڵһ׶Σڶ׶Ҫع־Ĺ£\n\n![ͼƬ](https://s9.51cto.com/oss/202207/03/c7d9f9e7001e9fca3d8309b146f3014ecfbf06.gif \"ͼƬ\")![ͼƬ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/02880c46589d54a3f12449afa76a44e9b8e73a.png \"ͼƬ\")\n\nͼǿԿTMTC뿪һȫһͨ@GlobalTransactionalעTC᷵һȫID(XID)ִб֮ǰRMTCעһ֧ undo log ִбredo log ύTC㱨ִOK\n\nԶ̵ãIDݸ񣬿ִб֮ǰTCע֧񣬿ͬundo Logredo LogTC㱨״̬ɹ\n\nȫύTC֪ͨRMһundoredo־һִʧܣôعͨundo logлع\n\nﻹһ⣬Ϊÿӱύ֪ͨعʱ棬Ѿ޸ģֱundo logлعܻᵼݲһµ\n\nʱ RM redo log֤ԱǷһӶ֪Ƿб޸Ĺundo logڱ޸ǰݣعredologڱ޸ĺݣڻعУ顣\n\nûб޸ĹֱӽлعݣredologУд\n\n### ʵս\n\n˽ATģ͵Ļʵսһ£ATģ;ʵֵġ cloud-alibaba-seata-order  cloud-alibaba-seata-stock\n\nṹt_ordert_stockundo_logűĿԴͱṹundo_log˱ݵĻعĩӡ\n\ncloud-alibaba-seata-orderĴ£\n\ncontroller\n\n\n\n\n\n\n\n\n\n\n```\n@RestController\npublic class OrderController {\n    @Autowired\n    private OrderService orderService;\n    @GetMapping(\"order/create\")\n    @GlobalTransactional //ֲʽ\n    public String create(){\n        orderService.create();\n        return \"ɹ\";\n    }\n}\n```\n\n\n\n\n\n\n\n\n\n\n\nOrderService\n\n\n\n\n\n\n\n\n\n\n```\npublic interface OrderService {\n    void create();\n}\n```\n\n\n\n\n\n\n\n\n\n\n\nStockClient\n\n\n\n\n\n\n\n\n\n\n```\n@FeignClient(value = \"seata-stock\")\npublic interface StockClient {\n    @GetMapping(\"/stock/reduce\")\n    String reduce();\n\n}\n```\n\n\n\n\n\n\n\n\n\n\n\nOrderServiceImpl\n\n\n\n\n\n\n\n\n\n\n```\n@Service\npublic class OrderServiceImpl implements OrderService{\n    @Autowired\n    private OrderMapper orderMapper;\n    @Autowired\n    private StockClient stockClient;\n    @Override\n    public void create() {\n        //ۼ\n        stockClient.reduce();\n        System.out.println(\"ۼɹ\");\n        //ֹ쳣 ڻعϢ\n        int i = 1/0;\n        System.err.println(\"쳣\");\n        //\n        orderMapper.createOrder();\n        System.out.println(\"ɹ\");\n    }\n}\n```\n\n\n\n\n\n\n\n\n\n\n\nOrderMapper\n\n\n\n\n\n\n\n\n\n\n```\n@Mapper\npublic interface OrderMapper {\n    @Insert(\"insert into t_order (order_no,order_num) value (order_no+1,1)\")\n    void createOrder();\n}\n```\n\n\n\n\n\n\n\n\n\n\n\ncloud-alibaba-seata-stockĴ£\n\n\n\n\n\n\n\n\n\n\n```\n@RestController\npublic class StockController {\n    @Autowired\n    private StockService stockService;\n    @GetMapping(\"stock/reduce\")\n    public String reduce(){\n        stockService.reduce();\n        return \"ѿۼ\"+ new Date();\n    }\n}\n```\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n```\npublic interface StockService {\n    void reduce();\n}\n```\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n```\n@Service\npublic class StockServiceImpl implements StockService{\n    @Autowired\n    StockMapper stockMapper;\n    @Override\n    public void reduce() {\n        stockMapper.reduce();\n    }\n}\n```\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n```\n@Mapper\n@Repository\npublic interface StockMapper {\n    @Update(\"update t_stock set order_num = order_num - 1 where order_no = 1 \")\n    void reduce();\n\n}\n```\n\n\n\n\n\n\n\n\n\n\n\n붼Ƚϼ򵥣ǾͲעҲУҪorderstock֮ǰǵNacosSeataҪʱǷorderRestӿڣhttp://localhost:8087/order/create,Ϊ֤undo_logıڴ洢عݣOrderServiceImpl.create()Ӷϵ㣬debugķʽ\n\n![ͼƬ](https://s5.51cto.com/oss/202207/03/e9df0649789b2ad3b0a9120c1992f9a7e55cb9.gif \"ͼƬ\")![ͼƬ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/64e370d42b8375a8f7a1285cc1bc47b1437bff.png \"ͼƬ\")\n\nȻhttp://localhost:8087/order/createڵʱ䣬ȥundo_logͿ֣ᷢȷʵˣundo_logҲ˶ӦĿռ¼޸ĵǰϢݾعݡ\n\n![ͼƬ](https://s8.51cto.com/oss/202207/03/9361fc3173dcb931a615608893af1f48fbeed0.gif \"ͼƬ\")![ͼƬ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/567b564906d2c473d3d770087b745a5218587f.png \"ͼƬ\")\n\nǵF9ͨԺ󣬿ָundo_logҲûˣʱ֤ǵSeataЧعɹ\n\n![ͼƬ](https://s8.51cto.com/oss/202207/03/66c11ca96212ce62540349442f14ccff8b87a4.gif \"ͼƬ\")![ͼƬ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/75cd0d103bd57d300e5194f66aa826f8494b94.png \"ͼƬ\")\n\nǾ֤ATִй̣XATCCģͣSeataATģͿӦԴҵ񳡾ҿҵ룬޸֪Эύ߻عͨAOPɣֻҪעҵ񼴿ɡ\n\nSeataҪڲͬķ֮䴫ȫΨһIDDubboȿܼɻȽѺãDubboùʿIDĴݣIDĴ̶ԿҲ޸֪\n\n## Seata-TCCģʽ\n\nʹðhttps://seata.io/zh-cn/blog/integrate-seata-tcc-mode-with-spring-cloud.html\n\n### ʲôTCC\n\nTCC ǷֲʽеĶ׶ύЭ飬ȫΪ Try-Confirm-CancelԴԤTryȷϲConfirmȡCancelǵľ庬£\n\n1.  TryҵԴļ鲢Ԥ\n2.  Confirmҵύ commit ֻҪ Try ɹôòһɹ\n3.  Cancelҵȡعòض Try ԤԴͷš\n\nTCC һʽķֲʽҪҵϵͳʵ֣ҵϵͳŷǳԣԸӣŵ TCC ȫݿ⣬ܹʵֿݿ⡢ӦԴЩͬݷͨʽı뷽ʽʵһԭӲõؽڸָҵ񳡾µķֲʽ⡣\n\n![ͼƬ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1431eaa67529de68d4f9506ae25dc17c3882b8.png \"ͼƬ\")\n\n### TCCAT\n\nAT ģʽ ֱ֧ ACID   ϵݿ⣺\n\n*   һ׶ prepare ΪڱУһύҵݸºӦع־¼\n*   ׶ commit Ϊϳɹ**Զ**첽ع־\n*   ׶ rollback Ϊͨع־**Զ**ɲݻع\n\nӦģTCC ģʽڵײԴ֧֣\n\n*   һ׶ prepare Ϊ**Զ** prepare ߼\n*   ׶ commit Ϊ**Զ** commit ߼\n*   ׶ rollback Ϊ**Զ** rollback ߼\n\nν TCC ģʽְָ֧ **Զ** ķ֧뵽ȫĹС\n\n#### ص㣺\n\n1.  ԱȽǿҪԼʵ߼\n2.  ̻ûܽǿ\n\n## Seata-Sagaģʽ\n\nSagaģʽSEATAṩĳSagaģʽУҵÿ߶ύ񣬵ĳһʧ򲹳ǰѾɹĲߣһ׶Ͷ׶βִдʱˣһ޸Ļᣩҵ񿪷ʵ֡\n\nSaga ģʽ·ֲʽͨ¼ģ֮첽ִеģSaga ģʽһֳ\n\n֮ǰѧϰSeataֲʽֲģʹõĵ΢ȫԸݿߵ޸ģһЩ⻷£ϵͳյϵͳ޷޸ģͬʱûκηֲʽ룩ôATXATCCģͽȫʹãΪ˽⣬Sagaģ͡\n\n磺߿˾ķϵͳ޷죬ʹSagaģʽ\n\n![ͼƬ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/e923ef663d8c0a9de06072d96c6debbac49795.png \"ͼƬ\")\n\nSagaģʽSeataṩĳṩ칹ϵͳͳһģ͡SagaģʽУеҵ񶼲ֱӲĴֻ𱾵Ĵȫյöʵ֣ڽҵ߼ʱĳһҵʱԶȫѾɹߣһ׶εúͶ׶εķ񲹳ȫҵ񿪷ʵ֡\n\n### Saga״̬\n\nĿǰSeataṩSagaģʽֻͨ״̬ʵ֣ҪֹĽSagaҵ̻ƣҽתΪJsonļڳʱļʵҵԼ񲹳ҪSaga״̬ͼĻƣһҪͨSaga״̬ʵ֡\n\n#### ԭ\n\n*   ͨ״̬ͼõ̲jsonļ\n*   ״̬ͼһڵԵһ񣬽ڵĲڵ㡣\n*   ״̬ͼ json ״ִ̬У쳣ʱ״̬淴ִѳɹڵӦĲڵ㽫ع\n*   ʵֵַ֧ѡ񡢲̡תӳ䡢ִ״̬жϡ쳣ȹܡ\n\n#### Saga״̬Ӧ\n\n**ٷĵַ**https://seata.io/zh-cn/docs/user/saga.html\n\nSeata Safa״̬ӻͼʹõַhttps://github.com/seata/seata/blob/develop/saga/seata-saga-statemachine-designer/README.zh-CN.md\n\n## ܽ\n\nܵ˵SeataATģʽٷ֮80ķֲʽҵATģʽʵֵһԣԿܴм״̬XAģʽʵֵǿһԣЧʽϵһ㣬Saga֮ͬķֲʽԹڷֲʽĴģͣеҵ񳡾XAATûҵԣSagaTCCһҵ롣\n\n# ο\nhttps://www.51cto.com/article/713007.html\nhttps://lijunyi.xyz/docs/SpringCloud/SpringCloud.html#_2-2-x-%E5%88%86%E6%94%AF\nhttps://mp.weixin.qq.com/s/2jeovmj77O9Ux96v3A0NtA\nhttps://juejin.cn/post/6931922457741770760\nhttps://github.com/D2C-Cai/herring\nhttp://c.biancheng.net/springcloud"
  },
  {
    "path": "docs/Spring全家桶/SpringCloudAlibaba/SpringCloudAlibabaSentinel.md",
    "content": "## ժҪ\n\nSpring Cloud Alibaba ṩ΢񿪷һվʽSentinel Ϊ֮һ۶һϵз񱣻ܣĽ÷ϸܡ\n\n## Sentinel\n\n΢Уͷ֮ȶԱԽԽҪ Sentinel Ϊ㣬ơ۶Ͻϵͳرȶάȱȶԡ\n\nSentinel:\n\n*   ḻӦóн˰Ͱͽ 10 ˫ʮһĺĳɱʵʱ۶βӦã\n*   걸ʵʱأͬʱṩʵʱļعܡڿ̨пӦõĵ̨뼶ݣ 500 ̨¹ģļȺĻ\n*   㷺ĿԴ̬ṩ伴õԴ/ģ飬 Spring CloudDubbogRPC ϣ\n*   Ƶ SPI չ㣺ṩáƵ SPI չ㡣ͨʵչ㣬ٵĶ߼\n\n## װSentinel̨\n\n> Sentinel̨һĿ̨Ӧãʵʱ鿴ԴؼȺԴܣṩһϵеĹܣع򡢽ȵȡ\n\n*   ȴӹSentinelص`sentinel-dashboard-1.6.3.jar`ļصַ[github.com/alibaba/Sen](https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Falibaba%2FSentinel%2Freleases \"https://github.com/alibaba/Sentinel/releases\")\n\n*   ɺSentinel̨\n\n```\njava -jar sentinel-dashboard-1.6.3.jar\nƴ\n```\n\n*   Sentinel̨Ĭ8080˿ϣ¼˺Ϊ`sentinel`ͨµַԽзʣ[http://localhost:8080](https://link.juejin.cn?target=http%3A%2F%2Flocalhost%3A8080 \"http://localhost:8080\")\n\n![image-20230423173003900](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230423173003900.png)\n\n\n\n\n\n\n*   Sentinel̨Բ鿴̨ʵʱݡ\n\n![image-20230423173019956](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230423173019956.png)\n\n\n\n\n\n\n## sentinel-serviceģ\n\n> Ǵһsentinel-serviceģ飬ʾSentinel۶ܡ\n\n*   pom.xmlʹNacosΪעģҪͬʱNacos\n\n```\n<dependency>\n    <groupId>com.alibaba.cloud</groupId>\n    spring-cloud-starter-alibaba-nacos-discovery\n</dependency>\n<dependency>\n    <groupId>com.alibaba.cloud</groupId>\n    spring-cloud-starter-alibaba-sentinel\n</dependency>\nƴ\n```\n\n*   application.ymlãҪNacosSentinel̨ĵַ\n\n```\nserver:\n  port: 8401\nspring:\n  application:\n    name: sentinel-service\n  cloud:\n    nacos:\n      discovery:\n        server-addr: localhost:8848 #Nacosַ\n    sentinel:\n      transport:\n        dashboard: localhost:8080 #sentinel dashboardַ\n        port: 8719\nservice-url:\n  user-service: http://nacos-user-service\nmanagement:\n  endpoints:\n    web:\n      exposure:\n        include: '*'\nƴ\n```\n\n## \n\n> Sentinel Starter ĬΪе HTTP ṩ㣬Ҳͨʹ@SentinelResourceԶһЩΪ\n\n### RateLimitController\n\n> ڲ۶Ϻܡ\n\n```\n/**\n * \n * Created by macro on 2019/11/7.\n */\n@RestController\n@RequestMapping(\"/rateLimit\")\npublic class RateLimitController {\n\n    /**\n     * ԴҪָ߼\n     */\n    @GetMapping(\"/byResource\")\n    @SentinelResource(value = \"byResource\",blockHandler = \"handleException\")\n    public CommonResult byResource() {\n        return new CommonResult(\"Դ\", 200);\n    }\n\n    /**\n     * URLĬϵ߼\n     */\n    @GetMapping(\"/byUrl\")\n    @SentinelResource(value = \"byUrl\",blockHandler = \"handleException\")\n    public CommonResult byUrl() {\n        return new CommonResult(\"url\", 200);\n    }\n\n    public CommonResult handleException(BlockException exception){\n        return new CommonResult(exception.getClass().getCanonicalName(),200);\n    }\n\n}\nƴ\n```\n\n### Դ\n\n> ǿԸ@SentinelResourceעжvalueԴƣҪָ߼\n\n*   عSentinel̨ãʹNacosעģNacossentinel-service\n\n*   SentinelõعҪȷ½ӿڣSentinel̨вŻжӦϢȷ¸ýӿڣ[http://localhost:8401/rateLimit/byResource](https://link.juejin.cn?target=http%3A%2F%2Flocalhost%3A8401%2FrateLimit%2FbyResource \"http://localhost:8401/rateLimit/byResource\")\n\n*   Sentinel̨ع򣬸@SentinelResourceעvalueֵ\n\n![image-20230423173034164](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230423173034164.png)\n\n\n\n\n\n\n*   ٷĽӿڣԷַԼϢ\n\n![image-20230423173044930](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230423173044930.png)\n\n\n\n\n\n\n### URL\n\n> ǻͨʵURL᷵ĬϵϢ\n\n*   Sentinel̨عʹ÷ʵURL\n\n![image-20230423173055243](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230423173055243.png)\n\n\n\n\n\n\n*   ηʸýӿڣ᷵Ĭϵ[http://localhost:8401/rateLimit/byUrl](https://link.juejin.cn?target=http%3A%2F%2Flocalhost%3A8401%2FrateLimit%2FbyUrl \"http://localhost:8401/rateLimit/byUrl\")\n\n![image-20230423173105426](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230423173105426.png)\n\n\n\n\n\n\n### Զ߼\n\n> ǿԶͨõ߼Ȼ@SentinelResourceָ\n\n*   CustomBlockHandlerԶ߼\n\n```\n/**\n * Created by macro on 2019/11/7.\n */\npublic class CustomBlockHandler {\n\n    public CommonResult handleException(BlockException exception){\n        return new CommonResult(\"ԶϢ\",200);\n    }\n}\nƴ\n```\n\n*   RateLimitControllerʹԶ߼\n\n```\n/**\n * \n * Created by macro on 2019/11/7.\n */\n@RestController\n@RequestMapping(\"/rateLimit\")\npublic class RateLimitController {\n\n    /**\n     * Զͨõ߼\n     */\n    @GetMapping(\"/customBlockHandler\")\n    @SentinelResource(value = \"customBlockHandler\", blockHandler = \"handleException\",blockHandlerClass = CustomBlockHandler.class)\n    public CommonResult blockHandler() {\n        return new CommonResult(\"ɹ\", 200);\n    }\n\n}\nƴ\n```\n\n## ۶Ϲ\n\n> Sentinel ֶ֧ԷýбԹӦý۶ϲʹRestTemplatenacos-user-serviceṩĽӿʾ¸ùܡ\n\n*   Ҫʹ@SentinelRestTemplateװRestTemplateʵ\n\n```\n/**\n * Created by macro on 2019/8/29.\n */\n@Configuration\npublic class RibbonConfig {\n\n    @Bean\n    @SentinelRestTemplate\n    public RestTemplate restTemplate(){\n        return new RestTemplate();\n    }\n}\nƴ\n```\n\n*   CircleBreakerController࣬nacos-user-serviceṩӿڵĵã\n\n```\n/**\n * ۶Ϲ\n * Created by macro on 2019/11/7.\n */\n@RestController\n@RequestMapping(\"/breaker\")\npublic class CircleBreakerController {\n\n    private Logger LOGGER = LoggerFactory.getLogger(CircleBreakerController.class);\n    @Autowired\n    private RestTemplate restTemplate;\n    @Value(\"${service-url.user-service}\")\n    private String userServiceUrl;\n\n    @RequestMapping(\"/fallback/{id}\")\n    @SentinelResource(value = \"fallback\",fallback = \"handleFallback\")\n    public CommonResult fallback(@PathVariable Long id) {\n        return restTemplate.getForObject(userServiceUrl + \"/user/{1}\", CommonResult.class, id);\n    }\n\n    @RequestMapping(\"/fallbackException/{id}\")\n    @SentinelResource(value = \"fallbackException\",fallback = \"handleFallback2\", exceptionsToIgnore = {NullPointerException.class})\n    public CommonResult fallbackException(@PathVariable Long id) {\n        if (id == 1) {\n            throw new IndexOutOfBoundsException();\n        } else if (id == 2) {\n            throw new NullPointerException();\n        }\n        return restTemplate.getForObject(userServiceUrl + \"/user/{1}\", CommonResult.class, id);\n    }\n\n    public CommonResult handleFallback(Long id) {\n        User defaultUser = new User(-1L, \"defaultUser\", \"123456\");\n        return new CommonResult<>(defaultUser,\"񽵼\",200);\n    }\n\n    public CommonResult handleFallback2(@PathVariable Long id, Throwable e) {\n        LOGGER.error(\"handleFallback2 id:{},throwable class:{}\", id, e.getClass());\n        User defaultUser = new User(-2L, \"defaultUser2\", \"123456\");\n        return new CommonResult<>(defaultUser,\"񽵼\",200);\n    }\n}\nƴ\n```\n\n*   nacos-user-servicesentinel-service\n\n*   ǲûnacos-user-serviceжidΪ4ûз½ӿڻ᷵ط񽵼[http://localhost:8401/breaker/fallback/4](https://link.juejin.cn?target=http%3A%2F%2Flocalhost%3A8401%2Fbreaker%2Ffallback%2F4 \"http://localhost:8401/breaker/fallback/4\")\n\n```\n{\n\t\"data\": {\n\t\t\"id\": -1,\n\t\t\"username\": \"defaultUser\",\n\t\t\"password\": \"123456\"\n\t},\n\t\"message\": \"񽵼\",\n\t\"code\": 200\n}\nƴ\n```\n\n*   ʹexceptionsToIgnoreNullPointerExceptionǷʽӿڱָʱᷢ񽵼[http://localhost:8401/breaker/fallbackException/2](https://link.juejin.cn?target=http%3A%2F%2Flocalhost%3A8401%2Fbreaker%2FfallbackException%2F2 \"http://localhost:8401/breaker/fallbackException/2\")\n\n![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/11/18/16e7eb0fb3df7c01~tplv-t2oaga2asx-zoom-in-crop-mark:4536:0:0:0.awebp)\n\n\n\n\n\n\n## Feignʹ\n\n> SentinelҲFeignʹFeignзʱҲʹ۶ϡ\n\n*   Ҫpom.xmlFeign\n\n```\n<dependency>\n    <groupId>org.springframework.cloud</groupId>\n    spring-cloud-starter-openfeign\n</dependency>\nƴ\n```\n\n*   application.ymlдSentinelFeign֧֣\n\n```\nfeign:\n  sentinel:\n    enabled: true #sentinelfeign֧\nƴ\n```\n\n*   Ӧ@EnableFeignClientsFeignĹܣ\n\n*   һUserServiceӿڣڶnacos-user-serviceĵã\n\n```\n/**\n * Created by macro on 2019/9/5.\n */\n@FeignClient(value = \"nacos-user-service\",fallback = UserFallbackService.class)\npublic interface UserService {\n    @PostMapping(\"/user/create\")\n    CommonResult create(@RequestBody User user);\n\n    @GetMapping(\"/user/{id}\")\n    CommonResult<User> getUser(@PathVariable Long id);\n\n    @GetMapping(\"/user/getByUsername\")\n    CommonResult<User> getByUsername(@RequestParam String username);\n\n    @PostMapping(\"/user/update\")\n    CommonResult update(@RequestBody User user);\n\n    @PostMapping(\"/user/delete/{id}\")\n    CommonResult delete(@PathVariable Long id);\n}\nƴ\n```\n\n*   UserFallbackServiceʵUserServiceӿڣڴ񽵼߼\n\n```\n/**\n * Created by macro on 2019/9/5.\n */\n@Component\npublic class UserFallbackService implements UserService {\n    @Override\n    public CommonResult create(User user) {\n        User defaultUser = new User(-1L, \"defaultUser\", \"123456\");\n        return new CommonResult<>(defaultUser,\"񽵼\",200);\n    }\n\n    @Override\n    public CommonResult<User> getUser(Long id) {\n        User defaultUser = new User(-1L, \"defaultUser\", \"123456\");\n        return new CommonResult<>(defaultUser,\"񽵼\",200);\n    }\n\n    @Override\n    public CommonResult<User> getByUsername(String username) {\n        User defaultUser = new User(-1L, \"defaultUser\", \"123456\");\n        return new CommonResult<>(defaultUser,\"񽵼\",200);\n    }\n\n    @Override\n    public CommonResult update(User user) {\n        return new CommonResult(\"ʧܣ񱻽\",500);\n    }\n\n    @Override\n    public CommonResult delete(Long id) {\n        return new CommonResult(\"ʧܣ񱻽\",500);\n    }\n}\nƴ\n```\n\n*   UserFeignControllerʹUserServiceͨFeignnacos-user-serviceеĽӿڣ\n\n```\n/**\n * Created by macro on 2019/8/29.\n */\n@RestController\n@RequestMapping(\"/user\")\npublic class UserFeignController {\n    @Autowired\n    private UserService userService;\n\n    @GetMapping(\"/{id}\")\n    public CommonResult getUser(@PathVariable Long id) {\n        return userService.getUser(id);\n    }\n\n    @GetMapping(\"/getByUsername\")\n    public CommonResult getByUsername(@RequestParam String username) {\n        return userService.getByUsername(username);\n    }\n\n    @PostMapping(\"/create\")\n    public CommonResult create(@RequestBody User user) {\n        return userService.create(user);\n    }\n\n    @PostMapping(\"/update\")\n    public CommonResult update(@RequestBody User user) {\n        return userService.update(user);\n    }\n\n    @PostMapping(\"/delete/{id}\")\n    public CommonResult delete(@PathVariable Long id) {\n        return userService.delete(id);\n    }\n}\nƴ\n```\n\n*   ½ӿڻᷢ񽵼ط񽵼Ϣ[http://localhost:8401/user/4](https://link.juejin.cn?target=http%3A%2F%2Flocalhost%3A8401%2Fuser%2F4 \"http://localhost:8401/user/4\")\n\n```\n{\n\t\"data\": {\n\t\t\"id\": -1,\n\t\t\"username\": \"defaultUser\",\n\t\t\"password\": \"123456\"\n\t},\n\t\"message\": \"񽵼\",\n\t\"code\": 200\n}\nƴ\n```\n\n## ʹNacos洢\n\n> Ĭ£Sentinel̨ùʱ̨͹ʽͨAPIͻ˲ֱӸµڴСһӦãʧǽνùг־ûԴ洢NacosΪ\n\n### ԭʾͼ\n\n![image-20230423173120010](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230423173120010.png)\n\n\n\n\n\n\n*   ֱĴĽ͵ͻˣ\n\n*   Sentinel̨ҲȥȡϢ\n\n### ʾ\n\n*   pom.xml\n\n```\n<dependency>\n    <groupId>com.alibaba.csp</groupId>\n    sentinel-datasource-nacos\n</dependency>\nƴ\n```\n\n*   ޸application.ymlļNacosԴã\n\n```\nspring:\n  cloud:\n    sentinel:\n      datasource:\n        ds1:\n          nacos:\n            server-addr: localhost:8848\n            dataId: ${spring.application.name}-sentinel\n            groupId: DEFAULT_GROUP\n            data-type: json\n            rule-type: flow\nƴ\n```\n\n*   Nacosã\n\n![image-20230423173137516](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230423173137516.png)\n\n\n\n\n\n\n*   Ϣ£\n\n```\n[\n    {\n        \"resource\": \"/rateLimit/byUrl\",\n        \"limitApp\": \"default\",\n        \"grade\": 1,\n        \"count\": 1,\n        \"strategy\": 0,\n        \"controlBehavior\": 0,\n        \"clusterMode\": false\n    }\n]\nƴ\n```\n\n*   زͣ\n\n    *   resourceԴƣ\n    *   limitAppԴӦã\n    *   gradeֵͣ0ʾ߳1ʾQPS\n    *   countֵ\n    *   strategyģʽ0ʾֱӣ1ʾ2ʾ·\n    *   controlBehaviorЧ0ʾʧܣ1ʾWarm Up2ʾŶӵȴ\n    *   clusterModeǷȺ\n*   Sentinel̨Ѿ\n\n![image-20230423173152487](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230423173152487.png)\n\n\n\n\n\n\n*   ٷʲԽӿڣԷַϢ\n\n![image-20230423173203461](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230423173203461.png)\n\n\n\n\n\n\n## ο\n\nSpring Cloud Alibaba ٷĵ[github.com/alibaba/spr](https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Falibaba%2Fspring-cloud-alibaba%2Fwiki \"https://github.com/alibaba/spring-cloud-alibaba/wiki\")\n\n## ʹõģ\n\n```\nspringcloud-learning\n nacos-user-service -- עᵽnacosṩUserCRUDӿڵķ\n sentinel-service -- sentinelܲԷ\nƴ\n```\n\n## ĿԴַ\n\n[github.com/macrozheng/](https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fmacrozheng%2Fspringcloud-learning \"https://github.com/macrozheng/springcloud-learning\")\n\n\n\nߣMacroZheng\nӣhttps://juejin.cn/post/6844903999876022279\nԴϡ\nȨСҵתϵ߻Ȩҵתע\n\n# ο\nhttps://lijunyi.xyz/docs/SpringCloud/SpringCloud.html#_2-2-x-%E5%88%86%E6%94%AF\nhttps://mp.weixin.qq.com/s/2jeovmj77O9Ux96v3A0NtA\nhttps://juejin.cn/post/6931922457741770760\nhttps://github.com/D2C-Cai/herring\nhttp://c.biancheng.net/springcloud"
  },
  {
    "path": "docs/Spring全家桶/SpringCloudAlibaba/SpringCloudAlibabaSkywalking.md",
    "content": "# 1\\. SkyWalking \n\n> Skywalking ɹڿԴɣԭ OneAPM ʦĿǰڻΪԴύ Apache ĲƷͬʱ Zipkin/Pinpoint/CAT ˼·ַ֧ʽ㡣һڷֲʽٵӦóܼϵͳչһ OpenTracing ּ֯ƽصһЩ淶ͱ׼\n\n*   SkyWalking һԴƽ̨ڴӷԭʩռۺϺͿӻݡ\n*   SkyWalking ṩһּ򵥵ķάֲʽϵͳͼԿƲ鿴һִAPMרΪԭķֲʽϵͳơ\n*   SkyWalking άȶӦýмӣservice, service instanceʵ, endpoint˵㣩ʵͲ˵ˣ˵Ƿеĳ·˵URI\n*   SkyWalking û˽Ͷ˵֮˹ϵ鿴ÿ/ʵ/˵Ķþ\n\n## SkyWalking\n\nSkyWalkingҪļģ:\n\n1.  Agent Ҫϵͳвɼָ꣬·ݣ͸ oap \n2.  oap  Agent ͹ݣ洢ִзṩѯͱܡ\n3.  Storage  UI 洢Լ鿴ݡ\n\n# 2\\. ʹ Docker ٴ SkyWalking 8.0\n\n1.  ** linux ѡ񲢽Ŀ¼**\n\n```\nmkdir skywalking-docker\nƴ\n```\n\n1.  ** skywalking-docker Ŀ¼һΪ skywalking.yaml Ľűļ**\n\n```\nversion: '3'\nservices:\n  elasticsearch7:\n    image: docker.elastic.co/elasticsearch/elasticsearch:7.5.0\n    container_name: elasticsearch7\n    restart: always\n    ports:\n      - 9023:9200\n    environment:\n      - discovery.type=single-node\n      - bootstrap.memory_lock=true\n      - \"ES_JAVA_OPTS=-Xms512m -Xmx512m\"\n      - TZ=Asia/Shanghai\n    ulimits:\n      memlock:\n        soft: -1\n        hard: -1\n    networks:\n      - skywalking\n    volumes:\n      - elasticsearch7:/usr/share/elasticsearch/data\n  oap:\n    image: apache/skywalking-oap-server:8.0.1-es7\n    container_name: oap\n    depends_on:\n      - elasticsearch7\n    links:\n      - elasticsearch7\n    restart: always\n    ports:\n      - 9022:11800\n      - 9021:12800\n    networks:\n      - skywalking\n    volumes:\n      - ./ext-config:/skywalking/ext-config\n  ui:\n    image: apache/skywalking-ui:8.0.1\n    container_name: ui\n    depends_on:\n      - oap\n    links:\n      - oap\n    restart: always\n    ports:\n      - 9020:8080\n    environment:\n      SW_OAP_ADDRESS: oap:12800\n    networks:\n      - skywalking\n\nnetworks:\n  skywalking:\n    driver: bridge\n\nvolumes:\n  elasticsearch7:\n    driver: local\nƴ\n```\n\n**ע**븲 oap е /skywalking/config Ŀ¼µļǿ docker йһ /skywalking/ext-config Ŀ¼ļĿ¼мɡ\n\n![image-20230423174422281](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230423174422281.png)\n\n1.  **ִ skywalking.yaml ű**\n\n```\ndocker-compose -f skywalking.yaml up\nƴ\n```\n\n1.  ** skywalking Ŀָ̨Ǳ̣ʼȻǿյ**\n\n```\nhttp://(װSkyWalkingIP):9020\nƴ\n```\n\n![image-20230423174444272](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230423174444272.png)\n\n# 3\\.  Spring Ŀ SkyWalking ͻ\n\nȫ־׷ traceId ʹã\n\n1.  ** pom ļ**\n\n```\n        <dependency>\n            <groupId>org.apache.skywalking</groupId>\n            apm-toolkit-logback-1.x\n            <version>8.0.1</version>\n        </dependency>\n        <dependency>\n            <groupId>org.apache.skywalking</groupId>\n            apm-toolkit-trace\n            <version>8.0.1</version>\n        </dependency>\nƴ\n```\n\n1.  ** resources Ŀ¼  logback-spring.xml ļ**:\n\n```\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<configuration>\n    <property name=\"logger.path\" value=\"/mnt/logs\"/>\n\n    <!-- ɫ־ -->\n    <!-- ɫ־Ⱦ -->\n    <conversionRule conversionWord=\"clr\" converterClass=\"org.springframework.boot.logging.logback.ColorConverter\"/>\n    <conversionRule conversionWord=\"wex\"\n                    converterClass=\"org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter\"/>\n    <conversionRule conversionWord=\"wEx\"\n                    converterClass=\"org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter\"/>\n    <!-- ɫ־ʽ -->\n    <property name=\"CONSOLE_LOG_PATTERN\"\n              value=\"${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}\"/>\n\n    <!-- ̨ -->\n    \n        <filter class=\"ch.qos.logback.classic.filter.ThresholdFilter\">\n            <level>info</level>\n        </filter>\n        <encoder>\n            <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>\n            <charset>UTF-8</charset>\n        </encoder>\n    \n\n    <!-- ConsoleAppender־̨ -->\n    \n        <encoder class=\"ch.qos.logback.core.encoder.LayoutWrappingEncoder\">\n            <layout class=\"org.apache.skywalking.apm.toolkit.log.logback.v1.x.mdc.TraceIdMDCPatternLogbackLayout\">\n                <Pattern>\n                    <![CDATA[%clr(%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}}){faint} [%X{tid}] %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}]]></Pattern>\n            </layout>\n        </encoder>\n    \n\n    <!-- ļ -->\n    <!-- ʱ levelΪ DEBUG ־ -->\n    <!-- \n        <file>${logger.path}/log_debug.log</file>\n        &lt;!&ndash;־ļʽ&ndash;&gt;\n        <encoder>\n            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>\n            <charset>UTF-8</charset> &lt;!&ndash; ַ &ndash;&gt;\n        </encoder>\n        &lt;!&ndash; ־¼ĹԣڣС¼ &ndash;&gt;\n        <rollingPolicy >\n            &lt;!&ndash; ־鵵 &ndash;&gt;\n            <fileNamePattern>${logger.path}/debug/log-debug-%d{yyyy-MM-dd}.%i.log</fileNamePattern>\n            <timeBasedFileNamingAndTriggeringPolicy >\n                <maxFileSize>100MB</maxFileSize>\n            </timeBasedFileNamingAndTriggeringPolicy>\n            &lt;!&ndash;־ļ&ndash;&gt;\n            <maxHistory>15</maxHistory>\n        </rollingPolicy>\n        &lt;!&ndash; ־ļֻ¼debug &ndash;&gt;\n        <filter >\n            <level>debug</level>\n            <onMatch>ACCEPT</onMatch>\n            <onMismatch>DENY</onMismatch>\n        </filter>\n     -->\n\n    <!-- ʱ levelΪ INFO ־ -->\n    \n        <!-- ڼ¼־ļ·ļ -->\n        <file>${logger.path}/log_info.log</file>\n        <!--־ļʽ-->\n        <encoder>\n            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>\n            <charset>UTF-8</charset>\n        </encoder>\n        <!-- ־¼ĹԣڣС¼ -->\n        <rollingPolicy class=\"ch.qos.logback.core.rolling.TimeBasedRollingPolicy\">\n            <!-- ÿ־鵵·Լʽ -->\n            <fileNamePattern>${logger.path}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>\n            <timeBasedFileNamingAndTriggeringPolicy class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP\">\n                <maxFileSize>100MB</maxFileSize>\n            </timeBasedFileNamingAndTriggeringPolicy>\n            <!--־ļ-->\n            <maxHistory>15</maxHistory>\n        </rollingPolicy>\n        <!-- ־ļֻ¼info -->\n        <filter class=\"ch.qos.logback.classic.filter.LevelFilter\">\n            <level>info</level>\n            <onMatch>ACCEPT</onMatch>\n            <onMismatch>DENY</onMismatch>\n        </filter>\n    \n\n    <!-- ʱ levelΪ WARN ־ -->\n    <!-- \n        &lt;!&ndash; ڼ¼־ļ·ļ &ndash;&gt;\n        <file>${logger.path}/log_warn.log</file>\n        &lt;!&ndash;־ļʽ&ndash;&gt;\n        <encoder>\n            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>\n            <charset>UTF-8</charset> &lt;!&ndash; ˴ַ &ndash;&gt;\n        </encoder>\n        &lt;!&ndash; ־¼ĹԣڣС¼ &ndash;&gt;\n        <rollingPolicy >\n            <fileNamePattern>${logger.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>\n            <timeBasedFileNamingAndTriggeringPolicy >\n                <maxFileSize>100MB</maxFileSize>\n            </timeBasedFileNamingAndTriggeringPolicy>\n            &lt;!&ndash;־ļ&ndash;&gt;\n            <maxHistory>15</maxHistory>\n        </rollingPolicy>\n        &lt;!&ndash; ־ļֻ¼warn &ndash;&gt;\n        <filter >\n            <level>warn</level>\n            <onMatch>ACCEPT</onMatch>\n            <onMismatch>DENY</onMismatch>\n        </filter>\n     -->\n\n    <!-- ʱ levelΪ ERROR ־ -->\n    \n        <!-- ڼ¼־ļ·ļ -->\n        <file>${logger.path}/log_error.log</file>\n        <!--־ļʽ-->\n        <encoder>\n            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>\n            <charset>UTF-8</charset> <!-- ˴ַ -->\n        </encoder>\n        <!-- ־¼ĹԣڣС¼ -->\n        <rollingPolicy class=\"ch.qos.logback.core.rolling.TimeBasedRollingPolicy\">\n            <fileNamePattern>${logger.path}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>\n            <timeBasedFileNamingAndTriggeringPolicy class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP\">\n                <maxFileSize>100MB</maxFileSize>\n            </timeBasedFileNamingAndTriggeringPolicy>\n            <!--־ļ-->\n            <maxHistory>15</maxHistory>\n        </rollingPolicy>\n        <!-- ־ļֻ¼ERROR -->\n        <filter class=\"ch.qos.logback.classic.filter.LevelFilter\">\n            <level>ERROR</level>\n            <onMatch>ACCEPT</onMatch>\n            <onMismatch>DENY</onMismatch>\n        </filter>\n    \n\n    <!--\n        rootڵǱѡڵ㣬ָ־ֻһlevel\n        level:ôӡ𣬴Сд޹أTRACE, DEBUG, INFO, WARN, ERROR, ALL  OFF\n        ΪINHERITEDͬNULLĬDEBUG\n        ԰Ԫأʶappenderӵlogger\n    -->\n    <root level=\"info\">\n        \n        \n        <!---->\n        \n        <!---->\n        \n    </root>\n\n</configuration>\nƴ\n```\n\n**ע**־ãҪⲿ `` á\n\n1.  ** skywalking  SkyWalking APMҪҪõ agent**\n\nskywalking صַ[skywalking.apache.org/downloads/](https://link.juejin.cn?target=http%3A%2F%2Fskywalking.apache.org%2Fdownloads%2F \"http://skywalking.apache.org/downloads/\")\n\n![image-20230423174506711](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230423174506711.png)\n\n1.  **ѹص apache-skywalking-apm-es7-8.0.1.tar.gz Ŀ¼ṹͼ**\n\n![image-20230423174522153](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230423174522153.png)\n\nֻҪе agent Ŀ¼Уagent ĶЩ\n\n![image-20230423174533023](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230423174533023.png)\n\n agent Ŀ¼ƵһƵĿ¼£һҪ JVM Ŀ¼ȻֱӷŵĿ\n\n![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8deaee160419423e9a71e710d3b2c3dd~tplv-k3u1fbpfcp-zoom-in-crop-mark:1512:0:0:0.awebp)\n\n1.  ** idea  JVM **\n\n```\n-javaagent:(agentļڵĿ¼)\\agent\\skywalking-agent.jar -Dskywalking.agent.service_name=()-service -Dskywalking.agent.instance_name=()-instance -Dskywalking.collector.backend_service=(װSkyWalkingIP):9022\nƴ\n```\n\n![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/96e9d5a3aa5c44c3b0b948929609ae1f~tplv-k3u1fbpfcp-zoom-in-crop-mark:1512:0:0:0.awebp)\n\nΪ skywalking Ƿʽʵֲַʽ·ٺܼأһ javaagent ķʽ\n\n> **Javaagent ʲô**JVM ǰ̬ Instrument\n>\n> Javaagent  java һ javaagent ָһ jar ҶԸ java Ҫ\n>\n> 1.   jar  MANIFEST.MF ļָ Premain-Class \n> 2.  Premain-Class ָǸʵ premain() \n>\n> premain() ⣬ main() ֮ǰĵࡣ Java ʱִ main() ֮ǰjvm  -javaagent ָ jar  Premain-Class  premain()  \n\n1.  **ǰƪдĿЩĿȫϱߵһ飬Ч**\n\n*   طherring-gatewayzuul ͳһ΢\n*   ֤herring-oauth2oauth2 ֤΢\n*   Աherring-member-service΢֮һյᵽ֤֤\n*   herring-orders-service΢֮յᵽ֤֤\n*   Ʒherring-product-service΢֮յᵽ֤֤\n\n![image-20230423174600279](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230423174600279.png)\n\n1.  **һ£ tokenȻ /api/member/update**\n\n```\n#### \n\nPOST http://localhost:8080/oauth2-service/oauth/token?grant_type=password&username=admin&password=123456&client_id=app-client&client_secret=client-secret-8888&scope=all\nAccept: */*\nCache-Control: no-cache\nƴ\n```\n\nõؽ token\n\n```\n{\n  \"access_token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiZ2F0ZXdheS1zZXJ2aWNlIl0sInVzZXJfbmFtZSI6ImFkbWluIiwiand0LWV4dCI6IkpXVCDmianlsZXkv6Hmga8iLCJzY29wZSI6WyJhbGwiXSwiZXhwIjoxNjEzOTcwMDk2LCJhdXRob3JpdGllcyI6WyJST0xFX0FETUlOIl0sImp0aSI6IjU4MDY5ODlhLWUyNDQtNGQyMy04YTU5LTBjODRiYzE0Yjk5OSIsImNsaWVudF9pZCI6ImFwcC1jbGllbnQifQ.EP4acam0tkJQ9kSGRGk_mQsfi1y4M_hhiBL0H931v60\",\n  \"token_type\": \"bearer\",\n  \"refresh_token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiZ2F0ZXdheS1zZXJ2aWNlIl0sInVzZXJfbmFtZSI6ImFkbWluIiwiand0LWV4dCI6IkpXVCDmianlsZXkv6Hmga8iLCJzY29wZSI6WyJhbGwiXSwiYXRpIjoiNTgwNjk4OWEtZTI0NC00ZDIzLThhNTktMGM4NGJjMTRiOTk5IiwiZXhwIjoxNjE0MDM0ODk2LCJhdXRob3JpdGllcyI6WyJST0xFX0FETUlOIl0sImp0aSI6IjQxZGM1ZDc1LTZmZDgtNDU3My04YmRjLWI4ZTMwNWEzMThmMyIsImNsaWVudF9pZCI6ImFwcC1jbGllbnQifQ.CGmGx_msqJBHxa95bBROY2SAO14RyeRklVPYrRxZ7pQ\",\n  \"expires_in\": 7199,\n  \"scope\": \"all\",\n  \"jwt-ext\": \"JWT չϢ\",\n  \"jti\": \"5806989a-e244-4d23-8a59-0c84bc14b999\"\n}\nƴ\n```\n\nִ /api/member/update\n\n```\n####\n\nGET http://localhost:8080/member-service/api/member/update\nAccept: */*\nCache-Control: no-cache\nAuthorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiZ2F0ZXdheS1zZXJ2aWNlIl0sInVzZXJfbmFtZSI6ImFkbWluIiwiand0LWV4dCI6IkpXVCDmianlsZXkv6Hmga8iLCJzY29wZSI6WyJhbGwiXSwiZXhwIjoxNjEzOTcwMDk2LCJhdXRob3JpdGllcyI6WyJST0xFX0FETUlOIl0sImp0aSI6IjU4MDY5ODlhLWUyNDQtNGQyMy04YTU5LTBjODRiYzE0Yjk5OSIsImNsaWVudF9pZCI6ImFwcC1jbGllbnQifQ.EP4acam0tkJQ9kSGRGk_mQsfi1y4M_hhiBL0H931v60\nƴ\n```\n\n**Ǳ̽չʾ**:\n\n![image-20230423174639471](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230423174639471.png)\n\n![image-20230423174721822](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230423174721822.png)\n\n![image-20230423174742703](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230423174742703.png)\n\n**ͼչʾ**\n\n![image-20230423174809108](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230423174809108.png)\n\n![image-20230423174831290](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230423174831290.png)\n\n**·׷ٽչʾ**\n\n![image-20230423174845526](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230423174845526.png)\n\n\n\nߣײ˵\nӣhttps://juejin.cn/post/6931922457741770760\nԴϡ\nȨСҵתϵ߻Ȩҵתע\n\n# ο\nhttps://lijunyi.xyz/docs/SpringCloud/SpringCloud.html#_2-2-x-%E5%88%86%E6%94%AF\nhttps://mp.weixin.qq.com/s/2jeovmj77O9Ux96v3A0NtA\nhttps://juejin.cn/post/6931922457741770760\nhttps://github.com/D2C-Cai/herring\nhttp://c.biancheng.net/springcloud"
  },
  {
    "path": "docs/Spring全家桶/SpringCloudAlibaba/SpringCloudAlibaba概览.md",
    "content": "Spring Cloud Alibabaṩ΢񿪷һվʽSpring CloudֲAlibabaԪ֮ĲSpring Cloud AlibabaԿٴ΢ܹɼСҵҪҵ̨ͼֻ̨ҵתͣSpring Cloud Alibabaһ\n\n# ʲôSpring Cloud Alibaba\n\nðȿSpring Cloud AlibabaĹͼʾ\n\n![image-20230423165959115](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230423165959115.png)\n\nͼоͿԿSpring Cloud AlibabaSpring CloudĿðɣĿһʼĶλǸӹϵ\n\nٷû߸Spring Cloudļܹ˼룬ֻһǿչ\n\n\n# ΪʲôҪSpring Cloud Alibaba\n\n\nȿSpring Cloud AlibabaЩ\n\n\n\nSpring Cloud Alibaba ܼѰ汾ҲշѰ汾\n\nSentinelԡΪ㣬ơԡݴ͸رȷṩԱȶԡ\n\n\n\nNacosһֺ̬߱ͷֲʽõȹܵĹƽ̨ҪڹԭӦó\n\n\n\nRocketMQһܡ߿áĽڼϢмSpring Cloud Alibaba RocketMQ ƻװԱɡ伴á\n\n\n\nDubboһJavaĸܿԴRPCܡ\n\n\n\nSeataһʹõķֲʽ΢ܹ\n\nOSSƶ洢񣩣һּܵİȫƴ洢񣬿Դ洢ͷκεطĴݡ\n\n\n\nSchedulerXһֲʽȲƷֶָ֧ʱ㴥\n\n\n\nSMSһָȫϢṩݡЧܵͨŹܣɰҵϵͻ\n\n\n\nSpring Cloud Alibabaĸ汾ĶԱȣͼʾ\n\n![image-20230423170034102](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230423170034102.png)\n\nûSpring Cloud Alibaba֮ǰʹSpring Cloud΢񿪷\n\n\n\nSpring Cloudֶ֧עģEurekaZooKeeperConsulȡ\n\n\n\nԱҪEurekaΪעģҪһEureka ServerȺڹעķԪݣȻӦ÷ҪEurekaҪʹöӦע⽫ṩԼעᵽEurekaעġͬZooKeeperConsulҲǲͬķʽʹöӦעĵעɷעͶġ\n\n\n\nSpring CloudΪ˷ԱٵĽ벻ͬעģͳһʹע@EnableDiscoveryClient+ӦעĵStarterȻEurekaϵʹ÷ʽ@EnableEurekaClient+ӦעĵStarterҪSpring CloudѾֹͣ˶Eurekaά\n\n\n\nðˣSpring CloudѾZooKeeperConsulʹ÷ʽͳһԱǳĽӦýSpring CloudĿǰֳһµעģNacosܷǳߣ֧CPAPģʽSpring Cloud֧֡\n\n\n\nûSpring Cloud Alibaba֮ǰʹNacos\n\n\n\nðɣNacosһֲַ֧ʽעĺͷֲʽĵNacosٷṩ˺ܶģʽSpring FrameworkSpring Bootȣײ㱾NacosṩSDK,Nacos Client\n\n\n\nSpring Framework+Nacos Clientnacos-spring-contextҪԱԼάNacosNamingServceNacosConfigServiceʵҲ˵ԱҪԼNacos Clientοɱǳ\n\n\n\nSpring Boot+Nacos Starternacos-discovery-spring-boot-starter򿪷ԱԸЧĽNacosģҿʹSpring BootṩĸStarter\n\n\n\nSpring CloudΪܣʹNacosΪעģþᰡ\n\n\n\nôûһܼȿʹSpring CloudֿʹSpring Bootܼݸעأܸ˵ĸߴңSpring Cloud AlibabaĽ˿Ա΢ܹѡ͵⣬ˡ\n\n# Spring Cloud Alibabaĺļܹ˼\n\n\nSpring Cloud Alibabaܹͼ\n\n![image-20230423170108696](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230423170108696.png)\n\nSpring Cloud AlibabaܹľĿǽԴƷƳںϣҵϺµ޷ݣҵ񿪷ԱֻҪҵĿײ㼼ϸھͽSpring Cloud Alibaba\n\n\n\nοֲʹ\n\n\n\n\nðɣ˵ôԱôSpring Cloud Alibabaأ\n\nʵ򵥵ķʽȻԼԶʹSpring Cloud AlibabaĹܣȥԴ̵ܸϤԭ\n\n\n\nҲһЩ˻ΪҿԴϤˣһ֮ˣʲôء룬ǲԴ룬飬һ֮ҲǵġֻͨۺʵսϣŻ᳤ڵıϰȥϰ¹ʶ֪¡\n\n\n\nԱJavaԱӦöIDEAĿҾþͿٵһSpring Cloud AlibabaĿSpring FrameworkٷĽּĿSpring Initializrȡ\n\n\n\nоҪܽһЩʵҪSpring Cloud AlibabaļʼߣһЩõҵ񳡾̵ܸ˽Spring Cloud Alibaba˼롣\n\n\nCopyright ?  Link: [https://lijunyi.xyz/docs/SpringCloud/SpringCloud.html](https://lijunyi.xyz/docs/SpringCloud/SpringCloud.html)\n\nٷĵһ򵥵ĽSpring Cloud AlibabaЩҪ֣иӡ󣬺浱ȻϸĽܣȥ⡣\n\n- **ͽ**֧`WebServlet``WebFlux``OpenFeign``RestTemplate``Dubbo`ͽͨ`console`ʵʱ޸ԣָּ֧\n- **עͷ**עclientsͨSpringbeanʵںRibbon\n- **ֲʽ**ֲַ֧ʽϵͳչøıʱԶˢ\n- **Rpc **չSpring CloudRestTemplateOpenFeignֵ֧Dubbo RPC\n- **¼**ֹ֧ͨϢϵͳӵĸ߶ȿ¼΢\n- **ֲʽ**ָ֧ܡʹõķֲʽ\n- ƶ洢ȡƶŷ\n\nǰѹٷĵģԷSpring Cloud Alibabaǳǿ󣬻Ķ¿~\n\n\n\n# \n\n## ![springCloud-Alibaba-1](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/springCloud-Alibaba-1.png)[#](#汾ϵ) 汾ϵ\n\n[ٷ汾˵(opens new window)](https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E)\n\n### 2021.x ֧\n\n| Spring Cloud Alibaba Version | Spring Cloud Version  | Spring Boot Version |\n| ---------------------------- | --------------------- | ------------------- |\n| 2021.0.1.0*                  | Spring Cloud 2021.0.1 | 2.6.3               |\n| 2021.1                       | Spring Cloud 2020.0.1 | 2.4.2               |\n\n### 2.2.x ֧\n\n| Spring Cloud Alibaba Version      | Spring Cloud Version        | Spring Boot Version |\n| --------------------------------- | --------------------------- | ------------------- |\n| 2.2.8.RELEASE*                    | Spring Cloud Hoxton.SR12    | 2.3.12.RELEASE      |\n| 2.2.7.RELEASE                     | Spring Cloud Hoxton.SR12    | 2.3.12.RELEASE      |\n| 2.2.6.RELEASE                     | Spring Cloud Hoxton.SR9     | 2.3.2.RELEASE       |\n| 2.1.4.RELEASE                     | Spring Cloud Greenwich.SR6  | 2.1.13.RELEASE      |\n| 2.2.1.RELEASE                     | Spring Cloud Hoxton.SR3     | 2.2.5.RELEASE       |\n| 2.2.0.RELEASE                     | Spring Cloud Hoxton.RELEASE | 2.2.X.RELEASE       |\n| 2.1.2.RELEASE                     | Spring Cloud Greenwich      | 2.1.X.RELEASE       |\n| 2.0.4.RELEASE(ֹͣά) | Spring Cloud Finchley       | 2.0.X.RELEASE       |\n| 1.5.1.RELEASE(ֹͣά) | Spring Cloud Edgware        | 1.5.X.RELEASE       |\n\n### 汾ϵ\n\n| Spring Cloud Alibaba Version                              | Sentinel Version | Nacos Version | RocketMQ Version | Dubbo Version | Seata Version |\n| --------------------------------------------------------- | ---------------- | ------------- | ---------------- | ------------- | ------------- |\n| 2.2.8.RELEASE                                             | 1.8.4            | 2.1.0         | 4.9.3            | ~             | 1.5.1         |\n| 2021.0.1.0                                                | 1.8.3            | 1.4.2         | 4.9.2            | ~             | 1.4.2         |\n| 2.2.7.RELEASE                                             | 1.8.1            | 2.0.3         | 4.6.1            | 2.7.13        | 1.3.0         |\n| 2.2.6.RELEASE                                             | 1.8.1            | 1.4.2         | 4.4.0            | 2.7.8         | 1.3.0         |\n| 2021.1 or 2.2.5.RELEASE or 2.1.4.RELEASE or 2.0.4.RELEASE | 1.8.0            | 1.4.1         | 4.4.0            | 2.7.8         | 1.3.0         |\n| 2.2.3.RELEASE or 2.1.3.RELEASE or 2.0.3.RELEASE           | 1.8.0            | 1.3.3         | 4.4.0            | 2.7.8         | 1.3.0         |\n| 2.2.1.RELEASE or 2.1.2.RELEASE or 2.0.2.RELEASE           | 1.7.1            | 1.2.1         | 4.4.0            | 2.7.6         | 1.2.0         |\n| 2.2.0.RELEASE                                             | 1.7.1            | 1.1.4         | 4.4.0            | 2.7.4.1       | 1.0.0         |\n| 2.1.1.RELEASE or 2.0.1.RELEASE or 1.5.1.RELEASE           | 1.7.0            | 1.1.4         | 4.4.0            | 2.7.3         | 0.9.0         |\n| 2.1.0.RELEASE or 2.0.0.RELEASE or 1.5.0.RELEASE           | 1.6.3            | 1.1.1         | 4.4.0            | 2.7.3         | 0.7.1         |\n\n# ܽ\n\n\n\nľǴȫһЩSpring Cloud AlibabaһЩܹ˼룬ûдϸģϸڿԲοSpring Cloud AlibabaϵС\n\n\n# ο\nhttps://lijunyi.xyz/docs/SpringCloud/SpringCloud.html#_2-2-x-%E5%88%86%E6%94%AF\nhttps://mp.weixin.qq.com/s/2jeovmj77O9Ux96v3A0NtA\nhttps://juejin.cn/post/6931922457741770760\nhttps://github.com/D2C-Cai/herring\nhttp://c.biancheng.net/springcloud"
  },
  {
    "path": "docs/Spring全家桶/SpringCloudAlibaba源码分析/SpringCloudAlibabaNacos源码分析：服务发现.md",
    "content": "# һNacosͼ\n\n![ͼƬ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/b5b4ef0330dc4882b0fc2f73994face7.png \"ͼƬ\")\n\nԼһ̣ҲԲο[NacosעԴͼ](https://blog.csdn.net/Saintmm/article/details/121981184)\n\n# Դ\n\nspring-cloud-commonsжһ׷ֵĹ淶߼`DiscoveryClient`ӿУ\n![ͼƬ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/75ebfd400023456faaafd95e7d9cdbf7.png \"ͼƬ\")\nSpring Cloudʵֵַʵ`DiscoveryClient`ӿڣnacos-discoveryµ`NacosDiscoveryClient`ʵ`DiscoveryClient`ӿڡ\n![ͼƬ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/0c330a7ce5c744c4a2b25dc024b2430f.png \"ͼƬ\")\n\n# ͻ˷\n\n> 1nacosͻ?ֻ֮ȥעᡢûȡȲȥϢ\n> 2һʱ򣬲Żȥȡ񣬼`ػ`\n\n#### 1ȴӱػserviceInfoMapлȡʵϢȡͨ`NamingProxy`Nacos ˻ȡʵϢʱÿ ȡʵϢб±ػserviceInfoMap\n\n```\n// NacosDiscoveryClient#getInstances()\npublic List<ServiceInstance> getInstances(String serviceId) {\n    try {\n        // ͨNacosNamingServiceȡӦʵϢȥ\n        List<Instance> instances = discoveryProperties.namingServiceInstance()\n                .selectInstances(serviceId, true);\n        return hostToServiceInstanceList(instances, serviceId);\n    } catch (Exception e) {\n        throw new RuntimeException(\n                \"Can not get hosts from nacos server. serviceId: \" + serviceId, e);\n    }\n}\n\n// NacosNamingService#selectInstances()\npublic List<Instance> selectInstances(String serviceName, boolean healthy) throws NacosException {\n    return selectInstances(serviceName, new ArrayList<String>(), healthy);\n}\npublic List<Instance> selectInstances(String serviceName, List<String> clusters, boolean healthy)\n    throws NacosException {\n    // Ĭ߶ģʽ\n    return selectInstances(serviceName, clusters, healthy, true);\n}\npublic List<Instance> selectInstances(String serviceName, List<String> clusters, boolean healthy,\n                                      boolean subscribe) throws NacosException {\n    // ĬϲѯDEFAULT_GROUPµķʵϢ\n    return selectInstances(serviceName, Constants.DEFAULT_GROUP, clusters, healthy, subscribe);\n}\npublic List<Instance> selectInstances(String serviceName, String groupName, List<String> clusters, boolean healthy, boolean subscribe) throws NacosException {\n\n    ServiceInfo serviceInfo;\n    // Ĭ߶ģʽsubscribeΪTRUE\n    if (subscribe) {\n        serviceInfo = hostReactor.getServiceInfo(NamingUtils.getGroupedName(serviceName, groupName), StringUtils.join(clusters, \",\"));\n    } else {\n        serviceInfo = hostReactor.getServiceInfoDirectlyFromServer(NamingUtils.getGroupedName(serviceName, groupName), StringUtils.join(clusters, \",\"));\n    }\n    return selectInstances(serviceInfo, healthy);\n}\n```\n\n`HostReactor#getServiceInfo()`ȡʵϢĵط\n\n```\npublic ServiceInfo getServiceInfo(final String serviceName, final String clusters) {\n\n    NAMING_LOGGER.debug(\"failover-mode: \" + failoverReactor.isFailoverSwitch());\n    String key = ServiceInfo.getKey(serviceName, clusters);\n    if (failoverReactor.isFailoverSwitch()) {\n        return failoverReactor.getService(key);\n    }\n\n    // 1ӱػserviceInfoMapлȡʵϢ\n    ServiceInfo serviceObj = getServiceInfo0(serviceName, clusters);\n\n    // 2ػûУHTTPôNacos˻ȡ\n    if (null == serviceObj) {\n        serviceObj = new ServiceInfo(serviceName, clusters);\n\n        serviceInfoMap.put(serviceObj.getKey(), serviceObj);\n\n        updatingMap.put(serviceName, new Object());\n        updateServiceNow(serviceName, clusters);\n        updatingMap.remove(serviceName);\n\n    } else if (updatingMap.containsKey(serviceName)) {\n\n        if (UPDATE_HOLD_INTERVAL > 0) {\n            // hold a moment waiting for update finish\n            synchronized (serviceObj) {\n                try {\n                    serviceObj.wait(UPDATE_HOLD_INTERVAL);\n                } catch (InterruptedException e) {\n                    NAMING_LOGGER.error(\"[getServiceInfo] serviceName:\" + serviceName + \", clusters:\" + clusters, e);\n                }\n            }\n        }\n    }\n\n    // 3һʱÿһNacos˻ȡµķʵϢµػseriveInfoMap\n    scheduleUpdateIfAbsent(serviceName, clusters);\n\n    // 4 ӱػserviceInfoMapлȡʵϢ\n    return serviceInfoMap.get(serviceObj.getKey());\n}\n```\n\n1ӱػлȡʵϢ\n\n```\nprivate ServiceInfo getServiceInfo0(String serviceName, String clusters) {\n\n    String key = ServiceInfo.getKey(serviceName, clusters);\n\n    return serviceInfoMap.get(key);\n}\n```\n\n2HTTPôNacos˻ȡʵϢ\n\n```\npublic void updateServiceNow(String serviceName, String clusters) {\n    ServiceInfo oldService = getServiceInfo0(serviceName, clusters);\n    try {\n\n        // ͨNamingProxyHTTPӿڵãȡʵϢ\n        String result = serverProxy.queryList(serviceName, clusters, pushReceiver.getUDPPort(), false);\n        if (StringUtils.isNotEmpty(result)) {\n            // ±ػserviceInfoMap\n            processServiceJSON(result);\n        }\n    } catch (Exception e) {\n        NAMING_LOGGER.error(\"[NA] failed to update serviceName: \" + serviceName, e);\n    } finally {\n        if (oldService != null) {\n            synchronized (oldService) {\n                oldService.notifyAll();\n            }\n        }\n    }\n}\n```\n\n3һʱÿһNacos˻ȡµķʵϢµػseriveInfoMapУ\n\n```\npublic void scheduleUpdateIfAbsent(String serviceName, String clusters) {\n    if (futureMap.get(ServiceInfo.getKey(serviceName, clusters)) != null) {\n        return;\n    }\n\n    synchronized (futureMap) {\n        if (futureMap.get(ServiceInfo.getKey(serviceName, clusters)) != null) {\n            return;\n        }\n\n        // ʱ\n        ScheduledFuture<?> future = addTask(new UpdateTask(serviceName, clusters));\n        futureMap.put(ServiceInfo.getKey(serviceName, clusters), future);\n    }\n}\n\n// ʱִ߼UpdateTask#run()\npublic void run() {\n    try {\n        ServiceInfo serviceObj = serviceInfoMap.get(ServiceInfo.getKey(serviceName, clusters));\n\n        if (serviceObj == null) {\n            updateServiceNow(serviceName, clusters);\n            executor.schedule(this, DEFAULT_DELAY, TimeUnit.MILLISECONDS);\n            return;\n        }\n\n        if (serviceObj.getLastRefTime() <= lastRefTime) {\n            updateServiceNow(serviceName, clusters);\n            serviceObj = serviceInfoMap.get(ServiceInfo.getKey(serviceName, clusters));\n        } else {\n            // if serviceName already updated by push, we should not override it\n            // since the push data may be different from pull through force push\n            refreshOnly(serviceName, clusters);\n        }\n\n        // һʱ1sִ֮\n        executor.schedule(this, serviceObj.getCacheMillis(), TimeUnit.MILLISECONDS);\n\n        lastRefTime = serviceObj.getLastRefTime();\n    } catch (Throwable e) {\n        NAMING_LOGGER.warn(\"[NA] failed to update serviceName: \" + serviceName, e);\n    }\n\n}\n```\n\nѯʵб\n\n```\npublic String queryList(String serviceName, String clusters, int udpPort, boolean healthyOnly)\n    throws NacosException {\n\n    final Map<String, String> params = new HashMap<String, String>(8);\n    params.put(CommonParams.NAMESPACE_ID, namespaceId);\n    params.put(CommonParams.SERVICE_NAME, serviceName);\n    params.put(\"clusters\", clusters);\n    params.put(\"udpPort\", String.valueOf(udpPort));\n    params.put(\"clientIP\", NetUtils.localIP());\n    params.put(\"healthyOnly\", String.valueOf(healthyOnly));\n\n    return reqAPI(UtilAndComs.NACOS_URL_BASE + \"/instance/list\", params, HttpMethod.GET);\n}\n```\n\n#### 2HostReactorʵʱʵPushReceiverһ߳ѭͨ`DatagramSocket#receive()`NacosзʵϢUDP֪ͨ\n\n```\npublic class PushReceiver implements Runnable {\n    private DatagramSocket udpSocket;\n\n    public PushReceiver(HostReactor hostReactor) {\n        try {\n            this.hostReactor = hostReactor;\n            udpSocket = new DatagramSocket();\n            // һ߳\n            executorService = new ScheduledThreadPoolExecutor(1, new ThreadFactory() {\n                @Override\n                public Thread newThread(Runnable r) {\n                    Thread thread = new Thread(r);\n                    thread.setDaemon(true);\n                    thread.setName(\"com.alibaba.nacos.naming.push.receiver\");\n                    return thread;\n                }\n            });\n\n            executorService.execute(this);\n        } catch (Exception e) {\n            NAMING_LOGGER.error(\"[NA] init udp socket failed\", e);\n        }\n    }\n\n    public void run() {\n        while (true) {\n            try {\n                // byte[] is initialized with 0 full filled by default\n                byte[] buffer = new byte[UDP_MSS];\n                DatagramPacket packet = new DatagramPacket(buffer, buffer.length);\n\n                // Nacos˷ʵϢ֪ͨ\n                udpSocket.receive(packet);\n\n                String json = new String(IoUtils.tryDecompress(packet.getData()), \"UTF-8\").trim();\n                NAMING_LOGGER.info(\"received push data: \" + json + \" from \" + packet.getAddress().toString());\n\n                PushPacket pushPacket = JSON.parseObject(json, PushPacket.class);\n                String ack;\n                if (\"dom\".equals(pushPacket.type) || \"service\".equals(pushPacket.type)) {\n                    hostReactor.processServiceJSON(pushPacket.data);\n\n                    // send ack to server\n                    ack = \"{\\\"type\\\": \\\"push-ack\\\"\"\n                        + \", \\\"lastRefTime\\\":\\\"\" + pushPacket.lastRefTime\n                        + \"\\\", \\\"data\\\":\" + \"\\\"\\\"}\";\n                } else if (\"dump\".equals(pushPacket.type)) {\n                    // dump data to server\n                    ack = \"{\\\"type\\\": \\\"dump-ack\\\"\"\n                        + \", \\\"lastRefTime\\\": \\\"\" + pushPacket.lastRefTime\n                        + \"\\\", \\\"data\\\":\" + \"\\\"\"\n                        + StringUtils.escapeJavaScript(JSON.toJSONString(hostReactor.getServiceInfoMap()))\n                        + \"\\\"}\";\n                } else {\n                    // do nothing send ack only\n                    ack = \"{\\\"type\\\": \\\"unknown-ack\\\"\"\n                        + \", \\\"lastRefTime\\\":\\\"\" + pushPacket.lastRefTime\n                        + \"\\\", \\\"data\\\":\" + \"\\\"\\\"}\";\n                }\n\n                udpSocket.send(new DatagramPacket(ack.getBytes(Charset.forName(\"UTF-8\")),\n                    ack.getBytes(Charset.forName(\"UTF-8\")).length, packet.getSocketAddress()));\n            } catch (Exception e) {\n                NAMING_LOGGER.error(\"[NA] error while receiving push data\", e);\n            }\n        }\n    }\n\n}\n```\n\n# ġ˷\n\nNacos˵ķҪ£\n\n> 1ѯʵбȴӻserviceMapҵserviceӦClusterٴClusterSet`persistentInstances``ephemeralInstances`ȡȫʵϢ\n> 2ͻ˴ipudp˿ںżӵ`clientMap`ͣclientMap`NamingSubscriberService`ʵ`NamingSubscriberServiceV1Impl`keyservice namevalueǶ˸÷Ŀͻб(ip+˿ں)\n\nnamingĿµ InstanceControllerlist()\n\n#### 1ȡʵб\n\n```\n@GetMapping(\"/list\")\n@Secured(parser = NamingResourceParser.class, action = ActionTypes.READ)\npublic Object list(HttpServletRequest request) throws Exception {\n\n    String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);\n    String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);\n    NamingUtils.checkServiceNameFormat(serviceName);\n\n    String agent = WebUtils.getUserAgent(request);\n    String clusters = WebUtils.optional(request, \"clusters\", StringUtils.EMPTY);\n    String clientIP = WebUtils.optional(request, \"clientIP\", StringUtils.EMPTY);\n    int udpPort = Integer.parseInt(WebUtils.optional(request, \"udpPort\", \"0\"));\n    boolean healthyOnly = Boolean.parseBoolean(WebUtils.optional(request, \"healthyOnly\", \"false\"));\n\n    boolean isCheck = Boolean.parseBoolean(WebUtils.optional(request, \"isCheck\", \"false\"));\n\n    String app = WebUtils.optional(request, \"app\", StringUtils.EMPTY);\n    String env = WebUtils.optional(request, \"env\", StringUtils.EMPTY);\n    String tenant = WebUtils.optional(request, \"tid\", StringUtils.EMPTY);\n\n    Subscriber subscriber = new Subscriber(clientIP + \":\" + udpPort, agent, app, clientIP, namespaceId, serviceName,\n            udpPort, clusters);\n    // ȥInstanceOperatorServiceImpl#listInstance()ȡʵб\n    return getInstanceOperator().listInstance(namespaceId, serviceName, subscriber, clusters, healthyOnly);\n}\n\n//InstanceOperatorServiceImpl#listInstance()\npublic ServiceInfo listInstance(String namespaceId, String serviceName, Subscriber subscriber, String cluster,\n            boolean healthOnly) throws Exception {\n        ClientInfo clientInfo = new ClientInfo(subscriber.getAgent());\n        String clientIP = subscriber.getIp();\n        ServiceInfo result = new ServiceInfo(serviceName, cluster);\n        Service service = serviceManager.getService(namespaceId, serviceName);\n        long cacheMillis = switchDomain.getDefaultCacheMillis();\n\n        // now try to enable the push\n        try {\n            // ͷUdpPushServiceʵϢʱͨUDPķʽ֪ͨNacos Client\n            if (subscriber.getPort() > 0 && pushService.canEnablePush(subscriber.getAgent())) {\n                subscriberServiceV1.addClient(namespaceId, serviceName, cluster, subscriber.getAgent(),\n                        new InetSocketAddress(clientIP, subscriber.getPort()), pushDataSource, StringUtils.EMPTY,\n                        StringUtils.EMPTY);\n                cacheMillis = switchDomain.getPushCacheMillis(serviceName);\n            }\n        } catch (Exception e) {\n            Loggers.SRV_LOG.error(\"[NACOS-API] failed to added push client {}, {}:{}\", clientInfo, clientIP,\n                    subscriber.getPort(), e);\n            cacheMillis = switchDomain.getDefaultCacheMillis();\n        }\n\n        if (service == null) {\n            if (Loggers.SRV_LOG.isDebugEnabled()) {\n                Loggers.SRV_LOG.debug(\"no instance to serve for service: {}\", serviceName);\n            }\n            result.setCacheMillis(cacheMillis);\n            return result;\n        }\n\n        // Ƿ\n        checkIfDisabled(service);\n\n        // ǻȡעϢĹؼ룬ȡúʱʵ\n        List<com.alibaba.nacos.naming.core.Instance> srvedIps = service\n                .srvIPs(Arrays.asList(StringUtils.split(cluster, StringUtils.COMMA)));\n\n        // filter ips using selectorѡ˷\n        if (service.getSelector() != null && StringUtils.isNotBlank(clientIP)) {\n            srvedIps = selectorManager.select(service.getSelector(), clientIP, srvedIps);\n        }\n\n        // Ҳ򷵻صǰ\n        if (CollectionUtils.isEmpty(srvedIps)) {\n        .......\n        return result;\n    }\n\n// Service#srvIPs()\npublic List<Instance> srvIPs(List<String> clusters) {\n    if (CollectionUtils.isEmpty(clusters)) {\n        clusters = new ArrayList<>();\n        clusters.addAll(clusterMap.keySet());\n    }\n    return allIPs(clusters);\n}\n\n// Service#allIPs()\npublic List<Instance> allIPs(List<String> clusters) {\n    List<Instance> result = new ArrayList<>();\n    for (String cluster : clusters) {\n        // עʱ򣬻ὫʵϢдclusterMapУڴȡ\n        Cluster clusterObj = clusterMap.get(cluster);\n        if (clusterObj == null) {\n            continue;\n        }\n\n        result.addAll(clusterObj.allIPs());\n    }\n    return result;\n}\n\n// Cluster#allIPs()\npublic List<Instance> allIPs() {\n    List<Instance> allInstances = new ArrayList<>();\n    // ȡеĳ־ûʵ\n    allInstances.addAll(persistentInstances);\n    // ȡеʱʵ\n    allInstances.addAll(ephemeralInstances);\n    return allInstances;\n}\n```\n\n#### 2UDPʽʵ\n\nNamingSubscriberServiceV1Impl#addClient()\n\n```\npublic void addClient(String namespaceId, String serviceName, String clusters, String agent,\n        InetSocketAddress socketAddr, DataSource dataSource, String tenant, String app) {\n\n    // ʼͿͻʵPushClient\n    PushClient client = new PushClient(namespaceId, serviceName, clusters, agent, socketAddr, dataSource, tenant,\n            app);\n    // Ŀͻ\n    addClient(client);\n}\n\n// طaddClient()\npublic void addClient(PushClient client) {\n    // client is stored by key 'serviceName' because notify event is driven by serviceName change\n    // ͻɼ serviceName洢Ϊ֪ͨ¼serviceName\n    String serviceKey = UtilsAndCommons.assembleFullServiceName(client.getNamespaceId(), client.getServiceName());\n    ConcurrentMap<String, PushClient> clients = clientMap.get(serviceKey);\n    // ȡͻõServiceNameӦͿͻˣ½Ϳͻˣ\n    if (clients == null) {\n        clientMap.putIfAbsent(serviceKey, new ConcurrentHashMap<>(1024));\n        clients = clientMap.get(serviceKey);\n    }\n\n    PushClient oldClient = clients.get(client.toString());\n    // ϵPushClientˢ\n    if (oldClient != null) {\n        oldClient.refresh();\n    } else {\n        // 򻺴PushClient\n        PushClient res = clients.putIfAbsent(client.toString(), client);\n        if (res != null) {\n            Loggers.PUSH.warn(\"client: {} already associated with key {}\", res.getAddrStr(), res);\n        }\n        Loggers.PUSH.debug(\"client: {} added for serviceName: {}\", client.getAddrStr(), client.getServiceName());\n    }\n}\n```\n\n# 塢ܽ\n\nͻˣ\n\n> 1ȴӱػлȡʵϢ\n> 2άʱʱNacos˻ȡʵϢ\n\nˣ\n\n> 1ָռڴעеʵʱʵͻˣ\n> 2һUDPʵϢͷ\n\n\n# ο\nhttps://developer.aliyun.com/article/1058262\nhttps://ost.51cto.com/posts/14835\nhttps://developer.aliyun.com/article/1048465\nhttps://zhuanlan.zhihu.com/p/70478036\nhttps://juejin.cn/post/6999814668390760484#heading-8"
  },
  {
    "path": "docs/Spring全家桶/SpringCloudAlibaba源码分析/SpringCloudAlibabaNacos源码分析：服务注册.md",
    "content": "# һ NacosעԴ\n\n\n\n* * *\n\n\n\n## 1.1  Դ뷽ʽ\n\n\n\n* * *\n\n\n\nͻԴӴʽԴ\n\n\n\n\n\n\n\n```<plugin>  <groupId>org.apache.maven.plugins</groupId>  maven-source-plugin  <version>3.2.1</version>  <configuration>  true  </configuration>  <executions>  <execution>  <phase>compile</phase>  <goals>  <goal>jar</goal>  </goals>  </execution>  </executions> </plugin> ```\n\n\n\n\n\n\n\nȻ\n\n\n\n\n\n\n\n```mvn install -DskipTests ```\n\n\n\n\n\n\n\n## 1.2 \n\n\n\n* * *\n\n\n\n[github.com/alibaba/nac](https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Falibaba%2Fnacos%2Ftree%2F1.4.1)\n\nǻԴǻͨԴķʽ ͨdebugķʽжй̡\n\nǴԴĽǶһ£ϱעģ\n\nNacosNamingService Ƿעͷص࣬ｫǰķעʵķǿһʲôˣ\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/fc004433c7304147905c088dd3227005.png \"image.png\")\n\n![image-20211221124947544](E:\\BaiduNetdiskWorkspace\\springcloud alibaba\\img\\image-20211221124947544.png)\n\nƴһЩhttp󣬵עĽз֣ľ·\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/51f5b45b70ef4a7dbe1258e05314fa61.png \"image.png\")\n\nǶӦ·ǻصٷĵָϵ\n\n[nacos.io/zh-cn/docs/](https://link.juejin.cn?target=https%3A%2F%2Fnacos.io%2Fzh-cn%2Fdocs%2Fopen-api.html)\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/d3b30ba715ab4363b3ee8b4e21a2f3a2.png \"image.png\")\n\nãǽͲ濴ˣǿԵȥһ£\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/9f4bbf69cc1746e79545831252796e55.png \"image.png\")\n\nĵø\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/b06c94937d374ddb9fc2c224e5932205.png \"image.png\")\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/d0d55d3616ad4109837d9aee36e5a946.png \"image.png\")\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/450565c4c14f4f3bb2097ab1ea69aae7.png \"image.png\")\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/527d2af9bb0c4206b090d3410238a576.png \"image.png\")\n\nе\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/3f3f39de5aaa4ad2b3ac7dccc33aa574.png \"image.png\")\n\nͬѧô֪ϵʹﵽǿһ鿴Դ·ǿһǶ΢·ҪnacosķֹܣҪǵdiscoveryİһstarterǰѧspringboot֪κstarterһиspring.factoriesΪһ\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/be47c62f16f94507a1af84d0169ebf53.png \"image.png\")\n\n涯̬صܶ࣬NacosServiceRegistryAutoConfiguration ܷһnacosעԶ࣬\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/84017587439846a4b21a696f12775b6c.png \"image.png\")\n\nʵ࣬ǿһNacosAUtoServiceRegistration\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/fe7ee87e88944b689835432c833972c2.png \"image.png\")\n\nԶע࣬ǿԿһļɹϵһApplicationListener  springɺ󶼻ᷢһϢapplicaitonListenerͨϢȻִеġ֪һӦô\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/2a12ba62dae24aa092f97b0cff5dfaaa.png \"image.png\")\n\nԲ鿴ĳࡣ\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1f9253dbbbd349ffb6d0e60d0761b3ec.png \"image.png\")\n\n鿴onApplicationEventڷͻᷢһϢյϢͻbind\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/41a7323a2e2c49d19aaf5768e66af7d8.png \"image.png\")\n\nиif return ǾֱһǷ֧룬ķ֧ǾͲҪһ ҪߣֱӿstartûжӦĴ߼ǿԽ֧ ãstart, begininitregisterǺҪķһҪȥ\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/3923ffd589e9488eb1143811a50d28c0.png \"image.png\")\n\nһifͲÿڶifҪΪû߼㿴register()ӦþΪǲ鿴ע̡\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/d5b527e0900a4f1b9abcd76db4bfc138.png \"image.png\")\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/d3501fdf6479417180e881c38638bdac.png \"image.png\")\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7c0ea798fcd7404d8f350047e947add1.png \"image.png\")\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/0ca3ab1a98f248a0aa3854730eba85fb.png \"image.png\")\n\nҪ֪SpringBootԶװĻ֪ʶҪ֪Springֵ ֪ʶ\n\n## 1.3 ע\n\n\n\n* * *\n\n\n\nNacos߲֧첽ڴ\n\nղڷṩ潲ݣǷעһ\n\ninstanceʵһspringmvccontrollerǿȫ ControllerinstanceʵɣInstanceController\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/4e02f16787a84704add5aa41f9d7662a.png \"image.png\")\n\n֮ǰõpostǲ鿴postdelete,update...,ʲô restFul\n\nǷûжӦDefaultGroupڷעͷֵgroupǲõġõĻֻԼĹ淶ͷġڷעͷԴжûáռ䣬ȻǲתΪʵǷģеģ͡\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/09f059ba31a7444a88a24b03399d502d.png \"image.png\")\n\nǿһעʵʲô עעinstanceǾΧзǲǾaddInstance\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/4b69913abe6f40288a08dee89ecb0772.png \"image.png\")\n\ncreateEmptyService\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/b8a3063b3c924ee195af64d5d8efa647.png \"image.png\")\n\n1ȡservice λȡһΪգǿԽȥһ\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/fdf453ebadee486c9def86560bc3b8de.png \"image.png\")\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7025fb4edcc247b3aca0f1f851d26203.png \"image.png\")\n\nעǰ˵nacosģ͡ԲοͼmapǶӦע\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/4f1db03d3fcc4f93aaa17cc485fb59c2.png \"image.png\")\n\n÷ͳʼ\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1af3a9015b2c4e1ab06da283e4dc5332.png \"image.png\")\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/cb795dd33b114f84bc51cdbdca5af202.png \"image.png\")\n\nʼ\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/0d0583abf0184ed49bf5fd93c9effafe.png \"image.png\")\n\nǸscheduleNamingHealthһʱֻҪһtaskͿ\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/51ba8120a9954389b9e2f44d49532a14.png \"image.png\")\n\ntaskҪһrun\n\nǿǻȡеʵԵȥһ¡\n\nǰʱ -  ϴʱ 15 ʵΪǽ 30ûյֱ޳\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/f9131eea860e410685d8c4cd5beea69e.png \"image.png\")\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/586fd2fcb489414590295ce2dfafd91d.png \"image.png\")\n\nãǻرãcreateEmtyServiceǴһշ񣬺ǵʵǲǻעᵽ棬\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/9518406856b642298d3ee2f16e36bed0.png \"image.png\")\n\n> ǿԿһ·ģͣǰ˵һ\n>\n> ռ cluster Ⱥ\n>\n> ![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/0323827c8249417b8ab3fd6c4bd3e61e.png \"image.png\")\n>\n> ![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/044d8f58dc5b4c7e83dab05c62397566.png \"image.png\")\n>\n> ȺжӦʵ\n>\n> ![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/da6adcbf25f44992b08f919988f81b62.png \"image.png\")\n\nǿһaddinstance\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/cb9d4cb8fd4d4f0aa75e5d95c4923aae.png \"image.png\")\n\nӦkey:\n\n\n\n\n\n\n\n```String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, ephemeral); ```\n\n\n\n\n\n\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/6ab491f925e3403fa1377c2649d4721d.png \"image.png\")\n\n\n\n\n\n\n\n```//ȡעʵIP˿б List<Instance> instanceList = addIpAddresses(service, ephemeral, ips); ```\n\n\n\n\n\n\n\nǽ򵥵Ŀһ£Ƿadd,removeƳʵ\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/bebd689b54d04e0dab0e64ac552e965e.png \"image.png\")\n\nҪдעipsǾ͵ipsʾȻѭinstanceǿԿƳʹmapƳȥinstanceMapһ£ڷء\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/4d797c90012b4c22ac6e999bdfad8d63.png \"image.png\")\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/158d309e86084a76be392ac8b67ccb94.png \"image.png\")\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/04b51596154e4d4b933a39f0eb249587.png \"image.png\")\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/86453757688743df89df30752bbbe0e5.png \"image.png\")\n\n࣬ǿԲ²һ£debugȥȻ࣬ǵһ£ĵط\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/f7a189074e00495da10119f2a5de7bc5.png \"image.png\")\n\nָƣ\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/c54ca72e12e04d80b189d587a535d834.png \"image.png\")\n\nãȫһ£\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/2b24cf84302c42669bed76d19a11e301.png \"image.png\")\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/fcf6ffab779548a694a68c443a90a214.png \"image.png\")\n\n֪ǰ˵ephemeraltrueѡһ ʣ\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/4adb989d26cf46f98c943a114e047679.png \"image.png\")\n\nӦõEphemeralConsistencyServiceӦputEphemeralConsistencyServiceֻһӿڣӦõöӦʵʵ\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/87869280d709441fbb8ceec818d67a97.png \"image.png\")\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/3764ecf8f5904315af85482f3c3ee71d.png \"image.png\")\n\nǿһonput\n\nڷŵУ\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/02a34386b7444f0e8a6e35e57ceedf84.png \"image.png\")\n\nǰѺĵŵblockquene棬Ҳһ\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/81b1c141753c492493eb095ead9e34da.png \"image.png\")\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/252863f614a24c2da64713af52669ce5.png \"image.png\")\n\nעĹ̾ô򵥣һսע󣬷ŵǵеУŵɫ֮оͷˡǷŵ֮зС\n\nעNotifierһ̣߳ʦһɣһ߳̾ҪrunΪrun ִдĵط\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/659d96fac1d445859a6ea6086ab8277c.png \"image.png\")\n\nѭݴϵĴͻעϢʵֺ첽עϢ\n\n̻߳һֱתһֱУ˵͹ҵˣã㿴쳣ҲԵˣһֱУûócpu\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/487a2e6d15f642549fe0d145447cc5cf.png \"image.png\")\n\nעṹķ\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/3eab037db7e2480ba4a9b439b18567da.png \"image.png\")\n\nȵǸchangeʱǾͽonChangeǸǽserviceȥ\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/4adf69d76188430d860f2003b6c508d6.png \"image.png\")\n\nͲÿˣȿȨأȨشڶٵʱֵȨСڶٵʱһСֵȻǺĵķupdateIP\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/887332bfdcb64c2f9ad871ee55961b59.png \"image.png\")\n\nupdateIPsʲôأ ľұҪעʵȻͷŵǵclusterMap\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/87df4508357847f1ab49a785ab464e5b.png \"image.png\")\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/b31cf0150533469ebd4fbc462c6a0952.png \"image.png\")\n\nǵҿܾˣʲôʱ̣߳ʵʱϢأ ̴һڿŸNotifierΪһ̣߳ᶪһ̳߳нУǿһʵģ\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/16223a5f9aab4fdaa004d0698e6cb0a6.png \"image.png\")\n\nǿעǵspringһгʼ֮еõģǿһinitʲô\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/b9d2a25274834d73ab8f939006e9b075.png \"image.png\")\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/64f25704efac43a7ae6ee5afda46cdf7.png \"image.png\")\n\nһScheduled̳߳أ\n\n![image-20211222222903941](E:\\BaiduNetdiskWorkspace\\springcloud alibaba\\img\\image-20211222222903941.png)\n\nҲڶʼʱͽһ̳߳أȥnotifierӦķrunrunġͻʵʱ첽СдĺôǽдʹȫˡͨܵڴУ飬ĺô1\n\n![image.png](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/e27c8e72b30845c3ab9346b89932fb42.png \"image.png\")\n\n# ο\nhttps://lijunyi.xyz/docs/SpringCloud/SpringCloud.html#_2-2-x-%E5%88%86%E6%94%AF\nhttps://mp.weixin.qq.com/s/2jeovmj77O9Ux96v3A0NtA\nhttps://juejin.cn/post/6931922457741770760\nhttps://github.com/D2C-Cai/herring\nhttp://c.biancheng.net/springcloud\nhttps://github.com/macrozheng/springcloud-learning"
  },
  {
    "path": "docs/Spring全家桶/SpringCloudAlibaba源码分析/SpringCloudAlibabaNacos源码分析：概览.md",
    "content": "ڿƪ֮ǰöNACOSع˽⣬Ƽ[Spring Cloud Alibaba Nacosƪ](https://zhuanlan.zhihu.com/p/68700978)\n\nԹܣĿĵȥӦԴ룬һ˽⹦αʵֳġ\n\nһԴĶȺ̫ϸڣҪߴԴ٣ᡣ\n\n## һ\n\nGitHubӦ[ҳ](https://link.zhihu.com/?target=https%3A//github.com/alibaba/nacos)NACOScloneĿ¼ļ߳ǶڿԴаĲֲࡣ\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-e9966f158af7cfac39baf5bba456fd17_720w.webp)\n\n<figcaption>nacosĿ¼ṹ</figcaption>\n\n</figure>\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-9195cfddde4e94b16f239bc101825a5a_720w.webp)\n\n<figcaption>ģͼ</figcaption>\n\n</figure>\n\n<figure data-size=\"normal\">\n\n\n![](https://pic4.zhimg.com/80/v2-6b30d0fc994745002ee7dcc7b04154d3_720w.webp)\n\n<figcaption>nacosģ</figcaption>\n\n</figure>\n\nͼ˳ҵͻƿˣݾͼnacos-consolenacos-namingnacos-config˳ϣܿˡ\n\nǸо޴ֵĻǾƲnacos-exampleҪҵĵڣһ֪\n\n## ÷\n\nȴһ˵com.alibaba.nacos.api.NacosFactory\n\nľ̬ڴConfigServiceNamingServiceƣԴConfigServiceΪ\n\n\n\n```\npublic static ConfigService createConfigService(Properties properties) throws NacosException {\n        try {\n            Class<?> driverImplClass = Class.forName(\"com.alibaba.nacos.client.config.NacosConfigService\");\n            Constructor constructor = driverImplClass.getConstructor(Properties.class);\n            ConfigService vendorImpl = (ConfigService) constructor.newInstance(properties);\n            return vendorImpl;\n        } catch (Throwable e) {\n            throw new NacosException(-400, e.getMessage());\n        }\n}\n```\n\n\n\nûʲôӵ߼ʹõǻķԭpropertiesЩԿͨbootstrap.ymlָӦNacosConfigProperties\n\nҪϸǹ캯жnamespaceʼǲݡ\n\n\n\n```\nprivate void initNamespace(Properties properties) {\n        String namespaceTmp = null;\n\n        String isUseCloudNamespaceParsing =\n            properties.getProperty(PropertyKeyConst.IS_USE_CLOUD_NAMESPACE_PARSING,\n                System.getProperty(SystemPropertyKeyConst.IS_USE_CLOUD_NAMESPACE_PARSING,\n                    String.valueOf(Constants.DEFAULT_USE_CLOUD_NAMESPACE_PARSING)));\n\n        if (Boolean.valueOf(isUseCloudNamespaceParsing)) {\n            namespaceTmp = TemplateUtils.stringBlankAndThenExecute(namespaceTmp, new Callable<String>() {\n                @Override\n                public String call() {\n                    return TenantUtil.getUserTenantForAcm();\n                }\n            });\n\n            namespaceTmp = TemplateUtils.stringBlankAndThenExecute(namespaceTmp, new Callable<String>() {\n                @Override\n                public String call() {\n                    String namespace = System.getenv(PropertyKeyConst.SystemEnv.ALIBABA_ALIWARE_NAMESPACE);\n                    return StringUtils.isNotBlank(namespace) ? namespace : EMPTY;\n                }\n            });\n        }\n\n        if (StringUtils.isBlank(namespaceTmp)) {\n            namespaceTmp = properties.getProperty(PropertyKeyConst.NAMESPACE);\n        }\n        namespace = StringUtils.isNotBlank(namespaceTmp) ? namespaceTmp.trim() : EMPTY;\n        properties.put(PropertyKeyConst.NAMESPACE, namespace);\n}\n```\n\n\n\npropertiesָǷƻеnamespaceǵģȥȡƻϵͳǣôͶȡpropertiesָnamespaceûָĻսǿַӴϿȡƻnamespace첽ʽĿǰ汾ʹõͬá\n\nConfigService涨һϵнӿڷҪġ\n\nÿҵʵնΪHttp󣬾õserverAddrַתʹãȻһʱʱ󣬶󲻳ɹˣǾͻ׳쳣\n\nnacos-clientշն䵽nacos-configϣʹJdbcTemplateݳ־û\n\nһֵĴһףãȡúɾö֣Ͳչˡ\n\nصһüֵԴ롣\n\nȽעcom.alibaba.nacos.client.config.impl.CacheDataݽṹϣǸ͵ĳѪģͣҪǳ䵱listenerߵĽɫȡòôѺˡ\n\nʵϣԿCacheDataϢnamespace, contentlistenerۺһˣΪһÿԸӶlistenerʵʩΪlistenerӿڿжʵ֣ÿlistenerֻһʵϡ\n\n\n\n```\npublic void addListener(Listener listener) {\n        if (null == listener) {\n            throw new IllegalArgumentException(\"listener is null\");\n        }\n        ManagerListenerWrap wrap = new ManagerListenerWrap(listener);\n        if (listeners.addIfAbsent(wrap)) {\n            LOGGER.info(\"[{}] [add-listener] ok, tenant={}, dataId={}, group={}, cnt={}\", name, tenant, dataId, group,\n                listeners.size());\n        }\n}\n```\n\n\n\nʹCopyOnWriteArrayList.addIfAbsentҪequalsManagerListenerWrapǶlistenerһʽİʵequals\n\n\n\n```\n@Override\npublic boolean equals(Object obj) {\n        if (null == obj || obj.getClass() != getClass()) {\n            return false;\n        }\n        if (obj == this) {\n            return true;\n        }\n        ManagerListenerWrap other = (ManagerListenerWrap) obj;\n        return listener.equals(other.listener);\n}\n```\n\n\n\nϲ㷭ҵlistener߲ĹAPIcom.alibaba.nacos.client.config.impl.ClientWorker\n\nͬǶlistenerĹظУ飬cacheMapǹؼ¶壺\n\n\n\n```\nprivate final AtomicReference<Map<String, CacheData>> cacheMap = new AtomicReference<Map<String, CacheData>>()\n```\n\n\n\nʹ˾ԭԲԵAtomicReferenceԱⲢݲһµ⣬һHashMapvalueCacheData󣬶keyһɹģGroupKeyпҵ\n\n\n\n```\nstatic public String getKeyTenant(String dataId, String group, String tenant) {\n        StringBuilder sb = new StringBuilder();\n        urlEncode(dataId, sb);\n        sb.append('+');\n        urlEncode(group, sb);\n        if (StringUtils.isNotEmpty(tenant)) {\n            sb.append('+');\n            urlEncode(tenant, sb);\n        }\n        return sb.toString();\n}\n```\n\n\n\nʵǽϢá+ŽƴӣϢбˡ+͡%ʹurlEncodeбת塣ȻҲ׵ĽͲչˡ\n\n޷ǾǾcacheMapһϵgetsetάlistenerرעǣÿθ²һcopy󣬲˶֮setǣcacheMapС\n\n˵һlistenerġ\n\nȻClientWorkerпҵעתƵ캯СУע⵽ʼ̳߳أ\n\n\n\n```\n    executor = Executors.newScheduledThreadPool(1, new ThreadFactory() {\n            @Override\n            public Thread newThread(Runnable r) {\n                Thread t = new Thread(r);\n                t.setName(\"com.alibaba.nacos.client.Worker.\" + agent.getName());\n                t.setDaemon(true);\n                return t;\n            }\n        });\n\n        executorService = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors(), new ThreadFactory() {\n            @Override\n            public Thread newThread(Runnable r) {\n                Thread t = new Thread(r);\n                t.setName(\"com.alibaba.nacos.client.Worker.longPolling.\" + agent.getName());\n                t.setDaemon(true);\n                return t;\n            }\n        });\n\n        executor.scheduleWithFixedDelay(new Runnable() {\n            @Override\n            public void run() {\n                try {\n                    checkConfigInfo();\n                } catch (Throwable e) {\n                    LOGGER.error(\"[\" + agent.getName() + \"] [sub-check] rotate check error\", e);\n                }\n            }\n       }, 1L, 10L, TimeUnit.MILLISECONDS);\n```\n\n\n\nִжʱscheduledThreadPool̳߳صķֹҲǶ׵ģexecutorڷü񣬶executorServiceĽߣִĽɫ\n\nԷֻ̳߳1ִ̳߳߳صĺ߳CPU\n\nΪüһѯḶ́һִܼҪõƣNACOSĿǰʹһȽϼ򵥵ķ\n\n\n\n```\npublic void checkConfigInfo() {\n        // \n        int listenerSize = cacheMap.get().size();\n        // ȡΪ\n        int longingTaskCount = (int) Math.ceil(listenerSize / ParamUtil.getPerTaskConfigSize());\n        if (longingTaskCount > currentLongingTaskCount) {\n            for (int i = (int) currentLongingTaskCount; i < longingTaskCount; i++) {\n                // ҪжǷִ Ҫú롣 бġ仯̿\n                executorService.execute(new LongPollingRunnable(i));\n            }\n            currentLongingTaskCount = longingTaskCount;\n        }\n}\n```\n\n\n\nParamUtil.getPerTaskConfigSize()зصÿܼޣĬ3000ͨϵͳPER_TASK_CONFIG_SIZEޡ\n\nӴϿԿǰlistenerûг3000ü̳߳ػתϸֵĴ룬ǻᷢһЩģҪΧһϵ⡣\n\nѯҪ߼\n\n*   鱾ãCacheData洢Ϣһ£\n*   serverãCacheData洢Ϣ\n\n## ע뷢\n\nĻⲿִ뿴Ƚˣṹϻơ\n\nֱӽcom.alibaba.nacos.api.naming.NamingServiceжregisterInstanceعڷעᡣ\n\nȿInstanceʵݣidipportserviceNameclusterNameڼȺweightȨأhealthyǷenabledǷãephemeralǷʱģ9ȫConsole֡\n\nȻֱӿעķ\n\n\n\n```\n   @Override\n    public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {\n\n        if (instance.isEphemeral()) {\n            BeatInfo beatInfo = new BeatInfo();\n            beatInfo.setServiceName(NamingUtils.getGroupedName(serviceName, groupName));\n            beatInfo.setIp(instance.getIp());\n            beatInfo.setPort(instance.getPort());\n            beatInfo.setCluster(instance.getClusterName());\n            beatInfo.setWeight(instance.getWeight());\n            beatInfo.setMetadata(instance.getMetadata());\n            beatInfo.setScheduled(false);\n            beatReactor.addBeatInfo(NamingUtils.getGroupedName(serviceName, groupName), beatInfo);\n        }\n        serverProxy.registerService(NamingUtils.getGroupedName(serviceName, groupName), groupName, instance);\n    }\n```\n\n\n\nǰһδǶʱʵĴڹһ͸NACOS\n\nregisterServiceǷװHTTPInstanceControllerд\n\nĿspring-cloud-starter-alibaba-nacos-discoveryĬԶעġ뿴ԶעḶ́ԴAbstractAutoServiceRegistrationʼ֣һδ룺\n\n\n\n```\n\t@EventListener(WebServerInitializedEvent.class)\n\tpublic void bind(WebServerInitializedEvent event) {\n\t\tApplicationContext context = event.getApplicationContext();\n\t\tif (context instanceof ConfigurableWebServerApplicationContext) {\n\t\t\tif (\"management\".equals(\n\t\t\t\t\t((ConfigurableWebServerApplicationContext) context).getServerNamespace())) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\tthis.port.compareAndSet(0, event.getWebServer().getPort());\n\t\tthis.start();\n\t}\n```\n\n\n\nWebʼɵ¼ջִstart\n\n\n\n```\n\tpublic void start() {\n\t\tif (!isEnabled()) {\n\t\t\tif (logger.isDebugEnabled()) {\n\t\t\t\tlogger.debug(\"Discovery Lifecycle disabled. Not starting\");\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\t// only initialize if nonSecurePort is greater than 0 and it isn't already running\n\t\t// because of containerPortInitializer below\n\t\tif (!this.running.get()) {\n\t\t\tregister();\n\t\t\tif (shouldRegisterManagement()) {\n\t\t\t\tregisterManagement();\n\t\t\t}\n\t\t\tthis.context.publishEvent(new InstanceRegisteredEvent<>(this, getConfiguration()));\n\t\t\tthis.running.compareAndSet(false, true);\n\t\t}\n\n\t}\n```\n\n\n\nУregisterĵĲˣԴNacosServiceRegistryʵ֣\n\n\n\n```\n\t@Override\n\tpublic void register(NacosRegistration registration) {\n\n\t\tif (!registration.isRegisterEnabled()) {\n\t\t\tlogger.info(\"Nacos Registration is disabled...\");\n\t\t\treturn;\n\t\t}\n\t\tif (StringUtils.isEmpty(registration.getServiceId())) {\n\t\t\tlogger.info(\"No service to register for nacos client...\");\n\t\t\treturn;\n\t\t}\n\t\tNamingService namingService = registration.getNacosNamingService();\n\t\tString serviceId = registration.getServiceId();\n\n\t\tInstance instance = new Instance();\n\t\tinstance.setIp(registration.getHost());\n\t\tinstance.setPort(registration.getPort());\n\t\tinstance.setWeight(registration.getRegisterWeight());\n\t\tinstance.setClusterName(registration.getCluster());\n\t\tinstance.setMetadata(registration.getMetadata());\n\t\ttry {\n\t\t\tnamingService.registerInstance(serviceId, instance);\n\t\t\tlogger.info(\"nacos registry, {} {}:{} register finished\", serviceId, instance.getIp(), instance.getPort());\n\t\t}catch (Exception e) {\n\t\t\tlogger.error(\"nacos registry, {} register failed...{},\", serviceId, registration.toString(), e);\n\t\t}\n\t}\n```\n\n\n\nδͷǳϤˣվͻصnamingService.registerInstance\n\n\n\n```\n    /**\n     * Map<namespace, Map<group::serviceName, Service>>\n     */\n    private Map<String, Map<String, Service>> serviceMap = new ConcurrentHashMap<>();\n```\n\n\n\nϳһʵࣺcom.alibaba.nacos.naming.core.ServiceServiceǰInstanceһServiceжInstanceһCluster\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-21ca2c51d56d6401dee0db0444df8ddf_720w.webp)\n\n<figcaption>ʵȺ</figcaption>\n\n</figure>\n\nڵregisterInstanceעʵʱֶӦServiceûбעᣬôregisterServiceһʼӦClusterĶʱ\n\nregisterInstance෴deregisterInstanceΪȡעᣬҲΪǷʵߡ\n\nNACOSʵַֹܡ\n\nߣ÷ĽǶɵstarterĿиࣺNacosServerListҪǼ̳AbstractServerListʵؼĽӿڷ൱NACOSRibbonĶԽӵ㡣\n\n\n\n```\npublic interface ServerList<T extends Server> {\n\n    public List<T> getInitialListOfServers();\n\n    /**\n     * Return updated list of servers. This is called say every 30 secs\n     * (configurable) by the Loadbalancer's Ping cycle\n     * \n     */\n    public List<T> getUpdatedListOfServers();   \n\n}\n```\n\n\n\nNACOSӿڵʵ֣ʹgetServers뵽getServers棬ʵ˵NacosNamingService.selectInstancesͨserviceIdȡServiceInfoȻȡServiceЧInstance\n\nṩߣ÷ĽǶȿNACOSͨʱʵʱServiceInfoҪҵ߼HostReactorʵֵġǰserviceMapһHostReactorάserviceInfoMap\n\n\n\n```\nprivate Map<String, ServiceInfo> serviceInfoMap;\n```\n\n\n\nHostReactorFailoverReactorServiceInfo˴̻棬Ȼ˶ʱָĿ¼лServiceInfoԴʵFailoverơfailover-modeҲпصģʵһضļһݣЩõļҲͨʱʵֵġ\n\n\n\n```\nFile switchFile = new File(failoverDir + UtilAndComs.FAILOVER_SWITCH);\n```\n\n\n\nͼʾ\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-d16112ec6ff6dda0b029b019c313177c_720w.webp)\n\n<figcaption>ָͼ</figcaption>\n\n</figure>\n\n## ġ̨(Console)\n\nһǹ̨ʵ֣ʵһǳ͵WEBĿ\n\nʹSpring Security + JWTаȫƣǰ˼ReactJsJdbcTemplateݿ־û\n\nҪעǣ̨ṩĹܲǴnacos-consoleлȡݣǷɢ˸С\n\nnacos-consoleṩ˿̨¼namespacę״̬ùͷֱnacos-confignacos-namingṩAPIЩAPIǹᵽOpen-API\n\n## 塢ܽ\n\nNACOSԴͨ׶ûʲôҲûнвװͰһ̾ĳԱڰСʱ֮ڰĿ硣\n\nȻҲһЩɺӵȱ㣬磬ע͹٣뻹кܴعռ䣬tenantnamespaceʹá\n\nSpring Cloud Alibaba NacosĽܵ˾ͽˣϣ\n# ο\nhttps://lijunyi.xyz/docs/SpringCloud/SpringCloud.html#_2-2-x-%E5%88%86%E6%94%AF\nhttps://mp.weixin.qq.com/s/2jeovmj77O9Ux96v3A0NtA\nhttps://juejin.cn/post/6931922457741770760\nhttps://github.com/D2C-Cai/herring\nhttp://c.biancheng.net/springcloud\nhttps://github.com/macrozheng/springcloud-learning"
  },
  {
    "path": "docs/Spring全家桶/SpringCloudAlibaba源码分析/SpringCloudAlibabaNacos源码分析：配置中心.md",
    "content": "# Nacos\n\n## Nacosĵʹ\n\nοٷ[github.com/alibaba/spr](https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Falibaba%2Fspring-cloud-alibaba%2Fwiki%2FNacos-config \"https://github.com/alibaba/spring-cloud-alibaba/wiki/Nacos-config\")\n\n## Config\n\n           Nacos ģ Key ԪΨһȷ, NamespaceĬǿմռ䣨publicĬ DEFAULT_GROUP\n\n![image-20230429084711954](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230429084711954.png)\n\n* **֧õĶ̬**\n\n        ̬ˢʱµ EnviromentУÿһдEnviromentлȡ\n\n```\n@SpringBootApplication\npublic class NacosConfigApplication {\n\n    public static void main(String[] args) throws InterruptedException {\n        ConfigurableApplicationContext applicationContext = SpringApplication.run(NacosConfigApplication.class, args);\n\n         while(true) {\n        //̬ˢʱµ EnviromentУÿһдEnviromentлȡ\n         String userName = applicationContext.getEnvironment().getProperty(\"common.name\");\n        String userAge = applicationContext.getEnvironment().getProperty(\"common.age\");\n        System.err.println(\"common name :\" + userName + \"; age: \" + userAge);\n            TimeUnit.SECONDS.sleep(1);\n        }\n    }\n}\nƴ\n```\n\n* **֧profileȵ**\n\n  spring-cloud-starter-alibaba-nacos-config ڼõʱ򣬲 dataid Ϊ spring.application.name.{spring.application.name}.spring.application.name.{file-extension:properties} Ϊǰ׺ĻãdataidΪ spring.application.name?{spring.application.name}-spring.application.name?{profile}.file?extension:propertiesĻá\n\n  ճ׻µĲͬãͨSpringṩ{file-extension:properties} Ļáճ׻µĲͬãͨSpring ṩ file?extension:propertiesĻáճ׻µĲͬãͨSpringṩ{spring.profiles.active} á\n\n```\nspring.profiles.active=dev\nƴ\n```\n\n* **֧Զ namespace **\n\n  ڽ⻧ȵø롣ͬռ£Դͬ Group  Data ID áNamespace ĳó֮һǲͬõָ룬翪Իʣá񣩸ȡ\n\n  ûȷָ ${spring.cloud.nacos.config.namespace} õ£ Ĭʹõ Nacos  Public namespaceҪʹԶռ䣬ͨʵ֣\n\n```\nspring.cloud.nacos.config.namespace=71bb9785-231f-4eca-b4dc-6be446e12ff8\nƴ\n```\n\n* **֧Զ Group **\n\n  Group֯õά֮һͨһַ Buy  Trade üз飬Ӷ Data ID ͬü Nacos ϴһʱδд÷ƣ÷Ĭϲ DEFAULT_GROUP ÷ĳͬӦûʹͬͣ database_url ú MQ_topic á\n\nûȷָ ${spring.cloud.nacos.config.group} õ£ĬDEFAULT_GROUP ҪԶԼ Groupͨʵ֣\n\n```\nspring.cloud.nacos.config.group=DEVELOP_GROUP\nƴ\n```\n\n* **֧Զչ Data Id **\n\n        Data ID ֯õά֮һData ID ͨ֯ϵͳüһϵͳӦÿ԰üÿüԱһƱʶData ID ͨ Java  com.taobao.tc.refund.log.level֤ȫΨһԡǿơ\n\nͨԶչ Data Id ãȿԽӦüù⣬ֿ֧һӦжļ\n\n```\n# Զ Data Id \n#̵ͬͨ ֹ֧ DataId\nspring.cloud.nacos.config.sharedConfigs[0].data-id= common.yaml\nspring.cloud.nacos.config.sharedConfigs[0].group=REFRESH_GROUP\nspring.cloud.nacos.config.sharedConfigs[0].refresh=true\n\n# config external configuration\n# ֧һӦö DataId \nspring.cloud.nacos.config.extensionConfigs[0].data-id=ext-config-common01.properties\nspring.cloud.nacos.config.extensionConfigs[0].group=REFRESH_GROUP\nspring.cloud.nacos.config.extensionConfigs[0].refresh=true\n\nspring.cloud.nacos.config.extensionConfigs[1].data-id=ext-config-common02.properties\nspring.cloud.nacos.config.extensionConfigs[1].group=REFRESH_GROUP\nƴ\n```\n\n## õȼ\n\nSpring Cloud Alibaba Nacos Config Ŀǰṩ Nacos ȡصá\n\n*   A: ͨ spring.cloud.nacos.config.shared-configs ֶ֧ Data Id \n\n*   B: ͨ spring.cloud.nacos.config.ext-config[n].data-id ķʽֶ֧չ Data Id \n\n*   C: ͨڲع(ӦӦ+ Profile )Զص Data Id \n\nַʽͬʹʱǵһȼϵ:A < B < C\n\nȼӸߵͣ\n\n1.  nacos-config-product.yaml ׼\n\n2.  nacos-config.yaml ̲ͬͬͨ\n\n3.  ext-config: ͬ չ\n\n4.  shared-dataids ͬͨ\n\n## @RefreshScope\n\n@ValueעԻȡĵֵ޷̬֪޸ĺֵҪ@RefreshScopeע\n\n```\n@RestController\n@RefreshScope\npublic class TestController {\n\n    @Value(\"${common.age}\")\n    private String age;\n\n    @GetMapping(\"/common\")\n    public String hello() {\n        return age;\n    }\n}\nƴ\n```\n\n## NacosԴ\n\n**ϸԴͼ**\n\n[www.processon.com/view/link/6](https://link.juejin.cn?target=https%3A%2F%2Fwww.processon.com%2Fview%2Flink%2F60f78ddbf346fb761bbac19d \"https://www.processon.com/view/link/60f78ddbf346fb761bbac19d\")\n\n### ļܹ\n\n![image-20230429084840636](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230429084840636.png)\n\nʹdemo\n\n```\npublic class ConfigServerDemo {\n\n    public static void main(String[] args) throws NacosException, InterruptedException {\n        String serverAddr = \"localhost\";\n        String dataId = \"nacos-config-demo.yaml\";\n        String group = \"DEFAULT_GROUP\";\n        Properties properties = new Properties();\n        properties.put(PropertyKeyConst.SERVER_ADDR, serverAddr);\n        //ȡ÷\n        ConfigService configService = NacosFactory.createConfigService(properties);\n        //ȡ\n        String content = configService.getConfig(dataId, group, 5000);\n        System.out.println(content);\n        //ע\n        configService.addListener(dataId, group, new Listener() {\n            @Override\n            public void receiveConfigInfo(String configInfo) {\n                System.out.println(\"===recieve:\" + configInfo);\n            }\n\n            @Override\n            public Executor getExecutor() {\n                return null;\n            }\n        });\n\n        //\n        //boolean isPublishOk = configService.publishConfig(dataId, group, \"content\");\n        //System.out.println(isPublishOk);\n        //propertiesʽ\n        configService.publishConfig(dataId,group,\"common.age=30\", ConfigType.PROPERTIES.getType());\n\n        Thread.sleep(3000);\n        content = configService.getConfig(dataId, group, 5000);\n        System.out.println(content);\n\n//        boolean isRemoveOk = configService.removeConfig(dataId, group);\n//        System.out.println(isRemoveOk);\n//        Thread.sleep(3000);\n\n//        content = configService.getConfig(dataId, group, 5000);\n//        System.out.println(content);\n//        Thread.sleep(300000);\n    }\n}\nƴ\n```\n\n## nacos config clientԴ\n\nĺĽӿConfigService\n\n![image-20230429084850542](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230429084850542.png)\n\n### ȡ\n\n        ȡõҪ NacosConfigService  getConfig ͨ¸÷ֱӴӱļȡõֵļڻΪգͨ HTTP GET Զȡã浽ؿСͨ HTTP ȡԶʱNacos ṩ۶ϲԣһǳʱʱ䣬ԴĬΡ\n\n![image-20230429084858759](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230429084858759.png)\n\n### ע\n\nĿͻ˻ͨעﵽʱִлصĹ\n\n* NacosConfigService#getConfigAndSignListener\n\n* ConfigService#addListener\n\n        Nacos ͨϷʽעڲʵ־ǵ ClientWorker  addCacheDataIfAbsent CacheData һάעмʵе CacheData  ClientWorker еԭ cacheMap УڲĺĳԱУ\n\n![image-20230429084908207](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230429084908207.png)\n\n![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f14749bd9b614b21a55a261187cd5521~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.awebp)\n\n### óѯ\n\n         ClientWorker ͨµ̳߳óѯĹһǵ̵߳ executorÿ 10ms ÿ 3000 Ϊһȡѯ cacheData ʵװΪһ LongPollingTask ύڶ̳߳ executorService \n\n![image-20230429084917535](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230429084917535.png)\n\n## nacos config serverԴ\n\n### dump\n\n![image-20230429084926987](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230429084926987.png)\n\n       ʱͻ DumpService  init ݿ load ô洢ڱشϣһЩҪԪϢ MD5 ֵڴС˻ļбһʱ䣬жϵǴݿ dump ȫݻǲݣϴ 6h ڵĻ\n\nȫ dump Ȼմ̻棬Ȼ ID ÿȡһǧˢ̺ڴ档 dump ȡСʱãµĺɾģȰˢһڴļٸڴеȫȥȶһݿ⣬иıͬһΣȫ dump Ļһݿ IO ʹ IO \n\n### ÷\n\n        õĴλ ConfigController#publishConfigСȺһʼҲֻһ̨̨òMysqlнг־û˲ÿòѯȥ MySQL ǻ dump ڱļнû˵̨֮Ҫ֪ͨˢڴͱشеļݣᷢһΪ ConfigDataChangeEvent ¼¼ͨ HTTP ֪ͨмȺڵ㣨ļڴˢ¡\n\n### ѯ\n\n        ͻ˻һѯȡ˵ñ˴߼LongPollingServiceУһ Runnable ΪClientLongPolling˻Ὣܵѯװһ ClientLongPolling 񣬸һ AsyncContext Ӧͨʱ̳߳Ӻ 29.5s ִСȿͻ 30s ĳʱʱǰ 500ms Ϊ̶ϱ֤ͻ˲Ϊʱɳʱ.\n\n![image-20230429084934117](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230429084934117.png)\n\n\n\n\n\n\n\n\n\nߣƵŶ\nӣhttps://juejin.cn/post/6999814668390760484\nԴϡ\nȨСҵתϵ߻Ȩҵתע\n# ο\nhttps://lijunyi.xyz/docs/SpringCloud/SpringCloud.html#_2-2-x-%E5%88%86%E6%94%AF\nhttps://mp.weixin.qq.com/s/2jeovmj77O9Ux96v3A0NtA\nhttps://juejin.cn/post/6931922457741770760\nhttps://github.com/D2C-Cai/herring\nhttp://c.biancheng.net/springcloud\nhttps://github.com/macrozheng/springcloud-learning"
  },
  {
    "path": "docs/Spring全家桶/SpringCloudAlibaba源码分析/SpringCloudRocketMQ源码分析.md",
    "content": "### һNameServer\n\nԴڣNamesrvStartup#main\n\n##### 1.NamesrvController controller = createNamesrvController(args);\n\n*   в\n*   öNamesrvConfigNettyServerConfig\n*    -c -p\n*   RocketMQ_HOME\n*   final NamesrvController controller = new NamesrvController(namesrvConfig, nettyServerConfig);controller\n*   controller.getConfiguration().registerConfig(properties); עϢ\n\n##### 2.start(controller);\n\n*   controller.initialize() ִгʼ\n     this.kvConfigManager.load(); KV\n     this.remotingServer = new NettyRemotingServer(this.nettyServerConfig, this.brokerHousekeepingService);NettyServer紦\n     this.remotingExecutor =Executors.newFixedThreadPool(nettyServerConfig.getServerWorkerThreads(), new ThreadFactoryImpl(\"RemotingExecutorThread_\")); Netty̳߳\n     this.registerProcessor(); עNameServerProcessor עᵽRemotingServer\n     NamesrvController.this.routeInfoManager.scanNotActiveBroker() ʱƳԾBroker\n     NamesrvController.this.kvConfigManager.printAllPeriodically() ʱӡKVϢ\n*   Runtime.getRuntime().addShutdownHook עرչӣڹرշʱͷԴ\n*   controller.start() controller\n\nNameServerҪ\n1.άbrokerķַϢи\n2.ProducerconsumerṩBrokerķб\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/3245844-46ca880d83fb583b.png)\n\n\n\nimage.png\n\n\n\n### Broker\n\nԴڣBrokerstartup#main\n\n##### 1.createBrokerController(args)\n\n*   ĸöBrokerConfigNettyServerConfigNettyClientConfigMessageStoreConfig\n*   BrokerConfigֻ -c\n*   RocketMq_HOME\n*   RemotingUtil.string2SocketAddress(addr) namesrvAddrַв\n*   messageStoreConfig.getBrokerRole() ͨBrokerIdжӣmasterId=0DeldgerȺBrokerڵID-1\n*    -p-mĲӵĸö\n*   BrokerController controller = new BrokerController brokerControllerĸഫ\n*   controller.getConfiguration().registerConfig(properties); עᣨ£\n*   controller.initialize(); ʼcontroller\n     شϵļtopicConfigManagerconsumerOffsetManagersubscriptionGroupManagerconsumerFilterManager\n     this.messageStore =new DefaultMessageStore() Ϣ洢\n     this.messageStore.load() شļ\n     this.remotingServer = new NettyRemotingServer Netty\n     this.fastRemotingServer = new NettyRemotingServer fastRemotingServerRemotingServerܻ࣬VIP˿\n     ǳʼһЩ̳߳\n     this.registerProcessor(); brokerעһЩProcessor\n*   Runtime.getRuntime().addShutdownHook עرչ\n\n##### 2.start(BrokerController controller)\n\n*   this.messageStore.start(); ҪΪ˽CommitLogд¼ַComsumeQueueIndexFile\n*   NettyremotingServerfastRemotingServer\n*   this.fileWatchService.start(); ļ\n*   this.brokerOuterAPI.start(); brokerOuterAPIΪһNettyͻˣⷢ緢\n*   this.pullRequestHoldService.start(); ѯͣ\n*   this.filterServerManager.start(); ʹfilterй\n*   BrokerController.this.registerBrokerAll() Brokerĵע,ҪþǽbrokerעᵽNamesrv\n\nbrokerĺã\n**1.ΪclientʱnameServerϢ״̬**\n**2.Ϊʱڴ洢ϢӦconsumer˵**\n\n### Nettyע\n\n### ġBrokerע\n\nԴڣBrokerController.this.registerBrokerAll(true, false, brokerConfig.isForceRegister())\n\n\n\n```\npublic synchronized void registerBrokerAll(final boolean checkOrderConfig, boolean oneway, boolean forceRegister) {\n    TopicConfigSerializeWrapper topicConfigWrapper = this.getTopicConfigManager().buildTopicConfigSerializeWrapper();\n\n    if (!PermName.isWriteable(this.getBrokerConfig().getBrokerPermission())\n        || !PermName.isReadable(this.getBrokerConfig().getBrokerPermission())) {\n        ConcurrentHashMap<String, TopicConfig> topicConfigTable = new ConcurrentHashMap<String, TopicConfig>();\n        for (TopicConfig topicConfig : topicConfigWrapper.getTopicConfigTable().values()) {\n            TopicConfig tmp =\n                new TopicConfig(topicConfig.getTopicName(), topicConfig.getReadQueueNums(), topicConfig.getWriteQueueNums(),\n                                this.brokerConfig.getBrokerPermission());\n            topicConfigTable.put(topicConfig.getTopicName(), tmp);\n        }\n        topicConfigWrapper.setTopicConfigTable(topicConfigTable);\n    }\n    //ǱȽϹؼĵطжǷҪעᣬȻdoRegisterBrokerAllȥעᡣ\n    if (forceRegister || needRegister(this.brokerConfig.getBrokerClusterName(),\n                                      this.getBrokerAddr(),\n                                      this.brokerConfig.getBrokerName(),\n                                      this.brokerConfig.getBrokerId(),\n                                      this.brokerConfig.getRegisterBrokerTimeoutMills())) {\n        doRegisterBrokerAll(checkOrderConfig, oneway, topicConfigWrapper);\n    }\n}\n\n```\n\n\n\n\n\n```\n// BrokerעĵĲ\nprivate void doRegisterBrokerAll(boolean checkOrderConfig, boolean oneway,\n                                 TopicConfigSerializeWrapper topicConfigWrapper) {\n    // עbroker\n    List<RegisterBrokerResult> registerBrokerResultList = this.brokerOuterAPI.registerBrokerAll(\n        this.brokerConfig.getBrokerClusterName(),\n        this.getBrokerAddr(),\n        this.brokerConfig.getBrokerName(),\n        this.brokerConfig.getBrokerId(),\n        this.getHAServerAddr(),\n        topicConfigWrapper,\n        this.filterServerManager.buildNewFilterServerList(),\n        oneway,\n        this.brokerConfig.getRegisterBrokerTimeoutMills(),\n        this.brokerConfig.isCompressedRegister());\n\n    if (registerBrokerResultList.size() > 0) {\n        RegisterBrokerResult registerBrokerResult = registerBrokerResultList.get(0);\n        if (registerBrokerResult != null) {\n            //ע걣ӽڵĵַ\n            if (this.updateMasterHAServerAddrPeriodically && registerBrokerResult.getHaServerAddr() != null) {\n                this.messageStore.updateHaMasterAddress(registerBrokerResult.getHaServerAddr());\n            }\n\n            this.slaveSynchronize.setMasterAddr(registerBrokerResult.getMasterAddr());\n\n            if (checkOrderConfig) {\n                this.getTopicConfigManager().updateOrderTopicConfig(registerBrokerResult.getKvTable());\n            }\n        }\n    }\n}\n\n```\n\n\n\n\n\n```\npublic List<RegisterBrokerResult> registerBrokerAll(\n    final String clusterName,\n    final String brokerAddr,\n    final String brokerName,\n    final long brokerId,\n    final String haServerAddr,\n    final TopicConfigSerializeWrapper topicConfigWrapper,\n    final List<String> filterServerList,\n    final boolean oneway,\n    final int timeoutMills,\n    final boolean compressed) {\n    //ʹCopyOnWriteArrayListȫ\n    final List<RegisterBrokerResult> registerBrokerResultList = new CopyOnWriteArrayList<>();\n    // ȡnameServerĵַϢ\n    List<String> nameServerAddressList = this.remotingClient.getNameServerAddressList();\n    if (nameServerAddressList != null && nameServerAddressList.size() > 0) {\n\n        final RegisterBrokerRequestHeader requestHeader = new RegisterBrokerRequestHeader();\n        requestHeader.setBrokerAddr(brokerAddr);\n        requestHeader.setBrokerId(brokerId);\n        requestHeader.setBrokerName(brokerName);\n        requestHeader.setClusterName(clusterName);\n        requestHeader.setHaServerAddr(haServerAddr);\n        requestHeader.setCompressed(compressed);\n\n        RegisterBrokerBody requestBody = new RegisterBrokerBody();\n        requestBody.setTopicConfigSerializeWrapper(topicConfigWrapper);\n        requestBody.setFilterServerList(filterServerList);\n        final byte[] body = requestBody.encode(compressed);\n        final int bodyCrc32 = UtilAll.crc32(body);\n        requestHeader.setBodyCrc32(bodyCrc32);\n        //ͨCountDownLatch֤NameServerעһ\n        final CountDownLatch countDownLatch = new CountDownLatch(nameServerAddressList.size());\n        for (final String namesrvAddr : nameServerAddressList) {\n            brokerOuterExecutor.execute(new Runnable() {\n                @Override\n                public void run() {\n                    try {\n                        RegisterBrokerResult result = registerBroker(namesrvAddr, oneway, timeoutMills, requestHeader, body);\n                        if (result != null) {\n                            registerBrokerResultList.add(result);\n                        }\n\n                        log.info(\"register broker[{}]to name server {} OK\", brokerId, namesrvAddr);\n                    } catch (Exception e) {\n                        log.warn(\"registerBroker Exception, {}\", namesrvAddr, e);\n                    } finally {\n                        countDownLatch.countDown();\n                    }\n                }\n            });\n        }\n\n        try {\n            countDownLatch.await(timeoutMills, TimeUnit.MILLISECONDS);\n        } catch (InterruptedException e) {\n        }\n    }\n\n    return registerBrokerResultList;\n}\n\n```\n\n\n\nNameServer\n\n\n\n```\n//NameServerĺĴ\n@Override\n    public RemotingCommand processRequest(ChannelHandlerContext ctx,\n                                          RemotingCommand request) throws RemotingCommandException {\n\n    if (ctx != null) {\n        log.debug(\"receive request, {} {} {}\",\n                  request.getCode(),\n                  RemotingHelper.parseChannelRemoteAddr(ctx.channel()),\n                  request);\n    }\n\n    switch (request.getCode()) {\n        case RequestCode.PUT_KV_CONFIG:\n            return this.putKVConfig(ctx, request);\n        case RequestCode.GET_KV_CONFIG:\n            return this.getKVConfig(ctx, request);\n        case RequestCode.DELETE_KV_CONFIG:\n            return this.deleteKVConfig(ctx, request);\n        case RequestCode.QUERY_DATA_VERSION:\n            return queryBrokerTopicConfig(ctx, request);\n        case RequestCode.REGISTER_BROKER: //Brokerע汾Ĭǵǰܰ汾\n            Version brokerVersion = MQVersion.value2Version(request.getVersion());\n            if (brokerVersion.ordinal() >= MQVersion.Version.V3_0_11.ordinal()) {\n                return this.registerBrokerWithFilterServer(ctx, request); //ǰ汾\n            } else {\n                return this.registerBroker(ctx, request);\n            }\n        case RequestCode.UNREGISTER_BROKER:\n            return this.unregisterBroker(ctx, request);\n        case RequestCode.GET_ROUTEINFO_BY_TOPIC:\n            return this.getRouteInfoByTopic(ctx, request);\n        case RequestCode.GET_BROKER_CLUSTER_INFO:\n            return this.getBrokerClusterInfo(ctx, request);\n        case RequestCode.WIPE_WRITE_PERM_OF_BROKER:\n            return this.wipeWritePermOfBroker(ctx, request);\n        case RequestCode.GET_ALL_TOPIC_LIST_FROM_NAMESERVER:\n            return getAllTopicListFromNameserver(ctx, request);\n        case RequestCode.DELETE_TOPIC_IN_NAMESRV:\n            return deleteTopicInNamesrv(ctx, request);\n        case RequestCode.GET_KVLIST_BY_NAMESPACE:\n            return this.getKVListByNamespace(ctx, request);\n        case RequestCode.GET_TOPICS_BY_CLUSTER:\n            return this.getTopicsByCluster(ctx, request);\n        case RequestCode.GET_SYSTEM_TOPIC_LIST_FROM_NS:\n            return this.getSystemTopicListFromNs(ctx, request);\n        case RequestCode.GET_UNIT_TOPIC_LIST:\n            return this.getUnitTopicList(ctx, request);\n        case RequestCode.GET_HAS_UNIT_SUB_TOPIC_LIST:\n            return this.getHasUnitSubTopicList(ctx, request);\n        case RequestCode.GET_HAS_UNIT_SUB_UNUNIT_TOPIC_LIST:\n            return this.getHasUnitSubUnUnitTopicList(ctx, request);\n        case RequestCode.UPDATE_NAMESRV_CONFIG:\n            return this.updateConfig(ctx, request);\n        case RequestCode.GET_NAMESRV_CONFIG:\n            return this.getConfig(ctx, request);\n        default:\n            break;\n    }\n    return null;\n}\n\n```\n\n\n\nʵʾǽbrokerϢעᵽrouteInfoУ\n\n\n\n```\npublic RemotingCommand registerBrokerWithFilterServer(ChannelHandlerContext ctx, RemotingCommand request)\n    throws RemotingCommandException {\n    final RemotingCommand response = RemotingCommand.createResponseCommand(RegisterBrokerResponseHeader.class);\n    final RegisterBrokerResponseHeader responseHeader = (RegisterBrokerResponseHeader) response.readCustomHeader();\n    final RegisterBrokerRequestHeader requestHeader =\n        (RegisterBrokerRequestHeader) request.decodeCommandCustomHeader(RegisterBrokerRequestHeader.class);\n\n    if (!checksum(ctx, request, requestHeader)) {\n        response.setCode(ResponseCode.SYSTEM_ERROR);\n        response.setRemark(\"crc32 not match\");\n        return response;\n    }\n\n    RegisterBrokerBody registerBrokerBody = new RegisterBrokerBody();\n\n    if (request.getBody() != null) {\n        try {\n            registerBrokerBody = RegisterBrokerBody.decode(request.getBody(), requestHeader.isCompressed());\n        } catch (Exception e) {\n            throw new RemotingCommandException(\"Failed to decode RegisterBrokerBody\", e);\n        }\n    } else {\n        registerBrokerBody.getTopicConfigSerializeWrapper().getDataVersion().setCounter(new AtomicLong(0));\n        registerBrokerBody.getTopicConfigSerializeWrapper().getDataVersion().setTimestamp(0);\n    }\n    //routeInfoManagerǹ·Ϣĺ\n    RegisterBrokerResult result = this.namesrvController.getRouteInfoManager().registerBroker(\n        requestHeader.getClusterName(),\n        requestHeader.getBrokerAddr(),\n        requestHeader.getBrokerName(),\n        requestHeader.getBrokerId(),\n        requestHeader.getHaServerAddr(),\n        registerBrokerBody.getTopicConfigSerializeWrapper(),\n        registerBrokerBody.getFilterServerList(),\n        ctx.channel());\n\n    responseHeader.setHaServerAddr(result.getHaServerAddr());\n    responseHeader.setMasterAddr(result.getMasterAddr());\n\n    byte[] jsonValue = this.namesrvController.getKvConfigManager().getKVListByNamespace(NamesrvUtil.NAMESPACE_ORDER_TOPIC_CONFIG);\n    response.setBody(jsonValue);\n\n    response.setCode(ResponseCode.SUCCESS);\n    response.setRemark(null);\n    return response;\n}\n\n```\n\n\n\n### 塢ProducerϢ\n\nԴڣDefaultMQProducer#start\n1.this.defaultMQProducerImpl.start(); \n\n\n\n```\npublic void start(final boolean startFactory) throws MQClientException {\n    switch (this.serviceState) {\n        case CREATE_JUST:\n            // ĬϾCREATE_JUST\n            this.serviceState = ServiceState.START_FAILED;\n\n            this.checkConfig();\n            //޸ĵǰinstanceNameΪǰID\n            if (!this.defaultMQProducer.getProducerGroup().equals(MixAll.CLIENT_INNER_PRODUCER_GROUP)) {\n                this.defaultMQProducer.changeInstanceNameToPID();\n            }\n            //ͻ˺ĵMQͻ˹ ϢߣϢķߵķע\n            this.mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(this.defaultMQProducer, rpcHook);\n            //עMQͻ˹ʾ\n            boolean registerOK = mQClientFactory.registerProducer(this.defaultMQProducer.getProducerGroup(), this);\n            if (!registerOK) {\n                this.serviceState = ServiceState.CREATE_JUST;\n                throw new MQClientException(\"The producer group[\" + this.defaultMQProducer.getProducerGroup()\n                                            + \"] has been created before, specify another name please.\" + FAQUrl.suggestTodo(FAQUrl.GROUP_NAME_DUPLICATE_URL),\n                                            null);\n            }\n\n            this.topicPublishInfoTable.put(this.defaultMQProducer.getCreateTopicKey(), new TopicPublishInfo());\n            //ʾ --пͻmQClientFactory\n            if (startFactory) {\n                mQClientFactory.start();\n            }\n\n            log.info(\"the producer [{}] start OK. sendMessageWithVIPChannel={}\", this.defaultMQProducer.getProducerGroup(),\n                     this.defaultMQProducer.isSendMessageWithVIPChannel());\n            this.serviceState = ServiceState.RUNNING;\n            break;\n        case RUNNING:\n        case START_FAILED:\n        case SHUTDOWN_ALREADY:\n            throw new MQClientException(\"The producer service state not OK, maybe started once, \"\n                                        + this.serviceState\n                                        + FAQUrl.suggestTodo(FAQUrl.CLIENT_SERVICE_NOT_OK),\n                                        null);\n        default:\n            break;\n    }\n    // еbroker\n    this.mQClientFactory.sendHeartbeatToAllBrokerWithLock();\n\n    this.startScheduledTask();\n\n}\n\n```\n\n\n\n### ConsumerϢ\n\nѶڣDefaultMQPushConsumer#start\nthis.defaultMQPushConsumerImpl.start();\n\n\n\n```\npublic synchronized void start() throws MQClientException {\n    switch (this.serviceState) {\n        case CREATE_JUST:\n            log.info(\"the consumer [{}] start beginning. messageModel={}, isUnitMode={}\", this.defaultMQPushConsumer.getConsumerGroup(),\n                     this.defaultMQPushConsumer.getMessageModel(), this.defaultMQPushConsumer.isUnitMode());\n            this.serviceState = ServiceState.START_FAILED;\n\n            this.checkConfig();\n\n            this.copySubscription();\n\n            if (this.defaultMQPushConsumer.getMessageModel() == MessageModel.CLUSTERING) {\n                this.defaultMQPushConsumer.changeInstanceNameToPID();\n            }\n            //ͻʾҲǽġ\n            this.mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(this.defaultMQPushConsumer, this.rpcHook);\n            //ؾ\n            this.rebalanceImpl.setConsumerGroup(this.defaultMQPushConsumer.getConsumerGroup());\n            this.rebalanceImpl.setMessageModel(this.defaultMQPushConsumer.getMessageModel());\n            this.rebalanceImpl.setAllocateMessageQueueStrategy(this.defaultMQPushConsumer.getAllocateMessageQueueStrategy());\n            this.rebalanceImpl.setmQClientFactory(this.mQClientFactory);\n\n            this.pullAPIWrapper = new PullAPIWrapper(\n                mQClientFactory,\n                this.defaultMQPushConsumer.getConsumerGroup(), isUnitMode());\n            this.pullAPIWrapper.registerFilterMessageHook(filterMessageHookList);\n\n            if (this.defaultMQPushConsumer.getOffsetStore() != null) {\n                this.offsetStore = this.defaultMQPushConsumer.getOffsetStore();\n            } else {\n                //Կ㲥ģʽ뼯Ⱥģʽoffset洢ĵطһ\n                switch (this.defaultMQPushConsumer.getMessageModel()) {\n                        //㲥ģʽ߱ش洢offset\n                    case BROADCASTING:\n                        this.offsetStore = new LocalFileOffsetStore(this.mQClientFactory, this.defaultMQPushConsumer.getConsumerGroup());\n                        break;\n                        //ȺģʽBrokerԶ˴洢offset\n                    case CLUSTERING:\n                        this.offsetStore = new RemoteBrokerOffsetStore(this.mQClientFactory, this.defaultMQPushConsumer.getConsumerGroup());\n                        break;\n                    default:\n                        break;\n                }\n                this.defaultMQPushConsumer.setOffsetStore(this.offsetStore);\n            }\n            this.offsetStore.load();\n            //˳ѼConsumeMessageOrderlyService\n            if (this.getMessageListenerInner() instanceof MessageListenerOrderly) {\n                this.consumeOrderly = true;\n                this.consumeMessageService =\n                    new ConsumeMessageOrderlyService(this, (MessageListenerOrderly) this.getMessageListenerInner());\n                //ѼConsumeMessageConcurrentlyService\n            } else if (this.getMessageListenerInner() instanceof MessageListenerConcurrently) {\n               this.consumeOrderly = false;\n               this.consumeMessageService =\n               new ConsumeMessageConcurrentlyService(this, (MessageListenerConcurrently) this.getMessageListenerInner());\n            }\n\n           this.consumeMessageService.start();\n           //עߡƣͻֻҪҪעἴɣmQClientFactoryһ\n           boolean registerOK = mQClientFactory.registerConsumer(this.defaultMQPushConsumer.getConsumerGroup(), this);\n           if (!registerOK) {\n               this.serviceState = ServiceState.CREATE_JUST;\n               this.consumeMessageService.shutdown(defaultMQPushConsumer.getAwaitTerminationMillisWhenShutdown());\n               throw new MQClientException(\"The consumer group[\" + this.defaultMQPushConsumer.getConsumerGroup()\n               + \"] has been created before, specify another name please.\" + FAQUrl.suggestTodo(FAQUrl.GROUP_NAME_DUPLICATE_URL),\n               null);\n           }\n\n           mQClientFactory.start();\n           log.info(\"the consumer [{}] start OK.\", this.defaultMQPushConsumer.getConsumerGroup());\n           this.serviceState = ServiceState.RUNNING;\n           break;\n           case RUNNING:\n           case START_FAILED:\n           case SHUTDOWN_ALREADY:\n            throw new MQClientException(\"The PushConsumer service state not OK, maybe started once, \"\n                + this.serviceState\n                + FAQUrl.suggestTodo(FAQUrl.CLIENT_SERVICE_NOT_OK),null);\n           default:\n                break;\n           }\n\n               this.updateTopicSubscribeInfoWhenSubscriptionChanged();\n               this.mQClientFactory.checkClientInBroker();\n               this.mQClientFactory.sendHeartbeatToAllBrokerWithLock();\n               this.mQClientFactory.rebalanceImmediately();\n           }\n\n```\n\n\n\n**1consumer˵ģʽ**\n ȺģʽȺģʽÿconsumer䲻ͬϢ\n 㲥ģʽ㲥ģʽÿϢ͸consumer\n**2offset洢**\n 㲥ģʽthis.offsetStore = new LocalFileOffsetStore(); 洢ÿconsumer\n Ⱥģʽthis.offsetStore = new RemoteBrokerOffsetStore(); 洢broker\n\n\n\nߣҶ컨\nӣhttps://www.jianshu.com/p/8dd4cfeae39d\nԴ\nȨСҵתϵ߻Ȩҵתע\n# ο\nhttps://lijunyi.xyz/docs/SpringCloud/SpringCloud.html#_2-2-x-%E5%88%86%E6%94%AF\nhttps://mp.weixin.qq.com/s/2jeovmj77O9Ux96v3A0NtA\nhttps://juejin.cn/post/6931922457741770760\nhttps://github.com/D2C-Cai/herring\nhttp://c.biancheng.net/springcloud\nhttps://github.com/macrozheng/springcloud-learning"
  },
  {
    "path": "docs/Spring全家桶/SpringCloudAlibaba源码分析/SpringCloudSeata源码分析.md",
    "content": " | һߣJava\nԴ |ͷ\n\n**ѧϰĿ**\n\n*   Seata ATģʽԴ\n    **1 ATģʽ**\n    **1.1 ˼άƵ**\n    ѾATģʽĴԭԴУͨREADMEҲܿATģʽʹãǱĽӵײԴȥATģʽԭڷԭ֮ǰͼһĹ˼·ģʽ\n\nȿ˼άƵͼ![SpringCloud AlibabaϵС17Seata ATģʽԴϣ-Դ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/0388859932ccee17ed32246e2e5f4e0a88dee6.jpg \"SpringCloud AlibabaϵС17Seata ATģʽԴϣ-Դ\")1.2 ʼƵ\n![SpringCloud AlibabaϵС17Seata ATģʽԴϣ-Դ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/f24e8bd05e84b683e3f3227490146155bf5b17.jpg \"SpringCloud AlibabaϵС17Seata ATģʽԴϣ-Դ\")1.3 ִƵ\n![SpringCloud AlibabaϵС17Seata ATģʽԴϣ-Դ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/93dea0538eba8be5778699ea2af8f71c0e396c.jpg \"SpringCloud AlibabaϵС17Seata ATģʽԴϣ-Դ\")**2 Դ**\n**2.1 SeataAutoConfiguration**\nseataԴоҪseataҵSQLundo_logݣһ׶ɺύȫһ׶ҵʧܺͨundo_logع񣬽񲹳\n\nseataҲspringʹõģSpringBootseataҲһЩԶ![SpringCloud AlibabaϵС17Seata ATģʽԴϣ-Դ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/719ef4a775ece0688c929227415e97f68f1c58.jpg \"SpringCloud AlibabaϵС17Seata ATģʽԴϣ-Դ\")seataԶǳֱӣͽSeataAutoConfigurationǴ\n\n\n\n```\n@ComponentScan(basePackages = \"io.seata.spring.boot.autoconfigure.properties\")@ConditionalOnProperty(prefix = StarterConstants.SEATA_PREFIX, name = \"enabled\", havingValue = \"true\", matchIfMissing = true)@Configuration@EnableConfigurationProperties({SeataProperties.class})public class SeataAutoConfiguration {    }\n```\n\n\n\n\n\n\n\n\n\nȣ@ComponentScanɨһpropertiesһSeataPropertiesBean\n\n@ConditionalOnPropertyЧΪseata.enabled=trueĬֵtrueԿԿطֲʽܣclient˵file.confã\n\n@ConfigurationSeataAutoConfigurationΪspringࡣ\n\n@EnableConfigurationPropertiesðתһSeataPropertiesBeanʹá\n\nĶSeataAutoConfigurationڲ\n\n\n\n```\n@Bean@DependsOn({BEAN_NAME_SPRING_APPLICATION_CONTEXT_PROVIDER, BEAN_NAME_FAILURE_HANDLER})@ConditionalOnMissingBean(GlobalTransactionScanner.class)public GlobalTransactionScanner globalTransactionScanner(SeataProperties seataProperties, FailureHandler failureHandler) {    if (LOGGER.isInfoEnabled()) {        LOGGER.info(\"Automatically configure Seata\");    }    return new GlobalTransactionScanner(seataProperties.getApplicationId(), seataProperties.getTxServiceGroup(), failureHandler);}\n```\n\n\n\n\n\n\n\n\n\nԶõĺĵһBeanGlobalTransactionScanner\n\nǿBeanǳļ򵥣췽ֻҪһapplicationIdtxServiceGroup\n\napplicationId: spring.application.name=㶨ĵǰӦõ֣磺userService\n\ntxServiceGroup: applicationId  -seata-service-groupģ磺\nuserService-seata-service-group汾ϵ͵ĻʱܻseatafescarĬfescarΪ׺\n\nnewһGlobalTransactionScannerSeataAutoConfigurationԶþͽˡSeataAutoConfigurationֻһá\n\n**2.2 GlobalTransactionScanner**\nȻĵGlobalTransactionScanner࣬ǼעʵͿԲ²⵽һãɨ@GlobalTransactionalע⣬ԴǿĹܡ\n\nҪ˽࣬òĶһUMLͼ![SpringCloud AlibabaϵС17Seata ATģʽԴϣ-Դ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/83cb3ed76073a33cb19242ffe542b615346d16.jpg \"SpringCloud AlibabaϵС17Seata ATģʽԴϣ-Դ\")ԿGlobalTransactionScannerҪ4ֵùע\n\n1ApplicationContextAwareʾõspring\n\n2InitializingBeanӿڣ˳ʼʱһЩ\n\n3AbstractAutoProxyCreatorʾspringеBeanǿҲǿĲ²⡣\n\n4DisposableӿڣspringٵʱһЩ\n\n΢עһ4ִ˳\n\nApplicationContextAware -> InitializingBean -> AbstractAutoProxyCreator -> DisposableBean\n\n**2.3 InitializingBean**\n\n\n\n```\n@Overridepublic void afterPropertiesSet() {    if (disableGlobalTransaction) {        if (LOGGER.isInfoEnabled()) {            LOGGER.info(\"Global transaction is disabled.\");        }        return;    }    initClient();}\n```\n\n\n\n\n\n\n\n\n\nʼSeataClient˵ĶClientҪTransactionManagerResourceManagerΪ˼򻯰ɣûаinitClient´GlobalTransactionScannerһࡣ\n\ninitClient\n\n\n\n```\nprivate void initClient() {    //init TM    TMClient.init(applicationId, txServiceGroup);       //init RM    RMClient.init(applicationId, txServiceGroup);      registerSpringShutdownHook();}\n```\n\n\n\n\n\n\n\n\n\ninitClient߼ӣTMClient.initʼTransactionManagerRPCͻˣRMClient.initʼResourceManagerRPCͻˡseataRPCnettyʵ֣seataװһʹáעһSpringShutdownHookӺ\n\n**2.3.1 TMClientʼ**\n\n\n\n```\n@Overridepublic void init() {    timerExecutor.scheduleAtFixedRate(new Runnable() {        @Override        public void run() {            clientChannelManager.reconnect(getTransactionServiceGroup());        }    }, SCHEDULE_DELAY_MILLS, SCHEDULE_INTERVAL_MILLS, TimeUnit.MILLISECONDS);...}\n```\n\n\n\n\n\n\n\n\n\nһʱϽ\nclientChannelManager.reconnect\n\n\n\n```\nvoid reconnect(String transactionServiceGroup) {    List<String> availList = null;    try {        availList = getAvailServerList(transactionServiceGroup);    } catch (Exception e) {       ...    }   ...    for (String serverAddress : availList) {        try {            acquireChannel(serverAddress);        } catch (Exception e) {            ...        }    }}\n```\n\n\n\n\n\n\n\n\n\ntransactionServiceGroupȡseata-serveripַбȻ\n\n\n\n```\nprivate List<String> getAvailServerList(String transactionServiceGroup) throws Exception {    List<InetSocketAddress> availInetSocketAddressList = RegistryFactory.getInstance()        .lookup(transactionServiceGroup);    if (CollectionUtils.isEmpty(availInetSocketAddressList)) {        return Collections.emptyList();    }     return availInetSocketAddressList.stream()        .map(NetUtil::toStringAddress)        .collect(Collectors.toList());}\n```\n\n\n\n\n\n\n\n\n\nRegistryFactory.getInstance().lookup(transactionServiceGroup);ǶԲͬעģĬϿNacosʽʵ![SpringCloud AlibabaϵС17Seata ATģʽԴϣ-Դ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/745b802128d80980a6e082f91406451526cbb3.jpg \"SpringCloud AlibabaϵС17Seata ATģʽԴϣ-Դ\")ȸҵserverȺƣdefaultȻݼȺҵserverӦip˿ڵַ\n\n\n\n```\n@Overridepublic List<InetSocketAddress> lookup(String key) throws Exception {    //default    String clusterName = getServiceGroup(key);    if (clusterName == null) {        return null;    }    if (!LISTENER_SERVICE_MAP.containsKey(clusterName)) {        synchronized (LOCK_OBJ) {            if (!LISTENER_SERVICE_MAP.containsKey(clusterName)) {                List<String> clusters = new ArrayList<>();                clusters.add(clusterName);                List<Instance> firstAllInstances = getNamingInstance().getAllInstances(getServiceName(), getServiceGroup(), clusters);                if (null != firstAllInstances) {                    List<InetSocketAddress> newAddressList = firstAllInstances.stream()                        .filter(instance -> instance.isEnabled() && instance.isHealthy())                        .map(instance -> new InetSocketAddress(instance.getIp(), instance.getPort()))                        .collect(Collectors.toList());                    CLUSTER_ADDRESS_MAP.put(clusterName, newAddressList);                }                subscribe(clusterName, event -> {                    List<Instance> instances = ((NamingEvent) event).getInstances();                    if (null == instances && null != CLUSTER_ADDRESS_MAP.get(clusterName)) {                        CLUSTER_ADDRESS_MAP.remove(clusterName);                    } else if (!CollectionUtils.isEmpty(instances)) {                        List<InetSocketAddress> newAddressList = instances.stream()                            .filter(instance -> instance.isEnabled() && instance.isHealthy())                            .map(instance -> new InetSocketAddress(instance.getIp(), instance.getPort()))                            .collect(Collectors.toList());                        CLUSTER_ADDRESS_MAP.put(clusterName, newAddressList);                    }                });            }        }    }    return CLUSTER_ADDRESS_MAP.get(clusterName);}\n```\n\n\n\n\n\n\n\n\n\nSeata-serverIPַѻȡ,ȻacquireChannel\n\n\n\n```\nChannel acquireChannel(String serverAddress) {    Channel channelToServer = channels.get(serverAddress);    if (channelToServer != null) {        channelToServer = getExistAliveChannel(channelToServer, serverAddress);        if (channelToServer != null) {            return channelToServer;        }    }...    channelLocks.putIfAbsent(serverAddress, new Object());    synchronized (channelLocks.get(serverAddress)) {        return doConnect(serverAddress);    }}\n```\n\n\n\n\n\n\n\n\n\n󽫻ȡseata-serverIPַŵNettyзװTmClientͳʼ\n\nTmClientʼܽ᣺\n\n*   ʱԽһseata-server\n*   ʱȴnacosãиݷ(service_group)ҵȺ(cluster_name)\n*   ٸݼȺҵȺip˿б\n*   ipбѡһnetty\n    **2.3.2 RMClientʼ**\n\n\n\n```\npublic static void init(String applicationId, String transactionServiceGroup) {    // ȡ    RmRpcClient rmRpcClient = RmRpcClient.getInstance(applicationId, transactionServiceGroup);    // ResourceManagerĵ    rmRpcClient.setResourceManager(DefaultResourceManager.get());    // ӼServer˵Ϣ    rmRpcClient.setClientMessageListener(new RmMessageListener(DefaultRMHandler.get()));    // ʼRPC    rmRpcClient.init();}\n```\n\n\n\n\n\n\n\n\n\nTMClientȣRMClientһServerϢĻơҲ˵TMְServerͨţ磺ȫbegincommitrollbackȡ\n\nRMԴ⣬ΪȫcommitrollbackȵϢͣӶԱԴز\n\nԴresourceManagerϢصڽTCڶ׶ηύ߻عSeataжResourceManagerAbstractRMHandlerSPI䣬ResouceManagerΪ\n\n\n\n```\npublic class DefaultResourceManager implements ResourceManager {    protected void initResourceManagers() {        //init all resource managers        List<ResourceManager> allResourceManagers = EnhancedServiceLoader.loadAll(ResourceManager.class);        if (CollectionUtils.isNotEmpty(allResourceManagers)) {            for (ResourceManager rm : allResourceManagers) {                resourceManagers.put(rm.getBranchType(), rm);            }        }    }}\n```\n\n\n\n\n\n\n\n\n\nԿʼDefaultResouceManagerʱʹClassLoaderȥضӦJarµʵ֣ĬATģʽʹõʵݿ⣬Ҳrm-datasourceµʵ֣ʵ·Ҫλ/resources/META-INF/չӿȫ·ȥңͻҵӦʵ ![SpringCloud AlibabaϵС17Seata ATģʽԴϣ-Դ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/84399f558194f3dce18275c72cb9a92219db25.jpg \"SpringCloud AlibabaϵС17Seata ATģʽԴϣ-Դ\")ResourceManagerӦʵȫ·\nio.seata.rm.datasource.DataSourceManagerָύͻعķDefaultRMHandlerӦʵȫ·io.seata.rm.RMHandlerATǸserverϢӦύ߻عĻصࡣ\n\nRMClinetinit()TMClientһ\n\n**2.3.3 ܽ**\n\n*   Springʱʼ2ͻTmClientRmClient\n*   TmClientseata-serverͨNettyӲϢ\n*   RmClientseata-serverͨNettyӣն׶ύعϢڻص(RmHandler)\n\n | һߣJava\nԴ |ͷ\n\n**2.4 AbstractAutoProxyCreator**\nGlobalTransactionScannerʼTMRMԺٹעһAbstractAutoProxyCreatorԶ\n\nԶɶأ˵springеBeanǿʲôܣ\n\nGlobalTransactionScannerҪչAbstractAutoProxyCreatorwrapIfNecessary\n\nǿǰжϴʾǷBeanҪǿǿĻ\n\n**2.4.1 wrapIfNecessary**\n\n\n\n```\n@Overrideprotected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {    if (disableGlobalTransaction) {        return bean;    }    try {        synchronized (PROXYED_SET) {            // ͬBean            if (PROXYED_SET.contains(beanName)) {                return bean;            }             interceptor = null;            // жǷTCCģʽ            if (TCCBeanParserUtils.isTccAutoProxy(bean, beanName, applicationContext)) {                // TCCʵֵ                interceptor = new TccActionInterceptor(TCCBeanParserUtils.getRemotingDesc(beanName));            } else {                Class<?> serviceInterface = SpringProxyUtils.findTargetClass(bean);                Class<?>[] interfacesIfJdk = SpringProxyUtils.findInterfaces(bean);                 // жǷ@GlobalTransactional@GlobalLockע                if (!existsAnnotation(new Class[]{serviceInterface})                    && !existsAnnotation(interfacesIfJdk)) {                    return bean;                }                 if (interceptor == null) {                    // TCC                    if (globalTransactionalInterceptor == null) {                        globalTransactionalInterceptor = new GlobalTransactionalInterceptor(failureHandlerHook);                        ConfigurationCache.addConfigListener(                            ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION,                            (ConfigurationChangeListener)globalTransactionalInterceptor);                    }                    interceptor = globalTransactionalInterceptor;                }            }            // жϵǰBeanǷѾspringĴ            if (!AopUtils.isAopProxy(bean)) {                // ǣôһspringĴ̼                bean = super.wrapIfNecessary(bean, beanName, cacheKey);            } else {                // һspringĴ࣬ôȡѾڵϣȻӵüϵ                AdvisedSupport advised = SpringProxyUtils.getAdvisedSupport(bean);                Advisor[] advisor = buildAdvisors(beanName, getAdvicesAndAdvisorsForBean(null, null, null));                for (Advisor avr : advisor) {                    advised.addAdvisor(0, avr);                }            }             PROXYED_SET.add(beanName);            return bean;        }    } catch (Exception exx) {}}\n```\n\n\n\n\n\n\n\n\n\nwrapIfNecessaryϳǷֲ迴\n\n1isTccAutoProxyжǷtccģʽĻѡTccActionInterceptortccģʽѡ\nGlobalTransactionalInterceptorĬϲ\n\n2existAnnotationжϵǰBeanǷ߽ӿڵķ@GlobalTransactional@GlobalLockע⣬ûֱӷ\n\n3isAopProxyжϵǰBeanǷѾspringĴˣJDK̬CglibͨBeanԭеɴ߼ɣѾǴ࣬ôҪͨȡڵҲAdvisorֱӵüϵС\n\nwrapIfNecessaryķӣԴǺϤϸڵЩ\n\n**2.4.1.1 ATһ׶οȫ**\nҪȫĽӿϣ@GlobalTransactionalע⣬עһӦ\nGlobalTransactionalInterceptorinvokeط\n\n\n\n```\n@Overridepublic Object invoke(final MethodInvocation methodInvocation) throws Throwable {    Class<?> targetClass =        methodInvocation.getThis() != null ? AopUtils.getTargetClass(methodInvocation.getThis()) : null;    Method specificMethod = ClassUtils.getMostSpecificMethod(methodInvocation.getMethod(), targetClass);    if (specificMethod != null && !specificMethod.getDeclaringClass().equals(Object.class)) {        final Method method = BridgeMethodResolver.findBridgedMethod(specificMethod);        //ȡϵȫע        final GlobalTransactional globalTransactionalAnnotation =            getAnnotation(method, targetClass, GlobalTransactional.class);        //ȡϵȫע        final GlobalLock globalLockAnnotation = getAnnotation(method, targetClass, GlobalLock.class);        boolean localDisable = disable || (degradeCheck && degradeNum >= degradeCheckAllowTimes);        if (!localDisable) {            //ȫע⣬handleGlobalTransactionȫ            if (globalTransactionalAnnotation != null) {                return handleGlobalTransaction(methodInvocation, globalTransactionalAnnotation);            //ȫע⣬handleGlobalLockȫ            } else if (globalLockAnnotation != null) {                return handleGlobalLock(methodInvocation);            }        }    }    //ɶûУִͨУ    return methodInvocation.proceed();}\n```\n\n\n\n\n\n\n\n\n\nhandleGlobalTransactionе\ntransactionalTemplate.execute\n\n\n\n```\n// 2\\. ȫbeginTransactionbeginTransaction(txInfo, tx); Object rs = null;try {     // ִҵ񷽷business.execute()    rs = business.execute(); } catch (Throwable ex) {     // 3.쳣ִcompleteTransactionAfterThrowingع    completeTransactionAfterThrowing(txInfo, tx, ex);    throw ex;} // 4\\. û쳣ύcommitTransactioncommitTransaction(tx);\n```\n\n\n\n\n\n\n\n\n\nȫյ\nio.seata.tm.api.DefaultGlobalTransaction#begin(int, java.lang.String)\n\n\n\n```\n@Overridepublic void begin(int timeout, String name) throws TransactionException {    //˴Ľɫжйؼ//ǰȫķߣLauncherǲߣParticipant//ڷֲʽϵͳҲGlobalTransactionalע//ôĽɫParticipantԺbegin˳    //жǷߣLauncherǲߣParticipantǸݵǰǷѴXIDж    //ûXIDľLauncherѾXIDľParticipant    if (role != GlobalTransactionRole.Launcher) {        assertXIDNotNull();        if (LOGGER.isDebugEnabled()) {            LOGGER.debug(\"Ignore Begin(): just involved in global transaction [{}]\", xid);        }        return;    }    assertXIDNull();    if (RootContext.getXID() != null) {        throw new IllegalStateException();    }    xid = transactionManager.begin(null, null, name, timeout);    status = GlobalStatus.Begin;    RootContext.bind(xid);    if (LOGGER.isInfoEnabled()) {        LOGGER.info(\"Begin new global transaction [{}]\", xid);    } }\n```\n\n\n\n\n\n\n\n\n\nseata-serverȡȫXID\n\n\n\n```\n@Overridepublic String begin(String applicationId, String transactionServiceGroup, String name, int timeout)    throws TransactionException {    GlobalBeginRequest request = new GlobalBeginRequest();    request.setTransactionName(name);    request.setTimeout(timeout);    //    GlobalBeginResponse response = (GlobalBeginResponse) syncCall(request);    if (response.getResultCode() == ResultCode.Failed) {        throw new TmTransactionException(TransactionExceptionCode.BeginFailed, response.getMsg());    }    return response.getXid();}\nprivate AbstractTransactionResponse syncCall(AbstractTransactionRequest request) throws TransactionException {    try {        //TMClientװNetty        return (AbstractTransactionResponse) TmNettyRemotingClient.getInstance().sendSyncRequest(request);    } catch (TimeoutException toe) {        throw new TmTransactionException(TransactionExceptionCode.IO, \"RPC timeout\", toe);    }}\n```\n\n\n\n\n\n\n\n\n\nXIDRootContextУɴ˿ԿȫTMģTMȫseata-serverseata-serverܵseata룩\n\n\n\n```\n@Overrideprotected void doGlobalBegin(GlobalBeginRequest request, GlobalBeginResponse response, RpcContext rpcContext)    throws TransactionException {    //begin    response.setXid(core.begin(rpcContext.getApplicationId(), rpcContext.getTransactionServiceGroup(),                               request.getTransactionName(), request.getTimeout()));    if (LOGGER.isInfoEnabled()) {        LOGGER.info(\"Begin new global transaction applicationId: {},transactionServiceGroup: {}, transactionName: {},timeout:{},xid:{}\",                    rpcContext.getApplicationId(), rpcContext.getTransactionServiceGroup(), request.getTransactionName(), request.getTimeout(), response.getXid());    }}\n```\n\n\n\n\n\n\n\n\n\nio.seata.server.coordinator.DefaultCoordinator#doGlobalBeginܿͻ˿ȫ󣬵io.seata.server.coordinator.DefaultCore#beginȫ\n\n\n\n```\n@Overridepublic String begin(String applicationId, String transactionServiceGroup, String name, int timeout)    throws TransactionException {    GlobalSession session = GlobalSession.createGlobalSession(applicationId, transactionServiceGroup, name,                                                              timeout);    MDC.put(RootContext.MDC_KEY_XID, session.getXid());    session.addSessionLifecycleListener(SessionHolder.getRootSessionManager());//Ự    session.begin();     // transaction start event    eventBus.post(new GlobalTransactionEvent(session.getTransactionId(), GlobalTransactionEvent.ROLE_TC,                                             session.getTransactionName(), applicationId, transactionServiceGroup, session.getBeginTime(), null, session.getStatus()));     return session.getXid();}\n```\n\n\n\n\n\n\n\n\n\nͨǰỰ\n\n\n\n```\n@Overridepublic void begin() throws TransactionException {    this.status = GlobalStatus.Begin;    this.beginTime = System.currentTimeMillis();    this.active = true;    for (SessionLifecycleListener lifecycleListener : lifecycleListeners) {        lifecycleListener.onBegin(this);    }}\n```\n\n\n\n\n\n\n\n\n\n![SpringCloud AlibabaϵС17Seata ATģʽԴ£-Դ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/e7c784186f75581eba83325a0fc4708602dadf.jpg \"SpringCloud AlibabaϵС17Seata ATģʽԴ£-Դ\")\n\n\nio.seata.server.session.AbstractSessionManager#onBeginֵio.seata.server.storage.db.session.DataBaseSessionManager#addGlobalSession\n\n\n\n```\n@Overridepublic void addGlobalSession(GlobalSession session) throws TransactionException {    if (StringUtils.isBlank(taskName)) {        //        boolean ret = transactionStoreManager.writeSession(LogOperation.GLOBAL_ADD, session);        if (!ret) {            throw new StoreException(\"addGlobalSession failed.\");        }    } else {        boolean ret = transactionStoreManager.writeSession(LogOperation.GLOBAL_UPDATE, session);        if (!ret) {            throw new StoreException(\"addGlobalSession failed.\");        }    }}\n```\n\n\n\n\n\n\n\n\n\nݿд\n\n\n\n```\n@Overridepublic boolean writeSession(LogOperation logOperation, SessionStorable session) {    if (LogOperation.GLOBAL_ADD.equals(logOperation)) {        return logStore.insertGlobalTransactionDO(SessionConverter.convertGlobalTransactionDO(session));    } else if (LogOperation.GLOBAL_UPDATE.equals(logOperation)) {        return logStore.updateGlobalTransactionDO(SessionConverter.convertGlobalTransactionDO(session));    } else if (LogOperation.GLOBAL_REMOVE.equals(logOperation)) {        return logStore.deleteGlobalTransactionDO(SessionConverter.convertGlobalTransactionDO(session));    } else if (LogOperation.BRANCH_ADD.equals(logOperation)) {        return logStore.insertBranchTransactionDO(SessionConverter.convertBranchTransactionDO(session));    } else if (LogOperation.BRANCH_UPDATE.equals(logOperation)) {        return logStore.updateBranchTransactionDO(SessionConverter.convertBranchTransactionDO(session));    } else if (LogOperation.BRANCH_REMOVE.equals(logOperation)) {        return logStore.deleteBranchTransactionDO(SessionConverter.convertBranchTransactionDO(session));    } else {        throw new StoreException(\"Unknown LogOperation:\" + logOperation.name());    }}\n```\n\n\n\n\n\n\n\n\n\nseataglobal_tabݣȫѿ\n\n**2.4.1.2 ATһ׶ִҵSQL**\nȫѿҪִҵSQLundo_logݣȫسɹջִҵ񷽷ģSeataԴ˴sqlundo_logԴִеģSeataDataSourceConnectionStatementĴװ\n\n\n\n```\n/*** datasource滻ԭĵdatasource*/@Primary@Bean(\"dataSource\")public DataSourceProxy dataSourceProxy(DataSource druidDataSource){    return new DataSourceProxy(druidDataSource);}\n```\n\n\n\n\n\n\n\n\n\nĿʹõԴseataDataSourceProxy![SpringCloud AlibabaϵС17Seata ATģʽԴ£-Դ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/c8a7a189816e282015a950bfd3c13b7d859974.jpg \"SpringCloud AlibabaϵС17Seata ATģʽԴ£-Դ\")նSqlнStatementProxy\n\n\n\n```\n@Overridepublic boolean execute(String sql) throws SQLException {    this.targetSQL = sql;    return ExecuteTemplate.execute(this, (statement, args) -> statement.execute((String) args[0]), sql);}\n```\n\n\n\n\n\n\n\n\n\n\n\n```\npublic static <T, S extends Statement> T execute(List<SQLRecognizer> sqlRecognizers,                                                     StatementProxy<S> statementProxy,                                                     StatementCallback<T, S> statementCallback,                                                     Object... args) throws SQLException {         if (!RootContext.requireGlobalLock() && !StringUtils.equals(BranchType.AT.name(), RootContext.getBranchType())) {            //ȫֱִУ            return statementCallback.execute(statementProxy.getTargetStatement(), args);        }         String dbType = statementProxy.getConnectionProxy().getDbType();        if (CollectionUtils.isEmpty(sqlRecognizers)) {            sqlRecognizers = SQLVisitorFactory.get(                    statementProxy.getTargetSQL(),                    dbType);        }        Executor<T> executor;        if (CollectionUtils.isEmpty(sqlRecognizers)) {            executor = new PlainExecutor<>(statementProxy, statementCallback);        } else {            if (sqlRecognizers.size() == 1) {                SQLRecognizer sqlRecognizer = sqlRecognizers.get(0);                //ͬSQLͣͬ                switch (sqlRecognizer.getSQLType()) {                    case INSERT:                        executor = EnhancedServiceLoader.load(InsertExecutor.class, dbType,                                new Class[]{StatementProxy.class, StatementCallback.class, SQLRecognizer.class},                                new Object[]{statementProxy, statementCallback, sqlRecognizer});                        break;                    case UPDATE:                        executor = new UpdateExecutor<>(statementProxy, statementCallback, sqlRecognizer);                        break;                    case DELETE:                        executor = new DeleteExecutor<>(statementProxy, statementCallback, sqlRecognizer);                        break;                    case SELECT_FOR_UPDATE:                        executor = new SelectForUpdateExecutor<>(statementProxy, statementCallback, sqlRecognizer);                        break;                    default:                        executor = new PlainExecutor<>(statementProxy, statementCallback);                        break;                }            } else {                executor = new MultiExecutor<>(statementProxy, statementCallback, sqlRecognizers);            }        }        T rs;        try {            //ִSQL            rs = executor.execute(args);        } catch (Throwable ex) {            if (!(ex instanceof SQLException)) {                // Turn other exception into SQLException                ex = new SQLException(ex);            }            throw (SQLException) ex;        }        return rs;    }\n```\n\n\n\n\n\n\n\n\n\n*   жǷȫûУߴsql\n*   SQLVisitorFactoryĿsqlн\n*   ضsql(INSERT,UPDATE,DELETE,SELECT_FOR_UPDATE)Ƚ\n*   ִsqlؽ\n    ͬ͵SQLһinsertΪ\n\n![SpringCloud AlibabaϵС17Seata ATģʽԴ£-Դ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/b1eb1b610beb647b230995f19ecbb4bbc98602.jpg \"SpringCloud AlibabaϵС17Seata ATģʽԴ£-Դ\")insertʹõInsertExecutor.executeʵջʹ\nio.seata.rm.datasource.exec.BaseTransactionalExecutor#execute\n\n\n\n```\n@Overridepublic T execute(Object... args) throws Throwable {    if (RootContext.inGlobalTransaction()) {        String xid = RootContext.getXID();        statementProxy.getConnectionProxy().bind(xid);    }     statementProxy.getConnectionProxy().setGlobalLockRequire(RootContext.requireGlobalLock());    return doExecute(args);}\n```\n\n\n\n\n\n\n\n\n\nеxid󶨵statementProxyУdoExecuteAbstractDMLBaseExecutorеdoExecute\n\n\n\n```\n@Overridepublic T doExecute(Object... args) throws Throwable {    AbstractConnectionProxy connectionProxy = statementProxy.getConnectionProxy();    if (connectionProxy.getAutoCommit()) {        return executeAutoCommitTrue(args);    } else {        return executeAutoCommitFalse(args);    }}\n```\n\n\n\n\n\n\n\n\n\nе\nexecuteAutoCommitTrue/executeAutoCommitFalse\n\n\n\n```\nprotected T executeAutoCommitTrue(Object[] args) throws Throwable {    ConnectionProxy connectionProxy = statementProxy.getConnectionProxy();    try {        connectionProxy.setAutoCommit(false);        return new LockRetryPolicy(connectionProxy).execute(() -> {            T result = executeAutoCommitFalse(args);            connectionProxy.commit();            return result;        });    } catch (Exception e) {        ...    } finally {        connectionProxy.getContext().reset();        connectionProxy.setAutoCommit(true);    }}\n```\n\n\n\n\n\n\n\n\n\nϸ֣նǵexecuteAutoCommitFalse\n\n\n\n```\nprotected T executeAutoCommitFalse(Object[] args) throws Exception {    //getTableMeta    if (!JdbcConstants.MYSQL.equalsIgnoreCase(getDbType()) && getTableMeta().getPrimaryKeyOnlyName().size() > 1)    {        throw new NotSupportYetException(\"multi pk only support mysql!\");    }    //ȡbeforeImage    TableRecords beforeImage = beforeImage();    //ִҵsql    T result = statementCallback.execute(statementProxy.getTargetStatement(), args);    //ȡafterImage    TableRecords afterImage = afterImage(beforeImage);    //image    prepareUndoLog(beforeImage, afterImage);    return result;}\n```\n\n\n\n\n\n\n\n\n\nȡbeforeImage\n\n\n\n```\n//tableMetaСprotected TableMeta getTableMeta(String tableName) {    if (tableMeta != null) {        return tableMeta;    }    ConnectionProxy connectionProxy = statementProxy.getConnectionProxy();    tableMeta = TableMetaCacheFactory.getTableMetaCache(connectionProxy.getDbType())        .getTableMeta(connectionProxy.getTargetConnection(), tableName, connectionProxy.getDataSourceProxy().getResourceId());    return tableMeta;}\n```\n\n\n\n\n\n\n\n\n\nִҵsqlʹ\ncom.alibaba.druid.pool.DruidPooledPreparedStatement#executeִ\n\nȡafterImage![SpringCloud AlibabaϵС17Seata ATģʽԴ£-Դ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/540ff5c254dde029bed208247025e97bd1f497.jpg \"SpringCloud AlibabaϵС17Seata ATģʽԴ£-Դ\")ύʱundo_log־\n\n\n\n```\nprotected T executeAutoCommitTrue(Object[] args) throws Throwable {    ConnectionProxy connectionProxy = statementProxy.getConnectionProxy();    try {        connectionProxy.setAutoCommit(false);        return new LockRetryPolicy(connectionProxy).execute(() -> {            T result = executeAutoCommitFalse(args);            //            connectionProxy.commit();            return result;        });    } catch (Exception e) {        ...    } finally {        connectionProxy.getContext().reset();        connectionProxy.setAutoCommit(true);    }}\n```\n\n\n\n\n\n\n\n\n\n\n\n```\npublic void commit() throws SQLException {    try {        LOCK_RETRY_POLICY.execute(() -> {            //            doCommit();            return null;        });    } catch (SQLException e) {        throw e;    } catch (Exception e) {        throw new SQLException(e);    }}\n```\n\n\n\n\n\n\n\n\n\n\n\n```\nprivate void doCommit() throws SQLException {    if (context.inGlobalTransaction()) {        //        processGlobalTransactionCommit();    } else if (context.isGlobalLockRequire()) {        processLocalCommitWithGlobalLocks();    } else {        targetConnection.commit();    }}\n```\n\n\n\n\n\n\n\n\n\n\n\n```\nprivate void processGlobalTransactionCommit() throws SQLException {    try {        //seata-serverע֧Ϣ        register();    } catch (TransactionException e) {        recognizeLockKeyConflictException(e, context.buildLockKeys());    }    try {        //ύ֮ǰundo_log,flushUndoLogs        UndoLogManagerFactory.getUndoLogManager(this.getDbType()).flushUndoLogs(this);        targetConnection.commit();    } catch (Throwable ex) {       ...    }    if (IS_REPORT_SUCCESS_ENABLE) {        report(true);    }    context.reset();}\n```\n\n\n\n\n\n\n\n\n\n\n\n```\npublic void flushUndoLogs(ConnectionProxy cp) throws SQLException {    ConnectionContext connectionContext = cp.getContext();    if (!connectionContext.hasUndoLog()) {        return;    }     String xid = connectionContext.getXid();    long branchId = connectionContext.getBranchId();     ...//÷undo_log    insertUndoLogWithNormal(xid, branchId, buildContext(parser.getName()), undoLogContent,                            cp.getTargetConnection());}\n```\n\n\n\n\n\n\n\n\n\nڸ÷ע֧![SpringCloud AlibabaϵС17Seata ATģʽԴ£-Դ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/92309a950c73829078a3170c585fc58fb03b0d.jpg \"SpringCloud AlibabaϵС17Seata ATģʽԴ£-Դ\")ύseata-serverע֧Ϣseata-serverյseataԴ룩![SpringCloud AlibabaϵС17Seata ATģʽԴ£-Դ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/c116df5929a1116e61e683298e14b953450bb7.jpg \"SpringCloud AlibabaϵС17Seata ATģʽԴ£-Դ\")\n\nio.seata.server.coordinator.DefaultCoordinator#doBranchRegister\n\n\n\n```\npublic Long branchRegister(BranchType branchType, String resourceId, String clientId, String xid,                           String applicationData, String lockKeys) throws TransactionException {    GlobalSession globalSession = assertGlobalSessionNotNull(xid, false);    return SessionHolder.lockAndExecute(globalSession, () -> {        ...        try {            //ע            globalSession.addBranch(branchSession);        } catch (RuntimeException ex) {            ...        }        ...        return branchSession.getBranchId();    });}\n```\n\n\n\n\n\n\n\n\n\n\n\n```\n@Overridepublic void addBranch(BranchSession branchSession) throws TransactionException {    for (SessionLifecycleListener lifecycleListener : lifecycleListeners) {        //onAddBranchѡAbstractSessionManager        lifecycleListener.onAddBranch(this, branchSession);    }    branchSession.setStatus(BranchStatus.Registered);    add(branchSession);}\n```\n\n\n\n\n\n\n\n\n\nio.seata.server.storage.db.session.DataBaseSessionManager#addBranchSession\n\n\n\n```\n@Overridepublic void onAddBranch(GlobalSession globalSession, BranchSession branchSession) throws TransactionException {    //룬ѡDataBaseSessionManager    addBranchSession(globalSession, branchSession);}\n```\n\n\n\n\n\n\n\n\n\n\n\n```\n@Overridepublic void addBranchSession(GlobalSession globalSession, BranchSession session) throws TransactionException {    if (StringUtils.isNotBlank(taskName)) {        return;    }    //    boolean ret = transactionStoreManager.writeSession(LogOperation.BRANCH_ADD, session);    if (!ret) {        throw new StoreException(\"addBranchSession failed.\");    }}\n```\n\n\n\n\n\n\n\n\n\n\n\n```\n@Overridepublic boolean writeSession(LogOperation logOperation, SessionStorable session) {    if (LogOperation.GLOBAL_ADD.equals(logOperation)) {        return logStore.insertGlobalTransactionDO(SessionConverter.convertGlobalTransactionDO(session));    } else if (LogOperation.GLOBAL_UPDATE.equals(logOperation)) {        return logStore.updateGlobalTransactionDO(SessionConverter.convertGlobalTransactionDO(session));    } else if (LogOperation.GLOBAL_REMOVE.equals(logOperation)) {        return logStore.deleteGlobalTransactionDO(SessionConverter.convertGlobalTransactionDO(session));    } else if (LogOperation.BRANCH_ADD.equals(logOperation)) {        return logStore.insertBranchTransactionDO(SessionConverter.convertBranchTransactionDO(session));    } else if (LogOperation.BRANCH_UPDATE.equals(logOperation)) {        return logStore.updateBranchTransactionDO(SessionConverter.convertBranchTransactionDO(session));    } else if (LogOperation.BRANCH_REMOVE.equals(logOperation)) {        return logStore.deleteBranchTransactionDO(SessionConverter.convertBranchTransactionDO(session));    } else {        throw new StoreException(\"Unknown LogOperation:\" + logOperation.name());    }}\n```\n\n\n\n\n\n\n\n\n\n\n\n```\n@Overridepublic boolean insertBranchTransactionDO(BranchTransactionDO branchTransactionDO) {    String sql = LogStoreSqlsFactory.getLogStoreSqls(dbType).getInsertBranchTransactionSQL(branchTable);    Connection conn = null;    PreparedStatement ps = null;    try {        int index = 1;        conn = logStoreDataSource.getConnection();        conn.setAutoCommit(true);        ps = conn.prepareStatement(sql);        ps.setString(index++, branchTransactionDO.getXid());        ps.setLong(index++, branchTransactionDO.getTransactionId());        ps.setLong(index++, branchTransactionDO.getBranchId());        ps.setString(index++, branchTransactionDO.getResourceGroupId());        ps.setString(index++, branchTransactionDO.getResourceId());        ps.setString(index++, branchTransactionDO.getBranchType());        ps.setInt(index++, branchTransactionDO.getStatus());        ps.setString(index++, branchTransactionDO.getClientId());        ps.setString(index++, branchTransactionDO.getApplicationData());        return ps.executeUpdate() > 0;    } catch (SQLException e) {        throw new StoreException(e);    } finally {        IOUtil.close(ps, conn);    }}\n```\n\n\n\n\n\n\n\n\n\nSeata-serverӷ֧Ϣɣһ׶νҵݣundo_log֧ϢѾдݿ\n\n**2.4.1.3 AT׶ύ**\nصhandleGlobalTransactionУ\ntransactionalTemplate.execute\n\n\n\n```\n// 2\\. ȫbeginTransactionbeginTransaction(txInfo, tx); Object rs = null;try {     // ִҵ񷽷business.execute()    rs = business.execute(); } catch (Throwable ex) {     //һ׶    //Ƕ׶    // 3.쳣ִcompleteTransactionAfterThrowingع    completeTransactionAfterThrowing(txInfo, tx, ex);    throw ex;} // 4\\. û쳣ύcommitTransactioncommitTransaction(tx);\n```\n\n\n\n\n\n\n\n\n\n׶ύ\n\ncommitTransaction(tx);\n\n\n\n```\nprivate void commitTransaction(GlobalTransaction tx) throws TransactionalExecutor.ExecutionException {    try {        triggerBeforeCommit();        //        tx.commit();        triggerAfterCommit();    } catch (TransactionException txe) {        // 4.1 Failed to commit        throw new TransactionalExecutor.ExecutionException(tx, txe,                                                           TransactionalExecutor.Code.CommitFailure);    }}\n```\n\n\n\n\n\n\n\n\n\n![SpringCloud AlibabaϵС17Seata ATģʽԴ£-Դ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/61b35980530bd17bff9025f1112baa9e3e93e5.jpg \"SpringCloud AlibabaϵС17Seata ATģʽԴ£-Դ\")\n\n\n\n```\n@Overridepublic GlobalStatus commit(String xid) throws TransactionException {    GlobalCommitRequest globalCommit = new GlobalCommitRequest();    globalCommit.setXid(xid);    //syncCall    GlobalCommitResponse response = (GlobalCommitResponse) syncCall(globalCommit);    return response.getGlobalStatus();}\n```\n\n\n\n\n\n\n\n\n\n\n\n```\nprivate AbstractTransactionResponse syncCall(AbstractTransactionRequest request) throws TransactionException {    try {        return (AbstractTransactionResponse) TmNettyRemotingClient.getInstance().sendSyncRequest(request);    } catch (TimeoutException toe) {        throw new TmTransactionException(TransactionExceptionCode.IO, \"RPC timeout\", toe);    }}\n```\n\n\n\n\n\n\n\n\n\nͨTMseata-serverSeata-serverյȫύseataԴ룩\n\nDefaultCoordinator\n\n\n\n```\n@Overrideprotected void doGlobalCommit(GlobalCommitRequest request, GlobalCommitResponse response, RpcContext rpcContext)    throws TransactionException {    MDC.put(RootContext.MDC_KEY_XID, request.getXid());    //commit    response.setGlobalStatus(core.commit(request.getXid()));}\n```\n\n\n\n\n\n\n\n\n\n![SpringCloud AlibabaϵС17Seata ATģʽԴ£-Դ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/185262d74acd3a723a7770d5771c19fa9fa5cf.jpg \"SpringCloud AlibabaϵС17Seata ATģʽԴ£-Դ\")![SpringCloud AlibabaϵС17Seata ATģʽԴ£-Դ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/51249f8944b29b9ffc82897a818cb4c33ed06a.png \"SpringCloud AlibabaϵС17Seata ATģʽԴ£-Դ\")![SpringCloud AlibabaϵС17Seata ATģʽԴ£-Դ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/14a2f2b58ad3f11be518376d5e6f68cc678af9.jpg \"SpringCloud AlibabaϵС17Seata ATģʽԴ£-Դ\")\nSeata-serverյͻȫύȻصͻˣɾundo_logseataɾ֧ȫ\n\n֮ǰ˵RMClientڳʼʱԴresourceManagerϢصڽTCڶ׶ηύ߻ع![SpringCloud AlibabaϵС17Seata ATģʽԴ£-Դ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/884c07085b1230a1aab1883d691532ccf6b79c.png \"SpringCloud AlibabaϵС17Seata ATģʽԴ£-Դ\")Seata-serverɾ֧ݼȫ\n\n\n\n```\n@Overridepublic void removeBranch(BranchSession branchSession) throws TransactionException {    // do not unlock if global status in (Committing, CommitRetrying, AsyncCommitting),    // because it's already unlocked in 'DefaultCore.commit()'    if (status != Committing && status != CommitRetrying && status != AsyncCommitting) {        if (!branchSession.unlock()) {            throw new TransactionException(\"Unlock branch lock failed, xid = \" + this.xid + \", branchId = \" + branchSession.getBranchId());        }    }    for (SessionLifecycleListener lifecycleListener : lifecycleListeners) {        //        lifecycleListener.onRemoveBranch(this, branchSession);    }    remove(branchSession);}\n```\n\n\n\n\n\n\n\n\n\n\n\n```\nprivate void writeSession(LogOperation logOperation, SessionStorable sessionStorable) throws TransactionException {    if (!transactionStoreManager.writeSession(logOperation, sessionStorable)) {        if (LogOperation.GLOBAL_ADD.equals(logOperation)) {            throw new GlobalTransactionException(TransactionExceptionCode.FailedWriteSession,                                                 \"Fail to store global session\");        } else if (LogOperation.GLOBAL_UPDATE.equals(logOperation)) {            throw new GlobalTransactionException(TransactionExceptionCode.FailedWriteSession,                                                 \"Fail to update global session\");        } else if (LogOperation.GLOBAL_REMOVE.equals(logOperation)) {            throw new GlobalTransactionException(TransactionExceptionCode.FailedWriteSession,                                                 \"Fail to remove global session\");        } else if (LogOperation.BRANCH_ADD.equals(logOperation)) {            throw new BranchTransactionException(TransactionExceptionCode.FailedWriteSession,                                                 \"Fail to store branch session\");        } else if (LogOperation.BRANCH_UPDATE.equals(logOperation)) {            throw new BranchTransactionException(TransactionExceptionCode.FailedWriteSession,                                                 \"Fail to update branch session\");        } else if (LogOperation.BRANCH_REMOVE.equals(logOperation)) {            throw new BranchTransactionException(TransactionExceptionCode.FailedWriteSession,                                                 \"Fail to remove branch session\");        } else {            throw new BranchTransactionException(TransactionExceptionCode.FailedWriteSession,                                                 \"Unknown LogOperation:\" + logOperation.name());        }    }}\n```\n\n\n\n\n\n\n\n\n\n\n\n```\npublic static void endCommitted(GlobalSession globalSession) throws TransactionException {    globalSession.changeStatus(GlobalStatus.Committed);    //ɾȫ    globalSession.end();}\n```\n\n\n\n\n\n\n\n\n\nͻɾundo_log\n\nڽύ\n\n\n\n```\nprotected void doBranchCommit(BranchCommitRequest request, BranchCommitResponse response)    throws TransactionException {    String xid = request.getXid();    long branchId = request.getBranchId();    String resourceId = request.getResourceId();    String applicationData = request.getApplicationData();    if (LOGGER.isInfoEnabled()) {        LOGGER.info(\"Branch committing: \" + xid + \" \" + branchId + \" \" + resourceId + \" \" + applicationData);    }    //    BranchStatus status = getResourceManager().branchCommit(request.getBranchType(), xid, branchId, resourceId,                                                            applicationData);    response.setXid(xid);    response.setBranchId(branchId);    response.setBranchStatus(status);    if (LOGGER.isInfoEnabled()) {        LOGGER.info(\"Branch commit result: \" + status);    } }\n```\n\n\n\n\n\n\n\n\n\ngetResourceManagerȡľRMClientʼʱõԴDataSourceManager\n\n\n\n```\npublic BranchStatus branchCommit(BranchType branchType, String xid, long branchId, String resourceId,                                 String applicationData) throws TransactionException {    return asyncWorker.branchCommit(branchType, xid, branchId, resourceId, applicationData);}\n```\n\n\n\n\n\n\n\n\n\n\n\n```\n@Overridepublic BranchStatus branchCommit(BranchType branchType, String xid, long branchId, String resourceId,                                 String applicationData) throws TransactionException {    if (!ASYNC_COMMIT_BUFFER.offer(new Phase2Context(branchType, xid, branchId, resourceId, applicationData))) {        LOGGER.warn(\"Async commit buffer is FULL. Rejected branch [{}/{}] will be handled by housekeeping later.\", branchId, xid);    }    return BranchStatus.PhaseTwo_Committed;}\n```\n\n\n\n\n\n\n\n\n\nֻһASYNC_COMMIT_BUFFERListһ׶ύcontextύAsyncWorkerinit()\n\n\n\n```\npublic synchronized void init() {    LOGGER.info(\"Async Commit Buffer Limit: {}\", ASYNC_COMMIT_BUFFER_LIMIT);    ScheduledExecutorService timerExecutor = new ScheduledThreadPoolExecutor(1, new NamedThreadFactory(\"AsyncWorker\", 1, true));    timerExecutor.scheduleAtFixedRate(() -> {        try {//            doBranchCommits();         } catch (Throwable e) {            LOGGER.info(\"Failed at async committing ... {}\", e.getMessage());         }    }, 10, 1000 * 1, TimeUnit.MILLISECONDS);}\n```\n\n\n\n\n\n\n\n\n\n![SpringCloud AlibabaϵС17Seata ATģʽԴ£-Դ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/a730eb7781ec4bd3ad2578fb7855aa02d45fbc.jpg \"SpringCloud AlibabaϵС17Seata ATģʽԴ£-Դ\")ɾUndo_log\n\n**׶λع**\n\n׶λعseata-server˴׶ύƣʡ\n\n\n\n```\nprotected void doGlobalRollback(GlobalRollbackRequest request, GlobalRollbackResponse response,                                RpcContext rpcContext) throws TransactionException {    MDC.put(RootContext.MDC_KEY_XID, request.getXid());    //ȫֻعsea    response.setGlobalStatus(core.rollback(request.getXid()));}\n```\n\n\n\n\n\n\n\n\n\nҪعͻν񲹳\n\n\n\n```\n@Overridepublic BranchRollbackResponse handle(BranchRollbackRequest request) {    BranchRollbackResponse response = new BranchRollbackResponse();    exceptionHandleTemplate(new AbstractCallback<BranchRollbackRequest, BranchRollbackResponse>() {        @Override        public void execute(BranchRollbackRequest request, BranchRollbackResponse response)            throws TransactionException {            //            doBranchRollback(request, response);        }    }, request, response);    return response;}\n```\n\n\n\n\n\n\n\n\n\n\n\n```\npublic BranchStatus branchRollback(BranchType branchType, String xid, long branchId, String resourceId,                                   String applicationData) throws TransactionException {    DataSourceProxy dataSourceProxy = get(resourceId);    if (dataSourceProxy == null) {        throw new ShouldNeverHappenException();    }    try {        UndoLogManagerFactory.getUndoLogManager(dataSourceProxy.getDbType()).undo(dataSourceProxy, xid, branchId);    } catch (TransactionException te) {        StackTraceLogger.info(LOGGER, te,                              \"branchRollback failed. branchType:[{}], xid:[{}], branchId:[{}], resourceId:[{}], applicationData:[{}]. reason:[{}]\",                              new Object[]{branchType, xid, branchId, resourceId, applicationData, te.getMessage()});        if (te.getCode() == TransactionExceptionCode.BranchRollbackFailed_Unretriable) {            return BranchStatus.PhaseTwo_RollbackFailed_Unretryable;        } else {            return BranchStatus.PhaseTwo_RollbackFailed_Retryable;        }    }    return BranchStatus.PhaseTwo_Rollbacked; }\n```\n\n\n\n\n\n\n\n\n\nջعõUndoLogManager.undo(dataSourceProxy, xid, branchId);![SpringCloud AlibabaϵС17Seata ATģʽԴ£-Դ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/4842a0701546824cf2720855d8310a1274c576.jpg \"SpringCloud AlibabaϵС17Seata ATģʽԴ£-Դ\")жundologǷڣɾӦundologһύseataATģʽԴϡ\n\n# ο\nhttps://lijunyi.xyz/docs/SpringCloud/SpringCloud.html#_2-2-x-%E5%88%86%E6%94%AF\nhttps://mp.weixin.qq.com/s/2jeovmj77O9Ux96v3A0NtA\nhttps://juejin.cn/post/6931922457741770760\nhttps://github.com/D2C-Cai/herring\nhttp://c.biancheng.net/springcloud\nhttps://github.com/macrozheng/springcloud-learning"
  },
  {
    "path": "docs/Spring全家桶/SpringCloudAlibaba源码分析/SpringCloudSentinel源码分析.md",
    "content": " | һߣJava\nԴ |ͷ\n\n**ѧϰĿ**\n\n*   SentinelĹԭ\n    **1 ԭ**\n    SentinelУеԴӦһԴԼһEntryÿһentryԱʾһ󡣶SentinelУԵǰڹжʵصĿƣԭͼʾ\n\n![SpringCloud AlibabaϵС15Sentinelԭ-Դ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1296c955070646bbc74310e726679bbabfe585.jpg \"SpringCloud AlibabaϵС15Sentinelԭ-Դ\")ͼΪ˼չʾͼ Slot ˳Ѻ° Sentinel Slot Chain ˳һ\nһⲿ֮󣬻ᴴһEntryEntryͬʱҲᴴһϵеslot һÿslotвͬĹְ\n\n*   NodeSelectorSlot ռԴ·ЩԴĵ·״ṹ洢ڸݵ·\n*   ClusterBuilderSlot ڴ洢ԴͳϢԼϢԴ RT, QPS,thread count ȵȣЩϢΪάݣ\n*   StatisticSlot ڼ¼ͳƲͬγȵ runtime ָϢ\n*   FlowSlot ڸԤԼǰ slot ͳƵ״̬ƣ\n*   AuthoritySlot õĺڰ͵ԴϢڰƣ\n*   DegradeSlot ͨͳϢԼԤĹ۶Ͻ\n*   SystemSlot ͨϵͳ״̬ load1 ȣܵ\n*   LogSlot ڳ۶ϡϵͳʱ¼־\n*   ...\n    Sentinel  ProcessorSlot Ϊ SPI ӿڽչ1.7.2 汾ǰ SlotChainBuilder ΪSPIʹ Slot Chain ߱չмԶ slot  slot ˳򣬴ӶԸ Sentinel ԶĹܡ\n\n![SpringCloud AlibabaϵС15Sentinelԭ-Դ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/d65c2688084bf5be06d3687ce8663cb1b7167b.jpg \"SpringCloud AlibabaϵС15Sentinelԭ-Դ\")**Spring Cloud Sentinelԭ**\n\nSpring Cloud мSentinelǻʵ֣ʵ·¡\n\nSentinelWebAutoConfiguration>addInterceptors>SentinelWebInterceptor->AbstractSentinelInterceptor\n\n\n\n```\npublic boolean preHandle(HttpServletRequest request, HttpServletResponseresponse, Object handler) throws Exception {  try {    String resourceName = this.getResourceName(request);    if (StringUtil.isEmpty(resourceName)) {      return true;   } else if (this.increaseReferece(request,this.baseWebMvcConfig.getRequestRefName(), 1) != 1) {      return true;   } else {      String origin = this.parseOrigin(request);      String contextName = this.getContextName(request);      ContextUtil.enter(contextName, origin);      Entry entry = SphU.entry(resourceName, 1, EntryType.IN);     request.setAttribute(this.baseWebMvcConfig.getRequestAttributeName(), entry);      return true;   } } catch (BlockException var12) {    BlockException e = var12;    try {      this.handleBlockException(request, response, e);   } finally {      ContextUtil.exit();   }    return false; }}\n```\n\n\n\n\n\n\n\n\n\n> Դõͣ EntryType.IN ǳ EntryType.OUT עϵͳֻ IN Ч\n\n**2 SphU.entry**\nǼdubboҲãǼɵspring cloudҲãնǵSphU.entryжϵģǴSphU.entryȥ˽ʵԭ\n\nǿΨһɻģҲؼһ SphU.entry(resource)  ǴȥһԴԴǷǽӿڣôʲôأһҿɴ\n\n\n\n```\npublic static Entry entry(String name) throws BlockException {    return Env.sph.entry(name, EntryType.OUT, 1, OBJECTS0);}public class Env {    public static final Sph sph = new CtSph();    ......//ʡԲִ}\n```\n\n\n\n\n\n\n\n\n\n SphU.entry() ִл뵽 Sph.entry() SphĬʵ CtSph,ջCtSph entry \n\n\n\n```\n@Overridepublic Entry entry(String name, EntryType type, int count, Object... args) throws BlockException { //װһԴ    StringResourceWrapper resource = new StringResourceWrapper(name, type);    return entry(resource, count, args);}\n```\n\n\n\n\n\n\n\n\n\nҪͨǸԴȥװһ StringResourceWrapper ȻԼط̶ entryWithPriority(resourceWrapper, count, false, args)\n\n*   ResourceWrapper ʾsentinelԴ˷װ\n*   countʾռõĲĬ1\n*   prioritizedȼ\n\n\n\n```\nprivate Entry entryWithPriority(ResourceWrapper resourceWrapper, int count,boolean prioritized, Object... args)    throws BlockException {    //ȡĻ洢ThreadLocalУcontextл洢    Context context = ContextUtil.getContext();    // NullContextô˵ context name  2000 μ ContextUtil#trueEnter    //ʱSentinel ٽܴµ context ãҲǲЩµĽӿڵͳơ۶ϵ    if (context instanceof NullContext) {        // The {@link NullContext} indicates that the amount of context has exceeded the threshold,        // so here init the entry only. No rule checking will be done.        return new CtEntry(resourceWrapper, null, context);    }    if (context == null) {//ʹĬcontext        // ContextĲ        context = InternalContextUtil.internalEnter(Constants.CONTEXT_DEFAULT_NAME);    }    // Global switch is close, no rule checking will do.    if (!Constants.ON) {//ȫǷѾرˣͲ        return new CtEntry(resourceWrapper, null, context);    }    //ģʽеģʽ    //һslot    ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);    // lookProcessChain ֪ resource  Constants.MAX_SLOT_CHAIN_SIZE    // Ҳ 6000 ʱSentinel ʼµôҪΪ Sentinel ܿ    if (chain == null) {        return new CtEntry(resourceWrapper, null, context);    }    //ʼɸentry    Entry e = new CtEntry(resourceWrapper, chain, context);    try {        //ʼ        chain.entry(context, resourceWrapper, null, count, prioritized, args);    } catch (BlockException e1) {        e.exit(count, args); //׳쳣        throw e1;    } catch (Throwable e1) {        // This should not happen, unless there are errors existing in Sentinel internal.        RecordLog.info(\"Sentinel unexpected exception\", e1);    }    return e;//Ľ}\n```\n\n\n\n\n\n\n\n\n\nĴǿ֪÷Ҫǻȡ˱ԴӦԴ lookProcessChain з֣ȥȡһȥִԴϴȻﴦĻ£ô϶ǶڵǰصĴԷΪ¼֣\n\n*   Բȫ⣬ҪֱӷһCtEntry󣬲ٽк⣬ļ̡ݰװԴȡӦSlotChain\n*   ִSlotChainentrySlotChainentry׳BlockException򽫸쳣׳SlotChainentryִˣὫentry󷵻\n*   ϲ㷽BlockException˵ˣִ\n    **2.1 Context**\n    InternalContextUtil.internalEnter--->trueEnter\n\n\n\n```\nprotected static Context trueEnter(String name, String origin) {    //ThreadLocalлȡһο϶null    Context context = contextHolder.get();    if (context == null) {        //ǸContextֻȡNode        Map<String, DefaultNode> localCacheNameMap = contextNameNodeMap;        DefaultNode node = localCacheNameMap.get(name);        if (node == null) {            if (localCacheNameMap.size() > Constants.MAX_CONTEXT_NAME_SIZE) {                setNullContext();                return NULL_CONTEXT;            } else {                LOCK.lock();                try {                    node = contextNameNodeMap.get(name);                    if (node == null) {                        if (contextNameNodeMap.size() > Constants.MAX_CONTEXT_NAME_SIZE) {                            setNullContext();                            return NULL_CONTEXT;                        } else {                            //EntranceNode                            node = new EntranceNode(new StringResourceWrapper(name, EntryType.IN), null);                            //ȫֵĽڵ                            // Add entrance node.                            Constants.ROOT.addChild(node);//map                            Map<String, DefaultNode> newMap = new HashMap<>(contextNameNodeMap.size() + 1);                            newMap.putAll(contextNameNodeMap);                            newMap.put(name, node);                            contextNameNodeMap = newMap;                        }                    }                } finally {                    LOCK.unlock();                }            }        }        context = new Context(node, name);        context.setOrigin(origin);        //ThreadLocal        contextHolder.set(context);    }    return context;}\n```\n\n\n\n\n\n\n\n\n\n߼ǱȽϼ򵥵\n\n*   ThreadLocalȡȡʹȻͷ\n*   ȻMapиContextNameһNode\n*   ûҵNodeͼķʽһEntranceNodeȻMap\n*   ContextnodenameoriginٷThreadLocal\n    Contextʹ\n\nĿǰContext״̬ͼ![SpringCloud AlibabaϵС15Sentinelԭ-Դ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/a114ee154527b7fcf24169d5291c7bac87ac93.png \"SpringCloud AlibabaϵС15Sentinelԭ-Դ\")**2.2 slot**\nһslot·Ϊ\n\n> DefaultProcessorSlotChain -> NodeSelectorSlot -> ClusterBuilderSlot -> LogSlot ->StatisticSlot -> AuthoritySlot -> SystemSlot -> ParamFlowSlot -> FlowSlot -> DegradeSlot\n\n\n\n```\nProcessorSlot<Object> lookProcessChain(ResourceWrapper resourceWrapper) {    //ԿchainԴΪkeyͬԴ϶ǲͬchain    ProcessorSlotChain chain = chainMap.get(resourceWrapper);    if (chain == null) {////spring(bean) dubbo(˫ؼ)һޣû        synchronized (LOCK) {            chain = chainMap.get(resourceWrapper);            if (chain == null) {                //chainMapСһֵҲentryСˣһchainӦһentry                if (chainMap.size() >= Constants.MAX_SLOT_CHAIN_SIZE) {                    return null;                }                //һslot chain                chain = SlotChainProvider.newSlotChain();                //߼ǣ½һMapСoldMap+1                Map<ResourceWrapper, ProcessorSlotChain> newMap = new                    HashMap<ResourceWrapper, ProcessorSlotChain>(                    chainMap.size() + 1);                //ȻoldMapٷ½chain                newMap.putAll(chainMap);                newMap.put(resourceWrapper, chain); //ӵnewMap ӦǿǱƵ                chainMap = newMap;            }        }    }    return chain;}\n```\n\n\n\n\n\n\n\n\n\nĴԷ֣ȴӻлȡôһν϶ûеģ SlotChainProvider ȥ촦ɺ뻺Ա´ʹã\n\n\n\n```\npublic static ProcessorSlotChain newSlotChain() {    if (slotChainBuilder != null) {        return slotChainBuilder.build();    }    // ͨspiȥԼslotĻֻҪSPIʵSlotChainBuilderӿھͺ    //SentinelĬϵsentinel-coreµMETA-INF.services    slotChainBuilder = SpiLoader.loadFirstInstanceOrDefault(SlotChainBuilder.class, DefaultSlotChainBuilder.class);    if (slotChainBuilder == null) {        // Should not go through here.        RecordLog.warn(\"[SlotChainProvider] Wrong state when resolving slot chain builder, using default\");        slotChainBuilder = new DefaultSlotChainBuilder();    } else {        RecordLog.info(\"[SlotChainProvider] Global slot chain builder resolved: \"                       + slotChainBuilder.getClass().getCanonicalName());    }    return slotChainBuilder.build();}\n```\n\n\n\n\n\n\n\n\n\n˶εУ飬ȷbuilder ΪգȻͨȥ\n\n\n\n```\npublic class DefaultSlotChainBuilder implements SlotChainBuilder {    @Override    public ProcessorSlotChain build() {        ProcessorSlotChain chain = new DefaultProcessorSlotChain();        chain.addLast(new NodeSelectorSlot());        chain.addLast(new ClusterBuilderSlot());        chain.addLast(new LogSlot());        chain.addLast(new StatisticSlot());        chain.addLast(new SystemSlot());        chain.addLast(new AuthoritySlot());        chain.addLast(new FlowSlot());        chain.addLast(new DegradeSlot());        return chain;    }}\n```\n\n\n\n\n\n\n\n\n\nڷҲж˵ϾSentinel㷨ʵָأǿһ¹Ľܣ\n\n Sentinel 棬еԴӦһԴƣresourceNameÿԴöᴴһ Entry Entry ͨܵԶҲͨעķʽ SphU API ʽEntry ʱͬʱҲᴴһϵйۣܲslot chainЩвְͬ𡣾ְѾᵽˡ\n\n****\n\nִ¡\n\n* NodeSelectorSlotҪڹ\n\n* ClusterBuilderSlotڼȺ۶ϡ\n\n* LogSlotڼ¼־\n\n* StatisticSlotʵʱռʵʱϢ\n\n* AuthoritySlotȨУġ\n\n* SystemSlot֤ϵͳĹ\n\n* FlowSlotʵơ\n\n* DegradeSlotʵ۶ϻơ\n  **2.3 Entry**\n\n  > Entry e = new CtEntry(resourceWrapper, chain, context);\n\n\n\n```\nCtEntry(ResourceWrapper resourceWrapper, ProcessorSlot<Object> chain, Context context) {    super(resourceWrapper);    this.chain = chain;    this.context = context;    setUpEntryFor(context);}private void setUpEntryFor(Context context) {    // The entry should not be associated to NullContext.    if (context instanceof NullContext) {        return;    }    this.parent = context.getCurEntry();    if (parent != null) {        ((CtEntry) parent).child = this;    }    context.setCurEntry(this);}\n\n```\n\n\n\n\n\n\n\n\n\nһEntryɵʱcontext.getCurEntryضNULLôֱִContext.setCurEntry\n\nȻContext״̬ͼ  ![SpringCloud AlibabaϵС15Sentinelԭ-Դ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/57273a794d51cfc587c923de97943491e9da0b.jpg \"SpringCloud AlibabaϵС15Sentinelԭ-Դ\")ִһµSphu.entryٴ½һEntryʱcurEntrynullôִ((CtEntry)parent).child = this;\n\nͼ![SpringCloud AlibabaϵС15Sentinelԭ-Դ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/c3c36f101ef031102dd270287bf7635e715229.jpg \"SpringCloud AlibabaϵС15Sentinelԭ-Դ\")ԿԭCtEntryƳContext½CtEntry;CtEntryͨڲparentchild\n\n**2.4 NodeSelectorSlot**\nҪڹҪһ£ںлȽϹؼ¡\n\n\n\n```\n@Overridepublic void entry(Context context, ResourceWrapper resourceWrapper, Object obj,                  int count, boolean prioritized, Object... args)    throws Throwable {    //и棬contextֻnode    DefaultNode node = map.get(context.getName());    //˫ؼ⣬̰߳ȫ    if (node == null) {        synchronized (this) {            node = map.get(context.getName());            if (node == null) {                //ɵDefaultNodeڵ                node = new DefaultNode(resourceWrapper, null);                //Щ߼Ƿmap߼ΪmapȽϴ룬ܻһЩ                HashMap<String, DefaultNode> cacheMap = new HashMap<String,DefaultNode>(map.size());                cacheMap.putAll(map);                cacheMap.put(context.getName(), node);                map = cacheMap;                // ؼ⣬޸ĵĵط                ((DefaultNode) context.getLastNode()).addChild(node);            }        }    }    //滻contextеcurEntryеcurNode    context.setCurNode(node);    fireEntry(context, resourceWrapper, node, count, prioritized, args);}\n```\n\n\n\n\n\n\n\n\n\nѯǷnode߼Ҳܼ\n\n*   ContextNameѯǷNode\n*   ûоDefaultNode뻺棬Ȼ\n*   ContextcurEntryеcurnodeΪnode\n    мҪ˵\n\n1contextʾģһ̶߳ӦһcontextаһЩ\n\n*   name\n*   entranceNode\n*   curEntryǰentry\n*   originԴ\n*   async첽\n    2Node ʾһڵ㣬ڵᱣĳԴĸʵʱͳݣͨĳڵ㣬ͿԻöӦԴʵʱ״̬Ϣͽмֽڵ\n*   StatisticNodeʵNodeӿڣװ˻ͳƺͻȡ\n*   DefaultNodeĬϽڵ㣬NodeSelectorSlotдľڵ㣻ͬԴڲͬиԵ\n*   ClusterNodeȺڵ㣬ͬԴڲͬ\n*   EntranceNodeýڵʾһõڽڵ㣬ͨԻȡеӽڵ㣻ÿĶһڽڵ㣬ͳƵǰĵ\n*   OriginNodeһStatisticNode͵Ľڵ㣬ͬԴԴ\n    **2.5 StatisticSlot**\n    slot·УȽҪģͳԼslotһStatisticSlot\n\nStatisticSlot Sentinel ĺĹ֮ܲһͳʵʱĵݡ\n\n*   clusterNodeԴΨһʶ ClusterNode  runtime ͳ\n*   originԲͬߵͳϢ\n*   defaultnode: ĿƺԴ ID  runtime ͳ\n*   ڵͳ\n\n\n\n```\npublic void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,                  boolean prioritized, Object... args) throws Throwable {    try {        // Ƚɺ&processorSlotȻݴͳ        // Sentinelľʹ for ѭ ProcessorSlot ԭ        fireEntry(context, resourceWrapper, node, count, prioritized, args);        //ִеʾͨ飬        // Request passed, add thread count and pass count.        node.increaseThreadNum(); //ǰڵ߳1        node.addPassRequest(count);        //Բͬ͵node¼߳ͨͳơ        if (context.getCurEntry().getOriginNode() != null) {            // Add count for origin node.            context.getCurEntry().getOriginNode().increaseThreadNum();            context.getCurEntry().getOriginNode().addPassRequest(count);        }        if (resourceWrapper.getEntryType() == EntryType.IN) {            // Add count for global inbound entry node for global statistics.            Constants.ENTRY_NODE.increaseThreadNum();            Constants.ENTRY_NODE.addPassRequest(count);        }        //ɵ StatisticSlotCallbackRegistry#addEntryCallback ̬עProcessorSlotEntryCallback        for (ProcessorSlotEntryCallback<DefaultNode> handler :             StatisticSlotCallbackRegistry.getEntryCallbacks()) {            handler.onPass(context, resourceWrapper, node, count, args);        }        //ȼȴ쳣FlowRuleл漰    } catch (PriorityWaitException ex) {//߳ͳ        node.increaseThreadNum();        if (context.getCurEntry().getOriginNode() != null) {            // Add count for origin node.            context.getCurEntry().getOriginNode().increaseThreadNum();        }        if (resourceWrapper.getEntryType() == EntryType.IN) {            // Add count for global inbound entry node for global statistics.            Constants.ENTRY_NODE.increaseThreadNum();        }        // Handle pass event with registered entry callback handlers.        for (ProcessorSlotEntryCallback<DefaultNode> handler :             StatisticSlotCallbackRegistry.getEntryCallbacks()) {            handler.onPass(context, resourceWrapper, node, count, args);        }    } catch (BlockException e) {        // Blocked, set block exception to current entry.        context.getCurEntry().setBlockError(e); //쳣ǰentry        // Add block count.        node.increaseBlockQps(count); //ӱ        //ݲͬNodeĴ        if (context.getCurEntry().getOriginNode() != null) {            context.getCurEntry().getOriginNode().increaseBlockQps(count);        }        if (resourceWrapper.getEntryType() == EntryType.IN) {            // Add count for global inbound entry node for global statistics.            Constants.ENTRY_NODE.increaseBlockQps(count);        }        // Handle block event with registered entry callback handlers.        for (ProcessorSlotEntryCallback<DefaultNode> handler :             StatisticSlotCallbackRegistry.getEntryCallbacks()) {            handler.onBlocked(e, context, resourceWrapper, node, count, args);        }        throw e;    } catch (Throwable e) {        // Unexpected internal error, set error to current entry.        context.getCurEntry().setError(e);        throw e;    }}\n```\n\n\n\n\n\n\n\n\n\nֳ֣һentry÷ȻᴥslotentrySystemSlotFlowSlotDegradeSlotȵĹͨͻ׳BlockExceptionnodeͳƱblock֮nodeͳͨ߳ϢڶexitУ˳Entryʱͳrtʱ䣬߳\n\nǿԿ node.addPassRequest() δfireEntryִִ֮еģζţǰͨsentinelصȹ򣬴ʱҪ¼Ҳִ node.addPassRequest()д룬Ǹȥ\n\n**2.5.1 addPassRequest**\n@Overridepublic void addPassRequest(int count) {    // øࣨStatisticNodeͳ    super.addPassRequest(count);    // clusterNode ͳƣҲǵøStatisticNode    this.clusterNode.addPassRequest(count);}\n֪nodeһ DefaultNode ʵڵһNodeSelectorSlot entryжԴ˷װװһDefaultNode\n\n*   DefaultNodeĳresourceĳcontextеʵʱָ꣬ÿDefaultNodeָһClusterNode\n*   ClusterNodeĳresourceеcontextʵʱָܺͣͬresourceṲͬһClusterNodeĸcontext\n    ֱʱ䴰\n\nڲʵʵõArrayMetricͳ\n\n\n\n```\n//ͳƣֳڣÿ500msͳQPSprivate transient volatile Metric rollingCounterInSecond = new ArrayMetric(SampleCountProperty.SAMPLE_COUNT,        IntervalProperty.INTERVAL);//շͳƣ60ڣÿ1000msprivate transient Metric rollingCounterInMinute = new ArrayMetric(60, 60 * 1000, false);public void addPassRequest(int count) {    rollingCounterInSecond.addPass(count);    rollingCounterInMinute.addPass(count);}\n```\n\n\n\n\n\n\n\n\n\nõǻڵķʽ¼Ĵ![SpringCloud AlibabaϵС15Sentinelԭ-Դ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/e4624bb214fd8020d1447098c8cde98a554e59.jpg \"SpringCloud AlibabaϵС15Sentinelԭ-Դ\")ĹϵͼʵǱȽģArrayMetricʵһװ࣬ڲͨLeapArrayʵ־ͳ߼LeapArrayά˶WindowWrapڣWindowWrapвMetricBucketָݵͳơ\n\n*   Metric: ָռĽӿڣ廬гɹ쳣TPSӦʱ\n*   ArrayMetric ںʵ\n*   LeapArray\n*   WindowWrap ÿһڵİװ࣬ڲݽṹMetricBucket\n*   MetricBucket ʾָͰ쳣ɹӦʱ\n*   MetricEvent ָͣͨ쳣ɹ\n    **2.5.2 ArrayMetric.addPass**\n    Ŵ¿뵽ArrayMetric.addPass\n*   LeapArrayиݵǰʱõӦĴ\n*   MetricBucketеaddPassӵǰеͳƴ\n\nӴǿԿָ addPass ͨһ ArrayMetric ࣬ڽ ArrayMetric пһ¡Ĵʾ\n\n\n\n```\nprivate final LeapArray<MetricBucket> data;// SAMPLE_COUNT=2  INTERVAL=1000public ArrayMetric(int sampleCount, int intervalInMs) {  //ʾڵĴС2ÿһڵʱ䵥λ500ms    this.data = new OccupiableBucketLeapArray(sampleCount, intervalInMs);}public void addPass(int count) {    WindowWrap<MetricBucket> wrap = data.currentWindow();    wrap.value().addPass(count);}\n```\n\n\n\n\n\n\n\n\n\nڳ뻬ô windowˣwindowǴͨ data ȡǰڡĴڴСΪ sampleCount=2.ǿԿͨ MetricBucket ָ꣬άһͳLongAdder[] counters 棬 WindowWrapǿԿÿһ WindowWrapɣ\n\n\n\n```\npublic class WindowWrap<T> {// ʱ䴰ڵĳ    private final long windowLengthInMs;// ʱ䴰ڵĿʼʱ䣬λǺ    private long windowStart; //ʱ䴰ڵݣ WindowWrap ÷ͱʾֵģʵϾ MetricBucket     private T value;    //......ʡԲִ}\n```\n\n\n\n\n\n\n\n\n\nٿ LeapArray ࣺ\n\n\n\n```\npublic abstract class LeapArray<T> {    // ʱ䴰ڵĳ    protected int windowLength;    // ڵĸ    protected int sampleCount;    // ԺΪλʱ    protected int intervalInMs;    // ʱ䴰    protected AtomicReferenceArray<WindowWrap<T>> array;    /**     * LeapArray     * @param windowLength ʱ䴰ڵĳȣλ     * @param intervalInSec ͳƵļλ     */    public LeapArray(int windowLength, int intervalInSec) {        this.windowLength = windowLength;        // ʱ䴰ڵĲĬΪ2        this.sampleCount = intervalInSec * 1000 / windowLength;        this.intervalInMs = intervalInSec * 1000;//Ϊλʱ䴰Уʼȵ飺`AtomicReferenceArray<WindowWrap<T>>array`ʾڵĴСУÿڻռ500msʱ䡣        this.array = new AtomicReferenceArray<WindowWrap<T>>(sampleCount);    }}\n```\n\n\n\n\n\n\n\n\n\nԺĿ LeapArray дһ AtomicReferenceArray 飬ʱ䴰еͳֵвͨͳֵټƽֵҪյʵʱֵָˡԿĴͨעͣĬϲʱ䴰ڵĸ2ֵôõأǻһ LeapArray 󴴽ͨ StatisticNode Уnewһ ArrayMetricȻ󽫲һ·ϴݺ󴴽ģ\n\n\n\n```\nprivate transient volatile Metric rollingCounterInSecond = new ArrayMetric(SampleCountProperty.SAMPLE_COUNT,IntervalProperty.INTERVAL);\n```\n\n\n\n\n\n\n\n\n\n**2.5.3 currentWindow**\nǸȡǰڵķ data.currentWindow() У\n\n\n\n```\n@Overridepublic WindowWrap<Window> currentWindow(long time) {    .....//ʡԲִ    //㵱ǰʱڻе㷽ʽȽϼ򵥣ǰʱԵʱ䴰ڵʱ䳤ȣٴʱ䴰ڳȽȡģ int idx = calculateTimeIdx(timeMillis);    //㵱ǰʱʱ䴰еĿʼʱ    long windowStart = calculateWindowStart(timeMillis);    // timeÿһwindowLengthĳȣtimeIdͻ1ʱ䴰ھͻǰһ        while (true) {        // Ӳиȡʱ䴰        WindowWrap<Window> old = array.get(idx);        // array鳤Ȳ˹󣬷oldܶ¶вˣͻᴴܶWindowWrap        //Ϊգ˵˴δʼ        if (old == null) {            // ûлȡ򴴽һµ            WindowWrap<Window> window = new WindowWrap<Window>(windowLength, currentWindowStart, new Window());            // ͨCAS´õȥ            if (array.compareAndSet(idx, null, window)) {                // óɹ򽫸ôڷ                return window;            } else {                // ǰ߳óʱƬȴ                Thread.yield();            }        // ǰڵĿʼʱoldĿʼʱȣֱӷold        } else if (currentWindowStart == old.windowStart()) {            return old;        // ǰʱ䴰ڵĿʼʱѾoldڵĿʼʱ䣬old        // timeΪµʱ䴰ڵĿʼʱ䣬ʱǰ        } else if (currentWindowStart > old.windowStart()) {            if (addLock.tryLock()) {                try {                    // if (old is deprecated) then [LOCK] resetTo currentTime.                    return resetWindowTo(old, currentWindowStart);                } finally {                    addLock.unlock();                }            } else {                Thread.yield();            }        // ܴ        } else if (currentWindowStart < old.windowStart()) {            // Cannot go through here.            return new WindowWrap<Window>(windowLength, currentWindowStart, new Window());        }    }}\n```\n\n\n\n\n\n\n\n\n\nܳ𲽽ֽ⣬ʵʿ԰ֳ¼\n\n1.  ݵǰʱ䣬ʱtimeIdtimeIdǰڲеidx\n2.  ݵǰʱǰڵӦöӦĿʼʱtimeԺΪλ\n3.  idxڲȡһʱ䴰ڡ\n4.  ѭжֱȡһǰʱ䴰 old \n\n*   oldΪգ򴴽һʱ䴰ڣ뵽arrayĵidxλãarrayѾˣһ AtomicReferenceArray\n*   ǰڵĿʼʱtimeoldĿʼʱȣô˵oldǵǰʱ䴰ڣֱӷold\n*   ǰڵĿʼʱtimeoldĿʼʱ䣬˵oldѾʱˣoldĿʼʱΪֵtimeһεѭжϵǰڵĿʼʱtimeoldĿʼʱȵʱ򷵻ء\n*   ǰڵĿʼʱtimeСoldĿʼʱ䣬ʵǲܴڵģΪtimeǵǰʱ䣬oldǹȥһʱ䡣\n    timeIdǻʱӣǰʱÿһwindowLengthĳȣtimeIdͼ1idxֻ01֮任Ϊarrayĳ2ֻʱ䴰ڡΪʲôĬֻڣ˾ΪsentinelǱȽĿܡʱ䴰бźܶͳݣʱ䴰ڹĻһռùڴ棬һʱ䴰ڹζʱ䴰ڵĳȻСʱ䴰ڳȱСͻᵼʱ䴰ڹƵĻһеĵһڶ\n\n\n\n```\nprivate int calculateTimeIdx(/*@Valid*/ long timeMillis) {    // timeÿһwindowLengthĳȣtimeIdͻ1ʱ䴰ھͻǰһ    long timeId = timeMillis / windowLengthInMs;     // idxֳ[0,arrayLength-1]еĳһΪarrayе    return (int)(timeId % array.length());}protected long calculateWindowStart(/*@Valid*/ long timeMillis) {    return timeMillis - timeMillis % windowLengthInMs;}\n```\n\n\n\n\n\n\n\n\n\nݵǰʱ windowLength õһ timeId(500msֵһµ),timeIdȡڵĳȽһȡģôһ 01λõһȻݵǰʱǰڵӦöӦĿʼʱtimeڸոտʼʱ array ǿյģôȡoldӦnullôᴴһµʵͼһ³ʼ LeapArray\n\nӦ currentWindow  4.1 (idx=0)![SpringCloud AlibabaϵС15Sentinelԭ-Դ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/95b0b64926eae4d2753986af9c978da9980da0.jpg \"SpringCloud AlibabaϵС15Sentinelԭ-Դ\")ȡnull,ôʼʱarraysֻһ(ǵһ(idx=0)Ҳǵڶ(idx=1))ÿʱ䴰ڵĳ500msζֻҪǰʱʱ䴰ڵĲֵ500ms֮ڣʱ䴰ھͲǰ磬統ǰʱߵ300500ʱǰʱ䴰ȻͬǸ\n\nӦ currentWindow  4.2 裺![SpringCloud AlibabaϵС15Sentinelԭ-Դ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/85c047336a2c2186bcc7737a4c28da852cb603.jpg \"SpringCloud AlibabaϵС15Sentinelԭ-Դ\")ʱǰߣ500msʱʱ䴰ھͻǰһʱͻµǰڵĿʼʱ,ʱǰߣֻҪ1000msǰڲᷢ仯дʵ resetWindowTo \n\n\n\n```\nprotected WindowWrap<MetricBucket> resetWindowTo(WindowWrap<MetricBucket> w, long time) {    // Update the start time and reset value.    // windowStart    w.resetTo(time);    MetricBucket borrowBucket = borrowArray.getWindowValue(time);    if (borrowBucket != null) {        w.value().reset();        w.value().addPass((int)borrowBucket.pass());    } else {        w.value().reset();    }    return w;}\n```\n\n\n\n\n\n\n\n\n\nӦ currentWindow  4.3 ![SpringCloud AlibabaϵС15Sentinelԭ-Դ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/03d6e3886f95f5768e05969d7eb8307c26f381.jpg \"SpringCloud AlibabaϵС15Sentinelԭ-Դ\")ʱǰߣǰʱ䳬1000msʱͻٴνһʱ䴰ڣʱarraysеĴڽһʧЧһµĴڽ滻![SpringCloud AlibabaϵС15Sentinelԭ-Դ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/241926a53c2a5b06938844199046d31e593d62.jpg \"SpringCloud AlibabaϵС15Sentinelԭ-Դ\")Դʱţʱ䴰Ҳڷ仯ڵǰʱн󣬻ᱻͳƵǰʱӦʱ䴰Уصaddpass У\n\n\n\n```\npublic void addPass(int count) {    WindowWrap<MetricBucket> wrap = data.currentWindow();    wrap.value().addPass(count);}\n```\n\n\n\n\n\n\n\n\n\nȡԺ뵽 wrap.value().addPass(count); QPSӡ wrap.value() õ֮ǰᵽ MetricBucket  Sentinel QPSݵͳƽά LongAdder[] УָʵúõĹƥ䣬鿴ǷҲ StatisticSlotentry е fireEntry(context, resourceWrapper, node, count, prioritized, args); ҪȽ뵽 FlowSlotentryˣ\n\n\n\n```\npublic void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,                  boolean prioritized, Object... args) throws Throwable {    checkFlow(resourceWrapper, context, node, count, prioritized);    fireEntry(context, resourceWrapper, node, count, prioritized, args);}\n```\n\n\n\n\n\n\n\n\n\nԿиҪķ checkFlow ȥ\n\n\n\n```\npublic void checkFlow(Function<String, Collection<FlowRule>> ruleProvider, ResourceWrapper resource,                      Context context, DefaultNode node, int count, boolean prioritized) throws BlockException {    if (ruleProvider == null || resource == null) {        return;    }    Collection<FlowRule> rules = ruleProvider.apply(resource.getName());    if (rules != null) {        for (FlowRule rule : rules) {            if (!canPassCheck(rule, context, node, count, prioritized)) {                throw new FlowException(rule.getLimitApp(), rule);            }        }    }}\n```\n\n\n\n\n\n\n\n\n\nһжӦˣõõ FlowRule ѭƥԴˡSentinel ԭ\n\n**2.6 FlowRuleSlot**\n slot ҪԤԴͳϢչ̶ĴЧһԴӦ߶ع´μ飬ֱȫͨһЧΪֹ:\n\n*   ָӦЧĹ򣬼Ե÷ģ\n*   ÷Ϊ other Ĺ\n*   ÷Ϊ default Ĺ\n\n\n\n```\n@Overridepublic void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,                  boolean prioritized, Object... args) throws Throwable {    checkFlow(resourceWrapper, context, node, count, prioritized);    fireEntry(context, resourceWrapper, node, count, prioritized, args);}\n```\n\n\n\n\n\n\n\n\n\n**2.6.1 checkFlow**\n뵽FlowRuleChecker.checkFlowС\n\n*   Դƣҵб\n*   Ϊգ򣬵canPassCheckУ顣\n\n\n\n```\npublic void checkFlow(Function<String, Collection<FlowRule>> ruleProvider,ResourceWrapper resource,                      Context context, DefaultNode node, int count, boolean prioritized) throws BlockException {    if (ruleProvider == null || resource == null) {        return;    }    Collection<FlowRule> rules = ruleProvider.apply(resource.getName());    if (rules != null) {        for (FlowRule rule : rules) {            if (!canPassCheck(rule, context, node, count, prioritized)) {                throw new FlowException(rule.getLimitApp(), rule);            }        }    }}\n```\n\n\n\n\n\n\n\n\n\n**2.6.2 canPassCheck**\nжǷǼȺģʽǣpassClusterCheck򣬵passLocalCheck\n\n\n\n```\npublic boolean canPassCheck(/*@NonNull*/ FlowRule rule, Context context,DefaultNode node, int acquireCount,                            boolean prioritized) {    String limitApp = rule.getLimitApp();    if (limitApp == null) {        return true;    }    if (rule.isClusterMode()) {        return passClusterCheck(rule, context, node, acquireCount, prioritized);    }    return passLocalCheck(rule, context, node, acquireCount, prioritized);}\n```\n\n\n\n\n\n\n\n\n\n**2.6.3 passLocalCheck**\n\n*   selectNodeByRequesterAndStrategyͲNode\n*   rule.getRater(), ݲͬΪcanPassУ顣\n\n\n\n```\nprivate static boolean passLocalCheck(FlowRule rule, Context context,DefaultNode node, int acquireCount,                                      boolean prioritized) {    Node selectedNode = selectNodeByRequesterAndStrategy(rule, context, node);    if (selectedNode == null) {        return true;    }    return rule.getRater().canPass(selectedNode, acquireCount, prioritized);}\n```\n\n\n\n\n\n\n\n\n\n**2.6.4 DefaultController.canPass**\nͨĬϵΪֱӾܾжϡ\n\n\n\n```\n@Overridepublic boolean canPass(Node node, int acquireCount, boolean prioritized) {    //ȸnodeȡԴǰʹqps߲صֵ    int curCount = avgUsedTokens(node);    //ǰʹõϱǷֵ    if (curCount + acquireCount > count) {//Ϊtrue˵Ӧñ        // һȼ󣬲Ϊqps򲻻ʧܣȥռδʱ䴰ڣȵһʱ䴰ͨ        if (prioritized && grade == RuleConstant.FLOW_GRADE_QPS) { //            long currentTime;            long waitInMs;            currentTime = TimeUtil.currentTimeMillis();            waitInMs = node.tryOccupyNext(currentTime, acquireCount, count);            if (waitInMs < OccupyTimeoutProperty.getOccupyTimeout()) {                node.addWaitingRequest(currentTime + waitInMs, acquireCount);                node.addOccupiedPass(acquireCount);                sleep(waitInMs);                // PriorityWaitException indicates that the request will pass after waiting for {@link @waitInMs}.                throw new PriorityWaitException(waitInMs);            }        }        return false;    }    return true;}\n```\n\n\n\n\n\n\n\n\n\nһܾ׳ FlowException 쳣\n\n**2.7 PriorityWait**\nDefaultController.canPassУ´ȥĴ\n\n\n\n```\nnode.addWaitingRequest(currentTime + waitInMs, acquireCount);node.addOccupiedPass(acquireCount);\n```\n\n\n\n\n\n\n\n\n\n> addWaitingRequest -> ArrayMetric.addWaiting->OccupiableBucketLeapArray.addWaiting\n\nborrowArrayһFutureBucketLeapArrayﶨδʱ䴰ڣȻδʱĴȥӼ\n\n\n\n```\n@Overridepublic void addWaiting(long time, int acquireCount) {    WindowWrap<MetricBucket> window = borrowArray.currentWindow(time);    window.value().add(MetricEvent.PASS, acquireCount);}\n```\n\n\n\n\n\n\n\n\n\nգStatisticSlot.entryУ쳣\n\nȼȽϸߵ񣬲ҵǰѾﵽֵ׳쳣ʵȥռδһʱ䴰ȥм׳쳣֮󣬻뵽StatisticSlotнвȻֱͨ\n\n\n\n```\npublic void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,                  boolean prioritized, Object... args) throws Throwable {    try{        //...    } catch (PriorityWaitException ex) {        node.increaseThreadNum();        if (context.getCurEntry().getOriginNode() != null) {            // Add count for origin node.            context.getCurEntry().getOriginNode().increaseThreadNum();        }        if (resourceWrapper.getEntryType() == EntryType.IN) {            // Add count for global inbound entry node for global statistics.            Constants.ENTRY_NODE.increaseThreadNum();        }        // Handle pass event with registered entry callback handlers.        for (ProcessorSlotEntryCallback<DefaultNode> handler :             StatisticSlotCallbackRegistry.getEntryCallbacks()) {            handler.onPass(context, resourceWrapper, node, count, args);        }    }}\n```\n\n\n\n# ο\nhttps://lijunyi.xyz/docs/SpringCloud/SpringCloud.html#_2-2-x-%E5%88%86%E6%94%AF\nhttps://mp.weixin.qq.com/s/2jeovmj77O9Ux96v3A0NtA\nhttps://juejin.cn/post/6931922457741770760\nhttps://github.com/D2C-Cai/herring\nhttp://c.biancheng.net/springcloud\nhttps://github.com/macrozheng/springcloud-learning"
  },
  {
    "path": "docs/Spring全家桶/SpringCloud源码分析/SpringCloudConfig源码分析.md",
    "content": "Ҫ˽spring cloud configǾͱ˽springbootenvironmentļع̡\n\n\n\n```\nSpringApplication.run(AppApiApplication.class, args);\n\npublic static ConfigurableApplicationContext run(Class<?>[] primarySources,\n            String[] args) {\n    return new SpringApplication(primarySources).run(args);\n}\n\n```\n\n\n\n`SpringApplicationĹ췽`ôһδ\n\n\n\n```\nsetInitializers((Collection) getSpringFactoriesInstances(\n                ApplicationContextInitializer.class));\n        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));\n\n```\n\n\n\n\n\n```\nSet<String> names = new LinkedHashSet<>(\n                SpringFactoriesLoader.loadFactoryNames(type, classLoader));\n\n```\n\n\n\nδоǴMETA-INFĿ¼ȡ`ApplicationListenerApplicationContextInitializer`͵ࡣ\nspring-boot.jarspring.factories`ConfigFileApplicationListener`࣬ûҲapplicationļļضͨ\n\n\n\n```\n# Application Listeners\norg.springframework.context.ApplicationListener=\\\norg.springframework.boot.context.config.ConfigFileApplicationListener,\\\n\n# Run Listeners\norg.springframework.boot.SpringApplicationRunListener=\\\norg.springframework.boot.context.event.EventPublishingRunListener\n\n```\n\n\n\nspringboot׶\n\n\n\n```\n    public ConfigurableApplicationContext run(String... args) {\n        StopWatch stopWatch = new StopWatch();\n        stopWatch.start();\n        ConfigurableApplicationContext context = null;\n        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();\n        configureHeadlessProperty();\n        SpringApplicationRunListeners listeners = getRunListeners(args);\n        listeners.starting();\n        try {\n            ApplicationArguments applicationArguments = new DefaultApplicationArguments(\n                    args);\n            ConfigurableEnvironment environment = prepareEnvironment(listeners,\n                    applicationArguments);\n            configureIgnoreBeanInfo(environment);\n            Banner printedBanner = printBanner(environment);\n            context = createApplicationContext();\n            exceptionReporters = getSpringFactoriesInstances(\n                    SpringBootExceptionReporter.class,\n                    new Class[] { ConfigurableApplicationContext.class }, context);\n            prepareContext(context, environment, listeners, applicationArguments,\n                    printedBanner);\n            refreshContext(context);\n            afterRefresh(context, applicationArguments);\n            stopWatch.stop();\n            if (this.logStartupInfo) {\n                new StartupInfoLogger(this.mainApplicationClass)\n                        .logStarted(getApplicationLog(), stopWatch);\n            }\n            listeners.started(context);\n            callRunners(context, applicationArguments);\n        }\n        catch (Throwable ex) {\n            handleRunFailure(context, ex, exceptionReporters, listeners);\n            throw new IllegalStateException(ex);\n        }\n\n        try {\n            listeners.running(context);\n        }\n        catch (Throwable ex) {\n            handleRunFailure(context, ex, exceptionReporters, null);\n            throw new IllegalStateException(ex);\n        }\n        return context;\n    }\n\n```\n\n\n\n\n\n```\nConfigurableEnvironment environment = prepareEnvironment(listeners,\n                    applicationArguments);\nprepareContext(context, environment, listeners, applicationArguments,\n                    printedBanner);\n\n```\n\n\n\nδװػĴ\n`prepareEnvironment`гʼ`environment`\n\n\n\n```\n    private ConfigurableEnvironment getOrCreateEnvironment() {\n        if (this.environment != null) {\n            return this.environment;\n        }\n        switch (this.webApplicationType) {\n        case SERVLET:\n            return new StandardServletEnvironment();\n        case REACTIVE:\n            return new StandardReactiveWebEnvironment();\n        default:\n            return new StandardEnvironment();\n        }\n    }\n\n```\n\n\n\nΪservlet`Environment`Ϊ`StandardServletEnvironment`ڸĵĹִ`customizePropertySources`\n\n\n\n```\n    public AbstractEnvironment() {\n        customizePropertySources(this.propertySources);\n        if (logger.isDebugEnabled()) {\n            logger.debug(\"Initialized \" + getClass().getSimpleName() + \" with PropertySources \" + this.propertySources);\n        }\n    }\n\n```\n\n\n\n\n\n```\n    protected void customizePropertySources(MutablePropertySources propertySources) {\n        propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));\n        propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));\n        if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {\n            propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));\n        }\n        super.customizePropertySources(propertySources);\n    }\n\n```\n\n\n\n\n\n```\n    @Override\n    protected void customizePropertySources(MutablePropertySources propertySources) {\n        propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));\n        propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));\n    }\n\n```\n\n\n\n,ǵõ`StandardServletEnvironment`Ҽص˳\n\n1.  servletConfigInitParams\n2.  servletContextInitParams\n3.  jndiProperties\n4.  systemProperties\n5.  systemEnvironment\n\n\n\n```\n    protected void configureEnvironment(ConfigurableEnvironment environment,\n            String[] args) {\n        configurePropertySources(environment, args);\n        configureProfiles(environment, args);\n    }\n\n    protected void configurePropertySources(ConfigurableEnvironment environment,\n            String[] args) {\n        MutablePropertySources sources = environment.getPropertySources();\n        if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {\n            sources.addLast(\n                    new MapPropertySource(\"defaultProperties\", this.defaultProperties));\n        }\n        if (this.addCommandLineProperties && args.length > 0) {\n            String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;\n            if (sources.contains(name)) {\n                PropertySource<?> source = sources.get(name);\n                CompositePropertySource composite = new CompositePropertySource(name);\n                composite.addPropertySource(new SimpleCommandLinePropertySource(\n                        \"springApplicationCommandLineArgs\", args));\n                composite.addPropertySource(source);\n                sources.replace(name, composite);\n            }\n            else {\n                sources.addFirst(new SimpleCommandLinePropertySource(args));\n            }\n        }\n    }\n\n```\n\n\n\nǿԿּ`defaultProperties`Լ`SimpleCommandLinePropertySource`вǰ档ˣڵ˳\n\n1.  SimpleCommandLinePropertySourcem\n2.  servletConfigInitParams\n3.  servletContextInitParams\n4.  jndiProperties\n5.  systemProperties\n6.  systemEnvironment\n7.  defaultProperties\n\n\n\n```\n    public void environmentPrepared(ConfigurableEnvironment environment) {\n        for (SpringApplicationRunListener listener : this.listeners) {\n            listener.environmentPrepared(environment);\n        }\n    }\n\n    @Override\n    public void environmentPrepared(ConfigurableEnvironment environment) {\n        this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(\n                this.application, this.args, environment));\n    }\n\n```\n\n\n\n`SpringApplicationRunListener`Ϊ`EventPublishingRunListener`,ǰ`getRunListeners`\n\n\n\n```\n    private SpringApplicationRunListeners getRunListeners(String[] args) {\n        Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };\n        return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(\n                SpringApplicationRunListener.class, types, this, args));\n    }\n\n```\n\n\n\nͨ췽ʼʱthis`initialMulticaster`еlistenerΪapplicationеlistener\n\n\n\n```\n    public EventPublishingRunListener(SpringApplication application, String[] args) {\n        this.application = application;\n        this.args = args;\n        this.initialMulticaster = new SimpleApplicationEventMulticaster();\n        for (ApplicationListener<?> listener : application.getListeners()) {\n            this.initialMulticaster.addApplicationListener(listener);\n        }\n    }\n\n```\n\n\n\nᷢ͸¼֪ͨ`ConfigFileApplicationListener`\n\n\n\n```\n    @Override\n    public void onApplicationEvent(ApplicationEvent event) {\n        if (event instanceof ApplicationEnvironmentPreparedEvent) {\n            onApplicationEnvironmentPreparedEvent(\n                    (ApplicationEnvironmentPreparedEvent) event);\n        }\n        if (event instanceof ApplicationPreparedEvent) {\n            onApplicationPreparedEvent(event);\n        }\n    }\n\n```\n\n\n\n\n\n```\n    private void onApplicationEnvironmentPreparedEvent(\n            ApplicationEnvironmentPreparedEvent event) {\n        List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();\n        postProcessors.add(this);\n        AnnotationAwareOrderComparator.sort(postProcessors);\n        for (EnvironmentPostProcessor postProcessor : postProcessors) {\n            postProcessor.postProcessEnvironment(event.getEnvironment(),\n                    event.getSpringApplication());\n        }\n    }\n\n    List<EnvironmentPostProcessor> loadPostProcessors() {\n        return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class,\n                getClass().getClassLoader());\n    }\n\n```\n\n\n\nиչ㣬ͨȡ`spring.factories`ļõ`EnvironmentPostProcessor``postProcessEnvironment`ԼҲ뵽`postProcessors`Уֻߵ`postProcessors`\n\n\n\n```\n    @Override\n    public void postProcessEnvironment(ConfigurableEnvironment environment,\n            SpringApplication application) {\n        addPropertySources(environment, application.getResourceLoader());\n    }\n\n```\n\n\n\n\n\n```\n    protected void addPropertySources(ConfigurableEnvironment environment,\n            ResourceLoader resourceLoader) {\n        RandomValuePropertySource.addToEnvironment(environment);\n        new Loader(environment, resourceLoader).load();\n    }\n\n    public static void addToEnvironment(ConfigurableEnvironment environment) {\n        environment.getPropertySources().addAfter(\n                StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,\n                new RandomValuePropertySource(RANDOM_PROPERTY_SOURCE_NAME));\n        logger.trace(\"RandomValuePropertySource add to Environment\");\n    }\n\n        public void load() {\n            this.profiles = new LinkedList<>();\n            this.processedProfiles = new LinkedList<>();\n            this.activatedProfiles = false;\n            this.loaded = new LinkedHashMap<>();\n            initializeProfiles();\n            while (!this.profiles.isEmpty()) {\n                Profile profile = this.profiles.poll();\n                if (profile != null && !profile.isDefaultProfile()) {\n                    addProfileToEnvironment(profile.getName());\n                }\n                load(profile, this::getPositiveProfileFilter,\n                        addToLoaded(MutablePropertySources::addLast, false));\n                this.processedProfiles.add(profile);\n            }\n            resetEnvironmentProfiles(this.processedProfiles);\n            load(null, this::getNegativeProfileFilter,\n                    addToLoaded(MutablePropertySources::addFirst, true));\n            addLoadedPropertySources();\n        }\n\n```\n\n\n\nֽrandomü뵽systemEnvironment֮ǵü˳\ndefaultProperties\n\n1.  SimpleCommandLinePropertySourcem\n2.  servletConfigInitParams\n3.  servletContextInitParams\n4.  jndiProperties\n5.  systemProperties\n6.  systemEnvironment\n7.  RandomValuePropertySource\n8.  defaultProperties\n\n·\n\n> classpath:/,classpath:/config/,file:./,file:./config/\n> spring.config.location=\n> spring.config.additional-location=\n\nļĬΪapplication\n\n> spring.config.name=\n> ļΪspring.config.nameõ\n\nͨ`load`·ļļ뵽`environment``spring.profiles.active`Ҽprefix + \"-\" + profile + fileExtensionļҲ`application-<spring.profiles.active>.properties``y`ļ\n\n\n\n```\n        private void load(Profile profile, DocumentFilterFactory filterFactory,\n                DocumentConsumer consumer) {\n            getSearchLocations().forEach((location) -> {\n                boolean isFolder = location.endsWith(\"/\");\n                Set<String> names = isFolder ? getSearchNames() : NO_SEARCH_NAMES;\n                names.forEach(\n                        (name) -> load(location, name, profile, filterFactory, consumer));\n            });\n        }\n\n        private void load(String location, String name, Profile profile,\n                DocumentFilterFactory filterFactory, DocumentConsumer consumer) {\n            if (!StringUtils.hasText(name)) {\n                for (PropertySourceLoader loader : this.propertySourceLoaders) {\n                    if (canLoadFileExtension(loader, location)) {\n                        load(loader, location, profile,\n                                filterFactory.getDocumentFilter(profile), consumer);\n                        return;\n                    }\n                }\n            }\n            Set<String> processed = new HashSet<>();\n            for (PropertySourceLoader loader : this.propertySourceLoaders) {\n                for (String fileExtension : loader.getFileExtensions()) {\n                    if (processed.add(fileExtension)) {\n                        loadForFileExtension(loader, location + name, \".\" + fileExtension,\n                                profile, filterFactory, consumer);\n                    }\n                }\n            }\n        }\n\n```\n\n\n\nͨ`this.propertySourceLoaders`жǷϸSourceLoaderĺ׺ϣͽԴؽ,`propertySourceLoaders``Loader`췽\n\n\n\n```\n        Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {\n            this.environment = environment;\n            this.resourceLoader = (resourceLoader != null) ? resourceLoader\n                    : new DefaultResourceLoader();\n            this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(\n                    PropertySourceLoader.class, getClass().getClassLoader());\n        }\n\n```\n\n\n\n\n\n```\n# PropertySource Loaders\norg.springframework.boot.env.PropertySourceLoader=\\\norg.springframework.boot.env.PropertiesPropertySourceLoader,\\\norg.springframework.boot.env.YamlPropertySourceLoader\n\n```\n\n\n\n˿Լ`yml`ļ`properties`ļ\n\n\n\n```\n        private void addLoadedPropertySources() {\n            MutablePropertySources destination = this.environment.getPropertySources();\n            List<MutablePropertySources> loaded = new ArrayList<>(this.loaded.values());\n            Collections.reverse(loaded);\n            String lastAdded = null;\n            Set<String> added = new HashSet<>();\n            for (MutablePropertySources sources : loaded) {\n                for (PropertySource<?> source : sources) {\n                    if (added.add(source.getName())) {\n                        addLoadedPropertySource(destination, lastAdded, source);\n                        lastAdded = source.getName();\n                    }\n                }\n            }\n        }\n\n```\n\n\n\nշ뵽`environment`\n\nǾSpring cloud config\n\n`spring-cloud-context`jarУ`spring.factories`˸`BootstrapApplicationListener`\nҸ`ConfigFileApplicationListener`springcloudô\n¼`BootstrapApplicationListener`\n\n\n\n```\n# Application Listeners\norg.springframework.context.ApplicationListener=\\\norg.springframework.cloud.bootstrap.BootstrapApplicationListener,\\\n\n```\n\n\n\n\n\n```\n    @Override\n    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {\n        ConfigurableEnvironment environment = event.getEnvironment();\n        if (!environment.getProperty(\"spring.cloud.bootstrap.enabled\", Boolean.class,\n                true)) {\n            return;\n        }\n        // don't listen to events in a bootstrap context\n        if (environment.getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {\n            return;\n        }\n        ConfigurableApplicationContext context = null;\n        String configName = environment\n                .resolvePlaceholders(\"${spring.cloud.bootstrap.name:bootstrap}\");\n        for (ApplicationContextInitializer<?> initializer : event.getSpringApplication()\n                .getInitializers()) {\n            if (initializer instanceof ParentContextApplicationContextInitializer) {\n                context = findBootstrapContext(\n                        (ParentContextApplicationContextInitializer) initializer,\n                        configName);\n            }\n        }\n        if (context == null) {\n            context = bootstrapServiceContext(environment, event.getSpringApplication(),\n                    configName);\n        }\n        apply(context, event.getSpringApplication(), environment);\n    }\n\n```\n\n\n\n\n\n```\n    private ConfigurableApplicationContext bootstrapServiceContext(\n            ConfigurableEnvironment environment, final SpringApplication application,\n            String configName) {\n        StandardEnvironment bootstrapEnvironment = new StandardEnvironment();\n        MutablePropertySources bootstrapProperties = bootstrapEnvironment\n                .getPropertySources();\n        for (PropertySource<?> source : bootstrapProperties) {\n            bootstrapProperties.remove(source.getName());\n        }\n        String configLocation = environment\n                .resolvePlaceholders(\"${spring.cloud.bootstrap.location:}\");\n        Map<String, Object> bootstrapMap = new HashMap<>();\n        bootstrapMap.put(\"spring.config.name\", configName);\n        // if an app (or test) uses spring.main.web-application-type=reactive, bootstrap will fail\n        // force the environment to use none, because if though it is set below in the builder\n        // the environment overrides it\n        bootstrapMap.put(\"spring.main.web-application-type\", \"none\");\n        if (StringUtils.hasText(configLocation)) {\n            bootstrapMap.put(\"spring.config.location\", configLocation);\n        }\n        bootstrapProperties.addFirst(\n                new MapPropertySource(BOOTSTRAP_PROPERTY_SOURCE_NAME, bootstrapMap));\n        for (PropertySource<?> source : environment.getPropertySources()) {\n            if (source instanceof StubPropertySource) {\n                continue;\n            }\n            bootstrapProperties.addLast(source);\n        }\n        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();\n        // Use names and ensure unique to protect against duplicates\n        List<String> names = new ArrayList<>(SpringFactoriesLoader\n                .loadFactoryNames(BootstrapConfiguration.class, classLoader));\n        for (String name : StringUtils.commaDelimitedListToStringArray(\n                environment.getProperty(\"spring.cloud.bootstrap.sources\", \"\"))) {\n            names.add(name);\n        }\n        // TODO: is it possible or sensible to share a ResourceLoader?\n        SpringApplicationBuilder builder = new SpringApplicationBuilder()\n                .profiles(environment.getActiveProfiles()).bannerMode(Mode.OFF)\n                .environment(bootstrapEnvironment)\n                // Don't use the default properties in this builder\n                .registerShutdownHook(false).logStartupInfo(false)\n                .web(WebApplicationType.NONE);\n        final SpringApplication builderApplication = builder.application();\n        if(builderApplication.getMainApplicationClass() == null){\n            // gh_425:\n            // SpringApplication cannot deduce the MainApplicationClass here\n            // if it is booted from SpringBootServletInitializer due to the\n            // absense of the \"main\" method in stackTraces.\n            // But luckily this method's second parameter \"application\" here\n            // carries the real MainApplicationClass which has been explicitly\n            // set by SpringBootServletInitializer itself already.\n            builder.main(application.getMainApplicationClass());\n        }\n        if (environment.getPropertySources().contains(\"refreshArgs\")) {\n            // If we are doing a context refresh, really we only want to refresh the\n            // Environment, and there are some toxic listeners (like the\n            // LoggingApplicationListener) that affect global static state, so we need a\n            // way to switch those off.\n            builderApplication\n                    .setListeners(filterListeners(builderApplication.getListeners()));\n        }\n        List<Class<?>> sources = new ArrayList<>();\n        for (String name : names) {\n            Class<?> cls = ClassUtils.resolveClassName(name, null);\n            try {\n                cls.getDeclaredAnnotations();\n            }\n            catch (Exception e) {\n                continue;\n            }\n            sources.add(cls);\n        }\n        AnnotationAwareOrderComparator.sort(sources);\n        builder.sources(sources.toArray(new Class[sources.size()]));\n        final ConfigurableApplicationContext context = builder.run();\n        // gh-214 using spring.application.name=bootstrap to set the context id via\n        // `ContextIdApplicationContextInitializer` prevents apps from getting the actual\n        // spring.application.name\n        // during the bootstrap phase.\n        context.setId(\"bootstrap\");\n        // Make the bootstrap context a parent of the app context\n        addAncestorInitializer(application, context);\n        // It only has properties in it now that we don't want in the parent so remove\n        // it (and it will be added back later)\n        bootstrapProperties.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);\n        mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);\n        return context;\n    }\n\n```\n\n\n\nҪ\n\n1.  Environmentspring.config.name=bootstrap\n2.  һµSpringApplicationsourcesΪչµBootstrapConfiguration\n\n\n\n```\nList<String> names = new ArrayList<>(SpringFactoriesLoader\n                .loadFactoryNames(BootstrapConfiguration.class, classLoader));\n\n```\n\n\n\n򵥵ľǿΪһµΪBootstrapConfigurationõࡣͨrunõ˳ʼBeanFactoryҽcontenxtװAncestorInitializer뵽ԼSpringApplication\n\n\n\n```\napplication.addInitializers(new AncestorInitializer(context));\n\n```\n\n\n\n`apply`,ȡ`ApplicationContextInitializer`͵ж뵽ǵǰ`SpringApplication`\n\n\n\n```\n    private void apply(ConfigurableApplicationContext context,\n            SpringApplication application, ConfigurableEnvironment environment) {\n        @SuppressWarnings(\"rawtypes\")\n        List<ApplicationContextInitializer> initializers = getOrderedBeansOfType(context,\n                ApplicationContextInitializer.class);\n        application.addInitializers(initializers\n                .toArray(new ApplicationContextInitializer[initializers.size()]));\n        addBootstrapDecryptInitializer(application);\n    }\n\n```\n\n\n\n`spring-cloud-context`У`spring.factories`\n\n\n\n```\norg.springframework.cloud.bootstrap.BootstrapConfiguration=\\\norg.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration,\\\n\n```\n\n\n\n`SpringApplication`Ѿ`AncestorInitializer,PropertySourceBootstrapConfiguration`ApplicationContextInitializer\n\nص\n\n\n\n```\n    private void prepareContext(ConfigurableApplicationContext context,\n            ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,\n            ApplicationArguments applicationArguments, Banner printedBanner) {\n        context.setEnvironment(environment);\n        postProcessApplicationContext(context);\n        applyInitializers(context);\n        listeners.contextPrepared(context);\n        if (this.logStartupInfo) {\n            logStartupInfo(context.getParent() == null);\n            logStartupProfileInfo(context);\n        }\n\n        // Add boot specific singleton beans\n        context.getBeanFactory().registerSingleton(\"springApplicationArguments\",\n                applicationArguments);\n        if (printedBanner != null) {\n            context.getBeanFactory().registerSingleton(\"springBootBanner\", printedBanner);\n        }\n\n        // Load the sources\n        Set<Object> sources = getAllSources();\n        Assert.notEmpty(sources, \"Sources must not be empty\");\n        load(context, sources.toArray(new Object[0]));\n        listeners.contextLoaded(context);\n    }\n\n```\n\n\n\n\n\n```\n    protected void applyInitializers(ConfigurableApplicationContext context) {\n        for (ApplicationContextInitializer initializer : getInitializers()) {\n            Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(\n                    initializer.getClass(), ApplicationContextInitializer.class);\n            Assert.isInstanceOf(requiredType, context, \"Unable to call initializer.\");\n            initializer.initialize(context);\n        }\n    }\n\n```\n\n\n\nͻõ`ApplicationContextInitializer.initialize`\nͻ`AncestorInitializer`\n\n\n\n```\n        @Override\n        public void initialize(ConfigurableApplicationContext context) {\n            while (context.getParent() != null && context.getParent() != context) {\n                context = (ConfigurableApplicationContext) context.getParent();\n            }\n            reorderSources(context.getEnvironment());\n            new ParentContextApplicationContextInitializer(this.parent)\n                    .initialize(context);\n        }\n\n    @Override\n    public void initialize(ConfigurableApplicationContext applicationContext) {\n        if (applicationContext != this.parent) {\n            applicationContext.setParent(this.parent);\n            applicationContext.addApplicationListener(EventPublisher.INSTANCE);\n        }\n    }\n\n```\n\n\n\nԿｫBootStrapΪǵǰĸҸеĶ󶼳ʼˣ`PropertySourceBootstrapConfiguration`Ҳʼ,Ҹ`ApplicationContextInitializer`뵽Լ棬˻óʼ˵`PropertySourceBootstrapConfiguration.initialize`,`PropertySourceLocator`ע\n\n\n\n```\n# Bootstrap components\norg.springframework.cloud.bootstrap.BootstrapConfiguration=\\\norg.springframework.cloud.config.client.ConfigServiceBootstrapConfiguration,\\\n\n```\n\n\n\n\n\n```\n    @Bean\n    @ConditionalOnMissingBean(ConfigServicePropertySourceLocator.class)\n    @ConditionalOnProperty(value = \"spring.cloud.config.enabled\", matchIfMissing = true)\n    public ConfigServicePropertySourceLocator configServicePropertySource(ConfigClientProperties properties) {\n        ConfigServicePropertySourceLocator locator = new ConfigServicePropertySourceLocator(\n                properties);\n        return locator;\n    }\n\n```\n\n\n\n\n\n```\n    @Autowired(required = false)\n    private List<PropertySourceLocator> propertySourceLocators = new ArrayList<>();\n\n    @Override\n    public void initialize(ConfigurableApplicationContext applicationContext) {\n        CompositePropertySource composite = new CompositePropertySource(\n                BOOTSTRAP_PROPERTY_SOURCE_NAME);\n        AnnotationAwareOrderComparator.sort(this.propertySourceLocators);\n        boolean empty = true;\n        ConfigurableEnvironment environment = applicationContext.getEnvironment();\n        for (PropertySourceLocator locator : this.propertySourceLocators) {\n            PropertySource<?> source = null;\n            source = locator.locate(environment);\n            if (source == null) {\n                continue;\n            }\n            logger.info(\"Located property source: \" + source);\n            composite.addPropertySource(source);\n            empty = false;\n        }\n        if (!empty) {\n            MutablePropertySources propertySources = environment.getPropertySources();\n            String logConfig = environment.resolvePlaceholders(\"${logging.config:}\");\n            LogFile logFile = LogFile.get(environment);\n            if (propertySources.contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {\n                propertySources.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);\n            }\n            insertPropertySources(propertySources, composite);\n            reinitializeLoggingSystem(environment, logConfig, logFile);\n            setLogLevels(applicationContext, environment);\n            handleIncludedProfiles(environment);\n        }\n    }\n\n```\n\n\n\nջ`ConfigServicePropertySourceLocator`\n\n\n\n```\n    private Environment getRemoteEnvironment(RestTemplate restTemplate,\n            ConfigClientProperties properties, String label, String state) {\n        String path = \"/{name}/{profile}\";\n        String name = properties.getName();\n        String profile = properties.getProfile();\n        String token = properties.getToken();\n        int noOfUrls = properties.getUri().length;\n        if (noOfUrls > 1) {\n            logger.info(\"Multiple Config Server Urls found listed.\");\n        }\n\n        Object[] args = new String[] { name, profile };\n        if (StringUtils.hasText(label)) {\n            if (label.contains(\"/\")) {\n                label = label.replace(\"/\", \"(_)\");\n            }\n            args = new String[] { name, profile, label };\n            path = path + \"/{label}\";\n        }\n        ResponseEntity<Environment> response = null;\n\n        for (int i = 0; i < noOfUrls; i++) {\n            Credentials credentials = properties.getCredentials(i);\n            String uri = credentials.getUri();\n            String username = credentials.getUsername();\n            String password = credentials.getPassword();\n\n            logger.info(\"Fetching config from server at : \" + uri);\n\n            try {\n                HttpHeaders headers = new HttpHeaders();\n                addAuthorizationToken(properties, headers, username, password);\n                if (StringUtils.hasText(token)) {\n                    headers.add(TOKEN_HEADER, token);\n                }\n                if (StringUtils.hasText(state) && properties.isSendState()) {\n                    headers.add(STATE_HEADER, state);\n                }\n\n                final HttpEntity<Void> entity = new HttpEntity<>((Void) null, headers);\n                response = restTemplate.exchange(uri + path, HttpMethod.GET, entity,\n                        Environment.class, args);\n            }\n            catch (HttpClientErrorException e) {\n                if (e.getStatusCode() != HttpStatus.NOT_FOUND) {\n                    throw e;\n                }\n            }\n            catch (ResourceAccessException e) {\n                logger.info(\"Connect Timeout Exception on Url - \" + uri\n                        + \". Will be trying the next url if available\");\n                if (i == noOfUrls - 1)\n                    throw e;\n                else\n                    continue;\n            }\n\n            if (response == null || response.getStatusCode() != HttpStatus.OK) {\n                return null;\n            }\n\n            Environment result = response.getBody();\n            return result;\n        }\n\n        return null;\n    }\n\n```\n\n\n\nԿǵ÷˵Ľӿڻȡµá\nⲿŵsystemEnvironment֮ǰ˾ͻḲǱãǿͨ\n\n\n\n```\n@ConfigurationProperties(\"spring.cloud.config\")\npublic class PropertySourceBootstrapProperties {\n\n    /**\n     * Flag to indicate that the external properties should override system properties.\n     * Default true.\n     */\n    private boolean overrideSystemProperties = true;\n\n    /**\n     * Flag to indicate that {@link #isOverrideSystemProperties()\n     * systemPropertiesOverride} can be used. Set to false to prevent users from changing\n     * the default accidentally. Default true.\n     */\n    private boolean allowOverride = true;\n\n    /**\n     * Flag to indicate that when {@link #setAllowOverride(boolean) allowOverride} is\n     * true, external properties should take lowest priority, and not override any\n     * existing property sources (including local config files). Default false.\n     */\n    private boolean overrideNone = false;\n\n```\n\n\n\n\n\n```\nif (!remoteProperties.isAllowOverride() || (!remoteProperties.isOverrideNone()\n                && remoteProperties.isOverrideSystemProperties())) {\n            propertySources.addFirst(composite);\n            return;\n        }\n        if (remoteProperties.isOverrideNone()) {\n            propertySources.addLast(composite);\n            return;\n        }\n        if (propertySources\n                .contains(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME)) {\n            if (!remoteProperties.isOverrideSystemProperties()) {\n                propertySources.addAfter(\n                        StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,\n                        composite);\n            }\n            else {\n                propertySources.addBefore(\n                        StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,\n                        composite);\n            }\n        }\n        else {\n            propertySources.addLast(composite);\n        }\n\n```\n\n\n\n**ܽ**\n\n1.  չ\n    1.1 `EnvironmentPostProcessor`\n    Ի\n    1.2 `PropertySourceLoader`\n    ͬĸʽļ\n    1.2 `ApplicationListener`\n    spring-cloudͨչBootstrapApplicationListener\n    1.3 `BootstrapConfiguration`\n    ͨչ\n\n2.  ļع\n    ͨApplicationListener¼һµSpringAplictionཫBootstrapConfigurationչΪ࣬Ȼһʼ˵BootStrapװAncestorInitializer뵽ԼУþǽԼĸΪBootStrapͨBootStrapóʼúApplicationContextInitializerͶ󣬶PropertySourceBootstrapConfiguration࣬עPropertySourceLocator࣬ConfigServiceBootstrapConfigurationbean\n    ConfigServicePropertySourceLocatorջὫBootStrapгʼõPropertySourceBootstrapConfiguration뵽ԼеãյinitializeȻConfigServicePropertySourceLocator.locateȥConfig serverȡá\n\n\n\nߣӵ¶_to\nӣhttps://www.jianshu.com/p/60c6ab0e79d5\nԴ\nȨСҵתϵ߻Ȩҵתע\n\n# ο\nhttps://lijunyi.xyz/docs/SpringCloud/SpringCloud.html#_2-2-x-%E5%88%86%E6%94%AF\nhttps://mp.weixin.qq.com/s/2jeovmj77O9Ux96v3A0NtA\nhttps://juejin.cn/post/6931922457741770760\nhttps://github.com/D2C-Cai/herring\nhttp://c.biancheng.net/springcloud\nhttps://github.com/macrozheng/springcloud-learning"
  },
  {
    "path": "docs/Spring全家桶/SpringCloud源码分析/SpringCloudEureka源码分析.md",
    "content": "EurekaԴ\n\n**1 **\n**1.1 Eurekaʲô**\n\nǶףEurekaעģעҪʵʲôأȷˡ\n\n1.  ȻעģҪܱipportϢɣEureka-serverṩĻܡ\n2.  ע֮󣬻ҪṩһЩ̬֪ߵĹܰɣһߣEureka-server֪һĲֵˡ\n3.  Eureka-server˸֪ı仯֪֮ܵͨѶ˰ɣǣʱserver֪ͨclientػclientԼȥȡϢأڲ֪ȻȥԴ֤\n4.  OKĹܶʵˣEurekaϸˣǻһȻipͶ˿ڶEureka-server棬Ѷ˵÷˵ʱͨõOpenFeignOpenFeignô֪ĸģ֮ǰдapplication.properties<servicename>.ribbon.listOfServersУEurekaôԶдȥأ\n5.  4ܻעĸеĹܣʱ˼һ£ע΢ĿУעҲΪһҲҪȺģʱǾҪһ£Ⱥô֤һԣûʲôۣ\n    EurekaҪʵֵĵĹܣЩṩˣĿôȥأֱȥAPIɣ鷳ȥѧһEurekaapi궿ˡ\n\nʱǾͻ뵽SpringBootԶװStarter˺beanԶע룬ײֱõbeanȻStarterӦԶǵAPIģOKǻعͷҷ֣ҵEureka-clientһstarterٺ٣е㶫ˡclient˺߼϶ǰǷװ˸beanȻǵ˺apiˡ\n\nEurekaĺĹһܽ᣺\n\n1.  ʵעᣬڴ\n2.  ̬֪Ľ״̬\n3.  ķ֣̬֪ı仯\n    **1.2 Ƶ**\n    ȷ˺ĹܣԼεõģ󵨵Ƶһºͼ\n\n![SpringCloudϵСSpring Cloud Դ֮Eureka-Դ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/f15564313c2406546821713bcaf3eb0e9ac73d.jpg \"SpringCloudϵСSpring Cloud Դ֮Eureka-Դ\")Ƶ֮ͨԴһ֤\n\n**2 Դ**\n**2.1 ע**\nעspring bootӦʱġִ·ҲȻعһǰǽ֪ʶ\n\n˵spring cloudһ̬ṩһױ׼ױ׼ͨͬʵ֣оͰע/֡۶ϡؾȣspring-cloud-commonУ\norg.springframework.cloud.client.serviceregistry ·£ԿһעĽӿڶ  ServiceRegistry Ƕspring cloudзעһӿڡ\n\nǿһϵͼӿһΨһʵ EurekaServiceRegistry ʾõEureka ServerΪעġ\n\n![SpringCloudϵСSpring Cloud Դ֮Eureka-Դ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/49d085328febfffcbae4030dac151db2d0bfcb.jpg \"SpringCloudϵСSpring Cloud Դ֮Eureka-Դ\")**2.1.1 עʱ**\nעķǿԲ²һӦʲôʱɣҪʵӦòѲ²⵽עȡڷǷѾˡspring bootУȵspring еö֮עᡣspring bootеrefreshContextɡ\n\nǹ۲һfinishRefreshϿԿˢµĲҲˢ֮ҪĺõĲҪ\n\n* ջ\n\n* ʼһLifecycleProcessorSpringʱbeanspringʱbean\n\n* LifecycleProcessoronRefreshʵLifecycleӿڵbean\n\n* ContextRefreshedEvent\n\n* עBeanͨJMXмغ͹\n\n\n\n  ```\n  protected void finishRefresh() {\n      // Clear context-level resource caches (such as ASM metadata from scanning).\n      clearResourceCaches();\n      // Initialize lifecycle processor for this context.\n      initLifecycleProcessor();\n      // Propagate refresh to lifecycle processor first.\n      getLifecycleProcessor().onRefresh();\n      // Publish the final event.\n      publishEvent(new ContextRefreshedEvent(this));\n      // Participate in LiveBeansView MBean, if active.\n      LiveBeansView.registerApplicationContext(this);\n  }\n  ```\n\n\n\n\n\n\n\n\n\nУصע getLifecycleProcessor().onRefresh() ǵڴonrefreshҵSmartLifecycleӿڵʵಢstart\n\n**2.1.2 SmartLifeCycle**\nչһSmartLifeCycle֪ʶ SmartLifeCycleһӿڣSpringеBeanҳʼ֮󣬻صʵSmartLifeCycleӿڵжӦķ磨start\n\nʵԼҲչspringboot̵mainͬĿ¼£дһ࣬ʵSmartLifeCycleӿڣͨ @Service ΪһbeanΪҪspringȥأȵbean\n\n\n\n```\n@Service\npublic class TestSmartLifeCycle implements SmartLifecycle {\n    /**\n     * ִ.ʾstart.\n     * isAutoStartup()ֵ,ֻisAutoStartup()trueʱ,start()Żᱻִ\n     */\n    @Override\n    public void start() {\n        System.out.println(\"----------start-----------\");\n    }\n    /**\n     * ֹͣǰִз\n     * ǰ: isRunning()trueŻᱻִ\n     */\n    @Override\n    public void stop() {\n        System.out.println(\"----------stop-----------\");\n    }\n    /**\n     * ط״̬,Ӱ쵽Ƿstop\n     * @return\n     */\n    @Override\n    public boolean isRunning() {\n        return false;\n    }\n    /**\n     * Ƿstart,Ҫע\n     * ǰfalseǲִstart()\n     * @return\n     */\n    @Override\n    public boolean isAutoStartup() {\n        return true;\n    }\n    @Override\n    public void stop(Runnable runnable) {\n        stop();\n        runnable.run();\n    }\n    /**\n     * ִָ˳\n     * ǰжʵSmartLifecycle,򰴴˷ִֵ\n     * @return\n     */\n    @Override\n    public int getPhase() {\n        return 0;\n    }\n}\n```\n\n\n\n\n\n\n\n\n\nţspring bootӦú󣬿Կ̨ start ַ\n\nDefaultLifecycleProcessor.startBeansϼһdebugԺԵĿԼTestSmartLifeCycleɨ赽ˣøbeanstart![SpringCloudϵСSpring Cloud Դ֮Eureka-Դ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/75b18e528ba1879148c106d9bd3fb61ab9b5bb.jpg \"SpringCloudϵСSpring Cloud Դ֮Eureka-Դ\")startBeansУǿԿȻʵSmartLifeCycleBeanȻѭʵSmartLifeCyclebeanstart¡\n\n\n\n```\nprivate void startBeans(boolean autoStartupOnly) {\n    Map<String, Lifecycle> lifecycleBeans = this.getLifecycleBeans();\n    Map<Integer, DefaultLifecycleProcessor.LifecycleGroup> phases = new HashMap();\n    lifecycleBeans.forEach((beanName, bean) -> {\n        if (!autoStartupOnly || bean instanceof SmartLifecycle &&\n            ((SmartLifecycle)bean).isAutoStartup()) {\n            int phase = this.getPhase(bean);\n            DefaultLifecycleProcessor.LifecycleGroup group =\n                (DefaultLifecycleProcessor.LifecycleGroup)phases.get(phase);\n            if (group == null) {\n                group = new DefaultLifecycleProcessor.LifecycleGroup(phase,this.timeoutPerShutdownPhase, lifecycleBeans, autoStartupOnly);\n                phases.put(phase, group);\n            }\n            group.add(beanName, bean);\n        }\n    });\n    if (!phases.isEmpty()) {\n        List<Integer> keys = new ArrayList(phases.keySet());\n        Collections.sort(keys);\n        Iterator var5 = keys.iterator();\n        while(var5.hasNext()) {\n            Integer key = (Integer)var5.next();\n            ((DefaultLifecycleProcessor.LifecycleGroup)phases.get(key)).start();\n        }\n    }\n}\n```\n\n\n\n\n\n\n\n\n\n**2.1.3 doStart**\n\n\n\n```\nprivate void doStart(Map<String, ? extends Lifecycle> lifecycleBeans, String beanName, boolean autoStartupOnly) {\n    Lifecycle bean = (Lifecycle)lifecycleBeans.remove(beanName);\n    if (bean != null && bean != this) {\n        String[] dependenciesForBean = this.getBeanFactory().getDependenciesForBean(beanName);\n        String[] var6 = dependenciesForBean;\n        int var7 = dependenciesForBean.length;\n        for(int var8 = 0; var8 < var7; ++var8) {\n            String dependency = var6[var8];\n            this.doStart(lifecycleBeans, dependency, autoStartupOnly);\n        }\n        if (!bean.isRunning() && (!autoStartupOnly || !(bean instanceof SmartLifecycle) || ((SmartLifecycle)bean).isAutoStartup())) {\n            if (this.logger.isTraceEnabled()) {\n                this.logger.trace(\"Starting bean '\" + beanName + \"' of type [\" + bean.getClass().getName() + \"]\");\n            }\n            try {\n                bean.start(); //ʱ BeanʵӦEurekaAutoServiceRegistration\n            } catch (Throwable var10) {\n                throw new ApplicationContextException(\"Failed to start bean '\" + beanName + \"'\", var10);\n            }\n            if (this.logger.isDebugEnabled()) {\n                this.logger.debug(\"Successfully started bean '\" + beanName + \"'\");\n            }\n        }\n    }\n}\n```\n\n\n\n\n\n\n\n\n\nʱbean.start()õĿ\nEurekaAutoServiceRegistrationеstartΪȻʵSmartLifeCycleӿڡ\n\n\n\n```\npublic class EurekaAutoServiceRegistration implements AutoServiceRegistration,SmartLifecycle, Ordered, SmartApplicationListener {\n    @Override\n    public void start() {\n        // only set the port if the nonSecurePort or securePort is 0 and this.port != 0\n        if (this.port.get() != 0) {\n            if (this.registration.getNonSecurePort() == 0) {\n                this.registration.setNonSecurePort(this.port.get());\n            }\n            if (this.registration.getSecurePort() == 0 &&\n                this.registration.isSecure()) {\n                this.registration.setSecurePort(this.port.get());\n            }\n        }\n        // only initialize if nonSecurePort is greater than 0 and it isn't already running\n        // because of containerPortInitializer below\n        if (!this.running.get() && this.registration.getNonSecurePort() > 0) {\n            this.serviceRegistry.register(this.registration);\n            this.context.publishEvent(new InstanceRegisteredEvent<>(this,\n                                                                    this.registration.getInstanceConfig()));\n            this.running.set(true);\n        }\n    }\n}\n```\n\n\n\n\n\n\n\n\n\nstartУǿԿ\nthis.serviceRegistry.register ʵϾǷעĻơ\n\nʱthis.serviceRegistryʵӦ EurekaServiceRegistry  ԭ\nEurekaAutoServiceRegistrationĹ췽Уһֵ췽EurekaClientAutoConfiguration Զװбװͳʼģ¡\n\n\n\n```\n@Bean\n@ConditionalOnBean(AutoServiceRegistrationProperties.class)\n@ConditionalOnProperty(value = \"spring.cloud.service-registry.auto-registration.enabled\", matchIfMissing = true)\npublic EurekaAutoServiceRegistration eurekaAutoServiceRegistration(\n    ApplicationContext context, EurekaServiceRegistry registry,\n    EurekaRegistration registration) {\n    return new EurekaAutoServiceRegistration(context, registry, registration);\n}\n```\n\n\n\n\n\n\n\n\n\n**2.2 ע**\nǷע\n\n\n\n```\npublic class EurekaAutoServiceRegistration implements AutoServiceRegistration,\nSmartLifecycle, Ordered, SmartApplicationListener {\n    @Override\n    public void start() {\n        //ʡ...\n        this.serviceRegistry.register(this.registration);\n        this.context.publishEvent(new InstanceRegisteredEvent<> this,this.registration.getInstanceConfig()));\n    }\n}\n```\n\n\n\n\n\n\n\n\n\nthis.serviceRegistry.register(this.registration); ջ\n\nEurekaServiceRegistry е register ʵַע\n\n**2.2.1 register**\n\n\n\n```\n@Override\npublic void register(EurekaRegistration reg) {\n    maybeInitializeClient(reg);\n    if (log.isInfoEnabled()) {\n        log.info(\"Registering application \"\n                 + reg.getApplicationInfoManager().getInfo().getAppName()\n                 + \" with eureka with status \"\n                 + reg.getInstanceConfig().getInitialStatus());\n    }\n    //õǰʵ״̬һʵ״̬仯ֻҪ״̬DOWNôͻᱻִзעᡣ\n    reg.getApplicationInfoManager().setInstanceStatus(reg.getInstanceConfig().getInitialStatus());\n    //ýĴ\n    reg.getHealthCheckHandler().ifAvailable(healthCheckHandler -> reg.getEurekaClient().registerHealthCheck(healthCheckHandler));\n}\n```\n\n\n\n\n\n\n\n\n\nע᷽вûEurekaķȥִעᣬǽһ״̬Լý鴦Ǽһ\nreg.getApplicationInfoManager().setInstanceStatus\n\n\n\n```\npublic synchronized void setInstanceStatus(InstanceStatus status) {\n    InstanceStatus next = instanceStatusMapper.map(status);\n    if (next == null) {\n        return;\n    }\n    InstanceStatus prev = instanceInfo.setStatus(next);\n    if (prev != null) {\n        for (StatusChangeListener listener : listeners.values()) {\n            try {\n                listener.notify(new StatusChangeEvent(prev, next));\n            } catch (Exception e) {\n                logger.warn(\"failed to notify listener: {}\", listener.getId(),e);\n            }\n        }\n    }\n}\n```\n\n\n\n\n\n\n\n\n\nУͨһ״̬¼okʱlistenerʵStatusChangeListener Ҳǵ StatusChangeListener notify¼Ǵһ״̬Ӧеط¼Ȼ¼עᡣ\n\nʱΪҵ˷ȻȥһһӿڡǷǾ̬ڲӿڣ޷ֱӿʵࡣ\n\nҶԴĶ飬ңΪһܲ²⵽һĳط˳ʼĹǣҵ\nEurekaServiceRegistry.registerе reg.getApplicationInfoManager ʵʲôǷApplicationInfoManagerEurekaRegistrationеԡEurekaRegistrationEurekaAutoServiceRegistrationʵġ룬ǲԶװʲôҵEurekaClientAutoConfiguration࣬ȻBeanһЩԶװ䣬а EurekaClient  ApplicationInfoMangager  EurekaRegistration ȡ\n\n**2.2.2 EurekaClientConfiguration**\n\n\n\n```\n@Configuration(proxyBeanMethods = false)\n@ConditionalOnMissingRefreshScope\nprotected static class EurekaClientConfiguration {\n    @Autowired\n    private ApplicationContext context;\n    @Autowired\n    private AbstractDiscoveryClientOptionalArgs<?> optionalArgs;\n    @Bean(destroyMethod = \"shutdown\")\n    @ConditionalOnMissingBean(value = EurekaClient.class,search = SearchStrategy.CURRENT)\n    public EurekaClient eurekaClient(ApplicationInfoManager manager,EurekaClientConfig config) {\n        return new CloudEurekaClient(manager, config, this.optionalArgs,this.context);\n    }\n    @Bean\n    @ConditionalOnMissingBean(value = ApplicationInfoManager.class,search = SearchStrategy.CURRENT)\n    public ApplicationInfoManager eurekaApplicationInfoManager(\n        EurekaInstanceConfig config) {\n        InstanceInfo instanceInfo = new InstanceInfoFactory().create(config);\n        return new ApplicationInfoManager(config, instanceInfo);\n    }\n    @Bean\n    @ConditionalOnBean(AutoServiceRegistrationProperties.class)\n    @ConditionalOnProperty(\n        value = \"spring.cloud.service-registry.auto-registration.enabled\",\n        matchIfMissing = true)\n    public EurekaRegistration eurekaRegistration(EurekaClient eurekaClient,CloudEurekaInstanceConfig\n                                                 instanceConfig,ApplicationInfoManager applicationInfoManager, @Autowired(required = false)\n                                                 ObjectProvider<HealthCheckHandler> healthCheckHandler) {\n        return EurekaRegistration.builder(instanceConfig).with(applicationInfoManager).with(eurekaClient).with(healthCheckHandler).build();\n    }\n}\n```\n\n\n\n\n\n\n\n\n\nѷ֣ƺһҪBeanʱԶװ䣬ҲCloudEurekaClient ҿԺ׵ʶ𲢲²Eurekaͻ˵һ࣬ʵֺͷ˵ͨԼǺܶԴһ·Ҫôڹ췽ȥܶĳʼһЩִ̨еĳҪôͨ첽¼ķʽţǿһCloudEurekaClientĳʼ̣Ĺ췽лͨ super øĹ췽ҲDiscoveryClientĹ졣\n\n**2.2.3 CloudEurekaClient**\nsuper(applicationInfoManager, config, args);øĹ췽CloudEurekaClientĸDiscoveryClient.\n\n\n\n```\npublic CloudEurekaClient(ApplicationInfoManager applicationInfoManager,EurekaClientConfig config,AbstractDiscoveryClientOptionalArgs<?> args,ApplicationEventPublisher publisher) {\n    super(applicationInfoManager, config, args);\n    this.applicationInfoManager = applicationInfoManager;\n    this.publisher = publisher;\n    this.eurekaTransportField = ReflectionUtils.findField(DiscoveryClient.class,\"eurekaTransport\");\n    ReflectionUtils.makeAccessible(this.eurekaTransportField);\n}\n```\n\n\n\n\n\n\n\n\n\n**2.2.4 DiscoveryClient**\nǿԿյDiscoveryClient췽УзǳĴ롣ʵܶԲҪģ󲿷ֶһЩʼʼ˼ʱ\n\n* scheduler\n\n* heartbeatExecutor ʱ\n\n* cacheRefreshExecutor ʱȥͬ˵ʵб\n\n\n\n  ```\n  DiscoveryClient(ApplicationInfoManager applicationInfoManager,EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args,Provider<BackupRegistry> backupRegistryProvider,EndpointRandomizer endpointRandomizer) {\n      //ʡԲִ...\n      //ǷҪeureka serverϻȡַϢ\n      if (config.shouldFetchRegistry()) {\n          this.registryStalenessMonitor = new ThresholdLevelsMetric(this,METRIC_REGISTRY_PREFIX + \"lastUpdateSec_\", new long[]{15L, 30L, 60L, 120L, 240L,480L});\n      } else {\n          this.registryStalenessMonitor = ThresholdLevelsMetric.NO_OP_METRIC;\n      }\n      //ǷҪעᵽeureka server\n      if (config.shouldRegisterWithEureka()) {\n          this.heartbeatStalenessMonitor = new ThresholdLevelsMetric(this,METRIC_REGISTRATION_PREFIX + \"lastHeartbeatSec_\", new long[]{15L, 30L, 60L,120L, 240L, 480L});\n      } else {\n          this.heartbeatStalenessMonitor = ThresholdLevelsMetric.NO_OP_METRIC;\n      }\n      //ҪעᲢҲҪ·ַ\n      if (!config.shouldRegisterWithEureka() && !config.shouldFetchRegistry()) {\n  \n          return;  // no need to setup up an network tasks and we are done\n      }\n      try {\n          // default size of 2 - 1 each for heartbeat and cacheRefresh\n          scheduler = Executors.newScheduledThreadPool(2,new ThreadFactoryBuilder()       .setNameFormat(\"DiscoveryClient-%d\")\n                                                       .setDaemon(true)\n                                                       .build());\n          heartbeatExecutor = new ThreadPoolExecutor(1, clientConfig.getHeartbeatExecutorThreadPoolSize(), 0,\n                                                     TimeUnit.SECONDS,\n                                                     new SynchronousQueue<Runnable>(),\n                                                     new ThreadFactoryBuilder()\n                                                     .setNameFormat(\"DiscoveryClient-HeartbeatExecutor-%d\")\n                                                     .setDaemon(true)\n                                                     .build()\n                                                    );  // use direct handoff\n          cacheRefreshExecutor = new ThreadPoolExecutor(\n              1, clientConfig.getCacheRefreshExecutorThreadPoolSize(), 0,\n              TimeUnit.SECONDS,\n              new SynchronousQueue<Runnable>(),\n              new ThreadFactoryBuilder()\n              .setNameFormat(\"DiscoveryClient-CacheRefreshExecutor-%d\")\n              .setDaemon(true)\n              .build()\n          );  // use direct handoff\n          eurekaTransport = new EurekaTransport();\n          scheduleServerEndpointTask(eurekaTransport, args);\n          AzToRegionMapper azToRegionMapper;\n          if (clientConfig.shouldUseDnsForFetchingServiceUrls()) {\n              azToRegionMapper = new DNSBasedAzToRegionMapper(clientConfig);\n          } else {\n              azToRegionMapper = new PropertyBasedAzToRegionMapper(clientConfig);\n          }\n          if (null != remoteRegionsToFetch.get()) {\n  \n              azToRegionMapper.setRegionsToFetch(remoteRegionsToFetch.get().split(\",\"));\n          }\n          instanceRegionChecker = new InstanceRegionChecker(azToRegionMapper,\n                                                            clientConfig.getRegion());\n      } catch (Throwable e) {\n          throw new RuntimeException(\"Failed to initialize DiscoveryClient!\", e);\n      }\n      //ҪעᵽEureka serverǿ˳ʼʱǿעᣬregister()ע\n      if (clientConfig.shouldRegisterWithEureka() &&\n          clientConfig.shouldEnforceRegistrationAtInit()) {\n          try {\n              if (!register() ) {\n                  throw new IllegalStateException(\"Registration error at startup.Invalid server response.\");\n              }\n          } catch (Throwable th) {\n              logger.error(\"Registration error at startup: {}\", th.getMessage());\n              throw new IllegalStateException(th);\n          }\n      }\n      // finally, init the schedule tasks (e.g. cluster resolvers, heartbeat,instanceInfo replicator, fetch\n      initScheduledTasks();\n  }\n  ```\n\n\n\n\n\n\n\n\n\n**2.2.5 initScheduledTasks**\ninitScheduledTasks ȥһʱ\n\n*   ˿עˢ·бῪcacheRefreshExecutorʱ\n*   ˷עᵽEurekaͨҪ.\n\n1.  \n\nͨڲʵStatusChangeListener ʵ״̬ؽӿڣǰڷģnotifyķʵϻ֡\n\n\n\n```\nprivate void initScheduledTasks() {\n    //˿עˢ·бῪcacheRefreshExecutorʱ\n    if (clientConfig.shouldFetchRegistry()) {\n        // registry cache refresh timer\n        int registryFetchIntervalSeconds =\n            clientConfig.getRegistryFetchIntervalSeconds();\n        int expBackOffBound =\n            clientConfig.getCacheRefreshExecutorExponentialBackOffBound();\n        scheduler.schedule(\n            new TimedSupervisorTask(\n                \"cacheRefresh\",\n                scheduler,\n                cacheRefreshExecutor,\n                registryFetchIntervalSeconds,\n                TimeUnit.SECONDS,\n                expBackOffBound,\n                new CacheRefreshThread()\n            ),\n            registryFetchIntervalSeconds, TimeUnit.SECONDS);\n    }\n    //˷עᵽEurekaͨҪ\n    if (clientConfig.shouldRegisterWithEureka()) {\n        int renewalIntervalInSecs =\n            instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();\n        int expBackOffBound =\n            clientConfig.getHeartbeatExecutorExponentialBackOffBound();\n        logger.info(\"Starting heartbeat executor: \" + \"renew interval is: {}\",\n                    renewalIntervalInSecs);\n        // Heartbeat timer\n        scheduler.schedule(\n            new TimedSupervisorTask(\n                \"heartbeat\",\n                scheduler,\n                heartbeatExecutor,\n                renewalIntervalInSecs,\n                TimeUnit.SECONDS,\n                expBackOffBound,\n                new HeartbeatThread()\n            ),\n            renewalIntervalInSecs, TimeUnit.SECONDS);\n        // InstanceInfo replicator ʼһ:instanceInfoReplicator\n        instanceInfoReplicator = new InstanceInfoReplicator(\n            this,\n            instanceInfo,\n            clientConfig.getInstanceInfoReplicationIntervalSeconds(),\n            2); // burstSize\n        statusChangeListener = new ApplicationInfoManager.StatusChangeListener()\n        {\n            @Override\n            public String getId() {\n                return \"statusChangeListener\";\n            }\n            @Override\n            public void notify(StatusChangeEvent statusChangeEvent) {\n                if (InstanceStatus.DOWN == statusChangeEvent.getStatus() ||\n                    InstanceStatus.DOWN ==\n                    statusChangeEvent.getPreviousStatus()) {\n                    // log at warn level if DOWN was involved\n                    logger.warn(\"Saw local status change event {}\",\n                                statusChangeEvent);\n                } else {\n                    logger.info(\"Saw local status change event {}\",\n                                statusChangeEvent);\n                }\n                instanceInfoReplicator.onDemandUpdate();\n            }\n        };\n        //עʵ״̬仯ļ\n        if (clientConfig.shouldOnDemandUpdateStatusChange()) {\n            applicationInfoManager.registerStatusChangeListener(statusChangeListener);\n        }\n        //һʵϢҪΪ˿һʱ̣߳ÿ40жʵϢǷע\n        instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationInte\n                                     rvalSeconds());\n    } else {\n        logger.info(\"Not registering with Eureka server per configuration\");\n    }\n}\n```\n\n\n\n\n\n\n\n\n\n**2.2.6 onDemandUpdate**\nҪǸʵǷ仯עĵݡ\n\n\n\n```\npublic boolean onDemandUpdate() {\n    //ж\n    if (rateLimiter.acquire(burstSize, allowedRatePerMinute)) {\n        if (!scheduler.isShutdown()) {\n            //ύһ\n            scheduler.submit(new Runnable() {\n                @Override\n                public void run() {\n                    logger.debug(\"Executing on-demand update of local InstanceInfo\");\n                    //ȡ֮ǰѾύҲstartύĸûִɣȡ֮ǰ\n                    Future latestPeriodic = scheduledPeriodicRef.get();\n                    if (latestPeriodic != null && !latestPeriodic.isDone()) {\n                        logger.debug(\"Canceling the latest scheduled update, it will be rescheduled at the end of on demand update\");\n                        latestPeriodic.cancel(false);//δɣȡ\n                    }\n                    //ͨrunʱִУ൱еһ\n                    InstanceInfoReplicator.this.run();\n                }\n            });\n            return true;\n        } else {\n            logger.warn(\"Ignoring onDemand update due to stopped scheduler\");\n            return false;\n        }\n    } else {\n        logger.warn(\"Ignoring onDemand update due to rate limiter\");\n        return false;\n    }\n}\n```\n\n\n\n\n\n\n\n\n\n**2.2.7 run**\nrunʵϺǰԶװִеķע᷽һģҲǵ register зעᣬfinallyУÿ30sᶨʱִһµǰrun м顣\n\n\n\n```\npublic void run() {\n    try {\n        discoveryClient.refreshInstanceInfo();\n        Long dirtyTimestamp = instanceInfo.isDirtyWithTime();\n        if (dirtyTimestamp != null) {\n            discoveryClient.register();\n            instanceInfo.unsetIsDirty(dirtyTimestamp);\n        }\n    } catch (Throwable t) {\n        logger.warn(\"There was a problem with the instance info replicator\", t);\n    } finally {\n        Future next = scheduler.schedule(this, replicationIntervalSeconds,\n                                         TimeUnit.SECONDS);\n        scheduledPeriodicRef.set(next);\n    }\n}\n```\n\n\n\n\n\n\n\n\n\n**2.2.8 register**\nգҵעˣ\neurekaTransport.registrationClient.register յõ AbstractJerseyEurekaHttpClient#register(...)` ȻԼȥ룬ͻᷢȥ֮ǰкܶȥĴ룬繤ģʽװģʽȡ\n\n\n\n```\nboolean register() throws Throwable {\n    logger.info(PREFIX + \"{}: registering service...\", appPathIdentifier);\n    EurekaHttpResponse<Void> httpResponse;\n    try {\n        httpResponse = eurekaTransport.registrationClient.register(instanceInfo);\n    } catch (Exception e) {\n        logger.warn(PREFIX + \"{} - registration failed {}\", appPathIdentifier,e.getMessage(), e);\n        throw e;\n    }\n    if (logger.isInfoEnabled()) {\n        logger.info(PREFIX + \"{} - registration status: {}\", appPathIdentifier,httpResponse.getStatusCode());\n    }\n    return httpResponse.getStatusCode() == Status.NO_CONTENT.getStatusCode();\n}\n```\n\n\n\n\n\n\n\n\n\nȻǷһhttp󣬷Eureka-Serverapps/${APP_NAME}ӿڣǰʵϢ͵Eureka Serverб档\n\nˣǻѾ֪Spring Cloud Eureka ʱѷϢעᵽEureka Serverϵˡ\n\n\n\n```\npublic EurekaHttpResponse<Void> register(InstanceInfo info) {\n    String urlPath = \"apps/\" + info.getAppName();\n    ClientResponse response = null;\n    try {\n        Builder resourceBuilder =\n            jerseyClient.resource(serviceUrl).path(urlPath).getRequestBuilder();\n        addExtraHeaders(resourceBuilder);\n        response = resourceBuilder\n            .header(\"Accept-Encoding\", \"gzip\")\n            .type(MediaType.APPLICATION_JSON_TYPE)\n            .accept(MediaType.APPLICATION_JSON)\n            .post(ClientResponse.class, info);\n        return\n            anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build();\n    } finally {\n        if (logger.isDebugEnabled()) {\n            logger.debug(\"Jersey HTTP POST {}/{} with instance {}; statusCode={}\", serviceUrl, urlPath, info.getId(),\n                         response == null ? \"N/A\" : response.getStatus());\n        }\n        if (response != null) {\n            response.close();\n        }\n    }\n}\n```\n\n\n\n\n\n\n\n\n\nǣƺʼ⻹ûнҲSpring BootӦʱstartյ\nStatusChangeListener.notify ȥ·һ״̬ûֱӵregisterעᡣǼȥһ statusChangeListener.notify \n\n**2.2.9 ܽ**\nˣ֪Eureka Clientעʱطִзע\n\n1.  Spring BootʱԶװƽCloudEurekaClientע뵽ִ˹췽ڹ췽һʱÿ40sִһжϣжʵϢǷ˱仯ᷢע\n2.  Spring BootʱͨrefreshյStatusChangeListener.notifyз״̬ļķܵ¼֮ȥִзעᡣ\n    **2.3 Server߼**\n    ûԴʵ֮ǰһ֪϶ķʵݽ˴洢ôȥEureka Server˿һ´̡\n\nڣ\ncom.netflix.eureka.resources.ApplicationResource.addInstance() \n\nҿԷ֣ṩREST񣬲õjerseyʵֵġJerseyǻJAX-RS׼ṩRESTʵֵ֧֣Ͳչˡ\n\n**2.3.1 addInstance()**\nEurekaClientregisterעʱ\nApplicationResource.addInstance\n\nעǷһPOSTϵǰʵϢ ApplicationResource  addInstanceзעᡣ\n\n\n\n```\n@POST\n@Consumes({\"application/json\", \"application/xml\"})\npublic Response addInstance(InstanceInfo info, @HeaderParam(\"x-netflix-discovery-replication\") String isReplication) {\n    logger.debug(\"Registering instance {} (replication={})\", info.getId(),\n                 isReplication);\n    DataCenterInfo dataCenterInfo = info.getDataCenterInfo();\n    if (dataCenterInfo instanceof UniqueIdentifier) {\n        String dataCenterInfoId =\n            ((UniqueIdentifier)dataCenterInfo).getId();\n        if (this.isBlank(dataCenterInfoId)) {\n            boolean experimental =            \"true\".equalsIgnoreCase(this.serverConfig.getExperimental(\"registration.validation.dataCenterInfoId\"));\n            if (experimental) {\n                String entity = \"DataCenterInfo of type \" +\n                    dataCenterInfo.getClass() + \" must contain a valid id\";\n                return Response.status(400).entity(entity).build();\n            }\n            if (dataCenterInfo instanceof AmazonInfo) {\n                AmazonInfo amazonInfo = (AmazonInfo)dataCenterInfo;\n                String effectiveId = amazonInfo.get(MetaDataKey.instanceId);\n                if (effectiveId == null) {\n                    amazonInfo.getMetadata().put(MetaDataKey.instanceId.getName(), info.getId());\n                }\n            } else {\n                logger.warn(\"Registering DataCenterInfo of type {} without an appropriate id\", dataCenterInfo.getClass());\n            }\n        }\n    }\n    this.registry.register(info, \"true\".equals(isReplication));\n    return Response.status(204).build();\n}\n\n```\n\n\n\n\n\n\n\n\n\n**2.3.2 register**\n\nPeerAwareInstanceRegistryImplϵͼϵͼԿPeerAwareInstanceRegistryӿΪLeaseManagerLookupService,\n\n*   LookupServiceķʾΪ\n*   LeaseManager˴ͻעᣬԼעȲ\n\n![SpringCloudϵСSpring Cloud Դ֮Eureka-Դ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/897d97d444664479bda0430417ec683f1f079a.jpg \"SpringCloudϵСSpring Cloud Դ֮Eureka-Դ\") addInstance Уյõ\nPeerAwareInstanceRegistryImpl.register \n\n* leaseDuration ʾԼʱ䣬Ĭ90sҲǵ˳90sûյͻ˵޳ýڵ\n\n* super.registerڵע\n\n* ϢƵEureka ServerȺеϣͬʵҲܼ򵥣ǻüȺенڵ㣬Ȼע\n\n\n\n  ```\n  public void register(final InstanceInfo info, final boolean isReplication) {\n      int leaseDuration = Lease.DEFAULT_DURATION_IN_SECS;\n      if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() >0) {\n          leaseDuration = info.getLeaseInfo().getDurationInSecs(); //ͻԼʱʱ䣬ÿͻ˵ʱ\n      }\n      super.register(info, leaseDuration, isReplication); //ڵע\n      //ƵEureka ServerȺеڵ\n      replicateToPeers(Action.Register, info.getAppName(), info.getId(), info,\n                       null, isReplication);\n  }\n  ```\n\n\n\n\n\n\n\n\n\n**2.3.3 AbstractInstanceRegistry.register**\n˵Eureka-Serverķעᣬʵǽͻ˴ݹʵݱ浽Eureka-ServerеConcurrentHashMapС\n\n\n\n```\npublic void register(InstanceInfo registrant, int leaseDuration, boolean\n                     isReplication) {\n    try {\n        read.lock();\n        //registryлõǰʵϢappName\n        Map<String, Lease<InstanceInfo>> gMap =\n            registry.get(registrant.getAppName());\n        REGISTER.increment(isReplication); //עϢ\n        if (gMap == null) {//ǰappNameǵһעᣬʼһConcurrentHashMap\n            final ConcurrentHashMap<String, Lease<InstanceInfo>> gNewMap = new\n                ConcurrentHashMap<String, Lease<InstanceInfo>>();\n            gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap);\n            if (gMap == null) {\n                gMap = gNewMap;\n            }\n        }\n        //gMapвѯѾڵLeaseϢLeaseķΪԼʵѷṩߵʵϢװһleaseṩ˶ڸķʵԼ\n        Lease<InstanceInfo> existingLease = gMap.get(registrant.getId());\n        // instanceѾǣͿͻ˵instanceϢȽϣʱµǸΪЧinstanceϢ\n        if (existingLease != null && (existingLease.getHolder() != null)) {\n            Long existingLastDirtyTimestamp =\n                existingLease.getHolder().getLastDirtyTimestamp();\n            Long registrationLastDirtyTimestamp =\n                registrant.getLastDirtyTimestamp();\n            logger.debug(\"Existing lease found (existing={}, provided={}\",\n                         existingLastDirtyTimestamp, registrationLastDirtyTimestamp);\n            // this is a > instead of a >= because if the timestamps are equal,we still take the remote transmitted\n            // InstanceInfo instead of the server local copy.\n            if (existingLastDirtyTimestamp > registrationLastDirtyTimestamp) {\n                logger.warn(\"There is an existing lease and the existing lease's dirty timestamp {} is greater\" +\n                            \" than the one that is being registered {}\",\n                            existingLastDirtyTimestamp, registrationLastDirtyTimestamp);\n                logger.warn(\"Using the existing instanceInfo instead of the new instanceInfo as the registrant\");\n                registrant = existingLease.getHolder();\n            }\n        } else {\n            //leaseʱ뵽δ룬\n            synchronized (lock) {\n                if (this.expectedNumberOfClientsSendingRenews > 0) {\n                    // Since the client wants to register it, increase the number of clients sending renews\n                    this.expectedNumberOfClientsSendingRenews =\n                        this.expectedNumberOfClientsSendingRenews + 1;\n                    updateRenewsPerMinThreshold();\n                }\n            }\n            logger.debug(\"No previous lease information found; it is new registration\");\n        }\n        //һlease\n        Lease<InstanceInfo> lease = new Lease<InstanceInfo>(registrant,\n                                                            leaseDuration);\n        if (existingLease != null) {\n            // ԭLeaseϢʱserviceUpTimestamp, ֤ʱһֱǵһעǸ\n            lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp());\n        }\n        gMap.put(registrant.getId(), lease);\n        synchronized (recentRegisteredQueue) {//ӵעĶ\n            recentRegisteredQueue.add(new Pair<Long, String>(\n                System.currentTimeMillis(),\n                registrant.getAppName() + \"(\" + registrant.getId() + \")\"));\n        }\n        // ʵ״̬Ƿ仯ǲҴڣ򸲸ԭ״̬\n        if (!InstanceStatus.UNKNOWN.equals(registrant.getOverriddenStatus())) {\n            logger.debug(\"Found overridden status {} for instance {}. Checking to see if needs to be add to the \"\n                         + \"overrides\", registrant.getOverriddenStatus(),\n                         registrant.getId());\n            if (!overriddenInstanceStatusMap.containsKey(registrant.getId())) {\n                logger.info(\"Not found overridden id {} and hence adding it\",\n                            registrant.getId());\n                overriddenInstanceStatusMap.put(registrant.getId(),\n                                                registrant.getOverriddenStatus());\n            }\n        }\n        InstanceStatus overriddenStatusFromMap =\n            overriddenInstanceStatusMap.get(registrant.getId());\n        if (overriddenStatusFromMap != null) {\n            logger.info(\"Storing overridden status {} from map\",\n                        overriddenStatusFromMap);\n            registrant.setOverriddenStatus(overriddenStatusFromMap);\n        }\n        // Set the status based on the overridden status rules\n        InstanceStatus overriddenInstanceStatus =\n            getOverriddenInstanceStatus(registrant, existingLease, isReplication);\n        registrant.setStatusWithoutDirty(overriddenInstanceStatus);\n        // õinstanceStatusжǷUP״̬\n        if (InstanceStatus.UP.equals(registrant.getStatus())) {\n            lease.serviceUp();\n        }\n        // עΪ\n        registrant.setActionType(ActionType.ADDED);\n        // Լ¼У¼ʵÿα仯 עϢȡ\n        recentlyChangedQueue.add(new RecentlyChangedItem(lease));\n        registrant.setLastUpdatedTimestamp();\n        //ûʧЧ\n        invalidateCache(registrant.getAppName(), registrant.getVIPAddress(),\n                        registrant.getSecureVipAddress());\n        logger.info(\"Registered instance {}/{} with status {} (replication={})\",\n                    registrant.getAppName(), registrant.getId(),\n                    registrant.getStatus(), isReplication);\n    } finally {\n        read.unlock();\n    }\n}\n```\n\n\n\n\n\n\n\n\n\n**2.3.4 С**\nˣǾͰѷעڿͻ˺ͷ˵ĴһϸķʵEureka Serverˣѿͻ˵ĵַϢ浽ConcurrentHashMapд洢ҷṩߺע֮䣬ὨһơڼطṩߵĽ״̬\n\n**2.4 Eureka Ķ༶**\nEureka Server(registryreadWriteCacheMapreadOnlyCacheMap)עϢĬ¶ʱÿ30sreadWriteCacheMapͬreadOnlyCacheMapÿ60s90sδԼĽڵ㣬Eureka Clientÿ30sreadOnlyCacheMap·עϢͻ˷עregistry·עϢ\n\n**2.4.1 ༶**\nΪʲôҪƶ༶أԭܼ򵥣ǵڴģķע͸ʱֻ޸һConcurrentHashMapݣôƱΪĴڵ¾Ӱܡ\n\nEurekaAPģֻͣҪտþСõ༶ʵֶд롣ע᷽дʱֱдڴעд֮ʧЧд档\n\nȡעϢӿȴֻȡֻûȥдȡдûȥڴעȡֻȡ˴ϸӣңд»дֻ\n\n*   responseCacheUpdateIntervalMs  readOnlyCacheMap µĶʱʱĬΪ30\n*   responseCacheAutoExpirationInSeconds : readWriteCacheMap ʱ䣬ĬΪ 180 롣\n    **2.4.2 עĻʧЧ**\n    AbstractInstanceRegistry.register󣬻invalidateCache(registrant.getAppName(), registrant.getVIPAddress(),registrant.getSecureVipAddress()); ʹöдʧЧ\n\n\n\n```\npublic void invalidate(Key... keys) {\n    for (Key key : keys) {\n        logger.debug(\"Invalidating the response cache key : {} {} {} {}, {}\",\n                     key.getEntityType(), key.getName(), key.getVersion(),\n                     key.getType(), key.getEurekaAccept());\n        readWriteCacheMap.invalidate(key);\n        Collection<Key> keysWithRegions = regionSpecificKeys.get(key);\n        if (null != keysWithRegions && !keysWithRegions.isEmpty()) {\n            for (Key keysWithRegion : keysWithRegions) {\n                logger.debug(\"Invalidating the response cache key : {} {} {} {} {}\",\n                             key.getEntityType(), key.getName(),\n                             key.getVersion(), key.getType(), key.getEurekaAccept());\n                readWriteCacheMap.invalidate(keysWithRegion);\n            }\n        }\n    }\n}\n```\n\n\n\n\n\n\n\n\n\n**2.4.3 ʱͬ**\nResponseCacheImplĹ췽Уһʱᶨʱдеݱ仯иºͬ\n\n\n\n```\nprivate TimerTask getCacheUpdateTask() {\n    return new TimerTask() {\n        @Override\n        public void run() {\n            logger.debug(\"Updating the client cache from response cache\");\n            for (Key key : readOnlyCacheMap.keySet()) {\n                if (logger.isDebugEnabled()) {\n                    logger.debug(\"Updating the client cache from response cache for key : {} {} {} {}\",\n                                 key.getEntityType(), key.getName(),\n                                 key.getVersion(), key.getType());\n                }\n                try {\n                    CurrentRequestVersion.set(key.getVersion());\n                    Value cacheValue = readWriteCacheMap.get(key);\n                    Value currentCacheValue = readOnlyCacheMap.get(key);\n                    if (cacheValue != currentCacheValue) {\n                        readOnlyCacheMap.put(key, cacheValue);\n                    }\n                } catch (Throwable th) {\n                    logger.error(\"Error while updating the client cache from response cache for key {}\", key.toStringCompact(), th);\n                } finally {\n                    CurrentRequestVersion.remove();\n                }\n            }\n        }\n    };\n}\n```\n\n\n\n\n\n\n\n\n\n**2.5 Լ**\nνķԼʵһơͻ˻ᶨڷԼô򵥸ҿһ´ʵ\n\n**2.5.1 initScheduledTasks**\nͻ˻\nDiscoveryClient.initScheduledTasks УһĶʱ\n\n\n\n```\n// Heartbeat timer\nscheduler.schedule(\n    new TimedSupervisorTask(\n        \"heartbeat\",\n        scheduler,\n        heartbeatExecutor,\n        renewalIntervalInSecs,\n        TimeUnit.SECONDS,\n        expBackOffBound,\n        new HeartbeatThread()\n    ),\n    renewalIntervalInSecs, TimeUnit.SECONDS);\n```\n\n\n\n\n\n\n\n\n\n**2.5.2 HeartbeatThread**\nȻʱУִһ HearbeatThread ̣̻߳߳ᶨʱrenew()Լ\n\n\n\n```\n//ÿ30sһ\nprivate class HeartbeatThread implements Runnable {\n    public void run() {\n        if (renew()) {\n            lastSuccessfulHeartbeatTimestamp = System.currentTimeMillis();\n        }\n    }\n}\n```\n\n\n\n\n\n\n\n\n\n**2.5.3 յĴ**\nApplicationResource.getInstanceInfoӿУ᷵һInstanceResourceʵڸʵ£һstatusUpdateĽӿ״̬\n\n\n\n```\n@Path(\"{id}\")\npublic InstanceResource getInstanceInfo(@PathParam(\"id\") String id) {\n    return new InstanceResource(this, id, serverConfig, registry);\n}\n```\n\n\n\n\n\n\n\n\n\n**2.5.4 InstanceResource.statusUpdate()**\nڸ÷Уصע registry.statusUpdate \nAbstractInstanceRegistry.statusUpdateָṩڷ˴洢Ϣеı仯\n\n\n\n```\n@PUT\n@Path(\"status\")\npublic Response statusUpdate(\n    @QueryParam(\"value\") String newStatus,\n    @HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication,\n    @QueryParam(\"lastDirtyTimestamp\") String lastDirtyTimestamp) {\n    try {\n        if (registry.getInstanceByAppAndId(app.getName(), id) == null) {\n            logger.warn(\"Instance not found: {}/{}\", app.getName(), id);\n            return Response.status(Status.NOT_FOUND).build();\n        }\n        boolean isSuccess = registry.statusUpdate(app.getName(), id,                      \n                                                  InstanceStatus.valueOf(newStatus), lastDirtyTimestamp,\n                                                  \"true\".equals(isReplication));\n        if (isSuccess) {\n            logger.info(\"Status updated: {} - {} - {}\", app.getName(), id,\n                        newStatus);\n            return Response.ok().build();\n        } else {\n            logger.warn(\"Unable to update status: {} - {} - {}\", app.getName(),\n                        id, newStatus);\n            return Response.serverError().build();\n        }\n    } catch (Throwable e) {\n        logger.error(\"Error updating instance {} for status {}\", id,\n                     newStatus);\n        return Response.serverError().build();\n    }\n}\n```\n\n\n\n\n\n\n\n\n\n**2.5.5 AbstractInstanceRegistry.statusUpdate**\nУõӦöӦʵбȻLease.renew()ȥԼ\n\n\n\n```\npublic boolean statusUpdate(String appName, String id,\n                            InstanceStatus newStatus, String\n                            lastDirtyTimestamp,\n                            boolean isReplication) {\n    try {\n        read.lock();\n        // ״̬Ĵ ״̬ͳ\n        STATUS_UPDATE.increment(isReplication);\n        // ӱȡʵϢ\n        Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);\n        Lease<InstanceInfo> lease = null;\n        if (gMap != null) {\n            lease = gMap.get(id);\n        }\n        // ʵڣֱӷأʾʧ\n        if (lease == null) {\n            return false;\n        } else {\n            // ִһleaserenewҪǸinstanceʱ䡣\n            lease.renew();\n            // ȡinstanceʵϢ\n            InstanceInfo info = lease.getHolder();\n            // Lease is always created with its instance info object.\n            // This log statement is provided as a safeguard, in case this invariant is violated.\n            if (info == null) {\n                logger.error(\"Found Lease without a holder for instance id {}\",\n                             id);\n            }\n            // instanceϢΪʱʵ״̬˱仯\n            if ((info != null) && !(info.getStatus().equals(newStatus))) {\n                // ״̬UP״̬ôһserviceUp() , ҪǸ·עʱ\n                \n                    if (InstanceStatus.UP.equals(newStatus)) {\n                        lease.serviceUp();\n                    }\n                // instance Id ״̬ӳϢ븲ǻMAPȥ\n                overriddenInstanceStatusMap.put(id, newStatus);\n                // Set it for transfer of overridden status to replica on\n                // ø״̬ʵϢȥ\n                info.setOverriddenStatus(newStatus);\n                long replicaDirtyTimestamp = 0;\n                info.setStatusWithoutDirty(newStatus);\n                if (lastDirtyTimestamp != null) {\n                    replicaDirtyTimestamp = Long.valueOf(lastDirtyTimestamp);\n                }\n                // If the replication's dirty timestamp is more than the existing one, just update\n                // it to the replica's.\n                // replicaDirtyTimestamp ʱinstancegetLastDirtyTimestamp() ,\n\n                if (replicaDirtyTimestamp > info.getLastDirtyTimestamp()) {\n                    info.setLastDirtyTimestamp(replicaDirtyTimestamp);\n                }\n                info.setActionType(ActionType.MODIFIED);\n                recentlyChangedQueue.add(new RecentlyChangedItem(lease));\n                info.setLastUpdatedTimestamp();\n                //д\n                invalidateCache(appName, info.getVIPAddress(),\n                                info.getSecureVipAddress());\n            }\n            return true;\n        }\n    } finally {\n        read.unlock();\n    }\n}\n```\n\n\n\n\n\n\n\n\n\nˣԼܾͷˡ\n\n**2.6 **\nǼоķֹ̣ǿͻҪܹ\n\nʱȡָṩߵĵַб\nEureka server˵ַ仯ʱҪ̬֪\n**2.6.1 DiscoveryClientʱѯ**\n췽УǰĿͻĬϿfetchRegistryeureka-serverȡݡ\n\n\n\n```\nDiscoveryClient(ApplicationInfoManager applicationInfoManager,\n                EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args,\n                Provider<BackupRegistry> backupRegistryProvider,\n                EndpointRandomizer endpointRandomizer) {\n    if (clientConfig.shouldFetchRegistry() && !fetchRegistry(false)) {\n        fetchRegistryFromBackup();\n    }\n}\n```\n\n\n\n\n\n\n\n\n\n**2.6.2 fetchRegistry**\n\n\n\n```\nprivate boolean fetchRegistry(boolean forceFullRegistryFetch) {\n    Stopwatch tracer = FETCH_REGISTRY_TIMER.start();\n    try {\n        // If the delta is disabled or if it is the first time, get all\n        // applications\n        Applications applications = getApplications();\n        if (clientConfig.shouldDisableDelta()\n            ||\n            (!Strings.isNullOrEmpty(clientConfig.getRegistryRefreshSingleVipAddress()))\n            || forceFullRegistryFetch\n            || (applications == null)\n            || (applications.getRegisteredApplications().size() == 0)\n            || (applications.getVersion() == -1)) //Client application does not have latest library supporting delta\n        {\n            logger.info(\"Disable delta property : {}\",\n                        clientConfig.shouldDisableDelta());\n            logger.info(\"Single vip registry refresh property : {}\",\n                        clientConfig.getRegistryRefreshSingleVipAddress());\n            logger.info(\"Force full registry fetch : {}\",\n                        forceFullRegistryFetch);\n            logger.info(\"Application is null : {}\", (applications == null));\n            logger.info(\"Registered Applications size is zero : {}\",\n                        (applications.getRegisteredApplications().size() == 0));\n            logger.info(\"Application version is -1: {}\",\n                        (applications.getVersion() == -1));\n            getAndStoreFullRegistry();\n        } else {\n            getAndUpdateDelta(applications);\n        }\n        applications.setAppsHashCode(applications.getReconcileHashCode());\n        logTotalInstances();\n    } catch (Throwable e) {\n        logger.error(PREFIX + \"{} - was unable to refresh its cache! status = {}\", appPathIdentifier, e.getMessage(), e);\n        return false;\n    } finally {\n        if (tracer != null) {\n            tracer.stop();\n        }\n    }\n    // Notify about cache refresh before updating the instance remote status\n    onCacheRefreshed();\n    // Update remote status based on refreshed data held in the cache\n    updateInstanceRemoteStatus();\n    // registry was fetched successfully, so return true\n    return true;\n}\n```\n\n\n\n\n\n\n\n\n\n**2.6.3 ʱˢ±صַб**\nÿ30sһ\nDiscoveryClientʱ򣬻ʼһЩǰǷˡһ̬±طַб cacheRefreshTask \n\nִеCacheRefreshThread̡߳һִе񣬾һ¡\n\n\n\n```\nprivate void initScheduledTasks() {\n    if (clientConfig.shouldFetchRegistry()) {\n        // registry cache refresh timer\n        int registryFetchIntervalSeconds =\n            clientConfig.getRegistryFetchIntervalSeconds();\n        int expBackOffBound =\n            clientConfig.getCacheRefreshExecutorExponentialBackOffBound();\n        cacheRefreshTask = new TimedSupervisorTask(\n            \"cacheRefresh\",\n            scheduler,\n            cacheRefreshExecutor,\n            registryFetchIntervalSeconds,\n            TimeUnit.SECONDS,\n            expBackOffBound,\n            new CacheRefreshThread()\n        );\n        scheduler.schedule(\n            cacheRefreshTask,\n            registryFetchIntervalSeconds, TimeUnit.SECONDS);\n    }\n```\n\n\n\n\n\n\n\n\n\n**2.6.4 TimedSupervisorTask**\nϿTimedSupervisorTaskǹ̶һʱͻὫһڵļʱʱôÿμʱ䶼һһֱⲿ趨ΪֹһٳʱʱֻԶָΪʼֵƻֵѧϰġ\n\n\n\n```\npublic void run() {\n      Future future = null;\n  try {\n    //ʹFuture趨̵߳ĳʱʱ䣬ǰ߳̾Ͳ޵ȴ\n    future = executor.submit(task);\n    threadPoolLevelGauge.set((long) executor.getActiveCount());\n    //ָȴ̵߳ʱ\n    future.get(timeoutMillis, TimeUnit.MILLISECONDS);  // block until done or timeout\n    //delayǸõıõǵÿִɹὫdelay\n    delay.set(timeoutMillis);\n    threadPoolLevelGauge.set((long) executor.getActiveCount());\n } catch (TimeoutException e) {\n    logger.error(\"task supervisor timed out\", e);\n    timeoutCounter.increment();\n    long currentDelay = delay.get();\n    //̳߳ʱʱ򣬾Ͱdelayᳬⲿʱ趨ʱʱ\n    long newDelay = Math.min(maxDelay, currentDelay * 2);\n    //Ϊµֵǵ̣߳CAS\n    delay.compareAndSet(currentDelay, newDelay);\n } catch (RejectedExecutionException e) {\n    //һ̳߳صз˴񣬴˾ܾԣͻὫͣ\n    if (executor.isShutdown() || scheduler.isShutdown()) {\n      logger.warn(\"task supervisor shutting down, reject the task\", e);\n   } else {\n      logger.error(\"task supervisor rejected the task\", e);\n   }\n    rejectedCounter.increment();\n } catch (Throwable e) {\n    //һδ֪쳣ͣ\n    if (executor.isShutdown() || scheduler.isShutdown()) {\n      logger.warn(\"task supervisor shutting down, can't accept the task\");\n   } else {\n      logger.error(\"task supervisor threw an exception\", e);\n   }\n    throwableCounter.increment();\n } finally {\n    //ҪôִϣҪô쳣cancel\n    if (future != null) {\n      future.cancel(true);\n   }\n    //ֻҪûָֹͣȴʱִ֮һͬ\n    if (!scheduler.isShutdown()) {\n      //ԭֻҪûֹͣٴһִʱʱdealyֵ\n      //ⲿʱĳʱʱΪ30루췽timeoutʱΪ50(췽expBackOffBound)\n      //һûгʱô30ʼ\n      //һʱˣô50ʼ쳣иԶĲԶ60볬50룩\n      scheduler.schedule(this, delay.get(), TimeUnit.MILLISECONDS);\n   }\n }\n}\n```\n\n\n\n\n\n\n\n\n\n**2.6.5 refreshRegistry**\nδҪ߼\n\n* жremoteRegionsǷ˱仯\n\n* fetchRegistryȡطַ\n\n\n\n  ```\n  @VisibleForTesting\n  void refreshRegistry() {\n      try {\n          boolean isFetchingRemoteRegionRegistries =\n              isFetchingRemoteRegionRegistries();\n          boolean remoteRegionsModified = false;\n          //awsϣжһԶµϢ͵ǰԶϢбȽϣȣ\n          String latestRemoteRegions =\n              clientConfig.fetchRegistryForRemoteRegions();\n          if (null != latestRemoteRegions) {\n              String currentRemoteRegions = remoteRegionsToFetch.get();\n              if (!latestRemoteRegions.equals(currentRemoteRegions)) {\n                  //жһ\n              }\n              boolean success = fetchRegistry(remoteRegionsModified);\n              if (success) {\n                  registrySize = localRegionApps.get().size();\n                  lastSuccessfulRegistryFetchTimestamp =\n                      System.currentTimeMillis();\n              }\n              // ʡ\n          } catch (Throwable e) {\n              logger.error(\"Cannot fetch registry from server\", e);\n          }\n      }\n  ```\n\n\n\n\n\n\n\n\n\n**2.6.6 fetchRegistry**\n\n\n\n  ```\n  private boolean fetchRegistry(boolean forceFullRegistryFetch) {\n      Stopwatch tracer = FETCH_REGISTRY_TIMER.start();\n      try {\n          // If the delta is disabled or if it is the first time, get all\n          // applications\n          // ȡػķбϢ\n          Applications applications = getApplications();\n          //ж϶ȷǷ񴥷ȫ£һ㶼ȫ£\n          //1\\. Ƿ£\n          //2\\. Ƿĳregionرע\n          //3\\. ⲿʱǷָͨȫ£\n          //4\\. ػδЧķбϢ\n          if (clientConfig.shouldDisableDelta()\n              ||\n              (!Strings.isNullOrEmpty(clientConfig.getRegistryRefreshSingleVipAddress()))\n              || forceFullRegistryFetch\n              || (applications == null)\n              || (applications.getRegisteredApplications().size() == 0)\n              || (applications.getVersion() == -1)) //Client application does not\n              have latest library supporting delta\n          {\n              //ȫ\n              getAndStoreFullRegistry();\n          } else {\n              //\n              getAndUpdateDelta(applications);\n          }\n          //¼һhash\n          applications.setAppsHashCode(applications.getReconcileHashCode());\n          logTotalInstances(); //־ӡӦõʵ֮\n      } catch (Throwable e) {\n          logger.error(PREFIX + \"{} - was unable to refresh its cache! status = {}\", appPathIdentifier, e.getMessage(), e);\n          return false;\n      } finally {\n          if (tracer != null) {\n              tracer.stop();\n          }\n      }\n      //ػµ¼㲥עļע÷ѱCloudEurekaClientд\n      onCacheRefreshed();\n      // Update remote status based on refreshed data held in the cache\n      //ոոµĻУEureka serverķба˵ǰӦõ״̬\n      //ǰʵĳԱlastRemoteInstanceStatus¼һθµĵǰӦ״̬\n      //״̬updateInstanceRemoteStatusȽ һ£͸lastRemoteInstanceStatusҹ㲥Ӧ¼\n      updateInstanceRemoteStatus();\n      // registry was fetched successfully, so return true\n      return true;\n  }\n  ```\n\n\n\n\n\n\n\n\n\n**2.6.7 getAndStoreFullRegistry**\neureka server˻ȡעĵĵַϢȻ²õػ localRegionApps \n\n\n\n```\nprivate void getAndStoreFullRegistry() throws Throwable {\n    long currentUpdateGeneration = fetchRegistryGeneration.get();\n    logger.info(\"Getting all instance registry info from the eureka server\");\n    Applications apps = null;\n    EurekaHttpResponse<Applications> httpResponse =\n        clientConfig.getRegistryRefreshSingleVipAddress() == null\n        ? eurekaTransport.queryClient.getApplications(remoteRegionsRef.get())\n        :\n    eurekaTransport.queryClient.getVip(clientConfig.getRegistryRefreshSingleVipAddre\n                                       ss(), remoteRegionsRef.get());\n    if (httpResponse.getStatusCode() == Status.OK.getStatusCode()) {\n        apps = httpResponse.getEntity();\n    }\n    logger.info(\"The response status is {}\", httpResponse.getStatusCode());\n    if (apps == null) {\n        logger.error(\"The application is null for some reason. Not storing this information\");\n    } else if (fetchRegistryGeneration.compareAndSet(currentUpdateGeneration,\n                                                     currentUpdateGeneration + 1)) {\n        localRegionApps.set(this.filterAndShuffle(apps));\n        logger.debug(\"Got full registry with apps hashcode {}\",\n                     apps.getAppsHashCode());\n    } else {\n        logger.warn(\"Not updating applications as another thread is updating it already\");\n    }\n}\n```\n\n\n\n\n\n\n\n\n\n**2.6.8 ˲ѯַ**\nǰ֪ͻ˷ַĲѯ֣һȫһȫѯ󣬻Eureka-serverApplicationsResourcegetContainers\n\n󣬻\nApplicationsResource.getContainerDifferential\n\n**2.6.9 ApplicationsResource.getContainers**\nտͻ˷͵ĻȡȫעϢ\n\n\n\n```\n@GET\npublic Response getContainers(@PathParam(\"version\") String version,\n                              @HeaderParam(HEADER_ACCEPT) String acceptHeader,\n                              @HeaderParam(HEADER_ACCEPT_ENCODING) String\n                              acceptEncoding,\n                              @HeaderParam(EurekaAccept.HTTP_X_EUREKA_ACCEPT)\n                              String eurekaAccept,\n                              @Context UriInfo uriInfo,\n                              @Nullable @QueryParam(\"regions\") String\n                              regionsStr) {\n    boolean isRemoteRegionRequested = null != regionsStr &&\n        !regionsStr.isEmpty();\n    String[] regions = null;\n    if (!isRemoteRegionRequested) {\n        EurekaMonitors.GET_ALL.increment();\n    } else {\n        regions = regionsStr.toLowerCase().split(\",\");\n        Arrays.sort(regions); // So we don't have different caches for same regions queried in different order.\n        EurekaMonitors.GET_ALL_WITH_REMOTE_REGIONS.increment();\n    }\n    // EurekaServer޷ṩ񣬷403\n    if (!registry.shouldAllowAccess(isRemoteRegionRequested)) {\n        return Response.status(Status.FORBIDDEN).build();\n    }\n    CurrentRequestVersion.set(Version.toEnum(version));\n    KeyType keyType = Key.KeyType.JSON;// ÷ݸʽĬJSON\n    String returnMediaType = MediaType.APPLICATION_JSON;\n    if (acceptHeader == null || !acceptHeader.contains(HEADER_JSON_VALUE)) {\n        // յͷûоʽϢ򷵻ظʽΪXML\n        keyType = Key.KeyType.XML;\n        returnMediaType = MediaType.APPLICATION_XML;\n    }\n    // \n    Key cacheKey = new Key(Key.EntityType.Application,\n                           ResponseCacheImpl.ALL_APPS,\n                           keyType, CurrentRequestVersion.get(),\n                           EurekaAccept.fromString(eurekaAccept), regions\n                          );\n    // زͬı͵ݣȥȡݵķһ\n    Response response;\n    if (acceptEncoding != null && acceptEncoding.contains(HEADER_GZIP_VALUE)) {\n        response = Response.ok(responseCache.getGZIP(cacheKey))\n            .header(HEADER_CONTENT_ENCODING, HEADER_GZIP_VALUE)\n            .header(HEADER_CONTENT_TYPE, returnMediaType)\n            .build();\n    } else {\n        response = Response.ok(responseCache.get(cacheKey))\n            .build();\n    }\n    CurrentRequestVersion.remove();\n    return response;\n}\n```\n\n\n\n\n\n\n\n\n\n**2.6.10 responseCache.getGZIP**\nӻжȡݡ\n\n\n\n```\npublic byte[] getGZIP(Key key) {\n    Value payload = getValue(key, shouldUseReadOnlyResponseCache);\n    if (payload == null) {\n        return null;\n    }\n    return payload.getGzipped();\n}\nValue getValue(final Key key, boolean useReadOnlyCache) {\n    Value payload = null;\n    try {\n        if (useReadOnlyCache) {\n            final Value currentPayload = readOnlyCacheMap.get(key);\n            if (currentPayload != null) {\n                payload = currentPayload;\n            } else {\n                payload = readWriteCacheMap.get(key);\n                readOnlyCacheMap.put(key, payload);\n            }\n        } else {\n            payload = readWriteCacheMap.get(key);\n        }\n    } catch (Throwable t) {\n        logger.error(\"Cannot get value for key : {}\", key, t);\n    }\n    return payload;\n}\n```\n\n# ο\nhttps://lijunyi.xyz/docs/SpringCloud/SpringCloud.html#_2-2-x-%E5%88%86%E6%94%AF\nhttps://mp.weixin.qq.com/s/2jeovmj77O9Ux96v3A0NtA\nhttps://juejin.cn/post/6931922457741770760\nhttps://github.com/D2C-Cai/herring\nhttp://c.biancheng.net/springcloud\nhttps://github.com/macrozheng/springcloud-learning"
  },
  {
    "path": "docs/Spring全家桶/SpringCloud源码分析/SpringCloudGateway源码分析.md",
    "content": "**ѧϰĿ**\n\n1.  Gatewayԭ\n    **1 Bean׼**\n    ǰҲôˣǼ\n    spring-cloud-starter-gateway֣һstarter˵ȥspring.factoriesļһЩҪbeanԶװIoCˡ\n\n![SpringCloudϵСSpring Cloud Դ֮Gateway-Դ](https://dl-harmonyos.51cto.com/images/202207/788f3c1494307a2ad7d935811c9e62bab2c435.jpg \"SpringCloudϵСSpring Cloud Դ֮Gateway-Դ\")1.\nGatewayClassPathWarningAutoConfiguration\n\n\n\n```\n@Configuration(proxyBeanMethods = false)\n//ǰGatewayAutoConfiguration֮ǰ\n@AutoConfigureBefore(GatewayAutoConfiguration.class)\npublic class GatewayClassPathWarningAutoConfiguration {\n\t...      \n\t@Configuration(proxyBeanMethods = false)\n\t@ConditionalOnClass(name = \"org.springframework.web.servlet.DispatcherServlet\")\n\tprotected static class SpringMvcFoundOnClasspathConfiguration {\n\t\tpublic SpringMvcFoundOnClasspathConfiguration() {\n\t\t\tlog.warn(BORDER\n\t\t\t\t\t+ \"Spring MVC found on classpath, which is incompatible with Spring Cloud Gateway at this time. \"\n\t\t\t\t\t+ \"Please remove spring-boot-starter-web dependency.\" + BORDER);\n\t\t}\n\n\t}\n\t@Configuration(proxyBeanMethods = false)\n\t@ConditionalOnMissingClass(\"org.springframework.web.reactive.DispatcherHandler\")\n\tprotected static class WebfluxMissingFromClasspathConfiguration {\n\n\t\tpublic WebfluxMissingFromClasspathConfiguration() {\n\t\t\tlog.warn(BORDER + \"Spring Webflux is missing from the classpath, \"\n\t\t\t\t\t+ \"which is required for Spring Cloud Gateway at this time. \"\n\t\t\t\t\t+ \"Please add spring-boot-starter-webflux dependency.\" + BORDER);\n\t\t}\n\t}\n}\n```\n\n\n\n\n\n\n\n\n\nܿʵϾͨConditionOnClassConditionOnMissingClass־ӡĹܣClassPath\norg.springframework.web.servlet.DispatcherServletĻʵһBeanȻӡ־spring-boot-starter-webȻټClassPathǷȷwebfluxûУӡ־spring-boot-starter-webflux\n\n2.GatewayAutoConfiguration\n\nΪ̫ͲչʾˣоټȽҪ\n\n*   PropertiesRouteDefinitionLocatorڴļyml/propertiesжȡ·Ϣ\n*   RouteDefinitionLocator RouteDefinition תΪ Route\n*   RoutePredicateHandlerMapping mvc HandlerMapping GatewayʵֵġƥӦroute\n*   GatewayPropertiesymlϢװ GatewayProperties \n*   AfterRoutePredicateFactory·ɶԹЩԹʱѾɶӦbeanǲſ yml һ£Ч\n*   RetryGatewayFilterFactory Gateway ЩʱѾɶӦbeanǲſ yml һ£Ч\n*   GlobalFilterʵࣺȫֹ\n\n3.HttpHandlerAutoConfigurationWebFluxAutoConfiguration࣬GatewayAutoConfiguration֮ʵֱʵHttpHandlerWebFluxConfigBean\n\n**2 ִ**\nһнHystrixԭHystrixкҵ߼ͨӦʽɵģʵϣGatewayҲǻͬı̷ͬģGatewayͬSpringMVCҲǳơ\n\nǰʱ򣬴£\n\n1.  ȱDispatcherHandlerأȻURIн\n2.  ȻURIȥHandlerMappingȡҪִеWebHandler\n3.  ȻѡһʵHandlerAdapterִ\n4.  ִWebHandler\n    gatewayʱе󶼻뵽DispatcherHandlerеhandleһ𿴿\n\n\n\n```\n@Override\npublic Mono<Void> handle(ServerWebExchange exchange) {\n    if (this.handlerMappings == null) {\n        return createNotFoundError();\n    }\n    //webFluxӦʽ\n    return Flux\n        // 1.Ǳе handlerMapping\n        .fromIterable(this.handlerMappings)\n        // 2.ȡӦhandlerMapping 糣õ RequestMappingHandlerMappingRoutePredicateHandlerMapping\n        .concatMap(mapping -> mapping.getHandler(exchange))\n        .next()\n        .switchIfEmpty(createNotFoundError())\n        // 3.ȡӦöӦĴ\n        .flatMap(handler -> invokeHandler(exchange, handler))\n        // 4.ش\n        .flatMap(result -> handleResult(exchange, result));\n}\n```\n\n\n\n\n\n\n\n\n\n**2.1 getHandler**\ngetHandlerGatewayĺ߼ڣgetHandlerлȡӦHandlerMapping\n\n\nAbstractHandlerMapping.getHandlerԴ\n\n\n\n```\n@Override\npublic Mono<Object> getHandler(ServerWebExchange exchange) {\n    //һȡ·ɵʵ࣬뵽RoutePredicateHandlerMapping\n    return getHandlerInternal(exchange).map(handler -> {\n        if (logger.isDebugEnabled()) {\n            logger.debug(exchange.getLogPrefix() + \"Mapped to \" + handler);\n        }\n        ServerHttpRequest request = exchange.getRequest();\n        if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {\n            CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(exchange) : null);\n            CorsConfiguration handlerConfig = getCorsConfiguration(handler, exchange);\n            config = (config != null ? config.combine(handlerConfig) : handlerConfig);\n            if (!this.corsProcessor.process(config, exchange) || CorsUtils.isPreFlightRequest(request)) {\n                return REQUEST_HANDLED_HANDLER;\n            }\n        }\n        return handler;\n    });\n}\n```\n\n\n\n\n\n\n\n\n\n\n\n```\n@Override\nprotected Mono<?> getHandlerInternal(ServerWebExchange exchange) {\n    // don't handle requests on management port if set and different than server port\n    if (this.managementPortType == DIFFERENT && this.managementPort != null\n        && exchange.getRequest().getURI().getPort() == this.managementPort) {\n        return Mono.empty();\n    }\n    exchange.getAttributes().put(GATEWAY_HANDLER_MAPPER_ATTR, getSimpleName());\n    //ѰҲƥ·\n    return lookupRoute(exchange)\n        // .log(\"route-predicate-handler-mapping\", Level.FINER) //name this\n        .flatMap((Function<Route, Mono<?>>) r -> {\n            //Ƴоɵ\n            exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR);\n            if (logger.isDebugEnabled()) {\n                logger.debug(\n                    \"Mapping [\" + getExchangeDesc(exchange) + \"] to \" + r);\n            }\n            //Ѹ·İ󶨣ؾ\n            exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, r);\n            // webHandler\n            return Mono.just(webHandler);\n        }).switchIfEmpty(Mono.empty().then(Mono.fromRunnable(() -> {\n        exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR);\n        if (logger.isTraceEnabled()) {\n            logger.trace(\"No RouteDefinition found for [\"\n                         + getExchangeDesc(exchange) + \"]\");\n        }\n    })));\n}\n```\n\n\n\n\n\n\n\n\n\nlookupRouteҵymlõе·ɶԹBeforeAfterPathȵȣִapply·ƥ䣬жǷִͨ˳springbootԶʱԼƶ\n\n\n\n```\nprotected Mono<Route> lookupRoute(ServerWebExchange exchange) {\n    // getRoutes ȡеĶԹ\n    return this.routeLocator.getRoutes()\n        .concatMap(route -> Mono.just(route).filterWhen(r -> {\n            exchange.getAttributes().put(GATEWAY_PREDICATE_ROUTE_ATTR, r.getId());\n            // ȻȡRouteڲpredicate\n            //Ȼapply ִжԣжǷͨ\n            return r.getPredicate().apply(exchange);\n        }).doOnError(e -> logger.error(\n                       \"Error applying predicate for route: \" + route.getId(),\n                       e))\n                   .onErrorResume(e -> Mono.empty()))\n        .next()\n        .map(route -> {\n            if (logger.isDebugEnabled()) {\n                logger.debug(\"Route matched: \" + route.getId());\n            }\n            validateRoute(route, exchange);\n            return route;\n        });\n}\n```\n\n\n\n\n\n\n\n\n\ngetRoutes()ͨ\nRouteDefinitionRouteLocatorļлȡ·ɵģȻҵ·תRoute\n\n\n\n```\n@Override\npublic Flux<Route> getRoutes() {\n    // getRouteDefinitions() ļлȡ·\n    Flux<Route> routes = this.routeDefinitionLocator.getRouteDefinitions()\n        // convertToRoute()ҵ·תRoute\n        .map(this::convertToRoute);\n    ...\n}\n```\n\n\n\n\n\n\n\n\n\n\n\n```\npublic class Route implements Ordered {\n\tprivate final String id;\n\tprivate final URI uri;\n\tprivate final int order;\n\tprivate final AsyncPredicate<ServerWebExchange> predicate;\n\tprivate final List<GatewayFilter> gatewayFilters;\n\tprivate final Map<String, Object> metadata;\t\n    ...\n}\n```\n\n\n\n\n\n\n\n\n\n**2.2 invokeHandler**\nGatewayһƥ·ɺ󷵻صwebHandler͵ģҲҪҵӦHandlerAdaptorȡӦ invokeHandler(exchange, handler)\n\n\n\n```\nprivate Mono<HandlerResult> invokeHandler(ServerWebExchange exchange, Object handler) {\n    if (this.handlerAdapters != null) {\n        //ҵеHandlerAdapterȥƥWebFlux\n        for (HandlerAdapter handlerAdapter : this.handlerAdapters) {\n            if (handlerAdapter.supports(handler)) {\n                return handlerAdapter.handle(exchange, handler);\n            }\n        }\n    }\n    return Mono.error(new IllegalStateException(\"No HandlerAdapter: \" + handler));\n}\n```\n\n\n\n\n\n\n\n\n\nSimpleHandlerAdapter еhandle\n\n\n\n```\n@Override\npublic Mono<HandlerResult> handle(ServerWebExchange exchange, Object handler) {\n    //WebHandler \n    WebHandler webHandler = (WebHandler) handler;\n    Mono<Void> mono = webHandler.handle(exchange);\n    return mono.then(Mono.empty());\n}\n```\n\n\n\n\n\n\n\n\n\nwebHandler.handleǴйķùglobalFiltersgatewayFilters\n\n\n\n```\n@Override\npublic Mono<Void> handle(ServerWebExchange exchange) {\n    // 1\\. ·İ󶨹ϵȡӦ·Route\n    Route route = exchange.getRequiredAttribute(GATEWAY_ROUTE_ATTR);\n    List<GatewayFilter> gatewayFilters = route.getFilters();\n    // 2\\. ռе globalFilters List<GatewayFilter>\n    //עʹģʽ\n    List<GatewayFilter> combined = new ArrayList<>(this.globalFilters);\n    // 3\\.  gatewayFilters ҲList<GatewayFilter>γһ\n    combined.addAll(gatewayFilters);\n    // 4\\. order\n    AnnotationAwareOrderComparator.sort(combined);\n    if (logger.isDebugEnabled()) {\n        logger.debug(\"Sorted gatewayFilterFactories: \" + combined);\n    }\n    // 5\\. ִйеÿһ\n    return new DefaultGatewayFilterChain(combined).filter(exchange);\n}\n```\n\n\n\n\n\n\n\n\n\nע⣺װʱǰglobalFiltersgatewayFiltersֹŽList<GatewayFilter>Уôأ\n\nʵõһ  ģʽ\n\n*   globalFiltersȰglobalFiltersתGatewayFilterAdapter GatewayFilterAdapterڲGlobalFilterͬʱҲʵGatewayFilterʹ globalFiltersgatewayFilters  GatewayFilterAdapterй棡\n*   gatewayFiltersֱӷ뼴ɣ\n    **3 ؾ**\n    GatewayĸؾֻҪyml uri: lb://userʵָؾ⣬ײȫֹLoadBalancerClientFilterfilterȥģ\n\nԶ\nhttp://localhost:9527/get/3Ϊ9527ΪGatewayĶ˿\n\n\n\n```\npublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {\n    // 1\\. ·İ󶨹ϵ\n    // ȡԭʼurlhttp://localhost:9527/get/3\n    URI url = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);\n    String schemePrefix = exchange.getAttribute(GATEWAY_SCHEME_PREFIX_ATTR);\n    if (url == null\n        || (!\"lb\".equals(url.getScheme()) && !\"lb\".equals(schemePrefix))) {\n        return chain.filter(exchange);\n    }\n    addOriginalRequestUrl(exchange, url);\n    if (log.isTraceEnabled()) {\n        log.trace(\"LoadBalancerClientFilter url before: \" + url);\n    }\n    // 2\\. ͨribbonĸؾ㷨ݷȥnacosEurekaѡһʵ\n    // ʵuser url ַhttp://localhost:8080/get/3\n    final ServiceInstance instance = choose(exchange);\n    if (instance == null) {\n        throw NotFoundException.create(properties.isUse404(),\n                                       \"Unable to find instance for \" + url.getHost());\n    }\n    // 3\\. õԭ uri http://localhost:9527/get/3\n    URI uri = exchange.getRequest().getURI();\n    String overrideScheme = instance.isSecure() ? \"https\" : \"http\";\n    if (schemePrefix != null) {\n        overrideScheme = url.getScheme();\n    }\n    // 4\\. ÷ʵinstanceuri滻ԭuriַ õ µurl\n    // µurl: http://localhost:8080/get/3\n    URI requestUrl = loadBalancer.reconstructURI(\n        new DelegatingServiceInstance(instance, overrideScheme), uri);\n    if (log.isTraceEnabled()) {\n        log.trace(\"LoadBalancerClientFilter url chosen: \" + requestUrl);\n    }\n    // 5\\. ٴμ¼Ĺϵ\n    exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);\n    // 6\\. ִйе\n    return chain.filter(exchange);\n}\n```\n\n# ο\nhttps://lijunyi.xyz/docs/SpringCloud/SpringCloud.html#_2-2-x-%E5%88%86%E6%94%AF\nhttps://mp.weixin.qq.com/s/2jeovmj77O9Ux96v3A0NtA\nhttps://juejin.cn/post/6931922457741770760\nhttps://github.com/D2C-Cai/herring\nhttp://c.biancheng.net/springcloud\nhttps://github.com/macrozheng/springcloud-learning"
  },
  {
    "path": "docs/Spring全家桶/SpringCloud源码分析/SpringCloudHystrix源码分析.md",
    "content": "ѧϰĿ\n\n1. дMiniHystrix\n2. RxJava֪ʶ\n3. Hystrixĺ̷\n4. Դ֤\n   1 дMini\n   ѾҽܹHystrixĺĹܺʹˣ޷Ǿṩ۶ϡȹܣ۶Ϻ͸Ŀģǽʹùʵĵע⣺@EnableHystrix@HystrixCommand@HystrixCollapserͨע @HystrixCommand߼̳ HystrixCommand ʵֽԼһЩϲȲ\n\nʽԭ֮ǰҪȷһ㣬 @HystrixCommand עʵַ񽵼Hystrix ڲǲAOPķʽشģݣҲϸʵһ¼װ Hystrix һ£ҪΪ²\n\n- Լ@HystrixCommand ע⡣\n- ʵĴ߼\n- Եá\n  1.Զע\n\n\n\n    @Target({ElementType.METHOD})\n    @Retention(RetentionPolicy.RUNTIME)\n    @Documented\n    public @interface MyHystrixCommand {\n        //Ĭϳʱʱ\n        int timeout() default 1000;\n        //˷\n        String fallback() default \"\";\n    }\n\n\n\n\n\n\n\n\n\n2.Զ\n\n\n\n    @Aspect  //Aspectֲ֧ұΪһ\n    @Component\n    public class MyHystrixCommandAspect {\n        ExecutorService executorService= Executors.newFixedThreadPool(10);\n    \n        //е\n        @Pointcut(value = \"@annotation(MyHystrixCommand)\")\n        public void pointCut(){\n    \n        }\n        //е㷽⻷ִ  @Around൱@Before@AfterReturningܵܺ\n        @Around(value = \"pointCut()&&@annotation(hystrixCommand)\")\n        public Object doPointCut(ProceedingJoinPoint joinPoint, MyHystrixCommand hystrixCommand) throws Exception {\n            int timeout=hystrixCommand.timeout();\n            Future future=executorService.submit(()->{\n                try {\n                    //ִproceedĿ귽ִ\n                    return joinPoint.proceed();\n                } catch (Throwable throwable) {\n                    throwable.printStackTrace();\n                }\n                return null;\n            });\n            Object rs;\n            try {\n                //ͨget첽ȴʵֳʱ\n                rs=future.get(timeout, TimeUnit.MILLISECONDS);\n            } catch (InterruptedException | ExecutionException | TimeoutException e) {\n                future.cancel(true);\n                if(StringUtils.isBlank(hystrixCommand.fallback())){\n                    throw new Exception(\"fallback is null\");\n                }\n                //fallback\n                rs=invokeFallback(joinPoint,hystrixCommand.fallback());\n            }\n            return rs;\n        }\n        private Object invokeFallback(ProceedingJoinPoint joinPoint,String fallback) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {\n            //ȡķMethod\n            MethodSignature signature=(MethodSignature)joinPoint.getSignature(); //ȡͱϢ\n            Method method=signature.getMethod();\n            Class<?>[] parameterTypes=method.getParameterTypes();\n            //õص\n            try {\n                Method fallbackMethod=joinPoint.getTarget().getClass().getMethod(fallback,parameterTypes);\n                method.setAccessible(true);\n                //ͨص\n                return fallbackMethod.invoke(joinPoint.getTarget(),joinPoint.getArgs());\n            } catch (Exception e) {\n                throw e;\n            }\n        }\n    }\n\n\n\n\n\n\n\n\n\n3.Զ\n\n\n\n    @RestController\n    public class MyHystrixController {\n        @Autowired\n        OrderServiceClient orderServiceClient;\n        @MyHystrixCommand(fallback = \"fallback\",timeout = 2000)\n        @GetMapping(\"/myhystrix/get/{num}\")\n        public String get(@PathVariable(\"num\") int num){\n            return orderServiceClient.orderLists(num);\n        }\n        public String fallback(int num){\n            return \"Զעⷽ\";\n        }\n    }\n\n\n\n\n\n\n\n\n\nhttp://localhost:8080/myhystrix/get/1ʱᴥΪڷˣnum=1ʱ3s\n\nOKǾʵһװHystrixCommandֻʵHystrixĵһһע棬ĵײ߼ԶԶûô򵥣ڽԴ֮ǰһRxJavaʲôΪHystrixײ߼ǻӦʽʵֵġ\n\n2 RxJava\n2.1 RxJava\nRxJava һӦʽ̣¼첽⡣¼ʽá߼ࡣ\n\nRxJava۲ģʽĶԱ\n\n- ͳ۲һ۲߶۲ߣ۲߷ıʱʱ֪ͨй۲\n- RxJavaһ۲߶۲ߣ۲һڱ۲֮䳯һ򴫵ݣֱݸ۲ \n  ʵ˵ˣRxJavaд2ָһǱ۲ߣһǹ۲ߣ۲߶ͬһ۲ߵʱôű۲ĳ¼ʱͻȥص۲ߡ\n\n2.2 ۲\n\nObserver\n\n\n\n    Observer observer = new Observer() {\n        @Override\n        public void onCompleted() {\n            System.out.println(\"۲Complete¼ø÷\");\n        }\n        @Override\n        public void onError(Throwable throwable) {\n            System.out.println(\"Error¼Ӧ\");\n        }\n        @Override\n        public void onNext(Object o) {\n            System.out.println(\"Next¼Ӧ:\" + o);\n        }\n    };\n\n\n\n\n\n\n\n\n\n\n\n    //Subscriber = RxJava õһʵ Observer ĳ࣬ Observer ӿڽչ\n    Subscriber subscriber = new Subscriber() {\n        @Override\n        public void onCompleted() {\n            System.out.println(\"۲Complete¼ø÷\");\n        }\n        @Override\n        public void onError(Throwable throwable) {\n            System.out.println(\"Error¼Ӧ\");\n        }\n        @Override\n        public void onNext(Object o) {\n            System.out.println(\"Next¼Ӧ:\" + o);\n        }\n    };\n\n\n\n\n\n\n\n\n\nSubscriber Observer ӿڵ\n\n߻ʹ÷ʽһ£RxJavasubscribeУObserverȱתSubscriberʹã\nSubscriber Observer ӿڽչ\n\n- onStart()ڻδӦ¼ǰãһЩʼsubscribe ڵ̵߳ãл̣߳ԲܽнUI±絯Щ\n- unsubscribe()ȡġڸ÷ú󣬹۲߽ٽӦ¼onStopпԵô˷ġø÷ǰʹ isUnsubscribed() ж״̬ȷ۲ObservableǷ񻹳й۲Subscriberá\n  2.3 ۲\n  RxJava ṩ˶ַ ۲߶Observable\n\n\n\n    // 1just(T...)ֱӽĲηͳ\n    Observable observable = Observable.just(\"A\", \"B\", \"C\");\n    // εã\n    // onNext(\"A\");\n    // onNext(\"B\");\n    // onNext(\"C\");\n    // onCompleted();\n    // 2fromArray(T[]) / from(Iterable<? extends T>) :  / Iterable ֳɾηͳ\n    String[] words = {\"A\", \"B\", \"C\"};\n    Observable observable = Observable.fromArray(words);\n    // εã\n    // onNext(\"A\");\n    // onNext(\"B\");\n    // onNext(\"C\");\n    // onCompleted();\n\n\n\n\n\n\n\n\n\n2.4 \n\n\n\n    observable.subscribe(observer); //Ĺϵ\n\n\n\n\n\n\n\n\n\n2.5 \n\n\n\n    public class RxJavaDemo {\n        // ReactiveX Java  Ӧʽ̿(android\n        // Java stream() java8\n        //۲ģʽ\n        public static void main(String[] args) throws ExecutionException, InterruptedException {\n            final String[] datas = new String[]{\"¼1\"};\n            // ִĻص ֹ\n            //Observableǰصcall쳣ֹ\n            final Action0 onComplated = new Action0() {\n                @Override\n                public void call() {\n                    System.out.println(\"۲Ҫ\");\n                }\n            };\n            //۲\n            Observable<String> observable = Observable.defer(new Func0<Observable<String>>() {\n                @Override\n                public Observable<String> call() {\n                    Observable observable1 = Observable.from(datas);\n                    return observable1.doOnCompleted(onComplated);\n                }\n            });\n    //        Observable<String> observable = Observable.just(\"¼1\",\"¼2\",\"\");\n            //۲\n            Observer observer = new Observer() {\n                @Override\n                public void onCompleted() {\n                    System.out.println(\"Comlate¼Ӧ\");\n                }\n                @Override\n                public void onError(Throwable throwable) {\n                    System.out.println(\"Error¼Ӧ\");\n                }\n                @Override\n                public void onNext(Object o) {\n                    System.out.println(\"Next¼Ӧ:\" + o);\n                }\n            };\n            observable.subscribe(observer); //Ĺϵ\n    \n    //        String s = observable.toBlocking().toFuture().get();//첽ȴ\n    //        System.out.println(s);\n        }\n    }\n\n\n\n\n\n\n\n\n\nOKָʹRxJavaˣǿʼߣԴ롣\n\n3 Դ\nϹṩԴͼͼϿԿʵȥɨHystrixCommandעķȻأִ߼涨executequeueѡһеãȻ߼HystrixCommandע⣬Hystrix@EnableHystrixע⡣\n\n\n\n    @SpringBootApplication\n    @EnableFeignClients(\"com.example.clients\")\n    //@EnableDiscoveryClient //עʾUserע\n    @EnableHystrix //עⷽʽHystrix\n    public class HystrixEclipseUserApplication {\n    \n        public static void main(String[] args) {\n            SpringApplication.run(HystrixEclipseUserApplication.class, args);\n        }\n    \n    }\n\n\n\n\n\n\n\n\n\n뵽@EnableHystrixע\n\n\n\n    @Target({ElementType.TYPE})\n    @Retention(RetentionPolicy.RUNTIME)\n    @Documented\n    @Inherited\n    @EnableCircuitBreaker\n    public @interface EnableHystrix {\n    }\n    //@EnableHystrix̳@EnableCircuitBreaker\n    @Target(ElementType.TYPE)\n    @Retention(RetentionPolicy.RUNTIME)\n    @Documented\n    @Inherited\n    @Import(EnableCircuitBreakerImportSelector.class)\n    public @interface EnableCircuitBreaker {\n    }\n\n\n\n\n\n\n\n\n\nⲽ룬źܶѧspringbootͬѧϤˣõImportע⣬ǿ϶һЩˣȻٽ\nEnableCircuitBreakerImportSelector;\n\n\n\n    @Order(Ordered.LOWEST_PRECEDENCE - 100)\n    public class EnableCircuitBreakerImportSelector\n    \t\textends SpringFactoryImportSelector<EnableCircuitBreaker> {\n    \t@Override\n    \tprotected boolean isEnabled() {\n    \t\treturn getEnvironment().getProperty(\"spring.cloud.circuit.breaker.enabled\",\n    \t\t\t\tBoolean.class, Boolean.TRUE);\n    \t}\n    }\n\n\n\n\n\n\n\n\n\nEnableCircuitBreakerImportSelector̳SpringFactoryImportSelectorSpringFactoryImportSelectorϤĴ룬ʵDeferredImportSelectorӿڣʵselectImportsselectImportsļspring.factoriesضӦ org.springframework.cloud.client.circuitbreaker.EnableCircuitBreakerspring.facotriesļ\n\n\n\n    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\\\n    org.springframework.cloud.netflix.hystrix.HystrixAutoConfiguration,\\\n    org.springframework.cloud.netflix.hystrix.HystrixCircuitBreakerAutoConfiguration,\\\n    org.springframework.cloud.netflix.hystrix.ReactiveHystrixCircuitBreakerAutoConfiguration,\\\n    org.springframework.cloud.netflix.hystrix.security.HystrixSecurityAutoConfiguration\n    org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker=\\\n    org.springframework.cloud.netflix.hystrix.HystrixCircuitBreakerConfiguration\n\n\n\n\n\n\n\n\n\nӦEnableAutoConfigurationЩʵspringʱͨԶװƻȥʵע뵽IoCУǺĹע\nHystrixCircuitBreakerConfigurationࡣ\n\n\n\n    @Configuration(proxyBeanMethods = false)\n    public class HystrixCircuitBreakerConfiguration {\n        //Ǻĵbean\n    \t@Bean\n    \tpublic HystrixCommandAspect hystrixCommandAspect() {\n    \t\treturn new HystrixCommandAspect();\n    \t}\n    \t...\n    }\n\n\n\n\n\n\n\n\n\n뵽л֣ᷢҪעΪ@HystrixCommand@HystrixCollapserִעεķʱᱻִ\nmethodsAnnotatedWithHystrixCommand\n\n3.1 HystrixCommandAspect\n\n\n\n    @Aspect\n    public class HystrixCommandAspect {\n        private static final Map<HystrixPointcutType, MetaHolderFactory> META_HOLDER_FACTORY_MAP;\n        static {\n            //̬ͨעʵ\n            META_HOLDER_FACTORY_MAP = ImmutableMap.<HystrixPointcutType, MetaHolderFactory>builder()\n                .put(HystrixPointcutType.COMMAND, new CommandMetaHolderFactory())\n                .put(HystrixPointcutType.COLLAPSER, new CollapserMetaHolderFactory())\n                .build();\n        }\n        //עHystrixCommand\n        @Pointcut(\"@annotation(com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand)\")\n        public void hystrixCommandAnnotationPointcut() {\n        }\n        //עHystrixCollapserϲ\n        @Pointcut(\"@annotation(com.netflix.hystrix.contrib.javanica.annotation.HystrixCollapser)\")\n        public void hystrixCollapserAnnotationPointcut() {\n        }\n        //֪ͨ\n        @Around(\"hystrixCommandAnnotationPointcut() || hystrixCollapserAnnotationPointcut()\")\n        public Object methodsAnnotatedWithHystrixCommand(final ProceedingJoinPoint joinPoint) throws Throwable {\n            //ȡĿ귽\n            Method method = getMethodFromTarget(joinPoint);\n            Validate.notNull(method, \"failed to get method from joinPoint: %s\", joinPoint);\n            //ֻעעķ\n            if (method.isAnnotationPresent(HystrixCommand.class) && method.isAnnotationPresent(HystrixCollapser.class)) {\n                throw new IllegalStateException(\"method cannot be annotated with HystrixCommand and HystrixCollapser \" +\n                                                \"annotations at the same time\");\n            }\n            //ݲͬע⣬ѡӦmetaHolderFactory, MetaHolder, MetaHolder Ϣ\n            MetaHolderFactory metaHolderFactory = META_HOLDER_FACTORY_MAP.get(HystrixPointcutType.of(method));\n            //ȡĿ귽ĵԪݣǩ\n            MetaHolder metaHolder = metaHolderFactory.create(joinPoint);\n            /**\n             * CommandCollapser  GenericCommand ͬ GenericObservableCommand첽\n             * GenericCommandкܶsuperͨHystrixCommandBuilderFactory.getInstance().create(metaHolder) һHystrixCommandBuilderΪGenericCommadĲ\n             * new  GenericCommand ͨsuperAbstractHystrixCommand\n             * AbstractHystrixCommand ͨsuperHystrixCommand\n             * HystrixCommandյAbstractCommand  һ·\n             * һAbstractCommandз\n             */\n            HystrixInvokable invokable = HystrixCommandFactory.getInstance().create(metaHolder);\n            //ݷֵƶִ\n            ExecutionType executionType = metaHolder.isCollapserAnnotationPresent() ?\n                metaHolder.getCollapserExecutionType() : metaHolder.getExecutionType();\n            //ݲִͬͣؽ\n            Object result;\n            try {\n                //ǷӦʽģЩͬĻ߼\n                if (!metaHolder.isObservable()) {\n                    //executeִ\n                    result = CommandExecutor.execute(invokable, executionType, metaHolder);\n                } else {\n                    result = executeObservable(invokable, executionType, metaHolder);\n                }\n            } catch (HystrixBadRequestException e) {\n                throw e.getCause();\n            } catch (HystrixRuntimeException e) {\n                throw hystrixRuntimeExceptionToThrowable(metaHolder, e);\n            }\n            return result;\n        }\n        //HystrixCommandʱMetaHolderĴ\n        private static class CommandMetaHolderFactory extends MetaHolderFactory {\n            @Override\n            public MetaHolder create(Object proxy, Method method, Object obj, Object[] args, final ProceedingJoinPoint joinPoint) {\n                //ȡעHystrixCommand\n                HystrixCommand hystrixCommand = method.getAnnotation(HystrixCommand.class);\n                //ݷؽƶַ֪ͣʽִ\n                ExecutionType executionType = ExecutionType.getExecutionType(method.getReturnType());\n                MetaHolder.Builder builder = metaHolderBuilder(proxy, method, obj, args, joinPoint);\n                if (isCompileWeaving()) {\n                    builder.ajcMethod(getAjcMethodFromTarget(joinPoint));\n                }\n                //ûжٲҪһhystrixCommandעʲô\n                return builder.defaultCommandKey(method.getName())\n                    .hystrixCommand(hystrixCommand)\n                    .observableExecutionMode(hystrixCommand.observableExecutionMode())  //ִģʽ\n                    .executionType(executionType) //ִзʽ\n                    .observable(ExecutionType.OBSERVABLE == executionType)\n                    .build();\n            }\n        }\n    }\n    //öExecutionType\n    public static ExecutionType getExecutionType(Class<?> type) {\n        if (Future.class.isAssignableFrom(type)) {\n            return ExecutionType.ASYNCHRONOUS;\n        } else if (Observable.class.isAssignableFrom(type)) {\n            return ExecutionType.OBSERVABLE;\n        } else {\n            return ExecutionType.SYNCHRONOUS;\n        }\n    }\n\n\n\n\n\n\n\n\n\nصͬͨǿԿHystrixInvokable  GenericCommandͬĿ CommandExecutor.execute(invokable, executionType, metaHolder)\n\n\n\n    public class CommandExecutor {\n        public static Object execute(HystrixInvokable invokable, ExecutionType executionType, MetaHolder metaHolder) throws RuntimeException {\n            Validate.notNull(invokable);\n            Validate.notNull(metaHolder);\n    \n            switch (executionType) {\n                case SYNCHRONOUS: {\n                    //ص㿴ͬȰGenericCommand תHystrixExecutable ִexecute\n                    return castToExecutable(invokable, executionType).execute();\n                }\n                case ASYNCHRONOUS: {\n                    // ǿתHystrixExecutable  첽ִ\n                    HystrixExecutable executable = castToExecutable(invokable, executionType);\n                    //  fallback첽ִУִвذװ\n                    if (metaHolder.hasFallbackMethodCommand()\n                            && ExecutionType.ASYNCHRONOUS == metaHolder.getFallbackExecutionType()) {\n                        return new FutureDecorator(executable.queue());\n                    }\n                    return executable.queue();\n                }\n                case OBSERVABLE: {\n                    // ǿת HystrixObservable\n                    HystrixObservable observable = castToObservable(invokable);\n                    // жִģʽǲǼ/裬ѡģʽִ\n                    return ObservableExecutionMode.EAGER == metaHolder.getObservableExecutionMode() ? observable.observe() : observable.toObservable();\n                }\n                default:\n                    throw new RuntimeException(\"unsupported execution type: \" + executionType);\n            }\n        }\n    }\n\n\n\n\n\n\n\n\n\nҪִӴпԿֱִͣͬ첽ԼӦʽУӦʽַΪCold Observableobservable.toObservable()  HotObservableobservable.observe()ĬϵexecutionType=SYNCHRONOUS ͬ\n\n- execute()ִͬУһһĶʱ׳쳣\n- queue()첽ִУһ Future 󣬰ִн󷵻صĵһ\n- observe()һ Observable ĶѾѵˡ\n- toObservable()һ Observable ĶҪԼֶĲѵ\n  ͼϵ£\n\nͨGenericCommandһϷնλHystrixCommandиexecute()\n\n\n\n    public abstract class HystrixCommand<R> extends AbstractCommand<R> implements HystrixExecutable<R>, HystrixInvokableInfo<R>, HystrixObservable<R> {\n        //ִͬ\n        public R execute() {\n            try {\n                //ͨqueue().get()ִͬУװ첽Ľ\n                return queue().get();\n            } catch (Exception e) {\n                throw Exceptions.sneakyThrow(decomposeException(e));\n            }\n        }\n       //첽ִУʲôʱget()ɵ߾get()ʱ\n       public Future<R> queue() {\n            //ĴնλAbstractCommandtoObservable()\n            // toObservableתΪObservable,toBlockingתΪBlockingObservable, \n            // toFutureתΪFuture,ObservableĴͶ\n            final Future<R> delegate = toObservable().toBlocking().toFuture();   \t\n            final Future<R> f = new Future<R>() {\n                .....\n                @Override\n                public R get() throws InterruptedException, ExecutionException {\n                    return delegate.get();\n                }\n                @Override\n                public R get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {\n                    return delegate.get(timeout, unit);\n                }       \t\n            };\n            //⴦£Ѿִˣget()Ҳ\n            if (f.isDone()) {\n                try {\n                    f.get();\n                    return f;\n                } catch (Exception e) {\n                    ...\n                }\n            }\n            return f;\n        }\n    }\n\n\n\n\n\n\n\n\n\nУصˣһ\njava.util.concurrent.Future Ȼ getʱίɸ delegate delegate toObservable().toBlocking().toFuture(); ô롣ڵصӦ÷ toObservable() У\n\n3.2 toObservable\nͨObservableһ۲ߣ۲߻ᱻtoObservable().toBlocking().toFuture() ʵдĺĺȥһЩ۶߼жִʵҵ߼ִfallbackĻصȻ󽫽ظFuture run() ִҵ߼Ҫ¼£\n\n- һѵĶҲ֪ЩǸɶģҪ\n- жǷ˻棬ˣҲˣȥObservableʽһ\n- һ۲ߣ۲ߺȥصʵҵ߼fallback\n\n߼۲߻ȥִapplyHystrixSemanticsĶ\n\n\n\n    public Observable<R> toObservable() {\n        final AbstractCommand<R> _cmd = this;\n        // ִĻص ֹ\n        //Observableǰصcall쳣ֹ\n        final Action0 terminateCommandCleanup = new Action0() {\n    \t\t...\n        };\n        // Ϊȡ洢ӳ٣˱׼\n        //ȡʱļлص call\n        final Action0 unsubscribeCommandCleanup = new Action0() {\n            @Override\n            public void call() {\n    \t\t\t...\n            }\n        };\n        // ִʱĻص\n        final Func0<Observable<R>> applyHystrixSemantics = new Func0<Observable<R>>() {\n            @Override\n            public Observable<R> call() {\n                if (commandState.get().equals(CommandState.UNSUBSCRIBED)) {\n                    // ֹ̡\n                    return Observable.never();\n                }\n                //ִObservable\n                return applyHystrixSemantics(_cmd);\n            }\n        };\n        final Func1<R, R> wrapWithAllOnNextHooks = new Func1<R, R>() {\n            @Override\n            public R call(R r) {\n    \t\t\t...\n            }\n        };\n        final Action0 fireOnCompletedHook = new Action0() {\n            @Override\n            public void call() {\n    \t\t\t...\n            }\n        };\n        // Observable,øִ\n        return Observable.defer(new Func0<Observable<R>>() {\n            @Override\n            public Observable<R> call() {\n                // ־, CASִֻ֤һ\n                if (!commandState.compareAndSet(CommandState.NOT_STARTED, CommandState.OBSERVABLE_CHAIN_CREATED)) {\n                    IllegalStateException ex = new IllegalStateException(\"This instance can only be executed once. Please instantiate a new instance.\");\n                    //TODO make a new error type for this\n                    throw new HystrixRuntimeException(FailureType.BAD_REQUEST_EXCEPTION, _cmd.getClass(), getLogMessagePrefix() + \" command executed multiple times - this is not permitted.\", ex, null);\n                }\n                // ʼʱ\n                commandStartTimestamp = System.currentTimeMillis();\n                // ӡ־\n                if (properties.requestLogEnabled().get()) {\n                    // log this command execution regardless of what happened\n                    if (currentRequestLog != null) {\n                        currentRequestLog.addExecutedCommand(_cmd);\n                    }\n                }\n                // 濪أKEYHystrix󻺴湦ܣhystrixֽ֧һ\n                // һͬkeyֱӴӻȡ\n                final boolean requestCacheEnabled = isRequestCachingEnabled();\n                final String cacheKey = getCacheKey();\n                // 棬ͼӻȡĬ false\n                if (requestCacheEnabled) {\n                    HystrixCommandResponseFromCache<R> fromCache = (HystrixCommandResponseFromCache<R>) requestCache.get(cacheKey);\n                    if (fromCache != null) {\n                        isResponseFromCache = true;\n                        return handleRequestCacheHitAndEmitValues(fromCache, _cmd);\n                    }\n                }\n                // ִObservable\n                // Observable, applyHystrixSemantics() Observable\n                Observable<R> hystrixObservable =\n                    Observable.defer(applyHystrixSemantics)\n                    .map(wrapWithAllOnNextHooks);\n                Observable<R> afterCache;\n                // put in cache \n                if (requestCacheEnabled && cacheKey != null) {\n                    // wrap it for caching\n                    HystrixCachedObservable<R> toCache = HystrixCachedObservable.from(hystrixObservable, _cmd);\n                    HystrixCommandResponseFromCache<R> fromCache = (HystrixCommandResponseFromCache<R>) requestCache.putIfAbsent(cacheKey, toCache);\n                    if (fromCache != null) {\n                        // another thread beat us so we'll use the cached value instead\n                        toCache.unsubscribe();\n                        isResponseFromCache = true;\n                        return handleRequestCacheHitAndEmitValues(fromCache, _cmd);\n                    } else {\n                        // we just created an ObservableCommand so we cast and return it\n                        afterCache = toCache.toObservable();\n                    }\n                } else {\n                    afterCache = hystrixObservable;\n                }\n                // ڻص\n                return afterCache\n                    //Observableǰص쳣ֹ\n                    .doOnTerminate(terminateCommandCleanup)     \n                    //ȡʱļ\n                    .doOnUnsubscribe(unsubscribeCommandCleanup) \n                    //Observableֹʱļ\n                    .doOnCompleted(fireOnCompletedHook);\n            }\n        });\n    }\n\n\n\n\n\n\n\n\n\n߼applyHystrixSemantics\n\n\n\n    Observable<R> hystrixObservable =\n        Observable.defer(applyHystrixSemantics)\n        .map(wrapWithAllOnNextHooks);\n\n\n\n\n\n\n\n\n\n\n\n    final Func0<Observable<R>> applyHystrixSemantics = new Func0<Observable<R>>() {\n        @Override\n        public Observable<R> call() {\n            if (commandState.get().equals(CommandState.UNSUBSCRIBED)) {\n                return Observable.never();\n            }\n            return applyHystrixSemantics(_cmd);\n        }\n    };\n\n\n\n\n\n\n\n\n\nﴫ_cmdһGenericCommandջִеGenericCommandеrun\n\ncircuitBreaker.allowRequest() жǷ۶״̬ģtrueʾûд۶״ִ̬У򣬵 handleShortCircuitViaFallback ʵַ񽵼ջصԶfallbackС\n\nǰhystrixδ۶״̬\n\n- getExecutionSemaphore жϵǰǷΪź̳߳أȻĬ̳߳أȻٵtryAcquireʱдΪtrue\n\nexecuteCommandAndObserve\n\n\n\n    private Observable<R> applyHystrixSemantics(final AbstractCommand<R> _cmd) {\n    \n        executionHook.onStart(_cmd);\n    \n        // Ƿ󣬼·Ƿ Ҳкü\n        if (circuitBreaker.allowRequest()) {\n            // źȡ\n            final TryableSemaphore executionSemaphore = getExecutionSemaphore();\n            final AtomicBoolean semaphoreHasBeenReleased = new AtomicBoolean(false);\n    \n            // źͷŻص\n            final Action0 singleSemaphoreRelease = new Action0() {\n                @Override\n                public void call() {\n                    if (semaphoreHasBeenReleased.compareAndSet(false, true)) {\n                        executionSemaphore.release();\n                    }\n                }\n            };\n    \n            // 쳣ص\n            final Action1<Throwable> markExceptionThrown = new Action1<Throwable>() {\n                @Override\n                public void call(Throwable t) {\n                    eventNotifier.markEvent(HystrixEventType.EXCEPTION_THROWN, commandKey);\n                }\n            };\n    \n            // ȡźţضӦ Observable\n            // ǷźԴ룬δ com.netflix.hystrix.AbstractCommand.TryableSemaphoreNoOp#tryAcquire ĬϷͨ\n            if (executionSemaphore.tryAcquire()) {\n                try {\n                    executionResult = executionResult.setInvocationStartTime(System.currentTimeMillis());\n                    return executeCommandAndObserve(_cmd)   // ִǻصԲ\n                        .doOnError(markExceptionThrown)\n                        .doOnTerminate(singleSemaphoreRelease)\n                        .doOnUnsubscribe(singleSemaphoreRelease);\n                } catch (RuntimeException e) {\n                    return Observable.error(e);\n                }\n            } else {\n                // ȡźʧ򽵼\n                return handleSemaphoreRejectionViaFallback();\n            }\n        } else {\n            // ·Ѵ򿪣ֱӽ\n            return handleShortCircuitViaFallback();\n        }\n    }\n\n\n\n\n\n\n\n\n\nһִʧܽ뽵߼ֱӽ뵽 HystrixCommand#getFallbackObservable\n\n\n\n    public abstract class HystrixCommand<R> extends AbstractCommand<R> implements HystrixExecutable<R>, HystrixInvokableInfo<R>, HystrixObservable<R> {\n        @Override\n        final protected Observable<R> getFallbackObservable() {\n            return Observable.defer(new Func0<Observable<R>>() {\n                @Override\n                public Observable<R> call() {\n                    try {\n                        return Observable.just(getFallback());\n                    } catch (Throwable ex) {\n                        return Observable.error(ex);\n                    }\n                }\n            });\n        }\n    }\n\n\n\n\n\n\n\n\n\ngetFallbackջصԶfallback\n\nصexecuteCommandAndObserveҪ\n\n- 岻ͬĻصdoOnNextdoOnCompletedonErrorResumeNextdoOnEach\n- executeCommandWithSpecifiedIsolation\n\nִʱԿ Observable.liftʵִʱܡ\n\n\n\n    private Observable<R> executeCommandAndObserve(final AbstractCommand<R> _cmd) {\n        final HystrixRequestContext currentRequestContext = HystrixRequestContext.getContextForCurrentThread();\n        // ActionFuncǶһAction޷ֵFuncзֵ\n        // doOnNextеĻصִ֮ǰִеĲ\n        final Action1<R> markEmits = new Action1<R>() {\n            @Override\n            public void call(R r) {\n                if (shouldOutputOnNextEvents()) {\n                    executionResult = executionResult.addEvent(HystrixEventType.EMIT);\n                    eventNotifier.markEvent(HystrixEventType.EMIT, commandKey);\n                }\n                if (commandIsScalar()) {\n                    long latency = System.currentTimeMillis() - executionResult.getStartTimestamp();\n                    eventNotifier.markCommandExecution(getCommandKey(), properties.executionIsolationStrategy().get(), (int) latency, executionResult.getOrderedList());\n                    eventNotifier.markEvent(HystrixEventType.SUCCESS, commandKey);\n                    executionResult = executionResult.addEvent((int) latency, HystrixEventType.SUCCESS);\n                    circuitBreaker.markSuccess();\n                }\n            }\n        };\n        // doOnCompletedеĻصִϺִеĲ\n        final Action0 markOnCompleted = new Action0() {\n            @Override\n            public void call() {\n                if (!commandIsScalar()) {\n                    long latency = System.currentTimeMillis() - executionResult.getStartTimestamp();\n                    eventNotifier.markCommandExecution(getCommandKey(), properties.executionIsolationStrategy().get(), (int) latency, executionResult.getOrderedList());\n                    eventNotifier.markEvent(HystrixEventType.SUCCESS, commandKey);\n                    executionResult = executionResult.addEvent((int) latency, HystrixEventType.SUCCESS);\n                    circuitBreaker.markSuccess();\n                }\n            }\n        };\n        // onErrorResumeNextеĻصִʧܺĻ߼\n        final Func1<Throwable, Observable<R>> handleFallback = new Func1<Throwable, Observable<R>>() {\n            @Override\n            public Observable<R> call(Throwable t) {\n                Exception e = getExceptionFromThrowable(t);\n                executionResult = executionResult.setExecutionException(e);\n                if (e instanceof RejectedExecutionException) {\n                    // ̵߳ʧܻص\n                    return handleThreadPoolRejectionViaFallback(e);\n                } else if (t instanceof HystrixTimeoutException) {\n                    // ʱص\n                    return handleTimeoutViaFallback();\n                } else if (t instanceof HystrixBadRequestException) {\n                    // HystrixBadRequestException 쳣ص\n                    return handleBadRequestByEmittingError(e);\n                } else {\n                    if (e instanceof HystrixBadRequestException) {\n                        eventNotifier.markEvent(HystrixEventType.BAD_REQUEST, commandKey);\n                        return Observable.error(e);\n                    }\n                    // \n                    return handleFailureViaFallback(e);\n                }\n            }\n        };\n        // doOnEachеĻص`Observable`ÿһݶִص\n        final Action1<Notification<? super R>> setRequestContext = new Action1<Notification<? super R>>() {\n            @Override\n            public void call(Notification<? super R> rNotification) {\n                setRequestContextIfNeeded(currentRequestContext);\n            }\n        };\n        // Ӧ Observableʵ ̸߳롢 Ȳ\n        Observable<R> execution;\n        // ж ʱعǷ\n        if (properties.executionTimeoutEnabled().get()) {\n            // HystrixObservableTimeoutOperator  תӦ Observable\n            execution = executeCommandWithSpecifiedIsolation(_cmd)\n                .lift(new HystrixObservableTimeoutOperator<R>(_cmd));\n        } else {\n            execution = executeCommandWithSpecifiedIsolation(_cmd);\n        }\n        //ûص\n        return execution.doOnNext(markEmits)\n            .doOnCompleted(markOnCompleted)\n            .onErrorResumeNext(handleFallback)\n            .doOnEach(setRequestContext);\n    }\n\n\n\n\n\n\n\n\n\n3.3 executeCommandWithSpecifiedIsolation\nǸݵǰͬԴִвͬ߼THREADSEMAPHORE\n\n\n\n    private Observable<R> executeCommandWithSpecifiedIsolation(final AbstractCommand<R> _cmd) {\n        // ̸߳, Ƿ THREAD Դ뽵\n        if (properties.executionIsolationStrategy().get() == ExecutionIsolationStrategy.THREAD) {\n            //һObservable\n            return Observable.defer(new Func0<Observable<R>>() {\n                @Override\n                public Observable<R> call() {\n                    executionResult = executionResult.setExecutionOccurred();\n                    if (!commandState.compareAndSet(CommandState.OBSERVABLE_CHAIN_CREATED, CommandState.USER_CODE_EXECUTED)) {\n                        return Observable.error(new IllegalStateException(\"execution attempted while in state : \" + commandState.get().name()));\n                    }\n    \n                    metrics.markCommandStart(commandKey, threadPoolKey, ExecutionIsolationStrategy.THREAD);\n    \n                    // ڰװ߳гʱأҲκμ߼\n                    if (isCommandTimedOut.get() == TimedOutStatus.TIMED_OUT) {\n                        // the command timed out in the wrapping thread so we will return immediately\n                        // and not increment any of the counters below or other such logic\n                        return Observable.error(new RuntimeException(\"timed out before executing run()\"));\n                    }\n    \n                    // ߳\n                    if (threadState.compareAndSet(ThreadState.NOT_USING_THREAD, ThreadState.STARTED)) {\n                        //we have not been unsubscribed, so should proceed\n                        HystrixCounters.incrementGlobalConcurrentThreads();\n                        threadPool.markThreadExecution();\n                        // store the command that is being run\n                        endCurrentThreadExecutingCommand = Hystrix.startCurrentThreadExecutingCommand(getCommandKey());\n                        executionResult = executionResult.setExecutedInThread();\n    \n                        try {\n                            executionHook.onThreadStart(_cmd);\n                            executionHook.onRunStart(_cmd);\n                            executionHook.onExecutionStart(_cmd);\n                            // Observable,ջ᷵һװǵrun()߼Observable\n                            return getUserExecutionObservable(_cmd);\n                        } catch (Throwable ex) {\n                            return Observable.error(ex);\n                        }\n                    } else {\n                        //command has already been unsubscribed, so return immediately\n                        return Observable.error(new RuntimeException(\"unsubscribed before executing run()\"));\n                    }\n                }\n            }).doOnTerminate(new Action0() {\n                @Override\n                public void call() {\n                    if (threadState.compareAndSet(ThreadState.STARTED, ThreadState.TERMINAL)) {\n                        handleThreadEnd(_cmd);\n                    }\n                    if (threadState.compareAndSet(ThreadState.NOT_USING_THREAD, ThreadState.TERMINAL)) {\n                    }\n                }\n            }).doOnUnsubscribe(new Action0() {\n                @Override\n                public void call() {\n                    if (threadState.compareAndSet(ThreadState.STARTED, ThreadState.UNSUBSCRIBED)) {\n                        handleThreadEnd(_cmd);\n                    }\n                    if (threadState.compareAndSet(ThreadState.NOT_USING_THREAD, ThreadState.UNSUBSCRIBED)) {\n                    }\n                }\n            }).subscribeOn(threadPool.getScheduler(new Func0<Boolean>() {\n                @Override\n                public Boolean call() {\n                    return properties.executionIsolationThreadInterruptOnTimeout().get() && _cmd.isCommandTimedOut.get() == TimedOutStatus.TIMED_OUT;\n                }\n            }));\n        } else {\n            // ź\n            return Observable.defer(new Func0<Observable<R>>() {\n                @Override\n                public Observable<R> call() {\n                    executionResult = executionResult.setExecutionOccurred();\n                    if (!commandState.compareAndSet(CommandState.OBSERVABLE_CHAIN_CREATED, CommandState.USER_CODE_EXECUTED)) {\n                        return Observable.error(new IllegalStateException(\"execution attempted while in state : \" + commandState.get().name()));\n                    }\n                    metrics.markCommandStart(commandKey, threadPoolKey, ExecutionIsolationStrategy.SEMAPHORE);\n                    endCurrentThreadExecutingCommand = Hystrix.startCurrentThreadExecutingCommand(getCommandKey());\n                    try {\n                        executionHook.onRunStart(_cmd);\n                        executionHook.onExecutionStart(_cmd);\n                        // ִ\n                        return getUserExecutionObservable(_cmd); \n                    } catch (Throwable ex) {\n                        //If the above hooks throw, then use that as the result of the run method\n                        return Observable.error(ex);\n                    }\n                }\n            });\n        }\n    }\n\n\n\n\n\n\n\n\n\n- жǷǻڶ·ʵ֣·򿪣жӦصʧܻ򽵼\n-  · رգȻȡźţȡʧӦص\n- ȡɹɷ executeCommandAndObserve Ӧ Observable ʵ ̸߳롢 Ȳͬʱע˶Ӧ ڻص\n  3.4 getUserExecutionObservable\n  Ȼִ HystrixCommand#getExecutionObservable\n\n\n\n    abstract class AbstractCommand<R> implements HystrixInvokableInfo<R>, HystrixObservable<R> {\n        private Observable<R> getUserExecutionObservable(final AbstractCommand<R> _cmd) {\n            Observable<R> userObservable;\n            try {\n                userObservable = getExecutionObservable();\n            } catch (Throwable ex) {\n                userObservable = Observable.error(ex);\n            }\n            return userObservable\n                    .lift(new ExecutionHookApplication(_cmd))\n                    .lift(new DeprecatedOnRunHookApplication(_cmd));\n        }\n    }\n    public abstract class HystrixCommand<R> extends AbstractCommand<R> implements HystrixExecutable<R>, HystrixInvokableInfo<R>, HystrixObservable<R> {\n        @Override\n        final protected Observable<R> getExecutionObservable() {\n            return Observable.defer(new Func0<Observable<R>>() {\n                @Override\n                public Observable<R> call() {\n                    try {\n                        return Observable.just(run());\n                    } catch (Throwable ex) {\n                        return Observable.error(ex);\n                    }\n                }\n            }).doOnSubscribe(new Action0() {\n                @Override\n                public void call() {\n                    // Save thread on which we get subscribed so that we can interrupt it later if needed\n                    executionThread.set(Thread.currentThread());\n                }\n            });\n        }\n    }\n\n\n\n\n\n\n\n\n\n run() Ѿˣҵִз\n\n\n\n    @ThreadSafe\n    public class GenericCommand extends AbstractHystrixCommand<Object> {\n        @Override\n        protected Object run() throws Exception {\n            LOGGER.debug(\"execute command: {}\", getCommandKey().name());\n            return process(new Action() {\n                @Override\n                Object execute() {\n                    return getCommandAction().execute(getExecutionType());\n                }\n            });\n        }\n    }\n\n\n\n\n\n\n\n\n\nյõԼҵ߼\n# ο\nhttps://lijunyi.xyz/docs/SpringCloud/SpringCloud.html#_2-2-x-%E5%88%86%E6%94%AF\nhttps://mp.weixin.qq.com/s/2jeovmj77O9Ux96v3A0NtA\nhttps://juejin.cn/post/6931922457741770760\nhttps://github.com/D2C-Cai/herring\nhttp://c.biancheng.net/springcloud\nhttps://github.com/macrozheng/springcloud-learning\n"
  },
  {
    "path": "docs/Spring全家桶/SpringCloud源码分析/SpringCloudLoadBalancer源码分析.md",
    "content": "# Spring Cloud LoadBalancer\n\n## \n\n> Spring Cloud LoadBalancerĿǰSpringٷǷspring-cloud-commonsSpring Cloud°汾Ϊ2021.0.2\n>\n> [Spring Cloud LoadBalancer ĵַ](https://docs.spring.io/spring-cloud-commons/docs/3.1.2/reference/html/#spring-cloud-loadbalancer) [https://docs.spring.io/spring-cloud-commons/docs/3.1.2/reference/html/#spring-cloud-loadbalancer](https://docs.spring.io/spring-cloud-commons/docs/3.1.2/reference/html/#spring-cloud-loadbalancer)\n>\n> [Spring Cloudĵַ](https://docs.spring.io/spring-cloud/docs/current/reference/html/) [https://docs.spring.io/spring-cloud/docs/current/reference/html/](https://docs.spring.io/spring-cloud/docs/current/reference/html/)\n\nһNetflix Ribbonֹͣ£Spring Cloud LoadBalancerSpring CloudٷԼṩĿͻ˸ؾ,ʵ֣Ribbon\n\n*   ؾΪ˸ؾ(ز⸺)Ϳͻ˲⸺ء\n    *   زӲF5LVSnginxȡ\n    *   ͻ˲Spring Cloud LoadBalancerΪһͻȥָάбԶľ⸺زԣѯСĽ˿ȸȵȣ\n\nSpring CloudṩԼĿͻ˸ƽʵ֡ڸؾƣReactiveLoadBalancerӿڣṩ˻round-robinѯRandomʵ֡Ϊ˴ӦʽServiceInstanceListSupplierѡʵҪʹServiceInstanceListSupplierĿǰ֧ServiceInstanceListSupplierĻڷֵʵ֣ʵʹ·еķֿͻ˴Service Discoveryмõʵ\n\nͨSpring Cloud LoadBalance\n\n```\nspring:\n  cloud:\n    loadbalancer:\n      enabled: false\n\n```\n\n## ʾ\n\nǰsimple-ecommerceĿڸPomϸԿǰ<<SpringCloudAlibabaע֮NacosʵսԴ>>Spring Cloudİ汾Ϊ2021.0.1ǰҲ˵Spring Cloud Alibabaspring-cloud-starter-alibaba-nacos-discoveryspring-cloud-loadbalancer\n\n> עHoxton֮ǰİ汾ĬϸؾΪRibbonҪƳRibbonúspring.cloud.loadbalancer.ribbon.enabled: false\n\nSpring BootĿstarterҲSpring Boot Caching and Evictor.\n\n```\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            spring-cloud-starter-loadbalancer\n        </dependency>\n\n```\n\nʹSpringٷṩ˸ؾĿͻ֮һRestTemplateRestTemplateSpringṩڷRestĿͻˣRestTemplateṩ˶ֱݷԶHttpķܹ߿ͻ˵ıдЧʡĬ£RestTemplateĬjdkHTTPӹߡRestTemplateConfig࣬ע @LoadBalancedע⣬ĬʹõReactiveLoadBalancerʵRoundRobinLoadBalancer\n\n```\npackage cn.itxs.ecom.order.config;\n\nimport org.springframework.cloud.client.loadbalancer.LoadBalanced;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.web.client.RestTemplate;\n\n@Configuration\npublic class RestTemplateConfig {\n    @LoadBalanced\n    @Bean\n    public RestTemplate restTemplate() {\n        return new RestTemplate() ;\n    }\n}\n\n```\n\n΢жdeductRest\n\n```\npackage cn.itxs.ecom.order.controller;\n\nimport cn.itxs.ecom.commons.service.OrderService;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.web.bind.annotation.PathVariable;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\nimport org.springframework.web.client.RestTemplate;\n\n/**\n * @Name OrderController\n * @Description \n * @Author itxs\n * @Date 2022/4/10 20:15\n * @Version 1.0\n * @History \n */\n@RestController\npublic class OrderController {\n\n    @Autowired\n    OrderService orderService;\n\n    @Autowired\n    private RestTemplate restTemplate;\n\n    @RequestMapping(\"/create/{userId}/{commodityCode}/{count}\")\n    public String create(@PathVariable(\"userId\") String userId,@PathVariable(\"commodityCode\") String commodityCode, @PathVariable(\"count\") int count){\n        return orderService.create(userId,commodityCode,count).toString();\n    }\n\n    @RequestMapping(\"/deductRest/{commodityCode}/{count}\")\n    public String deductRest(@PathVariable(\"commodityCode\") String commodityCode, @PathVariable(\"count\") int count){\n        String url = \"http://ecom-storage-service/deduct/\"+commodityCode+\"/\"+count;\n        return restTemplate.getForObject(url, String.class);\n    }\n}\n\n```\n\nǰserver.portǷNacosעNacosĵ÷ڱļbootstrap.ymlֱΪ4080408140823ʵ΢\n\n```\nserver:\n  port: 4080\n\n```\n\n![image-20220505191143684](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/938570a0b63b56671a862e8bda11577a.png)\n\n鿴nacos-б飬Կ3Ŀʵ1΢ʵ\n\n![image-20220505182432182](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/33fa03d937354fe00bb0bb2f3dd5c805.png)\n\n6ζdedectӿڣ[http://localhost:4070/deductRest/1001/1](http://localhost:4070/deductRest/1001/1) ӲԵĽҲ֤LoadBalancerĬѯؾԡ\n\n![image-20220505192217715](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1fc20b1d482d2a4e3924c5707ac2ff19.png)\n\n## ؾ㷨л\n\nԶ帺ؾCustomLoadBalancerConfiguration\n\n```\npackage cn.itxs.ecom.order.config;\n\nimport org.springframework.cloud.client.ServiceInstance;\nimport org.springframework.cloud.loadbalancer.core.RandomLoadBalancer;\nimport org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;\nimport org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;\nimport org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.core.env.Environment;\n\npublic class CustomLoadBalancerConfiguration {\n\n    @Bean\n    ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,\n                                                            LoadBalancerClientFactory loadBalancerClientFactory) {\n        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);\n        return new RandomLoadBalancer(loadBalancerClientFactory\n                .getLazyProvider(name, ServiceInstanceListSupplier.class),\n                name);\n    }\n}\n\n```\n\nRestTemplateConfigLoadBalancerClientָ࣬valueֵΪṩҲǿ΢ơ\n\n```\npackage cn.itxs.ecom.order.config;\n\nimport org.springframework.boot.web.client.RestTemplateBuilder;\nimport org.springframework.cloud.client.loadbalancer.LoadBalanced;\nimport org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.web.client.RestTemplate;\n\n@Configuration\n@LoadBalancerClient(value = \"ecom-storage-service\", configuration = CustomLoadBalancerConfiguration.class)\npublic class RestTemplateConfig {\n\n    @LoadBalanced\n    @Bean\n    public RestTemplate restTemplate(RestTemplateBuilder builder) {\n        return builder.build() ;\n    }\n}\n\n```\n\nηʶdedectӿڲȷлΪؾԡ\n\n## ɷʽ\n\nṩ3мSpring Cloud LoadBalancerķʽ˵һʹù֧Spring Web FluxӦʽ̣WebClientǴSpring WebFlux 5.0汾ʼṩһĻӦʽ̵ĽHttpĿͻ˹ߡӦʽ̵ĻReactorġWebClientṩ˱׼HttpʽӦgetpostputdeleteȷӦ\n\n![image-20220506233248585](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/ad44f363342f540b1cb1805b20be6ef0.png)\n\nڶ΢spring-boot-starter-webflux\n\n```\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            spring-boot-starter-webflux\n        </dependency>\n\n```\n\n΢WebClientConfig\n\n```\npackage cn.itxs.ecom.order.config;\n\nimport org.springframework.cloud.client.loadbalancer.LoadBalanced;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.web.reactive.function.client.WebClient;\n\n@Configuration\npublic class WebClientConfig {\n    @LoadBalanced\n    @Bean\n    WebClient.Builder webClientBuilder() {\n        return WebClient.builder();\n    }\n\n    @Bean\n    WebClient webClient() {\n        return webClientBuilder().build();\n    }\n}\n\n```\n\n΢񶩵WebClientӿʵ֣\n\n```\n    @Autowired\n    private WebClient webClient;   \n\n\t@RequestMapping(value = \"/deductWebClient/{commodityCode}/{count}\")\n    public Mono<String> deductWebClient(@PathVariable(\"commodityCode\") String commodityCode, @PathVariable(\"count\") int count) {\n        String url = \"http://ecom-storage-service/deduct/\"+commodityCode+\"/\"+count;\n        // WebClient\n        Mono<String> result = webClient.get().uri(url)\n                .retrieve().bodyToMono(String.class);\n        return result;\n    }\n\n```\n\n΢\n\n![image-20220506234934948](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/ec48e022accfb3298069c96b7e3799e7.png)\n\nʶеļWebClientӿڣ[http://localhost:4070/deductWebClient/1001/1](http://localhost:4070/deductWebClient/1001/1) سɹ\n\n![image-20220506234627330](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/5f737fc9d8806dc072ce3e1fdf983de6.png)\n\nǻûڹķʽͨWebClientʹReactiveLoadBalancerĿSpring Cloud LoadBalancer starterSpring -webflux·УReactorLoadBalancerExchangeFilterFunctionԶõġ\n\n΢񶩵WebClientʹReactiveLoadBalancerӿʵ֣\n\n```\n    @Autowired\n    private ReactorLoadBalancerExchangeFilterFunction lbFunction;   \n\n    @RequestMapping(value = \"/deductWebFluxReactor/{commodityCode}/{count}\")\n    public Mono<String> deductWebFluxReactor(@PathVariable(\"commodityCode\") String commodityCode, @PathVariable(\"count\") int count) {\n        String url = \"/deduct/\"+commodityCode+\"/\"+count;\n        Mono<String> result = WebClient.builder().baseUrl(\"http://ecom-storage-service\")\n                .filter(lbFunction)\n                .build()\n                .get()\n                .uri(url)\n                .retrieve()\n                .bodyToMono(String.class);\n        return result;\n    }\n\n```\n\n΢\n\n![image-20220507000930179](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/3546b9913f1253a1b23039ad6f7546d1.png)\n\nʶеļWebClientӿڣ[http://localhost:4070/deductWebFluxReactor/1001/1](http://localhost:4070/deductWebFluxReactor/1001/1) سɹ\n\n![image-20220507000746900](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/0bc2b04d23d11bc85009c7e040900b2b.png)\n\nLoadBalancerṩܶܣȤϸĺͶʵ\n\n![image-20220507001132987](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1d8f35933945b317a2dd71627fdfa748.png)\n\n## ԭ\n\n### RestTemplate\n\nSpring Cloud LoadBalancerԴȴRestTemplateؾļʵ֣֧֮Spring Web FluxӦʽ̵ʵԭ˼Ҳͬͨͻʵָؾ⡣RestTemplateԴп֪̳InterceptingHttpAccessor\n\n![image-20220508142236428](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/c3ee7af69f54aeed5f1026823779fee8.png)\n\nInterceptingHttpAccessorṩһsetInterceptorsҪʵClientHttpRequestInterceptorӿڼɣʵԶ˽ӿ֮ǰȵintercept൱ServletеFilter\n\n```\n\t// ʵڳInterceptingHttpAccessor\n\t// RestTemplate.InterceptingHttpAccessor#setInterceptors\n\tpublic void setInterceptors(List<ClientHttpRequestInterceptor> interceptors) {\n\t\tAssert.noNullElements(interceptors, \"'interceptors' must not contain null elements\");\n\t\t// Take getInterceptors() List as-is when passed in here\n\t\tif (this.interceptors != interceptors) {\n\t\t\tthis.interceptors.clear();\n\t\t\tthis.interceptors.addAll(interceptors);\n\t\t\tAnnotationAwareOrderComparator.sort(this.interceptors);\n\t\t}\n\t}\n\n```\n\n![image-20220508142443637](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/5062f793bb2bd7f996eb944f77af7137.png)\n\n### **LoadBalancerAutoConfiguration**\n\nӹ֪Spring Cloud LoadBalancerspring-cloud-commonsҲΪĵ@LoadBalancedעҲspring-cloud-commonsʵ֣SpringBootԶװԭȲ鿴ʵ߼ѷspring-cloud-commonsԶLoadBalancerAutoConfigurationReactorLoadBalancerClientAutoConfiguration\n\n![image-20220509001530634](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/b5be12e0dd8d515aa07613738efab122.png)\n\nʱ@ConditionalΪע⣩ԶLoadBalancerInterceptorע뵽RestTemplateС\n\n![image-20220508143752218](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/676f6d81529228511c862c780b0c4532.png)\n\n### **LoadBalancerLnterceptor**\n\nLoadBalancerInterceptorʵClientHttpRequestInterceptorӿڣҲʵinterceptʵָؾش\n\n![image-20220508144048248](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/bb587d8122fee4973eba2fc00ed1af3f.png)\n\n### **LoadBalancerClient**\n\nLoadBalancerClientڽиؾ߼̳ServiceInstanceChooserӿڣӷбѡһַеáLoadBalancerClientִexecute()ִģreconstructURI()عURL\n\n![image-20220508144435104](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/a3590c0ab24cb5ee04cf1cf513724fc0.png)\n\nLoadBalancerClientӿSpring Cloud LoadBalancerṩĬʵΪBlockingLoadBalancerClient\n\n![image-20220508144750601](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/256e4815a982966d7d6bfb82e0e97f67.png)\n\n```\n@SuppressWarnings({ \"unchecked\", \"rawtypes\" })\npublic class BlockingLoadBalancerClient implements LoadBalancerClient {\n\n\tprivate final ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerClientFactory;\n\n\t/**\n\t * @deprecated in favour of\n\t * {@link BlockingLoadBalancerClient#BlockingLoadBalancerClient(ReactiveLoadBalancer.Factory)}\n\t */\n\t@Deprecated\n\tpublic BlockingLoadBalancerClient(LoadBalancerClientFactory loadBalancerClientFactory,\n\t\t\tLoadBalancerProperties properties) {\n\t\tthis.loadBalancerClientFactory = loadBalancerClientFactory;\n\t}\n\n\tpublic BlockingLoadBalancerClient(ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerClientFactory) {\n\t\tthis.loadBalancerClientFactory = loadBalancerClientFactory;\n\t}\n\n\t@Override\n\tpublic <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {\n\t\tString hint = getHint(serviceId);\n\t\tLoadBalancerRequestAdapter<T, DefaultRequestContext> lbRequest = new LoadBalancerRequestAdapter<>(request,\n\t\t\t\tnew DefaultRequestContext(request, hint));\n\t\tSet<LoadBalancerLifecycle> supportedLifecycleProcessors = getSupportedLifecycleProcessors(serviceId);\n\t\tsupportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));\n\t\tServiceInstance serviceInstance = choose(serviceId, lbRequest);\n        // ѡ\n\t\tif (serviceInstance == null) {\n\t\t\tsupportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onComplete(\n\t\t\t\t\tnew CompletionContext<>(CompletionContext.Status.DISCARD, lbRequest, new EmptyResponse())));\n\t\t\tthrow new IllegalStateException(\"No instances available for \" + serviceId);\n\t\t}\n\t\treturn execute(serviceId, serviceInstance, lbRequest);\n\t}\n\n\t@Override\n\tpublic <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request)\n\t\t\tthrows IOException {\n\t\tDefaultResponse defaultResponse = new DefaultResponse(serviceInstance);\n\t\tSet<LoadBalancerLifecycle> supportedLifecycleProcessors = getSupportedLifecycleProcessors(serviceId);\n\t\tRequest lbRequest = request instanceof Request ? (Request) request : new DefaultRequest<>();\n\t\tsupportedLifecycleProcessors\n\t\t\t\t.forEach(lifecycle -> lifecycle.onStartRequest(lbRequest, new DefaultResponse(serviceInstance)));\n\t\ttry {\n\t\t\tT response = request.apply(serviceInstance);\n\t\t\tObject clientResponse = getClientResponse(response);\n\t\t\tsupportedLifecycleProcessors\n\t\t\t\t\t.forEach(lifecycle -> lifecycle.onComplete(new CompletionContext<>(CompletionContext.Status.SUCCESS,\n\t\t\t\t\t\t\tlbRequest, defaultResponse, clientResponse)));\n\t\t\treturn response;\n\t\t}\n\t\tcatch (IOException iOException) {\n\t\t\tsupportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onComplete(\n\t\t\t\t\tnew CompletionContext<>(CompletionContext.Status.FAILED, iOException, lbRequest, defaultResponse)));\n\t\t\tthrow iOException;\n\t\t}\n\t\tcatch (Exception exception) {\n\t\t\tsupportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onComplete(\n\t\t\t\t\tnew CompletionContext<>(CompletionContext.Status.FAILED, exception, lbRequest, defaultResponse)));\n\t\t\tReflectionUtils.rethrowRuntimeException(exception);\n\t\t}\n\t\treturn null;\n\t}\n\n\tprivate <T> Object getClientResponse(T response) {\n\t\tClientHttpResponse clientHttpResponse = null;\n\t\tif (response instanceof ClientHttpResponse) {\n\t\t\tclientHttpResponse = (ClientHttpResponse) response;\n\t\t}\n\t\tif (clientHttpResponse != null) {\n\t\t\ttry {\n\t\t\t\treturn new ResponseData(clientHttpResponse, null);\n\t\t\t}\n\t\t\tcatch (IOException ignored) {\n\t\t\t}\n\t\t}\n\t\treturn response;\n\t}\n\n\tprivate Set<LoadBalancerLifecycle> getSupportedLifecycleProcessors(String serviceId) {\n\t\treturn LoadBalancerLifecycleValidator.getSupportedLifecycleProcessors(\n\t\t\t\tloadBalancerClientFactory.getInstances(serviceId, LoadBalancerLifecycle.class),\n\t\t\t\tDefaultRequestContext.class, Object.class, ServiceInstance.class);\n\t}\n\n\t@Override\n\tpublic URI reconstructURI(ServiceInstance serviceInstance, URI original) {\n\t\treturn LoadBalancerUriTools.reconstructURI(serviceInstance, original);\n\t}\n\n\t@Override\n\tpublic ServiceInstance choose(String serviceId) {\n\t\treturn choose(serviceId, REQUEST);\n\t}\n\n    // ͨͬĸؾͻʵѡͬķ\n\t@Override\n\tpublic <T> ServiceInstance choose(String serviceId, Request<T> request) {\n\t\tReactiveLoadBalancer<ServiceInstance> loadBalancer = loadBalancerClientFactory.getInstance(serviceId);\n\t\tif (loadBalancer == null) {\n\t\t\treturn null;\n\t\t}\n\t\tResponse<ServiceInstance> loadBalancerResponse = Mono.from(loadBalancer.choose(request)).block();\n\t\tif (loadBalancerResponse == null) {\n\t\t\treturn null;\n\t\t}\n\t\treturn loadBalancerResponse.getServer();\n\t}\n\n\tprivate String getHint(String serviceId) {\n\t\tLoadBalancerProperties properties = loadBalancerClientFactory.getProperties(serviceId);\n\t\tString defaultHint = properties.getHint().getOrDefault(\"default\", \"default\");\n\t\tString hintPropertyValue = properties.getHint().get(serviceId);\n\t\treturn hintPropertyValue != null ? hintPropertyValue : defaultHint;\n\t}\n\n}\n\n```\n\n### **LoadBalancerClientFactory**\n\nBlockingLoadBalancerClientгLoadBalancerClientFactoryͨgetInstanceȡĸؾͻˡͨLoadBalancerClientFactoryȡĸؾʵloadBalancer.choose(request)ӿchoose()ʵָݸؾ㷨ѡһɸؾ⣬ReactiveLoadBalancer<t> getInstance(String serviceId) ĬʵLoadBalancerClientFactory\n![image-20220508190132565](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/4014445b0c2ea7189a232b5d257fb938.png)</t>\n\nLoadBalancerClientFactoryͻʵ˲ͬĸؾ㷨ѯȡLoadBalancerClientFactory̳NamedContextFactoryNamedContextFactory̳ApplicationContextAwareʵSpring ApplicationContext\n\n![image-20220508190412076](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/18f62338fdb636327fcdb2d08d33661b.png)\n\n### ReactiveLoadBalancer\n\nReactiveLoadBalancerؾʵַѡSpring Cloud BalancerʵѯRoundRobinLoadBalancerRandomLoadBalancerNacosLoadBalancer㷨\n\n![image-20220508235128931](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/f19b9fd0d246dbae37b05e67b1a79805.png)\n\n### LoadBalancerClientConfiguration\n\nûʽָؾ㷨ĬȱʡֵΪRoundRobinLoadBalancer\n\n```\n\t@Bean\n\t@ConditionalOnMissingBean\n\tpublic ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment,\n\t\t\tLoadBalancerClientFactory loadBalancerClientFactory) {\n\t\tString name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);\n\t\treturn new RoundRobinLoadBalancer(\n\t\t\t\tloadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);\n\t}\n\n```\n\n![image-20220508235645313](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/c8ae46d4cedb33f40939edb4f6fde542.png)\n\n### **LoadBalancerRequestFactory**\n\nLoadBalancerRequestcreateRequestڴLoadBalancerRequestڲLoadBalancerClientҲBlockingLoadBalancerClient\n\n![image-20220509000049541](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/c928dcb312df87a44c33ee77a296ee84.png)\n\nճĿУһ㸺ؾⶼǽFeignʹãʱFeignLoadBalancerԶFeignLoadBalancerAutoConfigurationʵ\n\n### ReactorLoadBalancerClientAutoConfiguration\n\nҲһ»WebClient@Loadbalanced̵룬ؾReactorLoadBalancerClientAutoConfigurationһԶװ࣬Ŀ WebClient  ReactiveLoadBalancer ֮Զװ̾ͿʼУʼһʵ ExchangeFilterFunction ʵںʵΪע뵽WebClientȤо\n\n![image-20220509001650781](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/869a4ea9a6e1409b84c927db08cebea1.png)\n\n## Զ帺ؾ\n\n֪LoadBalancerClientFactoryǴͻؾͿͻʵĹݿͻƴһSpring ApplicationContextȡbean˽뵽LoadBalancerClientFactoryУҪȥʵӽӿReactorServiceInstanceLoadBalancerΪȥȡؾʵʱͨȥвReactorServiceInstanceLoadBalancer͵beanʵֵģԲRandomLoadBalancerʵִ\n\n```\npackage org.springframework.cloud.loadbalancer.core;\n\nimport java.util.List;\nimport java.util.concurrent.ThreadLocalRandom;\n\nimport org.apache.commons.logging.Log;\nimport org.apache.commons.logging.LogFactory;\nimport reactor.core.publisher.Mono;\n\nimport org.springframework.beans.factory.ObjectProvider;\nimport org.springframework.cloud.client.ServiceInstance;\nimport org.springframework.cloud.client.loadbalancer.DefaultResponse;\nimport org.springframework.cloud.client.loadbalancer.EmptyResponse;\nimport org.springframework.cloud.client.loadbalancer.Request;\nimport org.springframework.cloud.client.loadbalancer.Response;\n\npublic class RandomLoadBalancer implements ReactorServiceInstanceLoadBalancer {\n\n\tprivate static final Log log = LogFactory.getLog(RandomLoadBalancer.class);\n\n\tprivate final String serviceId;\n\n\tprivate ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;\n\n\t/**\n\t * @param serviceInstanceListSupplierProvider a provider of\n\t * {@link ServiceInstanceListSupplier} that will be used to get available instances\n\t * @param serviceId id of the service for which to choose an instance\n\t */\n\tpublic RandomLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,\n\t\t\tString serviceId) {\n\t\tthis.serviceId = serviceId;\n\t\tthis.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;\n\t}\n\n\t@SuppressWarnings(\"rawtypes\")\n\t@Override\n\tpublic Mono<Response<ServiceInstance>> choose(Request request) {\n\t\tServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider\n\t\t\t\t.getIfAvailable(NoopServiceInstanceListSupplier::new);\n\t\treturn supplier.get(request).next()\n\t\t\t\t.map(serviceInstances -> processInstanceResponse(supplier, serviceInstances));\n\t}\n\n\tprivate Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier,\n\t\t\tList<ServiceInstance> serviceInstances) {\n\t\tResponse<ServiceInstance> serviceInstanceResponse = getInstanceResponse(serviceInstances);\n\t\tif (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {\n\t\t\t((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());\n\t\t}\n\t\treturn serviceInstanceResponse;\n\t}\n\n\tprivate Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {\n\t\tif (instances.isEmpty()) {\n\t\t\tif (log.isWarnEnabled()) {\n\t\t\t\tlog.warn(\"No servers available for service: \" + serviceId);\n\t\t\t}\n\t\t\treturn new EmptyResponse();\n\t\t}\n\t\tint index = ThreadLocalRandom.current().nextInt(instances.size());\n\n\t\tServiceInstance instance = instances.get(index);\n\n\t\treturn new DefaultResponse(instance);\n\t}\n\n}\n\n```\n\nʵֽм򵥷д\n\n```\npackage cn.itxs.ecom.order.config;\n\nimport java.util.List;\nimport java.util.Random;\nimport org.springframework.beans.factory.ObjectProvider;\nimport org.springframework.cloud.client.ServiceInstance;\nimport org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;\nimport org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;\nimport org.springframework.cloud.client.loadbalancer.DefaultResponse;\nimport org.springframework.cloud.client.loadbalancer.EmptyResponse;\nimport org.springframework.cloud.client.loadbalancer.Request;\nimport org.springframework.cloud.client.loadbalancer.Response;\nimport reactor.core.publisher.Mono;\n\npublic class ItxsRandomLoadBalancerClient implements ReactorServiceInstanceLoadBalancer {\n    // б\n    private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;\n\n    public ItxsRandomLoadBalancerClient(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider) {\n        this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;\n    }\n\n    @Override\n    public Mono<Response<ServiceInstance>> choose(Request request) {\n        ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider.getIfAvailable();\n        return supplier.get().next().map(this::getInstanceResponse);\n    }\n\n    /**\n     * ʹȡ\n     * @param instances\n     * @return\n     */\n    private Response<ServiceInstance> getInstanceResponse(\n            List<ServiceInstance> instances) {\n        System.out.println(\"ItxsRandomLoadBalancerClient start\");\n        if (instances.isEmpty()) {\n            return new EmptyResponse();\n        }\n\n        System.out.println(\"ItxsRandomLoadBalancerClient random\");\n        // 㷨\n        int size = instances.size();\n        Random random = new Random();\n        ServiceInstance instance = instances.get(random.nextInt(size));\n\n        return new DefaultResponse(instance);\n    }\n}\n\n```\n\nCustomLoadBalancerConfiguration滻Ϊ\n\n```\npackage cn.itxs.ecom.order.config;\n\nimport org.springframework.beans.factory.ObjectProvider;\nimport org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;\nimport org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;\nimport org.springframework.context.annotation.Bean;\n\npublic class CustomLoadBalancerConfiguration {\n\n    @Bean\n    public ReactorServiceInstanceLoadBalancer customLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider) {\n        return new ItxsRandomLoadBalancerClient(serviceInstanceListSupplierProvider);\n    }\n}\n\n```\n\n΢Ͷ΢񣬷http://localhost:4070/deductRest/1001/1 ̨ѴӡԶItxsRandomLoadBalancerClientе־ͳɹʽ\n\n![image-20220509003807968](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/c72e3f02f7e0d5d3343f8ae9c464b69c.png)\n\n![image-20220509003927550](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/59796bbbd6b3524e32759d42b622f1bc.png)\n\n# ο\nhttps://lijunyi.xyz/docs/SpringCloud/SpringCloud.html#_2-2-x-%E5%88%86%E6%94%AF\nhttps://mp.weixin.qq.com/s/2jeovmj77O9Ux96v3A0NtA\nhttps://juejin.cn/post/6931922457741770760\nhttps://github.com/D2C-Cai/herring\nhttp://c.biancheng.net/springcloud\nhttps://github.com/macrozheng/springcloud-learning"
  },
  {
    "path": "docs/Spring全家桶/SpringCloud源码分析/SpringCloudOpenFeign源码分析.md",
    "content": " | ľľ\nԴ |ͷ\n\n**ѧϰĿ**\n\n1.  ΪʲôһעʵԶ̵̹أƵײʵ̣\n2.  OpenFeignôʵRPCĻܵ\n3.  ͨԴ֤\n    **1 OpenFeignƵ**\n    ҪȷOpenFeignǻҪȷĺĿʲô\n\n˵ˣOpenFeignĵĿÿͻԶ̵ùвҪʲôĲֻҪõһȻøöķͺˣʣµĲOpenFeignȥɣʣһЩʲôأ\n\n1.  ȿ϶Ǳ֤ͨţǴ󵨵ز²һ£OpenFeignʵײǷװĵַ˿ڡԼӦĲ\n2.  ΣҪöȥ󷽷Զ̵ķ϶򵥣Ҳ󵨲²һ£öҲOpenFeignǴġ\n3.  ȻڵùУɶ̨ṩģ漰ؾˣ϶ҲOpenFeignˡ\n4.  ⣬һ£ȻڷǼȺǷ˵ĵַͶ˿ڻҪһעעᣬ϶ҲɿͻɣΪͻֻעҵ롣붼룬ҲOpenFeignˡ\n\nOKƵOpenFeignӦɵҪĿ꣬ôġ\n![SpringCloudϵСSpring Cloud Դ֮OpenFeign-Դ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/b45564c706ab4113cc23492cc0796907b851f8.jpg \"SpringCloudϵСSpring Cloud Դ֮OpenFeign-Դ\")֮ǰнһʲôֻҪǼspringspringbootĻһͨspringspringbootȥbeanĴģͨõ֮ȥöĺķOpenFeignڼspringbootʱҲӦ\n\n1.  ԵһOpenFeignspringbootͨspringbootõbeanͼеuserService\n2.  ϶򵥣ֻgetUserĹܡһ룬springǿʲôأ;ͺ֮ˣ\n3.  ôķʱȽ뵽invokeУinvokeУǿ˸ؾLoadBalanceΪgetUserʱҪ֪ǵ̨ķ\n4.  ؾ͵ƴӾhttpͷַ˿ˡ\n    OKϷOpenFeignײҪʵֵľ幦ܣҲĴ̣ôͨԴ֤һ£ǲôġ\n\n**2 Դ֤**\n\n![SpringCloudϵСSpring Cloud Դ֮OpenFeign-Դ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/93070e354fbacedc2b85274846d2dde7dd170a.jpg \"SpringCloudϵСSpring Cloud Դ֮OpenFeign-Դ\")**2.1 EnableFeignClients**\nǴע룬ע⿪FeignClientĽ̡\n\n\n\n```\n@EnableFeignClients(basePackages = \"com.example.client\")\n```\n\n\n\n\n\n\n\n\n\nע£õһ@Importע⣬֪ImportһģȥһFeignClientsRegistrarĶ塣\n\n\n\n```\n@Retention(RetentionPolicy.RUNTIME)\n@Target(ElementType.TYPE)\n@Documented\n@Import(FeignClientsRegistrar.class)\npublic @interface EnableFeignClients {\n}\n```\n\n\n\n\n\n\n\n\n\nFeignClientsRegistrarʵ\nImportBeanDefinitionRegistrarһ̬עbeanĽӿڣSpring Bootʱ򣬻ȥеregisterBeanDefinitionsʵֶ̬BeanװءregisterBeanDefinitionsspringʱִinvokeBeanFactoryPostProcessorsȻӦнעᣬImportSelector\n\n\n\n```\nclass FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware,EnvironmentAware {\n    @Override\n    public void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {\n        registerDefaultConfiguration(metadata, registry);\n        registerFeignClients(metadata, registry);\n    }\n}\n```\n\n\n\n\n\n\n\n\n\n**2.1.1 ImportBeanDefinitionRegistrar**\n򵥸ʾһ\nImportBeanDefinitionRegistrará\n\n*   һҪװصIOCеHelloService\n\n\n\n```\npublic class HelloService {\n}\n```\n\n\n\n\n\n\n\n\n\n*   һRegistrarʵ֣һbeanװصIOC\n\n\n\n```\npublic class FeignImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {\n    @Override\n    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {\n        BeanDefinition beanDefinition = new GenericBeanDefinition();\n        beanDefinition.setBeanClassName(HelloService.class.getName());\n        registry.registerBeanDefinition(\"helloService\",beanDefinition);\n    }\n}\n```\n\n\n\n\n\n\n\n\n\n* һע\n\n\n\n  ```\n  @Retention(RetentionPolicy.RUNTIME)\n  @Target({ElementType.TYPE})\n  @Documented\n  @Import({FeignImportBeanDefinitionRegistrar.class})\n  public @interface EnableFeignTest {\n  }\n  ```\n\n\n\n\n\n\n\n\n\n* \n\n\n\n```\n@EnableFeignClients(basePackages = \"com.example.clients\")\n@EnableFeignTest\n@SpringBootApplication\npublic class OpenfeignUserServiceApplication {\n    public static void main(String[] args) {\n        ConfigurableApplicationContext context = SpringApplication.run(OpenfeignUserServiceApplication.class, args);\n        System.out.println(context.getBean(HelloService.class));\n    }\n\n}\n```\n\n\n\n\n\n\n\n\n\n*   ͨʾԷ֣HelloServicebean ѾװصIOC\n    Ƕ̬װصĹʵ֣@Configurationע룬˺ܶԡ okٻصFeignClientĽ\n\n**2.1.2 FeignClientsRegistrar**\n\n* registerDefaultConfiguration ڲ SpringBoot ϼǷ@EnableFeignClients, иעĻ  Feign صһЩע\n\n* registerFeignClients ڲ classpath У ɨ @FeignClient ε࣬ ݽΪ BeanDefinition , ͨ Spring еBeanDefinitionReaderUtils.resgisterBeanDefinition  FeignClientBeanDeifinition ӵ spring .\n\n\n\n  ```\n  @Override\n  public void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {\n      //ע@EnableFeignClientsжdefaultConfigurationµ࣬װFeignClientSpecificationעᵽSpring\n      //@FeignClientһԣconfigurationǱʾFeignClientԶ࣬ҲͨregisterClientConfigurationעFeignClientSpecification\n      //ԣȫ@EnableFeignClientsõΪ׵ãڸ@FeignClientõľԶ\n      registerDefaultConfiguration(metadata, registry);\n      registerFeignClients(metadata, registry);\n  }\n  ```\n\n\n\n\n\n\n\n\n\n**2.2 registerDefaultConfiguration**\n\n\n\n  ```\n  private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {\n      // ȡmetadataйEnableFeignClientsֵֵԡ\n      Map<String, Object> defaultAttrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName(), true);\n      //defaultConfiguration ,ûʹĬϵconfiguration\n      if (defaultAttrs != null && defaultAttrs.containsKey(\"defaultConfiguration\")) {\n          String name;\n          if (metadata.hasEnclosingClass()) {\n              name = \"default.\" + metadata.getEnclosingClassName();\n          } else {\n              name = \"default.\" + metadata.getClassName();\n          }\n          //ע\n          this.registerClientConfiguration(registry, name, defaultAttrs.get(\"defaultConfiguration\"));\n      }\n  \n  }\n  ```\n\n\n\n\n\n\n\n\n\n\n\n```\nprivate void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {\n    //ʹBeanDefinitionBuilderBeanDefinition,ע\n    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);\n    builder.addConstructorArgValue(name);\n    builder.addConstructorArgValue(configuration);\n    registry.registerBeanDefinition(name + \".\" + FeignClientSpecification.class.getSimpleName(), builder.getBeanDefinition());\n}\n```\n\n\n\n\n\n\n\n\n\nBeanDefinitionRegistryspringڶ̬עBeanDefinitionϢĽӿڣregisterBeanDefinitionԽBeanDefinitionעᵽSpringУnameԾעBeanDefinitionƣעһFeignClientSpecificationĶ\n\nFeignClientSpecificationʵ\nNamedContextFactory.SpecificationӿڣFeignʵҪһķУԶõʵSpringCloudʹNamedContextFactoryһЩеApplicationContextöӦSpecificationЩдʵ\n\nNamedContextFactory3ܣ\n\n*   AnnotationConfigApplicationContextġ\n*   дȡbeanʵ\n*   ʱеfeignʵ\n    NamedContextFactoryиǳҪFeignContextڴ洢OpenFeignʵ\n\n\n\n```\npublic class FeignContext extends NamedContextFactory<FeignClientSpecification> {\n    public FeignContext() {\n       super(FeignClientsConfiguration.class, \"feign\", \"feign.client.name\");\n    }\n }\n```\n\n\n\n\n\n\n\n\n\nFeignContextﹹأ\n\nü\npring-cloud-openfeign-core-2.2.3.RELEASE.jar!\\META-INF\\spring.factories![SpringCloudϵСSpring Cloud Դ֮OpenFeign-Դ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/d830f20713aca01438472786f5f84c7058232b.jpg \"SpringCloudϵСSpring Cloud Դ֮OpenFeign-Դ\")**2.2.1 FeignAutoConfiguration**\n\n![SpringCloudϵСSpring Cloud Դ֮OpenFeign-Դ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/894c73791fbf7215782217d7132bc57f004085.jpg \"SpringCloudϵСSpring Cloud Դ֮OpenFeign-Դ\")\n\nĬϵFeignClientsConfigurationΪݸ캯\n\nFeignContextʱὫ֮ǰFeignClientSpecificationͨsetConfigurationsøcontextġ\n\n**2.2.2 createContext**\n\norg.springframework.cloud.context.named.NamedContextFactory#createContext\n\nFeignContextĸcreateContextὫ\nAnnotationConfigApplicationContextʵʵΪǰĵģڹfeignĲͬʵڵFeignClientFactoryBeangetObjectʱácreateContextĻὲ⣩\n\n\n\n```\nprotected AnnotationConfigApplicationContext createContext(String name) {\n    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();\n\t//ȡnameӦconfiguration,оעᵽcontext\n    if (this.configurations.containsKey(name)) {\n        for (Class<?> configuration : this.configurations.get(name)\n             .getConfiguration()) {\n            context.register(configuration);\n        }\n    }\n    //עdefaultConfiguration,Ҳ FeignClientsRegistrarregisterDefaultConfigurationעConfiguration\n    for (Map.Entry<String, C> entry : this.configurations.entrySet()) {\n        if (entry.getKey().startsWith(\"default.\")) {\n            for (Class<?> configuration : entry.getValue().getConfiguration()) {\n                context.register(configuration);\n            }\n        }\n    }\n    //עPropertyPlaceholderAutoConfiguration\n    context.register(PropertyPlaceholderAutoConfiguration.class,\n                     this.defaultConfigType);\n    //EnvironmentpropertySourcesԴ\n    context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(\n        this.propertySourceName,\n        Collections.<String, Object>singletonMap(this.propertyName, name)));\n    if (this.parent != null) {\n        // Uses Environment from parent as well as beans\n        context.setParent(this.parent);\n        // jdk11 issue\n        // https://github.com/spring-cloud/spring-cloud-netflix/issues/3101\n        context.setClassLoader(this.parent.getClassLoader());\n    }\n    context.setDisplayName(generateDisplayName(name));\n    context.refresh();\n    return context;\n}\n```\n\n\n\n\n\n\n\n\n\nNamedContextFactoryʵDisposableBeanԵʵʱ\n\n\n\n```\n@Override\npublic void destroy() {\n    Collection<AnnotationConfigApplicationContext> values = this.contexts.values();\n    for (AnnotationConfigApplicationContext context : values) {\n        // This can fail, but it never throws an exception (you see stack traces\n        // logged as WARN).\n        context.close();\n    }\n    this.contexts.clear();\n}\n```\n\n\n\n\n\n\n\n\n\nܽ᣺NamedContextFactoryᴴ\nAnnotationConfigApplicationContextʵnameΪΨһʶȻÿAnnotationConfigApplicationContextʵעᲿ࣬ӶԸһϵеĻɵʵͿԻnameһϵеʵΪͬFeignClient׼ͬʵ\n\n**2.3 registerFeignClients**\nҪɨ·е@FeignClientע⣬Ȼж̬Beanע롣ջ registerFeignClient \n\n\n\n```\npublic void registerFeignClients(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {\n    //ʡԴ...\n    registerFeignClient(registry, annotationMetadata, attributes);\n}\n```\n\n\n\n\n\n\n\n\n\nУȥװBeanDefinitionҲBeanĶ壬ȻעᵽSpring IOC\n\n\n\n```\nprivate void registerFeignClient(BeanDefinitionRegistry registry,AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {\n    String className = annotationMetadata.getClassName();\n    BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);\n    //ʡԴ...\n    BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition,className,new String[] { alias });\n    BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);\n}\n```\n\n\n\n\n\n\n\n\n\nǹעһ£BeanDefinitionBuilderһBeanDefinitionģͨgenericBeanDefinition ģҴһFeignClientFactoryBeanࡣ\n\nǿԷ֣FeignClient̬עһFactoryBean\n\n> Spring Cloud FengnClientʵSpringĴɴ࣬ŻеFeignClientBeanDefinitionΪFeignClientFactoryBeanͣFeignClientFactoryBean̳FactoryBeanһBean\n>\n> SpringУFactoryBeanһBeanBean\n>\n>  Bean һ Bean,  Bean ˵ ߼Ǹ֪ Bean ͨ Bean ǹ Bean, ֻǰĻȡ Bean ʽȥã bean 󷵻صʵǹBean  ִй Bean  getObject ߼صʾҲʵBeanʱȥgetObject\n\n\n\n```\npublic static BeanDefinitionBuilder genericBeanDefinition(Class<?> beanClass) {\n    BeanDefinitionBuilder builder = new BeanDefinitionBuilder(new GenericBeanDefinition());\n    builder.beanDefinition.setBeanClass(beanClass);\n    return builder;\n}\n```\n\n\n\n\n\n\n\n\n\n˵FeignClientעӿڣͨ\nFeignClientFactoryBean.getObject()һ\n\n**2.3.1 FeignClientFactoryBean.getObject**\ngetObjectõgetTargetapplicationContextȡFeignContextFeignContext̳NamedContextFactoryͳһάfeignиfeignͻ໥ġ\n\nţfeign.builderڹʱFeignContextȡõEncoderDecoderȸϢFeignContextƪѾᵽΪÿFeignͻ˷һǵĸspring\n\nFeign.Builder֮жǷҪLoadBalanceҪͨLoadBalanceķáʵյõTarget.target()\n\n\n\n```\n@Override\npublic Object getObject() throws Exception {\n    return getTarget();\n}\n<T> T getTarget() {\n    //ʵFeignĶFeignContext\n    FeignContext context = this.applicationContext.getBean(FeignContext.class);\n    Feign.Builder builder = feign(context);//Builder\n    if (!StringUtils.hasText(this.url)) {//urlΪգ߸ؾ⣬иؾ⹦ܵĴ\n        if (!this.name.startsWith(\"http\")) {\n            this.url = \"http://\" + this.name;\n        }\n        else {\n            this.url = this.name;\n        }\n        this.url += cleanPath();\n        return (T) loadBalance(builder, context,new HardCodedTarget<>(this.type, this.name,this.url));\n    }\n    //ָurlĬϵĴ\n    if (StringUtils.hasText(this.url) && !this.url.startsWith(\"http\")) {\n        this.url = \"http://\" + this.url;\n    }\n    String url = this.url + cleanPath();\n    //FeignContextgetInstanceȡClient\n    Client client = getOptional(context, Client.class);\n    if (client != null) {\n        if (client instanceof LoadBalancerFeignClient) {\n            // not load balancing because we have a url,\n            // but ribbon is on the classpath, so unwrap\n            client = ((LoadBalancerFeignClient) client).getDelegate();\n        }\n        if (client instanceof FeignBlockingLoadBalancerClient) {\n            // not load balancing because we have a url,\n            // but Spring Cloud LoadBalancer is on the classpath, so unwrap\n            client = ((FeignBlockingLoadBalancerClient) client).getDelegate();\n        }\n        builder.client(client);\n    }//Ĭϴ\n    Targeter targeter = get(context, Targeter.class);\n    return (T) targeter.target(this, builder, context,new HardCodedTarget<>(this.type, this.name,url));\n}\n```\n\n\n\n\n\n\n\n\n\n**2.3.2 loadBalance**\nɾ߱ؾfeignͻˣΪfeignͻ˹󶨸ؾͻ.\n\nClient client = (Client)this.getOptional(context, Client.class); лȡһClientĬLoadBalancerFeignClient\n\nFeignRibbonClientAutoConfigurationԶװУͨImportʵֵ\n\n\n\n```\n@Import({ HttpClientFeignLoadBalancedConfiguration.class,OkHttpFeignLoadBalancedConfiguration.class,DefaultFeignLoadBalancedConfiguration.class })\n```\n\n\n\n\n\n\n\n\n\n\n\n```\nprotected <T> T loadBalance(Builder builder, FeignContext context,\n                            HardCodedTarget<T> target) {\n    Client client = (Client)this.getOptional(context, Client.class);\n    if (client != null) {\n        builder.client(client);\n        Targeter targeter = (Targeter)this.get(context, Targeter.class);\n        return targeter.target(this, builder, context, target);\n    } else {\n        throw new IllegalStateException(\"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?\");\n    }\n}\n```\n\n\n\n\n\n\n\n\n\n**2.3.3 DefaultTarget.target**\n\n\n\n```\n@Override\npublic <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,FeignContext context, Target.HardCodedTarget<T> target) {\n    return feign.target(target);\n}\n```\n\n\n\n\n\n\n\n\n\n**2.3.4 ReflectiveFeign.newInstance**\nһ̬ķɶ̬֮ǰContractЭ飨Э򣬽ӿעϢڲMethodHandlerĴʽ\n\nʵֵĴпԿϤProxy.newProxyInstanceࡣҪÿĽӿڷضĴʵ֣һMethodHandlerĸǶӦInvocationHandler\n\n\n\n```\npublic <T> T newInstance(Target<T> target) {\n    //ݽӿContractЭʽӿϵķע⣬תڲMethodHandlerʽ\n    Map<String, MethodHandler> nameToHandler = this.[targetToHandlersByName.apply(target)];\n    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap();\n    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList();\n    Method[] var5 = target.type().getMethods();\n    int var6 = var5.length;\n    for(int var7 = 0; var7 < var6; ++var7) {\n        Method method = var5[var7];\n        if (method.getDeclaringClass() != Object.class) {\n            if (Util.isDefault(method)) {\n                DefaultMethodHandler handler = new DefaultMethodHandler(method);\n                defaultMethodHandlers.add(handler);\n                methodToHandler.put(method, handler);\n            } else {\n                methodToHandler.put(method,nameToHandler.get(Feign.configKey(target.type(), method)));\n            }\n        }\n    }\n    InvocationHandler handler = this.factory.create(target, methodToHandler);\n    // Proxy.newProxyInstance Ϊӿഴ̬ʵ֣еתInvocationHandler \n    T proxy = Proxy.newProxyInstance(target.type().getClassLoader(), new Class[]{target.type()}, handler);\n    Iterator var12 = defaultMethodHandlers.iterator();\n    while(var12.hasNext()) {\n        DefaultMethodHandler defaultMethodHandler = (DefaultMethodHandler)var12.next();\n        defaultMethodHandler.bindTo(proxy);\n    }\n    return proxy;\n}\n```\n\n\n\n\n\n\n\n\n\n**2.4 ӿڶĲ**\nFeignClientӿڵӦݡ\n\n**2.4.1 targetToHandlersByName.apply(target)**\nContractЭ򣬽ӿעϢڲ֣\n\ntargetToHandlersByName.apply(target);ӿڷϵע⣬ӶȵضϢȻһSynchronousMethodHandler ȻҪάһ<methodMethodHandler>mapInvocationHandlerʵFeignInvocationHandlerС\n\n\n\n```\npublic Map<String, MethodHandler> apply(Target target) {\n    List<MethodMetadata> metadata = contract.parseAndValidateMetadata(target.type());\n    Map<String, MethodHandler> result = new LinkedHashMap<String,MethodHandler>();\n    for (MethodMetadata md : metadata) {\n        BuildTemplateByResolvingArgs buildTemplate;\n        if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null)\n        {\n            buildTemplate = new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder,target);\n        } else if (md.bodyIndex() != null) {\n            buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder,queryMapEncoder, target);\n        } else {\n            buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder,target);\n        }\n        if (md.isIgnored()) {\n            result.put(md.configKey(), args -> {\n                throw new IllegalStateException(md.configKey() + \" is not a method handled by feign\");\n            });\n        } else {\n            result.put(md.configKey(),\n                       factory.create(target, md, buildTemplate, options, decoder,errorDecoder));\n        }\n    }\n    return result;\n}\n```\n\n\n\n\n\n\n\n\n\n**2.4.2 SpringMvcContract**\nǰSpring Cloud ΢УΪ˽ѧϰɱSpring MVCĲע Ҳ˵ дͻӿںд˴һͻ˺ͷ˿ͨSDKķʽԼͻֻҪ˷SDK APIͿʹӿڵı뷽ʽԽӷ\n\n̳Contract.BaseContractʵResourceLoaderAwareӿڣ\n\nþǶRequestMappingRequestParamRequestHeaderעнġ\n\n**2.5 OpenFeignù**\nǰķУ֪OpenFeignշصһ#\nReflectiveFeign.FeignInvocationHandlerĶ\n\nôͻ˷ʱ뵽\nFeignInvocationHandler.invokeУҶ֪һ̬ʵ֡\n\n\n\n```\npublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {\n    if (!\"equals\".equals(method.getName())) {\n        if (\"hashCode\".equals(method.getName())) {\n            return this.hashCode();\n        } else {\n            return \"toString\".equals(method.getName()) ? this.toString() :\n            ((MethodHandler)this.dispatch.get(method)).invoke(args);\n        }\n    } else {\n        try {\n            Object otherHandler = args.length > 0 && args[0] != null ?\n                Proxy.getInvocationHandler(args[0]) : null;\n            return this.equals(otherHandler);\n        } catch (IllegalArgumentException var5) {\n            return false;\n        }\n    }\n}\n```\n\n\n\n\n\n\n\n\n\nţinvokeУ this.dispatch.get(method)).invoke(args) this.dispatch.get(method) ᷵һSynchronousMethodHandler,ش\n\nݲɵRequestTemplateHttpģ棬¡\n\n\n\n```\npublic Object invoke(Object[] argv) throws Throwable {\n    RequestTemplate template = this.buildTemplateFromArgs.create(argv);\n    Options options = this.findOptions(argv);\n    Retryer retryer = this.retryer.clone();\n    while(true) {\n        try {\n            return this.executeAndDecode(template, options);\n        } catch (RetryableException var9) {\n            RetryableException e = var9;\n            try {\n                retryer.continueOrPropagate(e);\n            } catch (RetryableException var8) {\n                Throwable cause = var8.getCause();\n                if (this.propagationPolicy == ExceptionPropagationPolicy.UNWRAP\n                    && cause != null) {\n                    throw cause;\n                }\n                throw var8;\n            }\n            if (this.logLevel != Level.NONE) {\n                this.logger.logRetry(this.metadata.configKey(), this.logLevel);\n            }\n        }\n    }\n}\n```\n\n\n\n\n\n\n\n\n\n**2.5.1 executeAndDecode**\nĴ룬ѾrestTemplateƴװɣĴһ executeAndDecode() ÷ͨRequestTemplateRequestȻHttp ClientȡresponseȡӦϢ\n\n\n\n```\nObject executeAndDecode(RequestTemplate template, Options options) throws Throwable {\n    //תΪHttp\n    Request request = this.targetRequest(template);\n    if (this.logLevel != Level.NONE) {\n        this.logger.logRequest(this.metadata.configKey(), this.logLevel,request);\n    }\n    long start = System.nanoTime();\n    Response response;\n    try {\n        //Զͨ\n        response = this.client.execute(request, options);\n        //ȡؽ\n        response = response.toBuilder().request(request).requestTemplate(template).build();\n    } catch (IOException var16) {\n        if (this.logLevel != Level.NONE) {\n            this.logger.logIOException(this.metadata.configKey(), this.logLevel,var16, this.elapsedTime(start));\n        }\n        throw FeignException.errorExecuting(request, var16);\n    }\n    long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);\n    boolean shouldClose = true;\n    Response var10;\n    try {\n        if (this.logLevel != Level.NONE) {\n            response = this.logger.logAndRebufferResponse(this.metadata.configKey(), this.logLevel,response, elapsedTime);\n        }\n        if (Response.class != this.metadata.returnType()) {\n            Object result;\n            Object var21;\n            if (response.status() >= 200 && response.status() < 300) {\n                if (Void.TYPE == this.metadata.returnType()) {\n                    var10 = null;\n                    return var10;\n                }\n                result = this.decode(response);\n                shouldClose = this.closeAfterDecode;\n                var21 = result;\n                return var21;\n            }\n            if (this.decode404 && response.status() == 404 && Void.TYPE != this.metadata.returnType()) {\n                result = this.decode(response);\n                shouldClose = this.closeAfterDecode;\n                var21 = result;\n                return var21;\n            }\n            throw this.errorDecoder.decode(this.metadata.configKey(), response);\n        }\n        if (response.body() == null) {\n            var10 = response;\n            return var10;\n        }\n        if (response.body().length() != null && (long)response.body().length() <= 8192L) {\n            byte[] bodyData = Util.toByteArray(response.body().asInputStream());\n            Response var11 = response.toBuilder().body(bodyData).build();\n            return var11;\n        }\n        shouldClose = false;\n        var10 = response;\n    } catch (IOException var17) {\n        if (this.logLevel != Level.NONE) {\n            this.logger.logIOException(this.metadata.configKey(), this.logLevel,var17, elapsedTime);\n        }\n        throw FeignException.errorReading(request, response, var17);\n    } finally {\n        if (shouldClose) {\n            Util.ensureClosed(response.body());\n        }\n    }\n    return var10;\n}\n```\n\n\n\n\n\n\n\n\n\n**2.5.2 Client.execute**\nĬϲJDK HttpURLConnection Զ̵á\n\n\n\n```\n@Override\npublic Response execute(Request request, Options options) throws IOException {\n    HttpURLConnection connection = convertAndSend(request, options);\n    return convertResponse(connection, request);\n}\nResponse convertResponse(HttpURLConnection connection, Request request) throws IOException {\n    int status = connection.getResponseCode();\n    String reason = connection.getResponseMessage();\n    if (status < 0) {\n        throw new IOException(format(\"Invalid status(%s) executing %s %s\",status,connection.getRequestMethod(),connection.getURL()));\n    }\n    Map<String, Collection<String>> headers = new LinkedHashMap<>();\n    for (Map.Entry<String, List<String>> field :\n         connection.getHeaderFields().entrySet()) {\n        // response message\n        if (field.getKey() != null) {\n            headers.put(field.getKey(), field.getValue());\n        }\n    }\n    Integer length = connection.getContentLength();\n    if (length == -1) {\n        length = null;\n    }\n    InputStream stream;\n    if (status >= 400) {\n        stream = connection.getErrorStream();\n    } else {\n        stream = connection.getInputStream();\n    }\n    return Response.builder()\n        .status(status)\n        .reason(reason)\n        .headers(headers)\n        .request(request)\n        .body(stream, length)\n        .build();\n}\n```\n\n\n\n# ο\nhttps://lijunyi.xyz/docs/SpringCloud/SpringCloud.html#_2-2-x-%E5%88%86%E6%94%AF\nhttps://mp.weixin.qq.com/s/2jeovmj77O9Ux96v3A0NtA\nhttps://juejin.cn/post/6931922457741770760\nhttps://github.com/D2C-Cai/herring\nhttp://c.biancheng.net/springcloud\nhttps://github.com/macrozheng/springcloud-learning"
  },
  {
    "path": "docs/Spring全家桶/SpringCloud源码分析/SpringCloudRibbon源码分析.md",
    "content": "**ѧϰĿ**\n\n1.  ƵRibbonĺ\n2.  дһװRibbon\n3.  ͨԴ֤Ƶ\n    **1 Ƶ**\n    ʵRibbonĺ̺ܼ򵥣ʹù޷Ǿһspring-cloud-starter-netflix-ribbonjarȻڳʱעһRestTemplateڸöһ@LoadBalancedע⣬ȻͨRestTemplateȥURLʱܸݲͬĸؾȥͬķע⣬˵jarʲôأ\n\nҪףspring-cloud-starter-netflix-ribbonjar֪ǻstarterģ϶springbootԶװԭʱṩһԶ࣬ҪõĶע뵽IoCȥˣӹɡ\n\nȻҪRestTemplateȥĿʱʱǿ϶ҪʵIPͶ˿滻һʵǺĲ裬Ҫôأô֮ǰַͶ˿è̫ӻʵأ\n\nճУӦ֪һһʵ԰ɣԣʵϾڻȡRestTemplateʱ򣬽öһRestTemplateִĳʱ򣬶ȥִһ顣Ȼˡ\n\nͼƵ£![SpringCloudϵСRibbonԴ-Դ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1790fb324161913dd79098940f8102d652cd86.jpg \"SpringCloudϵСRibbonԴ-Դ\")**2 װRibbonʵ**\nƵ̣ǽʵһװRibbon\n\nĲҪ˼·\n\n1.ҪʵһstarterspringbootspringbootʱõӦRestTemplatebean󡣴һMavenquickstartĿ\n\n2.Ȼ\n\n\n\n```\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n  xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <groupId>com.example</groupId>\n  myribbon-spring-cloud-starter\n  <version>1.0-SNAPSHOT</version>\n\n  <name>myribbon-spring-cloud-starter</name>\n  <!-- FIXME change it to the project's website -->\n  <url>http://www.example.com</url>\n\n  <properties>\n    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n    <maven.compiler.source>1.8</maven.compiler.source>\n    <maven.compiler.target>1.8</maven.compiler.target>\n    <spring-boot.version>2.3.2.RELEASE</spring-boot.version>\n  </properties>\n\n  <!-- RestTemplateҪõSpringMVC -->\n  <dependencies>\n    <dependency>\n      <groupId>org.springframework.boot</groupId>\n      spring-boot-starter-web\n      <version>${spring-boot.version}</version>\n    </dependency>\n\n    <dependency>\n      <groupId>org.springframework.boot</groupId>\n      spring-boot-starter-test\n      <version>${spring-boot.version}</version>\n    </dependency>\n\n    <!-- 漯һЩĽӿ -->\n    <dependency>\n      <groupId>org.springframework.cloud</groupId>\n      spring-cloud-commons\n      <version>2.2.6.RELEASE</version>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->\n      <plugins>\n        <!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->\n        <plugin>\n          maven-clean-plugin\n          <version>3.1.0</version>\n        </plugin>\n        <!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->\n        <plugin>\n          maven-resources-plugin\n          <version>3.0.2</version>\n        </plugin>\n        <plugin>\n          maven-compiler-plugin\n          <version>3.8.0</version>\n        </plugin>\n        <plugin>\n          maven-surefire-plugin\n          <version>2.22.1</version>\n        </plugin>\n        <plugin>\n          maven-jar-plugin\n          <version>3.0.2</version>\n        </plugin>\n        <plugin>\n          maven-install-plugin\n          <version>2.5.2</version>\n        </plugin>\n        <plugin>\n          maven-deploy-plugin\n          <version>2.8.2</version>\n        </plugin>\n        <!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->\n        <plugin>\n          maven-site-plugin\n          <version>3.7.1</version>\n        </plugin>\n        <plugin>\n          maven-project-info-reports-plugin\n          <version>3.0.0</version>\n        </plugin>\n      </plugins>\n    </pluginManagement>\n  </build>\n</project>\n```\n\n\n\n\n\n\n\n\n\n3.\n\n\n\n```\n@Configuration\npublic class MyRibbonAutoConfiguration {\n    //װRibbonǿȥɸؾ㷨ԼʵipͶ˿滻\n    @Bean\n    public LoadBalancerClient loadBalancerClient(){\n        return new MyLoadBalancerClient();\n    }\n    //ռдMyLoadBalancedעRestTemplate\n    @MyLoadBalanced\n    @Autowired(required = false)\n    private List<RestTemplate> restTemplates = Collections.emptyList();\n    @Bean\n    @ConditionalOnMissingBean\n    public LoadBalancerRequestFactory loadBalancerRequestFactory(\n            LoadBalancerClient loadBalancerClient) {\n        return new LoadBalancerRequestFactory(loadBalancerClient);\n    }\n    //Ǻĵ\n    @Bean\n    public MyLoadBalancerInterceptor myLoadBalancerInterceptor(\n            LoadBalancerClient loadBalancerClient,\n            LoadBalancerRequestFactory requestFactory){\n        return new MyLoadBalancerInterceptor(loadBalancerClient,requestFactory);\n    }\n    //ռRestTemplateﶼһ\n    @Bean\n    public SmartInitializingSingleton smartInitializingSingleton(\n            final MyLoadBalancerInterceptor myLoadBalancerInterceptor){\n        return ()->{\n            for (RestTemplate restTemplate : MyRibbonAutoConfiguration.this.restTemplates) {\n                List<ClientHttpRequestInterceptor> list = new ArrayList<>(\n                        restTemplate.getInterceptors());\n                list.add(myLoadBalancerInterceptor);\n                restTemplate.setInterceptors(list);\n            }\n        };\n    }\n}\n```\n\n\n\n\n\n\n\n\n\n4.MyLoadBalancerClient\n\n\n\n```\npublic class MyLoadBalancerClient implements LoadBalancerClient {\n    @Autowired\n    AbstractEnvironment environment;\n    //1.\n    @Override\n    public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {\n        ServiceInstance server = this.choose(serviceId);\n        return execute(serviceId, server, request);\n    }\t\n    //3.ִHttp\n    @Override\n    public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException {\n        T returnVal = null;\n        try {\n            returnVal = request.apply(serviceInstance);\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        return returnVal;\n    } \n\t//4.һʵipport滻\n    @Override\n    public URI reconstructURI(ServiceInstance instance, URI original) {\n        String host = instance.getHost();\n        int port = instance.getPort();\n        if (host.equals(original.getHost())\n                && port == original.getPort()\n                ) {\n            return original;\n        }\n        try {\n            StringBuilder sb = new StringBuilder();\n            sb.append(\"http\").append(\"://\");\n            if (!Strings.isNullOrEmpty(original.getRawUserInfo())) {\n                sb.append(original.getRawUserInfo()).append(\"@\");\n            }\n            sb.append(host);\n            if (port >= 0) {\n                sb.append(\":\").append(port);\n            }\n            sb.append(original.getRawPath());\n            if (!Strings.isNullOrEmpty(original.getRawQuery())) {\n                sb.append(\"?\").append(original.getRawQuery());\n            }\n            if (!Strings.isNullOrEmpty(original.getRawFragment())) {\n                sb.append(\"#\").append(original.getRawFragment());\n            }\n            URI newURI = new URI(sb.toString());\n            return newURI;\n        }catch (URISyntaxException e){\n            throw new RuntimeException(e);\n        }\n    }\n    //2.ؾ㷨һзipͶ˿ѡõ㷨\n    @Override\n    public ServiceInstance choose(String serviceId) {\n        Server instance = new Server(serviceId,null,\"127.0.0.1\",8080);\n        String sr = environment.getProperty(serviceId+\".ribbon.listOfServers\");\n        if (!StringUtils.isEmpty(sr)){\n            String[] arr = sr.split(\",\",-1);\n            Random selector = new Random();\n            int next = selector.nextInt(arr.length);\n            String a = arr[next];\n            String[] srr = a.split(\":\",-1);\n            instance.setHost(srr[0]);\n            instance.setPort(Integer.parseInt(srr[1]));\n        }\n        return instance;\n    }\n}\n```\n\n\n\n\n\n\n\n\n\n5.߼ʵܼ򵥣http֮ǰִҵ߼\n\n\n\n```\npublic class MyLoadBalancerInterceptor implements ClientHttpRequestInterceptor {\n    private LoadBalancerClient loadBalancerClient;\n    private LoadBalancerRequestFactory requestFactory;\n    public MyLoadBalancerInterceptor(LoadBalancerClient loadBalancerClient,\n                                   LoadBalancerRequestFactory requestFactory) {\n        this.loadBalancerClient = loadBalancerClient;\n        this.requestFactory = requestFactory;\n    }\n    @Override\n    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {\n        final URI originalUri = request.getURI();\n        String serviceName = originalUri.getHost();\n        return this.loadBalancerClient.execute(serviceName,\n                this.requestFactory.createRequest(request, body, execution));\n    }\n}\n```\n\n\n\n\n\n\n\n\n\n6.Լע\n\n\n\n```\n@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\n@Inherited\n@Qualifier\npublic @interface MyLoadBalanced {\n}\n```\n\n\n\n\n\n\n\n\n\n7.һԼServerʵ\n\n\n\n```\npublic class Server implements ServiceInstance {\n    private String serviceId;\n    private String instanceId;\n    private String host;\n    private int port;\n    public Server(String serviceId, String instanceId, String host, int port) {\n        this.serviceId = serviceId;\n        this.instanceId = instanceId;\n        this.host = host;\n        this.port = port;\n    }\n    public Server() {\n    }\n    public void setServiceId(String serviceId) {\n        this.serviceId = serviceId;\n    }\n    public void setInstanceId(String instanceId) {\n        this.instanceId = instanceId;\n    }\n    public void setHost(String host) {\n        this.host = host;\n    }\n    public void setPort(int port) {\n        this.port = port;\n    }\n    @Override\n    public String getInstanceId() {\n        return null;\n    }\n    @Override\n    public String getServiceId() {\n        return null;\n    }\n    @Override\n    public String getHost() {\n        return host;\n    }\n    @Override\n    public int getPort() {\n        return port;\n    }\n    @Override\n    public boolean isSecure() {\n        return false;\n    }\n    @Override\n    public URI getUri() {\n        return null;\n    }\n    @Override\n    public Map<String, String> getMetadata() {\n        return null;\n    }\n    @Override\n    public String getScheme() {\n        return null;\n    }\n}\n```\n\n\n\n\n\n\n\n\n\n8.дspring.factoriesļ\n\n\n\n```\norg.springframework.boot.autoconfigure.EnableAutoConfiguration=\\\n    com.example.config.MyRibbonAutoConfiguration\n```\n\n\n\n\n\n\n\n\n\n9.jarԣԵʱҪļ\n\n\n\n```\n<serverName>.ribbon.listOfServers=127.0.0.1:2223,127.0.0.1:2222\n```\n\n\n\n\n\n\n\n\n\n**3 Դ֤**\n**3.1 @LoadBalanced**\nϽڿεĴ뿴ֻRestTemplateһ@LoadBalance,Ϳʵָؾˣǵ@LoadBalanceһ£עһ@Qualifierע⡣ע޶ĸbeanӦñԶע롣Spring޷жϳĸbeanӦñעʱ@QualifierעbeanԶע룬Ӽ롣\n\n\n\n```\n/**\n * Annotation to mark a RestTemplate or WebClient bean to be configured to use a\n * LoadBalancerClient.\n * @author Spencer Gibb\n */\n@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\n@Inherited\n@Qualifier\npublic @interface LoadBalanced {\n\n}\n```\n\n\n\n\n\n\n\n\n\nעп֪עRestTemplateǣʹøؾͻˣLoadBalancerClientԣɵRestTemplatebeanôһע⣬beanͻLoadBalancerClient\n\n**3.2 LoadBalancerClient**\nôٿLoadBalancerClientĴ룺\n\n\n\n```\npublic interface LoadBalancerClient extends ServiceInstanceChooser {\n\t<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;\n\t<T> T execute(String serviceId, ServiceInstance serviceInstance,\n\t\t\tLoadBalancerRequest<T> request) throws IOException;\n\tURI reconstructURI(ServiceInstance instance, URI original);\n}\npublic interface ServiceInstanceChooser {\n\tServiceInstance choose(String serviceId);\n}\n```\n\n\n\n\n\n\n\n\n\nLoadBalancerClientһӿڣ\n\nServiceInstance choose(String serviceId);ӷϾͿԿǸݴserviceIdӸؾѡһʵʵͨServiceInstanceʾ\nexecuteʹôӸؾѡķʵִݡ\nURI reconstructURI(ServiceInstance instance, URI original);¹һURIģǵڴУͨRestTemplateʱдǷɣͻURIתhost+portͨhost+portʽȥ\n**3.3 Զװ**\nspringboot֮󣬻ͨԶװԶȥ\nspring-cloud-netflix-ribbonjarMETA-INFĿ¼spring.factoriesļҽRibbonAutoConfigurationע롣RibbonAutoConfigurationΪ@AutoConfigureBeforeע⣬ֻLoadBalancerAutoConfigurationࡣLoadBalancerAutoConfigurationУspringὫб@LoadBalanceעεbeanע뵽IOC\n\n\n\n```\n@LoadBalanced\n@Autowired(required = false)\nprivate List<RestTemplate> restTemplates = Collections.emptyList();\n```\n\n\n\n\n\n\n\n\n\nͬʱLoadBalancerAutoConfigurationлΪÿRestTemplateʵLoadBalancerInterceptor\n\nRibbonAutoConfigurationעLoadBalancerClientӿڵʵRibbonLoadBalancerClient\n\n\n\n```\n@Bean\n@ConditionalOnMissingBean(LoadBalancerClient.class)\npublic LoadBalancerClient loadBalancerClient() {\n    return new RibbonLoadBalancerClient(springClientFactory());\n}\n```\n\n\n\n\n\n\n\n\n\n**3.4 **\nԶУrestTemplateʵLoadBalancerInterceptorԣrestTemplatehttpʱͻִintercept\n\ninterceptУrequest.getURI()ȡuriٻȡhostڷhttpʱõķΪhostԣͻõٵþLoadBalancerClientʵexecute\n\nLoadBalancerClientʵΪRibbonLoadBalancerClientյĸؾִУԣҪRibbonLoadBalancerClient߼\n\nȿRibbonLoadBalancerClientеexecute\n\n\n\n```\npublic <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)\n    throws IOException {\n    ILoadBalancer loadBalancer = getLoadBalancer(serviceId);\n    Server server = getServer(loadBalancer, hint);\n    if (server == null) {\n        throw new IllegalStateException(\"No instances available for \" + serviceId);\n    }\n    RibbonServer ribbonServer = new RibbonServer(serviceId, server,\n                                                 isSecure(server, serviceId),\n                                                 serverIntrospector(serviceId).getMetadata(server));\n\n    return execute(serviceId, ribbonServer, request);\n}\n\n@Override\npublic <T> T execute(String serviceId, ServiceInstance serviceInstance,\n                     LoadBalancerRequest<T> request) throws IOException {\n    Server server = null;\n    if (serviceInstance instanceof RibbonServer) {\n        server = ((RibbonServer) serviceInstance).getServer();\n    }\n    if (server == null) {\n        throw new IllegalStateException(\"No instances available for \" + serviceId);\n    }\n\n    RibbonLoadBalancerContext context = this.clientFactory\n        .getLoadBalancerContext(serviceId);\n    RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);\n    try {\n        T returnVal = request.apply(serviceInstance);\n        statsRecorder.recordStats(returnVal);\n        return returnVal;\n    }\n    // catch IOException and rethrow so RestTemplate behaves correctly\n    catch (IOException ex) {\n        statsRecorder.recordStats(ex);\n        throw ex;\n    }\n    catch (Exception ex) {\n        statsRecorder.recordStats(ex);\n        ReflectionUtils.rethrowRuntimeException(ex);\n    }\n    return null;\n}\n```\n\n\n\n\n\n\n\n\n\nΪserviceIdֶδͨgetLoadBalancerȡloadBalancerٸloadBalancerȡservergetServerĴ룺\n\n\n\n```\nprotected Server getServer(ILoadBalancer loadBalancer, Object hint) {\n    if (loadBalancer == null) {\n        return null;\n    }\n    // Use 'default' on a null hint, or just pass it on?\n    return loadBalancer.chooseServer(hint != null ? hint : \"default\");\n}\n```\n\n\n\n\n\n\n\n\n\nloadBalancerΪգֱӷؿգ͵loadBalancerchooseServerȡӦserver\n\nһILoadBalancerһӿڣһϵиؾʵֵķ\n\n\n\n```\npublic interface ILoadBalancer {\n\tpublic void addServers(List<Server> newServers);\n\tpublic Server chooseServer(Object key);\n\tpublic void markServerDown(Server server);\n\t@Deprecated\n\tpublic List<Server> getServerList(boolean availableOnly);\n    public List<Server> getReachableServers();\n\tpublic List<Server> getAllServers();\n}\n```\n\n\n\n\n\n\n\n\n\nЩȽֱۣ׾ܲ³ǸɶģaddServersһserverϣchooseServerѡһservermarkServerDownĳߣgetReachableServersȡõServerϣgetAllServersǻȡеserverϡ\nILoadBalancerкܶʵ֣ǾõĸأRibbonAutoConfigurationעSpringClientFactoryͨRibbonClientConfiguration࿴ڳʼʱ򣬷ZoneAwareLoadBalancerΪؾ\n\n\n\n```\n@Bean\n@ConditionalOnMissingBean\npublic ILoadBalancer ribbonLoadBalancer(IClientConfig config,\n                                        ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,\n                                        IRule rule, IPing ping, ServerListUpdater serverListUpdater) {\n    if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {\n        return this.propertiesFactory.get(ILoadBalancer.class, config, name);\n    }\n    return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,\n                                       serverListFilter, serverListUpdater);\n}\n```\n\n\n\n\n\n\n\n\n\n**3.5 ZoneAwareLoadBalancer**\nZoneAwareLoadBalancerпԿؾzoneйϵġ濴ZoneAwareLoadBalancerеchooseServer\n\n> eurekaṩregionzoneзѷAWS\n> regionԼΪϵķ޵߻ٻ߱ȵȣûоСơĿкregion\n> zoneԼΪregionڵľ˵regionΪȻ󱱾Ϳڴregion֮»ֳzone1,zone2zone\n\n\n\n```\n@Override\npublic Server chooseServer(Object key) {\n    //ֻеؾάʵZoneĸ1ʱŻִѡ\n    //ʹøʵ\n    if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {\n        logger.debug(\"Zone aware logic disabled or there is only one zone\");\n        return super.chooseServer(key);\n    }\n    Server server = null;\n    try {\n        LoadBalancerStats lbStats = getLoadBalancerStats();\n        //ΪǰؾеZoneֱ𴴽գzoneSnapshotУЩеں㷨\n        Map<String, ZoneSnapshot> zoneSnapshot = ZoneAvoidanceRule.createSnapshot(lbStats);\n        logger.debug(\"Zone snapshots: {}\", zoneSnapshot);\n        if (triggeringLoad == null) {\n            triggeringLoad = DynamicPropertyFactory.getInstance().getDoubleProperty(\n                \"ZoneAwareNIWSDiscoveryLoadBalancer.\" + this.getName() + \".triggeringLoadPerServerThreshold\", 0.2d);\n        }\n\n        if (triggeringBlackoutPercentage == null) {\n            triggeringBlackoutPercentage = DynamicPropertyFactory.getInstance().getDoubleProperty(\n                \"ZoneAwareNIWSDiscoveryLoadBalancer.\" + this.getName() + \".avoidZoneWithBlackoutPercetage\", 0.99999d);\n        }\n        //ÿZoneļϣgetAvailableZonesͨzoneSnapshotʵֿѡ\n        Set<String> availableZones = ZoneAvoidanceRule.getAvailableZones(zoneSnapshot, triggeringLoad.get(), triggeringBlackoutPercentage.get());\n        logger.debug(\"Available zones: {}\", availableZones);\n        if (availableZones != null &&  availableZones.size() < zoneSnapshot.keySet().size()) {\n            //ѡһZone\n            String zone = ZoneAvoidanceRule.randomChooseZone(zoneSnapshot, availableZones);\n            logger.debug(\"Zone chosen: {}\", zone);\n            if (zone != null) {\n                //öӦĸؾ\n                BaseLoadBalancer zoneLoadBalancer = getLoadBalancer(zone);\n                //ѡķʵ\n                //chooseServerнʹIRuleӿڵchooseѡʵIRuleӿڵʵֻʵZoneAvoidanceRuleѡķʵ\n                server = zoneLoadBalancer.chooseServer(key);\n            }\n        }\n    } catch (Exception e) {\n        logger.error(\"Error choosing server using zone aware logic for load balancer={}\", name, e);\n    }\n    if (server != null) {\n        return server;\n    } else {\n        logger.debug(\"Zone avoidance logic is not invoked.\");\n        return super.chooseServer(key);\n    }\n}\n```\n\n\n\n\n\n\n\n\n\nserverzoneͿѡʵһServer\n\nļ\n\n1.  setServerListForZones : Zoneз񻮷\n2.  chooseServerҲҪzoneйصļ㣬ȻĬϵZoneֻһֱǵõĸchooseServerkey)\n3.  getLoadBalancer(String zone) zoneȥȡLoadBalancer\n4.  setRule(IRule rule) Ϊÿؾù\n    ԿʵҪZoneһЩദǽԭͬһķʵٸݵл֡ҲΪܹӦõġ\n\n**3.5.1 DynamicServerListLoadBalancer**\n\n> ZoneAwareLoadBalancerĸ\n\nఴ˵Ƕ̬طбʹõġмȽҪķ\n\n1.  updateListOfServers:б±ػķб\n2.  enableAndInitLearnNewServersFeature:б¶ʱ\n3.  :@Monitor\n\nDynamicServerListLoadBalancerĺľǻȡбEurekaĬͨDomainExtractingServerListȡorg.springframework.cloud.netflix.ribbon.eureka.EurekaRibbonClientConfiguration#ribbonServerListûмEurekaʱ\n\n**3.5.2 BaseLoadBalancer**\n\n> DynamicServerListLoadBalancerĸ\n\nĬֵ\n\n*   ĬϵĸؾRoundRobinRule\n*   ĬϵPingSerialPingStrategy\n*   зʵallServerList\n*   ߷ʵupServerList\n    ![SpringCloudϵСRibbonԴ-Դ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/b2db0bc66c3045288a762907ff72e01883bf20.jpg \"SpringCloudϵСRibbonԴ-Դ\")๹нӦĸؾpingԣpingȴݹ\n\n**pingЩʲô**\n\nPingTaskΪһ߳񣬾ǶڼǷ񶼴ServerListUpdater»ƲͻribbonԼάһ׷ƣҪΪ˽ͷʧܵĸʡĬʹeurekaʱpingʹõNIWSDiscoveryPingɷ񱣻⡣eureka  ServerListUpdaterˢ·б![SpringCloudϵСRibbonԴ-Դ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/27db4c518aa7c100d427761832a9fa19fa8f04.jpg \"SpringCloudϵСRibbonԴ-Դ\")иõĶʱ˳ķҾԼдʱҲʹá\n\nͬһʱִʱ䳬˶ʱڣôһʱһʱûִʱȡ![SpringCloudϵСRibbonԴ-Դ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/f2d669b11f17530d2e62603d45949e934a5931.jpg \"SpringCloudϵСRibbonԴ-Դ\")Ҳ˺ܶƣзʵһµĶʱʹõǶǸallServersֻܶд![SpringCloudϵСRibbonԴ-Դ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/711c81e67b2c1cd3fb91005f53ea1e1352ad9a.png \"SpringCloudϵСRibbonԴ-Դ\")ڷping󣬽ͨķnewUpListУͨдupServerListס\n\nֻһдҲܶ![SpringCloudϵСRibbonԴ-Դ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/047543857174aa1130b0747d1d149ffd6d5632.jpg \"SpringCloudϵСRibbonԴ-Դ\")pingڼйڶдԭʹá\n\nҪ̾ǣ\n\n1.  ȡȫʵб\n2.  ʵǷpingServers\n3.  ״̬ıķchangedServers\n4.  ߵķnewUpList\n5.  newUpListֵupServerList ߷ʵб\n\npingServersǼ\n\n**BaseLoadBalancerܼ**\n\n1.  allServerListupServerListĶд\n2.  ṩallServerListupServerListɾĹ\n3.  ṩPingTaskpingĶʱ񣩣Pingerpingִ\n4.  ڸؾѡrule.choose(key)\n5.  ṩĬϵpingSerialPingStrategy\n    **3.6 LoadBalancerRequest**\n    ͨZoneAwareLoadBalancerѡServerٰ֮װRibbonServer֮ǰصserverǸöеһֶΣ֮⣬зserviceIdǷҪʹhttpsϢͨLoadBalancerRequestapplyserver󣬴Ӷʵ˸ؾ⡣\n\napplyĶ壺\n\n\n\n```\npublic interface LoadBalancerRequest<T> {\n    T apply(ServiceInstance instance) throws Exception;\n}\n```\n\n\n\n\n\n\n\n\n\nʱribbonServer󣬱ServiceInstance͵ĶнաServiceInstanceһӿڣ˷ϵͳУÿʵҪṩϢserviceIdhostportȡ\n\nLoadBalancerRequestһӿڣջͨʵapplyȥִУʵLoadBalancerInterceptorеRibbonLoadBalancerClientexecuteʱһ࣬ͨ鿴LoadBalancerInterceptorĴ뿴\n\nLoadBalancerRequestʱ򣬾дapplyapplyУ½һServiceRequestWrapperڲ࣬УдgetURIgetURIloadBalancerreconstructURIuri\n\nѾԴ֪RibbonʵָؾˣRestTemplateע⣬ͻLoadBalancerClientĶҲRibbonLoadBalancerClientͬʱ\nLoadBalancerAutoConfigurationãһLoadBalancerInterceptorõrestTemplateЩrestTemplateLoadBalancerInterceptor\n\nͨrestTemplateʱͻᾭУͻRibbonLoadBalancerClientеķȡݷͨؾⷽȡʵȻȥʵ\n\n**3.7 ȡб**\n˵Щζиؾģǻи⣬ʵǴEureka Serverϻȡģʵбλȡأô֤ʵбеʵǿõأ\n\nRibbonLoadBalancerClientѡʵʱͨILoadBalancerʵݸؾ㷨ѡʵģҲZoneAwareLoadBalancerchooseServerе߼Ǿ鿴ZoneAwareLoadBalancerļ̳йϵԿͼʾ![SpringCloudϵСRibbonԴ-Դ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/425d8ff567d9ac6171f6036dab1726a7da04a3.jpg \"SpringCloudϵСRibbonԴ-Դ\")ԿILoadBalancerӿڣAbstractLoadBalancer̳ӿڣBaseLoadBalancer̳AbstractLoadBalancer࣬\nDynamicServerListLoadBalancer̳BaseLoadBalancerZoneAwareLoadBalancer̳DynamicServerListLoadBalancer\n\nILoadBalancerӿڵĴѾˣڿAbstractLoadBalancerĴ룺\n\n\n\n```\npublic abstract class AbstractLoadBalancer implements ILoadBalancer {\n    public enum ServerGroup{\n        ALL,\n        STATUS_UP,\n        STATUS_NOT_UP        \n    }    \n    /**\n     * delegate to {@link #chooseServer(Object)} with parameter null.\n     */\n    public Server chooseServer() {\n    \treturn chooseServer(null);\n    }\n    /**\n     * List of servers that this Loadbalancer knows about\n     * \n     * @param serverGroup Servers grouped by status, e.g., {@link ServerGroup#STATUS_UP}\n     */\n    public abstract List<Server> getServerList(ServerGroup serverGroup);\n    /**\n     * Obtain LoadBalancer related Statistics\n     */\n    public abstract LoadBalancerStats getLoadBalancerStats();    \n}\n```\n\n\n\n\n\n\n\n\n\nһ࣬һö٣󷽷chooseServer\n\nٿBaseLoadBalancer࣬BaseLoadBalancerǸؾһʵ࣬Կlist\n\n\n\n```\n@Monitor(name = PREFIX + \"AllServerList\", type = DataSourceType.INFORMATIONAL)\nprotected volatile List<Server> allServerList = Collections\n    .synchronizedList(new ArrayList<Server>());\n@Monitor(name = PREFIX + \"UpServerList\", type = DataSourceType.INFORMATIONAL)\nprotected volatile List<Server> upServerList = Collections\n    .synchronizedList(new ArrayList<Server>());\n```\n\n\n\n\n\n\n\n\n\nϿάзʵбά״̬Ϊupʵб\nһԿBaseLoadBalancerʵֵILoadBalancerӿеķȡõķбͻupServerListأȡеķбͻallServerListء\n\n\n\n```\n@Override\npublic List<Server> getReachableServers() {\n    return Collections.unmodifiableList(upServerList);\n}\n@Override\npublic List<Server> getAllServers() {\n    return Collections.unmodifiableList(allServerList);\n}\n```\n\n\n\n\n\n\n\n\n\nٿDynamicServerListLoadBalancerࡣͷϵעͿ֪Զ̬ĻȡбfilterԷбйˡ\n\nDynamicServerListLoadBalancerУܿһServerList͵serverListImplֶΣServerListһӿڣ\n\n\n\n```\npublic interface ServerList<T extends Server> {\n    public List<T> getInitialListOfServers();\n    /**\n     * Return updated list of servers. This is called say every 30 secs\n     * (configurable) by the Loadbalancer's Ping cycle\n     * \n     */\n    public List<T> getUpdatedListOfServers();   \n}\n```\n\n\n\n\n\n\n\n\n\ngetInitialListOfServersǻȡʼķб\ngetUpdatedListOfServersǻȡµķб\nServerListжʵ࣬õĸأ\nEurekaRibbonClientConfigurationҵRibbonEurekaϵԶ࣬ĿǰûEurekaͨļãԻConfigurationBasedServerListࡣ\n\n# ο\nhttps://lijunyi.xyz/docs/SpringCloud/SpringCloud.html#_2-2-x-%E5%88%86%E6%94%AF\nhttps://mp.weixin.qq.com/s/2jeovmj77O9Ux96v3A0NtA\nhttps://juejin.cn/post/6931922457741770760\nhttps://github.com/D2C-Cai/herring\nhttp://c.biancheng.net/springcloud\nhttps://github.com/macrozheng/springcloud-learning"
  },
  {
    "path": "docs/Spring全家桶/SpringMVC/SpringMVC中的国际化功能.md",
    "content": "#### ʻ\n\n* * *\n\nڿӦóʱ򣬾ֶ֧Եֶ֧ԵĹ֮ܳΪʻӢinternationalizationдΪi18nΪĸiĩĸnм18ĸ\n\nضıػܣӢlocalizationдΪL10nػָݵڵʾȡ\n\nҲаߺϳΪȫ򻯣ӢglobalizationдΪg11n\n\nJavaУֶ֧Ժͱػͨ`MessageFormat``Locale`ʵֵģ\n\n\nWebӦóҪʵֹʻܣҪȾViewʱҪѸԵԴļͬûͬһҳʱʾԾǲͬġ\n\nSpring MVCӦóʵֹʻ\n\n### ȡLocale\n\nʵֹʻĵһǻȡû`Locale`WebӦóУHTTP淶涨Я`Accept-Language`ͷָʾû趨˳磺\n\n```  \nAccept-Language: zh-CN,zh;q=0.8,en;q=0.2  \n  \n```\n\nHTTPͷʾѡģѡģѡӢġ`q`ʾȨأǿɻһȼбתΪJava`Locale`û`Locale`ֻͨȨߵ`Locale`\n\nSpring MVCͨ`LocaleResolver`Զ`HttpServletRequest`лȡ`Locale`ж`LocaleResolver`ʵ࣬õ`CookieLocaleResolver`\n\n```  \n@Primary  \n@Bean  \nLocaleResolver createLocaleResolver() {  \n    var clr = new CookieLocaleResolver();    clr.setDefaultLocale(Locale.ENGLISH);    clr.setDefaultTimeZone(TimeZone.getDefault());    return clr;}  \n  \n```\n\n`CookieLocaleResolver``HttpServletRequest`лȡ`Locale`ʱȸһضCookieжǷָ`Locale`ûУʹHTTPͷȡûУͷĬϵ`Locale`\n\nûһηվʱ`CookieLocaleResolver`ֻܴHTTPͷȡ`Locale`ʹĬԡͨվҲûԼѡԣʱ`CookieLocaleResolver`ͻûѡԴŵCookieУһηʱͻ᷵ûϴѡԶĬԡ\n\n### ȡԴļ\n\nڶǰдģеַԴļķʽ洢ⲿڶԣļΪ`messages`ôԴļ밴·ʽclasspathУ\n\n*   ĬԣļΪ`messages.properties`\n*   ģLocale`zh_CN`ļΪ`messages_zh_CN.properties`\n*   ģLocale`ja_JP`ļΪ`messages_ja_JP.properties`\n*   ԡ\n\nÿԴļͬkey磬ĬӢģļ`messages.properties`£\n\n```  \nlanguage.select=Language  \nhome=Home  \nsignin=Sign In  \ncopyright=Copyright?{0,number,#}  \n  \n```\n\nļ`messages_zh_CN.properties`£\n\n```  \nlanguage.select=  \nhome=ҳ  \nsignin=¼  \ncopyright=Ȩ?{0,number,#}  \n  \n```\n\n### MessageSource\n\nǴһSpringṩ`MessageSource`ʵԶȡе`.properties`ļṩһͳһӿʵ֡롱\n\n```  \n// code, arguments, locale:  \nString text = messageSource.getMessage(\"signin\", null, locale);  \n  \n```\n\nУ`signin``.properties`ļжkeyڶ`Object[]`ΪʽʱĲһǻȡû`Locale`ʵ\n\n`MessageSource`£\n\n```  \n@Bean(\"i18n\")  \nMessageSource createMessageSource() {  \n    var messageSource = new ResourceBundleMessageSource();    // ָļUTF-8:  \n    messageSource.setDefaultEncoding(\"UTF-8\");    // ָļ:  \n    messageSource.setBasename(\"messages\");    return messageSource;}  \n  \n```\n\nע⵽`ResourceBundleMessageSource`ԶļԶԵԴļ\n\nע⵽Springᴴֻһ`MessageSource`ʵԼ`MessageSource`רŸҳʻʹõģΪ`i18n``MessageSource`ʵͻ\n\n### ʵֶ\n\nҪViewʹ`MessageSource``Locale`ԣͨдһ`MvcInterceptor`Դע뵽`ModelAndView`У\n\n```  \n@Component  \npublic class MvcInterceptor implements HandlerInterceptor {  \n    @Autowired    LocaleResolver localeResolver;  \n    // עעMessageSourcei18n:  \n    @Autowired    @Qualifier(\"i18n\")    MessageSource messageSource;  \n    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {        if (modelAndView != null) {            // ûLocale:  \n            Locale locale = localeResolver.resolveLocale(request);            // Model:  \n            modelAndView.addObject(\"__messageSource__\", messageSource);            modelAndView.addObject(\"__locale__\", locale);        }    }}  \n  \n```\n\nҪ`WebMvcConfigurer`ע`MvcInterceptor`ڣͿViewе`MessageSource.getMessage()`ʵֶԣ\n\n```  \n{{ __messageSource__.getMessage('signin', null, __locale__) }}  \n  \n```\n\nдȻУʽ̫ˡʹViewʱҪÿضView涨ƹʻPebbleУǿԷװһʻƾ»`_`һ´`ViewResolver`Ĵ룺\n\n```  \n@Bean  \nViewResolver createViewResolver(@Autowired ServletContext servletContext, @Autowired @Qualifier(\"i18n\") MessageSource messageSource) {  \n    var engine = new PebbleEngine.Builder()            .autoEscaping(true)            .cacheActive(false)            .loader(new Servlet5Loader(servletContext))            // չ:  \n            .extension(createExtension(messageSource))            .build();    var viewResolver = new PebbleViewResolver();    viewResolver.setPrefix(\"/WEB-INF/templates/\");    viewResolver.setSuffix(\"\");    viewResolver.setPebbleEngine(engine);    return viewResolver;}  \n  \nprivate Extension createExtension(MessageSource messageSource) {  \n    return new AbstractExtension() {        @Override        public Map<String, Function> getFunctions() {            return Map.of(\"_\", new Function() {                public Object execute(Map<String, Object> args, PebbleTemplate self, EvaluationContext context, int lineNumber) {                    String key = (String) args.get(\"0\");                    List<Object> arguments = this.extractArguments(args);                    Locale locale = (Locale) context.getVariable(\"__locale__\");                    return messageSource.getMessage(key, arguments.toArray(), \"???\" + key + \"???\", locale);                }                private List<Object> extractArguments(Map<String, Object> args) {                    int i = 1;                    List<Object> arguments = new ArrayList<>();                    while (args.containsKey(String.valueOf(i))) {                        Object param = args.get(String.valueOf(i));                        arguments.add(param);                        i++;                    }                    return arguments;                }                public List<String> getArgumentNames() {                    return null;                }            });        }    };}  \n  \n```\n\nǿ԰ѶҳдΪ\n\n```  \n{{ _('signin') }}  \n  \n```\n\nǴĶԣҪѲȥ\n\n```  \n<h5>{{ _('copyright', 2020) }}</h5>  \n  \n```\n\nʹViewʱҲӦӿʵָ﷨\n\n### лLocale\n\nҪûֶл`Locale`дһ`LocaleController`ʵָùܣ\n\n```  \n@Controller  \npublic class LocaleController {  \n    final Logger logger = LoggerFactory.getLogger(getClass());  \n    @Autowired    LocaleResolver localeResolver;  \n    @GetMapping(\"/locale/{lo}\")    public String setLocale(@PathVariable(\"lo\") String lo, HttpServletRequest request, HttpServletResponse response) {        // ݴloLocaleʵ:  \n        Locale locale = null;        int pos = lo.indexOf('_');        if (pos > 0) {            String lang = lo.substring(0, pos);            String country = lo.substring(pos + 1);            locale = new Locale(lang, country);        } else {            locale = new Locale(lo);        }        // 趨Locale:  \n        localeResolver.setLocale(request, response, locale);        logger.info(\"locale is set to {}.\", locale);        // ˢҳ:  \n        String referer = request.getHeader(\"Referer\");        return \"redirect:\" + (referer == null ? \"/\" : referer);    }}  \n  \n```\n\nҳУͨϽǸûṩһѡбЧ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230416194544.png)\n\nлģ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230416194602.png)\n\n### С\n\n֧ҪHTTPнûLocaleȻԲͬLocaleʾͬԣ\n\nSpring MVCӦóͨ`MessageSource``LocaleResolver`Viewʵֹʻ"
  },
  {
    "path": "docs/Spring全家桶/SpringMVC/SpringMVC中的常用功能.md",
    "content": "\n\n\n\n# Spring MVC ʹ@Controllerעⶨһ\n\n\n\n2018-07-26 14:02 \n\n\n\n\n\n\n\n<section>\n\n> [Original] The `@Controller` annotation indicates that a particular class serves the role of a controller. Spring does not require you to extend any controller base class or reference the Servlet API. However, you can still reference Servlet-specific features if you need to.\n\n`@Controller`עһΪĽɫڵġSpringҪȥ̳κο࣬ҲҪȥʵServletAPIȻҪĻҲȥʹκServletصԺʩ\n\n> [Original] The `@Controller` annotation acts as a stereotype for the annotated class, indicating its role. The dispatcher scans such annotated classes for mapped methods and detects `@RequestMapping` annotations (see the next section).\n\n`@Controller`עΪǱעԭͣstereotypeеĽɫ`DispatcherServlet`ɨע`@Controller`࣬ͨ`@RequestMapping`עõķһСڣ\n\n> [Original] You can define annotated controller beans explicitly, using a standard Spring bean definition in the dispatchers context. However, the `@Controller` stereotype also allows for autodetection, aligned with Spring general support for detecting component classes in the classpath and auto-registering bean definitions for them.\n\nȻҲԲʹ`@Controller`עʽȥ屻עbeanͨ׼Spring beanĶ巽ʽdispatherü`@Controller`ԭǿԱԶģSpring֧classpath·Զ⣬ԼѶbeanԶעᡣ\n\n> [Original] To enable autodetection of such annotated controllers, you add component scanning to your configuration. Use the spring-context schema as shown in the following XML snippet:\n\nҪмɨôܶעԶ⡣ʹXMLʾspring-context schema\n\n```\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<beans xmlns=\"http://www.springframework.org/schema/beans\"\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xmlns:p=\"http://www.springframework.org/schema/p\"\n    xmlns:context=\"http://www.springframework.org/schema/context\"\n    xsi:schemaLocation=\"\n        http://www.springframework.org/schema/beans\n        http://www.springframework.org/schema/beans/spring-beans.xsd\n        http://www.springframework.org/schema/context\n        http://www.springframework.org/schema/context/spring-context.xsd\">\n\n    <context:component-scan base-package=\"org.springframework.samples.petclinic.web\"/>\n\n    <!-- ... -->\n\n</beans>\n```\n\n</section>\n\n\n\n\n\n\n\n# Spring MVC ʹ@RequestMappingעӳ·\n\n\n\n2018-07-26 15:29 \n\n\n\n\n\n\n\nʹ`@RequestMapping`עURL`/appointments`ȣӳ䵽ϻĳضĴϡһ˵༶ע⸺һضĳģʽ·ӳ䵽һϣͬʱͨעϸӳ䣬ضHTTP󷽷GETPOSTȣHTTPǷЯضӳ䵽ƥķϡ\n\n<section>\n\nδʾPetcareչʾSpring MVCڿʹ`@RequestMapping`ע⣺\n\n```\n@Controller\n@RequestMapping(\"/appointments\")\npublic class AppointmentsController {\n\n    private final AppointmentBook appointmentBook;\n\n    @Autowired\n    public AppointmentsController(AppointmentBook appointmentBook) {\n        this.appointmentBook = appointmentBook;\n    }\n\n    @RequestMapping(method = RequestMethod.GET)\n    public Map<String, Appointment> get() {\n        return appointmentBook.getAppointmentsForToday();\n    }\n\n    @RequestMapping(path = \"/{day}\", method = RequestMethod.GET)\n    public Map<String, Appointment> getForDay(@PathVariable @DateTimeFormat(iso=ISO.DATE) Date day, Model model) {\n        return appointmentBook.getAppointmentsForDay(day);\n    }\n\n    @RequestMapping(path = \"/new\", method = RequestMethod.GET)\n    public AppointmentForm getNewForm() {\n        return new AppointmentForm();\n    }\n\n    @RequestMapping(method = RequestMethod.POST)\n    public String add(@Valid AppointmentForm appointment, BindingResult result) {\n        if (result.hasErrors()) {\n            return \"appointments/new\";\n        }\n        appointmentBook.addAppointment(appointment);\n        return \"redirect:/appointments\";\n    }\n}\n\n```\n\nʾУطʹõ`@RequestMapping`ע⡣һʹõ༶ģָʾ`/appointments`ͷ·ᱻӳ䵽¡`get()`ϵ`@RequestMapping`ע·˽һϸGETһ·Ϊ`/appointments`HTTPΪGET󣬽ս뵽`add()`ҲƵϸ`getNewForm()`ͬʱעܹܵHTTP·£һ·Ϊ`appointments/new`HTTPΪGET󽫻ᱻ\n\n`getForDay()`չʾʹ`@RequestMapping`עһɣURIģ塣URIģ壬Сڣ\n\n༶`@RequestMapping`עⲢǱġõĻе·Ǿ··µĴʾPetClinicչʾһжĿ\n\n```\n@Controller\npublic class ClinicController {\n\n    private final Clinic clinic;\n\n    @Autowired\n    public ClinicController(Clinic clinic) {\n        this.clinic = clinic;\n    }\n\n    @RequestMapping(\"/\")\n    public void welcomeHandler() {\n    }\n\n    @RequestMapping(\"/vets\")\n    public ModelMap vetsHandler() {\n        return new ModelMap(this.clinic.getVets());\n    }\n}\n\n```\n\nϴûָGETPUT/POST`@RequestMapping`עĬϻӳеHTTP󷽷ĳ󷽷עָ֮`@RequestMapping(method=GET)`СΧ\n\n## @Controller棨AOP\n\nʱϣʱʹAOPװο統ֱڿʹ`@Transactional`עʱ£Ƽʹ༶ڿʹãĴʽһǴĬʵһЩӿڣýӿֲ֧Spring ContextĻص`InitializingBean`, `*Aware`ȽӿڣҪ༶Ĵͱֶˡ磬ԭļ`<tx:annotation-driven/>`ҪʽΪ`<tx:annotation-driven proxy-target-/>`\n\n## Spring MVC 3.1֧@RequestMappingһЩ\n\n> They are recommended for use and even required to take advantage of new features in Spring MVC 3.1 and going forward.\n\nSpring 3.1һǿ`@RequestMapping`ֱ`RequestMappingHandlerMapping``RequestMappingHandlerAdapter`ƼһáвSpring MVC 3.1֮ԣעǱġMVCռMVC Java÷ʽ£༰Ĭǿġʹ÷ʽԱֶòʹáСڽҪһ£֮ǰһЩҪ仯\n\nSpring 3.1֮ǰܻͬĽ׶ηֱ༶ͷӳ䡪ȣ`DefaultAnnotationHanlderMapping`༶ѡһȻͨ`AnnotationMethodHandlerAdapter`λҪõķ\n\n> [Original] With the new support classes in Spring 3.1, the `RequestMappingHandlerMapping` is the only place where a decision is made about which method should process the request. Think of controller methods as a collection of unique endpoints with mappings for each method derived from type and method-level `@RequestMapping` information.\n\nSpring 3.1࣬`RequestMappingHandlerMapping`ΪʵʷΨһһط԰ѿеһϵдһϵжķڵ㣬ÿ༶ͷ`@RequestMapping`עлȡ㹻1·ӳϢ\n\n> [Original] This enables some new possibilities. For once a `HandlerInterceptor` or a `HandlerExceptionResolver` can now expect the Object-based handler to be a `HandlerMethod`, which allows them to examine the exact method, its parameters and associated annotations. The processing for a URL no longer needs to be split across different controllers.\n\nµĴʽµĿԡ֮ǰ`HandlerInterceptor``HandlerExceptionResolver`ڿȷõ϶һ`HandlerMethod`ܹͣȷ˽ϢĲӦϵעȡڲһURLĴҲҪָͬĿȥִˡ\n\n> [Original] There are also several things no longer possible: [Original] _Select a controller first with a `SimpleUrlHandlerMapping` or `BeanNameUrlHandlerMapping` and then narrow the method based on `@RequestMapping` annotations. [Original] _Rely on method names as a fall-back mechanism to disambiguate between two `@RequestMapping` methods that dont have an explicit path mapping URL path but otherwise match equally, e.g. by HTTP method. In the new support classes `@RequestMapping` methods have to be mapped uniquely. [Original] * Have a single default method (without an explicit path mapping) with which requests are processed if no other controller method matches more concretely. In the new support classes if a matching method is not found a 404 error is raised.\n\nͬʱҲһЩ仯Щûôˣ\n\n*   ͨ`SimpleUrlHandlerMapping``BeanNameUrlHandlerMapping`õĿȻͨ`@RequestMapping`עõϢλĴ\n*   Ϊѡı׼˵ע`@RequestMapping`ķ˷ӵȫͬURLӳHTTP󷽷°汾£`@RequestMapping`עķΨһӳ䣻\n*   һĬϷû·ӳ䣩·޷ӳ䵽¸ȷķȥʱΪṩĬϴ°汾У޷ΪһҵʵĴôһ404󽫱׳\n\n> [Original] The above features are still supported with the existing support classes. However to take advantage of new Spring MVC 3.1 features youll need to use the new support classes.\n\nʹԭ࣬ϵĹܻǿǣҪSpring MVC 3.1汾ķԣҪȥʹµࡣ\n\n> [Original] ## URI Template Patterns\n\n## URIģ\n\n> [Original] URI templates can be used for convenient access to selected parts of a URL in a `@RequestMapping` method.\n\nURIģΪٷ`@RequestMapping`ָURLһضĲṩܴı\n\n> [Original] A URI Template is a URI-like string, containing one or more variable names. When you substitute values for these variables, the template becomes a URI. The proposed RFC for URI Templates defines how a URI is parameterized. For example, the URI Template `http://www.example.com/users/{userId}` contains the variable userId. Assigning the value fred to the variable yields `http://www.example.com/users/fred`.\n\nURIģһURIַֻаһıʹʵʵֵȥЩʱģ˻һURIURIģRFCжһURIνвġ˵һURIģ`http://www.example.com/users/{userId}`Ͱһ_userId_ֵ_fred_ͱһURI`http://www.example.com/users/fred`\n\n> [Original] In Spring MVC you can use the `@PathVariable` annotation on a method argument to bind it to the value of a URI template variable:\n\nSpring MVCڷʹ`@PathVariable`ע⣬URIģеĲ\n\n```\n@RequestMapping(path=\"/owners/{ownerId}\", method=RequestMethod.GET)\npublic String findOwner(@PathVariable String ownerId, Model model) {\n    Owner owner = ownerService.findOwner(ownerId);\n    model.addAttribute(\"owner\", owner);\n    return \"displayOwner\";\n}\n\n```\n\n> [Original] The URI Template \"`/owners/{ownerId}`\" specifies the variable name `ownerId`. When the controller handles this request, the value of `ownerId` is set to the value found in the appropriate part of the URI. For example, when a request comes in for `/owners/fred`, the value of `ownerId` is `fred`.\n\nURIģ\"`/owners/{ownerId}`\"ָһΪ`ownerId`ʱ`ownerId`ֵͻᱻURIģжӦֵֵ䡣˵URI`/owners/fred`ʱ`ownerId`ֵ`fred`. `\n\n> Ϊ˴`@PathVariables`ע⣬Spring MVCͨҵURIģӦıעֱ\n>\n> ```\n> @RequestMapping(path=\"/owners/{ownerId}}\", method=RequestMethod.GET)\n> public String findOwner(@PathVariable(\"ownerId\") String theOwner, Model model) {\n>     // ķ롭\n> }\n> \n> ```\n>\n> ߣURIģеı뷽ĲͬģԲָһΡֻҪڱʱdebugϢSpring MVCͿԶƥURLģ뷽ͬı\n>\n> ```\n> @RequestMapping(path=\"/owners/{ownerId}\", method=RequestMethod.GET)\n> public String findOwner(@PathVariable String ownerId, Model model) {\n>     // ķ롭\n> }\n> \n> ```\n>\n> [Original] A method can have any number of `@PathVariable` annotations:\n\nһӵ`@PathVariable`ע⣺\n\n```\n@RequestMapping(path=\"/owners/{ownerId}/pets/{petId}\", method=RequestMethod.GET)\npublic String findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {\n    Owner owner = ownerService.findOwner(ownerId);\n    Pet pet = owner.getPet(petId);\n    model.addAttribute(\"pet\", pet);\n    return \"displayPet\";\n}\n\n```\n\n> [Original] When a `@PathVariable` annotation is used on a `Map<String, String>` argument, the map is populated with all URI template variables.\n\n`@PathVariable`עⱻӦ`Map<String, String>`͵ĲʱܻʹURIģmap\n\n> [Original] A URI template can be assembled from type and path level _@RequestMapping_ annotations. As a result the `findPet()` method can be invoked with a URL such as `/owners/42/pets/21`.\n\nURIģԴ༶ͷ _@RequestMapping_ עȡݡˣ`findPet()`Ա`/owners/42/pets/21`URL·ɲõ\n\n```\n_@Controller_\n@RequestMapping(\"/owners/{ownerId}\")\npublic class RelativePathUriTemplateController {\n\n    @RequestMapping(\"/pets/{petId}\")\n    public void findPet(_@PathVariable_ String ownerId, _@PathVariable_ String petId, Model model) {\n        // ʵ\n    }\n\n}\n\n```\n\n> [Original] A `@PathVariable` argument can be of _any simple type_ such as int, long, Date, etc. Spring automatically converts to the appropriate type or throws a `TypeMismatchException` if it fails to do so. You can also register support for parsing additional data types. See [the section called \"Method Parameters And Type Conversion\"](https://linesh.gitbooks.io/spring-mvc-documentation-linesh-translation/content/publish/21-3/mvc.html#mvc-ann-typeconversion) and [the section called \"Customizing WebDataBinder initialization\"](https://linesh.gitbooks.io/spring-mvc-documentation-linesh-translation/content/publish/21-3/mvc.html#mvc-ann-webdatabinder).\n\n`@PathVariable`ԱӦ __ ĲϣintlongDate͡SpringԶذѲתɺʵͣתʧܣ׳һ`TypeMismatchException`Ҫ͵תҲעԼࡣҪϸϢԲο[תһ](http://docs.spring.io/spring-framework/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-typeconversion)[WebDataBinderʼ̡һ](http://docs.spring.io/spring-framework/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-webdatabinder)\n\n## ʽURIģ\n\n> [Original] Sometimes you need more precision in defining URI template variables. Consider the URL `\"/spring-web/spring-web-3.0.5.jar\"`. How do you break it down into multiple parts?\n\nʱҪ׼ȷһURIģı˵URL`\"/spring-web/spring-web-3.0.5.jar`ҪôֽɼĲأ\n\n> [Original] The `@RequestMapping` annotation supports the use of regular expressions in URI template variables. The syntax is `{varName:regex}` where the first part defines the variable name and the second - the regular expression.For example:\n\n`@RequestMapping`ע֧URIģʹʽ﷨`{varName:regex}`еһֶ˱ڶ־ҪӦõʽĴ\n\n```\n@RequestMapping(\"/spring-web/{symbolicName:[a-z-]+}-{version:\\\\d\\\\.\\\\d\\\\.\\\\d}{extension:\\\\.[a-z]+}\")\n    public void handle(@PathVariable String version, @PathVariable String extension) {\n        // 벿ʡ...\n    }\n}\n\n```\n\n## Path Patterns÷׵ζ\n\n> [Original] In addition to URI templates, the `@RequestMapping` annotation also supports Ant-style path patterns (for example, `/myPath/*.do`). A combination of URI template variables and Ant-style globs is also supported (e.g. `/owners/*/pets/{petId}`).\n\nURIģ⣬`@RequestMapping`ע⻹֧Ant·ģʽ`/myPath/*.do`ȣˣ԰URIģAntglobʹã`/owners/*/pets/{petId}`÷ȣ\n\n## ·ʽƥ(Path Pattern Comparison)\n\n> [Original] When a URL matches multiple patterns, a sort is used to find the most specific match.\n\nһURLͬʱƥģ壨patternʱǽҪһ㷨ƥһ\n\n> [Original] A pattern with a lower count of URI variables and wild cards is considered more specific. For example `/hotels/{hotel}/*` has 1 URI variable and 1 wild card and is considered more specific than `/hotels/{hotel}/**` which as 1 URI variable and 2 wild cards.\n\nURIģĿͨܺٵǸ·ģ׼ȷٸӣ`/hotels/{hotel}/*`·ӵһURIһͨ`/hotels/{hotel}/**`·ӵһURIͨˣΪǰǸ׼ȷ·ģ塣\n\n> [Original] If two patterns have the same count, the one that is longer is considered more specific. For example `/foo/bar*` is longer and considered more specific than `/foo/*`.\n\nģURIģͨܺһ£·Ǹģ׼ȷٸӣ`/foo/bar*`ͱΪ`/foo/*`׼ȷΪǰߵ·\n\n> [Original] When two patterns have the same count and length, the pattern with fewer wild cards is considered more specific. For example `/hotels/{hotel}` is more specific than `/hotels/*`.\n\nģͳȾһ£ǸиͨģǸ׼ȷġ磬`/hotels/{hotel}`ͱ`/hotels/*`ȷ\n\n> [Original] There are also some additional special rules:\n\n֮⣬һЩĹ\n\n> [Original] _The **default mapping pattern** `/*_`is less specific than any other pattern. For example`/api/{a}/{b}/{c}` is more specific.\n>\n> [Original] _A **prefix pattern** such as `/public/*_`is less specific than any other pattern that doesn't contain double wildcards. For example`/public/path3/{a}/{b}/{c}` is more specific.\n\n*   **Ĭϵͨģʽ**`/**`еģʽ׼ȷȷ˵`/api/{a}/{b}/{c}`ͱĬϵͨģʽ`/**`Ҫ׼ȷ\n*   **ǰ׺ͨ**`/public/**`)Ϊκβ˫ͨģʽ׼ȷ˵`/public/path3/{a}/{b}/{c}`ͱ`/public/**`׼ȷ\n\n> [Original] For the full details see `AntPatternComparator` in `AntPathMatcher`. Note that the PathMatcher can be customized (see [Section 21.16.11, \"Path Matching\"](https://linesh.gitbooks.io/spring-mvc-documentation-linesh-translation/content/publish/21-3/mvc.html#mvc-config-path-matching) in the section on configuring Spring MVC).\n\nϸοࣺ`AntPatternComparator``AntPathMatcher`ֵһǣPathMatcherǿõģSpring MVCһе[·ƥ](https://www.w3cschool.cn/spring_mvc_documentation_linesh_translation/spring_mvc_documentation_linesh_translation-cgvo27t2.html)һ)\n\n## ռλ·ģʽpath patterns\n\n> [Original] Patterns in `@RequestMapping` annotations support ${} placeholders against local properties and/or system properties and environment variables. This may be useful in cases where the path a controller is mapped to may need to be customized through configuration. For more information on placeholders, see the javadocs of the `PropertyPlaceholderConfigurer` class.\n\n`@RequestMapping`ע֧·ʹռλȡһЩáϵͳáȡʱã˵ӳ·ҪͨƵĳ˽ռλϢԲο`PropertyPlaceholderConfigurer`ĵ\n\n## Suffix Pattern Matching\n\n## ׺ģʽƥ\n\n> [Original] By default Spring MVC performs `\".*\"` suffix pattern matching so that a controller mapped to `/person` is also implicitly mapped to `/person.*`. This makes it easy to request different representations of a resource through the URL path (e.g. `/person.pdf`, `/person.xml`).\n\nSpring MVCĬϲ`\".*\"`ĺ׺ģʽƥ·ƥ䣬ˣһӳ䵽`/person`·ĿҲʽرӳ䵽`/person.*`ʹͨURLͬһԴļĲͬʽø򵥣`/person.pdf``/person.xml`\n\n> [Original] Suffix pattern matching can be turned off or restricted to a set of path extensions explicitly registered for content negotiation purposes. This is generally recommended to minimize ambiguity with common request mappings such as `/person/{id}` where a dot might not represent a file extension, e.g. `/person/joe@email.com` vs `/person/joe@email.com.json)`. Furthermore as explained in the note below suffix pattern matching as well as content negotiation may be used in some circumstances to attempt malicious attacks and there are good reasons to restrict them meaningfully.\n\nԹرĬϵĺ׺ģʽƥ䣬ʽؽ·׺޶һЩضʽfor content negotiation purposeƼԼӳʱԴһЩԣ·`/person/{id}`ʱ·еĵźĿܲݸʽ`/person/joe@email.com` vs `/person/joe@email.com.json`Ҫᵽģ׺ģʽͨԼЭʱܻᱻڿйˣԺ׺ͨ޶кôġ\n\n> [Original] See [Section 21.16.11, \"Path Matching\"](https://linesh.gitbooks.io/spring-mvc-documentation-linesh-translation/content/publish/21-3/mvc.html#mvc-config-path-matching) for suffix pattern matching configuration and also [Section 21.16.6, \"Content Negotiation\"](https://linesh.gitbooks.io/spring-mvc-documentation-linesh-translation/content/publish/21-3/mvc.html#mvc-config-content-negotiation) for content negotiation configuration.\n\nں׺ģʽƥ⣬Բο[Spring MVC·ƥ](https://www.w3cschool.cn/spring_mvc_documentation_linesh_translation/spring_mvc_documentation_linesh_translation-cgvo27t2.html)Э̵⣬Բο[Spring MVC Э\"](https://www.w3cschool.cn/spring_mvc_documentation_linesh_translation/spring_mvc_documentation_linesh_translation-h8br27sx.html)ݡ\n\n## ׺ģʽƥRFD\n\n> [Original] Reflected file download (RFD) attack was first described in a [paper by Trustwave](https://www.trustwave.com/Resources/SpiderLabs-Blog/Reflected-File-Download---A-New-Web-Attack-Vector/) in 2014\\. The attack is similar to XSS in that it relies on input (e.g. query parameter, URI variable) being reflected in the response. However instead of inserting JavaScript into HTML, an RFD attack relies on the browser switching to perform a download and treating the response as an executable script if double-clicked based on the file extension (e.g. .bat, .cmd).\n\nRFD(Reflected file download)2014[Trustwaveһƪ](https://www.trustwave.com/Resources/SpiderLabs-Blog/Reflected-File-Download---A-New-Web-Attack-Vector/)бġXSSЩƣΪֹʽҲĳЩҪ루ѯURIȣҲresponseĳʽ֡ͬǣRFDͨHTMLдJavaScriptУתҳ棬ضʽ.bat.cmdȣresponseǿִнű˫ͻִС\n\n> [Original] In Spring MVC `@ResponseBody` and `ResponseEntity` methods are at risk because they can render different content types which clients can request including via URL path extensions. Note however that neither disabling suffix pattern matching nor disabling the use of path extensions for content negotiation purposes alone are effective at preventing RFD attacks.\n\nSpring MVC`@ResponseBody``ResponseEntity`зյģΪǻݿͻ󡪡URL·׺Ⱦͬ͡ˣú׺ģʽƥ߽ýΪЭ̿·ļ׺ЯǷRFDЧʽ\n\n> [Original] For comprehensive protection against RFD, prior to rendering the response body Spring MVC adds a `Content-Disposition:inline;filename=f.txt` header to suggest a fixed and safe download file filename. This is done only if the URL path contains a file extension that is neither whitelisted nor explicitly registered for content negotiation purposes. However it may potentially have side effects when URLs are typed directly into a browser.\n\nҪRFD߼ıģʽSpring MVCȾʼ֮ǰͷһ`Content-Disposition:inline;filename=f.txt`̶ָļļURL·аһļչʱãչȲбУҲûбʽرעЭʱʹáһЩã磬URLֶͨʱ\n\n> [Original] Many common path extensions are whitelisted by default. Furthermore REST API calls are typically not meant to be used as URLs directly in browsers. Nevertheless applications that use custom `HttpMessageConverter` implementations can explicitly register file extensions for content negotiation and the Content-Disposition header will not be added for such extensions. See [Section 21.16.6, \"Content Negotiation\"](https://linesh.gitbooks.io/spring-mvc-documentation-linesh-translation/content/publish/21-3/mvc.html#mvc-config-content-negotiation).\n\nܶೣõ·ļ׺ĬǱεġ⣬RESTAPIһǲӦֱURLġԼ`HttpMessageConverter`ʵ֣ȻʽעЭ̵ļͣContent-Dispositionͷᱻ뵽ͷС[Spring MVC Э](https://www.w3cschool.cn/spring_mvc_documentation_linesh_translation/spring_mvc_documentation_linesh_translation-h8br27sx.html)\n\n> [Original] This was originally introduced as part of work for [CVE-2015-5211](http://pivotal.io/security/cve-2015-5211). Below are additional recommendations from the report:\n>\n> *   Encode rather than escape JSON responses. This is also an OWASP XSS recommendation. For an example of how to do that with Spring see [spring-jackson-owasp](https://github.com/rwinch/spring-jackson-owasp).\n> *   Configure suffix pattern matching to be turned off or restricted to explicitly registered suffixes only.\n> *   Configure content negotiation with the properties \"useJaf\" and \"ignoreUnknownPathExtensions\" set to false which would result in a 406 response for URLs with unknown extensions. Note however that this may not be an option if URLs are naturally expected to have a dot towards the end.\n> *   Add `X-Content-Type-Options: nosniff` header to responses. Spring Security 4 does this by default.\n\nоڵķޣҪ˽XSSRFDϸٷ\n\n## \n\n> [Original] The URI specification [RFC 3986](http://tools.ietf.org/html/rfc3986#section-3.3) defines the possibility of including name-value pairs within path segments. There is no specific term used in the spec. The general \"URI path parameters\" could be applied although the more unique [\"Matrix URIs\"](http://www.w3.org/DesignIssues/MatrixURIs.html), originating from an old post by Tim Berners-Lee, is also frequently used and fairly well known. Within Spring MVC these are referred to as matrix variables.\n\nԭURI淶[RFC 3986](http://tools.ietf.org/html/rfc3986#section-3.3)·Яֵԣ淶ûȷļֵԶ˽СURI·Ҳн[URI](http://www.w3.org/DesignIssues/MatrixURIs.html)ġTim Berners-Lee䲩ᵽʹõҪƵһЩ֪ҲЩSpring MVCУǳļֵΪ\n\n> [Original] Matrix variables can appear in any path segment, each matrix variable separated with a \";\" (semicolon). For example: `\"/cars;color=red;year=2012\"`. Multiple values may be either \",\" (comma) separated `\"color=red,green,blue\"` or the variable name may be repeated `\"color=red;color=green;color=blue\"`.\n\nκ·г֣ÿԾ֮ʹһֺš;URI`\"/cars;color=red;year=2012\"`ֵöŸ`\"color=red,green,blue\"`ظ`\"color=red;color=green;color=blue\"`\n\n> [Original] If a URL is expected to contain matrix variables, the request mapping pattern must represent them with a URI template. This ensures the request can be matched correctly regardless of whether matrix variables are present or not and in what order they are provided.\n\nһURLпҪô·ӳϾҪʹURIģһ㡣ȷԱȷӳ䣬ܾURIǷֵ֡Ĵȡ\n\n> [Original] Below is an example of extracting the matrix variable \"q\":\n\nһӣչʾδӾлȡqֵ\n\n```\n// GET /pets/42;q=11;r=22\n\n@RequestMapping(path = \"/pets/{petId}\", method = RequestMethod.GET)\npublic void findPet(@PathVariable String petId, @MatrixVariable int q) {\n\n    // petId == 42\n    // q == 11\n\n}\n\n```\n\n> [Original] Since all path segments may contain matrix variables, in some cases you need to be more specific to identify where the variable is expected to be:\n\n·жԺоĳЩ£ҪøȷϢָһλã\n\n```\n// GET /owners/42;q=11/pets/21;q=22\n\n@RequestMapping(path = \"/owners/{ownerId}/pets/{petId}\", method = RequestMethod.GET)\npublic void findPet(\n    @MatrixVariable(name=\"q\", pathVar=\"ownerId\") int q1,\n    @MatrixVariable(name=\"q\", pathVar=\"petId\") int q2) {\n\n    // q1 == 11\n    // q2 == 22\n\n}\n\n```\n\n> [Original] A matrix variable may be defined as optional and a default value specified:\n\nҲһǱֵģһĬֵ\n\n```\n// GET /pets/42\n\n@RequestMapping(path = \"/pets/{petId}\", method = RequestMethod.GET)\npublic void findPet(@MatrixVariable(required=false, defaultValue=\"1\") int q) {\n\n    // q == 1\n\n}\n\n```\n\n> [Original] All matrix variables may be obtained in a Map:\n\nҲͨһMap洢еľ\n\n```\n// GET /owners/42;q=11;r=12/pets/21;q=22;s=23\n\n@RequestMapping(path = \"/owners/{ownerId}/pets/{petId}\", method = RequestMethod.GET)\npublic void findPet(\n    @MatrixVariable Map<String, String> matrixVars,\n    @MatrixVariable(pathVar=\"petId\") Map<String, String> petMatrixVars) {\n\n    // matrixVars: [\"q\" : [11,22], \"r\" : 12, \"s\" : 23]\n    // petMatrixVars: [\"q\" : 11, \"s\" : 23]\n\n}\n\n```\n\n> [Original] Note that to enable the use of matrix variables, you must set the `removeSemicolonContent`property of `RequestMappingHandlerMapping` to `false`. By default it is set to `true`.\n\nҪʹã`RequestMappingHandlerMapping``removeSemicolonContent`Ϊ`false`ֵĬ`true`ġ\n\n> [Original] The MVC Java config and the MVC namespace both provide options for enabling the use of matrix variables.\n>\n> MVCJavaúռöṩþķʽ\n>\n> [Original] If you are using Java config, The [Advanced Customizations with MVC Java Config](https://linesh.gitbooks.io/spring-mvc-documentation-linesh-translation/content/publish/21-3/mvc.html#mvc-config-advanced-java) section describes how the `RequestMappingHandlerMapping` can be customized.\n>\n> ʹJava̵ķʽ[MVC Java߼ƻáһ](http://docs.spring.io/spring-framework/docs/current/spring-framework-reference/html/mvc.html#mvc-config-advanced-java)ζ`RequestMappingHandlerMapping`жơ\n>\n> [Original] In the MVC namespace, the `<mvc:annotation-driven>` element has an `enable-matrix-variables` attribute that should be set to `true`. By default it is set to `false`.\n>\n> ʹMVCռʱ԰`<mvc:annotation-driven>`Ԫµ`enable-matrix-variables`Ϊ`true`ֵĬΪ`false`ġ\n\n```\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<beans xmlns=\"http://www.springframework.org/schema/beans\"\n    xmlns:mvc=\"http://www.springframework.org/schema/mvc\"\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"\n        http://www.springframework.org/schema/beans\n        http://www.springframework.org/schema/beans/spring-beans.xsd\n        http://www.springframework.org/schema/mvc\n        http://www.springframework.org/schema/mvc/spring-mvc.xsd\">\n\n    <mvc:annotation-driven enable-matrix-variables=\"true\"/>\n\n</beans>\n\n```\n\n## ѵý\n\n> [Original] You can narrow the primary mapping by specifying a list of consumable media types. The request will be matched only if the _Content-Type_ request header matches the specified media type. For example:\n\nָһѵýͣСӳķΧֻеͷ _Content-Type_ ֵָѵýͬʱŻᱻƥ䡣ӣ\n\n```\n@Controller\n@RequestMapping(path = \"/pets\", method = RequestMethod.POST, consumes=\"application/json\")\npublic void addPet(@RequestBody Pet pet, Model model) {\n    // ʵʡ\n}\n\n```\n\n> [Original] Consumable media type expressions can also be negated as in _!text/plain_ to match to all requests other than those with _Content-Type_ of _text/plain_. Also consider using constants provided in `MediaType` such as `APPLICATION_JSON_VALUE` and `APPLICATION_JSON_UTF8_VALUE`.\n\nָý͵ıʽлʹ÷񶨣磬ʹ _!text/plain_ ƥͷ _Content-Type_ в _text/plain_ ͬʱ`MediaType`лһЩ`APPLICATION_JSON_VALUE``APPLICATION_JSON_UTF8_VALUE`ȣƼʹǡ\n\n> [Original] The _consumes_ condition is supported on the type and on the method level. Unlike most other conditions, when used at the type level, method-level consumable types override rather than extend type-level consumable types.\n>\n> _consumes_ ṩǷ֧֡ԲͬͼʹʱͽͼãǼ̳йϵ\n\n## ý\n\n> [Original] You can narrow the primary mapping by specifying a list of producible media types. The request will be matched only if the _Accept_ request header matches one of these values. Furthermore, use of the _produces_ condition ensures the actual content type used to generate the response respects the media types specified in the _produces_ condition. For example:\n\nָһýͣСӳķΧֻеͷ _Accept_ ֵָýͬʱŻᱻƥ䡣ңʹ _produces_ ȷӦresponseָĿýͬġٸӣ\n\n```\n@Controller\n@RequestMapping(path = \"/pets/{petId}\", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)\n@ResponseBody\npublic Pet getPet(@PathVariable String petId, Model model) {\n    // ʵʡ\n}\n\n```\n\n> [Original] Be aware that the media type specified in the _produces_ condition can also optionally specify a character set. For example, in the code snippet above we specify the same media type than the default one configured in `MappingJackson2HttpMessageConverter`, including the `UTF-8`charset.\n>\n> Ҫעǣͨ _condition_ ָýҲַָСδУǻǸд`MappingJackson2HttpMessageConverter`Ĭõýͣͬʱָʹ`UTF-8`ַ\n>\n> [Original] Just like with _consumes_, producible media type expressions can be negated as in _!text/plain_ to match to all requests other than those with an _Accept_ header value of _text/plain_. Also consider using constants provided in `MediaType` such as `APPLICATION_JSON_VALUE` and `APPLICATION_JSON_UTF8_VALUE`.\n\n _consumes_ ƣýͱʽҲʹ÷񶨡磬ʹ _!text/plain_ ƥͷ _Accept_ в _text/plain_ ͬʱ`MediaType`лһЩ`APPLICATION_JSON_VALUE``APPLICATION_JSON_UTF8_VALUE`ȣƼʹǡ\n\n> [Original] The _produces_ condition is supported on the type and on the method level. Unlike most other conditions, when used at the type level, method-level producible types override rather than extend type-level producible types.\n>\n> _produces_ ṩǷ֧֡ԲͬͼʹʱͽͼãǼ̳йϵ\n\n## ͷֵ\n\n> [Original] You can narrow request matching through request parameter conditions such as `\"myParam\"`, `\"!myParam\"`, or `\"myParam=myValue\"`. The first two test for request parameter presence/absence and the third for a specific parameter value. Here is an example with a request parameter value condition:\n\nɸѡСƥ䷶Χ`\"myParam\"``\"!myParam\"``\"myParam=myValue\"`ȡǰɸѡ/ĳЩ󣬵ɸѡضֵиӣչʾʹֵɸѡ\n\n```\n@Controller\n@RequestMapping(\"/owners/{ownerId}\")\npublic class RelativePathUriTemplateController {\n\n    @RequestMapping(path = \"/pets/{petId}\", method = RequestMethod.GET, params=\"myParam=myValue\")\n    public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {\n        // ʵʵʡ\n    }\n\n}\n\n```\n\n> [Original] The same can be done to test for request header presence/absence or to match based on a specific request header value:\n\nͬͬɸѡͷĳ񣬻ɸѡһضֵͷ\n\n```\n@Controller\n@RequestMapping(\"/owners/{ownerId}\")\npublic class RelativePathUriTemplateController {\n\n    @RequestMapping(path = \"/pets\", method = RequestMethod.GET, headers=\"myHeader=myValue\")\n    public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {\n        // ʵʡ\n    }\n\n}\n\n```\n\n> [Original] Although you can match to _Content-Type_ and _Accept_ header values using media type wild cards (for example _\"content-type=text/*\"_ will match to _\"text/plain\"_ and _\"text/html\"_), it is recommended to use the _consumes_ and _produces_ conditions respectively instead. They are intended specifically for that purpose.\n>\n> ܣʹý͵ͨ _\"content-type=text/*\"_ƥͷ _Content-Type_ _Accept_ֵǸƼʹ _consumes_ _produces_ɸѡԵΪǾרΪֲͬĳġ\n\n</section>\n\n\n\n"
  },
  {
    "path": "docs/Spring全家桶/SpringMVC/SpringMVC中的异常处理器.md",
    "content": "\n\n\n\n# Spring MVC 쳣\n\n\n\n2018-07-26 14:32 \n\n\n\n\n\n\n\nSpringĴ쳣`HandlerExceptionResolver`ӿڵʵִָйгֵ쳣ĳ̶ֳϽ`HandlerExceptionResolver`webӦ`web.xml`ļܶ쳣ӳ䣨exception mapping񣬲Ⱥṩ˸ķʽṩ쳣׳ʱִеĸϢңһprogrammatic쳣ʽΪṩѡʹֱתһURL֮ǰʹServlet淶쳣ӳһģиķʽ쳣\n\n<section>\n\nʵ`HandlerExceptionResolver`ӿڲʵ쳣Ψһʽֻṩ`resolveException(Exception, Hanlder)`һʵֶѣ᷵һ`ModelAndView`֮⣬㻹Կṩ`SimpleMappingExceptionResolver`쳣ע`@ExceptionHandler``SimpleMappingExceptionResolver`ȡ׳쳣֣ӳ䵽һͼȥServlet APIṩ쳣ӳǹܵȼ۵ģҲԻڴʵȸϸ쳣ӳ䡣`@ExceptionHandler`עķ쳣׳ʱԴ쳣ķԶ`@Controller`עĿҲԶ`@ControllerAdvice`У߿ʹ쳣Ӧõ`@Controller`СһСڽṩΪϸϢ\n\n</section>\n\n\n\n\n\n\n\n# Spring MVC ʹ@ExceptionHandlerע\n\n\n\n2018-07-26 14:33 \n\n\n\n\n\n\n\n<section>\n\n`HandlerExceptionResolver`ӿԼ`SimpleMappingExceptionResolver`ʵʹʽؽ쳣ӳ䵽ضͼϣ쳣תforwardӦͼǰʹJavaЩжϺ߼һЩر`@ResponseBody`ӦͼƵĳ£ֱӦ״̬벢ͻҪĴϢֱдӦУǸķ\n\nҲʹ`@ExceptionHandler`㡣`@ExceptionHandler`ڿڲģôղɿκࣩе`@RequestMapping`׳쳣㽫`@ExceptionHandler``@ControllerAdvice`Уôᴦؿ׳쳣Ĵչʾһڿڲ`@ExceptionHandler`\n\n```\n@Controller\npublic class SimpleController {\n\n    // @RequestMapping methods omitted ...\n\n    @ExceptionHandler(IOException.class)\n    public ResponseEntity<String> handleIOException(IOException ex) {\n        // prepare responseEntity\n        return responseEntity;\n    }\n\n}\n\n```\n\n⣬`@ExceptionHandler`ע⻹Խһ쳣͵Ϊֵ׳б쳣ôӦ`@ExceptionHandler`ᱻáûиעκβֵôĬϴ쳣ͽǷЩ쳣\n\n׼Ŀ`@RequestMapping`ע⴦һ`@ExceptionHandler`ķͷֵҲԺ磬Servlet·Խ`HttpServletRequest`Portlet·Խ`PortletRequest`ֵ`String`͡»ᱻΪͼ`ModelAndView`͵ĶҲ`ResponseEntity`㻹ڷ`@ResponseBody`עʹϢתתϢΪض͵ݣȻдصӦС\n\n</section>\n\n\n\n\n\n\n\n# Spring MVC һ쳣\n\n\n\n2018-07-26 14:34 \n\n\n\n\n\n\n\nĹУSpring MVCܻ׳һЩ쳣`SimpleMappingExceptionResolver`ԸҪܷؽκ쳣ӳ䵽һĬϵĴͼͻͨԶӦķʽַ쳣ģô˾ҪΪӦöӦ״̬롣׳쳣ͲͬҪòͬ״̬ʶǿͻ˴4xxǷ˴5xx\n\n<section>\n\nĬϴ쳣`DefaultHandlerExceptionResolver`ὫSpring MVC׳쳣תɶӦĴ״̬롣ýMVCռûMVC JavaõķʽĬѾעˣ⣬ͨ`DispatcherServlet`עҲǿеģʹMVCռJava̷ʽõʱ򣩡±г˸ýܴһЩ쳣ǶӦ״̬롣\n\n| 쳣 | HTTP״̬ |\n| --- | --- |\n| `BindException` | 400 (Ч) |\n| `ConversionNotSupportedException` | 500 (ڲ) |\n| `HttpMediaTypeNotAcceptableException` | 406 () |\n| `HttpMediaTypeNotSupportedException` | 415 (ֵ֧ý) |\n| `HttpMessageNotReadableException` | 400 (Ч) |\n| `HttpMessageNotWritableException` | 500 (ڲ) |\n| `HttpRequestMethodNotSupportedException` | 405 (ֵ֧ķ) |\n| `MethodArgumentNotValidException` | 400 (Ч) |\n| `MissingServletRequestParameterException` | 400 (Ч) |\n| `MissingServletRequestPartException` | 400 (Ч) |\n| `NoHandlerFoundException` | 404 (δҵ) |\n| `NoSuchRequestHandlingMethodException` | 404 (δҵ) |\n| `TypeMismatchException` | 400 (Ч) |\n| `MissingPathVariableException` | 500 (ڲ) |\n| `NoHandlerFoundException` | 404 (δҵ) |\n\n´롣\n\nThe `DefaultHandlerExceptionResolver` works transparently by setting the status of the response. However, it stops short of writing any error content to the body of the response while your application may need to add developer- friendly content to every error response for example when providing a REST API. You can prepare a `ModelAndView` and render error content through view resolution?--?i.e. by configuring a `ContentNegotiatingViewResolver`, `MappingJackson2JsonView`, and so on. However, you may prefer to use`@ExceptionHandler` methods instead.\n\nIf you prefer to write error content via `@ExceptionHandler` methods you can extend `ResponseEntityExceptionHandler` instead. This is a convenient base for `@ControllerAdvice` classes providing an `@ExceptionHandler` method to handle standard Spring MVC exceptions and return `ResponseEntity`. That allows you to customize the response and write error content with message converters. See the `ResponseEntityExceptionHandler` javadocs for more details.\n\n</section>\n\n\n\n\n\n\n\n# Spring MVC ʹ@ResponseStatusעҵ쳣\n\n\n\n2020-07-31 10:52 \n\n\n\n\n\n\n\nҵ쳣ʹ`@ResponseStatus`ע⡣쳣׳ʱ`ResponseStatusExceptionResolver`ӦӦ״̬롣`DispatcherServlet`Ĭעһ`ResponseStatusExceptionResolver` Թʹá\n\nResponseStatusעʹ÷ǳ򵥣Ǵһ쳣࣬ע\n\n```\npackage com.zj.exception;\n\nimport org.springframework.http.HttpStatus;\nimport org.springframework.web.bind.annotation.ResponseStatus;\n\n@ResponseStatus(value=HttpStatus.FORBIDDEN,reason=\"ûƥ\")\npublic class UserNotMatchException extends RuntimeException{\n}\n```\n\n> ResponseStatusע\n> ԣvaluehttp״̬룬404500ȡreasonǴϢ\n\nдһĿ귽׳쳣\n\n```\n@RequestMapping(\"/testResponseStatus\")\npublic String testResponseStatus(int i){\n    if(i==0)\n        throw new UserNotMatchException();\n    return \"hello\";\n}\n```\n\n> ʹResponseStatusע֮û쳣Լ쳣һûĴ롣\n\n\n\n\n\n\n\n# Spring MVC ServletĬҳĶƻ\n\n\n\n2018-07-26 14:36 \n\n\n\n\n\n\n\nӦ״̬뱻Ϊ״̬룬ӦûʱServletͨȾһHTMLҳҪĬṩĴҳ`web.xml`жһҳ`<error-page>`ԪءServlet 3淶֮ǰôҳԪر뱻ʽָӳ䵽һĴһ쳣͡Servlet 3ʼҳҪӳ䵽ϢˣζţָλþǶServletĬϴҳԶˡ\n\n<section>\n\n```\n<error-page>\n    <location>/error</location>\n</error-page>\n\n```\n\nҳλڿһJSPҳ棬һЩURLֻҪָһ`@Controller`µĴ\n\nд`HttpServletResponse`ĴϢʹ״̬ڿͨȡ\n\n```\n@Controller\npublic class ErrorController {\n\n    @RequestMapping(path = \"/error\", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)\n    @ResponseBody\n    public Map<String, Object> handle(HttpServletRequest request) {\n\n        Map<String, Object> map = new HashMap<String, Object>();\n        map.put(\"status\", request.getAttribute(\"javax.servlet.error.status_code\"));\n        map.put(\"reason\", request.getAttribute(\"javax.servlet.error.message\"));\n\n        return map;\n    }\n\n}\n\n```\n\nJSPôʹ:\n\n```\n<%@ page contentType=\"application/json\" pageEncoding=\"UTF-8\"%>\n{\n    status:<%=request.getAttribute(\"javax.servlet.error.status_code\") %>,\n    reason:<%=request.getAttribute(\"javax.servlet.error.message\") %>\n}\n```\n\n</section>\n\n\n\n"
  },
  {
    "path": "docs/Spring全家桶/SpringMVC/SpringMVC中的拦截器.md",
    "content": "\n\n\n\n# Spring MVC ʹHandlerInterceptor\n\n\n\n2018-07-26 14:07 \n\n\n\n\n\n\n\nSpringĴӳư˴ҪΪض͵ӦһЩʱܺã磬ûݵȡ\n\n<section>\n\nӳ䴦õʵ `org.springframework.web.servlet`µ `HandlerInterceptor`ӿڡӿڶ `preHandle(..)`ڴʵִ _֮ǰ_ ᱻִУ `postHandle(..)`ڴִ __ ԺִУ `afterCompletion(..)` __ ִ֮СΪ͵ǰͺṩ㹻ԡ\n\n`preHandle(..)`һbooleanֵͨǷִдеĲ `true`ʱִУ `false` `DispatcherServlet`ΪѾ˶Ĵ˵ѾȾһʵͼôԼִеͲٱִˡ\n\nͨ`interceptors`ãѡм̳`AbstractHandlerMapping`Ĵӳ`HandlerMapping`ṩõĽӿڡʾ\n\n```\n<beans>\n    <bean id=\"handlerMapping\" class=\"org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping\">\n        <property name=\"interceptors\">\n            <list>\n                <ref bean=\"officeHoursInterceptor\"/>\n            </list>\n        </property>\n    </bean>\n\n    <bean id=\"officeHoursInterceptor\" class=\"samples.TimeBasedAccessInterceptor\">\n        <property name=\"openingTime\" value=\"9\"/>\n        <property name=\"closingTime\" value=\"18\"/>\n    </bean>\n<beans>\n\n```\n\n```\npackage samples;\n\npublic class TimeBasedAccessInterceptor extends HandlerInterceptorAdapter {\n\n    private int openingTime;\n    private int closingTime;\n\n    public void setOpeningTime(int openingTime) {\n        this.openingTime = openingTime;\n    }\n\n    public void setClosingTime(int closingTime) {\n        this.closingTime = closingTime;\n    }\n\n    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,\n            Object handler) throws Exception {\n        Calendar cal = Calendar.getInstance();\n        int hour = cal.get(HOUR_OF_DAY);\n        if (openingTime <= hour && hour < closingTime) {\n            return true;\n        }\n        response.sendRedirect(\"http://host.com/outsideOfficeHours.html\");\n        return false;\n    }\n}\n\n```\n\nУб˴󶼻ᱻ`TimeBasedAccessInterceptor`ءǰʱڹʱ⣬ôûͻᱻضһHTMLļʾûʾֻڹʱſԷʱվ֮Ϣ\n\n> ʹ`RequestMappingHandlerMapping`ʱʵʵĴһ`HandlerMethod`ʵʶһڴĿ\n\nSpring`HandlerInterceptorAdapter`ü̳`HandlerInterceptor`ӿڱøˡ\n\n> Уп󶼻ᱻõصһСصURLΧͨMVCռMVC Java̵ķʽãߣһ`MappedInterceptor`͵beanʵ [21.16.1 MVC JavaûMVCռ](https://www.w3cschool.cn/spring_mvc_documentation_linesh_translation/spring_mvc_documentation_linesh_translation-ouxg27ss.html)һСڡ\n\nҪעǣ`HandlerInterceptor`ĺ`postHandle`һע`@ResponseBody``ResponseEntity`ķЩУ`HttpMessageConverter``postHandle`֮ǰͰϢдӦС޷ٸıӦˣҪһӦͷ֮ġӦʵ`ResponseBodyAdvice`ӿڣ䶨Ϊһ`@ControllerAdvice`beanֱ`RequestMappingHandlerMapping`á\n\n</section>\n\n\n\n"
  },
  {
    "path": "docs/Spring全家桶/SpringMVC/SpringMVC中的视图解析器.md",
    "content": "\n\n\n\n# Spring MVC ʹViewResolverӿڽͼ\n\n\n\n2018-07-26 15:39 \n\n\n\n\n\n\n\n[Spring MVC ʵ](https://www.w3cschool.cn/spring_mvc_documentation_linesh_translation/spring_mvc_documentation_linesh_translation-7z6u27rb.html)һ۵ģSpring MVCпĴ뷵һ߼ͼ֣ʽأ緵һ`String``View``ModelAndView`ʽأԼķأSpringеͼһͼʶͼȾSpringзǳõͼ±г˴󲿷֣ҲһЩӡ\n\n<section>\n\n**21.3 ͼ**\n\n| ͼ |  |\n| --- | --- |\n| `AbstractCachingViewResolver` | һͼ࣬ṩ˻ͼĹܡͨͼܹʹ֮ǰҪ׼̳ͼԻûͼ |\n| `XmlViewResolver` | ͼӿ`ViewResolver`һʵ֣һXMLʽļXMLļSpring XMLbeanͬDTDĬϵļ`/WEB-INF/views.xml` |\n| `ResourceBundleViewResolver` | ͼӿ`ViewResolver`һʵ֣bundle·ָ`ResourceBundle`еbeanΪáһbundleclasspath·µһļСĬϵļΪ`views.properties` |\n| `UrlBasedViewResolver` | `ViewResolver`ӿڵһʵֱ֡ʹURL߼ͼ֮ⲻҪκʽӳ߼ͼͼԴֱӶӦģôֱӽķʽͺܷ㣬Ҫָӳ䡣 |\n| `InternalResourceViewResolver` | `UrlBasedViewResolver`һõࡣ֧ڲԴͼ˵ServletJSPԼ`JstlView``TilesView`ࡣYou can specify the view class for all views generated by this resolver by using `setViewClass(..)`ϸڣ`UrlBasedViewResolver`javaĵ |\n| `VelocityViewResolver` / `FreeMarkerViewResolver` | `UrlBasedViewResolver`µʵ֧࣬Velocityͼ`VelocityView`Velocityģ壩FreeMarkerͼ`FreeMarkerView`ԼǶӦࡣ |\n| `ContentNegotiatingViewResolver` | ͼӿ`ViewResolver`һʵ֣ļ`Accept`ͷһͼϸ[Spring MVC Эͼ](https://www.w3cschool.cn/spring_mvc_documentation_linesh_translation/spring_mvc_documentation_linesh_translation-gal927rn.html)һСڡ |\n\nǿԾٸӣʹõJSPͼôǿʹһURLͼ`UrlBasedViewResolver`ͼὫURLһͼתַͼȾ\n\n```\n<bean id=\"viewResolver\" class=\"org.springframework.web.servlet.view.UrlBasedViewResolver\">\n    <property name=\"viewClass\" value=\"org.springframework.web.servlet.view.JstlView\"/>\n    <property name=\"prefix\" value=\"/WEB-INF/jsp/\"/>\n    <property name=\"suffix\" value=\".jsp\"/>\n</bean>\n\n```\n\nһ`test`߼ͼôͼὫת`RequestDispatcher`߻Ὣ󽻸`/WEB-INF/jsp/test.jsp`ͼȥȾ\n\nҪӦʹöֲͬͼʹ`ResourceBundleViewResolver`\n\n```\n<bean id=\"viewResolver\"\n        class=\"org.springframework.web.servlet.view.ResourceBundleViewResolver\">\n    <property name=\"basename\" value=\"views\"/>\n    <property name=\"defaultParentView\" value=\"parentView\"/>\n</bean>\n\n```\n\n`ResourceBundleViewResolver`bundle·õ`ResourceBundle`ÿͼԣͼ`[viewname].(class)`Եֵָͼurl`[viewname].url`Եֵָһڽϸͼҵӡ㻹Կͼлͼpropertiesļͼ̳Сһļ̳ͨмΪڶͼָһĬϵͼࡣ\n\n> `AbstractCachingViewResolver`ܹѾͼʵرջҲǿԵģֻҪ`cache`Ϊ`false`ɡ⣬ʵҪʱˢĳͼ޸Velocityģʱʹ`removeFromCache(String viewName, Locale loc)``\n\n</section>\n\n\n\n\n\n\n\n# Spring MVC ͼ\n\n\n\n2018-07-26 14:11 \n\n\n\n\n\n\n\nSpring֧ͬʱʹöͼˣһ±磬ض¸дһͼȡͨѶͼõӦ(application context)еķʽǡҪָǵĴô`order`ԼɡסorderԵֵԽ󣬸ͼеλþԽ\n\n<section>\n\nĴУͼаһ`InternalResourceViewResolver`Զڽһ`XmlViewResolver`ָExcelͼ`InternalResourceViewResolver`֧Excelͼ\n\n```\n<bean id=\"jspViewResolver\" class=\"org.springframework.web.servlet.view.InternalResourceViewResolver\">\n    <property name=\"viewClass\" value=\"org.springframework.web.servlet.view.JstlView\"/>\n    <property name=\"prefix\" value=\"/WEB-INF/jsp/\"/>\n    <property name=\"suffix\" value=\".jsp\"/>\n</bean>\n\n<bean id=\"excelViewResolver\" class=\"org.springframework.web.servlet.view.XmlViewResolver\">\n    <property name=\"order\" value=\"1\"/>\n    <property name=\"location\" value=\"/WEB-INF/views.xml\"/>\n</bean>\n\n<!-- in views.xml -->\n\n<beans>\n    <bean name=\"report\" class=\"org.springframework.example.ReportExcelView\"/>\n</beans>\n\n```\n\nһͼܷһͼôSpringͼʱĽSpringǣֱһͼΪֹͼܷһͼSpring׳һ`ServletException`\n\nͼĽӿˣһͼ__nullֵģʾҵκκʵͼеͼôҲڲò˵ĳȷʵ޷ӦͼǷڡ磬`InternalResourceViewResolver`ڲʹ`RequestDispatcher`ҽɹǼһJSPͼǷڵΨһܷ̽ΨһһΡͬ`VelocityViewResolver`ͲͼҲĳضͼJavaĵǷreportڵͼˣ`InternalResourceViewResolver`ڽ󣬽ܵ½޷ȫִУΪ`InternalResourceViewResolver`_Զ_ һͼ\n\n</section>\n\n\n\n\n\n\n\n# Spring MVC ͼض\n\n\n\n2018-07-26 14:12 \n\n\n\n\n\n\n\nǰͨ᷵һ߼ͼȻͼһͼȥȾһЩServletJSPͼJSPȣͨ`InternalResourceViewResolver``InternalResourceView`ЭɵģͨServletAPI`RequestDispatcher.forward(..)``RequestDispatcher.include(..)`һڲתforwardãincludeͼVelocityXSLTȣͼֱӱдӦеġ\n\n<section>\n\nʱҪͼȾ֮ǰȰһHTTPضͻؿͻˡ磬һɹؽܵ`POST`ݣӦίһһγɹıύʱϣһضֳ£ֻǼ򵥵ʹڲתôζһҲܿ`POST`ЯݣܵһЩǱڵ⣬ܻݻȡ⣬һȾͼǰضǣֹûύݡʱʹضȷ͵һ`POST`󱻴յһضӦȻֱӱضһͬURLʹضӦЯURLһ`GET`ˣĽǶȿǰҳ沢`POST`Ľһ`GET`Ľͷֹûˢµԭύ˶ͬݡʱˢ»`GET`һνҳǰͬ`POST`ٷһ顣\n\n## ضͼ RedirectView\n\nǿضһַǣڿдһSpringضͼ`RedirectView`ʵʹ`DispatcherServlet`ʹһͼƣΪѾһضͼ`DispatcherServlet`ˣṹһͼȾ󡣽`RedirectView``HttpServletResponse.sendRedirect()`һHTTPضӦͻ\n\n`RedirectView`ͼʵɿڲģǸƼⲿضURLȻע뵽дڿ档Ϳͼһļáʵο [ضǰ׺redirect:](http://docs.spring.io/spring-framework/docs/4.2.4.RELEASE/spring-framework-reference/html/mvc.html#mvc-redirecting-redirect-prefix)һСڡ\n\n## ضĿ괫\n\nģеĬ϶ῼΪURIģӵضURLСʣµԣǻͻ߻͵ļϻ飬ǽԶӵURLĲѯȥmodelרΪض׼ģôл͵ӵѯпǸĽǣڰעĿУmodelܰרΪȾ;ԣһбֵֶȣΪ˱Ҳ¶URLУ`@RequestMapping`һ`RedirectAttributes`͵ķָרŹضͼ`RedirectView`ȡõԡضɹô`RedirectAttributes`еݾͻᱻʹãʹģmodelеݡ\n\n`RequestMappingHandlerAdapter`ṩһ`\"ignoreDefaultModelOnRedirect\"`־Ĭ`Model`еԶӦñڿضСӦһ`RedirectAttributes`ĲǾûвݵضͼ`RedirectView`СMVCռMVC Java÷ʽУΪάļԣ־ԱΪ`false`ӦһµĿôƼֵó`true`\n\nע⣬ǰURIеģضURLʱԶӦÿɼҪʽ`Model``RedirectAttributes`ԡ뿴ӣ\n\n```\n@RequestMapping(path = \"/files/{path}\", method = RequestMethod.POST)\npublic String upload(...) {\n    // ...\n    return \"redirect:files/{path}\";\n}\n\n```\n\nһضĿ괫ݵķͨ _ԣFlash Attributes_ضԲͬflashǴ洢HTTP sessionеģ˲URLУݣο [21.6 ʹ](http://docs.spring.io/spring-framework/docs/4.2.4.RELEASE/spring-framework-reference/html/mvc.html#mvc-flash-attributes)һڡ\n\n## ضǰ׺redirect:\n\nʹ`RedirectView`ضܹúܺãҪһ`RedirectView`ɿ˽ضôһķ⻹е㲻ͬϻ̫ǿʵӦȥӦαȾIn general it should operate only in terms of view names that have been injected into it.\n\nһرͼǰ׺`redirect:`صͼк`redirect:`ǰ׺ô`UrlBasedViewResolver`ࣩͻܵźţʶҪضȻͼʣµĲֻᱻضURL\n\nַʽͨһضͼ`RedirectView`ﵽЧһģһͿֻרעڴ߼ͼˡ߼ͼʽ`redirect:/myapp/some/resource`ض·ServletΪ·вң߼ͼʽ`redirect:http://myhost.com/some/arbitrary/path`ôضURLʹõľǾ·\n\nעǣע`@ResponseStatus`ôעõ״ֵ̬Ḳ`RedirectView`õӦ״ֵ̬\n\n## ضǰ׺forward:\n\nջᱻ`UrlBasedViewResolver`ͼʹһǰ׺`forward:`ᵼһ`InternalResourceView`ͼĴջ`RequestDispatcher.forward()`߻ΪͼʣµĲһURLˣǰ׺ʹ`InternalResourceViewResolver``InternalResourceView`ʱûرãJSP˵ҪʹõͼҪǿưһԴתServlet/JSPдʱǰ׺ܾͺãߣҲͬʱͼ\n\n`redirect:`ǰ׺һеͼʹ`forward:`ǰ׺ᷢκ쳣עȻֻδӦ⡣\n\n</section>\n\n\n\n"
  },
  {
    "path": "docs/Spring全家桶/SpringMVC/SpringMVC中的过滤器Filter.md",
    "content": "Filter\n\nSpring MVCУDispatcherServletֻҪ̶õweb.xmlУʣµĹҪרעڱдController\n\nǣServlet淶УǻʹFilterҪSpring MVCʹFilterӦô\n\nеͯЬһڵWebӦпܷˣעʱĻᵼ룬ΪServletĬϰUTF-8ȡΪ޸һ⣬ǿԼ򵥵ʹһEncodingFilterȫַΧHttpServletRequestHttpServletResponseǿΪUTF-8롣\n\nԼдһEncodingFilterҲֱʹSpring MVCԴһCharacterEncodingFilterFilterʱֻweb.xmlɣ\n````\n<web-app>\n    <filter>\n        <filter-name>encodingFilter</filter-name>\n        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>\n        <init-param>\n            <param-name>encoding</param-name>\n            <param-value>UTF-8</param-value>\n        </init-param>\n        <init-param>\n            <param-name>forceEncoding</param-name>\n            <param-value>true</param-value>\n        </init-param>\n    </filter>\n\n    <filter-mapping>\n        <filter-name>encodingFilter</filter-name>\n        <url-pattern>/*</url-pattern>\n    </filter-mapping>\n    ...\n</web-app>\n````\nΪFilterҵϵע⵽CharacterEncodingFilterʵSpringIoCûκιϵ߾֪ԷĴڣˣFilterʮּ򵥡\n\nٿһ⣺ûʹBasicģʽû֤HTTPͷAuthorization: Basic email:passwordʵ֣\n\nдһAuthFilter򵥵ʵַʽ\n````\n@Component\npublic class AuthFilter implements Filter {\n@Autowired\nUserService userService;\n\n    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)\n            throws IOException, ServletException {\n        HttpServletRequest req = (HttpServletRequest) request;\n        // ȡAuthorizationͷ:\n        String authHeader = req.getHeader(\"Authorization\");\n        if (authHeader != null && authHeader.startsWith(\"Basic \")) {\n            // Headerȡemailpassword:\n            String email = prefixFrom(authHeader);\n            String password = suffixFrom(authHeader);\n            // ¼:\n            User user = userService.signin(email, password);\n            // Session:\n            req.getSession().setAttribute(UserController.KEY_USER, user);\n        }\n        // :\n        chain.doFilter(request, response);\n    }\n}\n````\nˣSpringдAuthFilterһͨBeanServlet֪á\n\nֱweb.xmlAuthFilterע⵽AuthFilterʵServletSpringʼˣ@AutowireЧڵ¼UserServiceԱԶnull\n\nԣͨһַʽServletʵFilterSpringʵAuthFilterSpring MVCṩһDelegatingFilterProxyר飺\n````\n<web-app>\n    <filter>\n        <filter-name>authFilter</filter-name>\n        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>\n    </filter>\n\n    <filter-mapping>\n        <filter-name>authFilter</filter-name>\n        <url-pattern>/*</url-pattern>\n    </filter-mapping>\n    ...\n</web-app>\n````\nʵԭ\n\nServletweb.xmlжȡãʵDelegatingFilterProxyעauthFilter\nSpringͨɨ@ComponentʵAuthFilter\nDelegatingFilterProxyЧԶעServletContextϵSpringͼвΪauthFilterBeanҲ@ComponentAuthFilter\n\nDelegatingFilterProxyAuthFilterĴ£\n````\npublic class DelegatingFilterProxy implements Filter {\n    private Filter delegate;\n    public void doFilter(...) throws ... {\n        if (delegate == null) {\n            delegate = findBeanFromSpringContainer();\n        }\n        delegate.doFilter(req, resp, chain);\n    }\n}\n````\nһģʽļӦáǻͼʾ֮ùϵ£\n````\n                        \n           \n DelegatingFilterProxy >AuthFilter \n           \n       \n  DispatcherServlet     >Controllers   \n       \n\n    Servlet Container       Spring Container\n                         \n````\nweb.xmlõFilterֺSpringBeanֲһ£ôҪָBean֣\n````\n<filter>\n    <filter-name>basicAuthFilter</filter-name>\n    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>\n    <!-- ָBean -->\n    <init-param>\n        <param-name>targetBeanName</param-name>\n        <param-value>authFilter</param-value>\n    </init-param>\n</filter>\n````\nʵӦʱһ£ԼٲҪá\n\nҪʹBasicģʽû֤ǿʹcurlԡ磬û¼tom@example.comtomcatôȹһʹURLû:ַ\n\ntom%40example.com:tomcat\nBase64룬չHeader£\n\nAuthorization: Basic dG9tJTQwZXhhbXBsZS5jb206dG9tY2F0\nʹµcurlӦ£\n````\n$ curl -v -H 'Authorization: Basic dG9tJTQwZXhhbXBsZS5jb206dG9tY2F0' http://localhost:8080/profile\n> GET /profile HTTP/1.1\n> Host: localhost:8080\n> User-Agent: curl/7.64.1\n> Accept: */*\n> Authorization: Basic dG9tJTQwZXhhbXBsZS5jb206dG9tY2F0\n>\n< HTTP/1.1 200\n< Set-Cookie: JSESSIONID=CE0F4BFC394816F717443397D4FEABBE; Path=/; HttpOnly\n< Content-Type: text/html;charset=UTF-8\n< Content-Language: en-CN\n< Transfer-Encoding: chunked\n< Date: Wed, 29 Apr 2020 00:15:50 GMT\n<\n<!doctype html>\n````\n...HTML...\nӦ˵AuthFilterЧ"
  },
  {
    "path": "docs/Spring全家桶/SpringMVC/SpringMVC基本介绍与快速入门.md",
    "content": "\n\n\n\n## MVC Ƹ\n\n Java Web ĿУͳһʾ㡢Ʋ㡢ݲĲȫ JSP  JavaBean дǳ֮Ϊ **Model1**\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-7b3f9cd59394b017.png)\n\n\n\n\n\n*   **ֵı׶ˣ**\n*   JSP  Java Bean ֮ϣJava  HTML Ҳһ\n*   Ҫ󿪷߲Ҫ Java Ҫи߳ǰˮƽ\n*   ǰ˺ͺ໥ǰҪȴɣҲǰɣܽЧĲ\n*   Ը\n\nΪֱ׶ˣԺַܿʽͱ Servlet + JSP + Java Bean ˣڵ MVC ģ**Model2**ͼ\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-403a273b08fec826.png)\n\n\n\n\n\nûᵽ ServletȻӦ Java Beanеʾ JSP ȥɣģʽǾͳΪ MVC ģʽ\n\n*   **M  ģͣModel**\n    ģʲôأ ģ;ݣ dao,bean\n*   **V  ͼView**\n    ͼʲôأ ҳ, JSPչʾģе\n*   **C  controller)**\n    ʲô þǰѲͬ(Model)ʾڲͬͼ(View)ϣServlet ݵľĽɫ\n\n> չĶ[Webģʽ](https://mp.weixin.qq.com/s?__biz=MzI4Njg5MDA5NA==&mid=2247483775&idx=1&sn=c9d7ead744c6e0c3ab2fe55c09bbe61f&chksm=ebd7407edca0c9688f3870d895b760836101271b912899821fb35c5704fe215da2fc5daff2f9#rd)\n\n#### Spring MVC ļܹ\n\nΪ־òһֱδõݿı̣Ϊӭ NoSQL ǿSpring MVC ˷\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-a25782fb05f315de.png)\n\n\n\n\n\n**ͳģͲ㱻Ϊҵ(Service)ݷʲ㣨DAO,Data Access Object**  Service ¿ͨ Spring ʽݷʲ㣬ҵϻǷ NoSQL ܹͻ NoSQL ʹˣԴ߻ϵͳܡ\n\n*   **ص㣺**\n    ṹɢ Spring MVC ʹøͼ\n    ϣģ\n     Spring ޷켯\n\n* * *\n\n## Hello Spring MVC\n\nдһǵĵһ Spring MVC \n\n#### һ IDEA ½ Spring MVC Ŀ\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-74ea4c339e8f35f8.png)\n\n\n\n\n\nȡΪ HelloSpringMVCFinish\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-cc6cb7d01573ceee.png)\n\n\n\n\n\nIDEA ԶغñҪ jar ΪǴһЩĬϵĿ¼ļԺĿṹ£\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-f6fd3173da6e4939.png)\n\n\n\n\n\n#### ڶ޸ web.xml\n\nǴ web.xml ͼ޸ģ\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-2d8a3a7b48dbe59a.png)\n\n\n\n\n\n`<url-pattern>`ԪصֵΪ / ʾҪе󣬲Spring MVCĺ̨֮\n\n\n\n```\n<servlet-mapping>\n    <servlet-name>dispatcher</servlet-name>\n    <url-pattern>/</url-pattern>\n</servlet-mapping>\n\n```\n\n\n\n#### ༭ dispatcher-servlet.xml\n\nļĿͷ dispatcher  web.xml е `<servlet-name>` Ԫõ dispatcher Ӧ Spring MVC ӳļxxx-servlet.xmlǱ༭£\n\n\n\n```\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<beans xmlns=\"http://www.springframework.org/schema/beans\"\n       xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n       xsi:schemaLocation=\"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd\">\n\n    <bean id=\"simpleUrlHandlerMapping\"\n          class=\"org.springframework.web.servlet.handler.SimpleUrlHandlerMapping\">\n        <property name=\"mappings\">\n            <props>\n                <!-- /hello ·󽻸 id Ϊ helloController Ŀ-->\n                <prop key=\"/hello\">helloController</prop>\n            </props>\n        </property>\n    </bean>\n    <bean id=\"helloController\" class=\"controller.HelloController\"></bean>\n</beans>\n\n```\n\n\n\n#### Ĳд HelloController\n\n Packagecontroller´ HelloController࣬ʵ org.springframework.web.servlet.mvc.Controller ӿڣ\n\n\n\n```\npackage controller;\n\nimport org.springframework.web.servlet.ModelAndView;\nimport org.springframework.web.servlet.mvc.Controller;\n\npublic class HelloController implements Controller{\n    @Override\n    public ModelAndView handleRequest(javax.servlet.http.HttpServletRequest httpServletRequest, javax.servlet.http.HttpServletResponse httpServletResponse) throws Exception {\n        return null;\n    }\n}\n\n```\n\n\n\n*   **⣺** javax.servlet Ҳ\n*   ****  Tomcat Ŀ¼¡libļµ servlet-api.jar ̡libļ£\n\nSpring MVC ͨ ModelAndView ģͺͼһ\n\n\n\n```\nModelAndView mav = new ModelAndView(\"index.jsp\");\nmav.addObject(\"message\", \"Hello Spring MVC\");\n\n```\n\n\n\nʾͼindex.jsp\nģݵ message Hello Spring MVC\n\n\n\n```\npackage controller;\n\nimport org.springframework.web.servlet.ModelAndView;\nimport org.springframework.web.servlet.mvc.Controller;\n\npublic class HelloController implements Controller {\n\n    public ModelAndView handleRequest(javax.servlet.http.HttpServletRequest httpServletRequest, javax.servlet.http.HttpServletResponse httpServletResponse) throws Exception {\n        ModelAndView mav = new ModelAndView(\"index.jsp\");\n        mav.addObject(\"message\", \"Hello Spring MVC\");\n        return mav;\n    }\n}\n\n```\n\n\n\n#### 岽׼ index.jsp\n\n index.jsp ޸Ϊ\n\n\n\n```\n<%@ page language=\"java\" contentType=\"text/html; charset=UTF-8\"\n    pageEncoding=\"UTF-8\" isELIgnored=\"false\"%>\n\n<h1>${message}</h1>\n\n```\n\n\n\nݺܼ򵥣Elʽʾ message ݡ\n\n####  Tomcat ػ\n\nڡRun˵ҵEdit Configurations\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-bcca5f5a7c097d6b.png)\n\n\n\n\n\n Tomcat \n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-0600768275c85966.png)\n\n\n\n\n\nѡñص Tomcat ĺ֣\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-5f033d1463f08d7b.png)\n\n\n\n\n\n Deployment ǩҳ²\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-02aa0ac9a6707685.png)\n\n\n\n\n\n OK ͺˣǵϽǵν Tomcat \n\n*   **ֵ⣺** Tomcat ޷\n*   **ԭ** Tomcat Ҳص jar \n*   **** libļWEB-INF£½\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-b8fcf3da677299cf.png)\n\n\n\n\n\n#### ߲\n\nַlocalhost/hello\n\n\n\n\n\n![](//upload-images.jianshu.io/upload_images/7896890-390fb571e9f6ff03.png)\n\n\n\n\n\n> οϣ[Spring MVC ̳(how2j.cn)](http://how2j.cn/k/springmvc/springmvc-springmvc/615.html#step1891)\n\n* * *\n\n##  Spring MVC \n\nÿû Web еӻύʱͿʼˣʵԱһ뿪ʼȡӦأᾭܶվ㣬ÿһվ㶼һЩϢͬʱҲϢͼΪ Spring MVC ̣\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-65ef874ad7da59a2.png)\n\n\n\n\n\n#### һվDispatcherServlet\n\n뿪Ժ󣬵һվľ DispatcherServletһ Servletͨ J2EE ѧϰ֪ Servlet ز HTTP DispatcherServlet е󣬲ҽЩ͸ Spring MVC \n\n\n\n```\n<servlet>\n    <servlet-name>dispatcher</servlet-name>\n    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>\n    <load-on-startup>1</load-on-startup>\n</servlet>\n<servlet-mapping>\n    <servlet-name>dispatcher</servlet-name>\n    <!-- е -->\n    <url-pattern>/</url-pattern>\n</servlet-mapping>\n\n```\n\n\n\n*   **DispatcherServlet ͸ Spring MVC **\n\n#### ڶվӳ䣨HandlerMapping\n\n*   **⣺** ͵ӦóпܻжЩ󵽵Ӧ÷һأ\n\n DispatcherServlet ѯһӳȷһվӳ**Я URL Ϣо**Уͨ simpleUrlHandlerMapping  /hello ַ helloController \n\n\n\n```\n<bean id=\"simpleUrlHandlerMapping\"\n      class=\"org.springframework.web.servlet.handler.SimpleUrlHandlerMapping\">\n    <property name=\"mappings\">\n        <props>\n            <!-- /hello ·󽻸 id Ϊ helloController Ŀ-->\n            <prop key=\"/hello\">helloController</prop>\n        </props>\n    </property>\n</bean>\n<bean id=\"helloController\" class=\"controller.HelloController\"></bean>\n\n```\n\n\n\n#### վ\n\nһѡ˺ʵĿ DispatcherServlet Ὣ͸ѡеĿ˿ж为أûύ󣩵ȴЩϢ\n\n\n\n```\npublic ModelAndView handleRequest(javax.servlet.http.HttpServletRequest httpServletRequest, javax.servlet.http.HttpServletResponse httpServletResponse) throws Exception {\n    // ߼\n    ....\n}\n\n```\n\n\n\n#### վ DispatcherServlet\n\n߼ͨһЩϢЩϢҪظûʾϢǱΪ**ģͣModel**ԭʼϢʱġЩϢҪûѺõķʽиʽһ HTMLԣϢҪ͸һ**ͼview**ͨ JSP\n\nһ¾ǽģݴұʾȾͼ**߼ͼὫͬģͺͼͻ DispatcherServlet**\n\n\n\n```\npublic ModelAndView handleRequest(javax.servlet.http.HttpServletRequest httpServletRequest, javax.servlet.http.HttpServletResponse httpServletResponse) throws Exception {\n    // ߼\n    ....\n    // ظ DispatcherServlet\n    return mav;\n}\n\n```\n\n\n\n#### վͼ\n\nͲضͼϣݸ DispatcherServlet ͼֱӱʾĳض JSPʵϣȷͼ JSP෴**ݵĽһ߼ƣƽҲͼ**\n\nDispatcherServlet ʹͼview resolver߼ͼƥΪһضͼʵ֣Ҳܲ JSP\n\n> ֱӰ󶨵 index.jsp ͼ\n\n#### վͼ\n\nȻ DispatcherServlet Ѿ֪ĸͼȾˣҲˡ\n\nһվͼʵ֣ģݣҲˡͼʹģȾͨӦ󴫵ݸͻˡ\n\n\n\n```\n<%@ page language=\"java\" contentType=\"text/html; charset=UTF-8\"\n         pageEncoding=\"UTF-8\" isELIgnored=\"false\"%>\n\n<h1>${message}</h1>\n\n```\n\n\n\n* * *\n\n## ʹע Spring MVC\n\nѾ Spring MVC һ˽⣬ͨ XML õķʽ˵һ Spring MVC עӦôã\n\n#### һΪ HelloController ע\n\n\n\n```\npackage controller;\n\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.servlet.ModelAndView;\n\n@Controller\npublic class HelloController{\n\n    @RequestMapping(\"/hello\")\n    public ModelAndView handleRequest(javax.servlet.http.HttpServletRequest httpServletRequest, javax.servlet.http.HttpServletResponse httpServletResponse) throws Exception {\n        ModelAndView mav = new ModelAndView(\"index.jsp\");\n        mav.addObject(\"message\", \"Hello Spring MVC\");\n        return mav;\n    }\n}\n\n```\n\n\n\nʵֵĽӿҲȥ\n\n*   **򵥽һ£**\n*   `@Controller` ע⣺\n    ԣעģʵע Spring MVC Ӱ첢󡣣Spring ʵս˵Ǹʵɨ裬 `@Component` ע棬Լһ²УΪû JSP ͼһԼһûгɹ...\n*   `@RequestMapping` ע⣺\n    Ȼͱʾ· `/hello` ӳ䵽÷\n\n#### ڶȡ֮ǰ XML ע\n\n dispatcher-servlet.xml ļУע͵֮ǰãȻһɨ裺\n\n\n\n```\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<beans xmlns=\"http://www.springframework.org/schema/beans\"\n       xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n       xmlns:context=\"http://www.springframework.org/schema/context\"\n       xsi:schemaLocation=\"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd\">\n\n    <!--<bean id=\"simpleUrlHandlerMapping\"-->\n                                        <!-->-->\n    <!--<property name=\"mappings\">-->\n            <!--<props>-->\n                <!--&lt;!&ndash; /hello ·󽻸 id Ϊ helloController Ŀ&ndash;&gt;-->\n                <!--<prop key=\"/hello\">helloController</prop>-->\n            <!--</props>-->\n        <!--</property>-->\n    <!--</bean>-->\n    <!--<bean id=\"helloController\" ></bean>-->\n\n    <!-- ɨcontrollerµ -->\n    <context:component-scan base-package=\"controller\"/>\n</beans>\n\n```\n\n\n\n#### \n\nɣ `localhost/hello` ַȻܿЧ\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-390fb571e9f6ff03.png)\n\n\n\n\n\n#### @RequestMapping עϸ\n\n `@RequestMapping` ϣô൱Ǹõӳַǰһַ磺\n\n\n\n```\n@Controller\n@RequestMapping(\"/wmyskxz\")\npublic class HelloController {\n    @RequestMapping(\"/hello\")\n    public ModelAndView handleRequest(....) throws Exception {\n        ....\n    }\n}\n\n```\n\n\n\n*   ʵַ `localhost/wmyskxz/hello`\n\n* * *\n\n## ͼ\n\nǵ Spring MVC ͼλͼһ DispaterServlet ݹ߼ͼƥһضͼ\n\n*   **** һЩҳǲϣûûֱӷʵҪݵҳ棬ģ֧ŵҳ档\n*   **ɵ⣺**\n    ǿڡwebĿ¼·һtest.jspģһҪݵҳ棬ʲôҳ `localhost/test.jsp` ֱܹӷʵˣ**й¶**...\n    ǿֱ `localhost/index.jsp` ԣĳһհ׵ҳ棬Ϊûлȡ `${message}` ֱӷˣ**Ӱû**\n\n#### \n\nǽǵ JSP ļڡWEB-INFļеġpageļ£WEB-INF Java Web ĬϵİȫĿ¼ǲûֱӷʵ_Ҳ˵ͨ `localhost/WEB-INF/` ķʽԶʲģ_\n\nҪ߸ͼ dispatcher-servlet.xml ļã\n\n\n\n```\n<bean id=\"viewResolver\"\n      class=\"org.springframework.web.servlet.view.InternalResourceViewResolver\">\n    <property name=\"prefix\" value=\"/WEB-INF/page/\" />\n    <property name=\"suffix\" value=\".jsp\" />\n</bean>\n\n```\n\n\n\nһ Spring MVC õһͼýѭһԼ**ͼǰ׺ͺ׺ȷһ Web ӦͼԴ·ġ**ʵЧ\n\n#### һ޸ HelloController\n\nǽ޸һ£\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-2ce49e171bd6d547.png)\n\n\n\n\n\n#### ڶͼ\n\nãɣ\n\n\n\n```\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<beans xmlns=\"http://www.springframework.org/schema/beans\"\n       xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n       xmlns:context=\"http://www.springframework.org/schema/context\"\n       xsi:schemaLocation=\"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd\">\n\n    <!--<bean id=\"simpleUrlHandlerMapping\"-->\n                                        <!-->-->\n    <!--<property name=\"mappings\">-->\n            <!--<props>-->\n                <!--&lt;!&ndash; /hello ·󽻸 id Ϊ helloController Ŀ&ndash;&gt;-->\n                <!--<prop key=\"/hello\">helloController</prop>-->\n            <!--</props>-->\n        <!--</property>-->\n    <!--</bean>-->\n    <!--<bean id=\"helloController\" ></bean>-->\n\n    <!-- ɨcontrollerµ -->\n    <context:component-scan base-package=\"controller\"/>\n    <bean id=\"viewResolver\"\n          class=\"org.springframework.web.servlet.view.InternalResourceViewResolver\">\n        <property name=\"prefix\" value=\"/WEB-INF/page/\" />\n        <property name=\"suffix\" value=\".jsp\" />\n    </bean>\n</beans>\n\n```\n\n\n\n####  index.jsp ļ\n\nڡWEB-INFļ½һpageļУindex.jspļ棺\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-88995fd05ccd0f80.png)\n\n\n\n\n\n#### ĲԴ\n\n `localhost/hello` ·ȷЧ\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-390fb571e9f6ff03.png)\n\n\n\n\n\n*   **ԭ**\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-a716a3ac8f7e541d.png)\n\n\n\n\n\nǴ߼ͼΪ index ټ `/WEB-INF/page/` ǰ׺ `.jsp` ׺ȷͼ·ˣԺͿԽеͼ롾pageļˣ\n\n*   **ע⣺**ʱý dispatcher-servlet.xml µ\n\n* * *\n\n## \n\nʹÿղ Spring MVC ҵ߼ĵһΪ̽ Spring MVC ĴηʽΪһ򵥵ıύݣ\n\n\n\n```\n<!DOCTYPE html>\n<%@ page language=\"java\" contentType=\"text/html; charset=UTF-8\"\n         pageEncoding=\"UTF-8\" import=\"java.util.*\" isELIgnored=\"false\"%>\n<html>\n<head>\n    <meta charset=\"utf-8\">\n    <title>Spring MVC ηʽ</title>\n</head>\n<body>\n<form action=\"/param\" role=\"form\">\n    û<br/>\n    룺<br/>\n    \n</form>\n</body>\n</html>\n\n```\n\n\n\nͳɣǾһ£\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-b50a42db8debde97.png)\n\n\n\n\n\n#### ʹ Servlet ԭ API ʵ֣\n\nǺ֪ύ `/param` Ŀ¼ʹ Servlet ԭ API ܻܲȡݣ\n\n\n\n```\n@RequestMapping(\"/param\")\npublic ModelAndView getParam(HttpServletRequest request,\n                         HttpServletResponse response) {\n    String userName = request.getParameter(\"userName\");\n    String password = request.getParameter(\"password\");\n\n    System.out.println(userName);\n    System.out.println(password);\n    return null;\n}\n\n```\n\n\n\nԳɹ\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-df21058b7ef71924.png)\n\n\n\n\n\n#### ʹͬƥ\n\nǿ԰ѷβóɺǰ̨һķȡݣͬƥ򣩣\n\n\n\n```\n@RequestMapping(\"/param\")\npublic ModelAndView getParam(String userName,\n                             String password) {\n    System.out.println(userName);\n    System.out.println(password);\n    return null;\n}\n\n```\n\n\n\nԳɹ\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-55a1c296c778e506.png)\n\n\n\n\n\n*   **⣺** ֻǰ̨ǿϣǲϣ\n*   **** ʹ `@RequestParam(\"ǰ̨\")` ע룺\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-a649ad50866a01c5.png)\n\n\n\n\n\n*   **`@RequestParam` עϸڣ**\n    ע`value``required``defaultvalue`\n*   `value` ָ `name` Եʲô`value` ԶĬϲд\n*   `required` ǷҪиòΪtrueߡfalse\n*   `defaultvalue` Ĭֵ\n\n#### ʹģʹ\n\n*   **Ҫ ǰֱ̨ģеֶһ**\n\nΪǵıһ User ģͣ\n\n\n\n```\npackage pojo;\n\npublic class User {\n\n    String userName;\n    String password;\n\n    /* getter and setter */\n}\n\n```\n\n\n\nȻȻɹ\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-471d26bcb335aee6.png)\n\n\n\n\n\n#### \n\n*   **ע⣺**  Servlet еһ÷ֻ POST ЧΪֱӴ request\n\nǿͨ Spring MVC ַɣ web.xml ӣ\n\n\n\n```\n<filter>\n    <filter-name>CharacterEncodingFilter</filter-name>\n    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>\n    <init-param>\n        <param-name>encoding</param-name>\n        <!-- ñʽ -->\n        <param-value>utf-8</param-value>\n    </init-param>\n</filter>\n<filter-mapping>\n    <filter-name>CharacterEncodingFilter</filter-name>\n    <url-pattern>/*</url-pattern>\n</filter-mapping>\n\n```\n\n\n\n* * *\n\n## \n\nͨ棬֪ôݣܽ POST ⣬ôôأΪڡpage´һtest2.jsp\n\n\n\n```\n<!DOCTYPE html>\n<%@ page language=\"java\" contentType=\"text/html; charset=UTF-8\"\n         pageEncoding=\"UTF-8\" import=\"java.util.*\" isELIgnored=\"false\" %>\n<html>\n<head>\n    <title>Spring MVC ݻ</title>\n</head>\n<body>\n<h1>ݣ${message}</h1>\n</body>\n</html>\n\n```\n\n\n\n#### ʹ Servlet ԭ API ʵ\n\nһ Servlet ԭ API Ƿ\n\n\n\n```\n@RequestMapping(\"/value\")\npublic ModelAndView handleRequest(HttpServletRequest request,\n                                  HttpServletResponse response) {\n    request.setAttribute(\"message\",\"ɹ\");\n    return new ModelAndView(\"test1\");\n}\n\n```\n\n\n\nַ룺`localhost/value` \n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-66d2f24a876306e6.png)\n\n\n\n\n\n#### ʹ Spring MVC ṩ ModelAndView \n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-360ce67947be817d.png)\n\n\n\n\n\n#### ʹ Model \n\n Spring MVC Уͨʹķʽݣ\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-685dd384904ad28f.png)\n\n\n\n\n\n*   **ʹ `@ModelAttribute` ע⣺**\n\n\n\n```\n@ModelAttribute\npublic void model(Model model) {\n    model.addAttribute(\"message\", \"עɹ\");\n}\n\n@RequestMapping(\"/value\")\npublic String handleRequest() {\n    return \"test1\";\n}\n\n```\n\n\n\nдͻڷʿ handleRequest() ʱȵ model()  `message` ӽҳȥͼпֱӵãдᵼ¸ÿеķȵ model() ͬҲܷ㣬ΪԼָݡ\n\n* * *\n\n## ͻת\n\nǰ治ǵַ `/hello` ת index.jsp  `/test` ת test.jspЩǷ˵תҲ `request.getRequestDispatcher(\"ַ\").forward(request, response);`\n\nνпͻתأǼ HelloController бд\n\n\n\n```\n@RequestMapping(\"/hello\")\npublic ModelAndView handleRequest(javax.servlet.http.HttpServletRequest httpServletRequest, javax.servlet.http.HttpServletResponse httpServletResponse) throws Exception {\n    ModelAndView mav = new ModelAndView(\"index\");\n    mav.addObject(\"message\", \"Hello Spring MVC\");\n    return mav;\n}\n\n@RequestMapping(\"/jump\")\npublic ModelAndView jump() {\n    ModelAndView mav = new ModelAndView(\"redirect:/hello\");\n    return mav;\n}\n\n```\n\n\n\nʹ `redirect:/hello` ͱʾҪת `/hello` ·ڵַ룺`localhost/jump` Զת `/hello` ·£\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-390fb571e9f6ff03.png)\n\n\n\n\n\nҲã\n\n\n\n```\n@RequestMapping(\"/jump\")\npublic String jump() {\n    return \"redirect: ./hello\";\n}\n\n```\n\n\n\n* * *\n\n## ļϴ\n\nعһ´ͳļϴأ[](https://www.jianshu.com/p/e7837435bf4c)\n\nһ Spring MVC ʵļϴ\n\n*   **ע⣺** Ҫȵ `commons-io-1.3.2.jar`  `commons-fileupload-1.2.1.jar` \n\n#### һϴ\n\n dispatcher-servlet.xml һ䣺\n\n\n\n```\n<bean id=\"multipartResolver\" class=\"org.springframework.web.multipart.commons.CommonsMultipartResolver\"/>\n\n```\n\n\n\nϴ֧ܵ\n\n#### ڶд JSP\n\nļΪ upload.jspԴڡpage£\n\n\n\n```\n<%@ page contentType=\"text/html;charset=UTF-8\" language=\"java\" %>\n<html>\n<head>\n    <title>ļϴ</title>\n</head>\n<body>\n<form action=\"/upload\" method=\"post\" enctype=\"multipart/form-data\">\n    \n    \n</form>\n</body>\n</html>\n\n```\n\n\n\n#### д\n\n Packagecontroller½UploadControllerࣺ\n\n\n\n```\npackage controller;\n\nimport org.springframework.stereotype.Controller;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestParam;\nimport org.springframework.web.multipart.MultipartFile;\nimport org.springframework.web.servlet.ModelAndView;\n\n@Controller\npublic class UploadController {\n\n    @RequestMapping(\"/upload\")\n    public void upload(@RequestParam(\"picture\") MultipartFile picture) throws Exception {\n        System.out.println(picture.getOriginalFilename());\n    }\n\n    @RequestMapping(\"/test2\")\n    public ModelAndView upload() {\n        return new ModelAndView(\"upload\");\n    }\n\n}\n\n```\n\n\n\n#### Ĳ\n\nַ룺`localhost/test2` ѡļϴԳɹ\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7896890-531c47b14dbc71e5.png)\n\n\n\n\n\n* * *\n\n#### οϣ\n\n*   Java EE Ͽ\n*   Spring ʵս\n*   [How2j Spring MVC ϵн̳](http://how2j.cn/k/springmvc/springmvc-springmvc/615.html)\n*   ȫܵİٶȺܵĴ\n\n* * *\n\n> ӭתأתע\n> ID[@û](https://www.jianshu.com/u/a40d61a49221)\n> github[wmyskxz](https://github.com/wmyskxz/)\n> ӭע΢źţwmyskxz\n> Լѧϰ & ѧϰ & \n> ҪҲԼqqȺ3382693\n\n\n\nߣû\nӣhttps://www.jianshu.com/p/91a2d0a1e45a\nԴ\nȨСҵתϵ߻Ȩҵתע"
  },
  {
    "path": "docs/Spring全家桶/SpringMVC/SpringMVC如何实现文件上传.md",
    "content": "\n\n\n\n# Spring MVC ļϴ\n\n\n\n\n\n\n\n\n\n\nSpringöԶ·ϴ֧֣רڴwebӦеļϴͨעһɲε`MultipartResolver`öļ·ϴ֧֡ýӿڶ`org.springframework.web.multipart`¡SpringΪ[_һļϴ_](http://jakarta.apache.org/commons/fileupload)ṩ`MultipartResolver`ӿڵһʵ֣ΪServlet 3.0·תṩһʵ֡\n\n<section>\n\nĬ£SpringĶ·ϴ֧ǲģΪЩϣԼ·SpringĶ·ϴ֧֣ҪwebӦõһ·ÿ󣬽ǲһಿģ̱һ·ע`MultipartResolver`ᱻ֮еĶ·ϴԾһԴˡһ䷭Ĳãmultipartɶ·ǶಿֻСĶע˴\n\n</section>\n\n\n\n\n\n\n\n# Spring MVC ʹMultipartResolverCommons FileUploadļ\n\n\n\n2018-07-26 14:28 \n\n\n\n\n\n\n\nĴչʾʹһͨõĶ·ϴ`CommonsMultipartResolver`\n\n<section>\n\n```\n<bean id=\"multipartResolver\" class=\"org.springframework.web.multipart.commons.CommonsMultipartResolver\">\n\n    <!-- ֵ֧һԣֵ֧ļСֽΪλ -->\n    <property name=\"maxUploadSize\" value=\"100000\"/>\n\n</bean>\n\n```\n\nȻҪö·Ҫclasspath·׼jarʹõͨõĶ·ϴ`CommonsMultipartResolver`Ҫjar`commons-fileupload.jar`\n\nSpring`DispatcherServlet`⵽һಿʱἤĶ·󽻸ѵǰ`HttpServletRequest`װһֶ֧·ļϴ`MultipartHttpServletRequest``MultipartHttpServletRequest`㲻Իȡö·еϢĿлøö·ݱ\n\n</section>\n\n\n\n\n\n\n\n# Spring MVC Servlet 3.0µMultipartResolver\n\n\n\n2018-07-26 14:29 \n\n\n\n\n\n\n\nҪʹûServlet 3.0Ķ·תܣ`web.xml`Ϊ`DispatcherServlet`һ`multipart-config`ԪأͨServlet̵ķʹ`javax.servlet.MultipartConfigElement`עᣬԼԼServlet࣬ʹ`javax.servlet.annotation.MultipartConfig`ע⡣ļС洢λõѡServletעᣬΪServlet 3.0ڽMultipartResolverĲ㼶ЩϢ\n\n<section>\n\nͨһַʽServlet 3.0·תܣͿ԰һ`StandardServletMultipartResolver`ӵSpringȥˣ\n\n```\n<bean id=\"multipartResolver\" class=\"org.springframework.web.multipart.support.StandardServletMultipartResolver\">\n</bean>\n```\n\n</section>\n\n\n\n\n\n\n# Spring MVC еļϴ\n\n\n\n2018-07-26 14:30 \n\n\n\n\n\n\n\n`MultipartResolver`ɴʱһ̴ȣһļϴıֱϴԣ`enctype=\"multipart/form-data\"`֪ζԶ·ϴıб루encode\n\n<section>\n\n```\n<html>\n    <head>\n        <title>Upload a file please</title>\n    </head>\n    <body>\n        <h1>Please upload a file</h1>\n        <form method=\"post\" action=\"/form\" enctype=\"multipart/form-data\">\n            \n            \n            \n        </form>\n    </body>\n</html>\n\n```\n\nһǴһܴļϴĿҪĿ[һע`@Controller`Ŀ](http://docs.spring.io/spring-framework/docs/4.2.4.RELEASE/spring-framework-reference/html/mvc.html#mvc-ann-controller)һܵķ`MultipartHttpServletRequest``MultipartFile`\n\n```\n@Controller\npublic class FileUploadController {\n\n    @RequestMapping(path = \"/form\", method = RequestMethod.POST)\n    public String handleFormUpload(@RequestParam(\"name\") String name, @RequestParam(\"file\") MultipartFile file) {\n\n        if (!file.isEmpty()) {\n            byte[] bytes = file.getBytes();\n            // store the bytes somewhere\n            return \"redirect:uploadSuccess\";\n        }\n\n        return \"redirect:uploadFailure\";\n    }\n\n}\n\n```\n\n`@RequestParam`עνӦеĶֶεġУõ`byte[]`ļݣֻûκ¡ʵӦУܻὫ浽ݿ⡢洢ļϵͳϣĴ\n\nʹServlet 3.0Ķ·תʱҲʹ`javax.servlet.http.Part`Ϊ\n\n```\n@Controller\npublic class FileUploadController {\n\n    @RequestMapping(path = \"/form\", method = RequestMethod.POST)\n    public String handleFormUpload(@RequestParam(\"name\") String name, @RequestParam(\"file\") Part file) {\n\n        InputStream inputStream = file.getInputStream();\n        // store bytes from uploaded file somewhere\n\n        return \"redirect:uploadSuccess\";\n    }\n\n}\n```\n\n</section>\n\n\n\n\n\n\n\n# Spring MVC ͻ˷ļϴ\n\n\n\n2018-07-26 14:30 \n\n\n\n\n\n\n\nʹRESTfulĳ£ĿͻҲֱύ·ļһڽҲͬáͬǣύļͼ򵥵ıֶΣͻ˷͵ݿԸӸӣݿָΪĳضͣcontent type磬һ·ϴܵһǸļڶǸJSONʽݣ\n\n<section>\n\n```\n    POST /someUrl\n    Content-Type: multipart/mixed\n\n    --edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp\n    Content-Disposition: form-data; name=\"meta-data\"\n    Content-Type: application/json; charset=UTF-8\n    Content-Transfer-Encoding: 8bit\n\n    {\n        \"name\": \"value\"\n    }\n    --edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp\n    Content-Disposition: form-data; name=\"file-data\"; filename=\"file.properties\"\n    Content-Type: text/xml\n    Content-Transfer-Encoding: 8bit\n    ... File Data ...\n\n```\n\nΪ`meta-data`Ĳ֣ͨϵ`@RequestParam(\"meta-data\") String metadata`áǲΪJSONʽݵܸͨһӦǿͶ󣬾`@RequestBody`ͨ`HttpMessageConverter`һתһһ\n\nǿܵģʹ`@RequestPart`עʵ֣`@RequestParam`ע⽫ʹض·屻`HttpMessageConverter`תʱǶ·вͬͲ`'Content-Type'`\n\n```\n@RequestMapping(path = \"/someUrl\", method = RequestMethod.POST)\npublic String onSubmit(@RequestPart(\"meta-data\") MetaData metadata, @RequestPart(\"file-data\") MultipartFile file) {\n\n    // ...\n\n}\n\n```\n\nע`MultipartFile`ܹ`@RequestParam``@RequestPart`ע»õģַõݡķ`@RequestPart(\"meta-data\") MetaData`Ϊеͷ`'Content-Type'`ΪJSONݣȻͨ`MappingJackson2HttpMessageConverter`תضĶ\n\n</section>\n\n\n\n\n"
  },
  {
    "path": "docs/Spring全家桶/SpringMVC/SpringMVC常见注解.md",
    "content": "## 1. \n   ڱ̳Уǽ̽ org.springframework.web.bind.annotation е Spring Web ע͡\n\n## 2. @RequestMapping\n\n򵥵˵@RequestMapping @Controller ڲ򷽷 ʹã\n\n·ƺֵӳ䵽ĸ URL\nݵ HTTP \nparams HTTP Ĵڡڻֵ\nheaders HTTP ͷĴڡڻֵ\nģ÷ HTTP Щý\nproduces÷ HTTP ӦЩý\nһ򵥵ʾ\n\n````\n@Controller\nclass VehicleController {\n\n    @RequestMapping(value = \"/vehicles/home\", method = RequestMethod.GET)\n    String home() {\n        return \"home\";\n    }\n}\n````\n༶ӦôעͣǿΪ @Controller ед򷽷ṩĬá Ψһ Spring ʹ÷øǵḽ·ֵ URL\n\n磬úЧһģ\n\n````\n@Controller\n@RequestMapping(value = \"/vehicles\", method = RequestMethod.GET)\nclass VehicleController {\n\n    @RequestMapping(\"/home\")\n    String home() {\n        return \"home\";\n    }\n}\n````\n\n⣬@GetMapping@PostMapping@PutMapping@DeleteMapping @PatchMapping @RequestMapping Ĳͬ壬HTTP ѷֱΪGETPOSTPUTDELETE PATCH\n\nЩ Spring 4.3 汾ʼá\n\n## 3 @RequestBody\n\nǼ@RequestBody HTTP ӳ䵽һ\n\n````\n@PostMapping(\"/save\")\nvoid saveVehicle(@RequestBody Vehicle vehicle) {\n// ...\n}\n````\nлԶģȡ͡\n\n## 4 @PathVariable\n˵˵@PathVariable\n\nעָʾ󶨵 URI ģ ǿʹ @RequestMapping עָ URI ģ壬ʹ @PathVariable 󶨵ģ岿֮һ\n\nǿʹƻֵʵһ㣺\n\n````\n@RequestMapping(\"/{id}\")\nVehicle getVehicle(@PathVariable(\"id\") long id) {\n// ...\n}\n````\nģвֵ뷽ƥ䣬ǾͲעָ\n\n````\n@RequestMapping(\"/{id}\")\nVehicle getVehicle(@PathVariable long id) {\n// ...\n}\n````\n⣬ǿͨĲΪ false ·Ϊѡ\n\n````\n@RequestMapping(\"/{id}\")\nVehicle getVehicle(@PathVariable(required = false) long id) {\n// ...\n}\n````\n## 5. @RequestParam\n   We use @RequestParam for accessing HTTP request parameters:\n````\n@RequestMapping\nVehicle getVehicleByParam(@RequestParam(\"id\") long id) {\n// ...\n}\n````\n @PathVariable עͬѡ\n\nЩ֮⣬ Spring зûֵΪֵʱǿʹ @RequestParam ָעֵ ΪˣǱ defaultValue \n\nṩĬֵʽ required Ϊ false\n````\n@RequestMapping(\"/buy\")\nCar buyCar(@RequestParam(defaultValue = \"5\") int seatCount) {\n// ...\n}\n````\n˲֮⣬ǻԷ HTTP 󲿷֣cookie ͱͷ \n\nǿԷֱʹע@CookieValue @RequestHeader ǡ\n\n\n## 6. Response Handling Annotations\nڽĲУǽ Spring MVC в HTTP Ӧע͡\n\n### 6.1 @ResponseBody\n@ResponseBody 򷽷Spring ὫĽΪӦ\n\n````\n@ResponseBody\n@RequestMapping(\"/hello\")\nString hello() {\nreturn \"Hello World!\";\n}\n````\nעע @Controller ࣬򷽷ʹ\n\n### 6.2 @ExceptionHandler\n\nʹôעͣǿһԶ򷽷 򷽷׳κָ쳣ʱSpring ô˷\n\n쳣Ϊݸ\n````\n@ExceptionHandler(IllegalArgumentException.class)\nvoid onIllegalArgumentException(IllegalArgumentException exception) {\n// ...\n}\n````\n\n### 6.3 @ResponseStatus\nʹôעͶ򷽷עָͣӦ HTTP ״̬ ǿʹ code  value ״̬롣\n\n⣬ǿʹ reason ṩԭ\n\nҲԽ@ExceptionHandler һʹã\n\n@ExceptionHandler(IllegalArgumentException.class)\n@ResponseStatus(HttpStatus.BAD_REQUEST)\nvoid onIllegalArgumentException(IllegalArgumentException exception) {\n// ...\n}\n\nй HTTP Ӧ״̬ĸϢʱġ\n\n## 7  Webע\nһЩעͲֱӹ HTTP Ӧ ڽĲУǽġ\n\n### 7.1 @Controller\nǿʹ@Controller һSpring MVC  йظϢǹ Spring Bean Annotations ¡\n\n### 7.2 @RestController\n@RestController @Controller @ResponseBody\n\nˣǵЧģ\n\n````\n@Controller\n@ResponseBody\nclass VehicleRestController {\n// ...\n}\n````\n\n````\n@RestController\nclass VehicleRestController {\n// ...\n}\n````\n### 7.3 @ModelAttribute\nͨע⣬ǿͨṩģͼѾ MVC @Controller ģеԪأ\n\n````\n@PostMapping(\"/assemble\")\nvoid assembleVehicle(@ModelAttribute(\"vehicle\") Vehicle vehicleInModel) {\n// ...\n}\n````\n@PathVariable @RequestParam һͬƣǲָģͼ\n\n````\n@PostMapping(\"/assemble\")\nvoid assembleVehicle(@ModelAttribute Vehicle vehicle) {\n// ...\n}\n````\n⣬@ModelAttributeһ;עһSpringԶķֵӵģУ\n\n````\n@ModelAttribute(\"vehicle\")\nVehicle getVehicle() {\n// ...\n}\n````\nǰһǲָģͼSpring Ĭʹ÷ƣ\n````\n@ModelAttribute\nVehicle vehicle() {\n// ...\n}\n````\n Spring 򷽷֮ǰ @ModelAttribute ע͵ķ\n\nй @ModelAttribute ĸϢıġ\n\n### 7.4 @CrossOrigin\n@CrossOrigin Ϊע͵򷽷ÿͨţ\n\n````\n@CrossOrigin\n@RequestMapping(\"/hello\")\nString hello() {\nreturn \"Hello World!\";\n}\n````\nһ࣬е򷽷\n\nǿʹôע͵Ĳ΢ CORS Ϊ\n\nйϸϢʱġ\n\n\n# ο\nhttps://www.baeldung.com/spring-annotations\n"
  },
  {
    "path": "docs/Spring全家桶/SpringMVC源码分析/DispatcherServlet初始化流程.md",
    "content": "[һƪ](https://my.oschina.net/funcy/blog/4696657 \"һƪ\")Уͨһ򵥵 demo ɹ springmvc Ӧãṩ demo У֪ tomcat ʱ `MyWebApplicationInitializer#onStartup` Ȼ spring ô tomcat  spring أ\n\n### 1\\. servlet ʼ`DispatcherServlet#init`\n\nٻ `MyWebApplicationInitializer#onStartup` \n\n```\n@Override\npublic void onStartup(ServletContext servletContext) {\n   System.out.println(\"webApplicationInitializer ...\");\n   //  spring  applicationContext\n   AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();\n   context.register(MvcConfig.class);\n\n   // ʵ DispatcherServlet\n   DispatcherServlet servlet = new DispatcherServlet(context);\n\n   // DispatcherServletעᵽservlet\n   ServletRegistration.Dynamic registration = servletContext.addServlet(\"app\", servlet);\n   registration.setLoadOnStartup(1);\n   registration.addMapping(\"/*\");\n}\n\n```\n\nδ׼һ `AnnotationConfigWebApplicationContext`Ϊ `DispatcherServlet` УȻ servlet  `DispatcherServlet`servlet ʱͻ spring ˡҪľ `DispatcherServlet`Ǿ servlet.\n\n `DispatcherServlet` ļ̳нṹ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-1df54493011b3fc9cb9cafbb944f1a88256.png)\n\nͼԿspring ṩġ servlet ص`HttpServletBean``FrameworkServlet`  `DispatcherServlet`Ϊ servlet֪ʼΪ `GenericServlet#init()`Ҳ servlet ڷǵķҲ￪ʼ\n\n `DispatcherServlet` ʵ `HttpServletBean``FrameworkServlet``DispatcherServlet#init()` ʵϼ̳ `HttpServletBean#init`\n\n```\n@Override\npublic final void init() throws ServletException {\n\n    PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);\n    if (!pvs.isEmpty()) {\n        try {\n            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);\n            ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());\n            bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));\n            initBeanWrapper(bw);\n            bw.setPropertyValues(pvs, true);\n        }\n        catch (BeansException ex) {\n            ...\n        }\n    }\n\n    // ʼ servlet beanspringþе\n    initServletBean();\n}\n\n```\n\nԿһЩãȻ͵ `initServletBean`ûһЩ spring ʵԵݡǼвҪķ·£\n\n```\n-HttpServletBean#init\n -FrameworkServlet#initServletBean\n  -FrameworkServlet#initWebApplicationContext\n\n```\n\nһֱ `FrameworkServlet#initWebApplicationContext`\n\n```\nprotected WebApplicationContext initWebApplicationContext() {\n    // ȡΪWebServerApplicationContextĸõĽΪnull\n    WebApplicationContext rootContext =\n            WebApplicationContextUtils.getWebApplicationContext(getServletContext());\n    WebApplicationContext wac = null;\n\n    if (this.webApplicationContext != null) {\n        // webApplicationContextMyWebApplicationInitializer#onStart\n        // AnnotationConfigWebApplicationContext\n        wac = this.webApplicationContext;\n        if (wac instanceof ConfigurableWebApplicationContext) {\n            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;\n            if (!cwac.isActive()) {\n                if (cwac.getParent() == null) {\n                    cwac.setParent(rootContext);\n                }\n                //  AbstractApplicationContext#refresh \n                configureAndRefreshWebApplicationContext(cwac);\n            }\n        }\n    }\n    // wacΪnull,ﲻ\n    if (wac == null) {\n        wac = findWebApplicationContext();\n    }\n    // wacΪnull,ﲻ\n    if (wac == null) {\n        // WebApplicationContextAbstractApplicationContext#refresh\n        wac = createWebApplicationContext(rootContext);\n    }\n\n    // ʵϣrefreshEventReceivedΪtrueifĴ벢ִ\n    if (!this.refreshEventReceived) {\n        synchronized (this.onRefreshMonitor) {\n            // ˢӦģspringmvcش\n            onRefresh(wac);\n        }\n    }\n\n    if (this.publishContext) {\n        //  WebApplicationContextΪservletContext һԣ뵽 servletContext \n        // ֮Ϳʹ\n        // WebApplicationContextUtils.getWebApplicationContext(ServletContext, String attrName)\n        // ȡ\n        String attrName = getServletContextAttributeName();\n        getServletContext().setAttribute(attrName, wac);\n    }\n\n    return wac;\n}\n\n```\n\nزעͣʵ ҪĴΪ\n\n```\nprotected WebApplicationContext initWebApplicationContext() {\n    ...\n    //  AbstractApplicationContext#refresh \n    configureAndRefreshWebApplicationContext(cwac);\n    ...\n    return wac;\n}\n\n```\n\nط£\n\n> FrameworkServlet#configureAndRefreshWebApplicationContext\n\n```\nprotected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {\n    if (ObjectUtils.identityToString(wac).equals(wac.getId())) {\n        if (this.contextId != null) {\n            wac.setId(this.contextId);\n        }\n        else {\n            // Generate default id...\n            wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +\n                    ObjectUtils.getDisplayString(getServletContext().getContextPath()) + \n                    '/' + getServletName());\n        }\n    }\n    wac.setServletContext(getServletContext());\n    wac.setServletConfig(getServletConfig());\n    wac.setNamespace(getNamespace());\n    // ¼spring¼\n    // ʮҪ\n    wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));\n    ConfigurableEnvironment env = wac.getEnvironment();\n    if (env instanceof ConfigurableWebEnvironment) {\n        ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());\n    }\n    // չ㣬ûʲôܣԺչ\n    postProcessWebApplicationContext(wac);\n    applyInitializers(wac);\n    // þ AbstractApplicationContext.refresh\n    wac.refresh();\n}\n\n```\n\nʵϻ `ConfigurableWebApplicationContext` һЩԣ `AbstractApplicationContext#refresh`  spring  `AbstractApplicationContext#refresh` ķԲο [spring ֮ǰ׼](https://my.oschina.net/funcy/blog/4633169 \"spring֮ǰ׼\")\n\nspring ˡ\n\n### 2. `SourceFilteringListener`¼\n\nи⣺ springmvc У֪ spring ʶ `@Controller` `RequestMapping`/`@PostMapping`/`@GetMapping` עе·װΪһ uriȴⲿʣһ·ƺ spring ûЩôⲿֵĹеأ\n\nʵϣspring ⲿֵĹɵģҲ\n\n```\nwac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));\n\n```\n\n spring  ¼ spring ɺá\n\n spring ¼ݣԲο [spring ֮̽ spring ¼](https://my.oschina.net/funcy/blog/4713339 \"spring֮̽spring ¼\")ֱ˵ۣspring ṩ `ApplicationEventPublisher#publishEvent(Object)`¼`ApplicationEvent`¼ `ApplicationListener` ¼ spring ͨ `ApplicationEventPublisher#publishEvent(Object)`  `ApplicationEvent`¼ʱ`ApplicationListener` ¼\n\n `SourceFilteringListener`\n\n```\npublic class SourceFilteringListener implements GenericApplicationListener, SmartApplicationListener {\n\n    private final Object source;\n\n    @Nullable\n    private GenericApplicationListener delegate;\n\n    /**\n     * 췽 event  listener\n     */\n    public SourceFilteringListener(Object source, ApplicationListener<?> delegate) {\n        this.source = source;\n        this.delegate = (delegate instanceof GenericApplicationListener ?\n                (GenericApplicationListener) delegate : new GenericApplicationListenerAdapter(delegate));\n    }\n\n    /**\n     * ¼\n     */\n    @Override\n    public void onApplicationEvent(ApplicationEvent event) {\n        if (event.getSource() == this.source) {\n            // ¼\n            onApplicationEventInternal(event);\n        }\n    }\n\n    /**\n     *  ¼\n     */\n    protected void onApplicationEventInternal(ApplicationEvent event) {\n        if (this.delegate == null) {\n            throw new IllegalStateException(...);\n        }\n        // ջǵô¼onApplicationEvent\n        this.delegate.onApplicationEvent(event);\n    }\n\n    // ʡһЩ\n    ...\n\n```\n\nԿ`SourceFilteringListener` ͨ췽 `ContextRefreshListener` ʵȻ `SourceFilteringListener#onApplicationEvent` Уյõ `ContextRefreshListener#onApplicationEvent` \n\n `ContextRefreshListener`\n\n> FrameworkServlet.ContextRefreshListener\n\n```\nprivate class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {\n\n    @Override\n    public void onApplicationEvent(ContextRefreshedEvent event) {\n        FrameworkServlet.this.onApplicationEvent(event);\n    }\n}\n\n```\n\n `FrameworkServlet` ڲܼ࣬򵥣յõ `FrameworkServlet#onApplicationEvent`\n\n```\npublic void onApplicationEvent(ContextRefreshedEvent event) {\n    // ޸״̬ܹؼк\n    // FrameworkServlet#initWebApplicationContextonRefresh(...)Ͳ\n    this.refreshEventReceived = true;\n    synchronized (this.onRefreshMonitor) {\n         // ߼\n         onRefresh(event.getApplicationContext());\n    }\n}\n\n```\n\n `DispatcherServlet#onRefresh` ˣ\n\n```\n@Override\nprotected void onRefresh(ApplicationContext context) {\n    initStrategies(context);\n}\n\n/**\n * springmvcռؾ\n * Уʼspringmvcĸ\n */\nprotected void initStrategies(ApplicationContext context) {\n    initMultipartResolver(context);\n    initLocaleResolver(context);\n    initThemeResolver(context);\n    initHandlerMappings(context);\n    initHandlerAdapters(context);\n    initHandlerExceptionResolvers(context);\n    initRequestToViewNameTranslator(context);\n    initViewResolvers(context);\n    initFlashMapManager(context);\n}\n\n```\n\nԿеķ `DispatcherServlet#initStrategies`ֻУҳʼ springmvc \n\n### 3. `DispatcherServlet#initStrategies`ʼ springmvc \n\nspring ɺ󣬻ᷢ¼Ȼɼ `SourceFilteringListener` ¼ִм߼յõ `DispatcherServlet#initStrategies`ǽ `DispatcherServlet#initStrategies` ִй̡\n\nʵܼ򵥣 9 д룬ÿд붼ʼ springmvc һ `initMultipartResolver`\n\n```\npublic static final String MULTIPART_RESOLVER_BEAN_NAME = \"multipartResolver\";\n\nprivate void initMultipartResolver(ApplicationContext context) {\n    try {\n        // springлȡmultipartResolver\n        this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);\n    }\n    catch (NoSuchBeanDefinitionException ex) {\n        // ȡʧܣĬΪnull\n        this.multipartResolver = null;\n        }\n    }\n}\n\n```\n\n`multipartResolver` ļϴ bean spring УǴļϴʱһ `multipartResolver` bean\n\n```\n@Bean(name = \"multipartResolver\")\npublic MultipartResolver multipartResolver() {\n    CommonsMultipartResolver resolver = new CommonsMultipartResolver();\n    resolver.setDefaultEncoding(\"UTF-8\");\n    resolver.setResolveLazily(true);\n    resolver.setMaxInMemorySize(40960);\n    //ϴļΪ1G\n    resolver.setMaxUploadSize(1024 * 1024 * 1024);\n    return resolver;\n}\n\n```\n\nδ `multipartResolver` beanspring ĬΪ nullͲܽļϴˡ\n\n `springmvc` `HandlerMappings` ĳʼ̣\n\n> DispatcherServlet\n\n```\npublic static final String HANDLER_MAPPING_BEAN_NAME = \"handlerMapping\";\n\nprivate static final String DEFAULT_STRATEGIES_PATH = \"DispatcherServlet.properties\";\n\nprivate static final Properties defaultStrategies;\n\nstatic {\n    try {\n        // staticмDispatcherServlet.propertiesļ\n        ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, \n                DispatcherServlet.class);\n        defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);\n    }\n    catch (IOException ex) {\n        throw new IllegalStateException(...);\n    }\n}\n\n/**\n * handlerMappings \n */\n@Nullable\nprivate List<HandlerMapping> handlerMappings;\n\n/**\n * ʼ HandlerMappings\n * 1\\. spring лȡ HandlerMapping bean\n *     ȡɹѵõĽֵhandlerMappings\n * 2\\. δãȡĬϵ HandlerMapping bean\n */\nprivate void initHandlerMappings(ApplicationContext context) {\n    this.handlerMappings = null;\n    if (this.detectAllHandlerMappings) {\n        // ʵHandlerMappingӿڵbean\n        Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils\n                .beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);\n        // ﲻΪգ\n        if (!matchingBeans.isEmpty()) {\n            this.handlerMappings = new ArrayList<>(matchingBeans.values());\n            // SpringǸĽд\n            // ǰhandlerMappingԴ׸һ\n            AnnotationAwareOrderComparator.sort(this.handlerMappings);\n        }\n    }\n    else {\n        try {\n            HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);\n            this.handlerMappings = Collections.singletonList(hm);\n        }\n        catch (NoSuchBeanDefinitionException ex) {\n            // Ignore, we'll add a default HandlerMapping later.\n        }\n    }\n    if (this.handlerMappings == null) {\n        // δhandlerMappingsȡĬϵ handlerMappings\n        this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);\n    }\n}\n\n/**\n * ȡĬϵĲ\n */\nprotected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {\n    String key = strategyInterface.getName();\n    // ȡļDispatcherServlet.propertiesĬϵ class \n    String value = defaultStrategies.getProperty(key);\n    if (value != null) {\n        String[] classNames = StringUtils.commaDelimitedListToStringArray(value);\n        List<T> strategies = new ArrayList<>(classNames.length);\n        for (String className : classNames) {\n            try {\n                // ʹ÷䴴bean\n                Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());\n                Object strategy = createDefaultStrategy(context, clazz);\n                strategies.add((T) strategy);\n            }\n            catch (ClassNotFoundException ex) {\n                throw new BeanInitializationException(...);\n            }\n            catch (LinkageError err) {\n                throw new BeanInitializationException(...);\n            }\n        }\n        return strategies;\n    }\n    else {\n        return new LinkedList<>();\n    }\n}\n\n```\n\nʼ `HandlerMappings` ʱ\n\n1.  ȴ spring лȡ `HandlerMapping` beanȡɹʵҲܻãѵõĽֵ `DispatcherServlet`  `handlerMappings` ԣ\n2.  δʧܣ spring δ `HandlerMapping` ȡĬϵ `HandlerMapping` bean.\n3.  ȡĬϵ `HandlerMapping` bean ʱȡ `DispatcherServlet.properties` ãȻʹ÷ʵ\n\n `DispatcherServlet.properties` ļļλ` spring-webmvc/src/main/resources/org/springframework/web/servlet/DispatcherServlet.properties`£\n\n```\norg.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver\n\norg.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver\n\norg.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\\\n    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\\\n    org.springframework.web.servlet.function.support.RouterFunctionMapping\n...\n\n```\n\n`DispatcherServlet#initStrategies`  `initXxx()` ƣͲһһˡ\n\n### 4\\. ܽ\n\nҪ springmvc ̣ܽ£\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-7ab3fae1e39f545c1d7c1811351227fa434.png)\n\n1.  servlet  tomcatʱͨ spi ִ `ServletContainerInitializer#onStartup`  springmvc ṩ `SpringServletContainerInitializer` ʵ֣ `SpringServletContainerInitializer#onStartup` ᱻã\n2.  `SpringServletContainerInitializer#onStartup` Уspring  `WebApplicationInitializer#onStartup`  `MyWebApplicationInitializer` ʵ֣ `MyWebApplicationInitializer#onStartup` ᱻã\n3.   `MyWebApplicationInitializer#onStartup`  Ǵһ `applicationContext` 󣬽 `DispatcherServlet` 󶨣Ȼ `DispatcherServlet` עᵽ servlet У tomcat\n4.  `DispatcherServlet` עᵽ servlet У tomcat󣬸 servlet ڣ`DispatcherServlet#init` ᱻã\n5.  `DispatcherServlet#init` лִ spring ̣spring 󣬻ᷢ¼\n6.  spring ɺ`ContextRefreshListener`  spring ¼`FrameworkServlet.ContextRefreshListener#onApplicationEvent` ᱻãõõ `DispatcherServlet#initStrategies`\n7.  spring  `DispatcherServlet#initStrategies` гʼ `MultipartResolver``LocaleResolver` νĳʼʵǻȡ򴴽Ӧ beanȻֵ `DispatcherServlet` ԡ\n\nˣspringmvc ̾ˡǶûп **spring  `@RequestMapping` **ô spring δ һƪ½\n\n* * *\n\n_ԭӣ[https://my.oschina.net/funcy/blog/4710330](https://my.oschina.net/funcy/blog/4710330) ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע_"
  },
  {
    "path": "docs/Spring全家桶/SpringMVC源码分析/RequestMapping初始化流程.md",
    "content": "ǰУǷ `DispatcherServlet` ʼ̣Ľ `RequestMapping` ʼ̡˵ `RequestMapping` ʼֱ̣˵ spring  `@RequestMaping` עĹ̡\n\n### 1\\. ̸ `@EnableWebMvc`\n\n [spring mvc ֮ springmvc demo  @EnableWebMvc ע ](https://my.oschina.net/funcy/blog/4696657)һᵽspring ͨ `@EnableWebMvc` ע mvc ܣͨ `@Import` עΪĿ `DelegatingWebMvcConfiguration.class`ͨ `@Bean` עķ spring  mvc \n\n*   `public RequestMappingHandlerMapping requestMappingHandlerMapping(...)`\n*   `public PathMatcher mvcPathMatcher()`\n*   `public UrlPathHelper mvcUrlPathHelper()`\n*   ...\n\nôУ `@RequestMaping` עص `RequestMappingHandlerMapping`.\n\n### 2. `RequestMappingHandlerMapping#afterPropertiesSet` \n\n`RequestMappingHandlerMapping` Ǵ `WebMvcConfigurationSupport` У\n\n```\n@Bean\npublic RequestMappingHandlerMapping requestMappingHandlerMapping(\n        @Qualifier(\"mvcContentNegotiationManager\") ContentNegotiationManager contentNegotiationManager,\n        @Qualifier(\"mvcConversionService\") FormattingConversionService conversionService,\n        @Qualifier(\"mvcResourceUrlProvider\") ResourceUrlProvider resourceUrlProvider) {\n\n    // bean\n    RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();\n    mapping.setOrder(0);\n    mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));\n    mapping.setContentNegotiationManager(contentNegotiationManager);\n    mapping.setCorsConfigurations(getCorsConfigurations());\n\n    // ãһƪᵽgetXxx()ȡ\n    PathMatchConfigurer configurer = getPathMatchConfigurer();\n    Boolean useSuffixPatternMatch = configurer.isUseSuffixPatternMatch();\n    if (useSuffixPatternMatch != null) {\n        mapping.setUseSuffixPatternMatch(useSuffixPatternMatch);\n    }\n    Boolean useRegisteredSuffixPatternMatch = configurer.isUseRegisteredSuffixPatternMatch();\n    if (useRegisteredSuffixPatternMatch != null) {\n        mapping.setUseRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch);\n    }\n    Boolean useTrailingSlashMatch = configurer.isUseTrailingSlashMatch();\n    if (useTrailingSlashMatch != null) {\n        mapping.setUseTrailingSlashMatch(useTrailingSlashMatch);\n    }\n    UrlPathHelper pathHelper = configurer.getUrlPathHelper();\n    if (pathHelper != null) {\n        mapping.setUrlPathHelper(pathHelper);\n    }\n    PathMatcher pathMatcher = configurer.getPathMatcher();\n    if (pathMatcher != null) {\n        mapping.setPathMatcher(pathMatcher);\n    }\n    Map<String, Predicate<Class<?>>> pathPrefixes = configurer.getPathPrefixes();\n    if (pathPrefixes != null) {\n        mapping.setPathPrefixes(pathPrefixes);\n    }\n\n    return mapping;\n}\n\n// \nprotected RequestMappingHandlerMapping createRequestMappingHandlerMapping() {\n    return new RequestMappingHandlerMapping();\n}\n\n```\n\n `RequestMappingHandlerMapping` ģǴһȻ˸ԡ󴴽󣬼 spring bean ڣ̶ `RequestMappingHandlerMapping#afterPropertiesSet` \n\n```\n@Override\npublic void afterPropertiesSet() {\n    // һЩ\n    this.config = new RequestMappingInfo.BuilderConfiguration();\n    this.config.setUrlPathHelper(getUrlPathHelper());\n    this.config.setPathMatcher(getPathMatcher());\n    this.config.setSuffixPatternMatch(this.useSuffixPatternMatch);\n    this.config.setTrailingSlashMatch(this.useTrailingSlashMatch);\n    this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch);\n    this.config.setContentNegotiationManager(getContentNegotiationManager());\n    // øķ\n    super.afterPropertiesSet();\n}\n\n```\n\n һЩԣȻٵø `afterPropertiesSet()`׷ȥ\n\n> AbstractHandlerMethodMapping#afterPropertiesSet\n\n```\n@Override\npublic void afterPropertiesSet() {\n    initHandlerMethods();\n}\n\nprotected void initHandlerMethods() {\n    // getCandidateBeanNames()ȡbeanbeanName\n    // Ȼ󰤸 bean\n    for (String beanName : getCandidateBeanNames()) {\n        if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {\n            //  bean ¿\n            processCandidateBean(beanName);\n        }\n    }\n    // һ־ûʲô\n    handlerMethodsInitialized(getHandlerMethods());\n}\n\n```\n\nspring ڴʱȡ bean  beanNameȻ beanName а `AbstractHandlerMethodMapping#processCandidateBean`\n\n```\n// beanľ߼\nprotected void processCandidateBean(String beanName) {\n    // ȡ beanName Ӧ beanType\n    // 1\\. cglibbeanType Ϊ Xxx$$EnhancerBySpringCGLIB\n    // 2\\. jdk̬beanType Ϊ com.sum.proxy.$Proxy\n    Class<?> beanType = null;\n    try {\n        beanType = obtainApplicationContext().getType(beanName);\n    }\n    catch (Throwable ex) {\n        ...\n    }\n    // isHandler: beanTypeǷ @Controller  @RequestMapping ע\n    if (beanType != null && isHandler(beanType)) {\n        //  handlerMethods\n        detectHandlerMethods(beanName);\n    }\n}\n\n```\n\nǱȽϼ򵥣Ҫǻȡ `beanName` Ӧ `beanType`ȻжǷ `@Controller/@RequestMapping` ע⣬֮͵ `AbstractHandlerMethodMapping#detectHandlerMethods` һ\n\n `isHandler(Class)` Ҫ˵£\n\n1. ʶ `@Controller`ͬʶ `@RestController`ע `@Controller` ע⣬ʶע⣺\n\n   ```\n   //  @Controller\n   @Controller\n   // ʡע\n   public @interface XxxController {\n       ...\n   }\n   \n   ```\n\n2.  `beanName` Ӧ bean  cglib  beanbeanType Ϊ `Xxx$$EnhancerBySpringCGLIB`ʶ丸 (ҲĿ) ϵ `@Controller/@ReestMapping`;\n\n3.  `beanName` Ӧ bean  jdk ̬ beanbeanType Ϊ `com.sum.proxy.$Proxy`ʶ丸ӿϵ `@Controller/@RequestMapping`;\n\n4.  beanType  `com.sum.proxy.$Proxy`(jdk ̬)** ޷ʶĿϵ `@Controller/@RequestMapping` ** ģ\n\n5. ע `@Controller/@RequestMapping` Ҫʵ jdk ̬Ҫ `@Controller/@RequestMapping` ڽӿڼӿڵķϡ\n\n `AbstractHandlerMethodMapping#detectHandlerMethods`  `beanType` Ǳע `@Controller/@RequestMapping` ӿˡ\n\n### 3. `AbstractHandlerMethodMapping#detectHandlerMethods`\n\n `AbstractHandlerMethodMapping#detectHandlerMethods` ݣ\n\n```\n// handler\nprotected void detectHandlerMethods(Object handler) {\n    Class<?> handlerType = (handler instanceof String ?\n            obtainApplicationContext().getType((String) handler) : handler.getClass());\n    if (handlerType != null) {\n        // 1\\. cglib󣬵õ丸࣬ҲĿ\n        Class<?> userType = ClassUtils.getUserClass(handlerType);\n        // 2\\. ᴦ userTypeuserTypeĲObjectи༰ userType нӿڵķ\n        Map<Method, T> methods = MethodIntrospector.selectMethods(userType,\n                // 3\\. ÿ @RequestMapping򴴽 RequestMappingInfo\n                //  @RequestMapping Ϣװö\n                (MethodIntrospector.MetadataLookup<T>) method -> {\n                    try {\n                        // ﴦϵ @RequestMapping ע\n                        return getMappingForMethod(method, userType);\n                    }\n                    catch (Throwable ex) {\n                        ...\n                    }\n                });\n        methods.forEach((method, mapping) -> {\n            Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);\n            // 4\\. ｫhandlermappingmethod\n            registerHandlerMethod(handler, invocableMethod, mapping);\n        });\n    }\n}\n\n```\n\n¼£\n\n1.   cglib 󣬵õ丸࣬ҲĿࣨ**Ϊֻ cglib 󣬶 jdk ̬أ**  `isHandler(Class)` ˵֪spring ʶ jdk ̬Ӧϵ `@Controller/@RequestMapping` ע⣬˲ִе\n2.   `userType``userType` Ĳ Object и༰ `userType` нӿڵķ\n3.  ÿ `@RequestMapping`򴴽 `RequestMappingInfo` `@RequestMapping` ϢװöУ\n4.  עᣬ `handler``mapping`  `method` 浽 Map С\n\n#### 3.1 ҷ\n\n `detectHandlerMethods` ĴУ `userType``userType` иࣨ Object `userType` нӿڵķ£\n\n> MethodIntrospector#selectMethods(Class, MethodIntrospector.MetadataLookup)\n\n```\npublic static <T> Map<Method, T> selectMethods(Class<?> targetType, final \n            MetadataLookup<T> metadataLookup) {\n    final Map<Method, T> methodMap = new LinkedHashMap<>();\n    Set<Class<?>> handlerTypes = new LinkedHashSet<>();\n    Class<?> specificHandlerType = null;\n    // jdk̬\n    if (!Proxy.isProxyClass(targetType)) {\n        // cglib࣬ȡĸ class\n        specificHandlerType = ClassUtils.getUserClass(targetType);\n        handlerTypes.add(specificHandlerType);\n    }\n    // ȡнӿڣӿڵĸӿ.\n    handlerTypes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetType));\n    for (Class<?> currentHandlerType : handlerTypes) {\n        final Class<?> targetClass = (specificHandlerType != null \n                      ? specificHandlerType : currentHandlerType);\n        // currentHandlerTypecurrentHandlerTypeĲObjectиࡢ\n        // currentHandlerTypeнӿڵķ\n        // aopʱõҲ\n        ReflectionUtils.doWithMethods(currentHandlerType, method -> {\n            Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);\n            T result = metadataLookup.inspect(specificMethod);\n            if (result != null) {\n                Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);\n                if (bridgedMethod == specificMethod || \n                             metadataLookup.inspect(bridgedMethod) == null) {\n                    methodMap.put(specificMethod, result);\n                }\n            }\n        }, ReflectionUtils.USER_DECLARED_METHODS);\n    }\n    return methodMap;\n}\n\n```\n\n#### 3.2  `RequestMappingInfo`\n\nÿ `@RequestMapping`򴴽 `RequestMappingInfo` `@RequestMapping` ϢװöУ\n\n> RequestMappingHandlerMapping#getMappingForMethod\n\n```\nprotected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {\n    // ϵ @RequestMapping\n    RequestMappingInfo info = createRequestMappingInfo(method);\n    if (info != null) {\n        // ϵ @RequestMapping\n        RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);\n        if (typeInfo != null) {\n            // ϲϵ @RequestMapping \n            //  @RequestMapping(\"/test\")ϵ @RequestMapping(\"/hello\")\n            // ϲĽΪ /test/hello\n            info = typeInfo.combine(info);\n        }\n        String prefix = getPathPrefix(handlerType);\n        if (prefix != null) {\n            info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);\n        }\n    }\n    return info;\n}\n\n@Nullable\nprivate RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {\n    // ȡ @RequestMapping ע\n    RequestMapping requestMapping = AnnotatedElementUtils\n            .findMergedAnnotation(element, RequestMapping.class);\n    // ʵΪ\n    RequestCondition<?> condition = (element instanceof Class ?\n            getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));\n    return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);\n}\n\n//  RequestMappingInfo RequestMapping  @RequestMapping ע\nprotected RequestMappingInfo createRequestMappingInfo(\n        RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) {\n    // ʵǽ @RequestMapping עװΪRequestMappingInfo\n    RequestMappingInfo.Builder builder = RequestMappingInfo\n            .paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))\n            // Խ @RequestMapping ע\n            .methods(requestMapping.method())\n            .params(requestMapping.params())\n            .headers(requestMapping.headers())\n            .consumes(requestMapping.consumes())\n            .produces(requestMapping.produces())\n            .mappingName(requestMapping.name());\n    if (customCondition != null) {\n        builder.customCondition(customCondition);\n    }\n    return builder.options(this.config).build();\n}\n\n```\n\nǾ `@RequestMapping`  `RequestMappingInfo`ת\n\n `RequestMappingInfo` ʲô\n\n```\npublic final class RequestMappingInfo implements RequestCondition<RequestMappingInfo> {\n    // ṩ˺ܶԣӦ @RequestMapping\n    @Nullable\n    private final String name;\n    private final PatternsRequestCondition patternsCondition;\n    private final RequestMethodsRequestCondition methodsCondition;\n    private final ParamsRequestCondition paramsCondition;\n    private final HeadersRequestCondition headersCondition;\n    private final ConsumesRequestCondition consumesCondition;\n    private final ProducesRequestCondition producesCondition;\n    private final RequestConditionHolder customConditionHolder;\n\n    // 췽\n    public RequestMappingInfo(@Nullable String name, @Nullable PatternsRequestCondition patterns,\n            @Nullable RequestMethodsRequestCondition methods, @Nullable ParamsRequestCondition params,\n            @Nullable HeadersRequestCondition headers, @Nullable ConsumesRequestCondition consumes,\n            @Nullable ProducesRequestCondition produces, @Nullable RequestCondition<?> custom) {\n\n        this.name = (StringUtils.hasText(name) ? name : null);\n        this.patternsCondition = (patterns != null ? patterns : new PatternsRequestCondition());\n        this.methodsCondition = (methods != null ? methods : new RequestMethodsRequestCondition());\n        this.paramsCondition = (params != null ? params : new ParamsRequestCondition());\n        this.headersCondition = (headers != null ? headers : new HeadersRequestCondition());\n        this.consumesCondition = (consumes != null ? consumes : new ConsumesRequestCondition());\n        this.producesCondition = (produces != null ? produces : new ProducesRequestCondition());\n        this.customConditionHolder = new RequestConditionHolder(custom);\n    }\n\n    // builder ģʽǰʹbuilderRequestMappingInfo\n    private static class DefaultBuilder implements Builder {\n        // ʡ\n        ...\n\n        //  ʹbuilder()\n        @Override\n        public RequestMappingInfo build() {\n            ContentNegotiationManager manager = this.options.getContentNegotiationManager();\n            PatternsRequestCondition patternsCondition = new PatternsRequestCondition(\n                    this.paths, this.options.getUrlPathHelper(), this.options.getPathMatcher(),\n                    this.options.useSuffixPatternMatch(), this.options.useTrailingSlashMatch(),\n                    this.options.getFileExtensions());\n            //  RequestMappingInfo 췽\n            return new RequestMappingInfo(this.mappingName, patternsCondition,\n                    new RequestMethodsRequestCondition(this.methods),\n                    new ParamsRequestCondition(this.params),\n                    new HeadersRequestCondition(this.headers),\n                    new ConsumesRequestCondition(this.consumes, this.headers),\n                    new ProducesRequestCondition(this.produces, this.headers, manager),\n                    this.customCondition);\n        }\n    }\n    // ʡ\n    ...\n}\n\n```\n\n#### 3.3 ע\n\nװ `@RequestMapping` Ϣ󣬽ǽӿϢעᵽ springmvc ˣ\n\n> RequestMappingHandlerMapping#registerHandlerMethod\n\n```\n@Override\nprotected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {\n    // øķע߼\n    super.registerHandlerMethod(handler, method, mapping);\n    updateConsumesCondition(mapping, method);\n}\n//  @RequestBody ע\nprivate void updateConsumesCondition(RequestMappingInfo info, Method method) {\n    ConsumesRequestCondition condition = info.getConsumesCondition();\n    if (!condition.isEmpty()) {\n        for (Parameter parameter : method.getParameters()) {\n            //   @RequestBody ע⣬ BodyRequired ֵ\n            MergedAnnotation<RequestBody> annot = MergedAnnotations.from(parameter)\n                    .get(RequestBody.class);\n            if (annot.isPresent()) {\n                condition.setBodyRequired(annot.getBoolean(\"required\"));\n                break;\n            }\n        }\n    }\n}\n\n```\n\nգ־ע߼ `AbstractHandlerMethodMapping#registerHandlerMethod` ɵģշˡڷǰһõ `Map<Method, T> methods`:\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-ec6b4b2d2156e0e041982425173feda38fc.png)\n\nԿӦ `T`  `RequestMappingInfo` ˡ\n\n### 3. `AbstractHandlerMethodMapping#registerHandlerMethod` \n\n `AbstractHandlerMethodMapping#registerHandlerMethod` 룺\n\n```\npublic abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping \n        implements InitializingBean {\n\n    protected void registerHandlerMethod(Object handler, Method method, T mapping) {\n        this.mappingRegistry.register(mapping, handler, method);\n    }\n\n    // ʡ˺ö\n    ... \n\n    class MappingRegistry {\n        // Ϣȫmapmapping, handlerMethod, directUrls, nameϢ\n        private final Map<T, MappingRegistration<T>> registry = new HashMap<>();\n        // е mapping map/test/hello/test/{name}\n        private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();\n        // ȷurl map /test/hello\n        private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();\n\n        // ʡ˺ö\n        ...\n\n        public void register(T mapping, Object handler, Method method) {\n            ...\n            // ȡдд\n            this.readWriteLock.writeLock().lock();\n            try {\n                // 1\\. ȡ handlerMethodʵǽhandler  method װһ\n                HandlerMethod handlerMethod = createHandlerMethod(handler, method);\n                validateMethodMapping(handlerMethod, mapping);\n                // 2\\.  mappingLookup УΪ LinkedHashMap\n                // springmvcһҪmap\n                this.mappingLookup.put(mapping, handlerMethod);\n\n                // 3\\. ȡurlurlLookupΪMultiValueMapmap ͬһkeyжvalue\n                // springmvcһҪmap\n                List<String> directUrls = getDirectUrls(mapping);\n                for (String url : directUrls) {\n                    this.urlLookup.add(url, mapping);\n                }\n                String name = null;\n                if (getNamingStrategy() != null) {\n                    name = getNamingStrategy().getName(handlerMethod, mapping);\n                    addMappingName(name, handlerMethod);\n                }\n                CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);\n                if (corsConfig != null) {\n                    this.corsLookup.put(handlerMethod, corsConfig);\n                }\n\n                // 4\\. mapping, handlerMethod, directUrls, nameȷװregistry\n                // registry ΪHashMapspringmvc нӿϢȫһmap\n                this.registry.put(mapping, new MappingRegistration<>(mapping, \n                        handlerMethod, directUrls, name));\n            }\n            finally {\n                this.readWriteLock.writeLock().unlock();\n            }\n        }\n    }\n}\n\n```\n\nԿע߼ `AbstractHandlerMethodMapping.MappingRegistry#register` ɵġǾһע߼\n\n#### 3.1 ȡ `HandlerMethod`\n\nش£\n\n```\nprotected HandlerMethod createHandlerMethod(Object handler, Method method) {\n    if (handler instanceof String) {\n        return new HandlerMethod((String) handler,\n                obtainApplicationContext().getAutowireCapableBeanFactory(), method);\n    }\n    return new HandlerMethod(handler, method);\n}\n\n```\n\nηǼ򵥵ص `HandlerMethod` Ĺ췽\n\n```\npublic class HandlerMethod {\n\n    // ṩ˷ǳ\n    protected final Log logger = LogFactory.getLog(getClass());\n    private final Object bean;\n    @Nullable\n    private final BeanFactory beanFactory;\n    private final Class<?> beanType;\n    private final Method method;\n    private final Method bridgedMethod;\n    private final MethodParameter[] parameters;\n    @Nullable\n    private HttpStatus responseStatus;\n    @Nullable\n    private String responseStatusReason;\n    @Nullable\n    private HandlerMethod resolvedFromHandlerMethod;\n    @Nullable\n    private volatile List<Annotation[][]> interfaceParameterAnnotations;\n    private final String description;\n\n    // 췽\n    public HandlerMethod(Object bean, Method method) {\n        Assert.notNull(bean, \"Bean is required\");\n        Assert.notNull(method, \"Method is required\");\n        this.bean = bean;\n        this.beanFactory = null;\n        this.beanType = ClassUtils.getUserClass(bean);\n        this.method = method;\n        this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);\n        this.parameters = initMethodParameters();\n        evaluateResponseStatus();\n        this.description = initDescription(this.beanType, this.method);\n    }\n\n    // ʡ\n    ...\n}\n\n```\n\nԿ`HandlerMethod` зǳԣ췽ҲǸֵѡɴ˿ɿ`HandlerMethod` Ƕ `handler`  `method` һװ\n\n#### 3.2 ֤ mapping Ƿظ\n\n springmvc ʹУСĶͬ `requestMapping`쳣\n\n```\nCaused by: java.lang.IllegalStateException: Ambiguous mapping. Cannot map \n'xxxController' method xxxMethod to /xxx/xxx: There is already 'xxxControllter' \nbean method xxxMethod mapped.\n\n```\n\n쳣֤ mapping ʱظ mapping ģ£\n\n```\n// е mapping map/test/hello/test/{name}\nprivate final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();\n\nprivate void validateMethodMapping(HandlerMethod handlerMethod, T mapping) {\n    // ҵѴڵ method\n    HandlerMethod existingHandlerMethod = this.mappingLookup.get(mapping);\n    // ѴڵhandlerMethodΪգҲڵǰ handlerMethod\n    if (existingHandlerMethod != null && !existingHandlerMethod.equals(handlerMethod)) {\n        throw new IllegalStateException(\n                \"Ambiguous mapping. Cannot map '\" + handlerMethod.getBean() + \"' method \\n\" +\n                handlerMethod + \"\\nto \" + mapping + \": There is already '\" +\n                existingHandlerMethod.getBean() + \"' bean method\\n\" +\n                existingHandlerMethod + \" mapped.\");\n    }\n}\n\n```\n\nǸ `mapping`  `mappingLookup` в `HandlerMethod`ҵҵ `handlerMethod` ǵǰ `handlerMethod`ʾظͱ쳣ˡ\n\nֱж `HandlerMethod`  `RequestMappingInfo` жȵģ\n\n> HandlerMethod#equals\n\n```\n@Override\npublic boolean equals(@Nullable Object other) {\n    if (this == other) {\n        return true;\n    }\n    if (!(other instanceof HandlerMethod)) {\n        return false;\n    }\n    HandlerMethod otherMethod = (HandlerMethod) other;\n    return (this.bean.equals(otherMethod.bean) && this.method.equals(otherMethod.method));\n}\n\n```\n\n> RequestMappingInfo#equals\n\n```\n@Override\npublic boolean equals(@Nullable Object other) {\n    if (this == other) {\n        return true;\n    }\n    if (!(other instanceof RequestMappingInfo)) {\n        return false;\n    }\n    RequestMappingInfo otherInfo = (RequestMappingInfo) other;\n    return (this.patternsCondition.equals(otherInfo.patternsCondition) &&\n            this.methodsCondition.equals(otherInfo.methodsCondition) &&\n            this.paramsCondition.equals(otherInfo.paramsCondition) &&\n            this.headersCondition.equals(otherInfo.headersCondition) &&\n            this.consumesCondition.equals(otherInfo.consumesCondition) &&\n            this.producesCondition.equals(otherInfo.producesCondition) &&\n            this.customConditionHolder.equals(otherInfo.customConditionHolder));\n}\n\n```\n\n`RequestMappingInfo` Ҫͬжȣ `@RequestMapping`õ `RequestMappingInfo` ȣ\n\n```\n//  @RequestMappingȻ·ǡ/helloֵ֧󷽷ͬ\n// ˵õ RequestMappingInfo \n\n@RequestMapping(path = \"/hello\")\npublic String hello1() {\n    ...\n}\n\n@RequestMapping(path = \"/hello\", method = RequestMethod.GET)\npublic String hello2() {\n    ...\n}\n\n@RequestMapping(path = \"/hello\", method = RequestMethod.POST)\npublic String hello3() {\n    ...\n}\n\n```\n\n#### 3.3 ȡ `directUrls`\n\nspringmvc У url :\n\n1. ȷ url\n\n   ```\n   @RequestMapping(\"/hello\")\n   public String hello() {\n      ...\n   }\n   \n   ```\n\n2. ȷ url\n\n   ```\n   @RequestMapping(\"/{name}\")\n   public String hello(@PathVariable(\"name\") String name) {\n      ...\n   }\n   \n   ```\n\nspringmvc ṩרŵ `urlLookup` ȷ urlṹ£\n\n```\nMultiValueMap<String, LinkedList<RequestMappingInfo>>\n\n```\n\n springmvc λȡȷ url :\n\n```\nList<String> directUrls = getDirectUrls(mapping);\n\n/**\n * AbstractHandlerMethodMapping.MappingRegistry#getDirectUrls\n * ȡȷurl\n */\nprivate List<String> getDirectUrls(T mapping) {\n    List<String> urls = new ArrayList<>(1);\n    // RequestMappingInfoȡе MappingPathPattern\n    for (String path : getMappingPathPatterns(mapping)) {\n        // жϵõ MappingPathPattern ǷΪȷurl\n        if (!getPathMatcher().isPattern(path)) {\n            urls.add(path);\n        }\n    }\n    return urls;\n}\n\n/**\n * RequestMappingInfoHandlerMapping#getMappingPathPatterns\n * ȡ patterns,  RequestMappingInfo ȡ\n * ʵϾ @RequestMapping е path() ֵ\n */\n@Override\nprotected Set<String> getMappingPathPatterns(RequestMappingInfo info) {\n    return info.getPatternsCondition().getPatterns();\n}\n\n/**\n * AntPathMatcher#isPattern\n * жǷΪȷ url\n * ֻҪ *֮һͬʱ{}Ͳȷurl\n */\n@Override\npublic boolean isPattern(@Nullable String path) {\n    if (path == null) {\n        return false;\n    }\n    boolean uriVar = false;\n    for (int i = 0; i < path.length(); i++) {\n        char c = path.charAt(i);\n        if (c == '*' || c == '?') {\n            return true;\n        }\n        if (c == '{') {\n            uriVar = true;\n            continue;\n        }\n        if (c == '}' && uriVar) {\n            return true;\n        }\n    }\n    return false;\n}\n\n```\n\n£\n\n1.  ȡǰ `mapping`  `path`Ҳ `@RequestMapping`  `path()` ֵ\n2.  õ `path`һжǷΪȷ url (ֻҪ `*``?` ֮һͬʱ `{``}`Ͳȷ url).\n\n#### 3.4 עӿϢ\n\nעӿϢͱȽϼˣʵ map ݣ map\n\n*   `urlLookup`ȷ `url map`Ϊ `MultiValueMap<String, LinkedList<RequestMappingInfo>>``key` Ϊȷ url `/test/hello``value` Ϊ `LinkedList<RequestMappingInfo>`\n\n*   `mappingLookup`е `mapping map`е `@RequestMapping` Ӧ `RequestMappingInfo` ҵ `/test/hello``/test/{name}` Ӧ `RequestMappingInfo`Ϊ `Map<RequestMappingInfo, HandlerMethod>`\n\n*   `registry`Ϣȫ mapΪ `Map<RequestMappingInfo, MappingRegistration<RequestMappingInfo>>`е `RequestMappingInfo`key Ϊ `RequestMappingInfo`value Ϊ `MappingRegistration<RequestMappingInfo>` `MappingRegistration` Ϊ `mapping`, `handlerMethod`, `directUrls`, `name` İװ࣬Ҳ˵ `MappingRegistration`  `mapping`, `handlerMethod`, `directUrls`, `name` Ϣ\n\nЩ map ע൱ˣǼ򵥵ص `Map#put` Ͳ˵ˡ\n\n### 4\\. ܽ\n\nķ spring  `@RequestMapping` ע̣ⲿ `RequestMappingHandlerMapping#afterPropertiesSet` У£\n\n1.  ȡ bean  `beanName` 2 \n2.  ҵ `beanName` Ӧ `beanType`жǷ `@Controller/@RequestMapping` ע⣻\n3.  ԰ `@Controller/@RequestMapping`  `beanType`ҵ `@RequestMapping` עķ `@RequestMapping` עװΪ `RequestMappingInfo`һõĽΪһ map`Map<Method, RequestMappingInfo>`\n4.   `beanName``beanType`  `Map<Method, RequestMappingInfo>` עᵽ springmvc УȽҪ map\n   *   `MultiValueMap<String, LinkedList<RequestMappingInfo>>`\n   *   `Map<RequestMappingInfo, HandlerMethod>`(`HandlerMethod` Ϊ `Method` İװ)\n   *   `Map<RequestMappingInfo, MappingRegistration<RequestMappingInfo>>`(`MappingRegistration` Ϊ `RequestMappingInfo`, `HandlerMethod`, `directUrls`, `beanName` İװ)\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-fe807564e60040b262b28977a46a74f60e9.png)\n\nܵ˵`RequestMapping` ĴرȽҲǵ˺öβҵ\n\n* * *\n\n_ԭӣ[https://my.oschina.net/funcy/blog/4715079](https://my.oschina.net/funcy/blog/4715079) ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע_"
  },
  {
    "path": "docs/Spring全家桶/SpringMVC源码分析/SpringMVC整体源码结构总结.md",
    "content": "### 1\\. servlet 3.0 淶\n\nϵһʼȽ `servlet3.0` 淶ͨù淶ʵ web Ŀ `0xml` .\n\n*   `servlet3.0` 淶Уservlet ͨ `SPI` ṩһӿڣ`ServletContainerInitializer`\n*   spring ʵ˸ýӿڣʵ `SpringServletContainerInitializer`  `onStartup(...)` Уִʵ `WebApplicationInitializer` ӿڵ `onStartup(...)` ֻҪʵ `WebApplicationInitializer` ӿڼɣ\n*   ʵ `WebApplicationInitializer` ӿڵУ `onStartup(...)`  servlet ֶעһ  servlet`DispatcherServlet` servlet л spring \n\n̾\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-0874fa7ef39ca9c405cdf55d99ca891ebf2.png)\n\n### 2\\.  webMvc ķʽ\n\nǷ `webMvc` ַʽ\n\n#### 1. `@EnableWebMvc`\n\nַʽܼ򵥣ֻҪ\n\n```\n// ʹ@EnableWebMvcעmvc\n@Component\n@EnableWebMvc\npublic class MvcConfig {\n    ...\n}\n\n```\n\nҪ webMvc һЩʱҪʵ `WebMvcConfigurer`:\n\n```\n// ʵ WebMvcConfigurerԶ\n@Component\npublic class MyWebMvcConfigurer implements WebMvcConfigurer {\n\n    // дWebMvcConfigurerԶ\n}\n\n```\n\n#### 2\\. ʵ `WebMvcConfigurationSupport`\n\nһַʽ `webMvc`  ʽʵ `WebMvcConfigurationSupport`\n\n```\n@Component\npublic class MyWebMvcConfigurationSupport extends WebMvcConfigurationSupport {\n    // д÷Զ\n    ...\n\n   /**\n    * 磬ӿãֱд addCorsMappings \n    */\n    @Override\n    public void addCorsMappings(CorsRegistry registry) {\n        // Լ\n        ...\n    }\n\n}\n\n```\n\nҪעǣʹַʽҪԶʱͲȥʵ `WebMvcConfigurer` ӿˣӦֱд `WebMvcConfigurationSupport` еӦд `addCorsMappings()`.\n\n### 3\\. \n\nһͼ̣ܽ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-74d675bbae28247726b8d054e8758c3d8b1.png) ![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-aa6bb35d0ab26925c45c62ab4d709d05cdd.png)\n\n### 4\\. \n\nҲһͼ̣ܽ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-024b75e7f7952dbf1ace7aa5a8cfe3bcb77.png)\n\n* * *\n\n_ԭӣ[https://my.oschina.net/funcy/blog/4773418](https://my.oschina.net/funcy/blog/4773418) ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע_"
  },
  {
    "path": "docs/Spring全家桶/SpringMVC源码分析/SpringMVC源码分析：DispatcherServlet如何找到正确的Controller.md",
    "content": "# 目录\n* [前言](#前言)\n* [源码分析](#源码分析)\n* [实例](#实例)\n* [资源文件映射](#资源文件映射)\n* [总结](#总结)\n\n\n本文转载自互联网，侵删\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章将同步到我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《Spring和SpringMVC源码分析》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从spring基础入手，一步步地学习spring基础和springmvc的框架知识，并上手进行项目实战，spring框架是每一个Java工程师必须要学习和理解的知识点，进一步来说，你还需要掌握spring甚至是springmvc的源码以及实现原理，才能更完整地了解整个spring技术体系，形成自己的知识框架。\n\n后续还会有springboot和springcloud的技术专题，陆续为大家带来，敬请期待。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供部分知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->  \n## 前言\nSpringMVC是目前主流的Web MVC框架之一。\n\n我们使用浏览器通过地址 http://ip:port/contextPath/path 进行访问，SpringMVC是如何得知用户到底是访问哪个Controller中的方法，这期间到底发生了什么。\n\n本文将分析SpringMVC是如何处理请求与Controller之间的映射关系的，让读者知道这个过程中到底发生了什么事情。\n\n本文实际上是在上文基础上，深入分析\n\nHandlerMapping里的\n\nHandlerExecutionChain getHandler(HttpServletRequest var1) throws Exception;\n\n该方法的具体实现，包括它如何找到对应的方法，以及如何把结果保存在map里，以便让请求转发到对应的handler上，同时也分析了handleradaptor具体做了什么事情。\n\n## 源码分析\n\n在分析源码之前，我们先了解一下几个东西。\n\n1.这个过程中重要的接口和类。\n\n**HandlerMethod类：**\n\nSpring3.1版本之后引入的。 是一个封装了方法参数、方法注解，方法返回值等众多元素的类。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/232033308878895.jpg)\n\n它的子类InvocableHandlerMethod有两个重要的属性WebDataBinderFactory和HandlerMethodArgumentResolverComposite， 很明显是对请求进行处理的。\n\nInvocableHandlerMethod的子类ServletInvocableHandlerMethod有个重要的属性HandlerMethodReturnValueHandlerComposite，很明显是对响应进行处理的。\n\nServletInvocableHandlerMethod这个类在HandlerAdapter对每个请求处理过程中，都会实例化一个出来(上面提到的属性由HandlerAdapter进行设置)，分别对请求和返回进行处理。　　(RequestMappingHandlerAdapter源码，实例化ServletInvocableHandlerMethod的时候分别set了上面提到的重要属性)\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/240149411377243.png)\n\n**MethodParameter类：**\n\nHandlerMethod类中的parameters属性类型，是一个MethodParameter数组。MethodParameter是一个封装了方法参数具体信息的工具类，包括参数的的索引位置，类型，注解，参数名等信息。\n\nHandlerMethod在实例化的时候，构造函数中会初始化这个数组，这时只初始化了部分数据，在HandlerAdapter对请求处理过程中会完善其他属性，之后交予合适的HandlerMethodArgumentResolver接口处理。\n\n以类DeptController为例：\n\n```  \n@Controller  \n@RequestMapping(value = \"/dept\")  \npublic class DeptController {  \n  \n  @Autowired  private IDeptService deptService;  \n  @RequestMapping(\"/update\")  @ResponseBody  public String update(Dept dept) {    deptService.saveOrUpdate(dept);    return \"success\";  }  \n}  \n  \n```  \n\n(刚初始化时的数据)　　  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/241246397157212.png)\n\n(HandlerAdapter处理后的数据)\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/241246574657656.png)\n\n**RequestCondition接口：**\n\n**Spring3.1版本之后引入的。 是SpringMVC的映射基础中的请求条件，可以进行combine, compareTo，getMatchingCondition操作。这个接口是映射匹配的关键接口，其中getMatchingCondition方法关乎是否能找到合适的映射。**\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/241429158878034.png)\n\n**RequestMappingInfo类：**\n\nSpring3.1版本之后引入的。 是一个封装了各种请求映射条件并实现了RequestCondition接口的类。\n\n有各种RequestCondition实现类属性，patternsCondition，methodsCondition，paramsCondition，headersCondition，consumesCondition以及producesCondition，这个请求条件看属性名也了解，分别代表http请求的路径模式、方法、参数、头部等信息。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/241556162777007.png)\n\n**RequestMappingHandlerMapping类：**\n\n处理请求与HandlerMethod映射关系的一个类。\n\n2.Web服务器启动的时候，SpringMVC到底做了什么。\n\n先看AbstractHandlerMethodMapping的initHandlerMethods方法中。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/241831201065104.png)\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/241922304028276.png)\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/241932113242502.png)\n\n我们进入createRequestMappingInfo方法看下是如何构造RequestMappingInfo对象的。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/251242523404424.png)\n\nPatternsRequestCondition构造函数：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/252348515124305.png)\n\n类对应的RequestMappingInfo存在的话，跟方法对应的RequestMappingInfo进行combine操作。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/250107154024892.png)\n\n然后使用符合条件的method来注册各种HandlerMethod。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/260023477461660.png)\n\n下面我们来看下各种RequestCondition接口的实现类的combine操作。\n\nPatternsRequestCondition：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/251617000436911.png)\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/251917084021029.png)\n\nRequestMethodsRequestCondition：\n\n方法的请求条件，用个set直接add即可。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/251919265597456.png)\n\n其他相关的RequestConditon实现类读者可自行查看源码。\n\n最终，RequestMappingHandlerMapping中两个比较重要的属性\n\nprivate final Map<T, HandlerMethod> handlerMethods = new LinkedHashMap<T, HandlerMethod>();\n\nprivate final MultiValueMap<String, T> urlMap = new LinkedMultiValueMap<String, T>();\n\nT为RequestMappingInfo。\n\n构造完成。\n\n我们知道，SpringMVC的分发器DispatcherServlet会根据浏览器的请求地址获得HandlerExecutionChain。\n\n这个过程我们看是如何实现的。\n\n首先看HandlerMethod的获得(直接看关键代码了)：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/252206079491274.png)\n\n这里的比较器是使用RequestMappingInfo的compareTo方法(RequestCondition接口定义的)。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/252219494658063.png)\n\n然后构造HandlerExecutionChain加上拦截器\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/252208290431970.png)\n\n## 实例\n\n写了这么多，来点例子让我们验证一下吧。\n\n```  \n@Controller  \n@RequestMapping(value = \"/wildcard\")  \npublic class TestWildcardController {  \n  \n  @RequestMapping(\"/test/**\")  @ResponseBody  public String test1(ModelAndView view) {    view.setViewName(\"/test/test\");    view.addObject(\"attr\", \"TestWildcardController -> /test/**\");    return view;  }  \n  @RequestMapping(\"/test/*\")  @ResponseBody  public String test2(ModelAndView view) {    view.setViewName(\"/test/test\");    view.addObject(\"attr\", \"TestWildcardController -> /test*\");    return view;  }  \n  @RequestMapping(\"test?\")  @ResponseBody  public String test3(ModelAndView view) {    view.setViewName(\"/test/test\");    view.addObject(\"attr\", \"TestWildcardController -> test?\");    return view;  }  \n  @RequestMapping(\"test/*\")  @ResponseBody  public String test4(ModelAndView view) {    view.setViewName(\"/test/test\");    view.addObject(\"attr\", \"TestWildcardController -> test/*\");    return view;  }  \n}  \n  \n```  \n\n由于这里的每个pattern都带了*因此，都不会加入到urlMap中，但是handlerMethods还是有的。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/260032509654870.png)\n\n当我们访问：http://localhost:8888/SpringMVCDemo/wildcard/test1的时候。\n\n会先根据 \"/wildcard/test1\" 找urlMap对应的RequestMappingInfo集合，找不到的话取handlerMethods集合中所有的key集合(也就是RequestMappingInfo集合)。\n\n然后进行匹配，匹配根据RequestCondition的getMatchingCondition方法。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/260046407936253.png)\n\n最终匹配到2个RequestMappingInfo：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/260049401063106.png)\n\n然后会使用比较器进行排序。\n\n之前也分析过，比较器是有优先级的。\n\n我们看到，RequestMappingInfo除了pattern，其他属性都是一样的。\n\n我们看下PatternsRequestCondition比较的逻辑：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/260125371817100.png)\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/260125488223078.png)\n\n因此，/test*的通配符比/test?的多，因此，最终选择了/test?\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/260129091195087.png)\n\n直接比较优先于通配符。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/260129342756267.png)\n\n```  \n@Controller  \n@RequestMapping(value = \"/priority\")  \npublic class TestPriorityController {  \n  \n  @RequestMapping(method = RequestMethod.GET)  @ResponseBody  public String test1(ModelAndView view) {    view.setViewName(\"/test/test\");    view.addObject(\"attr\", \"其他condition相同，带有method属性的优先级高\");  \n    return view;  }  \n  @RequestMapping()  @ResponseBody  public String test2(ModelAndView view) {    view.setViewName(\"/test/test\");    view.addObject(\"attr\", \"其他condition相同，不带method属性的优先级高\");  \n    return view;  }  \n}  \n  \n```  \n\n这里例子，其他requestCondition都一样，只有RequestMethodCondition不一样。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/260151358222728.png)\n\n看出，方法多的优先级越多。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/260152065252948.png)\n\n至于其他的RequestCondition，大家自行查看源码吧。\n\n## 资源文件映射\n\n以上分析均是基于Controller方法的映射(RequestMappingHandlerMapping)。\n\nSpringMVC中还有静态文件的映射，SimpleUrlHandlerMapping。\n\nDispatcherServlet找对应的HandlerExecutionChain的时候会遍历属性handlerMappings，这个一个实现了HandlerMapping接口的集合。\n\n由于我们在*-dispatcher.xml中加入了以下配置：\n\n```  \n<mvc:resources location=\"/static/\" mapping=\"/static/**\"/>  \n```  \n\nSpring解析配置文件会使用ResourcesBeanDefinitionParser进行解析的时候，会实例化出SimpleUrlHandlerMapping。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/261025451501584.jpg)\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/261026409781228.jpg)\n\n其中注册的HandlerMethod为ResourceHttpRequestHandler。\n\n访问地址：http://localhost:8888/SpringMVCDemo/static/js/jquery-1.11.0.js\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/261013551199069.jpg)\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/261028504315946.jpg)\n\n地址匹配到/static/**。\n\n最终SimpleUrlHandlerMapping找到对应的Handler -> ResourceHttpRequestHandler。\n\nResourceHttpRequestHandler进行handleRequest的时候，直接输出资源文件的文本内容。\n\n## 总结\n\n大致上整理了一下SpringMVC对请求的处理，包括其中比较关键的类和接口，希望对读者有帮助。\n\n让自己对SpringMVC有了更深入的认识，也为之后分析数据绑定，拦截器、HandlerAdapter等打下基础。"
  },
  {
    "path": "docs/Spring全家桶/SpringMVC源码分析/SpringMVC源码分析：DispatcherServlet的初始化与请求转发.md",
    "content": "# 目录\n  * [前言](#前言)\n  * [容器上下文的建立](#容器上下文的建立)\n  * [初始化SpringMVC默认实现类](#初始化springmvc默认实现类)\n  * [总结](#总结)\n\n\n本文转载自互联网，侵删\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章将同步到我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《Spring和SpringMVC源码分析》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从spring基础入手，一步步地学习spring基础和springmvc的框架知识，并上手进行项目实战，spring框架是每一个Java工程师必须要学习和理解的知识点，进一步来说，你还需要掌握spring甚至是springmvc的源码以及实现原理，才能更完整地了解整个spring技术体系，形成自己的知识框架。\n\n后续还会有springboot和springcloud的技术专题，陆续为大家带来，敬请期待。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供部分知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n## 前言\n在我们第一次学Servlet编程，学java web的时候，还没有那么多框架。我们开发一个简单的功能要做的事情很简单，就是继承HttpServlet，根据需要重写一下doGet，doPost方法，跳转到我们定义好的jsp页面。Servlet类编写完之后在web.xml里注册这个Servlet类。\n\n除此之外，没有其他了。我们启动web服务器，在浏览器中输入地址，就可以看到浏览器上输出我们写好的页面。为了更好的理解上面这个过程，你需要学习关于Servlet生命周期的三个阶段，就是所谓的“init-service-destroy”。\n\n以上的知识，我觉得对于你理解SpringMVC的设计思想，已经足够了。SpringMVC当然可以称得上是一个复杂的框架，但是同时它又遵循Servlet世界里最简单的法则，那就是“init-service-destroy”。我们要分析SpringMVC的初始化流程，其实就是分析DispatcherServlet类的init()方法，让我们带着这种单纯的观点，打开DispatcherServlet的源码一窥究竟吧。\n\n## <init-param>配置元素读取</init-param>\n\n用Eclipse IDE打开DispatcherServlet类的源码，ctrl+T看一下。\n\n![](https://img2018.cnblogs.com/blog/1092007/201908/1092007-20190825145640146-1198909724.jpg)\n\nDispatcherServlet类的初始化入口方法init()定义在HttpServletBean这个父类中，HttpServletBean类作为一个直接继承于HttpServlet类的类，覆写了HttpServlet类的init()方法，实现了自己的初始化行为。\n\n```\n@Override\n    public final void init() throws ServletException {\n        if (logger.isDebugEnabled()) {\n            logger.debug(\"Initializing servlet '\" + getServletName() + \"'\");\n        }\n\n        // Set bean properties from init parameters.\n        try {\n            PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);\n            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);\n            ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());\n            bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.environment));\n            initBeanWrapper(bw);\n            bw.setPropertyValues(pvs, true);\n        }\n        catch (BeansException ex) {\n            logger.error(\"Failed to set bean properties on servlet '\" + getServletName() + \"'\", ex);\n            throw ex;\n        }\n\n        // Let subclasses do whatever initialization they like.\n        initServletBean();\n\n        if (logger.isDebugEnabled()) {\n            logger.debug(\"Servlet '\" + getServletName() + \"' configured successfully\");\n        }\n    }\n```\n\n这里的initServletBean()方法在HttpServletBean类中是一个没有任何实现的空方法，它的目的就是留待子类实现自己的初始化逻辑，也就是我们常说的模板方法设计模式。SpringMVC在此生动的运用了这个模式，init()方法就是模版方法模式中的模板方法，SpringMVC真正的初始化过程，由子类FrameworkServlet中覆写的initServletBean()方法触发。\n\n再看一下init()方法内被try,catch块包裹的代码，里面涉及到BeanWrapper，PropertyValues，ResourceEditor这些Spring内部非常底层的类。要深究具体代码实现上面的细节，需要对Spring框架源码具有相当深入的了解。我们这里先避繁就简，从代码效果和设计思想上面来分析这段try,catch块内的代码所做的事情：\n\n*   注册一个字符串到资源文件的编辑器，让Servlet下面的<init-param>配置元素可以使用形如“classpath:”这种方式指定SpringMVC框架bean配置文件的来源。</init-param>\n*   将web.xml中在DispatcherServlet这个Servlet下面的<init-param>配置元素利用JavaBean的方式（即通过setter方法）读取到DispatcherServlet中来。</init-param>\n\n这两点，我想通过下面一个例子来说明一下。\n\n我在web.xml中注册的DispatcherServlet配置如下：\n\n```\n<!-- springMVC配置开始 -->\n    <servlet>\n        <servlet-name>appServlet</servlet-name>\n        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>\n        <init-param>\n            <param-name>contextConfigLocation</param-name>\n            <param-value>classpath:spring/spring-servlet.xml</param-value>\n        </init-param>\n        <load-on-startup>1</load-on-startup>\n    </servlet>\n    <servlet-mapping>\n        <servlet-name>appServlet</servlet-name>\n        <url-pattern>/</url-pattern>\n    </servlet-mapping>\n    <!-- springMVC配置结束 -->\n```\n\n可以看到，我注册了一个名为contextConfigLocation的<init-param>元素，其值为“classpath:spring/spring-servlet.xml”，这也是大家常常用来指定SpringMVC配置文件路径的方法。上面那段try,catch块包裹的代码发挥的作用，一个是将“classpath:spring/spring-servlet.xml”这段字符串转换成classpath路径下的一个资源文件，供框架初始化读取配置元素。在我的工程中是在spring文件夹下面的配置文件spring-servlet.xml。</init-param>\n\n![](https://img2018.cnblogs.com/blog/1092007/201908/1092007-20190825145640348-460637659.jpg)\n\n另外一个作用，就是将contextConfigLocation的值读取出来，然后通过setContextConfigLocation()方法设置到DispatcherServlet中，这个setContextConfigLocation()方法是在FrameworkServlet类中定义的，也就是上面继承类图中DispatcherServlet的直接父类。\n\n我们在setContextConfigLocation()方法上面打上一个断点，启动web工程，可以看到下面的调试结果。\n\n![](https://img2018.cnblogs.com/blog/1092007/201908/1092007-20190825145640835-96255521.jpg)\n\nHttpServletBean类的作者是大名鼎鼎的Spring之父Rod Johnson。作为POJO编程哲学的大师，他在HttpServletBean这个类的设计中，运用了依赖注入思想完成了<init-param>配置元素的读取。他抽离出HttpServletBean这个类的目的也在于此，就是“以依赖注入的方式来读取Servlet类的<init-param>配置信息”，而且这里很明显是一种setter注入。</init-param></init-param>\n\n明白了HttpServletBean类的设计思想，我们也就知道可以如何从中获益。具体来说，我们继承HttpServletBean类（就像DispatcherServlet做的那样），在类中定义一个属性，为这个属性加上setter方法后，我们就可以在<init-param>元素中为其定义值。在类被初始化后，值就会被注入进来，我们可以直接使用它，避免了样板式的getInitParameter()方法的使用，而且还免费享有Spring中资源编辑器的功能，可以在web.xml中，通过“classpath:”直接指定类路径下的资源文件。</init-param>\n\n注意，虽然SpringMVC本身为了后面初始化上下文的方便，使用了字符串来声明和设置contextConfigLocation参数，但是将其声明为Resource类型，同样能够成功获取。鼓励读者们自己继承HttpServletBean写一个测试用的Servlet类，并设置一个参数来调试一下，这样能够帮助你更好的理解获取配置参数的过程。\n\n## 容器上下文的建立\n\n上一篇文章中提到过，SpringMVC使用了Spring容器来容纳自己的配置元素，拥有自己的bean容器上下文。在SpringMVC初始化的过程中，非常关键的一步就是要建立起这个容器上下文，而这个建立上下文的过程，发生在FrameworkServlet类中，由上面init()方法中的initServletBean()方法触发。\n\n```\n@Override\n    protected final void initServletBean() throws ServletException {\n        getServletContext().log(\"Initializing Spring FrameworkServlet '\" + getServletName() + \"'\");\n        if (this.logger.isInfoEnabled()) {\n            this.logger.info(\"FrameworkServlet '\" + getServletName() + \"': initialization started\");\n        }\n        long startTime = System.currentTimeMillis();\n\n        try {\n            this.webApplicationContext = initWebApplicationContext();\n            initFrameworkServlet();\n        }\n        catch (ServletException ex) {\n            this.logger.error(\"Context initialization failed\", ex);\n            throw ex;\n        }\n        catch (RuntimeException ex) {\n            this.logger.error(\"Context initialization failed\", ex);\n            throw ex;\n        }\n\n        if (this.logger.isInfoEnabled()) {\n            long elapsedTime = System.currentTimeMillis() - startTime;\n            this.logger.info(\"FrameworkServlet '\" + getServletName() + \"': initialization completed in \" +\n                    elapsedTime + \" ms\");\n        }\n    }\n```\n\ninitFrameworkServlet()方法是一个没有任何实现的空方法，除去一些样板式的代码，那么这个initServletBean()方法所做的事情已经非常明白：\n\n```\nthis.webApplicationContext = initWebApplicationContext();\n```\n\n这一句简单直白的代码，道破了FrameworkServlet这个类，在SpringMVC类体系中的设计目的，它是用来抽离出建立WebApplicationContext上下文这个过程的。\n\ninitWebApplicationContext()方法，封装了建立Spring容器上下文的整个过程，方法内的逻辑如下：\n\n1.  获取由ContextLoaderListener初始化并注册在ServletContext中的根上下文，记为rootContext\n2.  如果webApplicationContext已经不为空，表示这个Servlet类是通过编程式注册到容器中的（Servlet 3.0+中的ServletContext.addServlet()），上下文也由编程式传入。若这个传入的上下文还没被初始化，将rootContext上下文设置为它的父上下文，然后将其初始化，否则直接使用。\n3.  通过wac变量的引用是否为null，判断第2步中是否已经完成上下文的设置（即上下文是否已经用编程式方式传入），如果wac==null成立，说明该Servlet不是由编程式注册到容器中的。此时以contextAttribute属性的值为键，在ServletContext中查找上下文，查找得到，说明上下文已经以别的方式初始化并注册在contextAttribute下，直接使用。\n4.  检查wac变量的引用是否为null，如果wac==null成立，说明2、3两步中的上下文初始化策略都没成功，此时调用createWebApplicationContext(rootContext)，建立一个全新的以rootContext为父上下文的上下文，作为SpringMVC配置元素的容器上下文。大多数情况下我们所使用的上下文，就是这个新建的上下文。\n5.  以上三种初始化上下文的策略，都会回调onRefresh(ApplicationContext context)方法（回调的方式根据不同策略有不同），onRefresh方法在DispatcherServlet类中被覆写，以上面得到的上下文为依托，完成SpringMVC中默认实现类的初始化。\n6.  最后，将这个上下文发布到ServletContext中，也就是将上下文以一个和Servlet类在web.xml中注册名字有关的值为键，设置为ServletContext的一个属性。你可以通过改变publishContext的值来决定是否发布到ServletContext中，默认为true。\n\n以上面6点跟踪FrameworkServlet类中的代码，可以比较清晰的了解到整个容器上下文的建立过程，也就能够领会到FrameworkServlet类的设计目的，它是用来建立一个和Servlet关联的Spring容器上下文，并将其注册到ServletContext中的。跳脱开SpringMVC体系，我们也能通过继承FrameworkServlet类，得到与Spring容器整合的好处，FrameworkServlet和HttpServletBean一样，是一个可以独立使用的类。整个SpringMVC设计中，处处体现开闭原则，这里显然也是其中一点。\n\n## 初始化SpringMVC默认实现类\n\n初始化流程在FrameworkServlet类中流转，建立了上下文后，通过onRefresh(ApplicationContext context)方法的回调，进入到DispatcherServlet类中。\n\n```\n@Override\n    protected void onRefresh(ApplicationContext context) {\n        initStrategies(context);\n    }\n```\n\nDispatcherServlet类覆写了父类FrameworkServlet中的onRefresh(ApplicationContext context)方法，提供了SpringMVC各种编程元素的初始化。当然这些编程元素，都是作为容器上下文中一个个bean而存在的。具体的初始化策略，在initStrategies()方法中封装。\n\n```\nprotected void initStrategies(ApplicationContext context) {\n        initMultipartResolver(context);\n        initLocaleResolver(context);\n        initThemeResolver(context);\n        initHandlerMappings(context);\n        initHandlerAdapters(context);\n        initHandlerExceptionResolvers(context);\n        initRequestToViewNameTranslator(context);\n        initViewResolvers(context);\n        initFlashMapManager(context);\n    }\n```\n\n我们以其中initHandlerMappings(context)方法为例，分析一下这些SpringMVC编程元素的初始化策略，其他的方法，都是以类似的策略初始化的。\n\n```\nprivate void initHandlerMappings(ApplicationContext context) {\n        this.handlerMappings = null;\n\n        if (this.detectAllHandlerMappings) {\n            // Find all HandlerMappings in the ApplicationContext, including ancestor contexts.\n            Map<String, HandlerMapping> matchingBeans =\n                    BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);\n            if (!matchingBeans.isEmpty()) {\n                this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());\n                // We keep HandlerMappings in sorted order.\n                OrderComparator.sort(this.handlerMappings);\n            }\n        }\n        else {\n            try {\n                HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);\n                this.handlerMappings = Collections.singletonList(hm);\n            }\n            catch (NoSuchBeanDefinitionException ex) {\n                // Ignore, we'll add a default HandlerMapping later.\n            }\n        }\n\n        // Ensure we have at least one HandlerMapping, by registering\n        // a default HandlerMapping if no other mappings are found.\n        if (this.handlerMappings == null) {\n            this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);\n            if (logger.isDebugEnabled()) {\n                logger.debug(\"No HandlerMappings found in servlet '\" + getServletName() + \"': using default\");\n            }\n        }\n    }\n```\n\ndetectAllHandlerMappings变量默认为true，所以在初始化HandlerMapping接口默认实现类的时候，会把上下文中所有HandlerMapping类型的Bean都注册在handlerMappings这个List变量中。如果你手工将其设置为false，那么将尝试获取名为handlerMapping的Bean，新建一个只有一个元素的List，将其赋给handlerMappings。如果经过上面的过程，handlerMappings变量仍为空，那么说明你没有在上下文中提供自己HandlerMapping类型的Bean定义。此时，SpringMVC将采用默认初始化策略来初始化handlerMappings。\n\n点进去getDefaultStrategies看一下。\n\n```\n@SuppressWarnings(\"unchecked\")\n    protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {\n        String key = strategyInterface.getName();\n        String value = defaultStrategies.getProperty(key);\n        if (value != null) {\n            String[] classNames = StringUtils.commaDelimitedListToStringArray(value);\n            List<T> strategies = new ArrayList<T>(classNames.length);\n            for (String className : classNames) {\n                try {\n                    Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());\n                    Object strategy = createDefaultStrategy(context, clazz);\n                    strategies.add((T) strategy);\n                }\n                catch (ClassNotFoundException ex) {\n                    throw new BeanInitializationException(\n                            \"Could not find DispatcherServlet's default strategy class [\" + className +\n                                    \"] for interface [\" + key + \"]\", ex);\n                }\n                catch (LinkageError err) {\n                    throw new BeanInitializationException(\n                            \"Error loading DispatcherServlet's default strategy class [\" + className +\n                                    \"] for interface [\" + key + \"]: problem with class file or dependent class\", err);\n                }\n            }\n            return strategies;\n        }\n        else {\n            return new LinkedList<T>();\n        }\n    }\n```\n\n它是一个范型的方法，承担所有SpringMVC编程元素的默认初始化策略。方法的内容比较直白，就是以传递类的名称为键，从defaultStrategies这个Properties变量中获取实现类，然后反射初始化。\n\n需要说明一下的是defaultStrategies变量的初始化，它是在DispatcherServlet的静态初始化代码块中加载的。\n\n```\nprivate static final Properties defaultStrategies;\n\n    static {\n        // Load default strategy implementations from properties file.\n        // This is currently strictly internal and not meant to be customized\n        // by application developers.\n        try {\n            ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);\n            defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);\n        }\n        catch (IOException ex) {\n            throw new IllegalStateException(\"Could not load 'DispatcherServlet.properties': \" + ex.getMessage());\n        }\n    }\n```\n\n```\nprivate static final String DEFAULT_STRATEGIES_PATH = \"DispatcherServlet.properties\";\n```\n\n这个DispatcherServlet.properties里面，以键值对的方式，记录了SpringMVC默认实现类，它在spring-webmvc-3.1.3.RELEASE.jar这个jar包内，在org.springframework.web.servlet包里面。\n\n```\n# Default implementation classes for DispatcherServlet's strategy interfaces.\n# Used as fallback when no matching beans are found in the DispatcherServlet context.\n# Not meant to be customized by application developers.\n\norg.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver\n\norg.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver\n\norg.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\\\n    org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping\n\norg.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\\\n    org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\\\n    org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter\n\norg.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\\\n    org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\\\n    org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver\n\norg.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator\n\norg.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver\n\norg.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager\n```\n\n至此，我们分析完了initHandlerMappings(context)方法的执行过程，其他的初始化过程与这个方法非常类似。所有初始化方法执行完后，SpringMVC正式完成初始化，静静等待Web请求的到来。\n\n## 总结\n\n回顾整个SpringMVC的初始化流程，我们看到，通过HttpServletBean、FrameworkServlet、DispatcherServlet三个不同的类层次，SpringMVC的设计者将三种不同的职责分别抽象，运用模版方法设计模式分别固定在三个类层次中。其中HttpServletBean完成的是<init-param>配置元素的依赖注入，FrameworkServlet完成的是容器上下文的建立，DispatcherServlet完成的是SpringMVC具体编程元素的初始化策略。</init-param>\n\n\n\n"
  },
  {
    "path": "docs/Spring全家桶/SpringMVC源码分析/SpringMVC源码分析：SpringMVC概述.md",
    "content": "# 目录\n\n  * [1.1、SpringMVC引言](#11、springmvc引言)\n  * [1.2、SpringMVC的优势](#12、springmvc的优势)\n  * [二、SpringMVC入门](#二、springmvc入门)\n  * [2.1、环境搭建](#21、环境搭建)\n  * [2.1.1、引入依赖](#211、引入依赖)\n  * [2.1.2、编写配置文件](#212、编写配置文件)\n  * [2.1.3、配置web.xml](#213、配置webxml)\n  * [2.1.4、编写控制器](#214、编写控制器)\n  * [2.2、注解详解](#22、注解详解)\n  * [2.2.1、@Controller](#221、controller)\n  * [2.2.2、@RequestMapping](#222、requestmapping)\n  * [2.3、SpringMVC的跳转方式](#23、springmvc的跳转方式)\n  * [2.3.1、Controller ——>前台页面](#231、controller-前台页面)\n  * [2.3.1.1、forward](#2311、forward)\n  * [2.3.1.2、redirect](#2312、redirect)\n  * [2.3.1Controller ——>Controller](#231controller-controller)\n  * [2.3.1.1、forward](#2311、forward-1)\n  * [2.3.1.2、redirect](#2312、redirect-1)\n  * [2.4、SpringMVC的参数接收](#24、springmvc的参数接收)\n  * [2.4.1、Servlet接收参数的方式](#241、servlet接收参数的方式)\n  * [2.4.2、SpringMVC的参数接收](#242、springmvc的参数接收)\n  * [2.4.2.1、基本数据类型](#2421、基本数据类型)\n  * [2.4.2.2、对象类型](#2422、对象类型)\n  * [2.4.2.3、数组类型](#2423、数组类型)\n  * [2.4.2.4、集合类型](#2424、集合类型)\n  * [2.5、SpringMVC接收参数中文乱码问题](#25、springmvc接收参数中文乱码问题)\n  * [2.5.1、GET请求](#251、get请求)\n  * [2.5.2、POST请求](#252、post请求)\n  * [2.5.2.1、自定义过滤器解决POST乱码请求](#2521、自定义过滤器解决post乱码请求)\n  * [2.5.2.2、使用CharacterEncodingFilter解决POST乱码请求](#2522、使用characterencodingfilter解决post乱码请求)\n  * [2.6、SpringMVC中数据传递机制](#26、springmvc中数据传递机制)\n  * [2.6.1、什么事数据传递机制](#261、什么事数据传递机制)\n  * [2.6.2、Servlet的数据传递机制](#262、servlet的数据传递机制)\n  * [三、前端控制器](#三、前端控制器)\n  * [3.1、什么是前端控制器](#31、什么是前端控制器)\n  * [3.2、代码实现](#32、代码实现)\n  * [3.3、注意](#33、注意)\n  * [3.4、映射路径](#34、映射路径)\n  * [3.4.1、访问静态资源和 JSP 被拦截的原因](#341、访问静态资源和-jsp-被拦截的原因)\n  * [3.4.2、如何解决](#342、如何解决)\n  * [3.4.2.1、方式一](#3421、方式一)\n  * [3.4.2.2、方式二](#3422、方式二)\n  * [3.5、@ModelAttribute 注解](#35、modelattribute-注解)\n  * [四、处理响应](#四、处理响应)\n  * [4.1、返回 ModelAndView](#41、返回-modelandview)\n  * [4.2、返回String](#42、返回string)\n  * [4.3、改进](#43、改进)\n  * [五、请求转发和重定向](#五、请求转发和重定向)\n  * [5.1、请求转发和重定向的区别](#51、请求转发和重定向的区别)\n  * [5.2、请求转发](#52、请求转发)\n  * [5.3、重定向](#53、重定向)\n  * [5.4、请求路径](#54、请求路径)\n  * [六、参数处理](#六、参数处理)\n  * [6.1、处理简单类型的请求参数](#61、处理简单类型的请求参数)\n  * [6.1.1、请求参数名和控制器方法参数列表形参同名](#611、请求参数名和控制器方法参数列表形参同名)\n  * [6.1.2、请求参数名和控制器方法参数列表形参不同名](#612、请求参数名和控制器方法参数列表形参不同名)\n  * [6.2、处理复杂类型的请求参数](#62、处理复杂类型的请求参数)\n  * [6.2.1、数组类型](#621、数组类型)\n  * [6.2.2、自定义类型](#622、自定义类型)\n  * [6.3、处理日期类型的请求参数](#63、处理日期类型的请求参数)\n  * [6.3.1、日期在请求参数上](#631、日期在请求参数上)\n  * [6.3.2、在封装的对象上](#632、在封装的对象上)\n  * [七、文件上传与下载](#七、文件上传与下载)\n  * [7.1、文件上传](#71、文件上传)\n  * [7.1.1、编写表单](#711、编写表单)\n  * [7.1.2、修改web.xml](#712、修改webxml)\n  * [7.1.3、配置上传解析器](#713、配置上传解析器)\n  * [7.1.4、配置上传控制器](#714、配置上传控制器)\n  * [7.2、文件下载](#72、文件下载)\n  * [7.2.1、开发控制器](#721、开发控制器)\n\n\n\n## 1.1、SpringMVC引言\n\n为了使Spring有可插入的MVC架构,SpringFrameWork在Spring基础上开发SpringMVC框架,从而在使用Spring进行WEB开发时可以选择使用**Spring的SpringMVC框架作为web开发的控制器框架**。\n\n## 1.2、SpringMVC的优势\n\nSpringMVC是一个**典型的轻量级MVC框架**，在整个MVC架构中充当控制器框架,相对于之前的struts2框架,**SpringMVC运行更快,其注解式开发更高效灵活**。\n\n<figure data-size=\"normal\">\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-3363c79f645a232562a2d6e800a11967_720w.webp)\n</figure>\n\n1.  可以和Spring框架无缝整合。\n2.  运行效率远远高于struts2框架。\n3.  注解式开发更高效。\n\n## 二、SpringMVC入门\n\n## 2.1、环境搭建\n\n## 2.1.1、引入依赖\n\n依赖就忽略了，我放在了评论区！\n\n## 2.1.2、编写配置文件\n\n\n\n```\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<beans xmlns=\"http://www.springframework.org/schema/beans\"\n  xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n  xmlns:context=\"http://www.springframework.org/schema/context\"\n  xmlns:mvc=\"http://www.springframework.org/schema/mvc\"\n  xsi:schemaLocation=\"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd\">\n\n<!--  1\\. 开启注解扫描-->\n  <context:component-scan base-package=\"com.lin.controller\"/>\n<!--  2\\. 配置处理器映射器-->\n<!--  <bean  />-->\n<!--  3\\. 开启处理器适配器-->\n<!--  <bean  />-->\n<!--  上面两段配置被下面的一句话所替代（封装）-->\n  <mvc:annotation-driven />\n  <!--  4\\. 开启视图解析器-->\n  <bean >\n    <property name=\"prefix\" value=\"/\"/>\n    <property name=\"suffix\" value=\".jsp\"/>\n  </bean>\n</beans>\n复制代码\n```\n\n\n\n## 2.1.3、配置web.xml\n\n\n\n```\n<!DOCTYPE web-app PUBLIC\n \"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN\"\n \"http://java.sun.com/dtd/web-app_2_3.dtd\" >\n\n<web-app>\n  <display-name>Archetype Created Web Application</display-name>\n\n<!--  配置springmvc的核心servlet-->\n  <servlet>\n    <servlet-name>springmvc</servlet-name>\n    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>\n    <init-param>\n<!--      告诉springmvc配置文件的位置-->\n      <param-name>contextConfigLocation</param-name>\n      <param-value>classpath:springmvc.xml</param-value>\n    </init-param>\n  </servlet>\n  <servlet-mapping>\n<!--    拦截所有请求-->\n    <servlet-name>springmvc</servlet-name>\n    <url-pattern>/</url-pattern>\n  </servlet-mapping>\n</web-app>\n复制代码\n```\n\n\n\n## 2.1.4、编写控制器\n\n\n\n```\npackage com.lin.controller;\n\nimport org.apache.ibatis.annotations.Param;\nimport org.springframework.stereotype.Controller;\nimport org.springframework.web.bind.annotation.RequestMapping;\n\n/**\n * @author XiaoLin\n * @date 2021/2/17 17:09\n */\n@Controller\npublic class HellowController {\n\n  /**\n      * @Description:第一个springmvc测试类\n      * @author XiaoLin\n      * @date 2021/2/17\n      * @Param: [username, password]\n      * @return java.lang.String\n      */\n     /*\n      RequestMapping的修饰范围：可以用在类上和方法上，他的作用如下：\n      1\\. 用在方法上可以给当前方法加入指定的请求路径\n      2\\. 用在类上可以给类中的所有方法都加入一个统一的请求路径，在这个方法访问之前都必须加上\n      */\n  @RequestMapping(\"/hello\")\n  public String hello(String username,String password){\n    System.out.println(\"hello\");\n    return \"index\";\n  }\n}\n\n复制代码\n```\n\n\n\n## 2.2、注解详解\n\n## 2.2.1、@Controller\n\n该注解用来类上标识这是一个控制器组件类并创建这个类实例，告诉spring我是一个控制器。\n\n## 2.2.2、@RequestMapping\n\n这个注解可以作用在方法上或者是类上，用来指定请求路径。\n\n## 2.3、SpringMVC的跳转方式\n\n传统的Servlet开发跳转方式有两种：\n\n1.  forward：forward跳转，是在服务器内部跳转，所以是一次请求，地址栏不变。跳转时可以携带数据进行传递（使用request作用域进行传递）。\n2.  redirect：redirect跳转是客户端跳转，所以是多次请求,地址栏会改变，跳转时不可以携带数据传递。\n\n## 2.3.1、Controller ——>前台页面\n\n## 2.3.1.1、forward\n\n通过测试我们可以发现，SpringMVC默认的就是使用请求转发的方式来进行跳转到前台页面的;\n\n\n\n```\n@Controller\n@RequestMapping(\"forwoartAndRedirect\")\npublic class TestForwoartAndRedirect {\n\n  @RequestMapping(\"test\")\n  public String test(){\n    System.out.println(\"test\");\n    return\"index\";\n  }\n}\n复制代码\n```\n\n\n\n## 2.3.1.2、redirect\n\n如果我们想使用重定向的方式来进行跳转的话，需要使用SpringMVC提供给我们的关键字——redirect:来完成。\n\n语法：return \"redirect:/视图全路径名\";\n\n**注意：**在redirect:后接页面的不是逻辑名，而是全路径名。因为redirect跳转不会经过视图解析器。\n\n## 2.3.1Controller ——>Controller\n\n## 2.3.1.1、forward\n\n如果我们想使用请求转发的方式跳转到相同(不同)Controller的不同方法的时候，我们也需要使用SpringMVC提供的关键字：forward:。\n\n语法：return:\"forward: /需要跳转的类上的@RequestMapping的值/需要跳转的方法上的@RequestMapping的值;\"\n\n## 2.3.1.2、redirect\n\n如果我们想使用重定向的方式跳转到相同(不同)Controller的不同方法的时候，我们也需要使用SpringMVC提供的关键字：redirect:。\n\n语法：return:\"redirect: /需要跳转的类上的@RequestMapping的值/需要跳转的方法上的@RequestMapping的值;\"\n\n## 2.4、SpringMVC的参数接收\n\n## 2.4.1、Servlet接收参数的方式\n\n在传统的Servlet开发，我们一般都是用这种方式来进行接收请求参数的。\n\n\n\n```\n// 接收名字为name的参数\nrequest.getParameter(name)\n复制代码\n```\n\n\n\n他有几个需要注意的点：\n\n1.  参数要求是表单域的name属性。\n2.  getParameter方法用于获取单个值, 返回类型是String。\n3.  getParameterValues方法用于获取一组数据, 返回结果是String[]。\n4.  冗余代码较多, 使用麻烦, 类型需要自己转换。\n\n## 2.4.2、SpringMVC的参数接收\n\nSpringMVC使用的是控制器中方法形参列表来接收客户端的请求参数，他可以进行自动类型转换，**要求传递参数的key要与对应方法的形参变量名一致才可以完成自动赋值**。他的优势很明显：\n\n1.  简化参数接收形式(不需要调用任何方法, 需要什么参数, 就在控制器方法中提供什么参数)。\n2.  参数类型不需要自己转换了。日期时间（默认为yyyy/MM/dd）得注意，需要使用@DateTimeFormat注解声明日期转换时遵循的格式, 否则抛出400异常。\n\n## 2.4.2.1、基本数据类型\n\n**要求传递参数的key要与对应方法的形参变量名一致才可以完成自动赋值。**\n\n## 2.4.2.2、对象类型\n\n如果我们需要接收对象类型的话，直接将需要接收的对象作为控制器的方法参数声明即可。SpringMVC会自动封装对象，若传递参数key与对象中属性名一致，就会自动封装成对象。\n\n## 2.4.2.3、数组类型\n\n如果我们需要接收数组类型的时候，只需将要接收的数组类型直接声明为方法的形式参数即可。\n\n## 2.4.2.4、集合类型\n\nSpringMVC不能直接通过形式参数列表的方式接收集合类型的参数，如果需要接收集合类型的参数必须将集合放入一个对象中，并且提供get/set方法，才可以。推荐放入VO对象中进行封装，进而使用对象类型来进行接收。\n\n## 2.5、SpringMVC接收参数中文乱码问题\n\n## 2.5.1、GET请求\n\nGET请求方式出现乱码需要分Tomcat版本进行讨论：\n\n1.  Tomcat8.x版本之前：默认使用server.xml中的URIEncoding=\"ISO-8859-1\"编码，而不是\"UTF-8\"编码，进而会出现中文乱码。\n2.  Tomcat8.x版本之后：默认使用server.xml中的URIEncoding=\"UTF-8\"，所以不会出现中文乱码问题。\n\n## 2.5.2、POST请求\n\nSpringMVC中默认没有对POST请求进行任何编码处理，所以无论什么版本直接接收POST请求都会出现中文乱码。\n\n## 2.5.2.1、自定义过滤器解决POST乱码请求\n\n在Servlet阶段，我们学过过滤器，我们可以自定义过滤器来进行过滤编码。\n\n\n\n```\npackage com.filter;\n\nimport javax.servlet.*;\nimport java.io.IOException;\n\n//自定义编码filter\npublic class CharacterEncodingFilter  implements Filter {\n\n    private String encoding;\n    @Override\n    public void init(FilterConfig filterConfig) throws ServletException {\n        this.encoding = filterConfig.getInitParameter(\"encoding\");\n        System.out.println(encoding);\n    }\n\n    @Override\n    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {\n        request.setCharacterEncoding(encoding);\n        response.setCharacterEncoding(encoding);\n        chain.doFilter(request,response);\n    }\n\n    @Override\n    public void destroy() {\n\n    }\n}\n复制代码\n<!--配置post请求方式中文乱码的Filter-->\n  <filter>\n    <filter-name>charset</filter-name>\n    <filter-class>com.filter.CharacterEncodingFilter</filter-class>\n    <init-param>\n      <param-name>encoding</param-name>\n      <param-value>UTF-8</param-value>\n    </init-param>\n  </filter>\n\n  <filter-mapping>\n    <filter-name>charset</filter-name>\n    <url-pattern>/*</url-pattern>\n  </filter-mapping>\n复制代码\n```\n\n\n\n## 2.5.2.2、使用CharacterEncodingFilter解决POST乱码请求\n\n\n\n```\n<!--配置post请求方式中文乱码的Filter-->\n  <filter>\n    <filter-name>charset</filter-name>\n    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>\n    <init-param>\n      <param-name>encoding</param-name>\n      <param-value>UTF-8</param-value>\n    </init-param>\n  </filter>\n\n  <filter-mapping>\n    <filter-name>charset</filter-name>\n    <url-pattern>/*</url-pattern>\n  </filter-mapping>\n复制代码\npackage com.filter;\n\nimport javax.servlet.*;\nimport java.io.IOException;\n\n//自定义编码filter\npublic class CharacterEncodingFilter  implements Filter {\n\n    private String encoding;\n    @Override\n    public void init(FilterConfig filterConfig) throws ServletException {\n        this.encoding = filterConfig.getInitParameter(\"encoding\");\n        System.out.println(encoding);\n    }\n\n    @Override\n    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {\n        request.setCharacterEncoding(encoding);\n        response.setCharacterEncoding(encoding);\n        chain.doFilter(request,response);\n    }\n\n    @Override\n    public void destroy() {\n\n    }\n}\n复制代码\n```\n\n\n\n## 2.6、SpringMVC中数据传递机制\n\n## 2.6.1、什么事数据传递机制\n\n数据传递机制主要包含三个问题：\n\n1.  数据如何存储？\n2.  如何在页面中获取数据？\n3.  在页面中获取的数据该如何展示？\n\n## 2.6.2、Servlet的数据传递机制\n\n在以前的Servlet开发中，我们一般是将数据放入作用域（request、session、application），如果数据是单个的直接用EL表达式在前端进行展示，如果是集合或者数组，可以用EL表达式➕JSTL标签进行遍历后在前端进行展示。\n\n## 三、前端控制器\n\n## 3.1、什么是前端控制器\n\n在 MVC 框架中都存在一个前端控制器，在 WEB 应用的前端（Front）设置一个入口控制器（Controller），是用来提供一个集中的请求处理机制，所有的请求都被发往该控制器统一处理，然后把请求分发给各自相应的处理程序。一般用来做一个共同的处理，如权限检查，授权，日志记录等。因为前端控制的集中处理请求的能力，因此提高了可重用性和可拓展性。\n\n在没有前端控制器的时候，我们是这样传递和处理请求的。\n\n<figure data-size=\"normal\">\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/52752e7a606d50ae243cf94309de682b_v2-aadb0832d3c878ff17af7dfe66e7f914_720w.webp)\n</figure>\n\n有了前端控制器之后，我们变成了这样。\n\n<figure data-size=\"normal\">\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-cb0dcdd3922ebfba4781800dd492dd69_720w.webp)\n</figure>\n\n## 3.2、代码实现\n\nSpring MVC 已经提供了一个 DispatcherServlet 类作为前端控制器，所以要使用 Spring MVC 必须在web.xml 中配置前端控制器。\n\n\n\n```\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<web-app xmlns=\"http://java.sun.com/xml/ns/javaee\"\n  xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n  xsi:schemaLocation=\"http://java.sun.com/xml/ns/javaee\n                        http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd\"\n  version=\"3.0\">\n  <!-- Spring MVC 前端控制器-->\n  <servlet>\n    <servlet-name>dispatcherServlet</servlet-name>\n    <servlet-\n      class>org.springframework.web.servlet.DispatcherServlet</servlet-class>\n    <!-- 指定 Spring 容器启动加载的配置文件-->\n    <init-param>\n      <param-name>contextConfigLocation</param-name>\n      <param-value>classpath:mvc.xml</param-value>\n    </init-param>\n    <!-- Tomcat 启动初始化 -->\n    <load-on-startup>1</load-on-startup>\n  </servlet>\n  <servlet-mapping>\n    <servlet-name>dispatcherServlet</servlet-name>\n    <url-pattern>/</url-pattern>\n  </servlet-mapping>\n</web-app>\n复制代码\n```\n\n\n\n## 3.3、注意\n\nload-on-startup 元素是可选的：若值为 0 或者大于 0 时，表示容器在应用启动时就构建 Servlet 并调用其 init 方法做初始化操作（非负数的值越小，启动该 Servlet 的优先级越高）；若值为一个负数时或者没有指定时，则在第一次请求该 Servlet 才加载。配置的话，就可以让 SpringMVC 初始化的工作在容器启动的时候完成，而不是丢给用户请求去完成，提高用户访问的体验性。\n\n## 3.4、映射路径\n\n配置前端控制器的映射路径一般有以下的三种形式：\n\n1.  配置如 .do、.htm 是最传统方式，可以访问静态文件（图片、 JS、 CSS 等），但不支持 RESTful风格。\n2.  配置成 /，可以支持流行的 RESTful 风格，但会导致静态文件（图片、 JS、 CSS 等）被拦截后不能访问。\n3.  配置成 /*，是错误的方式，可以请求到 Controller 中，但跳转到调转到 JSP 时被拦截，不能渲染JSP 视图，也会导致静资源访问不了。\n\n## 3.4.1、访问静态资源和 JSP 被拦截的原因\n\n\n\n```\nTomcat 容器处理静态资源是交由内置 DefaultServlet 来处理的（拦截路径是 /），处理 JSP 资源是交由内置的 JspServlet 处理的（拦截路径是*.jsp | *.jspx）。\n    启动项目时，先加载容器的 web.xml，而后加载项目中的 web.xml。当拦截路径在两者文件中配置的一样，后面会覆盖掉前者。\n    所以前端控制器配置拦截路径是 / 的所有静态资源都会交由前端控制器处理，而拦截路径配置 /*，所有静态资源和 JSP 都会交由前端控制器处理。\n复制代码\n```\n\n\n\n## 3.4.2、如何解决\n\n## 3.4.2.1、方式一\n\n在 web.xml 中修改，修改前端控制器的映射路径修改为*.do，**但注意，访问控制器里的处理方法时，请求路径须携带 .do。**\n\n\n\n```\n<servlet-mapping>\n\t<servlet-name>dispatcherServlet</servlet-name>\n\t<url-pattern>*.do</url-pattern>\n</servlet-mapping>\t\n复制代码\n```\n\n\n\n## 3.4.2.2、方式二\n\n在 mvc.xml中加入一段配置，这个配置会在 Spring MVC 上下文中创建存入一个 DefaultServletHttpRequestHandler 的 bean，它会 对进入DispatcherServlet的请求进行筛查，若不是映射的请求，就将该请求交由容器默认的 Servlet处理。\n\n\n\n```\n<mvc:default-servlet-handler/>\n复制代码\n```\n\n\n\n## 3.5、@ModelAttribute 注解\n\n在形参中的对象（**必须是自定义类型**），SpringMVC会默认将他存入Model中，名称是参数的类名首字母小写，有些时候，这个类会显得格外长，但是我们又有这种需求，比方说：查询条件的回显。我们只需在自定义类的前面加@ModelAttribute，里面写我们需要修改的key的名称即可。\n\n\n\n```\npackage cn.wolfcode.web.controller;\n\t@Controller\n\tpublic class RequestController {\n\t\t@RequestMapping(\"/req7\")\n\t\t\tpublic String resp7(@ModelAttribute(\"u\") User user) {\n\t\t\treturn \"m\";\n\t\t}\n}\n复制代码\n```\n\n\n\n## 四、处理响应\n\nSpringMVC的作用是请求和处理响应，响应处理是指怎么编写控制器里面的处理方法接受请求做响应，找视图文件和往作用域中存入数据。要处理方法要做响应，一般处理方法返回的类型为 ModelAndView 和 String。\n\n## 4.1、返回 ModelAndView\n\n方法中返回 ModelAndView 对象，此对象中设置模型数据并指定视图。前端依旧是使用JSTL+CgLib来进行取值。他有两个常用方法：\n\n1.  addObject(String key, Object value)：设置共享数据的 key 和 value。\n2.  addObject(Object value)：设置共享数据的 value，key 为该 value 类型首字母小写。\n\n\n\n```\npackage cn.linstudy.web.controller;\n\nimport org.springframework.stereotype.Controller;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.servlet.ModelAndView;\n\n@Controller\npublic class ResponseController {\n  // 提供方法处理请求，localhost/resp1\n  @RequestMapping(\"/resp1\")\n  public ModelAndView resp1() {\n// 通过创建这个类对象，告诉 Spring MVC 找什么视图文件， 往作用域或者说往模型中存入什么数据\n    ModelAndView mv = new ModelAndView();\n// 往作用域或者模型中存入数据\n    mv.addObject(\"msg\", \"方法返回类型是 ModelAndView\");\n// 找视图\n    mv.setViewName(\"/WEB-INF/views/resp.jsp\");\n    return mv;\n  }\n复制代码\n```\n\n\n\n## 4.2、返回String\n\n返回 String 类型（使用广泛），此时如果我们需要共享数据，那么就需要用到HttpServlet对象，Spring帮我们封装好了一个对象：Model 。组合使用，用其往作用域或模型中存入数据。\n\n\n\n```\npackage cn.instudy.web.controller;\n\nimport org.springframework.stereotype.Controller;\nimport org.springframework.ui.Model;\nimport org.springframework.web.bind.annotation.RequestMapping;\n\n@Controller\npublic class ResponseController {\n  // 提供方法处理请求，localhost/resp2\n  @RequestMapping(\"/resp2\")\n  public String resp2(Model model) {\n// 往作用域或者模型中存入数据\n    model.addAttribute(\"msg\", \"方法返回类型是 String\");\n// 返回视图名\n    return \"/WEB-INF/views/resp.jsp\";\n  }\n}\n复制代码\n```\n\n\n\n## 4.3、改进\n\n我们会发现，如果我们需要写返回界面的话需要不断的写前缀和后缀，这个时候需要进行消除消除视图前缀和后缀，我们只需在Spring中进行配置视图解析器即可。\n\n\n\n```\n<!--\n配置视图解析器 配置这个Spring MVC 找视图的路径就是：前缀 + 逻辑视图名（处理方法设置或返回视图名）+ 后缀名\n-->\n<bean >\n\t<!-- 视图前缀 -->\n\t<property name=\"prefix\" value=\"/WEB-INF/views/\"/>\n\t<!-- 视图后缀 -->\n\t<property name=\"suffix\" value=\".jsp\"/>\n</bean>\n复制代码\n```\n\n\n\n## 五、请求转发和重定向\n\n## 5.1、请求转发和重定向的区别\n\n|  | 几次请求 | 地址栏 | WEB-INF中资源 | 共享请求数据 | 有无表单重复提交 |\n| --- | --- | --- | --- | --- | --- |\n| 请求转发 | 1次 | 不改变 | 可以访问 | 可以共享 | 有 |\n| 重定向 | 多次 | 改变 | 不可访问 | 不可共享 | 无 |\n\n## 5.2、请求转发\n\n加上forward 关键字，表示请求转发，相当于request.getRequestDispatcher().forward(request,response)，转发后浏览器地址栏不变，共享之前请求中的数据。**加了关键字后，配置的视图解析器就不起作用了。如果返回视图必须写全路径**\n\n\n\n```\npackage cn.linstudy.web.controller;\n@Controller\npublic class ResponseController {\n\n\t@RequestMapping(\"/TestForward\")\n\tpublic String forward() {\n\t\treturn \"forward:/WEB-INF/views/welcome.jsp\";\n\t}\n}\n复制代码\n```\n\n\n\n## 5.3、重定向\n\n加上 redirect 关键字，表示重定向，相当于 response.sendRedirect()，重定向后浏览器地址栏变为重定向后的地址，不共享之前请求的数据。\n\n\n\n```\npackage cn.linstudy.web.controller;\n@Controller\npublic class ResponseController {\n\t// localhost/r\n\t@RequestMapping(\"/TestRedirect\")\n\tpublic String redirect() {\n\t\treturn \"redirect:/static/demo.html\";\n\t}\n}\n复制代码\n```\n\n\n\n## 5.4、请求路径\n\n在请求转发和重定向的时候，我们一般有两种方式来写请求路径：\n\n1.  加/：使用是绝对路径（推荐使用），从项目根路径找。(/response/test6 ---> \"redirect:/hello.html\" ---> localhost:/hello.html)\n2.  不加/：使用是相对路径，相对于上一次访问上下文路径的上一级找。(/response/test6 ---> \"redirect:hello.html\" ---> localhost:/response/hello.html)\n\n## 六、参数处理\n\n## 6.1、处理简单类型的请求参数\n\n我们在控制器的如何获取请求中的简单数据类型的参数参数？简单数据类型包含基本数据类型及其包装类、String 和BigDecimal 等形参接收。\n\n## 6.1.1、请求参数名和控制器方法参数列表形参同名\n\n如果前台传递过来的参数名和控制器方法中参数列表的形参参数名相同就无需做任何操作，SpringMVC会自动帮我们进赋值。\n\n\n\n```\n// 请求路径为：/req1?username=zs&age=18\npackage cn.linstudy.web.controller;\n\t@Controller\n\tpublic class RequestController {\n\t\t@RequestMapping(\"/req1\")\n\t\tpublic ModelAndView resp1(String username, int age) {\n\t\t\tSystem.out.println(username);\n\t\t\tSystem.out.println(age);\n\t\t\treturn null;\n\t\t}\n}\n复制代码\n```\n\n\n\n## 6.1.2、请求参数名和控制器方法参数列表形参不同名\n\n如果前台传递过来的参数名和控制器方法中参数列表的形参参数名不相同的话，我们需要使用一个注解@RequestParam(\"前台携带的参数名\")来告诉SpringMVC我们任何对数据来进行赋值。\n\n\n\n```\n// 请求路径为：/req1?username=zs&age=18\npackage cn.linstudy.web.controller;\n\t@Controller\n\tpublic class RequestController {\n\t\t@RequestMapping(\"/req1\")\n\t\tpublic ModelAndView resp1(@RequestParam(\"username\") String username1, @RequestParam(\"age\") int age1) {\n\t\t\tSystem.out.println(username);\n\t\t\tSystem.out.println(age);\n\t\t\treturn null;\n\t\t}\n}\n复制代码\n```\n\n\n\n## 6.2、处理复杂类型的请求参数\n\n## 6.2.1、数组类型\n\n对于数组类型参数，我们只需在方法参数的形参列表中定义一个同名的数组类型进行接收即可。\n\n\n\n```\n// 请求路径 /req3?ids=1&ids=2&ids=3\npackage cn.linstudy.web.controller;\n\t@Controller\n\tpublic class RequestController {\n\t\t@RequestMapping(\"/req3\")\n\t\tpublic ModelAndView resp3(Long[] ids) {\n\t\t\tSystem.out.println(Arrays.toString(ids));\n\t\t\treturn null;\n\t\t}\n}\n复制代码\n```\n\n\n\n## 6.2.2、自定义类型\n\n我们在很多的时候，需要接收的是一个自定义类型的对象。比如说我们进行保存用户，需要将前台传递的数据进行封装成一个自定义的用户类型，那么这个时候，只需要保证自定义的类型里面的字段和前端传过来的字段相同（**注意传递参数名与封装对象的属性名一致**），SpringMVC即可自动进行封装。\n\n\n\n```\n// /req4?username=hehe&password=666\npackage cn.linstudy.web.controller;\t\n\t@Controller\n\tpublic class RequestController {\n\t\t@RequestMapping(\"/req4\")\n\t\tpublic ModelAndView resp4(User user) {\n\t\t\tSystem.out.println(user);\n\t\t\treturn null\n\t\t}\n}\n复制代码\n```\n\n\n\n底层 Spring MVC 根据请求地址对应调用处理方法，调用方法时发现要传递 User 类型的实参，SpringMVC 会反射创建 User 对象，之后通过请求参数名找对应的属性，给对象的属性设置对应的参数值。\n\n## 6.3、处理日期类型的请求参数\n\n## 6.3.1、日期在请求参数上\n\n如果日期在请求参数上，那么我们需要在处理方法的 Date 类型的形参贴上 @DateTimeFormat注解。\n\n\n\n```\npackage cn.linstudy.controller;\n\t@Controller\n\tpublic class RequestController {\n\t\t@RequestMapping(\"/req5\")\n\t\t// 注意形参的类型为 java.util.Date\n\t\tpublic ModelAndView resp5(@DateTimeFormat(pattern=\"yyyy-MM-dd\")Date date) {\n\t\t\tSystem.out.println(date.toLocaleString());\n\t\t\treturn null;\n\t\t}\n}\n复制代码\n```\n\n\n\n## 6.3.2、在封装的对象上\n\n如果日期在封装对象的字段，那么我们需要在字段的上贴@DateTimeFormat注解。\n\n\n\n```\npackage cn.linstudy.domain;\n\tpublic class User {\n\tprivate Long id;\n\tprivate String Username;\n\tprivate String password;\n\t// 增加下面这个字段，并贴注解\n\t@DateTimeFormat(pattern=\"yyyy-MM-dd\")\n\tprivate Date date;\n\t// 省略 setter getter toString\n}\n复制代码\npackage cn.linstudy.controller;\n\t@Controller\n\tpublic class RequestController {\n\t\t@RequestMapping(\"/req6\")\n\t\tpublic ModelAndView resp6(User user) {\n\t\t\tSystem.out.println(user);\n\t\t\treturn null;\n\t\t}\n}\n复制代码\n```\n\n\n\n## 七、文件上传与下载\n\n## 7.1、文件上传\n\n回顾之前使用 Servlet3.0 来解决文件上传的问题，编写上传表单（POST、multipart/form-data），还在处理方法 doPost 中编写解析上传文件的代码。但是在SpringMVC是可以帮我们简化文件上传的步骤和代码。\n\n## 7.1.1、编写表单\n\n注意请求数据类型必须是：multipart/form-data，且请求方式是POST。\n\n\n\n```\n<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\"UTF-8\">\n<title>文件上传</title>\n</head>\n<body>\n\t<form action=\"/upload\" method=\"POST\" enctype=\"multipart/form-data\">\n\t\t文件:<br>\n\t\t\n\t</form>\n</body>\n</html>\n复制代码\n```\n\n\n\n## 7.1.2、修改web.xml\n\n我们可以在web.xml中指定上传文件的大小。\n\n\n\n```\n<servlet>\n\t<servlet-name>dispatcherServlet</servlet-name>\n\t<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>\n\t<init-param>\n\t\t<param-name>contextConfigLocation</param-name>\n\t\t<param-value>classpath:mvc.xml</param-value>\n\t</init-param>\n\t<load-on-startup>1</load-on-startup>\n\t<multipart-config>\n\t\t<max-file-size>52428800</max-file-size>\n\t\t<max-request-size>52428800</max-request-size>\n\t</multipart-config>\n</servlet>\n<servlet-mapping>\n\t<servlet-name>dispatcherServlet</servlet-name>\n\t<url-pattern>/</url-pattern>\n</servlet-mapping>\n复制代码\n```\n\n\n\n## 7.1.3、配置上传解析器\n\n在mvc.xml中配置上传解析器，**使用springmvc中multipartfile接收客户端上传的文件必须配置文件上传解析器且解析的id必须为multipartResolver**\n\n\n\n```\n<bean id=\"multipartResolver\" >\n  <!--控制文件上传大小单位字节 默认没有大小限制 这里是2-->\n  <property name=\"maxUploadSize\" value=\"2097152\"/>\n</bean>\n复制代码\n```\n\n\n\n## 7.1.4、配置上传控制器\n\n\n\n```\npackage cn.linstudy.controller;\n\t@Controller\n\tpublic class UploadController {\n\t// Spring 容器存在 ServletContext 类型的对象，所以定义好 ServletContext 类型字段贴@Autowired 注解即可获取到\n\t@Autowired\n\tprivate ServletContext servletContext;\n\t@RequestMapping(\"/upload\")\n\tpublic ModelAndView upload(Part pic) throws Exception {\n\t\tSystem.out.println(pic.getContentType()); // 文件类型\n\t\tSystem.out.println(pic.getName()); // 文件参数名\n\t\tSystem.out.println(pic.getSize()); // 文件大小\n\t\tSystem.out.println(pic.getInputStream()); // 文件输入流\n\t\t// FileCopyUtils.copy(in, out)，一个 Spring 提供的拷贝方法\n\t\t// 获取项目 webapp 目录下 uploadDir 目录的绝对路径\n\t\tSystem.out.println(servletContext.getRealPath(\"/uploadDir\"));\n\t\treturn null;\n\t}\n}\n复制代码\n```\n\n\n\n## 7.2、文件下载\n\n文件下载:将服务器上的文件下载到当前用户访问的计算机的过程称之为文件下载\n\n## 7.2.1、开发控制器\n\n下载时必须设置响应的头信息,指定文件以何种方式保存,另外下载文件的控制器不能存在返回值,代表响应只用来下载文件信息`\n\n\n\n```\n/**\n     * 测试文件下载\n     * @param fileName 要下载文件名\n     * @return\n     */\n    @RequestMapping(\"download\")\n    public String download(String fileName, HttpServletRequest request, HttpServletResponse response) throws IOException {\n        //获取下载服务器上文件的绝对路径\n        String realPath = request.getSession().getServletContext().getRealPath(\"/down\");\n        //根据文件名获取服务上指定文件\n        FileInputStream is = new FileInputStream(new File(realPath, fileName));\n        //获取响应对象设置响应头信息\n        response.setHeader(\"content-disposition\",\"attachment;fileName=\"+ URLEncoder.encode(fileName,\"UTF-8\"));\n        ServletOutputStream os = response.getOutputStream();\n        IOUtils.copy(is,os);\n        IOUtils.closeQuietly(is);\n        IOUtils.closeQuietly(os);\n        return null;\n    }\n```\n"
  },
  {
    "path": "docs/Spring全家桶/SpringMVC源码分析/SpringMVC源码分析：SpringMVC的视图解析原理.md",
    "content": "# 目录\n* [目录](#目录)\n* [前言](#前言)\n* [重要接口和类介绍](#重要接口和类介绍)\n* [源码分析](#源码分析)\n* [编码自定义的ViewResolver](#编码自定义的viewresolver)\n* [总结](#总结)\n\n\n转自[SpringMVC视图机制详解[附带源码分析]](https://www.cnblogs.com/fangjian0423/p/springMVC-view-viewResolver.html)\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章将同步到我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《Spring和SpringMVC源码分析》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从spring基础入手，一步步地学习spring基础和springmvc的框架知识，并上手进行项目实战，spring框架是每一个Java工程师必须要学习和理解的知识点，进一步来说，你还需要掌握spring甚至是springmvc的源码以及实现原理，才能更完整地了解整个spring技术体系，形成自己的知识框架。\n\n后续还会有springboot和springcloud的技术专题，陆续为大家带来，敬请期待。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供部分知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->  \n\n\n## 目录\n\n*   [前言](http://www.cnblogs.com/fangjian0423/p/springMVC-view-viewResolver.html#preface)\n*   [重要接口和类介绍](http://www.cnblogs.com/fangjian0423/p/springMVC-view-viewResolver.html#introduction)\n*   [源码分析](http://www.cnblogs.com/fangjian0423/p/springMVC-view-viewResolver.html#analysis)\n*   [编码自定义的ViewResolver](http://www.cnblogs.com/fangjian0423/p/springMVC-view-viewResolver.html#demo)\n*   [总结](http://www.cnblogs.com/fangjian0423/p/springMVC-view-viewResolver.html#summary)\n*   [参考资料](http://www.cnblogs.com/fangjian0423/p/springMVC-view-viewResolver.html#reference)\n\n## 前言\n\nSpringMVC是目前主流的Web MVC框架之一。\n\n如果有同学对它不熟悉，那么请参考它的入门blog：[http://www.cnblogs.com/fangjian0423/p/springMVC-introduction.html](http://www.cnblogs.com/fangjian0423/p/springMVC-introduction.html)\n\n本文将分析SpringMVC的视图这部分内容，让读者了解SpringMVC视图的设计原理。\n\n## 重要接口和类介绍\n\n**1.View接口**\n\n视图基础接口，它的各种实现类是无状态的，因此是线程安全的。 该接口定义了两个方法：　  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/092359485145662.png)\n\n**2.AbstractView抽象类**\n\nView接口的基础实现类。我们稍微介绍一下这个抽象类。\n\n首先看下这个类的属性：  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/100014083899865.png)\n\n再看下抽象类中接口方法的实现：  \n　　getContentType方法直接返回contentType属性即可。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/100110310773735.png)\n\nrender方法：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/102247323892787.png)\n\n**3.AbstractUrlBasedView抽象类**\n\n继承自AbstractView抽象类，增加了1个类型为String的url参数。\n\n**4.InternalResourceView类**\n\n继承自AbstractUrlBasedView抽象类的类，表示JSP视图。\n\n我们看下这个类的renderMergedOutputModel方法(AbstractView抽象类定义的抽象方法，为View接口提供的render方法服务)。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/102252020309641.png)\n\n**5.JstlView类**\n\nJSTL视图，继承自InternalResourceView，该类大致上与InternalResourceView类一致。\n\n**6.AbstractTemplateView抽象类**\n\n继承自AbstractUrlBasedView抽象类，重写了renderMergedOutputModel方法，在该方法中会调用renderMergedTemplateModel方法，renderMergedTemplateModel方法为新定义的抽象方法。\n\n该抽象类有几个boolean属性exposeSessionAttributes，exposeRequestAttributes。 设置为true的话会将request和session中的键值和值丢入到renderMergedTemplateModel方法中的model这个Map参数中。\n\n这个类是某些模板引擎视图类的父类。 比如FreemarkerView，VelocityView。\n\n**7.FreeMarkerView类**\n\n继承自AbstractTemplateView抽象类。\n\n直接看renderMergedTemplateModel方法，renderMergedTemplateModel内部会调用doRender方法：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/102258387178319.png)\n\n**8\\. RedirectView类**\n\n继承自AbstractUrlBasedView，并实现SmartView接口。SmartView接口定义了1个boolean isRedirectView();方法。\n\n该视图的renderMergedOutputModel方法主要就是通过response.sendRedirect进行重定向。\n\n有关RedirectView方面的知识楼主另外写了1篇博客。[http://www.cnblogs.com/fangjian0423/p/springMVC-redirectView-analysis.html](http://www.cnblogs.com/fangjian0423/p/springMVC-redirectView-analysis.html)\n\n**9.ViewResolver接口**\n\n视图解释器，用来解析视图View，与View接口配合使用。\n\n该接口只有1个方法，通过视图名称viewName和Locale对象得到View接口实现类：\n\n \n  View resolveViewName(String viewName, Locale locale) throws Exception;```  \n  \n**10.AbstractCachingViewResolver抽象类**  \n  \n　　带有缓存功能的ViewResolver接口基础实现抽象类，该类有个属性名为viewAccessCache的以 \"viewName_locale\" 为key， View接口为value的Map。  \n  \n　　该抽象类实现的resolveViewName方法内部会调用createView方法，方法内部会调用loadView抽象方法。  \n  \n**11.UrlBasedViewResolver类**  \n  \n　　继承自AbstractCachingViewResolver抽象类、并实现Ordered接口的类，是ViewResolver接口简单的实现类。  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/102341126705605.png)  \n  \n　　该类复写了createView方法：  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/102349273585708.png)  \n  \n　　父类(AbstractCachingViewResolver)的createView方法内部会调用loadView抽象方法，UrlBasedViewResolver实现了这个抽象方法：  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/110037187498815.png)  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/110043510779523.png)  \n  \n　　下面对UrlBasedViewResolver做1个test，配置如下：  \n  \n```  \n<bean class=\"org.springframework.web.servlet.view.UrlBasedViewResolver\">  \n  <property name=\"prefix\" value=\"/WEB-INF/view/\"/>  <property name=\"suffix\" value=\".jsp\"/>  <property name=\"viewClass\" value=\"org.springframework.web.servlet.view.InternalResourceView\"/>  <property name=\"viewNames\">  \n      <value type=\"java.lang.String\">*</value>    \n  </property>  <property name=\"contentType\" value=\"text/html;charset=utf-8\"/>  \n  <property name=\"attributesMap\">    <map>      <entry key=\"mytest\" value=\"mytestvalue\"/>    </map>  </property>  <property name=\"attributes\">    <props>      <prop key=\"test\">testvalue</prop>    </props>  </property></bean>  \n\n```  \n  \n　　我们看到：以InternalResourceView这个JSP视图作为视图；viewNames我们设置了_，这里的_代表全部视图名(这个viewNames属性不设置也可以，代表全部视图名都处理)；http响应头部contentType信息：text/html;charset=utf-8；attributesMap和attributes传入的Map和Properties参数都会被丢入到staticAttributes属性中，这个staticAttributes会被设置成AbstractView的staticAttributes属性，也就是request域中的参数。  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/122254034056621.png)  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/122255053584961.png)  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/122253337649444.png)  \n  \n　　我们看到request域中没有设置mytest和testvalue值。但是页面中会显示，因为我们配置了attributesMap和attributes参数。  \n  \n　　如果我们把viewNames中的\"*\"改成\"index1\"。那么就报错了，因为处理视图名的时候index匹配不上index1。  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/122258119528822.png)  \n  \n**12.InternalResourceViewResolver类**  \n  \n　　继承自UrlBasedViewResolver，以InternalResourceView作为视图，若项目中存在“javax.servlet.jsp.jstl.core.Config”该类，那么会以JstlView作为视图。重写了buildView方法，主要就是为了给InternalResourceView视图设置属性。  \n  \n**13.AbstractTemplateViewResolver类**  \n  \n　　继承自UrlBasedViewResolver，重写了buildView方法，主要就是构造AbstractTemplateView以及为它设置相应的属性。  \n  \n**14.FreeMarkerViewResolver类**  \n  \n　　继承自AbstractTemplateViewResolver，将视图设置为FreeMarkerView。  \n  \n**15\\. ModelAndView对象**  \n  \n　　顾名思义，带有视图和Model属性的一个模型和视图类。  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/122342141709643.png)  \n  \n　　值得注意的是，这个视图属性是一个Object类型的数据，可以直接是View接口的实现类或者视图名(字符串)。  \n  \n## 源码分析  \n  \n下面我们来分析SpringMVC处理视图的源码。  \n  \nSpringMVC在处理请求的时候，通过RequestMappingHandlerMapping得到HandlerExecutionChain，然后通过RequestMappingHandlerAdapter得到1个ModelAndView对象，之后通过processDispatchResult方法处理。  \n  \nprocessDispatchResult方法如下：  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/122331490924509.png)  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/122339489831665.png)  \n  \n如果配置的ViewResolver如下：  \n  \n````  \n<bean class=\"org.springframework.web.servlet.view.InternalResourceViewResolver\">  \n  <property name=\"prefix\" value=\"/WEB-INF/view/\"/>  <property name=\"suffix\" value=\".jsp\"/></bean>  \n\n````  \n  \n那么就是使用InternalResourceViewResolver来解析视图。  \n  \n之前分析过，InternalResourceViewResolver重写了UrlBasedViewResolver的buildView方法。但是还是会调用UrlBasedViewResolver的buildView方法。  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/130106345772394.png)  \n  \n最终得到InternalResourceView或JstlView视图。这两个视图的render方法本文介绍重要接口及类的时候已分析。  \n  \nPS：DispathcerServlet中的viewResolvers属性是个集合，如果存在多个ViewResolver对象，必定会有优先级的问题，这部分的内容请参考楼主的另一篇博客：  \n  \n[http://www.cnblogs.com/fangjian0423/p/spring-Ordered-interface.html](http://www.cnblogs.com/fangjian0423/p/spring-Ordered-interface.html)  \n  \n## 编码自定义的ViewResolver  \n  \n下面，我们就来编写自定义的ViewResolver。  \n  \n自定义的ViewResolver处理视图名的时候，遇到 \"jsp:\" 开头的会找jsp页面，遇到 \"freemarker:\" 开头的找freemarker页面。  \n  \n````\npublic class CustomViewResolver extends UrlBasedViewResolver {\n\npublic static final String JSP_URL_PREFIX = \"jsp:\";  public static final String FTL_URL_PREFIX = \"freemarker:\";  \nprivate static final boolean jstlPresent = ClassUtils.isPresent(            \"javax.servlet.jsp.jstl.core.Config\", CustomViewResolver.class.getClassLoader());  \nprivate Boolean exposePathVariables = false;  \nprivate boolean exposeRequestAttributes = false;  \nprivate boolean allowRequestOverride = false;  \nprivate boolean exposeSessionAttributes = false;  \nprivate boolean allowSessionOverride = false;  \nprivate boolean exposeSpringMacroHelpers = true;  \npublic CustomViewResolver() {      this.setViewClass(FreeMarkerView.class);  }    \n@Override  protected AbstractUrlBasedView buildView(String viewName) throws Exception {    if(viewName.startsWith(FTL_URL_PREFIX)) {  \nreturn buildFreemarkerView(viewName.substring(FTL_URL_PREFIX.length()));    } else if(viewName.startsWith(JSP_URL_PREFIX)) {        Class viewCls = jstlPresent ? JstlView.class : InternalResourceView.class;        return buildView(viewCls, viewName.substring(JSP_URL_PREFIX.length()), getPrefix(), \".jsp\");    } else {        //默认以freemarker处理  \nreturn buildFreemarkerView(viewName);    }  }  \nprivate AbstractUrlBasedView build(Class viewClass, String viewName, String prefix, String suffix) {    AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass);  \nview.setUrl(prefix + viewName + suffix);    String contentType = getContentType();    if (contentType != null) {        view.setContentType(contentType);    }    view.setRequestContextAttribute(getRequestContextAttribute());    view.setAttributesMap(getAttributesMap());    if (this.exposePathVariables != null) {        view.setExposePathVariables(exposePathVariables);    }    return view;  }\n\nprivate AbstractUrlBasedView buildFreemarkerView(String viewName) throws Exception {    AbstractTemplateView view = (AbstractTemplateView) build(FreeMarkerView.class, viewName, \"\", getSuffix());  \nview.setExposeRequestAttributes(this.exposeRequestAttributes);    view.setAllowRequestOverride(this.allowRequestOverride);    view.setExposeSessionAttributes(this.exposeSessionAttributes);    view.setAllowSessionOverride(this.allowSessionOverride);    view.setExposeSpringMacroHelpers(this.exposeSpringMacroHelpers);    return view;  }\n\n//get set方法省略\n\n}\n````\n\n  \n  \nxml配置：  \n  \n```  \n<bean class=\"org.format.demo.support.viewResolver.CustomViewResolver\">  \n  <property name=\"prefix\" value=\"/WEB-INF/view/\"/>  <property name=\"suffix\" value=\".ftl\"/>  <property name=\"contentType\" value=\"text/html;charset=utf-8\"/>  <property name=\"exposeRequestAttributes\" value=\"true\"/>  <property name=\"exposeSessionAttributes\" value=\"true\"/>  <property name=\"exposeSpringMacroHelpers\" value=\"true\"/>  <property name=\"requestContextAttribute\" value=\"request\"/></bean>  \n\n\n<bean id=\"freemarkerConfig\" class=\"org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer\">  \n  <property name=\"templateLoaderPath\" value=\"/WEB-INF/view/\"/>  <property name=\"defaultEncoding\" value=\"utf-8\"/>  <property name=\"freemarkerSettings\">    <props>      <prop key=\"template_update_delay\">10</prop>      <prop key=\"locale\">zh_CN</prop>      <prop key=\"datetime_format\">yyyy-MM-dd</prop>      <prop key=\"date_format\">yyyy-MM-dd</prop>      <prop key=\"number_format\">#.##</prop>    </props>  </property></bean>  \n\n```  \n  \n简单解释一下：CustomViewResolver解析视图名的时候，判断 \"jsp:\" 和 \"freemarker:\" 开头的名字，如果是 \"jsp:\" 开头的，如果有JSTL依赖，构造JSTLView视图，否则构造InternalResourceView视图。如果是\"freemarker:\" 构造FreemarkerView。在构造视图之前分别会设置一些属性。  \n  \nxml配置：配置prefix是为了给jsp视图用的，freemarker视图不需要prefix，因为FreemarkerView内部会使用配置的FreeMarkerConfigurer，并用FreeMarkerConfigurer内部的templateLoaderPath属性作为前缀，配置的suffix是为了让FreemarkerView使用，当后缀。  \n  \n最后附上Controller代码：  \n  \n```  \n@Controller  \n@RequestMapping(value = \"/tvrc\")  \npublic class TestViewResolverController {\n\n    @RequestMapping(\"jsp\")    public ModelAndView jsp(ModelAndView view) {        view.setViewName(\"jsp:trvc/index\");        return view;    }  \n    @RequestMapping(\"/ftl\")    public ModelAndView freemarker(ModelAndView view) {        view.setViewName(\"freemarker:trvc/index\");        return view;    }  \n}\n```  \n  \n视图 /WEB-INF/view/trvc/index.jsp 中的的内容是输出  \n  \n### This is jsp page  \n  \n视图 /WEB-INF/view/trvc/index.ftl 中的的内容是输出  \n  \n### This is freemarker page  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/140202040149946.png)  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/140202212495247.png)  \n  \n## 总结  \n  \n本文分析了SpringMVC中的视图机制，View和ViewResolver这两个接口是视图机制的核心，并分析了几个重要的View和ViewResolver接口实现类，最终写了一个区别jsp和freemarker视图的ViewResolver实现类，让读者更加理解视图机制。  \n  \n希望这篇文章能帮助读者了解SpringMVC视图机制。  \n  \n文中难免有错误，希望读者能够指明出来。"
  },
  {
    "path": "docs/Spring全家桶/SpringMVC源码分析/SpringMVC源码分析：SpringMVC设计理念与DispatcherServlet.md",
    "content": "# 目录\n  * [SpringMVC简介](#springmvc简介)\n  * [HandlerMapping接口](#handlermapping接口)\n  * [DispatcherServlet接受请求并找到对应Handler](#dispatcherservlet接受请求并找到对应handler)\n  * [HandlerInterceptor接口](#handlerinterceptor接口)\n  * [HandlerAdapter](#handleradapter)\n  * [请求流程总结](#请求流程总结)\n\n\n转自：https://my.oschina.net/lichhao/blog\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章将同步到我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《Spring和SpringMVC源码分析》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从spring基础入手，一步步地学习spring基础和springmvc的框架知识，并上手进行项目实战，spring框架是每一个Java工程师必须要学习和理解的知识点，进一步来说，你还需要掌握spring甚至是springmvc的源码以及实现原理，才能更完整地了解整个spring技术体系，形成自己的知识框架。\n\n后续还会有springboot和springcloud的技术专题，陆续为大家带来，敬请期待。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供部分知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n## SpringMVC简介\n\nSpringMVC作为Struts2之后异军突起的一个表现层框架，正越来越流行，相信javaee的开发者们就算没使用过SpringMVC，也应该对其略有耳闻。我试图通过对SpringMVC的设计思想和源码实现的剖析，从抽象意义上的设计层面和实现意义上的代码层面两个方面，逐一揭开SpringMVC神秘的面纱，本文的代码，都是基于Spring的 3.1.3RELEASE版本。\n\n任何一个框架，都有自己特定的适用领域，框架的设计和实现，必定是为了应付该领域内许多通用的，烦琐的、基础的工作而生。SpringMVC作为一个表现层框架，也必须直面Web开发领域中表现层中的几大课题，并给出自己的回答：\n\n*   URL到框架的映射。\n*   http请求参数绑定\n*   http响应的生成和输出\n\n这三大课题，组成一个完整的web请求流程，每一个部分都具有非常广阔的外延。SpringMVC框架对这些课题的回答又是什么呢？\n\n学习一个框架，首要的是要先领会它的设计思想。从抽象、从全局上来审视这个框架。其中最具有参考价值的，就是这个框架所定义的核心接口。核心接口定义了框架的骨架，也在最抽象的意义上表达了框架的设计思想。\n\n下面我以一个web请求流程为载体，依次介绍SpringMVC的核心接口和类。\n\n用户在浏览器中，输入了http://www.xxxx.com/aaa/bbb.ccc的地址，回车后，浏览器发起一个http请求。请求到达你的服务器后，首先会被SpringMVC注册在web.xml中的前端转发器DispatcherServlet接收，DispatcherServlet是一个标准的Servlet，它的作用是接受和转发web请求到内部框架处理单元。\n\n## HandlerMapping接口\n\n下面看一下第一个出现在你面前的核心接口，它是在org.springframework.web.servlet包中定义的HandlerMapping接口：\n\n```\npackage org.springframework.web.servlet;\n\nimport javax.servlet.http.HttpServletRequest;\n\npublic interface HandlerMapping {\n\n    String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + \".pathWithinHandlerMapping\";\n\n    String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + \".bestMatchingPattern\";\n\n    String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + \".introspectTypeLevelMapping\";\n\n    String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + \".uriTemplateVariables\";\n\n    String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + \".producibleMediaTypes\";\n\n    HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;\n\n}\n```\n\n为了阅读方便，我去掉了源码中的注释，但是我强烈建议你一定要记得去阅读它，这样你才能从框架的设计者口中得到最准确的关于这个类或者接口的设计说明。类中定义的几个常量，我们先不去管它。关键在于这个接口中唯一的方法：\n\n```\nHandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;\n```\n\n这个方法就算对于一个java初学者来说，也很容易理解：它只有一个类型为HttpServletRequest的参数，throws Exception的声明表示它不处理任何类型的异常，HandlerExecutionChain是它的返回类型。\n\n## DispatcherServlet接受请求并找到对应Handler\n\n回到DispatcherServlet的处理流程，当DispatcherServlet接收到web请求后，由标准Servlet类处理方法doGet或者doPost，经过几次转发后，最终注册在DispatcherServlet类中的HandlerMapping实现类组成的一个List（有点拗口）会在一个循环中被遍历。以该web请求的HttpServletRequest对象为参数，依次调用其getHandler方法，第一个不为null的调用结果，将被返回。DispatcherServlet类中的这个遍历方法不长，贴一下，让大家有更直观的了解。\n\n```\n/**\n     * Return the HandlerExecutionChain for this request.\n     * <p>Tries all handler mappings in order.\n     * @param request current HTTP request\n     * @return the HandlerExecutionChain, or <code>null</code> if no handler could be found\n     */\n    protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {\n        for (HandlerMapping hm : this.handlerMappings) {\n            if (logger.isTraceEnabled()) {\n                logger.trace(\n                        \"Testing handler map [\" + hm + \"] in DispatcherServlet with name '\" + getServletName() + \"'\");\n            }\n            HandlerExecutionChain handler = hm.getHandler(request);\n            if (handler != null) {\n                return handler;\n            }\n        }\n        return null;\n    }\n```\n\n是的，第一步处理就这么简单的完成了。一个web请求经过处理后，会得到一个HandlerExecutionChain对象，这就是SpringMVC对URl映射给出的回答。需要留意的是，HandlerMapping接口的getHandler方法参数是HttpServletRequest，这意味着，HandlerMapping的实现类可以利用HttpServletRequest中的所有信息来做出这个HandlerExecutionChain对象的生成”决策“。这包括，请求头、url路径、cookie、session、参数等等一切你从一个web请求中可以得到的任何东西（最常用的是url路径）。\n\nSpirngMVC的第一个扩展点，就出现在这里。我们可以编写任意的HandlerMapping实现类，依据任何策略来决定一个web请求到HandlerExecutionChain对象的生成。可以说，从第一个核心接口的声明开始，SpringMVC就把自己的灵活性和野心暴露无疑：哥玩的就是”Open-Closed“。\n\nHandlerExecutionChain这个类，就是我们下一个要了解的核心类。从名字可以直观的看得出，这个对象是一个执行链的封装。熟悉Struts2的都知道，Action对象也是被层层拦截器包装，这里可以做个类比，说明SpringMVC确实是吸收了Struts2的部分设计思想。\n\nHandlerExecutionChain类的代码不长，它定义在org.springframework.web.servlet包中，为了更直观的理解，先上代码。\n\n```\npackage org.springframework.web.servlet;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\n\nimport org.springframework.util.CollectionUtils;\n\npublic class HandlerExecutionChain {\n\n    private final Object handler;\n\n    private HandlerInterceptor[] interceptors;\n\n    private List<HandlerInterceptor> interceptorList;\n\n    public HandlerExecutionChain(Object handler) {\n        this(handler, null);\n    }\n\n    public HandlerExecutionChain(Object handler, HandlerInterceptor[] interceptors) {\n        if (handler instanceof HandlerExecutionChain) {\n            HandlerExecutionChain originalChain = (HandlerExecutionChain) handler;\n            this.handler = originalChain.getHandler();\n            this.interceptorList = new ArrayList<HandlerInterceptor>();\n            CollectionUtils.mergeArrayIntoCollection(originalChain.getInterceptors(), this.interceptorList);\n            CollectionUtils.mergeArrayIntoCollection(interceptors, this.interceptorList);\n        }\n        else {\n            this.handler = handler;\n            this.interceptors = interceptors;\n        }\n    }\n\n    public Object getHandler() {\n        return this.handler;\n    }\n\n    public void addInterceptor(HandlerInterceptor interceptor) {\n        initInterceptorList();\n        this.interceptorList.add(interceptor);\n    }\n\n    public void addInterceptors(HandlerInterceptor[] interceptors) {\n        if (interceptors != null) {\n            initInterceptorList();\n            this.interceptorList.addAll(Arrays.asList(interceptors));\n        }\n    }\n\n    private void initInterceptorList() {\n        if (this.interceptorList == null) {\n            this.interceptorList = new ArrayList<HandlerInterceptor>();\n        }\n        if (this.interceptors != null) {\n            this.interceptorList.addAll(Arrays.asList(this.interceptors));\n            this.interceptors = null;\n        }\n    }\n\n    public HandlerInterceptor[] getInterceptors() {\n        if (this.interceptors == null && this.interceptorList != null) {\n            this.interceptors = this.interceptorList.toArray(new HandlerInterceptor[this.interceptorList.size()]);\n        }\n        return this.interceptors;\n    }\n\n    @Override\n    public String toString() {\n        if (this.handler == null) {\n            return \"HandlerExecutionChain with no handler\";\n        }\n        StringBuilder sb = new StringBuilder();\n        sb.append(\"HandlerExecutionChain with handler [\").append(this.handler).append(\"]\");\n        if (!CollectionUtils.isEmpty(this.interceptorList)) {\n            sb.append(\" and \").append(this.interceptorList.size()).append(\" interceptor\");\n            if (this.interceptorList.size() > 1) {\n                sb.append(\"s\");\n            }\n        }\n        return sb.toString();\n    }\n\n}\n```\n\n乱七八糟一大堆，相信你也没全看完，也没必要全看。其实只需要看两行足矣。\n\n```\nprivate final Object handler;\n\n    private HandlerInterceptor[] interceptors;\n```\n\n不出我们所料，一个实质执行对象，还有一堆拦截器。这不就是Struts2中的实现么，SpringMVC没有避嫌，还是采用了这种封装。得到HandlerExecutionChain这个执行链（execution chain）之后，下一步的处理将围绕其展开。\n\n## HandlerInterceptor接口\n\nHandlerInterceptor也是SpringMVC的核心接口，定义如下：\n\n```\npackage org.springframework.web.servlet;\n\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\npublic interface HandlerInterceptor {\n\n    boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)\n        throws Exception;\n\n    void postHandle(\n            HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)\n            throws Exception;\n\n    void afterCompletion(\n            HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)\n            throws Exception;\n\n}\n```\n\n至此，HandlerExecutionChain整个执行脉络也就清楚了：在真正调用其handler对象前，HandlerInterceptor接口实现类组成的数组将会被遍历，其preHandle方法会被依次调用，然后真正的handler对象将被调用。\n\nhandler对象被调用后，就生成了需要的响应数据，在将处理结果写到HttpServletResponse对象之前（SpringMVC称为渲染视图），其postHandle方法会被依次调用。视图渲染完成后，最后afterCompletion方法会被依次调用，整个web请求的处理过程就结束了。\n\n在一个处理对象执行之前，之后利用拦截器做文章，这已经成为一种经典的框架设计套路。Struts2中的拦截器会做诸如参数绑定这类复杂的工作，那么SpringMVC的拦截器具体做些什么呢？我们暂且不关心，虽然这是很重要的细节，但细节毕竟是细节，我们先来理解更重要的东西。\n\nHandlerInterceptor，是SpringMVC的第二个扩展点的暴露，通过自定义拦截器，我们可以在一个请求被真正处理之前、请求被处理但还没输出到响应中、请求已经被输出到响应中之后这三个时间点去做任何我们想要做的事情。Struts2框架的成功，就是源于这种拦截器的设计，SpringMVC吸收了这种设计思想，并推陈出新，更合理的划分了三个不同的时间点，从而给web请求处理这个流程，提供了更大的扩展性。\n\n这个HandlerExecutionChain类中以Object引用所声明的handler对象，到底是个什么东东？它是怎么被调用的？\n\n## HandlerAdapter\n\n回答这些问题之前，先看SpringMVC中的又一个核心接口，HandlerAdapter：\n\n```\npackage org.springframework.web.servlet;\n\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\npublic interface HandlerAdapter {\n\n    boolean supports(Object handler); \n\n    ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;\n\n    long getLastModified(HttpServletRequest request, Object handler);\n\n}\n```\n\n在DispatcherServlet中，除了HandlerMapping实现类的列表，同样也注册了一个HandlerAdapter实现类组成的列表，有代码为证。\n\n```\n/** List of HandlerMappings used by this servlet */\n    private List<HandlerMapping> handlerMappings;\n\n    /** List of HandlerAdapters used by this servlet */\n    private List<HandlerAdapter> handlerAdapters;\n```\n\n接下来，我们再以DispatcherServlet类中另外一段代码来回答上述的问题：\n\n```\n/**\n     * Return the HandlerAdapter for this handler object.\n     * @param handler the handler object to find an adapter for\n     * @throws ServletException if no HandlerAdapter can be found for the handler. This is a fatal error.\n     */\n    protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {\n        for (HandlerAdapter ha : this.handlerAdapters) {\n            if (logger.isTraceEnabled()) {\n                logger.trace(\"Testing handler adapter [\" + ha + \"]\");\n            }\n            if (ha.supports(handler)) {\n                return ha;\n            }\n        }\n        throw new ServletException(\"No adapter for handler [\" + handler +\n                \"]: Does your handler implement a supported interface like Controller?\");\n    }\n```\n\n## 请求流程总结\n\n这段代码已经很明显了，HandlerExecutionChain中的handler对象会被作为参数传递进去，在DispatcherServlet类中注册的HandlerAdapter实现类列表会被遍历，然后返回第一个supports方法返回true的HandlerAdapter对象，用这个HandlerAdapter实现类中的handle方法处理handler对象，并返回ModelAndView这个包含了视图和数据的对象。HandlerAdapter就是SpringMVC提供的第三个扩展点，你可以提供自己的实现类来处理handler对象。\n\nModelAndView对象的代码就不贴了，它是SpringMVC中对视图和数据的一个聚合类。其中的视图，就是由SpringMVC的最后一个核心接口View所抽象：\n\n```\npackage org.springframework.web.servlet;\n\nimport java.util.Map;\n\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\npublic interface View {\n\n    String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + \".responseStatus\";\n\n    String PATH_VARIABLES = View.class.getName() + \".pathVariables\";\n\n    String getContentType();\n\n    void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception;\n\n}\n```\n\n所有的数据，最后会作为一个Map对象传递到View实现类中的render方法，调用这个render方法，就完成了视图到响应的渲染。这个View实现类，就是来自HandlerAdapter中的handle方法的返回结果。当然从ModelAndView到真正的View实现类有一个解析的过程，ModelAndView中可以有真正的视图对象，也可以只是有一个视图的名字，SpringMVC会负责将视图名称解析为真正的视图对象。\n\n至此，我们了解了一个典型的完整的web请求在SpringMVC中的处理过程和其中涉及到的核心类和接口。\n\n在一个典型的SpringMVC调用中，HandlerExecutionChain中封装handler对象就是用@Controller注解标识的类的一个实例，根据类级别和方法级别的@RequestMapping注解，由默认注册的DefaultAnnotationHandlerMapping（3.1.3中更新为RequestMappingHandlerMapping类，但是为了向后兼容，DefaultAnnotationHandlerMapping也可以使用）生成HandlerExecutionChain对象，再由AnnotationMethodHandlerAdapter（3.1.3中更新为RequestMappingHandlerAdapter类，但是为了向后兼容，AnnotationMethodHandlerAdapter也可以使用）来执行这个HandlerExecutionChain对象，生成最终的ModelAndView对象后，再由具体的View对象的render方法渲染视图。\n\n可以看到，作为一个表现层框架，SpringMVC没有像Struts2那样激进，并没有采用和Web容器完全解耦的设计思想，而是以原生的Servlet框架对象为依托，通过合理的抽象，制定了严谨的的处理流程。这样做的结果是，执行效率比Struts2要高，灵活性也上升了一个层次。\n"
  },
  {
    "path": "docs/Spring全家桶/SpringMVC源码分析/SpringMVC源码分析：消息转换器HttpMessageConverter与@ResponseBody注解.md",
    "content": "# 目录\n* [前言](#前言)\n* [现象](#现象)\n* [源码分析](#源码分析)\n* [实例讲解](#实例讲解)\n* [关于配置](#关于配置)\n* [总结](#总结)\n* [详解RequestBody和@ResponseBody注解](#详解requestbody和responsebody注解)\n* [参考资料](#参考资料)\n\n转自[SpringMVC关于json、xml自动转换的原理研究[附带源码分析]](https://www.cnblogs.com/fangjian0423/p/springMVC-xml-json-convert.html)\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章将同步到我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《Spring和SpringMVC源码分析》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从spring基础入手，一步步地学习spring基础和springmvc的框架知识，并上手进行项目实战，spring框架是每一个Java工程师必须要学习和理解的知识点，进一步来说，你还需要掌握spring甚至是springmvc的源码以及实现原理，才能更完整地了解整个spring技术体系，形成自己的知识框架。\n\n后续还会有springboot和springcloud的技术专题，陆续为大家带来，敬请期待。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供部分知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n## 前言\n\nSpringMVC是目前主流的Web MVC框架之一。\n\n如果有同学对它不熟悉，那么请参考它的入门blog：[http://www.cnblogs.com/fangjian0423/p/springMVC-introduction.html](http://www.cnblogs.com/fangjian0423/p/springMVC-introduction.html)\n\n## 现象\n\n本文使用的demo基于maven，是根据入门blog的例子继续写下去的。\n\n我们先来看一看对应的现象。 我们这里的配置文件 *-dispatcher.xml中的关键配置如下(其他常规的配置文件不在讲解，可参考本文一开始提到的入门blog)：\n\n(视图配置省略)\n\n```  \n<mvc:resources location=\"/static/\" mapping=\"/static/**\"/>  \n<mvc:annotation-driven/>  \n<context:component-scan base-package=\"org.format.demo.controller\"/>  \n```  \n\npom中需要有以下依赖(Spring依赖及其他依赖不显示)：\n\n```  \n<dependency>  \n  <groupId>org.codehaus.jackson</groupId>  jackson-core-asl  <version>1.9.13</version></dependency>  \n<dependency>  \n  <groupId>org.codehaus.jackson</groupId>  jackson-mapper-asl  <version>1.9.13</version></dependency>  \n  \n```  \n\n这个依赖是json序列化的依赖。\n\nok。我们在Controller中添加一个method：\n\n````\n@RequestMapping(\"/xmlOrJson\")  \n@ResponseBody public Map <string, object=\"\">xmlOrJson() {  \n    Map <string, object=\"\">map = new HashMap<string, object=\"\">();  \n    map.put(\"list\", employeeService.list()); return map;  \n}</string,></string,></string,> \n````\n\n直接访问地址：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/101449596675807.png)\n\n我们看到，短短几行配置。使用@ResponseBody注解之后，Controller返回的对象 自动被转换成对应的json数据，在这里不得不感叹SpringMVC的强大。\n\n我们好像也没看到具体的配置，唯一看到的就是*-dispatcher.xml中的一句配置：<mvc:annotation-driven>。其实就是这个配置，导致了java对象自动转换成json对象的现象。</mvc:annotation-driven>\n\n那么spring到底是如何实现java对象到json对象的自动转换的呢？ 为什么转换成了json数据，如果想转换成xml数据，那该怎么办？\n\n## 源码分析\n\n**本文使用的spring版本是4.0.2。**\n\n在讲解<mvc:annotation-driven>这个配置之前，我们先了解下Spring的消息转换机制。@ResponseBody这个注解就是使用消息转换机制，最终通过json的转换器转换成json数据的。</mvc:annotation-driven>\n\nHttpMessageConverter接口就是Spring提供的http消息转换接口。有关这方面的知识大家可以参考\"参考资料\"中的[第二条链接](http://my.oschina.net/lichhao/blog/172562)，里面讲的很清楚。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/101510002604230.png)\n\n下面开始分析<mvc:annotation-driven>这句配置:</mvc:annotation-driven>\n\n这句代码在spring中的解析类是：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/101606162131470.png)\n\n在AnnotationDrivenBeanDefinitionParser源码的152行parse方法中：\n\n分别实例化了RequestMappingHandlerMapping，ConfigurableWebBindingInitializer，RequestMappingHandlerAdapter等诸多类。\n\n其中**RequestMappingHandlerMapping和RequestMappingHandlerAdapter**这两个类比较重要。\n\nRequestMappingHandlerMapping处理请求映射的，处理@RequestMapping跟请求地址之间的关系。\n\nRequestMappingHandlerAdapter是请求处理的适配器，也就是请求之后处理具体逻辑的执行，关系到哪个类的哪个方法以及转换器等工作，这个类是我们讲的重点，其中它的属性messageConverters是本文要讲的重点。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/101611179016436.png)\n\n私有方法:getMessageConverters\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/101630232136603.png)\n\n从代码中我们可以，RequestMappingHandlerAdapter设置messageConverters的逻辑：\n\n1.如果<mvc:annotation-driven>节点有子节点message-converters，那么它的转换器属性messageConverters也由这些子节点组成。</mvc:annotation-driven>\n\nmessage-converters的子节点配置如下：\n\n```  \n<mvc:annotation-driven>  \n  <mvc:message-converters>    <bean class=\"org.example.MyHttpMessageConverter\"/>    <bean class=\"org.example.MyOtherHttpMessageConverter\"/>  </mvc:message-converters></mvc:annotation-driven>  \n```  \n\n2.message-converters子节点不存在或它的属性register-defaults为true的话，加入其他的转换器：ByteArrayHttpMessageConverter、StringHttpMessageConverter、ResourceHttpMessageConverter等。\n\n我们看到这么一段：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/101640298384297.png)\n\n这些boolean属性是哪里来的呢，它们是AnnotationDrivenBeanDefinitionParser的静态变量。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/101641297132356.png)\n\n其中ClassUtils中的isPresent方法如下：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/101643277139672.png)\n\n看到这里，读者应该明白了为什么本文一开始在pom文件中需要加入对应的jackson依赖，为了让json转换器jackson成为默认转换器之一。\n\n<mvc:annotation-driven>的作用读者也明白了。</mvc:annotation-driven>\n\n下面我们看如何通过消息转换器将java对象进行转换的。\n\nRequestMappingHandlerAdapter在进行handle的时候，会委托给HandlerMethod（具体由子类ServletInvocableHandlerMethod处理）的invokeAndHandle方法进行处理，这个方法又转接给HandlerMethodReturnValueHandlerComposite处理。\n\nHandlerMethodReturnValueHandlerComposite维护了一个HandlerMethodReturnValueHandler列表。**HandlerMethodReturnValueHandler是一个对返回值进行处理的策略接口，这个接口非常重要。关于这个接口的细节，请参考楼主的另外一篇博客：**[http://www.cnblogs.com/fangjian0423/p/springMVC-request-param-analysis.html](http://www.cnblogs.com/fangjian0423/p/springMVC-request-param-analysis.html)。然后找到对应的HandlerMethodReturnValueHandler对结果值进行处理。\n\n最终找到RequestResponseBodyMethodProcessor这个Handler（由于使用了@ResponseBody注解）。\n\nRequestResponseBodyMethodProcessor的supportsReturnType方法：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/101803027605809.png)\n\n然后使用handleReturnValue方法进行处理：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/101803105889900.png)\n\n我们看到，这里使用了转换器。　　  \n具体的转换方法：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/101809037135949.png)\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/102031439173571.png)\n\n至于为何是请求头部的**Accept**数据，读者可以进去debug这个**getAcceptableMediaTypes**方法看看。 我就不罗嗦了～～～\n\nok。至此，我们走遍了所有的流程。\n\n现在，回过头来看。为什么一开始的demo输出了json数据？\n\n我们来分析吧。\n\n由于我们只配置了<mvc:annotation-driven>，因此使用spring默认的那些转换器。</mvc:annotation-driven>\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/101816581047144.png)\n\n很明显，我们看到了2个xml和1个json转换器。**要看能不能转换，得看HttpMessageConverter接口的public boolean canWrite(Class<?> clazz, MediaType mediaType)方法是否返回true来决定的。**\n\n我们先分析SourceHttpMessageConverter：\n\n它的canWrite方法被父类AbstractHttpMessageConverter重写了。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/101830573234896.png)\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/101832284176592.png)\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/101832352929525.png)\n\n发现SUPPORTED_CLASSES中没有Map类(本文demo返回的是Map类)，因此不支持。\n\n下面看Jaxb2RootElementHttpMessageConverter：\n\n这个类直接重写了canWrite方法。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/101838053851073.png)\n\n需要有XmlRootElement注解。 很明显，Map类当然没有。\n\n最终MappingJackson2HttpMessageConverter匹配，进行json转换。（为何匹配，请读者自行查看源码）\n\n## 实例讲解\n\n我们分析了转换器的转换过程之后，下面就通过实例来验证我们的结论吧。\n\n首先，我们先把xml转换器实现。\n\n之前已经分析，默认的转换器中是支持xml的。下面我们加上注解试试吧。\n\n由于Map是jdk源码中的部分，因此我们用Employee来做demo。\n\n因此，Controller加上一个方法：\n````\n@RequestMapping(\"/xmlOrJsonSimple\")  \n@ResponseBody public Employee xmlOrJsonSimple() { return employeeService.getById(1);  \n} \n````\n实体中加上@XmlRootElement注解\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/101903141989122.png)\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/101904598389030.png)\n\n我们发现，解析成了xml。\n\n这里为什么解析成xml，而不解析成json呢？\n\n之前分析过，消息转换器是根据class和mediaType决定的。\n\n我们使用firebug看到：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/102222464019898.png)\n\n我们发现Accept有xml，没有json。因此解析成xml了。\n\n我们再来验证，同一地址，HTTP头部不同Accept。看是否正确。\n\n````\n$.ajax({  \n    url: \"${request.contextPath}/employee/xmlOrJsonSimple\",  \n    success: function(res) {  \n        console.log(res);  \n    },  \n    headers: { \"Accept\": \"application/xml\" }  \n}); \n\n$.ajax({  \n    url: \"${request.contextPath}/employee/xmlOrJsonSimple\",  \n    success: function(res) {  \n        console.log(res);  \n    },  \n    headers: { \"Accept\": \"application/json\" }  \n}); \n````\n\n验证成功。\n\n## 关于配置\n\n如果不想使用<mvc:annotation-driven>中默认的RequestMappingHandlerAdapter的话，我们可以在重新定义这个bean，spring会覆盖掉默认的RequestMappingHandlerAdapter。</mvc:annotation-driven>\n\n为何会覆盖，请参考楼主的另外一篇博客：[http://www.cnblogs.com/fangjian0423/p/spring-Ordered-interface.html](http://www.cnblogs.com/fangjian0423/p/spring-Ordered-interface.html)\n\n    ` <bean><property name=\"messageConverters\"><list><bean><bean><bean></bean></bean></bean></list></property></bean> `  \n\n或者如果只想换messageConverters的话。\n\n```  \n<mvc:annotation-driven>  \n  <mvc:message-converters>    <bean class=\"org.example.MyHttpMessageConverter\"/>    <bean class=\"org.example.MyOtherHttpMessageConverter\"/>  </mvc:message-converters></mvc:annotation-driven>  \n  \n```  \n\n如果还想用其他converters的话。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/102311480731629.png)\n\n以上是spring-mvc jar包中的converters。\n\n这里我们使用转换xml的MarshallingHttpMessageConverter。\n\n这个converter里面使用了marshaller进行转换\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/102313161827280.png)\n\n我们这里使用XStreamMarshaller。　　  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/102319292603758.png)\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/102319412294581.png)\n\njson没有转换器，返回406.\n\n至于xml格式的问题，大家自行解决吧。 这里用的是XStream～。\n\n使用这种方式，pom别忘记了加入xstream的依赖：\n\n```  \n<dependency>  \n  <groupId>com.thoughtworks.xstream</groupId>  xstream  <version>1.4.7</version></dependency>  \n```  \n\n## 总结\n\n写了这么多，可能读者觉得有点罗嗦。 毕竟这也是自己的一些心得，希望都能说出来与读者共享。\n\n刚接触SpringMVC的时候，发现这种自动转换机制很牛逼，但是一直没有研究它的原理，目前，算是了了一个小小心愿吧，SpringMVC还有很多内容，以后自己研究其他内容的时候还会与大家一起共享的。\n\n文章难免会出现一些错误，希望读者们能指明出来。\n\n## 详解RequestBody和@ResponseBody注解\n\n概述 在SpringMVC中，可以使用@RequestBody和@ResponseBody两个注解，分别完成请求报文到对象和对象到响应报文的转换，底层这种灵活的消息转换机制，就是Spring3.x中新引入的HttpMessageConverter即消息转换器机制。\n\nHttp请求的抽象 还是回到请求-响应，也就是解析请求体，然后返回响应报文这个最基本的Http请求过程中来。我们知道，在servlet标准中，可以用javax.servlet.ServletRequest接口中的以下方法：\n\n```  \npublic ServletInputStream getInputStream() throws IOException;   \n```  \n\n来得到一个ServletInputStream。这个ServletInputStream中，可以读取到一个原始请求报文的所有内容。同样的，在javax.servlet.ServletResponse接口中，可以用以下方法：\n\n```  \npublic ServletOutputStream getOutputStream() throws IOException;  \n  \n```  \n\n来得到一个ServletOutputStream，这个ServletOutputSteam，继承自java中的OutputStream，可以让你输出Http的响应报文内容。\n\n让我们尝试着像SpringMVC的设计者一样来思考一下。我们知道，Http请求和响应报文本质上都是一串字符串，当请求报文来到java世界，它会被封装成为一个ServletInputStream的输入流，供我们读取报文。响应报文则是通过一个ServletOutputStream的输出流，来输出响应报文。\n\n我们从流中，只能读取到原始的字符串报文，同样，我们往输出流中，也只能写原始的字符。而在java世界中，处理业务逻辑，都是以一个个有业务意义的**对象**为处理维度的，那么在报文到达SpringMVC和从SpringMVC出去，都存在一个字符串到java对象的阻抗问题。这一过程，不可能由开发者手工转换。我们知道，在Struts2中，采用了OGNL来应对这个问题，而在SpringMVC中，它是HttpMessageConverter机制。我们先来看两个接口。\n\nHttpInputMessage 这个类是SpringMVC内部对一次Http请求报文的抽象，在HttpMessageConverter的read()方法中，有一个HttpInputMessage的形参，它正是SpringMVC的消息转换器所作用的受体“请求消息”的内部抽象，消息转换器从“请求消息”中按照规则提取消息，转换为方法形参中声明的对象。\n\n```  \npackage org.springframework.http;  \n  \nimport java.io.IOException;  \nimport java.io.InputStream;  \n  \npublic interface HttpInputMessage extends HttpMessage {  \n  \n    InputStream getBody() throws IOException;  \n}  \n  \n```  \n\nHttpOutputMessage 这个类是SpringMVC内部对一次Http响应报文的抽象，在HttpMessageConverter的write()方法中，有一个HttpOutputMessage的形参，它正是SpringMVC的消息转换器所作用的受体“响应消息”的内部抽象，消息转换器将“响应消息”按照一定的规则写到响应报文中。\n\n```  \npackage org.springframework.http;  \n  \nimport java.io.IOException;  \nimport java.io.OutputStream;  \n  \npublic interface HttpOutputMessage extends HttpMessage {  \n  \n    OutputStream getBody() throws IOException;  \n}  \n  \n```  \n\nHttpMessageConverter 对消息转换器最高层次的接口抽象，描述了一个消息转换器的一般特征，我们可以从这个接口中定义的方法，来领悟Spring3.x的设计者对这一机制的思考过程。\n\n```  \npackage org.springframework.http.converter;  \n  \nimport java.io.IOException;  \nimport java.util.List;  \n  \nimport org.springframework.http.HttpInputMessage;  \nimport org.springframework.http.HttpOutputMessage;  \nimport org.springframework.http.MediaType;  \n  \npublic interface HttpMessageConverter<T> {  \n  \n    boolean canRead(Class<?> clazz, MediaType mediaType);  \n    boolean canWrite(Class<?> clazz, MediaType mediaType);  \n    List<MediaType> getSupportedMediaTypes();  \n    T read(Class<? extends T> clazz, HttpInputMessage inputMessage)            throws IOException, HttpMessageNotReadableException;  \n    void write(T t, MediaType contentType, HttpOutputMessage outputMessage)            throws IOException, HttpMessageNotWritableException;  \n}  \n  \n```  \n\nHttpMessageConverter接口的定义出现了成对的canRead()，read()和canWrite()，write()方法，MediaType是对请求的Media Type属性的封装。举个例子，当我们声明了下面这个处理方法。\n\n```  \n@RequestMapping(value=\"/string\", method=RequestMethod.POST)  \npublic @ResponseBody String readString(@RequestBody String string) {  \n    return \"Read string '\" + string + \"'\";}  \n  \n```  \n\n在SpringMVC进入readString方法前，会根据@RequestBody注解选择适当的HttpMessageConverter实现类来将请求参数解析到string变量中，具体来说是使用了StringHttpMessageConverter类，它的canRead()方法返回true，然后它的read()方法会从请求中读出请求参数，绑定到readString()方法的string变量中。\n\n当SpringMVC执行readString方法后，由于返回值标识了@ResponseBody，SpringMVC将使用StringHttpMessageConverter的write()方法，将结果作为String值写入响应报文，当然，此时canWrite()方法返回true。\n\n我们可以用下面的图，简单描述一下这个过程。\n\n![消息转换图](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825151641382-1716038917.png)\n\nRequestResponseBodyMethodProcessor 将上述过程集中描述的一个类是org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor，这个类同时实现了HandlerMethodArgumentResolver和HandlerMethodReturnValueHandler两个接口。前者是将请求报文绑定到处理方法形参的策略接口，后者则是对处理方法返回值进行处理的策略接口。两个接口的源码如下：\n\n```  \npackage org.springframework.web.method.support;  \n  \nimport org.springframework.core.MethodParameter;  \nimport org.springframework.web.bind.WebDataBinder;  \nimport org.springframework.web.bind.support.WebDataBinderFactory;  \nimport org.springframework.web.context.request.NativeWebRequest;  \n  \npublic interface HandlerMethodArgumentResolver {  \n  \n    boolean supportsParameter(MethodParameter parameter);  \n    Object resolveArgument(MethodParameter parameter,                           ModelAndViewContainer mavContainer,                           NativeWebRequest webRequest,                           WebDataBinderFactory binderFactory) throws Exception;  \n}  \n  \npackage org.springframework.web.method.support;  \n  \nimport org.springframework.core.MethodParameter;  \nimport org.springframework.web.context.request.NativeWebRequest;  \n  \npublic interface HandlerMethodReturnValueHandler {  \n  \n    boolean supportsReturnType(MethodParameter returnType);  \n    void handleReturnValue(Object returnValue,                           MethodParameter returnType,                           ModelAndViewContainer mavContainer,                           NativeWebRequest webRequest) throws Exception;  \n}  \n  \n```  \n\nRequestResponseBodyMethodProcessor这个类，同时充当了方法参数解析和返回值处理两种角色。我们从它的源码中，可以找到上面两个接口的方法实现。\n\n对HandlerMethodArgumentResolver接口的实现：\n\n```  \npublic boolean supportsParameter(MethodParameter parameter) {  \n    return parameter.hasParameterAnnotation(RequestBody.class);}  \n  \npublic Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,  \n        NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {  \n    Object argument = readWithMessageConverters(webRequest, parameter, parameter.getGenericParameterType());  \n    String name = Conventions.getVariableNameForParameter(parameter);    WebDataBinder binder = binderFactory.createBinder(webRequest, argument, name);  \n    if (argument != null) {        validate(binder, parameter);    }  \n    mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());  \n    return argument;}  \n  \n```  \n\n对HandlerMethodReturnValueHandler接口的实现\n\n```  \npublic boolean supportsReturnType(MethodParameter returnType) {  \n    return returnType.getMethodAnnotation(ResponseBody.class) != null;}  \n  \n    public void handleReturnValue(Object returnValue, MethodParameter returnType,        ModelAndViewContainer mavContainer, NativeWebRequest webRequest)        throws IOException, HttpMediaTypeNotAcceptableException {  \n    mavContainer.setRequestHandled(true);    if (returnValue != null) {        writeWithMessageConverters(returnValue, returnType, webRequest);    }}  \n  \n```  \n\n看完上面的代码，整个HttpMessageConverter消息转换的脉络已经非常清晰。因为两个接口的实现，分别是以是否有@RequestBody和@ResponseBody为条件，然后分别调用HttpMessageConverter来进行消息的读写。\n\n如果你想问，怎么样跟踪到RequestResponseBodyMethodProcessor中，请你按照前面几篇博文的思路，然后到这里[spring-mvc-showcase](https://github.com/spring-projects/spring-mvc-showcase)下载源码回来，对其中HttpMessageConverter相关的例子进行debug，只要你肯下功夫，相信你一定会有属于自己的收获的。\n\n思考 张小龙在谈微信的本质时候说：“微信只是个平台，消息在其中流转”。在我们对SpringMVC源码分析的过程中，我们可以从HttpMessageConverter机制中领悟到类似的道理。在SpringMVC的设计者眼中，一次请求报文和一次响应报文，分别被抽象为一个请求消息HttpInputMessage和一个响应消息HttpOutputMessage。\n\n处理请求时，由合适的消息转换器将请求报文绑定为方法中的形参对象，在这里，同一个对象就有可能出现多种不同的消息形式，比如json和xml。同样，当响应请求时，方法的返回值也同样可能被返回为不同的消息形式，比如json和xml。\n\n在SpringMVC中，针对不同的消息形式，我们有不同的HttpMessageConverter实现类来处理各种消息形式。但是，只要这些消息所蕴含的“有效信息”是一致的，那么各种不同的消息转换器，都会生成同样的转换结果。至于各种消息间解析细节的不同，就被屏蔽在不同的HttpMessageConverter实现类中了。\n\n\n## 参考资料\n\n[http://my.oschina.net/HeliosFly/blog/205343](http://my.oschina.net/HeliosFly/blog/205343)\n\n[http://my.oschina.net/lichhao/blog/172562](http://my.oschina.net/lichhao/blog/172562)\n\n[http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html](http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html)"
  },
  {
    "path": "docs/Spring全家桶/SpringMVC源码分析/SpringMVC的Demo与@EnableWebMvc注解.md",
    "content": "### 1\\. demo ׼\n\nΪ˸õط springmvc Դ룬Ҫ׼һ springmvc  demo demo Ƿ `spring-learn` ģ顣\n\n#### 1\\.  tomcat \n\n tomcat 8 ֮tomcat ṩ˶аҪʱֱͿˣӦ gradle :\n\n```\noptional(\"org.apache.tomcat.embed:tomcat-embed-core\")\n\n```\n\n spring Ŀ `build.gradle` УѾ `tomcat-embed-core-9.0.29.jar` `spring-learn` ģʱָ汾\n\n#### 2\\. ׼\n\n```\npackage org.springframework.learn.mvc.demo01;\n\nimport org.springframework.context.annotation.ComponentScan;\nimport org.springframework.stereotype.Component;\nimport org.springframework.web.servlet.config.annotation.EnableWebMvc;\n\n@Component\n@ComponentScan(\"org.springframework.learn.mvc.demo01\")\n@EnableWebMvc\npublic class MvcConfig {\n\n}\n\n```\n\nΪ `MvcConfig`ָĿİɨ·Լͨ `@EnableWebMvc`  mvc ܡ\n\n#### 3\\. ʵ `WebApplicationInitializer`\n\n```\npackage org.springframework.learn.mvc.demo01;\n\nimport org.springframework.web.WebApplicationInitializer;\nimport org.springframework.web.context.support.AnnotationConfigWebApplicationContext;\nimport org.springframework.web.servlet.DispatcherServlet;\n\nimport javax.servlet.ServletContext;\nimport javax.servlet.ServletRegistration;\n\npublic class MyWebApplicationInitializer implements WebApplicationInitializer {\n\n    @Override\n    public void onStartup(ServletContext servletContext) {\n        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();\n        context.register(MvcConfig.class);\n\n        DispatcherServlet servlet = new DispatcherServlet(context);\n        ServletRegistration.Dynamic registration = servletContext.addServlet(\"app\", servlet);\n        registration.setLoadOnStartup(1);\n        registration.addMapping(\"/*\");\n    }\n}\n\n```\n\nspring ṩһӿ `WebApplicationInitializer`ʵָýӿʱ `onStartup(...)` д spring  `applicationContext`Ȼ servelet ע `DispatcherServlet`\n\n#### 4\\. ׼ controller\n\n```\npackage org.springframework.learn.mvc.demo01;\n\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\n@RestController\n@RequestMapping(\"/test\")\npublic class TestController {\n\n    @RequestMapping(\"/hello\")\n    public String hello() {\n         System.out.println(\"hello!!!\");\n         return \"hello world!\";\n    }\n}\n\n```\n\n׼һ򵥵 controllerһַ \"hello world\".\n\n#### 5\\. \n\nˣ\n\n```\npackage org.springframework.learn.mvc.demo01;\n\nimport org.apache.catalina.Context;\nimport org.apache.catalina.LifecycleListener;\nimport org.apache.catalina.connector.Connector;\nimport org.apache.catalina.startup.Tomcat;\n\npublic class MvcDemo01Main {\n\n    public static void main(String[] args) throws Exception {\n        Tomcat tomcat = new Tomcat();\n\n        Connector connector = new Connector();\n        connector.setPort(8080);\n        connector.setURIEncoding(\"UTF-8\");\n        tomcat.getService().addConnector(connector);\n\n        Context context = tomcat.addContext(\"\", System.getProperty(\"java.io.tmpdir\"));\n        LifecycleListener lifecycleListener = (LifecycleListener) \n                Class.forName(tomcat.getHost().getConfigClass())\n                .getDeclaredConstructor().newInstance();\n        context.addLifecycleListener(lifecycleListener);\n        tomcat.start();\n        tomcat.getServer().await();\n    }\n}\n\n```\n\n `main` УҪ tomcat ߼\n\nУ£\n\n̨\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-8d13bb650364488f8ce5e593eff00448ee1.png)\n\nҳ淵أ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-6a8407a1945dccd6e4ef217a383d4309ec5.png)\n\nԿһ򵥵 springmvc Ŀʹˡ\n\n### 2. `servlet 3.0` 淶\n\n¹ϵ springmvc Ŀһ⼸ xml ļ\n\n*   `web.xml`servlet ļ web ʱĲԼ `servlet`/`listener`/`filter`;\n*   `spring.xml`spring ļҪ spring bean.\n*   `spring-mvc.xml`springmvc ļ mvc ص beanļϴص beanͼ beancontroller ·ȡ\n\nĿʱȼ `web.xml` `web.xml` м spring ã spring \n\n demo УǷֲ ûЩã `web.xml` ļûУô web Ŀôأ\n\n `servlet`  `3.0` ֮ṩһ spi 淶spring ʵ£\n\n1.   `spring-web` ģ `/src/main/resources/META-INF/services/` ļ£ļ `javax.servlet.ServletContainerInitializer`\n\n```\norg.springframework.web.SpringServletContainerInitializer\n\n```\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-92853ebc9c4388d325244b81557ecf80ddd.png)\n\n1.  `org.springframework.web.SpringServletContainerInitializer` ʵ servlet 淶\n\n```\n// @HandlesTypes עservlet淶ʾ webAppInitializerClass Ϊ WebApplicationInitializer.class\n@HandlesTypes(WebApplicationInitializer.class)\npublic class SpringServletContainerInitializer implements ServletContainerInitializer {\n\n    /*\n     * д ServletContainerInitializer  onStartup \n     * Ҫʵ spring ṩ WebApplicationInitializer.classȻִ onStartup \n     *\n     * Set<Class<?>> webAppInitializerClasses еΪ WebApplicationInitializer.class\n     *  @HandlesTypes עָ\n     */\n    @Override\n    public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)\n            throws ServletException {\n\n        List<WebApplicationInitializer> initializers = new LinkedList<>();\n\n        if (webAppInitializerClasses != null) {\n            for (Class<?> waiClass : webAppInitializerClasses) {\n                if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&\n                        WebApplicationInitializer.class.isAssignableFrom(waiClass)) {\n                    try {\n                       // ʹ÷ʵ WebApplicationInitializer ʵ࣬ӵ initializers \n                        initializers.add((WebApplicationInitializer)\n                                ReflectionUtils.accessibleConstructor(waiClass).newInstance());\n                    }\n                    catch (Throwable ex) {\n                        ...\n                    }\n                }\n            }\n        }\n\n        servletContext.log(initializers.size() + \" ...\");\n        // ʵOrderdӿڣע @Order ע⣬ʵ PriorityOrderd ӿ\n        AnnotationAwareOrderComparator.sort(initializers);\n        for (WebApplicationInitializer initializer : initializers) {\n           //  WebApplicationInitializer ʵonStartup\n           initializer.onStartup(servletContext);\n        }\n    }\n\n}\n\n```\n\n1.  `WebApplicationInitializer` ʵ  demo ж `WebApplicationInitializer` ʵ֣\n\n```\npackage org.springframework.learn.mvc.demo01;\n\nimport org.springframework.web.WebApplicationInitializer;\nimport org.springframework.web.context.support.AnnotationConfigWebApplicationContext;\nimport org.springframework.web.servlet.DispatcherServlet;\n\nimport javax.servlet.ServletContext;\nimport javax.servlet.ServletRegistration;\n\npublic class MyWebApplicationInitializer implements WebApplicationInitializer {\n\n    /*\n     *  spring Ŀ\n     */\n    @Override\n    public void onStartup(ServletContext servletContext) {\n        //  spring  ApplicationContext\n        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();\n        context.register(MvcConfig.class);\n\n        //  DispatcherServlet  servlet \n        DispatcherServlet servlet = new DispatcherServlet(context);\n        ServletRegistration.Dynamic registration = servletContext.addServlet(\"app\", servlet);\n        registration.setLoadOnStartup(1);\n        registration.addMapping(\"/*\");\n    }\n}\n\n```\n\nִй£\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-0874fa7ef39ca9c405cdf55d99ca891ebf2.png)\n\nɴˣspring ˡ\n\n### 3\\. @EnableWebMvc \n\n demo Уͨ `@EnableWebMvc`  mvc ܣôעʲôأǽ `EnableWebMvc` ࣺ\n\n```\n@Retention(RetentionPolicy.RUNTIME)\n@Target(ElementType.TYPE)\n@Documented\n@Import(DelegatingWebMvcConfiguration.class)\npublic @interface EnableWebMvc {\n}\n\n```\n\nԿעͨ `@Import` ע `DelegatingWebMvcConfiguration.class` `DelegatingWebMvcConfiguration`:\n\n```\n@Configuration(proxyBeanMethods = false)\npublic class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {\n    ...\n\n}\n\n```\n\n `@Configuration` ע⣬Ǹ̳࣬е `WebMvcConfigurationSupport``WebMvcConfigurationSupport` Ϊ \"mvc ֧\"ô mvc صõġ\n\nΪ˸õطȽܼࣺ\n\n1. `DelegatingWebMvcConfiguration` `@EnableWebMvc` ࣬ `WebMvcConfigurationSupport` ࣬д `WebMvcConfigurationSupport` ṩ÷\n\n   ```\n   /*\n    * @ConfigurationǸ\n    * extends WebMvcConfigurationSupport̳WebMvcConfigurationSupport\n    */\n   @Configuration(proxyBeanMethods = false)\n   public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {\n   \n       // WebMvcConfigurerComposite  WebMvcConfigurer ϣᵽ\n       private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();\n   \n       /**\n        * configurers\n        * @Autowiredע⣬ʾspringеWebMvcConfigurer bean Ϊ\n        * configurersֵ Ȼø÷\n        */\n       @Autowired(required = false)\n       public void setConfigurers(List<WebMvcConfigurer> configurers) {\n           if (!CollectionUtils.isEmpty(configurers)) {\n               this.configurers.addWebMvcConfigurers(configurers);\n           }\n       }\n   \n       /**\n        * PathMatch\n        */\n       @Override\n       protected void configurePathMatch(PathMatchConfigurer configurer) {\n           //  WebMvcConfigurerComposite ķ\n           this.configurers.configurePathMatch(configurer);\n       }\n   \n       // ÷Ҳǵ WebMvcConfigurerComposite Ӧķõ\n       ...\n   \n   }\n   \n   ```\n\n2. `WebMvcConfigurerComposite``WebMvcConfigurer` ϣ\n\n   ```\n   /**\n    * ʵ WebMvcConfigurer\n    */\n   class WebMvcConfigurerComposite implements WebMvcConfigurer {\n   \n       // delegatesΪ WebMvcConfigurer ļ\n       private final List<WebMvcConfigurer> delegates = new ArrayList<>();\n   \n       /*\n        * DelegatingWebMvcConfiguration#setConfigurers\n        * ǰѴconfigurersӵdelegates(ҲWebMvcConfigurer)\n        */\n       public void addWebMvcConfigurers(List<WebMvcConfigurer> configurers) {\n           if (!CollectionUtils.isEmpty(configurers)) {\n               this.delegates.addAll(configurers);\n           }\n       }\n   \n       /**\n        * ʱdelegates(ҲWebMvcConfigurer)õ\n        * еÿһWebMvcConfigurer\n        */\n       @Override\n       public void configurePathMatch(PathMatchConfigurer configurer) {\n           for (WebMvcConfigurer delegate : this.delegates) {\n               delegate.configurePathMatch(configurer);\n           }\n       }\n   \n       // ƣʡ\n       ...\n   }\n   \n   ```\n\n3. `WebMvcConfigurer`springmvc ýӿڣṩ˷ǳ\n\n   ```\n   public interface WebMvcConfigurer {\n   \n       default void configurePathMatch(PathMatchConfigurer configurer) {\n       }\n   \n       default void configureContentNegotiation(ContentNegotiationConfigurer configurer) {\n       }\n   \n       default void configureAsyncSupport(AsyncSupportConfigurer configurer) {\n       }\n   \n       ...\n   }\n   \n   ```\n\n4. `WebMvcConfigurationSupport`springmvc ֧\n\n   ```\n   /**\n    * ʵawareӿ\n    */\n   public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {\n   \n       //=================  XxxAware ӿڵķ =================\n       @Override\n       public void setApplicationContext(@Nullable ApplicationContext applicationContext) {\n           this.applicationContext = applicationContext;\n       }\n   \n       @Override\n       public void setServletContext(@Nullable ServletContext servletContext) {\n           this.servletContext = servletContext;\n       }\n   \n       //================= @Bean spring bean =================\n       @Bean\n       public RequestMappingHandlerMapping requestMappingHandlerMapping(...) {\n           RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();\n           mapping.setOrder(0);\n           // getInterceptors(...) ȡ interceptors¿\n           mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));\n           mapping.setContentNegotiationManager(contentNegotiationManager);\n           // getCorsConfigurations(...) ȡCorsã¿\n           mapping.setCorsConfigurations(getCorsConfigurations());\n           // getPathMatchConfigurer(...) ȡPathMatchã¿\n           PathMatchConfigurer configurer = getPathMatchConfigurer();\n   \n           ...\n   \n           return mapping;\n       }\n       ...\n       //================= get xxx ÷springṩĬãԶ =======\n       // ȡ interceptors\n       protected final Object[] getInterceptors(\n               FormattingConversionService mvcConversionService,\n               ResourceUrlProvider mvcResourceUrlProvider) {\n           if (this.interceptors == null) {\n               InterceptorRegistry registry = new InterceptorRegistry();\n               // ÷ interceptor¿\n               addInterceptors(registry);\n               registry.addInterceptor(new ConversionServiceExposingInterceptor(mvcConversionService));\n               registry.addInterceptor(new ResourceUrlProviderExposingInterceptor(mvcResourceUrlProvider));\n               this.interceptors = registry.getInterceptors();\n           }\n           return this.interceptors.toArray();\n       }\n   \n       // ȡCors\n       protected final Map<String, CorsConfiguration> getCorsConfigurations() {\n           if (this.corsConfigurations == null) {\n               CorsRegistry registry = new CorsRegistry();\n               // ÷ CorsMapping¿\n               addCorsMappings(registry);\n               this.corsConfigurations = registry.getCorsConfigurations();\n           }\n           return this.corsConfigurations;\n       }\n   \n       // ȡPathMatch\n       protected PathMatchConfigurer getPathMatchConfigurer() {\n           if (this.pathMatchConfigurer == null) {\n               this.pathMatchConfigurer = new PathMatchConfigurer();\n               configurePathMatch(this.pathMatchConfigurer);\n           }\n           return this.pathMatchConfigurer;\n       }\n   \n       ...\n   \n       //================= ÷ʵ =================\n       // Զ Interceptorʵ\n       protected void addInterceptors(InterceptorRegistry registry) {\n       }\n   \n       // Զ CorsMappingʵ\n       protected void addCorsMappings(CorsRegistry registry) {\n       }\n   \n       // Զ PathMatch\n       protected void configurePathMatch(PathMatchConfigurer configurer) {\n       }\n       ...\n   \n   }\n   \n   ```\n\n   ԿķΪࣺ\n\n    *    `XxxAware` ķ`XxxAware` ӿ spring ṩbean ʼʱص\n    *    `@Bean` עķ spring  bean bean ʱ `getXxx` \n    *   `getXxx` ȡ÷ڸ÷У spring ṩĬãԼ `addXxx/configureXxx` Զã\n    *   `addXxx/configureXxx` ʵ֣ springmvc Զá\n\nܽ 4 Ĺϵ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-5e25e6e7303044e7282b3472492c04cf656.png)\n\nĸĹϵ`@EnableWebMvc` ִ̾һĿȻˣܽ£\n\n1.  `@EnableWebMvc`  spring ` DelegatingWebMvcConfiguration`\n\n2.  `DelegatingWebMvcConfiguration` а `@Autowired` עķ `setConfigurers(List<WebMvcConfigurer>)` spring bean лִУΪȡ `WebMvcConfigurer`  bean õ` DelegatingWebMvcConfiguration` У\n\n3.  `DelegatingWebMvcConfiguration` ̳ `WebMvcConfigurationSupport` spring bean лᴦ `WebMvcConfigurationSupport`  `@Bean` עķַȽ϶࣬ `requestMappingHandlerMapping()``mvcPathMatcher` ȣЩ smvc Ĺ\n\n4.  ڴ `WebMvcConfigurationSupport`  `@Bean` עķʱ `getXxx()` ȡãð spring ṩĬüԶã`getXxx()`  `WebMvcConfigurationSupport` ṩ\n\n5.  ڵ `WebMvcConfigurationSupport#getXxx()` ȡԶʱ `addXxx()/configureXxx()`÷ `WebMvcConfigurationSupport` ǿշ߼ (Ҳ` DelegatingWebMvcConfiguration`) ṩյ÷ʽ**ִе 2 ȡ `WebMvcConfigurer`  `addXxx()/configureXxx()`**\n\nͼʾ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-4057f913076f01ba0b507b4654e5031391c.png)\n\n springmvc ܣԶʱǿô:\n\n1. ʽ 1ʹ @EnableWebMvc ע mvc ܣʵ WebMvcConfigurerԶ\n\n   ```\n   // ʹ@EnableWebMvcעmvc\n   @Component\n   @EnableWebMvc\n   public class MvcConfig {\n       ...\n   }\n   \n   // ʵ WebMvcConfigurerԶ\n   @Component\n   public class MyWebMvcConfigurer implements WebMvcConfigurer {\n   \n       // дWebMvcConfigurerԶ\n   }\n   \n   ```\n\n2. ʽ 2ʵ `WebMvcConfigurationSupport` ࣬де÷\n\n   ```\n   @Component\n   public class MyWebMvcConfigurationSupport extends WebMvcConfigurationSupport {\n       // д÷Զ\n   \n   }\n   \n   ```\n\n   ַʽʵ `WebMvcConfigurer` ԶþͲЧˣԶֻ `WebMvcConfigurationSupport` á\n\nspringmvc ṩЩأ `WebMvcConfigurer` ṩķ\n\n*   `configurePathMatch`·\n*   `configureContentNegotiation`Э\n*   `configureAsyncSupport`\n*   `configureDefaultServletHandling`ĬϾ̬Դ\n*   `addFormatters`עԶת\n*   `addInterceptors`\n*   `addResourceHandlers`Դ\n*   `addCorsMappings`CORS \n*   `addViewControllers`ͼת\n*   `configureViewResolvers`ͼ\n*   `addArgumentResolvers`Զ巽\n*   `addReturnValueHandlers`Զ巵ؽ\n*   `configureMessageConverters`ϢתػḲĬע `HttpMessageConverter`\n*   `extendMessageConverters`ϢתһԶ `HttpMessageConverter`.\n*   `configureHandlerExceptionResolvers`쳣ת\n*   `extendHandlerExceptionResolvers`쳣ת\n*   `getValidator`:\n*   `getMessageCodesResolver`\n\nҪֻҪдط ɡ\n\n `WebMvcConfigurationSupport` Щ Bean `@Bean` עķ£\n\n*   `public RequestMappingHandlerMapping requestMappingHandlerMapping(...)`\n*   `public PathMatcher mvcPathMatcher()`\n*   `public UrlPathHelper mvcUrlPathHelper()`\n*   `public ContentNegotiationManager mvcContentNegotiationManager()`\n*   `public HandlerMapping viewControllerHandlerMapping(...)`\n*   `public BeanNameUrlHandlerMapping beanNameHandlerMapping(...)`\n*   `public RouterFunctionMapping routerFunctionMapping(...)`\n*   `public HandlerMapping resourceHandlerMapping(...)`\n*   `ResourceUrlProvider mvcResourceUrlProvider()`\n*   `public HandlerMapping defaultServletHandlerMapping()`\n*   `public RequestMappingHandlerAdapter requestMappingHandlerAdapter(...)`\n*   `public HandlerFunctionAdapter handlerFunctionAdapter()`\n*   `public FormattingConversionService mvcConversionService()`\n*   `public Validator mvcValidator()`\n*   `public CompositeUriComponentsContributor mvcUriComponentsContributor(...)`\n*   `public HttpRequestHandlerAdapter httpRequestHandlerAdapter()`\n*   `public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter()`\n*   `public HandlerExceptionResolver handlerExceptionResolver(...)`\n*   `public ViewResolver mvcViewResolver(...)`\n*   `HandlerMappingIntrospector mvcHandlerMappingIntrospector()`\n\nЩ springmvc õһЩľݾͲչˡ\n\n### 4\\. ܽ\n\nݱȽӣṩһ springmvc  demoȻ demo  `0 xml` ԭ (Ҳ `servlet 3.0` 淶Ž `@EnableWebMvc` Ĺܣؽ `WebMvcConfigurationSupport` á\n\n* * *\n\n_ԭӣ[https://my.oschina.net/funcy/blog/4696657](https://my.oschina.net/funcy/blog/4696657) ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע_"
  },
  {
    "path": "docs/Spring全家桶/SpringMVC源码分析/Spring容器启动Tomcat.md",
    "content": "'' [spring mvc ֮ springmvc demo  @EnableWebMvc ע](https://my.oschina.net/funcy/blog/4696657)һУṩһʾ demo demo  servlet Ȼͨ `servlet3.0` 淶 `DispatcherServlet` עᵽ `servlet` УȻ `DispatcherServlet#init`  spring ̾\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-0874fa7ef39ca9c405cdf55d99ca891ebf2.png)\n\nûʲô⣬Ҳãֻ spring ޷ȡ `DispatcherServlet`\n\n```\n@Component\npublic class Test {\n    // ǰṩʾtomcatspringע벻˵\n    @Autowired\n    public DispatcherServlet dispatcherServlet;\n\n    ...\n\n}\n\n```\n\nʱspring ϶ᱨΪҲ `DispatcherServlet` Ӧ bean\n\nڿ springboot Դʱ spring  tomcat ģ෴springboot  spring Ȼ spring  tomcat أﱾṩһ demo ģ¡\n\n### 1\\. ׼ `DispatcherServlet`\n\n```\n@Component\n@EnableWebMvc\npublic class MvcConfig implements WebMvcConfigurer {\n\n    @Override\n    public void configureViewResolvers(ViewResolverRegistry registry) {\n        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();\n        viewResolver.setPrefix(\"/WEB-INF/views/\");\n        viewResolver.setSuffix(\".html\");\n        registry.viewResolver(viewResolver);\n    }\n\n    /**\n     * dispatcherServlet\n     * @param webApplicationContext\n     * @return\n     */\n    @Bean\n    public DispatcherServlet dispatcherServlet(WebApplicationContext webApplicationContext) {\n        return new DispatcherServlet(webApplicationContext);\n    }\n\n}\n\n```\n\nԸ˵£\n\n*   `MvcConfig` ౻ `@EnableWebMvc` עǣʾҪ `web mvc` \n*   `MvcConfig` ʵ `WebMvcConfigurer`ͨд `WebMvcConfigurer` ķʵԶ `web mvc` \n*   `MvcConfig` л `DispatcherServlet` bean bean ᱣ浽 spring \n\n### 2\\. ׼һ `WebApplicationInitializer` ʵ\n\n```\n@Component\npublic class MyWebApplicationInitializer implements WebApplicationInitializer {\n\n    private static BeanFactory beanFactory;\n\n    private static AbstractRefreshableWebApplicationContext applicationContext;\n\n    @Override\n    public void onStartup(ServletContext servletContext) {\n        //  beanFactory лȡ DispatcherServlet עᵽservlet\n        DispatcherServlet servlet = beanFactory.getBean(DispatcherServlet.class);\n        ServletRegistration.Dynamic registration = servletContext.addServlet(\"app\", servlet);\n        // loadOnStartup ó -1 ʱֻڵһʱŻ init \n        registration.setLoadOnStartup(-1);\n        registration.addMapping(\"/*\");\n\n        // Ϊ applicationContext  servletContext\n        applicationContext.setServletContext(servletContext);\n    }\n\n    /**\n     *  beanFactory\n     * ΪʲôҪ beanFactoryֵΪ DispatcherServlet Ҫ beanFactory лȡ\n     * @param beanFactory\n     * @throws BeansException\n     */\n    public static void setBeanFactory(BeanFactory beanFactory) throws BeansException {\n        MyWebApplicationInitializer.beanFactory = beanFactory;\n    }\n\n    /**\n     *  applicationContext\n     * ΪʲôҪ applicationContext ֵΪ servletContext Ҫõ applicationContext\n     * @param applicationContext\n     */\n    public static void setApplicationContext(\n                AbstractRefreshableWebApplicationContext applicationContext) {\n        MyWebApplicationInitializer.applicationContext = applicationContext;\n    }\n}\n\n```\n\n`WebApplicationInitializer`  spring  servlet 3.0 淶ʵ֣ [spring mvc ֮ springmvc demo  @EnableWebMvc ע](https://my.oschina.net/funcy/blog/4696657)һҲϸtomcat ʱִ `WebApplicationInitializer#onStartup` \n\n `MyWebApplicationInitializer` ˵£\n\n*   `MyWebApplicationInitializer` ̬Ա`beanFactory`  `applicationContext`Ӧṩ̬ `set` Ҫעǣ̬ `set` Ҫ `onStartup()` ǰãҲ tomcat ǰõã\n*    `MyWebApplicationInitializer#onStartup` УǴ `beanFactory` лȡ `DispatcherServlet`Ȼעᵽ `servlet` УȻ `onStartup(...)` Ĳ `servletContext` õ `applicationContext` \n\n### 3\\. ׼һ `ServletContextAwareProcessor` \n\n```\npublic class MyServletContextAwareProcessor extends ServletContextAwareProcessor {\n\n\tAbstractRefreshableWebApplicationContext webApplicationContext;\n\n\t/**\n\t *  webApplicationContext\n\t * @param webApplicationContext\n\t */\n\tpublic MyServletContextAwareProcessor(\n                AbstractRefreshableWebApplicationContext webApplicationContext) {\n\t\tthis.webApplicationContext = webApplicationContext;\n\t}\n\n\t/**\n\t *  ServletContext\n\t * ȴ webApplicationContext лȡȡٴӸ෽лȡ\n\t * @return\n\t */\n\t@Override\n\tprotected ServletContext getServletContext() {\n\t\tServletContext servletContext = this.webApplicationContext.getServletContext();\n\t\treturn (servletContext != null) ? servletContext : super.getServletContext();\n\t}\n\n\t@Override\n\tprotected ServletConfig getServletConfig() {\n\t\tServletConfig servletConfig = this.webApplicationContext.getServletConfig();\n\t\treturn (servletConfig != null) ? servletConfig : super.getServletConfig();\n\t}\n}\n\n```\n\n `MyWebApplicationInitializer#onStartup` ж `applicationContext` õ `servletContext` ʹõģ`MyServletContextAwareProcessor` Ĺ췽 `webApplicationContext`Ȼд `getServletContext()` ȡ `servletContext` ʱȴ `webApplicationContext` лȡȡٴӸ෽лȡ\n\n### 4\\. ׼һ `ApplicationContext` ʵ\n\n`ApplicationContext` Ҫѡֱչ `AnnotationConfigWebApplicationContext`\n\n```\npublic class MyWebApplicationContext extends AnnotationConfigWebApplicationContext {\n\n    private Tomcat tomcat;\n\n    /**\n     * д postProcessBeanFactory \n     * Զ MyServletContextAwareProcessor\n     * @param beanFactory\n     */\n    @Override\n    protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {\n        beanFactory.addBeanPostProcessor(new MyServletContextAwareProcessor(this));\n        beanFactory.ignoreDependencyInterface(ServletContextAware.class);\n        WebApplicationContextUtils.registerWebApplicationScopes(getBeanFactory());\n    }\n\n    /**\n     *  tomcat\n     */\n    @Override\n    protected void onRefresh() {\n        // ȵøķ\n        super.onRefresh();\n        //  MyWebApplicationInitializer  beanFactory  applicationContext\n        MyWebApplicationInitializer.setBeanFactory(getBeanFactory());\n        MyWebApplicationInitializer.setApplicationContext(this);\n\n        // tomcatĴ\n        tomcat = new Tomcat();\n        Connector connector = new Connector();\n        connector.setPort(8080);\n        connector.setURIEncoding(\"UTF-8\");\n        tomcat.getService().addConnector(connector);\n\n        Context context = tomcat.addContext(\"\", System.getProperty(\"java.io.tmpdir\"));\n        LifecycleListener lifecycleListener = null;\n        try {\n            lifecycleListener = (LifecycleListener) \n                    Class.forName(tomcat.getHost().getConfigClass())\n                    .getDeclaredConstructor().newInstance();\n            context.addLifecycleListener(lifecycleListener);\n            // tomcat\n            tomcat.start();\n        } catch (Exception e) {\n            System.out.println(\"쳣\");\n            e.printStackTrace();\n        }\n    }\n\n}\n\n```\n\nչ spring ̣һдһһ:\n\n*   `postProcessBeanFactory()`ҪΪע `MyServletContextAwareProcessor`ǰ׼ `MyServletContextAwareProcessor` עģ֮дΪʹ `tomcat` ṩ `ServletContext`\n*   `onRefresh()` `MyWebApplicationInitializer`  `beanFactory`  `applicationContext` ֵȻ `tomcat`\n\n### 5\\. ׼һ򵥵 `Controller`\n\n׼һ `Controller`Ҫǰ֤ĿǷ\n\n```\n@RestController\n@RequestMapping(\"/test\")\npublic class TestController {\n\n    @RequestMapping(\"/hello\")\n    public String hello() {\n        System.out.println(\"hello!!!\");\n        return \"hello world!\";\n    }\n\n}\n\n```\n\n### 6\\. \n\nˣҪǴ spring Ҳ൱򵥣\n\n```\n@ComponentScan\npublic class MvcDemo03Main {\n\n    public static void main(String[] args) throws Exception {\n        MyWebApplicationContext webApplicationContext = new MyWebApplicationContext();\n        webApplicationContext.register(MvcDemo03Main.class);\n        webApplicationContext.refresh();\n    }\n}\n\n```\n\nУ `http://localhost:8080/test/hello`£\n\nҳ棺\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-d87b4a09e7a87e0535eb52a09759fcc6534.png)\n\n̨\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-c608886c751dcf595d74efb8506e5d67306.png)\n\n### 7\\. ⣺`DispatcherServlet#init` ٴ spring \n\nǰǷʹ tomcat  spring ķʽʱspring  `DispatcherServlet#init` ģʹ **spring  tomcat** ʽʱtomcat ִ `DispatcherServlet#init` ʱٴ spring \n\nֱӽ `FrameworkServlet#initWebApplicationContext` ϶ϵ㣺\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-64ee29f90ef5683f7968f782b9175d42e0f.png)\n\n `wac`  `this.webApplicationContext`  `MyWebApplicationContext` ʵڴ `DispatcherServlet` ʱ:\n\n```\n@Bean\npublic DispatcherServlet dispatcherServlet(WebApplicationContext webApplicationContext) {\n    // ڹ췽Ĳд webApplicationContext\n    return new DispatcherServlet(webApplicationContext);\n}\n\n```\n\nΪʲôΪ `DispatcherServlet#init` úﴦ spring ģϵе `if (!cwac.isActive()) {...` ʱ`!cwac.isActive()` ؽΪ `false` `if`  spring Ͳִеˡ\n\nͨ spring  tomcat  `DispatcherServlet#init` ﲻٴ spring `DispatcherServlet` һ spring beanǾͿڴʹ `@Autowired` ע⽫ע뵽ˣ\n\n```\n@Component\npublic class Test {\n    // ĵʾ spring  tomcatǿעɹ\n    @Autowired\n    public DispatcherServlet dispatcherServlet;\n\n    ...\n\n}\n\n```\n\n `spring`  `tomcat` ķ͵еѵ**ν `tomcat` ṩ `ServletContext` õ `ServletContextAwareProcessor` **еĽʽעᡣ\n\n* * *\n\n_ԭӣ[https://my.oschina.net/funcy/blog/4928222](https://my.oschina.net/funcy/blog/4928222) ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע_"
  },
  {
    "path": "docs/Spring全家桶/SpringMVC源码分析/请求执行流程（一）之获取Handler.md",
    "content": "һƪǷ `RequestMapping` ʼ̣Ľ spring mvc ִ̡\n\n### 1\\. ִ\n\n [spring mvc ֮ DispatcherServlet ʼ](https://my.oschina.net/funcy/blog/4710330 \"spring mvc֮DispatcherServlet ʼ\")һУ `servlet`  `DispatcherServlet` һϵгʼ̣ĽΧ `servlet`  `springmvc` ̡\n\n#### 1.1 ع servlet ִڣ\n\nڷ `DispatcherServlet` ǰҪع servlet ִڡ\n\nʵԶ servlet ʱһʵ `HttpServlet`Ȼд `doGet(xxx)``doPost()` ʵ servlet Ϊ `HttpServlet#service(ServletRequest, ServletResponse)`\n\n```\npublic abstract class HttpServlet extends GenericServlet {\n    ...\n\n    // ˲ת\n    @Override\n    public void service(ServletRequest req, ServletResponse res)\n        throws ServletException, IOException {\n\n        HttpServletRequest  request;\n        HttpServletResponse response;\n\n        // ﴦת\n        try {\n            request = (HttpServletRequest) req;\n            response = (HttpServletResponse) res;\n        } catch (ClassCastException e) {\n            throw new ServletException(lStrings.getString(\"http.non_http\"));\n        }\n        service(request, response);\n    }\n\n    /**\n     * ﴦ\n     * ӴԿʵһתж󷽷Ȼþķִ\n     */\n    protected void service(HttpServletRequest req, HttpServletResponse resp)\n        throws ServletException, IOException {\n\n        String method = req.getMethod();\n\n        // жϵ󷽷ȻҵӦķȥִ\n        if (method.equals(METHOD_GET)) {\n            long lastModified = getLastModified(req);\n            if (lastModified == -1) {\n                doGet(req, resp);\n            } else {\n                ...\n                doGet(req, resp);\n            }\n        } else if (method.equals(METHOD_HEAD)) {\n            long lastModified = getLastModified(req);\n            maybeSetLastModified(resp, lastModified);\n            doHead(req, resp);\n        } else if (method.equals(METHOD_POST)) {\n            doPost(req, resp);\n        } else if (method.equals(METHOD_PUT)) {\n            doPut(req, resp);\n        } else if (method.equals(METHOD_DELETE)) {\n            doDelete(req, resp);\n        } else if (method.equals(METHOD_OPTIONS)) {\n            doOptions(req,resp);\n        } else if (method.equals(METHOD_TRACE)) {\n            doTrace(req,resp);\n        } else {\n            // ûжӦķ\n            ...\n        }\n    }\n\n}\n\n```\n\n servlet Դ룬Ƚϼ򵥣ص㲿ֶעͣҪٴǿ£\n\n1.  servlet ִΪ `HttpServlet#service(ServletRequest, ServletResponse)`\n2.  `HttpServlet#service(HttpServletRequest, HttpServletResponse)` 󷽷ҵӦĴִУһ˵Զ servletֻҪд `doGet(xxx)``doPost(xxx)` ȷɡ\n\n̴£\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-60d0e2afcbdb75a175c6f28471fc467f0e5.png)\n\n#### 1.2 `DispatcherServlet` ĸࣺ`FrameworkServlet`\n\n˽ servlet ں󣬽͵÷һòˣ`FrameworkServlet``FrameworkServlet`  `HttpServlet` ࣬ʵ `HttpServlet` ĸ `doXxx()`ͬʱҲʵ `service(HttpServletRequest, HttpServletResponse)`\n\n```\n/**\n *  FrameworkServlet̳HttpServletBeanHttpServletBean̳HttpServlet\n *  FrameworkServletҲHttpServlet\n */\npublic abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {\n    @Override\n    protected final void doGet(HttpServletRequest request, HttpServletResponse response)\n            throws ServletException, IOException {\n        processRequest(request, response);\n    }\n\n    @Override\n    protected final void doPost(HttpServletRequest request, HttpServletResponse response)\n            throws ServletException, IOException {\n        processRequest(request, response);\n    }\n\n    @Override\n    protected final void doPut(HttpServletRequest request, HttpServletResponse response)\n            throws ServletException, IOException {\n        processRequest(request, response);\n    }\n\n    @Override\n    protected final void doDelete(HttpServletRequest request, HttpServletResponse response)\n            throws ServletException, IOException {\n        processRequest(request, response);\n    }\n\n    @Override\n    protected void service(HttpServletRequest request, HttpServletResponse response)\n            throws ServletException, IOException {\n        HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());\n        if (httpMethod == HttpMethod.PATCH || httpMethod == null) {\n            processRequest(request, response);\n        }\n        else {\n            // GET/POST/PUT/DELETE 󷽷ǻøķ\n            super.service(request, response);\n        }\n    }\n}\n\n```\n\nԿϴУһĳ൱ߣ`FrameworkServlet#processRequest` `doXxx(xxx)` `service(xxx)` `processRequest(xxx)`Ǿʲô\n\n> FrameworkServlet#processRequest\n\n```\nprotected final void processRequest(HttpServletRequest request, HttpServletResponse response)\n        throws ServletException, IOException {\n    // ¼ʼʱ\n    long startTime = System.currentTimeMillis();\n    Throwable failureCause = null;\n    // ¼ǰ̵߳Ϣ\n    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();\n    LocaleContext localeContext = buildLocaleContext(request);\n    RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();\n    ServletRequestAttributes requestAttributes = buildRequestAttributes(\n            request, response, previousAttributes);\n    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);\n    asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), \n             new RequestBindingInterceptor());\n    initContextHolders(request, localeContext, requestAttributes);\n    try {\n        // Ĵ\n        doService(request, response);\n    }\n    catch (ServletException | IOException ex) {\n        failureCause = ex;\n        throw ex;\n    }\n    catch (Throwable ex) {\n        failureCause = ex;\n        throw new NestedServletException(\"Request processing failed\", ex);\n    }\n    finally {\n        // ̰߳Ϣ\n        resetContextHolders(request, previousLocaleContext, previousAttributes);\n        if (requestAttributes != null) {\n            requestAttributes.requestCompleted();\n        }\n        logResult(request, response, failureCause, asyncManager);\n        // ¼֪ͨ\n        publishRequestHandledEvent(request, response, startTime, failureCause);\n    }\n}\n\n```\n\nȻе㳤󲿷̹ϵصֻмУ\n\n```\n    ...\n    try {\n        // Ĵ\n        doService(request, response);\n    }\n    catch (ServletException | IOException ex) {\n        failureCause = ex;\n        throw ex;\n    }\n    ...\n\n```\n\nɴ˿Կʵʴķ `FrameworkServlet#doService` С`FrameworkServlet#doService` Ǹ󷽷\n\n```\nprotected abstract void doService(HttpServletRequest request, \n        HttpServletResponse response) throws Exception;\n\n```\n\nʵ࣬Ҳ `DispatcherServlet#doService` С\n\n#### 1.3 `DispatcherServlet#doService`\n\n `DispatcherServlet#doService` ɶ£\n\n```\npublic class DispatcherServlet extends FrameworkServlet {\n    @Override\n    protected void doService(HttpServletRequest request, \n            HttpServletResponse response) throws Exception {\n        logRequest(request);\n        // ʡһ\n        ...\n        try {\n            // Ĵ\n            doDispatch(request, response);\n        }\n        finally {\n            ...\n        }\n    }\n}\n\n```\n\n˷Ҳûʲôʵֻǵһ `doDispatch` Ȼûˡʵϣ`DispatcherServlet#doDispatch` մ߼ص \n\nһܽ `DispatcherServlet` ̣\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-f854710c25924666cd5ab755c2983a9baf7.png)\n\n### 2\\. springmvc ַ`DispatcherServlet#doDispatch`\n\nһڵǷ springmvc ķ `DispatcherServlet#doDispatch`ھʹ֣߼\n\n```\nprotected void doDispatch(HttpServletRequest request, HttpServletResponse response) \n            throws Exception {\n    HttpServletRequest processedRequest = request;\n    HandlerExecutionChain mappedHandler = null;\n    boolean multipartRequestParsed = false;\n    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);\n    try {\n        ModelAndView mv = null;\n        Exception dispatchException = null;\n        try {\n            //ļϴ⴦\n            processedRequest = checkMultipart(request);\n            multipartRequestParsed = (processedRequest != request);\n            // 1\\. ȡӦhandler, \n            // HandlerаشControllerеķһHandlerInterceptor\n            mappedHandler = getHandler(processedRequest);\n            if (mappedHandler == null) {\n                // ûҵ404\n                noHandlerFound(processedRequest, response);\n                return;\n            }\n            // 2\\. ȡӦhandlerAdapter handler(xxx)\n            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());\n            // last-modified\n            String method = request.getMethod();\n            boolean isGet = \"GET\".equals(method);\n            if (isGet || \"HEAD\".equals(method)) {\n                long lastModified = ha.getLastModified(request, mappedHandler.getHandler());\n                if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {\n                    return;\n                }\n            }\n            // 3\\. spring,  HandlerInterceptor#preHandle \n            if (!mappedHandler.applyPreHandle(processedRequest, response)) {\n                return;\n            }\n            // 4\\. ͨȡhandlerAdapterhandle\n            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());\n            if (asyncManager.isConcurrentHandlingStarted()) {\n                return;\n            }\n            // ûзͼʹĬϵ\n            applyDefaultViewName(processedRequest, mv);\n            // 5\\. ִ HandlerInterceptor#postHandle \n            mappedHandler.applyPostHandle(processedRequest, response, mv);\n        }\n        catch (Exception ex) {\n            dispatchException = ex;\n        }\n        catch (Throwable err) {\n            dispatchException = new NestedServletException(\"Handler dispatch failed\", err);\n        }\n        // 6\\. ؽȾͼԼִ HandlerInterceptor#afterCompletion\n        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);\n    }\n    catch (...) {\n        // ִ HandlerInterceptor#afterCompletion\n        triggerAfterCompletion(processedRequest, response, mappedHandler, ex);\n    }\n    finally {\n        if (asyncManager.isConcurrentHandlingStarted()) {\n            if (mappedHandler != null) {\n                // صִз AsyncHandlerInterceptor#afterConcurrentHandlingStarted\n                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);\n            }\n        }\n        else {\n            if (multipartRequestParsed) {\n                cleanupMultipart(processedRequest);\n            }\n        }\n    }\n}\n\n```\n\nڵ㳤̺springmvc ̶ˣѹؼչʾ£\n\n1.  ȡӦ `HandlerExecutionChain`, ȡ `HandlerExecutionChain` аش`Controller` еķһ `HandlerInterceptor` \n2.  ȡӦ `handlerAdapter`ö `handler(xxx)` \n3.  ִ spring  `HandlerInterceptor#preHandle` \n4.  Ҳͨȡ `handlerAdapter`  `handle(xxx)` \n5.  ִ spring  `HandlerInterceptor#postHandle` \n6.  ؽȾͼԼִ spring  `HandlerInterceptor#afterCompletion`\n\nܵˣ̷ˡ\n\n### 3\\. ȡ `HandlerExecutionChain`\n\nȡ `HandlerExecutionChain` ķ `DispatcherServlet#getHandler` У\n\n```\nprotected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {\n    if (this.handlerMappings != null) {\n        // еhandlerMapping\n        //  handlerMapping WebMvcConfigurationSupport\n        for (HandlerMapping mapping : this.handlerMappings) {\n            // þhandlerĸhandlerֱܹӷ\n            HandlerExecutionChain handler = mapping.getHandler(request);\n            if (handler != null) {\n                return handler;\n            }\n        }\n    }\n    return null;\n}\n\n```\n\n `handlerMappings`  `WebMvcConfigurationSupport` ģһķܲο [springmvc demo  @EnableWebMvc ע](https://my.oschina.net/funcy/blog/4678093 \"springmvc demo  @EnableWebMvc ע\")һģ `handlerMappings` Щɶ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-4465a638ca6c52f24f8c9343986be0bbee9.png)\n\n `RequestMappingHandlerMapping` ŴѾϤ `@Controller`/`@RequestMapping` ʽʵֵ `controller`Ӧ `HandlerMapping`  `RequestMappingHandlerMapping` `HandlerMapping`ֱӦͬʽʵֵ `controller`һ㣬ȤСаٶȣͲչˡ\n\nǼ `AbstractHandlerMapping#getHandler` \n\n```\npublic final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {\n    // 1\\. þʵȥȡhandler\n    Object handler = getHandlerInternal(request);\n    // ΪʹĬϵ\n    if (handler == null) {\n        handler = getDefaultHandler();\n    }\n    // ûĬϵķؿ\n    if (handler == null) {\n        return null;\n    }\n    // ͨBeanNameȥȡhandler\n    if (handler instanceof String) {\n        String handlerName = (String) handler;\n        handler = obtainApplicationContext().getBean(handlerName);\n    }\n    // 2\\. ȡ executionChainʵҵ uri Ӧ Interceptors,\n    // ȻҵhandlerһװHandlerExecutionChain\n    // InterceptorsҲWebMvcConfigurationSupportõ\n    HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);\n    // 3\\. ·صãCorsHandlerExecutionChain\n    // ԿνcorsãҲʵֵ\n    if (hasCorsConfigurationSource(handler)) {\n        CorsConfiguration config = (this.corsConfigurationSource != null \n                ? this.corsConfigurationSource.getCorsConfiguration(request) : null);\n        CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);\n        config = (config != null ? config.combine(handlerConfig) : handlerConfig);\n        // صӵ InterceptorsӵListĵһ\n        executionChain = getCorsHandlerExecutionChain(request, executionChain, config);\n    }\n    return executionChain;\n}\n\n```\n\nҪ£\n\n1.  þʵȥȡ handlerص㣬\n2.  ȡ `executionChain` `executionChain` ˰һ `handler` ⣬ `uri` Ӧ `Interceptors`ȡΪȡе `Interceptors` ã `WebMvcConfigurationSupport` õģһж uri Ƿ `Interceptor`  uri ã\n3.  ȡ cors ãȻӵ `executionChain` е `Interceptors` бĵһλţûcors Ҳ `WebMvcConfigurationSupport` õġ\n\n#### 3.1  `HandlerMethod`\n\nǽ `getHandlerInternal(xxx)` \n\n> AbstractHandlerMethodMapping#getHandlerInternal\n\n```\n@Override\nprotected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {\n    // ȡurl\n    String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);\n    request.setAttribute(LOOKUP_PATH, lookupPath);\n    this.mappingRegistry.acquireReadLock();\n    try {\n        // uriӦhandlerMethod\n        HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);\n        // handlerMethodΪգ´һHandlerMethod\n        return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);\n    }\n    finally {\n        this.mappingRegistry.releaseReadLock();\n    }\n}\n\n```\n\nﻹǵ `lookupHandlerMethod(xxx)`  `handlerMethod`\n\n> AbstractHandlerMethodMapping#lookupHandlerMethod\n\n```\nprotected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) \n        throws Exception {\n    List<Match> matches = new ArrayList<>();\n    // ȴurlLookupңurlLookupһmapkeyurlvalueLinkedList<RequestMappingInfo>\n    List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);\n    if (directPathMatches != null) {\n        // ڷصһ listеƥĽһmatches\n        addMatchingMappings(directPathMatches, matches, request);\n    }\n    if (matches.isEmpty()) {\n        // ͨurlûҵе mappings ƥ䣬ƥ /test/{name} url\n        // mappingsҲһmapkeyRequestMappingInfo valueHandlerMethod\n        addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);\n    }\n    // ҵƥmapping,ӦHandlerMethod\n    // ȽϹ RequestMappingInfo#compareTo\n    if (!matches.isEmpty()) {\n        Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));\n        matches.sort(comparator);\n        Match bestMatch = matches.get(0);\n        if (matches.size() > 1) {\n            if (CorsUtils.isPreFlightRequest(request)) {\n                return PREFLIGHT_AMBIGUOUS_MATCH;\n            }\n            Match secondBestMatch = matches.get(1);\n            // ҵƥ䣬׳쳣\n            if (comparator.compare(bestMatch, secondBestMatch) == 0) {\n                Method m1 = bestMatch.handlerMethod.getMethod();\n                Method m2 = secondBestMatch.     .,m.bvc .getMethod();\n                String uri = request.getRequestURI();\n                throw new IllegalStateException(...);\n            }\n        }W\n        request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);\n        handleMatch(bestMatch.mapping, lookupPath, request);\n        return bestMatch.handlerMethod;\n    }\n    else {\n        return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);\n    }\n}\n\n```\n\nǴ handler ĻȡˡĻȡΪ裺\n\n1.  ȴ `urlLookup` ң`urlLookup` һ `map``key`  `url``value`  `LinkedList<RequestMappingInfo>` `map.get(xxx)` \n2.  ͨ `url` ûҵе `mappings` ƥ䣬ƥ `/test/{name}`  `url``mappings` Ҳһ `map``key`  `RequestMappingInfo` `value`  `HandlerMethod`\n3.  ҵ˶ `HandlerMethod` `RequestMappingInfo#compareTo` ṩķҵѵ `RequestMappingInfo` Ӧ `HandlerMethod`\n\n `mappings` ҵƥ `RequestMappingInfo` ģ\n\n> AbstractHandlerMethodMapping#addMatchingMappings\n\n```\nprivate void addMatchingMappings(Collection<T> mappings, List<Match> matches, \n            HttpServletRequest request) {\n    for (T mapping : mappings) {\n        // ƥҵз mappings\n        T match = getMatchingMapping(mapping, request);\n        if (match != null) {\n            matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping)));\n        }\n    }\n}\n\n```\n\nշƥĴ `RequestMappingInfo#getMatchingCondition` У`RequestMappingInfo` һ `compareTo` Ҳһ鿴£\n\n> RequestMappingInfo\n\n```\n/**\n * ƥ\n * ֱƥ 󷽷(get,post)ͷ\n */\npublic RequestMappingInfo getMatchingCondition(HttpServletRequest request) {\n    RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(request);\n    if (methods == null) {\n        return null;\n    }\n    ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(request);\n    if (params == null) {\n        return null;\n    }\n    HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(request);\n    if (headers == null) {\n        return null;\n    }\n    ConsumesRequestCondition consumes = this.consumesCondition.getMatchingCondition(request);\n    if (consumes == null) {\n        return null;\n    }\n    ProducesRequestCondition produces = this.producesCondition.getMatchingCondition(request);\n    if (produces == null) {\n        return null;\n    }\n    PatternsRequestCondition patterns = this.patternsCondition.getMatchingCondition(request);\n    if (patterns == null) {\n        return null;\n    }\n    RequestConditionHolder custom = this.customConditionHolder.getMatchingCondition(request);\n    if (custom == null) {\n        return null;\n    }\n    return new RequestMappingInfo(this.name, patterns,\n            methods, params, headers, consumes, produces, custom.getCondition());\n}\n\n/**\n * ȽϹҵƥ\n * ֱȽ 󷽷(get,post)ͷ\n */\npublic int compareTo(RequestMappingInfo other, HttpServletRequest request) {\n    int result;\n    if (HttpMethod.HEAD.matches(request.getMethod())) {\n        result = this.methodsCondition.compareTo(other.getMethodsCondition(), request);\n        if (result != 0) {\n            return result;\n        }\n    }\n    result = this.patternsCondition.compareTo(other.getPatternsCondition(), request);\n    if (result != 0) {\n        return result;\n    }\n    result = this.paramsCondition.compareTo(other.getParamsCondition(), request);\n    if (result != 0) {\n        return result;\n    }\n    result = this.headersCondition.compareTo(other.getHeadersCondition(), request);\n    if (result != 0) {\n        return result;\n    }\n    result = this.consumesCondition.compareTo(other.getConsumesCondition(), request);\n    if (result != 0) {\n        return result;\n    }\n    result = this.producesCondition.compareTo(other.getProducesCondition(), request);\n    if (result != 0) {\n        return result;\n    }\n    result = this.methodsCondition.compareTo(other.getMethodsCondition(), request);\n    if (result != 0) {\n        return result;\n    }\n    result = this.customConditionHolder.compareTo(other.customConditionHolder, request);\n    if (result != 0) {\n        return result;\n    }\n    return 0;\n}\n\n```\n\nƥ䣬ǱȽϣ󷽷 (get,post )ͷһһд\n\nǾ springmvc ҵ `HandlerMethod` ˡ\n\n#### 3.2  `Interceptors`\n\nǻص `AbstractHandlerMapping#getHandler`λȡ `Interceptor` ģ\n\n```\npublic final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {\n    ...\n    // 2\\. ȡ executionChainʵҵ uri Ӧ Interceptors,\n    // ȻҵhandlerһװHandlerExecutionChain\n    // InterceptorsҲWebMvcConfigurationSupportõ\n    HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);\n    ...\n    return executionChain;\n}\n\n```\n\n `getHandlerExecutionChain` \n\n> AbstractHandlerMapping#getHandlerExecutionChain\n\n```\nprotected HandlerExecutionChain getHandlerExecutionChain(Object handler, \n            HttpServletRequest request) {\n    HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?\n            (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));\n    // ȡǰ·\n    String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);\n    for (HandlerInterceptor interceptor : this.adaptedInterceptors) {\n        if (interceptor instanceof MappedInterceptor) {\n            MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;\n            // жϵǰ·Ƿinterceptorõ·\n            if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {\n                chain.addInterceptor(mappedInterceptor.getInterceptor());\n            }\n        }\n        else {\n            chain.addInterceptor(interceptor);\n        }\n    }\n    return chain;\n}\n\n```\n\nȽϼ򵥣ѾڴעͣͲ˵ \n\n#### 3.3  cors \n\nõĴ\n\n```\npublic final HandlerExecutionChain getHandler(HttpServletRequest request) \n        throws Exception {\n    ...\n    // 3\\. ·صãCorsHandlerExecutionChain\n    // ԿνcorsãҲʵֵ\n    if (hasCorsConfigurationSource(handler)) {\n        // ȡ\n        CorsConfiguration config = (this.corsConfigurationSource != null \n                ? this.corsConfigurationSource.getCorsConfiguration(request) : null);\n        CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);\n        config = (config != null ? config.combine(handlerConfig) : handlerConfig);\n        // صӵ InterceptorsӵListĵһ\n        executionChain = getCorsHandlerExecutionChain(request, executionChain, config);\n    }\n    return executionChain;\n}\n\n```\n\nҲ `WebMvcConfigurationSupport` ã\n\n```\nprotected void addCorsMappings(CorsRegistry registry) {\n    ...\n}\n\n```\n\nspringmvc ȡú󣬻ӵ `HandlerExecutionChain` У\n\n```\n# AbstractHandlerMapping#getCorsHandlerExecutionChain\nprotected HandlerExecutionChain getCorsHandlerExecutionChain(HttpServletRequest request,\n        HandlerExecutionChain chain, @Nullable CorsConfiguration config) {\n    if (CorsUtils.isPreFlightRequest(request)) {\n        HandlerInterceptor[] interceptors = chain.getInterceptors();\n        chain = new HandlerExecutionChain(new PreFlightHandler(config), interceptors);\n    }\n    else {\n        // ӵInterceptorsλ\n        chain.addInterceptor(0, new CorsInterceptor(config));\n    }\n    return chain;\n}\n\n# HandlerExecutionChain#addInterceptor(int, HandlerInterceptor)\npublic void addInterceptor(int index, HandlerInterceptor interceptor) {\n    // ʵǲһlist\n    initInterceptorList().add(index, interceptor);\n}\n\n```\n\n `HandlerExecutionChain` Уһ `List`  `Interceptor`ȡĿãӵ `List`  `index=0` λá\n\n`handler` ͻȡˣ `handler` ֣\n\n*   `HandlerMethod`: ķڱֻ `@Controller` ʽ controllerԼΪ `@RequestMapping` עķ\n*   `List<Interceptor>`: пãôû List ĵһλ\n\n### 4\\. ȡ `HandlerAdapter`\n\nٻص `DispatcherServlet#doDispatch` ȡ `HandlerAdapter` ķ\n\n```\nprotected void doDispatch(HttpServletRequest request, HttpServletResponse response) \n        throws Exception {\n            ...\n    // 2\\. ȡӦhandlerAdapter handler(xxx)\n    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());\n    ...\n\n```\n\n `getHandlerAdapter(xxx)` \n\n> DispatcherServlet#getHandlerAdapter\n\n```\nprotected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {\n    // handlerAdapters beanҲWebMvcConfigurationSupport\n    if (this.handlerAdapters != null) {\n        for (HandlerAdapter adapter : this.handlerAdapters) {\n            // ͬhandlerAdapterжϷͬ\n            if (adapter.supports(handler)) {\n                return adapter;\n            }\n        }\n    }\n    throw new ServletException(...);\n}\n\n```\n\nԿҵǰе `adapter`ȻжǷܴǰ `handler`е `adapter` £\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-dedb2d58b9e99091aeb559f6c5a4aab4ccc.png)\n\nжǷܴǰ `handler` ģǿһ `handler` `AbstractHandlerMethodAdapter#supports` \n\n> AbstractHandlerMethodAdapter#supports\n\n```\n@Override\npublic final boolean supports(Object handler) {\n    // жhandlerǷΪHandlerMethodʵ\n    return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));\n}\n\n```\n\nһ򵥵жϣȻٵ `supportsInternal` \n\n> RequestMappingHandlerAdapter#supportsInternal\n\n```\nprotected boolean supportsInternal(HandlerMethod handlerMethod) {\n    return true;\n}\n\n```\n\nֱӷ true, ڿɼ `handler` ʵ `HandlerMethod`ôͻ᷵ `RequestMappingHandlerAdapter`.\n\nһҵ `adapter` Ϊ `RequestMappingHandlerAdapter` `adapter` ʲôأƪľȵˣʣµƪ¼\n\n* * *\n\n_ԭӣ[https://my.oschina.net/funcy/blog/4717420](https://my.oschina.net/funcy/blog/4717420) ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע_"
  },
  {
    "path": "docs/Spring全家桶/SpringMVC源码分析/请求执行流程（二）之执行Handler方法.md",
    "content": " **springmvc ִ**ĵڶƪ£һƪУǷ `DispatcherServlet#doDispatch` ִܽзΪ²裺\n\n1.  ȡӦ `HandlerExecutionChain`, ȡ `HandlerExecutionChain` аش`Controller` еķһ `HandlerInterceptor` \n2.  ȡӦ `handlerAdapter`ö `handler(xxx)` \n3.  ִ spring  `HandlerInterceptor#preHandle` \n4.  Ҳͨȡ `handlerAdapter`  `handle(xxx)` \n5.  ִ spring  `HandlerInterceptor#postHandle` \n6.  ؽȾͼԼִ spring  `HandlerInterceptor#afterCompletion`\n\nţǼ `HandlerExecutionChain` ĻȡԼ `handlerAdapter` ĻȡϻأĽĲ衣\n\n### 5\\. ִ spring `HandlerInterceptor#preHandle`\n\n```\nprotected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {\n    ...\n    // 3\\. spring,  HandlerInterceptor#preHandle \n    // mappedHandlerǵ1ȡHandlerExecutionChain\n    if (!mappedHandler.applyPreHandle(processedRequest, response)) {\n        return;\n    }\n    ...\n}\n\n```\n\n `mappedHandler`ǵ 1 ȡ `HandlerExecutionChain` `HandlerExecutionChain#applyPreHandle`\n\n> HandlerExecutionChain#applyPreHandle\n\n```\n/**\n * ִ HandlerInterceptor#preHandle \n */\nboolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) \n        throws Exception {\n    // ȡе\n    HandlerInterceptor[] interceptors = getInterceptors();\n    if (!ObjectUtils.isEmpty(interceptors)) {\n        // ִ preHandle \n        for (int i = 0; i < interceptors.length; i++) {\n            HandlerInterceptor interceptor = interceptors[i];\n            if (!interceptor.preHandle(request, response, this.handler)) {\n                // ʧˣִ HandlerInterceptor#afterCompletion \n                triggerAfterCompletion(request, response, null);\n                return false;\n            }\n            this.interceptorIndex = i;\n        }\n    }\n    return true;\n}\n\n/**\n * ִ HandlerInterceptor#afterCompletion \n * Ϊ˱֤HandlerInterceptor#afterCompletionִУ\n * ķῴڶõ\n */\nvoid triggerAfterCompletion(HttpServletRequest request, \n        HttpServletResponse response, @Nullable Exception ex) throws Exception {\n    HandlerInterceptor[] interceptors = getInterceptors();\n    if (!ObjectUtils.isEmpty(interceptors)) {\n        // ִ HandlerInterceptor#afterCompletion \n        for (int i = this.interceptorIndex; i >= 0; i--) {\n            HandlerInterceptor interceptor = interceptors[i];\n            try {\n                interceptor.afterCompletion(request, response, this.handler, ex);\n            }\n            catch (Throwable ex2) {\n                logger.error(\"HandlerInterceptor.afterCompletion threw exception\", ex2);\n            }\n        }\n    }\n}\n\n```\n\nһ `HandlerExecutionChain`\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-40ba62630b5d83a16ed9aba35aba48af8be.png)\n\n `HandlerInterceptor`  `handler` ɶ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-6c50a936804c1db4a7b817dbb5ff417034d.png)\n\n `handler`  `HandlerMethod`Ϣͦḻģ `bean`/`beanFactory`/`method`⼸ԣǿԶиֲ\n\n### 6\\. ִУ`AbstractHandlerMethodAdapter#handle`\n\nٻص `DispatcherServlet#doDispatch`ִ `HandlerInterceptor#preHandle` 󣬾еͷϷ`handler` ִУҲ `controller` У`url` ӦķִУ\n\n```\nprotected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {\n    ...\n    // 4\\. ͨȡhandlerAdapterhandle\n    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());\n    ...\n}\n\n```\n\nһ·ȥ `RequestMappingHandlerAdapter#invokeHandlerMethod` \n\n```\nprotected ModelAndView invokeHandlerMethod(HttpServletRequest request,\n        HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {\n    // װrequesrequest\n    ServletWebRequest webRequest = new ServletWebRequest(request, response);\n    try {\n        // ȡ @InitBinder עķ\n        // ǰcontroller @ControllerAdvice ע @InitBinder עķ\n        WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);\n        // ȡ @ModelAttribute עķ\n        // ǰcontroller @ControllerAdvice ע @ModelAttribute עķ\n        ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);\n        // ִж\n        ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);\n        if (this.argumentResolvers != null) {\n            invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);\n        }\n        if (this.returnValueHandlers != null) {\n            invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);\n        }\n        invocableMethod.setDataBinderFactory(binderFactory);\n        invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);\n        // ModelAndView\n        ModelAndViewContainer mavContainer = new ModelAndViewContainer();\n        mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));\n        modelFactory.initModel(webRequest, mavContainer, invocableMethod);\n        mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);\n        // 첽\n        AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);\n        asyncWebRequest.setTimeout(this.asyncRequestTimeout);\n        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);\n        asyncManager.setTaskExecutor(this.taskExecutor);\n        asyncManager.setAsyncWebRequest(asyncWebRequest);\n        asyncManager.registerCallableInterceptors(this.callableInterceptors);\n        asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);\n        if (asyncManager.hasConcurrentResult()) {\n            Object result = asyncManager.getConcurrentResult();\n            mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];\n            asyncManager.clearConcurrentResult();\n            LogFormatUtils.traceDebug(logger, traceOn -> {\n                String formatted = LogFormatUtils.formatValue(result, !traceOn);\n                return \"Resume with async result [\" + formatted + \"]\";\n            });\n            invocableMethod = invocableMethod.wrapConcurrentResult(result);\n        }\n        // ִControllerķص㣩\n        invocableMethod.invokeAndHandle(webRequest, mavContainer);\n        if (asyncManager.isConcurrentHandlingStarted()) {\n            return null;\n        }\n        // ؽ\n        return getModelAndView(mavContainer, modelFactory, webRequest);\n    }\n    finally {\n        webRequest.requestCompleted();\n    }\n}\n\n```\n\nе㳤ص㷽ֻһУ`invocableMethod.invokeAndHandle(webRequest, mavContainer);`ǰĲִֶǰ׼ȡ `@InitBinder` עķȡ `@ModelAttribute` עķ׼ `webRequest`(װ `request`  `response` )  `mavContainer`(`ModelAndView` װ) ȡֱӽ `invocableMethod.invokeAndHandle`ִеģ\n\n> ServletInvocableHandlerMethod#invokeAndHandle\n\n```\npublic void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,\n        Object... providedArgs) throws Exception {\n    // ִhandler\n    Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);\n    //  RequestHandled ֵspringmvcݸֵжҪҪת\n    if (returnValue == null) {\n        if (isRequestNotModified(webRequest) || getResponseStatus() != null \n                || mavContainer.isRequestHandled()) {\n            disableContentCachingIfNecessary(webRequest);\n            mavContainer.setRequestHandled(true);\n            return;\n        }\n    }\n    else if (StringUtils.hasText(getResponseStatusReason())) {\n        mavContainer.setRequestHandled(true);\n        return;\n    }\n    mavContainer.setRequestHandled(false);\n    Assert.state(this.returnValueHandlers != null, \"No return value handlers\");\n    try {\n        // ؽ\n        this.returnValueHandlers.handleReturnValue(\n                returnValue, getReturnValueType(returnValue), mavContainer, webRequest);\n    }\n    catch (Exception ex) {\n        throw ex;\n    }\n}\n\n```\n\n﷽ǵ `invokeForRequest` ִзȻٸݷķֵ `mavContainer`  `RequestHandled` ֵؽ `invokeForRequest` \n\n> InvocableHandlerMethod#invokeForRequest\n\n```\n@Nullable\npublic Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainermavContainer,\n        Object... providedArgs) throws Exception {\n    // \n    Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);\n    // ÷\n    return doInvoke(args);\n}\n\n```\n\nǴ˲Ȼʹ÷зáʵϣִУĵľǲˣ controller  handler Уǿָ\n\n```\n// ֱӴ\n@RequestMapping(\"xxx\")\npublic Object test(String name) {\n    ...\n}\n\n// ڲϱ@RequestParam@RequestHeaderע\n@RequestMapping(\"xxx\")\npublic Object test(@RequestParam(\"name\") String name, \n                   @RequestHeader(\"uid\") String uid) {\n    ...\n}\n\n// ĲװΪ\n@RequestMapping(\"xxx\")\npublic Object test(User user) {\n    ...\n}\n\n// ķʹform(Ҳk1=v1&2=v2&...ķ)\n// ʹ RequestBody ʽΣݷϢ\n@RequestMapping(\"xxx\")\npublic Object test(@RequestBody User user) {\n    ...\n}\n\n...\n\n```\n\nǰ淶ʱspringmvc  springmvc һġ\n\n#### \n\n `InvocableHandlerMethod#getMethodArgumentValues` \n\n> InvocableHandlerMethod#getMethodArgumentValues\n\n```\nprotected Object[] getMethodArgumentValues(NativeWebRequest request, \n        @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {\n    // ȡвԼΪ÷ȡhandlerȻװΪ MethodParameter\n    MethodParameter[] parameters = getMethodParameters();\n    if (ObjectUtils.isEmpty(parameters)) {\n        return EMPTY_ARGS;\n    }\n    Object[] args = new Object[parameters.length];\n    // δÿ\n    for (int i = 0; i < parameters.length; i++) {\n        MethodParameter parameter = parameters[i];\n        parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);\n        args[i] = findProvidedArgument(parameter, providedArgs);\n        if (args[i] != null) {\n            continue;\n        }\n        // жǷвֵ֧ǰĽ\n        if (!this.resolvers.supportsParameter(parameter)) {\n            throw new IllegalStateException(formatArgumentError(parameter, \"No suitable resolver\"));\n        }\n        try {\n            // \n            args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, \n                    this.dataBinderFactory);\n        }\n        catch (Exception ex) {\n            throw ex;\n        }\n    }\n    return args;\n}\n\n```\n\nȻȡ handler Ĳ (ԼΪ÷ȡ handler ȻװΪ `MethodParameter`)ȻЩʱõҪķ`resolvers.supportsParameter(...)`  `resolvers.resolveArgument(...)`յõķ `HandlerMethodArgumentResolverComposite` У\n\n> HandlerMethodArgumentResolverComposite\n\n```\n@Nullable\npublic Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainermavContainer,\n        NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {\n    // ȡһ\n    HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);\n    if (resolver == null) {\n        throw new IllegalArgumentException(...);\n    }\n    //  HandlerMethodArgumentResolver#resolveArgument \n    return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);\n}\n\nprivate HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {\n    HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);\n    if (result == null) {\n        // еĽ\n        for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {\n            // ҵһ\n            //  HandlerMethodArgumentResolver#supportsParameter \n            if (resolver.supportsParameter(parameter)) {\n                result = resolver;\n                this.argumentResolverCache.put(parameter, result);\n                break;\n            }\n        }\n    }\n    return result;\n}\n\n```\n\nҪǱȻ `HandlerMethodArgumentResolver#supportsParameter`  `HandlerMethodArgumentResolver#resolveArgument` Ĳ`HandlerMethodArgumentResolver` Ǹӿڣ\n\n```\npublic interface HandlerMethodArgumentResolver {\n    /**\n     * ǰǷִ֧ǰ\n     */\n    boolean supportsParameter(MethodParameter parameter);\n\n    /**\n     * Ľ\n     */\n    @Nullable\n    Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,\n            NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;\n}\n\n```\n\nĲˡ springmvc Уṩ˶ֲأ˵ĵԣֶ 26 \n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-fd30bc847fc2cf7082de3731e68df6ae5f9.png) ![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-894b391bbe4567734ee7237d900bc55ee31.png)\n\nЩİ£springmvc ֲֶ֧շʽڲƱȽϸӣ漰ִηʽĲչۣȤСвĵ\n\n#### ִ handler \n\n󣬾Ϳʼִ handler ˡǻص `InvocableHandlerMethod#invokeForRequest` \n\n> InvocableHandlerMethod#invokeForRequest\n\n```\n@Nullable\npublic Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainermavContainer,\n        Object... providedArgs) throws Exception {\n    // \n    Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);\n    // ÷\n    return doInvoke(args);\n}\n\n```\n\n `doInvoke` \n\n```\n@Nullable\nprotected Object doInvoke(Object... args) throws Exception {\n    // ʹ÷ִз\n    ReflectionUtils.makeAccessible(getBridgedMethod());\n    try {\n        // Ƿ\n        return getBridgedMethod().invoke(getBean(), args);\n    }\n    catch (...) {\n        ...\n    }\n}\n\n```\n\nܼ򵥣÷зִеģͲˡ\n\n#### ز\n\nǻص `ServletInvocableHandlerMethod#invokeAndHandle` \n\n> ServletInvocableHandlerMethod#invokeAndHandle\n\n```\npublic void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,\n        Object... providedArgs) throws Exception {\n    // ִhandle\n    Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);\n    ...\n    try {\n        // ؽ\n        this.returnValueHandlers.handleReturnValue(\n                returnValue, getReturnValueType(returnValue), mavContainer, webRequest);\n    }\n    catch (Exception ex) {\n        throw ex;\n    }\n}\n\n```\n\n귽ִк󣬽žʹؽ\n\n> HandlerMethodReturnValueHandlerComposite#handleReturnValue\n\n```\n@Override\npublic void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,\n        ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {\n    // ݷһʵhandler\n    HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);\n    if (handler == null) {\n        throw new IllegalArgumentException(...);\n    }\n    handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);\n}\n\n@Nullable\nprivate HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameterreturnType) {\n    boolean isAsyncValue = isAsyncReturnValue(value, returnType);\n    // жǷܴǰ\n    for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {\n        if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {\n            continue;\n        }\n        if (handler.supportsReturnType(returnType)) {\n            return handler;\n        }\n    }\n    return null;\n}\n\n```\n\nȡ `ReturnValueHandler` ·ǰȡ `ArgumentResolver` ·޼`ReturnValueHandler`  `HandlerMethodReturnValueHandler` ࣬`HandlerMethodReturnValueHandler` £\n\n```\npublic interface HandlerMethodReturnValueHandler {\n\n    /**\n     * жϵǰReturnValueHandlerܷreturnType\n     */\n    boolean supportsReturnType(MethodParameter returnType);\n\n    /**\n     * Ĵ߼\n     */\n    void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,\n            ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;\n\n}\n\n```\n\nͬǰһӿҲ\n\n*   `boolean supportsReturnType(xxx)`жϵǰ `ReturnValueHandler` ܷ `returnType`\n*   `void handleReturnValue(xxx)`Ĵ߼\n\nͬأspringmvc Ҳṩ˷ǳʵز\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-8e3c1fd4dca7c13efbceac5800e26145963.png)\n\nڲķأһ򵥵ʾ\n\nֵʱ\n\n```\n@Controller\n@RequestMapping(\"/xxx\")\npublic class XxxController {\n\n    @RequestMapping(\"/index\")\n    public String index() {\n        return \"index\";\n    }\n}\n\n```\n\nصҳĽһͼӦ `HandlerMethodReturnValueHandler` Ϊ `ViewNameMethodReturnValueHandler`\n\n```\npublic class ViewNameMethodReturnValueHandler implements HandlerMethodReturnValueHandler {\n\n    @Nullable\n    private String[] redirectPatterns;\n\n    // ʡ redirectPatterns settergetter\n    ...\n\n    @Override\n    public boolean supportsReturnType(MethodParameter returnType) {\n        Class<?> paramType = returnType.getParameterType();\n        // ִ֧ķֵֵͣΪvoidΪַ\n        return (void.class == paramType || CharSequence.class.isAssignableFrom(paramType));\n    }\n\n    /**\n     * Ĵ߼\n     */\n    @Override\n    public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,\n            ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {\n\n        if (returnValue instanceof CharSequence) {\n            // viewName Ƿֵ\n            String viewName = returnValue.toString();\n            mavContainer.setViewName(viewName);\n            if (isRedirectViewName(viewName)) {\n                // ǷҪת\n                mavContainer.setRedirectModelScenario(true);\n            }\n        }\n        else if (returnValue != null) {\n            throw new UnsupportedOperationException(...);\n        }\n    }\n\n    /**\n     * жǷҪת\n     */\n    protected boolean isRedirectViewName(String viewName) {\n        // this.redirectPatterns ĬΪnullе setter \n        return (PatternMatchUtils.simpleMatch(this.redirectPatterns, viewName) \n            // ƥ redirect: ͷҲ˵Ƿ \"redirect:index\"ͱýҪת\n            || viewName.startsWith(\"redirect:\"));\n    }\n\n}\n\n```\n\nȽϼ򵥣ؼڴעͣͲ˵ˡֵһǣ `handleReturnValue(xxx)` Уspringmvc صַΪ `viewName` Ҫע£ڴͼʱ `viewName` õӦ `View`\n\n#### ȡ ModelAndView\n\nǻص `RequestMappingHandlerAdapter#invokeHandlerMethod` \n\n```\n@Nullable\nprotected ModelAndView invokeHandlerMethod(HttpServletRequest request,\n        HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {\n    ServletWebRequest webRequest = new ServletWebRequest(request, response);\n    try {\n        ...\n        // ִControllerķ\n        invocableMethod.invokeAndHandle(webRequest, mavContainer);\n        if (asyncManager.isConcurrentHandlingStarted()) {\n            return null;\n        }\n        // ִнõ ModelAndView\n        return getModelAndView(mavContainer, modelFactory, webRequest);\n    }\n    finally {\n        webRequest.requestCompleted();\n    }\n}\n\n```\n\nִ `invocableMethod.invokeAndHandle(webRequest, mavContainer)` žǴִнõ `ModelAndView` ˣ `RequestMappingHandlerAdapter#getModelAndView` \n\n```\nprivate ModelAndView getModelAndView(ModelAndViewContainer mavContainer,\n        ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {\n    //  ModelFactoryǰȡ @ModelAttribute עķ\n    modelFactory.updateModel(webRequest, mavContainer);\n    if (mavContainer.isRequestHandled()) {\n        return null;\n    }\n    ModelMap model = mavContainer.getModel();\n    // ͼ󣬰mavContainer.getViewName()뵽ModelAndViewĹ췽\n    ModelAndView mav = new ModelAndView(mavContainer.getViewName(), \n            model, mavContainer.getStatus());\n    if (!mavContainer.isViewReference()) {\n        mav.setView((View) mavContainer.getView());\n    }\n    // ض\n    if (model instanceof RedirectAttributes) {\n        Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();\n        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);\n        if (request != null) {\n            RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);\n        }\n    }\n    return mav;\n}\n\n```\n\nԿ`ModelAndView` Ǵǰִн `viewName` õġ\n\nˣ`AbstractHandlerMethodAdapter#handle` ִϡ\n\n### 7\\. ִ`HandlerInterceptor#postHandle`\n\nٻص `DispatcherServlet#doDispatch` \n\n> DispatcherServlet#doDispatch\n\n```\nprotected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {\n    ...\n\n    try {\n        ...\n        try {\n            ...\n            // 4.ͨȡhandlerAdapterhandle\n            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());\n            // 5.ûзͼʹĬϵ\n            applyDefaultViewName(processedRequest, mv);\n            // 6\\. ִ HandlerInterceptor#postHandle \n            mappedHandler.applyPostHandle(processedRequest, response, mv);\n        }\n        catch (...) {\n            ...\n        }\n        // 7\\. ؽִ HandlerInterceptor.afterCompletion\n        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);\n    }\n    catch (...) {\n        ...\n    }\n}\n\n```\n\nִ `handler` 󣬾Ϳʼִ `HandlerInterceptor#postHandle` ˣ\n\n> HandlerExecutionChain#applyPostHandle\n\n```\nvoid applyPostHandle(HttpServletRequest request, HttpServletResponse response, \n        @NullableModelAndView mv) throws Exception {\n    HandlerInterceptor[] interceptors = getInterceptors();\n    if (!ObjectUtils.isEmpty(interceptors)) {\n        // ִ postHandle(...) \n        for (int i = interceptors.length - 1; i >= 0; i--) {\n            HandlerInterceptor interceptor = interceptors[i];\n            interceptor.postHandle(request, response, this.handler, mv);\n        }\n    }\n}\n\n```\n\nͬǰִƣͲˡҪǿǣ`HandlerInterceptor#postHandle` ִʱ**ִ `handler` ֮ͼ֮ǰ**ˣǿﴦһЩȾ\n\nƪľȵˣrequest ִеḷ́ƪٷ\n\n* * *\n\n_ԭӣ[https://my.oschina.net/funcy/blog/4741104](https://my.oschina.net/funcy/blog/4741104) ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע_"
  },
  {
    "path": "docs/Spring全家桶/Spring源码分析/SpringAOP/AOP示例demo及@EnableAspectJAutoProxy.md",
    "content": "在前面的文章中，我们成功的编译了 spring 源码，也构建了第一个 spring 测试 demo，接下来我们就基于[第一个 spring 源码调试 demo](https://my.oschina.net/funcy/blog/4533250 \"第一个spring源码调试demo\") 中的代码，来对 spring 源码进行源码分析。\n\n### 1\\. spring 启动流程概览\n\n在前面 demo 的 `main()` 方法中，有这么一行：\n\n```\nApplicationContext context =\n        new AnnotationConfigApplicationContext(\"org.springframework.learn.demo01\");\n\n```\n\n这短短的一行就是 spring 的整个启动流程了。上面的代码中，声明了一个 `ApplicationContext` 类型的对象 `context`，右边使用其子类 `AnnotationConfigApplicationContext` 实例化，并在构造方法中传入了包名 `org.springframework.learn.demo01`，这个包名就表明了接下来要扫描哪些包。\n\n> 这里我们接触到了 spring 的第一个组件：`ApplicationContext`，关于 `ApplicationContext` 的分析，可以参考我的文章 [spring 组件（一）：ApplicationContext](https://my.oschina.net/funcy/blog/4597456 \"spring组件（一）：ApplicationContext\")。\n\n进入到 `AnnotationConfigApplicationContext`，代码如下：\n\n> AnnotationConfigApplicationContext#AnnotationConfigApplicationContext(String...)\n\n```\npublic AnnotationConfigApplicationContext(String... basePackages) {\n     // 1\\. 调用无参构造函数，会先调用父类GenericApplicationContext的构造函数\n     // 2\\. 父类的构造函数里面就是初始化DefaultListableBeanFactory，并且赋值给beanFactory\n     // 3\\. 本类的构造函数里面，初始化了一个读取器：AnnotatedBeanDefinitionReader read，\n     //    一个扫描器ClassPathBeanDefinitionScanner scanner\n     // 4\\. 这个scanner，就是下面 scan(basePackages) 调用的对象\n     this();\n\n     //对传入的包进行扫描，扫描完成后，会得到一个 BeanDefinition 的集合\n     scan(basePackages);\n\n     //启动spring，在这里完成spring容器的初始化操作，\n     //包括bean的实例化、属性注入，将bean保存到spring容器中等\n     refresh();\n}\n\n```\n\n这个类就三行，相关操作都已在代码中注释了，这里稍微再总结下，这段代码主要做了三件事：\n\n1.  调用无参构造，进行属性初始化\n2.  进行包扫描，得到 BeanDefinition\n3.  启用 spring 容器。\n\n接着，我们再来看看 spring 启动流程中，做了哪些事：\n\n> AbstractApplicationContext#refresh\n\n```\npublic void refresh() throws BeansException, IllegalStateException {\n    // 使用synchronized是为了避免refresh() 还没结束，再次发起启动或者销毁容器引起的冲突\n    synchronized (this.startupShutdownMonitor) {\n        // 做一些准备工作，记录容器的启动时间、标记“已启动”状态、检查环境变量等\n        prepareRefresh();\n\n        // 初始化BeanFactory容器、注册BeanDefinition, 最终获得了DefaultListableBeanFactory\n        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();\n\n        // 还是一些准备工作:\n        // 1\\. 设置了一个类加载器\n        // 2\\. 设置了bean表达式解析器\n        // 3\\. 添加了属性编辑器的支持\n        // 4\\. 添加了一个后置处理器：ApplicationContextAwareProcessor\n        // 5\\. 设置了一些忽略自动装配的接口\n        // 6\\. 设置了一些允许自动装配的接口，并且进行了赋值操作\n        // 7\\. 在容器中还没有XX的bean的时候，帮我们注册beanName为XX的singleton bean\n        prepareBeanFactory(beanFactory);\n\n        try {\n            // Spring的一个扩展点. 如果有Bean实现了BeanFactoryPostProcessor接口，\n            // 那么在容器初始化以后，Spring 会负责调用里面的 postProcessBeanFactory 方法。\n            // 具体的子类可以在这步的时候添加特殊的 BeanFactoryPostProcessor 的实现类，来做些事\n            postProcessBeanFactory(beanFactory);\n\n            // 调用BeanFactoryPostProcessor各个实现类的postProcessBeanFactory(factory) 方法\n            invokeBeanFactoryPostProcessors(beanFactory);\n\n            // 扩展点,注册 BeanPostProcessor 的实现类，注意不是BeanFactoryPostProcessor\n            registerBeanPostProcessors(beanFactory);\n\n            // 初始化当前 ApplicationContext 的 MessageSource，用在国际化操作中\n            initMessageSource();\n\n            // 这个方法主要为初始化当前 ApplicationContext 的事件广播器\n            initApplicationEventMulticaster();\n\n            // 这也是spring的一个扩展点\n            onRefresh();\n\n            // Check for listener beans and register them.\n            // 注册事件监听器\n            registerListeners();\n\n            // 初始化所有的 singleton beans\n            finishBeanFactoryInitialization(beanFactory);\n\n            // 完成启动，\n            finishRefresh();\n        }\n\n        catch (BeansException ex) {\n            if (logger.isWarnEnabled()) {\n                logger.warn(\"Exception encountered during context initialization - \" +\n                    \"cancelling refresh attempt: \" + ex);\n            }\n\n            // Destroy already created singletons to avoid dangling resources.\n            // 销毁已经初始化的的Bean\n            destroyBeans();\n\n            // Reset 'active' flag.\n            // 重置 'active' 状态\n            cancelRefresh(ex);\n\n            // Propagate exception to caller.\n            throw ex;\n        }\n\n        finally {\n            // Reset common introspection caches in Spring's core, since we\n            // might not ever need metadata for singleton beans anymore...\n            // 清除缓存\n            resetCommonCaches();\n        }\n    }\n}\n\n```\n\n这个方法虽然代码不多，但包含了 spring bean 的整个创建过程，每个方法做了些什么，在代码中都有注释，这里就不赘述了。\n\n实际上，`refresh()` 涵盖了 spring 整个创建 bean 的流程，在后面的文章中，我们也将重点展开这里面的方法来分析，在现阶段只需要大致了解这些方法做了什么事即可。\n\n整个流程总结如下：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-9307fefa65470e5c36ae6044631b5416aef.png)\n\n### 2\\. spring 启动中 `beanFactory` 的变化\n\n本文中的源码解读就到这里了，接下来我们来看看，spring 启动中 `beanFactory` 有些什么变化。\n\n> `beanFactory` 是 spring 的重要组件之一，直译为 spring bean 工厂，是 spring 生产 bean 与保存 bean 的地方，关于 `beanFactory` 的详细分析，可以查看 [spring BeanFactory 分析](https://my.oschina.net/funcy/blog/4597529 \"spring BeanFactory分析\")。\n\n我们将断点打在 `AnnotationConfigApplicationContext#AnnotationConfigApplicationContext(String...)` 的 `this()` 方法上，然后运行 demo01 的 `main()` 方法：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-c3c672a675d9b06f03ea29cb31f6ed5d012.png)\n\n此时的变量中，并没有 `beanFactory`，我们自己添加 `beanFactory` 到调度窗口的变量列表中：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-e9d8ae8fdd3b02b2279376303e3eae4cf2f.png)\n\n这样就能看到对应的值了：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-44d1ade26f0cb667425b0dd99d82666877f.png)\n\n可以看到，此时的 `beanFactory` 为 null，表明 `beanFactory` 并未实例化，我们继续运行：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-664780be9dfef73c12a3f163b349e7e54d8.png)\n\n当运行完 `this()` 后，发现 `beanFactory` 已经有值了，类型为 `DefaultListableBeanFactory`。但是，在查看 `beanFactory` 对象时，发现 `beanFactory` 的属性太多了，我们应该重点关注啥呢？\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-1b617cf7edda29c652a7661d4be3779ec85.png)\n\n我们这部分主要关注 spring bean 的创建，因此只需要关注 `beanFactory` 的两个属性就可以了：\n\n*   beanDefinitionMap：存放 beanDefinition 的 map.\n*   singletonObjects：存放 spring bean 的 map，spring bean 创建后都存放在这里，也即直观上理解的 `spring 容器`.\n\n> `BeanDefinition` 是 spring 重要组件之一，为‘spring bean 的描述’，简单来说，就是说明了一个 spring bean 应该如何创建。关于 `BeanDefinition` 的详细分析，可以查看 [spring BeanDefinition 分析](https://my.oschina.net/funcy/blog/4597536)。\n\n我们手动添加变量，如下：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-0c6368478258f8b9f76b47fc1c85b02f13f.png)\n\n可以看到，此时的 `beanDefinitionMap` 中已经有 4 个对象了，显然是在 `this()` 方法中添加的，关于这块我们后面会分析。\n\n接着运行，发现 `beanDefinitionMap` 又多了两个：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-a493061fbd4b4066f9a4d91e91ff61e8c4e.png)\n\n这里的 `beanObj1` 与 `beanObj2` 就是我们自己的类了，由此可以判断出 **spring 就是在 `AnnotationConfigApplicationContext#scan` 方法中对包进行扫描的**。\n\n接下来，代码执行进入 `AbstractApplicationContext#refresh` 方法，我们一行行运行下去，发现运行到 `prepareBeanFactory(beanFactory);` 时，`singletonObjects` 中第一次出现了对象：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-8cebcb82f5a8754fd1bb4bb3eb3c57dda2d.png)\n\n可以看到，这里出现了 3 个类，基本都跟系统、环境相关，如 `environment` 是 spring 当前使用的环境 (`profile`)，`systemProperties` 当前系统的属性（操作系统、操作系统版本等）。\n\n继续往下运行，发现代码运行到 `invokeBeanFactoryPostProcessors(beanFactory)` 时，又多了 4 个类：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-baa09e51272baa384418cb2c82b9dfb079b.png)\n\n关于这几个类的作用，我们后面的文章中会分析，这里先不必管。继续往下运行，发现在 `registerBeanPostProcessors(beanFactory);` 中，又多了一个对象：\n\n```\norg.springframework.context.annotation.internalAutowiredAnnotationProcessor\n\n```\n\n这里我们依旧不用管这个对象，接着运行下去，可以看到在运行 `initMessageSource()` 时，又多了一个对象：\n\n```\nmessageSource -> {DelegatingMessageSource@1847} \"Empty MessageSource\"\n\n```\n\n显然，这个对象是用来处理国际化问题的，不过由于 demo01 中并没有用到国际化，所以这里显示 `Empty MessageSource`。继续运行，发现运行到 `initApplicationEventMulticaster();` 时，又多了一个对象：\n\n```\napplicationEventMulticaster -> {SimpleApplicationEventMulticaster@1869} \n\n```\n\n显然，这个对象是用来处理 `ApplicationContext` 的广播事件的，我们的 demo 中并没有用到，暂时不必理会。继续下去，发现在运行完 `finishBeanFactoryInitialization(beanFactory);`，`singletonObjects` 中终于出现了我们期待的对象：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-68b1ee71e468ef8cf839230c07b64c45563.png)\n\n由此可见，对象就是在该方法中创建的。\n\n### 总结\n\n1.  spring 包的描述：`AnnotationConfigApplicationContext#scan`\n2.  spring bean 的创建：`AbstractApplicationContext#finishBeanFactoryInitialization`\n\n本文主要是了解 spring 启动流程，从整体上把握 spring 启动过程中的 beanFactory 的变化。本文意在了解 spring 的整体启动流程，后续的分析中，我们将对这些流程进行展开分析。\n\n* * *\n\n_本文原文链接：[https://my.oschina.net/funcy/blog/4597493](https://my.oschina.net/funcy/blog/4597493) ，限于作者个人水平，文中难免有错误之处，欢迎指正！原创不易，商业转载请联系作者获得授权，非商业转载请注明出处。_"
  },
  {
    "path": "docs/Spring全家桶/Spring源码分析/SpringAOP/AnnotationAwareAspectJAutoProxyCreator分析（上）.md",
    "content": "在[上一篇文章](https://my.oschina.net/funcy/blog/4678093 \"上一篇文章\")的分析中，我们介绍了使用 `@EnableAspectJAutoProxy` 开启用 spring aop 功能，而 `@EnableAspectJAutoProxy` 最终向 spring 中引入了一个类：`AnnotationAwareAspectJAutoProxyCreator`，本文就从该类入手，进 一步分析 spring aop 功能。\n\n首先我们来看看这个类的继承关系：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-53b639dcaacb2480e9098a046407a80a574.png)\n\n从继承关系上来看，`AnnotationAwareAspectJAutoProxyCreator` 是一个 `BeanPostProcessor`，结合前面的分析，spring `BeanPostProcessor` 的执行是在 spring bean 初始化前后，这里我们通过断点调试的方式验证下。\n\n### 1\\. 调试查看代理对象的产生\n\n我们将断点打在 `AbstractAutowireCapableBeanFactory#initializeBean(String, Object, RootBeanDefinition)` 方法上，然后在 debug 模式下运行：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-449fe5a3456350ae0d9854c1c13f4a59a80.png)\n\n此时的 `wrappedBean` 的类型还是 `AopBean1`，继续往下，当运行完 `applyBeanPostProcessorsAfterInitialization` 后，`wrappedBean` 的类型就变了样：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-8b791334bf1873c652a265db9ab1d0e1441.png)\n\n表现上看还是 `AopBean1`，但前面出现了 `$Poxy19` 字样，且多了一个属性：`JdkDynamicAopProxy`，这表明该动态是 jdk 动态代理生成的对象。\n\n再看看 `AopBean2` 运行到此处的变化：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-4fc5d03af4298f55d69e5c619cd72df1ff0.png)\n\n可以看到，类名中出现了 `SpringCGLIB` 字样，这表示该对象是由 spring cglib 代理生成的。\n\n从以上调试结果来看，spring 是在 `AnnotationAwareAspectJAutoProxyCreator#postProcessAfterInitialization` 中完成对象代理的。\n\n实际上，`AnnotationAwareAspectJAutoProxyCreator` 中并未实现 `postProcessAfterInitialization` 方法，`AnnotationAwareAspectJAutoProxyCreator` 的 `postProcessAfterInitialization` 方法继承自 `AbstractAutoProxyCreator#postProcessAfterInitialization`，因此 spring 对象代理的实际完成是在 `AbstractAutoProxyCreator#postProcessAfterInitialization` 中。\n\n`AnnotationAwareAspectJAutoProxyCreator` 是 `BeanPostProcessor` 的子类，spring 在 bean 的初始化前后会调用 `BeanPostProcessor#postProcessBeforeInitialization` 与 `BeanPostProcessor#postProcessAfterInitialization` 方法，我们将从源码上看看 `AnnotationAwareAspectJAutoProxyCreator` 在 bean 的初始前后是如何完成 aop 操作。\n\n### 2. `AbstractAutoProxyCreator#postProcessBeforeInitialization` 方法\n\n代理对象的生成虽然是在 `BeanPostProcessor#postProcessAfterInitialization` 方法，但正所谓 “做戏做全套”，本文我们先分析 `AbstractAutoProxyCreator#postProcessBeforeInitialization` 方法，看看这个方法做了些什么，至于 `AbstractAutoProxyCreator#postProcessAfterInitialization` 方法，将留到下一篇文章分析。\n\n`AnnotationAwareAspectJAutoProxyCreator` 的 `postProcessBeforeInitialization` 方法继承自 `AbstractAutoProxyCreator`，`AbstractAutoProxyCreator#postProcessBeforeInitialization` 方法如下：\n\n```\npublic Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {\n    Object cacheKey = getCacheKey(beanClass, beanName);\n\n    if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {\n        if (this.advisedBeans.containsKey(cacheKey)) {\n            return null;\n        }\n        //1\\. 加载所有增强\n        if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {\n            this.advisedBeans.put(cacheKey, Boolean.FALSE);\n            return null;\n        }\n    }\n\n    // 2\\. 如果有自定义的TargetSource，则运行下面的方法来创建代理\n    TargetSource targetSource = getCustomTargetSource(beanClass, beanName);\n    if (targetSource != null) {\n        if (StringUtils.hasLength(beanName)) {\n            this.targetSourcedBeans.add(beanName);\n        }\n        Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);\n        Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);\n        this.proxyTypes.put(cacheKey, proxy.getClass());\n        return proxy;\n    }\n\n    return null;\n}\n\n```\n\n这个方法主要功能就是加载项目中的增强方法，处理代码如下：\n\n```\nif (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {\n     ...\n}\n\n```\n\n这段代码包含两个方法：`isInfrastructureClass(beanClass)` 与 `shouldSkip(beanClass, beanName)`，先来看 `isInfrastructureClass(beanClass)`\n\n#### 2.1 `isInfrastructureClass(beanClass)` 方法\n\n> AnnotationAwareAspectJAutoProxyCreator#isInfrastructureClass\n\n```\n@Override\nprotected boolean isInfrastructureClass(Class<?> beanClass) {\n     // 判断当前类是否为 Advice/Pointcut/Advisor/AopInfrastructureBean 的子类\n     return (super.isInfrastructureClass(beanClass) ||\n          // 判断当前beanClass是否为切面：包含有@Aspect注解， 且不由ajc编译\n         (this.aspectJAdvisorFactory != null && this.aspectJAdvisorFactory.isAspect(beanClass)));\n}\n\n```\n\n以上代码就两行，功能注释得很清楚了，简单来说就是判断是否为 aop 自身相关的类，如果是 aop 自身相关的类，就不进行切面操作了。\n\n#### 2.2 `shouldSkip(beanClass, beanName)` 方法\n\n接着，我们来看 `shouldSkip(beanClass, beanName)` 方法：\n\n> AspectJAwareAdvisorAutoProxyCreator#shouldSkip\n\n```\n@Override\nprotected boolean shouldSkip(Class<?> beanClass, String beanName) {\n    //查找所有标识了@Aspect注解的类，这个方法很重要 ，下面会继续分析\n    List<Advisor> candidateAdvisors = findCandidateAdvisors();\n    for (Advisor advisor : candidateAdvisors) {\n        // 如果当前 advisor 为 AspectJPointcutAdvisor 的实例且 AspectName 为 beanName，返回true\n        if (advisor instanceof AspectJPointcutAdvisor &&\n                ((AspectJPointcutAdvisor) advisor).getAspectName().equals(beanName)) {\n            return true;\n        }\n    }\n    // 调用父类的方法，判断当前 bean 是否为 OriginalInstance\n    return super.shouldSkip(beanClass, beanName);\n}\n\n```\n\n这个方法会先查找所有标识了 `@Aspect` 注解的类，然后做一些判断，其中 `findCandidateAdvisors` 很关键，我们来看看这个方法：\n\n> AnnotationAwareAspectJAutoProxyCreator#findCandidateAdvisors\n\n```\n@Override\nprotected List<Advisor> findCandidateAdvisors() {\n    // 调用父类的方法，查找当前beanFactory中的Advisor bean\n    List<Advisor> advisors = super.findCandidateAdvisors();\n    if (this.aspectJAdvisorsBuilder != null) {\n        // 将包含 @Aspect 注解的类构建为 Advisor，这句是关键\n        advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());\n    }\n    return advisors;\n}\n\n```\n\n首先来看下 `super.findCandidateAdvisors()` 方法，也就是 `AbstractAdvisorAutoProxyCreator#findCandidateAdvisors`:\n\n```\nprotected List<Advisor> findCandidateAdvisors() {\n    Assert.state(this.advisorRetrievalHelper != null, \"No BeanFactoryAdvisorRetrievalHelper available\");\n    return this.advisorRetrievalHelper.findAdvisorBeans();\n}\n\n```\n\n这个调用了 `BeanFactoryAdvisorRetrievalHelper#findAdvisorBeans`，继续下去：\n\n```\npublic List<Advisor> findAdvisorBeans() {\n    String[] advisorNames = this.cachedAdvisorBeanNames;\n    if (advisorNames == null) {\n        // 1\\. 查找当前beanFactory中所有 Advisor 的 bean class\n        advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(\n                this.beanFactory, Advisor.class, true, false);\n        this.cachedAdvisorBeanNames = advisorNames;\n    }\n    ...\n\n    List<Advisor> advisors = new ArrayList<>();\n    for (String name : advisorNames) {\n            ...\n            // 2\\. 从spring中获取 bean class 对应的 bean，将其放入advisors中\n            advisors.add(this.beanFactory.getBean(name, Advisor.class));\n            ...\n\n    }\n    return advisors;\n}\n\n```\n\n以上代码做了两件事：\n\n1.  查找当前 beanFactory 中所有 Advisor 的 bean class，Advisor 可以是用户实现 Advisor 相关接口，也可以是 xml 指定的\n2.  从 spring 中获取 bean class 对应的 bean，将其放入 advisors 中\n\n#### 2.3 `aspectJAdvisorsBuilder.buildAspectJAdvisors()` 方法\n\n接着我们再回过头来看看 `aspectJAdvisorsBuilder.buildAspectJAdvisors())` 方法 ：\n\n> BeanFactoryAspectJAdvisorsBuilder#buildAspectJAdvisors\n\n```\npublic List<Advisor> buildAspectJAdvisors() {\n    List<String> aspectNames = this.aspectBeanNames;\n\n    // 只有在第一次运行时，才会成立\n    if (aspectNames == null) {\n        synchronized (this) {\n            aspectNames = this.aspectBeanNames;\n            if (aspectNames == null) {\n                List<Advisor> advisors = new ArrayList<>();\n                aspectNames = new ArrayList<>();\n                //1\\. 获取所有Bean名称\n                String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(\n                        this.beanFactory, Object.class, true, false);\n                for (String beanName : beanNames) {\n                    ...\n                    //2\\. 判断Bean的Class上是否标识@Aspect注解\n                    if (this.advisorFactory.isAspect(beanType)) {\n                        aspectNames.add(beanName);\n                        AspectMetadata amd = new AspectMetadata(beanType, beanName);\n                        if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {\n                            // 创建factory对象，这个对象里包含了beanFactory与@Aspect的beanName\n                            MetadataAwareAspectInstanceFactory factory =\n                                new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);\n                            // 3\\. 解析所有的增强方法, 这个方法是重点的重点\n                            List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);\n                            if (this.beanFactory.isSingleton(beanName)) {\n                                //将解析的Bean名称及类上的增强缓存起来,每个Bean只解析一次\n                                this.advisorsCache.put(beanName, classAdvisors);\n                            }\n                            ...\n                        }\n                        else {\n                            ...\n                        }\n                    }\n                }\n                // 对属性赋值，之后就不会再运行了\n                this.aspectBeanNames = aspectNames;\n                return advisors;\n            }\n        }\n    }\n\n    if (aspectNames.isEmpty()) {\n        return Collections.emptyList();\n    }\n    List<Advisor> advisors = new ArrayList<>();\n    for (String aspectName : aspectNames) {\n        //从缓存中获取当前Bean的切面实例，如果不为空，则指明当前Bean的Class标识了@Aspect，且有切面方法\n        List<Advisor> cachedAdvisors = this.advisorsCache.get(aspectName);\n        if (cachedAdvisors != null) {\n            advisors.addAll(cachedAdvisors);\n        }\n        else {\n            MetadataAwareAspectInstanceFactory factory = this.aspectFactoryCache.get(aspectName);\n            advisors.addAll(this.advisorFactory.getAdvisors(factory));\n        }\n    }\n    return advisors;\n}\n\n```\n\n方法虽然有点长，不过只做了 3 件事：\n\n1.  获取所有 beanName：`BeanFactoryUtils.beanNamesForTypeIncludingAncestors`\n2.  找出所有标记 Aspect 注解的类：`advisorFactory.isAspect(beanType)`\n3.  对标记 Aspect 的类提取增强器：`this.advisorFactory.getAdvisors(factory)`\n\n这里我们重点分析第 3 点，看看 `Advisors` 是如何构建的。\n\n> ReflectiveAspectJAdvisorFactory#getAdvisors\n\n```\npublic List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) {\n    //获取Aspect类、类名称、并校验\n    // aspectInstanceFactory 里包含beanFactory与 @Aspect 的 beanName\n    Class<?> aspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();\n    String aspectName = aspectInstanceFactory.getAspectMetadata().getAspectName();\n    //校验类的合法性相关\n    validate(aspectClass);\n\n    MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory =\n                  new LazySingletonAspectInstanceFactoryDecorator(aspectInstanceFactory);\n    List<Advisor> advisors = new ArrayList<>();\n    // 获取这个类除去 @Pointcut 外的所有方法，看下面的分析\n    for (Method method : getAdvisorMethods(aspectClass)) {\n          // 生成增强实例，这个 方法是重点，看后面的分析\n          // 注意 advisors.size()，这个指定了advisor的顺序，由于获取之后会个添加\n          // 操作，因此这个值是递增且是不重复的\n          Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, \n                    advisors.size(), aspectName);\n          if (advisor != null) {\n              // 添加 advisors 列表\n              advisors.add(advisor);\n          }\n    }\n    // 如果需要增强且配置了延迟增强,则在第一个位置添加同步实例化增强方法\n    if (!advisors.isEmpty() && lazySingletonAspectInstanceFactory\n                    .getAspectMetadata().isLazilyInstantiated()) {\n        Advisor instantiationAdvisor = new SyntheticInstantiationAdvisor(\n                    lazySingletonAspectInstanceFactory);\n        advisors.add(0, instantiationAdvisor);\n    }\n    // 获取属性中配置DeclareParents注解的增强\n    for (Field field : aspectClass.getDeclaredFields()) {\n        Advisor advisor = getDeclareParentsAdvisor(field);\n        if (advisor != null) {\n              advisors.add(advisor);\n        }\n    }\n    return advisors;\n}\n\n/**\n * 获取指定类除@Pointcut外的所有方法\n */\nprivate List<Method> getAdvisorMethods(Class<?> aspectClass) {\n    final List<Method> methods = new ArrayList<>();\n    // 递归操作 ：排除@Pointcut标识的方法，获取当前类的的方法、来自接口的默认方法，再对父类进行同样的操作\n    // 最终结果：除@Pointcut标识之外的所有方法，得到的方法集合包括：\n    // 1\\. 继承自父类的方法，\n    // 2\\. 来自自接口的默认的方法\n    // 3\\. 不包含继承自Object的方法\n    ReflectionUtils.doWithMethods(aspectClass, method -> {\n        if (AnnotationUtils.getAnnotation(method, Pointcut.class) == null) {\n            methods.add(method);\n        }\n    }, ReflectionUtils.USER_DECLARED_METHODS);\n    //对得到的所有方法排序，\n    //如果方法标识了切面注解，则按@Around, @Before, @After, @AfterReturning, @AfterThrowing的顺序排序\n    //如果没有标识这些注解，则按方法名称的字符串排序,\n    //有注解的方法排在无注解的方法之前\n    //最后的排序应该是这样的Around, Before, After, AfterReturning, AfterThrowing\n    methods.sort(METHOD_COMPARATOR);\n    return methods;\n}\n\n```\n\n以上方法主要做了两件事：\n\n1.  获取当前类除 `@Pointcut` 外的所有增强方法，包括继承自父类的方法，继承自 Object 的方法，以及来自接口的默认方法\n2.  遍历得到的方法，从符合条件的方法得到 Advisor 实例，保存到集合中\n\n关于第一步，上面的代码已经分析了，主要是根据反射来进行的，就不再深入了，这里我们来看第二步，该操作是在 `ReflectiveAspectJAdvisorFactory#getAdvisor` 方法中：\n\n```\n@Override\n@Nullable\n/**\n * declarationOrderInAspect：指定了Advisor的顺序，来源于 AdvisorMethods 的排序，上面已分析\n */\npublic Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory \n        aspectInstanceFactory, int declarationOrderInAspect, String aspectName) {\n    //再次校验类的合法性\n    validate(aspectInstanceFactory.getAspectMetadata().getAspectClass());\n    //切点表达式的包装类里面包含这些东西：@Around(\"testAop()\") 里的 testAop()\n    AspectJExpressionPointcut expressionPointcut = getPointcut(\n            candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass());\n    if (expressionPointcut == null) {\n        return null;\n    }\n    //根据方法、切点、AOP实例工厂、类名、序号生成切面实例，下面我们先来看看是如何实例化的\n    return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod,\n               this, aspectInstanceFactory, declarationOrderInAspect, aspectName);\n}\n\n/**\n * 处理切点表达式\n */\n@Nullable\nprivate AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, \n               Class<?> candidateAspectClass) {\n    // 查询方法上的切面注解，根据注解生成相应类型的AspectJAnnotation,\n    // 在调用AspectJAnnotation的构造函数的同时，根据注解value或pointcut属性得到切点表达式，\n    // 有argNames则设置参数名称\n    AspectJAnnotation<?> aspectJAnnotation =\n               AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);\n    // 过滤那些不含@Before, @Around, @After, @AfterReturning, @AfterThrowing注解的方法\n    if (aspectJAnnotation == null) {\n        return null;\n    }\n\n    //生成带表达式的切面切入点，设置其切入点表达式\n    AspectJExpressionPointcut ajexp =\n               new AspectJExpressionPointcut(candidateAspectClass, new String[0], new Class<?>[0]);\n    ajexp.setExpression(aspectJAnnotation.getPointcutExpression());\n    if (this.beanFactory != null) {\n        ajexp.setBeanFactory(this.beanFactory);\n    }\n    return ajexp;\n}\n\n```\n\n`Advisor` 实例化过程如下：\n\n```\npublic InstantiationModelAwarePointcutAdvisorImpl(AspectJExpressionPointcut declaredPointcut,\n        Method aspectJAdviceMethod, AspectJAdvisorFactory aspectJAdvisorFactory,\n        MetadataAwareAspectInstanceFactory aspectInstanceFactory,\n        int declarationOrder, String aspectName) {\n    // 处理属性设置\n    this.declaredPointcut = declaredPointcut;\n    this.declaringClass = aspectJAdviceMethod.getDeclaringClass();\n    this.methodName = aspectJAdviceMethod.getName();\n    this.parameterTypes = aspectJAdviceMethod.getParameterTypes();\n    this.aspectJAdviceMethod = aspectJAdviceMethod;\n    this.aspectJAdvisorFactory = aspectJAdvisorFactory;\n    this.aspectInstanceFactory = aspectInstanceFactory;\n    this.declarationOrder = declarationOrder;\n    this.aspectName = aspectName;\n\n    if (aspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {\n        ...\n    }\n    else {\n        this.pointcut = this.declaredPointcut;\n        this.lazy = false;\n        //初始化对应的增强器，这里是重点\n        this.instantiatedAdvice = instantiateAdvice(this.declaredPointcut);\n    }\n}\n\n/**\n * 实例化过程\n */\nprivate Advice instantiateAdvice(AspectJExpressionPointcut pointcut) {\n    // getAdvice: 处理 Advice 实例化操作\n    Advice advice = this.aspectJAdvisorFactory.getAdvice(this.aspectJAdviceMethod, pointcut,\n            this.aspectInstanceFactory, this.declarationOrder, this.aspectName);\n    return (advice != null ? advice : EMPTY_ADVICE);\n}\n\n```\n\n这段代码清晰明了，主要是实例化 `Advisor`，所谓的实例化，就是往 `Advisor` 对象里设置了一些属性。这里 `instantiatedAdvice` 属性需要特别说明一下，这个属性封装了切面方法，也就是增强的内容。\n\n*   切面方法 (切面具体的执行代码) 封装为 `Advice`；\n*   `Advice` 是 `Advisor` 的一个属性。\n\n#### 2.4 实例化 `Advice`\n\n接下来看看 `Advice` 的流程：\n\n> ReflectiveAspectJAdvisorFactory#getAdvice\n\n```\n@Override\n@Nullable\npublic Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut expressionPointcut,\n        MetadataAwareAspectInstanceFactory aspectInstanceFactory,\n        int declarationOrder, String aspectName) {\n\n    Class<?> candidateAspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();\n    //又是一次校验\n    validate(candidateAspectClass);\n\n    AspectJAnnotation<?> aspectJAnnotation =\n            AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);\n    if (aspectJAnnotation == null) {\n        return null;\n    }\n\n    if (!isAspect(candidateAspectClass)) {\n        throw new AopConfigException(...);\n    }\n\n    AbstractAspectJAdvice springAdvice;\n    // 根据注解类型生成不同的通知实例，\n    // 对应的注解就是 @Before, @Around, @After, @AfterReturning, @AfterThrowing\n    switch (aspectJAnnotation.getAnnotationType()) {\n        case AtPointcut:\n            return null;\n        case AtAround:\n            springAdvice = new AspectJAroundAdvice(\n                    candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);\n            break;\n        case AtBefore:\n            springAdvice = new AspectJMethodBeforeAdvice(\n                    candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);\n            break;\n        case AtAfter:\n            springAdvice = new AspectJAfterAdvice(\n                    candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);\n            break;\n        case AtAfterReturning:\n            springAdvice = new AspectJAfterReturningAdvice(\n                    candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);\n            AfterReturning afterReturningAnnotation = (AfterReturning) aspectJAnnotation.getAnnotation();\n            if (StringUtils.hasText(afterReturningAnnotation.returning())) {\n                springAdvice.setReturningName(afterReturningAnnotation.returning());\n            }\n            break;\n        case AtAfterThrowing:\n            springAdvice = new AspectJAfterThrowingAdvice(\n                   candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);\n            AfterThrowing afterThrowingAnnotation = (AfterThrowing) aspectJAnnotation.getAnnotation();\n            if (StringUtils.hasText(afterThrowingAnnotation.throwing())) {\n                springAdvice.setThrowingName(afterThrowingAnnotation.throwing());\n            }\n            break;\n        default:\n            throw new UnsupportedOperationException(...);\n    }\n\n    //设置通知方法所属的类\n    springAdvice.setAspectName(aspectName);\n    //设置通知的序号,同一个类中有多个切面注解标识的方法时,按上方说的排序规则来排序，\n    //其序号就是此方法在列表中的序号，第一个就是0\n    springAdvice.setDeclarationOrder(declarationOrder);\n    //获取通知方法的所有参数\n    String[] argNames = this.parameterNameDiscoverer.getParameterNames(candidateAdviceMethod);\n    //将通知方法上的参数设置到通知中\n    if (argNames != null) {\n        springAdvice.setArgumentNamesFromStringArray(argNames);\n    }\n    //计算参数绑定工作，此方法详解请接着往下看\n    springAdvice.calculateArgumentBindings();\n    return springAdvice;\n}\n\n```\n\n我们知道，切面注解标识的方法第一个参数要求是 `JoinPoint`，如果是 `@Around` 注解，则第一个参数可以是 `ProceedingJoinPoint`，计算参数绑定就是来验证参数类型的。\n\n对于不同的通知，spring 会封装成不同的 `advice`，这里先来看看 `advice` 的继承结构：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-671bd92a16642a059ff722ec42c8ea7ef28.png)\n\n关于 `advice`，后面在分析切面的执行时会详细分析，这里只需知道 “不同的切面类型，最终会封装为不同的 advice” 即可。\n\n再来看看 spring 计算参数绑定的流程：\n\n> AbstractAspectJAdvice\n\n```\n/**\n * 处理参数绑定\n */\npublic final synchronized void calculateArgumentBindings() {\n    if (this.argumentsIntrospected || this.parameterTypes.length == 0) {\n        return;\n    }\n\n    int numUnboundArgs = this.parameterTypes.length;\n    Class<?>[] parameterTypes = this.aspectJAdviceMethod.getParameterTypes();\n    //切面注解标识的方法第一个参数要求是JoinPoint,或StaticPart，若是@Around注解则也可以是ProceedingJoinPoint\n    if (maybeBindJoinPoint(parameterTypes[0]) || maybeBindProceedingJoinPoint(parameterTypes[0]) ||\n            maybeBindJoinPointStaticPart(parameterTypes[0])) {\n        numUnboundArgs--;\n    }\n\n    if (numUnboundArgs > 0) {\n        //绑定属性其他属性\n        bindArgumentsByName(numUnboundArgs);\n    }\n\n    this.argumentsIntrospected = true;\n}\n\n```\n\n以上代码验证了切面注解标识的方法第一个参数的类型，此外，如果在方法中定义了多个方法，spring 还会进一步进行参数绑定：\n\n> AbstractAspectJAdvice\n\n```\n/**\n  * 绑定属性其他属性\n  */\nprivate void bindArgumentsByName(int numArgumentsExpectingToBind) {\n    if (this.argumentNames == null) {\n        //获取方法参数的名称\n        this.argumentNames = createParameterNameDiscoverer()\n                .getParameterNames(this.aspectJAdviceMethod);\n    }\n    if (this.argumentNames != null) {\n        // 继续绑定\n        bindExplicitArguments(numArgumentsExpectingToBind);\n    }\n    else {\n        throw new IllegalStateException(...);\n    }\n}\n\n/**\n * 针对 @AfterReturning 与 @AfterThrowing 注解，进一步处理其参数\n */\nprivate void bindExplicitArguments(int numArgumentsLeftToBind) {\n    Assert.state(this.argumentNames != null, \"No argument names available\");\n    //此属性用来存储方法未绑定的参数名称，及参数的序号\n    this.argumentBindings = new HashMap<>();\n\n    int numExpectedArgumentNames = this.aspectJAdviceMethod.getParameterCount();\n    if (this.argumentNames.length != numExpectedArgumentNames) {\n        throw new IllegalStateException(...);\n    }\n\n    // argumentIndexOffset代表第一个未绑定参数的顺序\n    int argumentIndexOffset = this.parameterTypes.length - numArgumentsLeftToBind;\n    for (int i = argumentIndexOffset; i < this.argumentNames.length; i++) {\n        //存储未绑定的参数名称及其顺序的映射关系\n        this.argumentBindings.put(this.argumentNames[i], i);\n    }\n\n    // 如果是@AfterReturning注解的returningName 有值，验证，解析，同时得到定义返回值的类型\n    if (this.returningName != null) {\n        if (!this.argumentBindings.containsKey(this.returningName)) {\n            throw new IllegalStateException(...);\n        }\n        else {\n            Integer index = this.argumentBindings.get(this.returningName);\n            this.discoveredReturningType = this.aspectJAdviceMethod.getParameterTypes()[index];\n            this.discoveredReturningGenericType = this.aspectJAdviceMethod\n                        .getGenericParameterTypes()[index];\n        }\n    }\n    // 如果是@AfterThrowing注解的throwingName 有值，验证，解析，同时得到抛出异常的类型\n    if (this.throwingName != null) {\n        if (!this.argumentBindings.containsKey(this.throwingName)) {\n            throw new IllegalStateException(...);\n        }\n        else {\n            Integer index = this.argumentBindings.get(this.throwingName);\n            this.discoveredThrowingType = this.aspectJAdviceMethod.getParameterTypes()[index];\n        }\n    }\n\n    configurePointcutParameters(this.argumentNames, argumentIndexOffset);\n}\n\n/**\n *  这一步仅是将前面未处理过的参数做一个保存，记录到 pontcut 中，待后续再做处理\n */\nprivate void configurePointcutParameters(String[] argumentNames, int argumentIndexOffset) {\n    int numParametersToRemove = argumentIndexOffset;\n    if (this.returningName != null) {\n        numParametersToRemove++;\n    }\n    if (this.throwingName != null) {\n        numParametersToRemove++;\n    }\n    String[] pointcutParameterNames = new String[argumentNames.length - numParametersToRemove];\n    Class<?>[] pointcutParameterTypes = new Class<?>[pointcutParameterNames.length];\n    Class<?>[] methodParameterTypes = this.aspectJAdviceMethod.getParameterTypes();\n\n    int index = 0;\n    for (int i = 0; i < argumentNames.length; i++) {\n        if (i < argumentIndexOffset) {\n            continue;\n        }\n        if (argumentNames[i].equals(this.returningName) ||\n            argumentNames[i].equals(this.throwingName)) {\n            continue;\n        }\n        pointcutParameterNames[index] = argumentNames[i];\n        pointcutParameterTypes[index] = methodParameterTypes[i];\n        index++;\n    }\n    //剩余的未绑定的参数会赋值给AspectJExpressionPointcut(表达式形式的切入点)的属性，以备后续使用\n    this.pointcut.setParameterNames(pointcutParameterNames);\n    this.pointcut.setParameterTypes(pointcutParameterTypes);\n}\n\n```\n\n### 3\\. 总结\n\n本文主要是分析了 `AnnotationAwareAspectJAutoProxyCreator#postProcessBeforeInitialization` 方法，也就是 `AbstractAutoProxyCreator#postProcessBeforeInitialization` 方法，在该方法的运行过程中，主要是将 `@Aspect` 类封装成一个个 `Advisor`，封装步骤如下：\n\n1.  找到项目中标注了 `@Aspect` 的类；\n2.  遍历上一步得到的类，通过反射得到该类的方法，包括父接口的默认方法、父类的方法但不包括 Object 类的方法；\n3.  从上述方法中，找到标注了 `@Around`, `@Before`, `@After`, `@AfterReturning`, `@AfterThrowing` 等的方法，将其封装为 `Advisor` 对象。\n\n关于 `Advisor`，其中有个重要属性为 `Advice`，这个属性就是切面方法的具体内容。关于 `Advice`，在后面分析切面的执行时会详细分析。\n\n* * *\n\n_本文原文链接：[https://my.oschina.net/funcy/blog/4678817](https://my.oschina.net/funcy/blog/4678817) ，限于作者个人水平，文中难免有错误之处，欢迎指正！原创不易，商业转载请联系作者获得授权，非商业转载请注明出处。_"
  },
  {
    "path": "docs/Spring全家桶/Spring源码分析/SpringAOP/AnnotationAwareAspectJAutoProxyCreator分析（下）.md",
    "content": "[һƪ](https://my.oschina.net/funcy/blog/4678817 \"һƪ\")Ҫ `AbstractAutoProxyCreator#postProcessAfterInitialization`  `AbstractAutoProxyCreator#postProcessAfterInitialization` \n\nĵ\n\n```\n|-AnnotationConfigApplicationContext\n |-AbstractApplicationContext#refresh\n  |-AbstractApplicationContext#finishBeanFactoryInitialization\n   |-ConfigurableListableBeanFactory#preInstantiateSingletons\n    |-AbstractBeanFactory#getBean(String)\n     |-AbstractBeanFactory#doGetBean\n      |-DefaultSingletonBeanRegistry#getSingleton(String, ObjectFactory<?>)\n       |-ObjectFactory#getObject\n        |-AbstractBeanFactory#createBean\n         |-AbstractAutowireCapableBeanFactory#doCreateBean\n          |-AbstractAutowireCapableBeanFactory#initializeBean(String, Object, RootBeanDefinition)\n           |-AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsAfterInitialization\n            |-AbstractAutoProxyCreator#postProcessAfterInitialization\n\n```\n\nʵĵ spring bean Ĵ̣ǽ `AbstractAutoProxyCreator#postProcessAfterInitialization`\n\n```\n@Override\npublic Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {\n    if (bean != null) {\n        Object cacheKey = getCacheKey(bean.getClass(), beanName);\n        if (this.earlyProxyReferences.remove(cacheKey) != bean) {\n           // wrapIfNecessary()\n           return wrapIfNecessary(bean, beanName, cacheKey);\n        }\n    }\n    return bean;\n}\n\n```\n\n `AbstractAutoProxyCreator#wrapIfNecessary`\n\n```\nprotected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {\n    //Ѿ\n    if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {\n        return bean;\n    }\n    //ǰǿ࣬\n    if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {\n        return bean;\n    }\n\n    // ҪһжϵǰǷΪ࣬ôһƪѾˣͲ˵\n    if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {\n        this.advisedBeans.put(cacheKey, Boolean.FALSE);\n        return bean;\n    }\n\n    // ҪУǷӦñȡǿ\n    Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);\n    //ȡǿҪǿ\n    if (specificInterceptors != DO_NOT_PROXY) {\n        this.advisedBeans.put(cacheKey, Boolean.TRUE);\n        // Ҫ\n        Object proxy = createProxy(\n            bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));\n        this.proxyTypes.put(cacheKey, proxy.getClass());\n        return proxy;\n    }\n\n    this.advisedBeans.put(cacheKey, Boolean.FALSE);\n    return bean;\n}\n\n```\n\n е㳤붼жϣ aop ܹϵйϵĴֻУ\n\n```\n// Ҫһ\n// 1\\. isInfrastructureClassжϵǰǷΪaop࣬\n//    Advice/Pointcut/Advisorȵ࣬Ƿ @AspectJע\n// 2\\. shouldSkip࣬жǷų\nif (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {\n    ...\n}\n\n// ҪУǷӦñȡǿ\nObject[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);\n\n// Ҫ\nObject proxy = createProxy(\n    bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));\n\n```\n\n`Ҫһ`һƪѾҪ`Ҫ``Ҫ`\n\n### 1\\. ȡǿ\n\n> AbstractAdvisorAutoProxyCreator\n\n```\n@Override\n@Nullable\nprotected Object[] getAdvicesAndAdvisorsForBean(\n        Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {\n    // ҷǿ¿\n    List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);\n    if (advisors.isEmpty()) {\n        return DO_NOT_PROXY;\n    }\n    return advisors.toArray();\n}\n\n/**\n * ҷǿ\n */\nprotected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {\n    //ȡеǿһƪѾ\n    List<Advisor> candidateAdvisors = findCandidateAdvisors();\n    //֤beanClassǷñӦã򷵻beanǿ\n    List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);\n    extendAdvisors(eligibleAdvisors);\n    if (!eligibleAdvisors.isEmpty()) {\n        eligibleAdvisors = sortAdvisors(eligibleAdvisors);\n    }\n    return eligibleAdvisors;\n}\n\n/**\n * ֤beanClassǷñ\n */\nprotected List<Advisor> findAdvisorsThatCanApply(\n        List<Advisor> candidateAdvisors, Class<?> beanClass, String beanName) {\n    ProxyCreationContext.setCurrentProxiedBeanName(beanName);\n    try {\n        // ֤beanClassǷñ\n        return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);\n    }\n    finally {\n        ProxyCreationContext.setCurrentProxiedBeanName(null);\n    }\n}\n\n```\n\nspring ķñȽһ·׷٣յ `AopUtils.findAdvisorsThatCanApply` ¿\n\n> AopUtils\n\n```\npublic static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {\n    if (candidateAdvisors.isEmpty()) {\n        return candidateAdvisors;\n    }\n    List<Advisor> eligibleAdvisors = new ArrayList<>();\n    //  candidateAdvisorsжǷ\n    for (Advisor candidate : candidateAdvisors) {\n        //ǿص㣬¿\n        if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {\n            eligibleAdvisors.add(candidate);\n        }\n    }\n    boolean hasIntroductions = !eligibleAdvisors.isEmpty();\n    for (Advisor candidate : candidateAdvisors) {\n        if (candidate instanceof IntroductionAdvisor) {\n            // already processed\n            continue;\n        }\n        //ͨbeanĴ\n        if (canApply(candidate, clazz, hasIntroductions)) {\n            eligibleAdvisors.add(candidate);\n        }\n    }\n    return eligibleAdvisors;\n}\n\n/**\n * жǷҪǿ\n */\npublic static boolean canApply(Advisor advisor, Class<?> targetClass) {\n    // һ\n    return canApply(advisor, targetClass, false);\n}\n\n/**\n * жǷҪǿ\n */\npublic static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) {\n    //ų\n    if (advisor instanceof IntroductionAdvisor) {\n        return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);\n    }\n    else if (advisor instanceof PointcutAdvisor) {\n        PointcutAdvisor pca = (PointcutAdvisor) advisor;\n        //÷\n        return canApply(pca.getPointcut(), targetClass, hasIntroductions);\n    }\n    else {\n        // It doesn't have a pointcut so we assume it applies.\n        return true;\n    }\n}\n\n/**\n * жǷҪǿ\n */\npublic static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {\n    Assert.notNull(pc, \"Pointcut must not be null\");\n    //еǷų\n    if (!pc.getClassFilter().matches(targetClass)) {\n        return false;\n    }\n    //֤עǷڷ\n    MethodMatcher methodMatcher = pc.getMethodMatcher();\n    if (methodMatcher == MethodMatcher.TRUE) {\n        // No need to iterate the methods if we're matching any method anyway...\n        return true;\n    }\n\n    IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;\n    if (methodMatcher instanceof IntroductionAwareMethodMatcher) {\n        introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;\n    }\n\n    // classestargetClassObjectиࡢнӿ\n    Set<Class<?>> classes = new LinkedHashSet<>();\n    if (!Proxy.isProxyClass(targetClass)) {\n        classes.add(ClassUtils.getUserClass(targetClass));\n    }\n    classes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetClass));\n\n    // ѭжϷǷҪ\n    // Կ\n    // 1\\. ֻҪһҪôͻᱻ\n    // 2\\. зҪôҲᱻ\n    for (Class<?> clazz : classes) {\n         // ȡ clazz ķ\n         // ǰķObjectи෽ӿڵĬϷ\n        Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);\n        for (Method method : methods) {\n            //ȡʵֵнӿں㼶ķѭ֤\n            if (introductionAwareMethodMatcher != null ?\n                introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) :\n                methodMatcher.matches(method, targetClass)) {\n                return true;\n            }\n        }\n    }\n\n    return false;\n}\n\n```\n\nϾ˽ spring жһǷҪģܽ£\n\n1.  ȡĿе󣬽е淽װΪһ `List<Advisor>`һƪϸ\n2.   `Advisor`ÿһ `Advisor`÷ȡǰĳ `Object` и༰ӿڣΪ `Set<Class>`\n3.   `Set<Class>`ÿһ `Class`÷ȡ `Class`  Object иķӿڵĬϷΪ `Method[]`;\n4.   `Method[]`һ `method`  `Advisor` ʾǰ `Advisor` Ӧõǰ bean bean ҪһյõĽҲһ `List<Advisor>`ʾж `Advisor` ҪӦõö\n\nαڣ\n\n```\n// 1\\. ȡеAdvisor\nList<Advisor> advisorList = getAdvisorList();\n// 2\\. Advisor\nfor(Advisor advisor : advisorList) {\n    // ȡǰĳ`Object`и༰ӿڣclassSetҲtargetClass\n    Set<Class> classSet = getSuperClassAndInterfaces(targetClass);\n    for(Class cls : classSet) {\n        // clsжķǰķObjectи෽ӿڵĬϷ\n        Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);\n        //  Щ\n        for (Method method : methods) {\n             // жmethodǷ\n        }\n    }\n}\n\n```\n\nõ `List<Advisor>` 󣬽Ǹ `List<Advisor>` ˡ\n\n### 2\\. \n\n spring ̡\n\n> `AbstractAutoProxyCreator#wrapIfNecessary`\n\n```\nprotected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {\n    ...\n    if (specificInterceptors != DO_NOT_PROXY) {\n        this.advisedBeans.put(cacheKey, Boolean.TRUE);\n        // ﴴ\n        // - specificInterceptorsӦõö  Advisor\n        //    specificInterceptorsĻȡ̣һѾϸˣ׸\n        // - SingletonTargetSourceԭʼһװ\n        Object proxy = createProxy(\n            bean.getClass(), beanName, specificInterceptors, \n                        new SingletonTargetSource(bean));\n        this.proxyTypes.put(cacheKey, proxy.getClass());\n        return proxy;\n    }\n    this.advisedBeans.put(cacheKey, Boolean.FALSE);\n    return bean;\n}\n\n```\n\n `SingletonTargetSource`\n\n```\npublic class SingletonTargetSource implements TargetSource, Serializable {\n\n    private static final long serialVersionUID = 9031246629662423738L;\n\n    private final Object target;\n\n    public SingletonTargetSource(Object target) {\n        Assert.notNull(target, \"Target object must not be null\");\n        this.target = target;\n    }\n\n    @Override\n    public Class<?> getTargetClass() {\n        return this.target.getClass();\n    }\n\n    @Override\n    public Object getTarget() {\n        return this.target;\n    }\n\n    ...\n}\n\n```\n\nܼ򵥣Ƕԭʼһװ¿\n\n> AbstractAutoProxyCreator#createProxy\n\n```\nprotected Object createProxy(Class<?> beanClass, @Nullable String beanName,\n        @Nullable Object[] specificInterceptors, TargetSource targetSource) {\n\n    if (this.beanFactory instanceof ConfigurableListableBeanFactory) {\n        AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) \n            this.beanFactory, beanName, beanClass);\n    }\n\n    ProxyFactory proxyFactory = new ProxyFactory();\n    //ʹproxyFactorycopyǰе\n    proxyFactory.copyFrom(this);\n\n    // жǷʹCglib̬עָ\n    // @EnableAspectJAutoProxy(proxyTargetClass = true)\n    if (!proxyFactory.isProxyTargetClass()) {\n        //  beanFactory  ConfigurableListableBeanFactory\n        //  BeanDefinition ԣĳһʹ cglib \n        if (shouldProxyTargetClass(beanClass, beanName)) {\n            proxyFactory.setProxyTargetClass(true);\n        }\n        else {\n            // ûÿ, жbeanǷкʵĽӿʹJDKĶ̬\n            // ע⣺JDK̬Ǵнӿڵ\n            // ûʵκνӿֻʹCglib̬\n            evaluateProxyInterfaces(beanClass, proxyFactory);\n        }\n    }\n\n    // Advisor\n    // 1\\. ӹ Interceptor\n    // 2\\. ڸadvisorжͣȻתΪ\n    Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);\n    //ǿ\n    proxyFactory.addAdvisors(advisors);\n    //Ҫ\n    proxyFactory.setTargetSource(targetSource);\n    //Springһչ㣬ĬʵΪաҪԴʱʵ\n    customizeProxyFactory(proxyFactory);\n\n    proxyFactory.setFrozen(this.freezeProxy);\n    if (advisorsPreFiltered()) {\n        proxyFactory.setPreFiltered(true);\n    }\n    //ʹôȡ\n    return proxyFactory.getProxy(getProxyClassLoader());\n}\n\n```\n\n `@EnableAspectJAutoProxy` עУʹ `proxyTargetClass = true` Ŀʹ `cglib` ڴҲ֣\n\n```\n// ֻproxyFactory.isProxyTargetClass()ΪfalseʱŻж\n// ֮ @EnableAspectJAutoProxy(proxyTargetClass = true) ʱ\n// ĴǲеģĬʹþcglib\nif (!proxyFactory.isProxyTargetClass()) {\n    // жû BeanDefinition ʹ cglib\n    if (shouldProxyTargetClass(beanClass, beanName)) {\n        proxyFactory.setProxyTargetClass(true);\n    }\n    else {\n        // ǷӿڵǷjdk̬\n        evaluateProxyInterfaces(beanClass, proxyFactory);\n    }\n}\n\n```\n\nspring жһǷ jdk ̬أ֪˵ʵ˽ӿڣͿʹöֻ̬ʹ cglib  spring жϵģ\n\n> ProxyProcessorSupport#evaluateProxyInterfaces\n\n```\n/**\n * жǷʹjdk̬\n */\nprotected void evaluateProxyInterfaces(Class<?> beanClass, ProxyFactory proxyFactory) {\n    // ȡнӿ\n    Class<?>[] targetInterfaces = ClassUtils.getAllInterfacesForClass(beanClass, getProxyClassLoader());\n    boolean hasReasonableProxyInterface = false;\n    for (Class<?> ifc : targetInterfaces) {\n        // 1.isConfigurationCallbackInterface: жifcǷΪInitializingBeanDisposableBean\n        //   CloseableAutoCloseableԼ Aware\n        // 2.isInternalLanguageInterface: ǷΪڲԽӿڣgroovymock\n        // 3.ifc.getMethods().length > 0ӿڵķ1\n        if (!isConfigurationCallbackInterface(ifc) && !isInternalLanguageInterface(ifc) &&\n                ifc.getMethods().length > 0) {\n            hasReasonableProxyInterface = true;\n            break;\n        }\n    }\n    if (hasReasonableProxyInterface) {\n         // ҪеĽӿڶõproxyFactory\n         // һ£һA ʵ˽ӿ I1  I2\n         // AĶa  йܵspringôʹ beanFactory.get(I1.class)\n         //  beanFactory.get(I1.class)Ӧܻȡa.\n         for (Class<?> ifc : targetInterfaces) {\n             proxyFactory.addInterface(ifc);\n         }\n    }\n    else {\n        proxyFactory.setProxyTargetClass(true);\n    }\n}\n\n```\n\nԴspring жǷʹ jdk ̬Ĺ֪ϵĲ࣬ʵӿھʹ jdk ̬spring ų `InitializingBean``DisposableBean``Closeable``AutoCloseable` ȽӿڣͬʱҲųκηĽӿڡ\n\n spring жǷʹ jdk ̬󣬽ӿ spring δġΪ˵⣬ȼ `AbstractAutoProxyCreator#createProxy`\n\n```\nprotected Object createProxy(Class<?> beanClass, @Nullable String beanName,\n        @Nullable Object[] specificInterceptors, TargetSource targetSource) {\n    // ʡһЩ\n    ...\n    ProxyFactory proxyFactory = new ProxyFactory();\n    //ʹproxyFactorycopyǰе\n    proxyFactory.copyFrom(this);\n    // ʡ˺öж\n    proxyFactory.setProxyTargetClass(true);\n    //ǿ\n    proxyFactory.addAdvisors(advisors);\n    //Ҫ\n    proxyFactory.setTargetSource(targetSource);\n    ...\n    //ʹôȡ\n    return proxyFactory.getProxy(getProxyClassLoader());\n}\n\n```\n\nԿһ `ProxyFactory` ȻöһЩֵ¿\n\n> ProxyFactory#getProxy(java.lang.ClassLoader)\n\n```\npublic Object getProxy(@Nullable ClassLoader classLoader) {\n    return createAopProxy().getProxy(classLoader);\n}\n\n```\n\n`createAopProxy()`  `getProxy(classLoader)` `createAopProxy()`\n\n> ProxyCreatorSupport#createAopProxy\n\n```\nprotected final synchronized AopProxy createAopProxy() {\n    if (!this.active) {\n         activate();\n    }\n    return getAopProxyFactory().createAopProxy(this);\n}\n\n```\n\n\n\n> DefaultAopProxyFactory#createAopProxy\n\n```\n/**\n * жϴ\n * ʹjdk̬ͷ JdkDynamicAopProxy\n * ͷ ObjenesisCglibAopProxy\n */\npublic AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {\n    if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {\n        Class<?> targetClass = config.getTargetClass();\n        if (targetClass == null) {\n            throw new AopConfigException(...);\n        }\n        if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {\n            return new JdkDynamicAopProxy(config);\n        }\n        return new ObjenesisCglibAopProxy(config);\n    }\n    else {\n        return new JdkDynamicAopProxy(config);\n    }\n}\n\n```\n\nǾף`JdkDynamicAopProxy`  jdk ̬ģ`ObjenesisCglibAopProxy`  cglib ġ `getProxy(classLoader)` \n\n> JdkDynamicAopProxy#getProxy(java.lang.ClassLoader)\n\n```\n@Override\npublic Object getProxy(@Nullable ClassLoader classLoader) {\n    Class<?>[] proxiedInterfaces = AopProxyUtils\n            .completeProxiedInterfaces(this.advised, true);\n    // Ƿequals()hashCode()\n    findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);\n    //  jdk  \n    return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);\n}\n\n```\n\nõĴʲôģ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-ee46c03d86755b936862c9e8cde266fca1e.png)\n\nԿ `h` Աľ `JdkDynamicAopProxy` `JdkDynamicAopProxy`  `advised` Ա˴ĴϢ\n\n> CglibAopProxy#getProxy(java.lang.ClassLoader)\n\n```\npublic Object getProxy(@Nullable ClassLoader classLoader) {\n    try {\n        Class<?> rootClass = this.advised.getTargetClass();\n        Assert.state(rootClass != null, \"xxx\");\n\n        Class<?> proxySuperClass = rootClass;\n        if (rootClass.getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) {\n            proxySuperClass = rootClass.getSuperclass();\n            Class<?>[] additionalInterfaces = rootClass.getInterfaces();\n            for (Class<?> additionalInterface : additionalInterfaces) {\n                this.advised.addInterface(additionalInterface);\n            }\n        }\n\n        validateClassIfNecessary(proxySuperClass, classLoader);\n\n        //  Enhancer 󣬲setһЩ\n        Enhancer enhancer = createEnhancer();\n        if (classLoader != null) {\n            enhancer.setClassLoader(classLoader);\n            if (classLoader instanceof SmartClassLoader &&\n                    ((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {\n                enhancer.setUseCache(false);\n            }\n        }\n        // SuperclassҪ\n        enhancer.setSuperclass(proxySuperClass);\n        // ýӿڣ SpringProxyAdvised\n        enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));\n        enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);\n        enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader));\n\n        Callback[] callbacks = getCallbacks(rootClass);\n        Class<?>[] types = new Class<?>[callbacks.length];\n        for (int x = 0; x < types.length; x++) {\n            types[x] = callbacks[x].getClass();\n        }\n        enhancer.setCallbackFilter(new ProxyCallbackFilter(\n                this.advised.getConfigurationOnlyCopy(), \n                this.fixedInterceptorMap, this.fixedInterceptorOffset));\n        enhancer.setCallbackTypes(types);\n\n        return createProxyClassAndInstance(enhancer, callbacks);\n    }\n    catch (CodeGenerationException | IllegalArgumentException ex) {\n        throw new AopConfigException(...);\n    }\n    catch (Throwable ex) {\n        throw new AopConfigException(\"Unexpected AOP exception\", ex);\n    }\n}\n\n```\n\nspring ʹ cglib Ҫõ `Enhancer` ࣬һٷ\n\nҲõĶ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-97d647a818f62fa0979dabd58f3aa19e473.png)\n\n### 3\\. ܽ\n\nҪ `AbstractAutoProxyCreator#postProcessAfterInitialization`÷Ҫ£\n\n1.  ȡĿ࣬е㷽װΪ `List`ʵϣһĲҲ `AbstractAutoProxyCreator#postProcessBeforeInitialization` ִУȻ󽫽һʵֱڻý\n2.  ȡǰǿһжЩǿڵǰжʱȻȡǰнӿ벻 Object ĸ࣬ȻһжЩӿеķǷǿֻҪһ㣬ͱʾǰҪ\n3.  󣺴ʱĬ£Ƿʵ˽ӿѡʹ jdk ̬ cglibӦڵǰ bean  `List` ҲװС\n\n* * *\n\n_ԭӣ[https://my.oschina.net/funcy/blog/4687961](https://my.oschina.net/funcy/blog/4687961) ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע_"
  },
  {
    "path": "docs/Spring全家桶/Spring源码分析/SpringAOP/SpringAop（五）：cglib代理.md",
    "content": "\n\n[һƪ](https://my.oschina.net/funcy/blog/4696654 \"һƪ\") spring  jdK ̬ spring  cglib \n\n### 1\\. cglib \n\njdk Ȼṩ˶̬Ƕ̬һ㣺**ûʵֽӿڣ޷ jdk ̬**Ϊ˽㣬spring  cglib \n\ncglib ײǻ asm ģҲֱӲֽ룬൱ڶ asm һװֱӲ룬Ҫ java ָֽļܽУֽɬѶһ㲻ֱӲ cglib װֽĲͱü򵥶ˣ**¶ʹ cglib װõķֽ**\n\nspring cglib λ `spring-core` ģ飺\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-6b64440344221ca08ed8c822b5b22c1d341.png)\n\n asm  cglib ˵\n\n```\n/**\n * Spring's repackaging of\n * ASM 7.0\n * (with Spring-specific patches; for internal use only).\n *\n * <p>This repackaging technique avoids any potential conflicts with\n * dependencies on ASM at the application level or from third-party\n * libraries and frameworks.\n *\n * <p>As this repackaging happens at the class file level, sources\n * and javadocs are not available here.\n */\n package org.springframework.asm;\n\n```\n\nעһ䣺`Spring's repackaging of ASM 7.0` spring  `asm7.0` ´.\n\n```\n/**\n * Spring's repackaging of\n * CGLIB 3.3\n * (with Spring-specific patches; for internal use only).\n *\n * <p>This repackaging technique avoids any potential conflicts with\n * dependencies on CGLIB at the application level or from third-party\n * libraries and frameworks.\n *\n * <p>As this repackaging happens at the class file level, sources\n * and javadocs are not available here.\n */\npackage org.springframework.cglib;\n\n```\n\nעһ䣺`Spring's repackaging of CGLIB 3.3` spring  `CGLIB 3.3` ´.\n\nν´أ⣬ǽ `asm7.0`  `CGLIB 3.3` ԴĸƵ spring Ŀ¡ spring û `gradle` ļ `asm`  `cglib` ൱ jar ĿֱĿԴ룡\n\n### 2\\. cglib ʾ\n\nʽʼ֮ǰ  cglib νеġ\n\n׼һࣺ\n\n```\npackage org.springframework.learn.demo04;\n\npublic class CglibProxyService {\n    public void hello01() {\n        System.out.println(\"hello01\");\n    }\n}\n\n```\n\n׼һ `MethodInterceptor` jdk ̬е `InvocationHandler`\n\n```\npackage org.springframework.learn.demo04;\n\nimport org.springframework.cglib.proxy.MethodInterceptor;\nimport org.springframework.cglib.proxy.MethodProxy;\n\nimport java.lang.reflect.Method;\n\npublic class MyMethodInterceptor implements MethodInterceptor {\n\n    /** Ŀ */\n    private Object target;\n\n    public MyMethodInterceptor(Object target){\n        this.target = target;\n    }\n\n    @Override\n    public Object intercept(Object proxyObj, Method method, Object[] objects, \n                MethodProxy proxy) throws Throwable {\n        System.out.println(\"ִзΪ:\" + method.getName());\n        return proxy.invoke(target, objects);\n    }\n}\n\n```\n\nࣺ\n\n```\npackage org.springframework.learn.demo04;\n\nimport org.springframework.cglib.proxy.Enhancer;\n\n/**\n * \n *\n * @author fangchengyan\n * @date 2020-11-01 9:23 \n */\npublic class Demo04Main {\n\n    public static void main(String[] args) {\n        CglibProxyService target = new CglibProxyService();\n        MyMethodInterceptor interceptor = new MyMethodInterceptor(target);\n\n        Enhancer enhancer = new Enhancer();\n        // ø\n        enhancer.setSuperclass(CglibProxyService.class);\n        // callbackcallbackṩ MyMethodInterceptor\n        enhancer.setCallback(interceptor);\n        // ʹ enhancer \n        CglibProxyService proxy = (CglibProxyService)enhancer.create();\n        proxy.hello01();\n    }\n}\n\n```\n\nУ£\n\n```\nִзΪ:hello01\nhello01\n\n```\n\nԿ `MyMethodInterceptor#intercept` ִĿķ\n\nͬ jdk ̬ȽϺ󣬷ߴ߶ƣ\n\n*   `InvocationHandler`  `InvocationHandler`ߴʽһ ![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-6f3e8213e743554a7a8ffc587b98b1d7d3a.png)\n\n*   Ĵһʹ `Enhangcer` д󴴽һʹ÷װõķж󴴽 ![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-8ee847f9cbb7ec7fa6c35b2ad5a5537ef3a.png)\n\nӴ󴴽Կ `cglib` ɶʱõĲ϶࣬ܽϷḻ\n\n `Enhangcer` δԼ `org.springframework.asm`  `org.springframework.cglib` µĴ룬Щ cglib ݣͲˡ\n\n### 3\\. spring  `cglib` \n\n spring δģ\n\n> CglibAopProxy#getProxy(java.lang.ClassLoader)\n\n```\npublic Object getProxy(@Nullable ClassLoader classLoader) {\n    try {\n        Class<?> rootClass = this.advised.getTargetClass();\n        Assert.state(rootClass != null, \"...\");\n\n        Class<?> proxySuperClass = rootClass;\n        if (rootClass.getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) {\n            proxySuperClass = rootClass.getSuperclass();\n            Class<?>[] additionalInterfaces = rootClass.getInterfaces();\n            for (Class<?> additionalInterface : additionalInterfaces) {\n                this.advised.addInterface(additionalInterface);\n            }\n        }\n\n        // Ŀм飬Ҫ\n        // 1\\. Ŀ귽ʹfinalΣ\n        // 2\\. Ŀ귽private͵ģ\n        // 3\\. Ŀ귽ǰȨ޵ģ\n        // κһǰͲܱʱ÷ͻᱻԹ\n        validateClassIfNecessary(proxySuperClass, classLoader);\n\n        Enhancer enhancer = createEnhancer();\n        if (classLoader != null) {\n            enhancer.setClassLoader(classLoader);\n            if (classLoader instanceof SmartClassLoader &&\n                    ((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {\n                enhancer.setUseCache(false);\n            }\n        }\n        // SuperclassҪ\n        enhancer.setSuperclass(proxySuperClass);\n        // AopProxyUtils.completeProxiedInterfaces()ҪĿΪҪɵĴ\n        // SpringProxyAdvisedDecoratingProxyҪʵֵĽӿڡӿڵ£\n        // 1\\. SpringProxyһսӿڣڱǵǰɵĴSpringɵĴࣻ\n        // 2\\. AdvisedSpringɴʹõԶڸýӿУ\n        //    AdvisorAdviceԣ\n        // 3\\. DecoratingProxyýӿڻȡǰĿClass͡\n        enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));\n        enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);\n        enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader));\n\n        // callback\n        Callback[] callbacks = getCallbacks(rootClass);\n        Class<?>[] types = new Class<?>[callbacks.length];\n        for (int x = 0; x < types.length; x++) {\n            types[x] = callbacks[x].getClass();\n        }\n        // ôиҪʹõ߼ProxyCallbackFilter.accept()\n        // ֵһһӦCallbackи߼±꣬Ҳ˵CallbackFilter\n        // ָ˴иҪʹCallbackеĸļ߼\n        enhancer.setCallbackFilter(new ProxyCallbackFilter( this.advised.getConfigurationOnlyCopy(),\n                this.fixedInterceptorMap, this.fixedInterceptorOffset));\n        enhancer.setCallbackTypes(types);\n\n        // ɴ\n        return createProxyClassAndInstance(enhancer, callbacks);\n    }\n    catch (...) {\n        ...\n    }\n}\n\n```\n\nϴ `Enhancer` ԣ `classLoader``superclass``callbackFilter` ȣ `createProxyClassAndInstance(xxx)` \n\n> ObjenesisCglibAopProxy#createProxyClassAndInstance\n\n```\nprotected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) {\n    // \n    Class<?> proxyClass = enhancer.createClass();\n    Object proxyInstance = null;\n\n    // ݴ࣬ʹ÷ɶ\n    if (objenesis.isWorthTrying()) {\n        try {\n            proxyInstance = objenesis.newInstance(proxyClass, enhancer.getUseCache());\n        }\n        catch (Throwable ex) {\n            ...\n        }\n    }\n\n    if (proxyInstance == null) {\n        try {\n            Constructor<?> ctor = (this.constructorArgs != null ?\n                    proxyClass.getDeclaredConstructor(this.constructorArgTypes) :\n                    proxyClass.getDeclaredConstructor());\n            ReflectionUtils.makeAccessible(ctor);\n            proxyInstance = (this.constructorArgs != null ?\n                    ctor.newInstance(this.constructorArgs) : ctor.newInstance());\n        }\n        catch (Throwable ex) {\n            throw new AopConfigException(...);\n        }\n    }\n    // callback\n    // ˶ callback ʱͨ CallbackFilter ȷʹĸ callback\n    ((Factory) proxyInstance).setCallbacks(callbacks);\n    return proxyInstance;\n}\n\n```\n\nͨڵڶֵ `demo04` ֪cglib ִУᾭ `MethodInterceptor#intercept` õģҲ `Enhancer`  `callback` ԣ˽ `callback` Ļȡشλ `CglibAopProxy#getCallbacks`\n\n> CglibAopProxy#getCallbacks\n\n```\nprivate Callback[] getCallbacks(Class<?> rootClass) throws Exception {\n    boolean exposeProxy = this.advised.isExposeProxy();\n    boolean isFrozen = this.advised.isFrozen();\n    boolean isStatic = this.advised.getTargetSource().isStatic();\n\n    // ûԶĴ߼callbackУ @Before@Around@After淽\n    // DynamicAdvisedInterceptorе\n    Callback aopInterceptor = new DynamicAdvisedInterceptor(this.advised);\n\n    Callback targetInterceptor;\n    // жҪ¶ǣʹAopContextýõThreadLocal\n    // ûͨAopContextȡĿ\n    if (exposeProxy) {\n        // жϱĶǷǾ̬ģǾ̬ģĿ󻺴ÿζʹøö󼴿ɣ\n        // ĿǶ̬ģDynamicUnadvisedExposedInterceptorÿζһµ\n        // Ŀ֯Ĵ߼\n        targetInterceptor = (isStatic ?\n                new StaticUnadvisedExposedInterceptor(this.advised.getTargetSource().getTarget()) :\n                new DynamicUnadvisedExposedInterceptor(this.advised.getTargetSource()));\n    }\n    else {\n        // ΨһǷʹAopContext¶ɵĴ\n        targetInterceptor = (isStatic ?\n                new StaticUnadvisedInterceptor(this.advised.getTargetSource().getTarget()) :\n                new DynamicUnadvisedInterceptor(this.advised.getTargetSource()));\n    }\n\n    // ǰCallbackڲñķ\n    Callback targetDispatcher = (isStatic ?\n            new StaticDispatcher(this.advised.getTargetSource().getTarget()) : new SerializableNoOp());\n\n    // ȡcallbackװΪһ\n    Callback[] mainCallbacks = new Callback[] {\n            // ûԼ\n            aopInterceptor,  // for normal advice\n            // Ƿ¶\n            targetInterceptor,  // invoke target without considering advice, if optimized\n            // κβ\n            new SerializableNoOp(),  // no override for methods mapped to this\n            // ڴ洢Advisedķַ\n            targetDispatcher, this.advisedDispatcher,\n            // equalsõ\n            new EqualsInterceptor(this.advised),\n            // hashcodeõ\n            new HashCodeInterceptor(this.advised)\n    };\n\n    Callback[] callbacks;\n\n    // ĿǾ̬ģ߼ĵǹ̶ģĿл\n    if (isStatic && isFrozen) {\n        Method[] methods = rootClass.getMethods();\n        Callback[] fixedCallbacks = new Callback[methods.length];\n        this.fixedInterceptorMap = new HashMap<>(methods.length);\n\n        for (int x = 0; x < methods.length; x++) {\n            Method method = methods[x];\n            // ȡĿ߼\n            List<Object> chain = this.advised\n                    .getInterceptorsAndDynamicInterceptionAdvice(method, rootClass);\n            fixedCallbacks[x] = new FixedChainStaticTargetInterceptor(\n                    chain, this.advised.getTargetSource().getTarget(), this.advised.getTargetClass());\n            // Եл\n            this.fixedInterceptorMap.put(method, x);\n        }\n\n        // ɵľ̬Callback\n        callbacks = new Callback[mainCallbacks.length + fixedCallbacks.length];\n        System.arraycopy(mainCallbacks, 0, callbacks, 0, mainCallbacks.length);\n        System.arraycopy(fixedCallbacks, 0, callbacks, mainCallbacks.length, fixedCallbacks.length);\n        // fixedInterceptorOffset¼˵ǰ̬ĵ߼ʼλã\n        // ¼ôںʹCallbackFilterʱǾ̬ĵ\n        // ֱͨòȡӦĵֱԹǰĶ̬\n        this.fixedInterceptorOffset = mainCallbacks.length;\n    }\n    else {\n        callbacks = mainCallbacks;\n    }\n    return callbacks;\n}\n\n```\n\nϴȽϳҪþǻȡ `callback`Ȼ spring ṩڶ `callback`Զ֪ͨص callback ֻһ `DynamicAdvisedInterceptor` `callback`  `CglibAopProxy.DynamicAdvisedInterceptor#intercept` УڴеԶִ֪ͨеġ\n\nһǵõ cglib Ĵ󣬽淽ִеġ\n\n### 4\\. cglib 淽ִ\n\ncglib 淽ִ `CglibAopProxy.DynamicAdvisedInterceptor#intercept` \n\n> `CglibAopProxy.DynamicAdvisedInterceptor#intercept`\n\n```\npublic Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) \n        throws Throwable {\n    Object oldProxy = null;\n    boolean setProxyContext = false;\n    Object target = null;\n    // ͨTargetSourceȡĿ\n    TargetSource targetSource = this.advised.getTargetSource();\n    try {\n        // жҪ¶򽫵ǰõThreadLocal\n        if (this.advised.exposeProxy) {\n            oldProxy = AopContext.setCurrentProxy(proxy);\n            setProxyContext = true;\n        }\n        target = targetSource.getTarget();\n        Class<?> targetClass = (target != null ? target.getClass() : null);\n        // ȡĿ߼ĵ\n        List<Object> chain = this.advised\n                .getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);\n        Object retVal;\n        if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {\n            Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);\n            // ûֱӵĿķ\n            retVal = methodProxy.invoke(target, argsToUse);\n        }\n        else {\n            // 裺\n            // 1\\. ִnew CglibMethodInvocation()\n            // 2\\. ִCglibMethodInvocation#proceed\n            retVal = new CglibMethodInvocation(proxy, target, method, args, \n                    targetClass, chain, methodProxy).proceed();\n        }\n        // ԷֵдֵǵǰĿôɵĴ󷵻أ\n        // ֵΪգҷֵǷvoidĻͣ׳쳣\n        // ϣֱӽɵķֵ\n        retVal = processReturnType(proxy, target, method, retVal);\n        return retVal;\n    }\n    finally {\n        if (target != null && !targetSource.isStatic()) {\n            targetSource.releaseTarget(target);\n        }\n        if (setProxyContext) {\n            AopContext.setCurrentProxy(oldProxy);\n        }\n    }\n}\n\n```\n\nִ `new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed()`ȥ\n\n> CglibAopProxy.CglibMethodInvocation#proceed\n\n```\npublic Object proceed() throws Throwable {\n    try {\n        return super.proceed();\n    }\n    catch (...) {\n        ....\n    }\n}\n\n```\n\nֱӵõǸķ`CglibAopProxy.CglibMethodInvocation` ĸ˭أһȥ־Ȼ `ReflectiveMethodInvocation``super.proceed()` õ `ReflectiveMethodInvocation#proceed`\n\n[һ ƪ](https://my.oschina.net/funcy/blog/4696654 \"һ ƪ\")УǾϸ `ReflectiveMethodInvocation#proceed` ĵụ̀ڣ cglib ִеҲͬĴ룬һִй̾Ͳظˡ\n\nһͼ˵ִ֪ͨй̣\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-1c31c8e6279af4c150df18ebbd345c7f110.png)\n\nյִ˳\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-a1f3ccebe3b7335eaef88b8a39ca36b9e05.png)\n\n### 5\\. ܽ\n\nķ cglib ִйִ̣λ `CglibAopProxy.DynamicAdvisedInterceptor#intercept`յõ `ReflectiveMethodInvocation#proceed` jdk ִ̬ͬ\n\n* * *\n\n_ԭӣ[https://my.oschina.net/funcy/blog/4696655](https://my.oschina.net/funcy/blog/4696655) ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע_"
  },
  {
    "path": "docs/Spring全家桶/Spring源码分析/SpringAOP/SpringAop（六）：aop总结.md",
    "content": "ǰ漸ƪǷ spring aop ش룬ܽᡣ\n\n### 1\\. spring  aop \n\n [spring aopһʾ demo  @EnableAspectJAutoProxy](https://my.oschina.net/funcy/blog/4678093 \"spring aopһʾ demo  @EnableAspectJAutoProxy\") һУǷ spring ͨ `@EnableAspectJAutoProxy` ע aop ܣעʵ spring е `AnnotationAwareAspectJAutoProxyCreator`һ `BeanPostProcessor` bean ʼǰɴɡ\n\n### 2\\. \n\nspring aop עⷽʽʵͨ `AnnotationAwareAspectJAutoProxyCreator` ģһ `BeanPostProcessor` bean ĳʼǰִеĲ£\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-c634a0bda86d94cce68aaa46ac74f57d41d.png)\n\n### 3\\. 淽ִ\n\nôķjdk ݴͶѡִ `InvocationHandler#invoke`(jdk ̬)  `MethodInterceptor#intercept`(cglib )һڴʱѾˣ޷ĸݿ߿ɷӡУspring ȡõǰ AdvisorsȻִ Advisors 淽£\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-96bb9ba4b77e60a85a1da1c2cec3858edf7.png) ![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-013fb0c06e03fbe5044c211497df8ce306a.png)\n\n* * *\n\n_ԭӣ[https://my.oschina.net/funcy/blog/4701587](https://my.oschina.net/funcy/blog/4701587) ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע_\n\n"
  },
  {
    "path": "docs/Spring全家桶/Spring源码分析/SpringAOP/SpringAop（四）：jdk动态代理.md",
    "content": "\n\nһƪµǷ spring ڴ˴дķʽΪ `jdk̬` `cglib`ǽ spring Ķ̬\n\n### 1. jdk ̬\n\n spring Ķ̬ǰ˽ jdk Ķ̬jdk ̬ҪӿڣΪ׼ӿڣ\n\n> IJdkDynamicProxy01\n\n```java\npackage org.springframework.learn.demo03;\n\npublic interface IJdkDynamicProxy01 {\n    void hello01();\n}\n```\n\n> IJdkDynamicProxy02\n\n```java\npackage org.springframework.learn.demo03;\n\npublic interface IJdkDynamicProxy02 {\n    void hello02();\n}\n```\n\n׼ʵࣺ\n\n> JdkDynamicProxyImpl01\n\n```java\npackage org.springframework.learn.demo03;\n\npublic class JdkDynamicProxyImpl01 implements IJdkDynamicProxy01, IJdkDynamicProxy02{\n    @Override\n    public void hello01() {\n        System.out.println(\"hello01\");\n    }\n\n    @Override\n    public void hello02() {\n        System.out.println(\"hello02\");\n    }\n}\n```\n\n> JdkDynamicProxyImpl02\n\n```java\npackage org.springframework.learn.demo03;\n\npublic class JdkDynamicProxyImpl02 implements IJdkDynamicProxy01 {\n\n    @Override\n    public void hello01() {\n        System.out.println(\"hello01\");\n    }\n\n}\n```\n\nҪעǣ`JdkDynamicProxyImpl01` ʵ `IJdkDynamicProxy01`  `IJdkDynamicProxy02` ӿڣ`JdkDynamicProxyImpl02` ֻʵ `IJdkDynamicProxy01` һ ӿڡ\n\n׼һ `InvocationHandler`:\n\n> MyInvocationHandler\n\n```java\npackage org.springframework.learn.demo03;\n\nimport java.lang.reflect.InvocationHandler;\nimport java.lang.reflect.Method;\n\npublic class MyInvocationHandler implements InvocationHandler {\n\n     /** Ŀ */\n     private Object target;\n\n    public MyInvocationHandler(Object target){\n        this.target = target;\n    }\n\n    @Override\n    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {\n        System.out.println(\"ִзΪ:\" + method.getName());\n        // ִ\n        Object rs = method.invoke(target,args);\n        return rs;\n    }\n\n}\n```\n\n:\n\n```java\npackage org.springframework.learn.demo03;\n\nimport java.lang.reflect.Proxy;\n\npublic class Demo03Main {\n\n    public static void main(String[] args) {\n        System.out.println(\"------------bean01------------\");\n        JdkDynamicProxyImpl01 bean01 = new JdkDynamicProxyImpl01();\n        Object obj1 = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),\n                // JdkDynamicProxyImpl01ʵ IJdkDynamicProxy01, IJdkDynamicProxy02\n                // classΪ IJdkDynamicProxy01, IJdkDynamicProxy02\n                new Class<?>[]{ IJdkDynamicProxy01.class, IJdkDynamicProxy02.class },\n                new MyInvocationHandler(bean01));\n        // Խǿת\n        ((IJdkDynamicProxy01) obj1).hello01();\n        ((IJdkDynamicProxy02) obj1).hello02();\n\n        System.out.println(\"------------bean01------------\");\n        Object obj2 = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),\n                 // JdkDynamicProxyImpl01ʵ IJdkDynamicProxy01, IJdkDynamicProxy02\n                 // classΪ IJdkDynamicProxy01\n                 new Class<?>[]{ IJdkDynamicProxy01.class },\n                 new MyInvocationHandler(bean01));\n        ((IJdkDynamicProxy01) obj2).hello01();\n        // 쳣java.lang.ClassCastException: class com.sun.proxy.$Proxy1 cannot be cast to class xxx\n        //((IJdkDynamicProxy02) obj2).hello02();\n\n        System.out.println(\"-----------bean02-------------\");\n        JdkDynamicProxyImpl02 bean02 = new JdkDynamicProxyImpl02();\n        Object obj3 = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),\n                 // JdkDynamicProxyImpl01ʵ IJdkDynamicProxy01\n                 // classΪ IJdkDynamicProxy01, IJdkDynamicProxy02\n                 new Class<?>[]{ IJdkDynamicProxy01.class, IJdkDynamicProxy02.class },\n                 new MyInvocationHandler(bean02));\n        ((IJdkDynamicProxy01) obj3).hello01();\n        IJdkDynamicProxy02 proxy02 = (IJdkDynamicProxy02) obj3;\n        // 쳣java.lang.IllegalArgumentException: object is not an instance of declaring class\n        //proxy02.hello02();\n\n    }\n}\n```\n\nн\n\n```\nִзΪ:hello01\nhello01\nִзΪ:hello02\nhello02\n------------bean01------------\nִзΪ:hello01\nhello01\n-----------bean02-------------\nִзΪ:hello01\nhello01\n```\n\nԽ£\n\n1. `Proxy#newProxyInstance(ClassLoader, Class<?>[], InvocationHandler)` ĵڶĽӿڣǴĽӿִִͣΪ `invoke()` \n2. `JdkDynamicProxyImpl01` ͬʱʵ `IJdkDynamicProxy01`  `IJdkDynamicProxy02` ӿڣӿʱֻ `IJdkDynamicProxy01` obj2 ǿתΪ `IJdkDynamicProxy02` ʱͻᱨ `ClassCastException`ǿתʧܣ `Proxy#newProxyInstance(ClassLoader, Class<?>[], InvocationHandler)` ǴĽӿͣ\n3. `JdkDynamicProxyImpl02` ֻʵ `IJdkDynamicProxy01` ӿڣӿʱ `IJdkDynamicProxy01`  `IJdkDynamicProxy02` `obj3` ǿתΪ `IJdkDynamicProxy02` ʱδ쳣ִ `proxy02.hello02()` ʱȴ `java.lang.IllegalArgumentException: object is not an instance of declaring class`ͬ `Proxy#newProxyInstance(ClassLoader, Class<?>[], InvocationHandler)` ǴĽӿͣĿ޹ء\n\n### 2. ٴη spring jdk ̬Ĵ\n\nķ spring δģ\n\n```java\n@Override\npublic Object getProxy(@Nullable ClassLoader classLoader) {\n    // ȡĿʵֵĽӿ\n    Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);\n    // Ƿequals()hashCode()\n    findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);\n    //  jdk  \n    return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);\n}\n```\n\n1. ĽӿΪ `proxiedInterfaces`ֵĿʵֵнӿڣͬʱ spring ҲĽӿڣ `SpringProxy``Advised`ЩһƪѾϸˣ\n2. ָ `InvocationHandler` Ϊ `this`Ҳ `JdkDynamicAopProxy` Ķʵ `JdkDynamicAopProxy` ʵ `InvocationHandler`.\n\nɵһֵķ֪jdk ̬ķ `java.lang.reflect.InvocationHandler#invoke` ִеģҲ `JdkDynamicAopProxy#invoke`Ǿ `JdkDynamicAopProxy#invoke`  spring ִдġ\n\n### 3. jdk ִ̬\n\nspring jdk ִ̬ `JdkDynamicAopProxy#invoke`\n\n> JdkDynamicAopProxy#invoke\n\n```java\npublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {\n    Object oldProxy = null;\n    boolean setProxyContext = false;\n\n    TargetSource targetSource = this.advised.targetSource;\n    Object target = null;\n\n    try {\n        // ִе equals Ҫִ\n        if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {\n            return equals(args[0]);\n        }\n        // ִе hashCode Ҫִ\n        else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {\n            return hashCode();\n        }\n        // ִеclassDecoratingProxyҲҪִ\n        else if (method.getDeclaringClass() == DecoratingProxy.class) {\n            return AopProxyUtils.ultimateTargetClass(this.advised);\n        }\n        else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&\n                method.getDeclaringClass().isAssignableFrom(Advised.class)) {\n            return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);\n        }\n\n        Object retVal;\n\n        // ж advisedexposeProxy ֵǷΪ true\n        // advisedexposeProxyԴ @EnableAspectJAutoProxy  exposeProxy\n        //  ָʱ@EnableAspectJAutoProxy(exposeProxy = true)´ִ\n        if (this.advised.exposeProxy) {\n            // ǰ proxy ŵ threadLocal \n            //  (UserService (AopContext.currentProxy)).getUser() ʽ\n            oldProxy = AopContext.setCurrentProxy(proxy);\n            setProxyContext = true;\n        }\n\n        // ȡĿĿclass\n        target = targetSource.getTarget();\n        Class<?> targetClass = (target != null ? target.getClass() : null);\n\n        //  aop  advisor תΪжϸ÷ʹЩ淽\n        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(\n                method, targetClass);\n        if (chain.isEmpty()) {\n            // Ϊգ÷ûбأֱִͨ\n            Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);\n            retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);\n        }\n        else {\n            // һö\n            MethodInvocation invocation =\n                   new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);\n            // ִУص\n            retVal = invocation.proceed();\n        }\n\n        Class<?> returnType = method.getReturnType();\n        if (retVal != null && retVal == target &&\n                returnType != Object.class && returnType.isInstance(proxy) &&\n                !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {\n            retVal = proxy;\n        }\n        else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {\n            throw new AopInvocationException(...);\n        }\n        return retVal;\n    }\n    finally {\n        if (target != null && !targetSource.isStatic()) {\n            targetSource.releaseTarget(target);\n        }\n        if (setProxyContext) {\n            AopContext.setCurrentProxy(oldProxy);\n        }\n    }\n}\n```\n\nϷ£\n\n1. жҪִеķǷΪ `equals``hashcode` ȣЩҪ\n2. ȡҪִеķ淽õһϣ\n3. 淽뼰Ŀ귽\n\nصע淽Ŀ귽ִУؼ£\n\n```java\n//  aop  advisor תΪжϸ÷ʹЩ淽\nList<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(\n        method, targetClass);\n// һö\nMethodInvocation invocation =\n       new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);\n// ִУص\nretVal = invocation.proceed();\n```\n\n#### ȡ `MethodInterceptor`\n\nڷִǰ `getInterceptorsAndDynamicInterceptionAdvice(...)`ȡִе淽ģҲ `MethodInterceptor`\n\n> AdvisedSupport#getInterceptorsAndDynamicInterceptionAdvice\n\n```java\npublic List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, \n        @Nullable Class<?>targetClass) {\n    MethodCacheKey cacheKey = new MethodCacheKey(method);\n    List<Object> cached = this.methodCache.get(cacheKey);\n    if (cached == null) {\n        // ȡٷ\n        cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(\n                this, method, targetClass);\n        this.methodCache.put(cacheKey, cached);\n    }\n    return cached;\n}\n```\n\n\n\n```java\n/**\n * ȡ Interceptor£\n */\n@Override\npublic List<Object> getInterceptorsAndDynamicInterceptionAdvice(\n        Advised config, Method method, @Nullable Class<?> targetClass) {\n    AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();\n    // ȡ advisorsaopadvisors£\n    Advisor[] advisors = config.getAdvisors();\n    List<Object> interceptorList = new ArrayList<>(advisors.length);\n    Class<?> actualClass = (targetClass != null ? targetClass : method.getDeclaringClass());\n    Boolean hasIntroductions = null;\n    for (Advisor advisor : advisors) {\n        // advisorPointcutAdvisorʹPointcutAdvisorPointcutƥ\n        if (advisor instanceof PointcutAdvisor) {\n            PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;\n            // ж߼ĵǷǰйˣйٽĿ귽ƥ䣬\n            // ûУٽһƥ䡣\n            if (config.isPreFiltered() \n                    || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) {\n                MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();\n                boolean match;\n                if (mm instanceof IntroductionAwareMethodMatcher) {\n                    if (hasIntroductions == null) {\n                        hasIntroductions = hasMatchingIntroductions(advisors, actualClass);\n                    }\n                    match = ((IntroductionAwareMethodMatcher) mm)\n                            .matches(method, actualClass, hasIntroductions);\n                }\n                else {\n                    match = mm.matches(method, actualClass);\n                }\n                if (match) {\n                    // AdvisorתΪMethodInterceptor\n                    MethodInterceptor[] interceptors = registry.getInterceptors(advisor);\n                    if (mm.isRuntime()) {\n                        for (MethodInterceptor interceptor : interceptors) {\n                            //  interceptormethodMatcherװInterceptorAndDynamicMethodMatcher\n                            interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptor, mm));\n                        }\n                    }\n                    else {\n                        interceptorList.addAll(Arrays.asList(interceptors));\n                    }\n                }\n            }\n        }\n        else if (advisor instanceof IntroductionAdvisor) {\n            // жΪIntroductionAdvisor͵Advisor򽫵װΪInterceptor\n            IntroductionAdvisor ia = (IntroductionAdvisor) advisor;\n            if (config.isPreFiltered() || ia.getClassFilter().matches(actualClass)) {\n                Interceptor[] interceptors = registry.getInterceptors(advisor);\n                interceptorList.addAll(Arrays.asList(interceptors));\n            }\n        }\n        else {\n            // ṩʹԶתAdvisorת߼ΪgetInterceptors()\n            // ʹӦAdapterĿAdvisorƥ䣬ƥϣͨgetInterceptor()\n            // ԶAdviceתΪMethodInterceptor\n            Interceptor[] interceptors = registry.getInterceptors(advisor);\n            interceptorList.addAll(Arrays.asList(interceptors));\n        }\n    }\n    return interceptorList;\n}\n```\n\nǸϷܽ»ȡ `MethodInterceptor` Ĺ̹£\n\n1. ȡĿе `advisors`\n\n2. ÿ\n\n\n\n   ```\n   advisor\n   ```\n\n\n\n̴\n\n1.  `advisor`  `PointcutAdvisor`ʹе `Pointcut` ƥ䣬ƥɹ󣬻ȡ `MethodInterceptor` أ\n2.  `advisor`  `IntroductionAdvisor`ʹе `ClassFilter` ƥ䣬ƥɹ󣬻ȡ `MethodInterceptor` أ\n3. 㣬ֱӻȡ `MethodInterceptor` أ\n\nô `MethodInterceptor` λȡأǼ¿\n\n```java\n//  AdvisorAdapter ĵط\nprivate final List<AdvisorAdapter> adapters = new ArrayList<>(3);\n\n//  adapter\npublic DefaultAdvisorAdapterRegistry() {\n    // @Before\n    registerAdvisorAdapter(new MethodBeforeAdviceAdapter());\n    // @AfterReturning\n    registerAdvisorAdapter(new AfterReturningAdviceAdapter());\n    // @AfterThrowing\n    registerAdvisorAdapter(new ThrowsAdviceAdapter());\n}\n\n/**\n * ȡadvisorӦMethodInterceptor\n */\n@Override\npublic MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException {\n    List<MethodInterceptor> interceptors = new ArrayList<>(3);\n    // ȡǰadvisorMethodInterceptor\n    Advice advice = advisor.getAdvice();\n    //  advice  MethodInterceptorʵ\n    if (advice instanceof MethodInterceptor) {\n        interceptors.add((MethodInterceptor) advice);\n    }\n    // \n    // ʹ AdvisorAdapter  advice תΪ MethodInterceptor\n    // adviceadapter adapter.getInterceptor ȡ MethodInterceptor\n    for (AdvisorAdapter adapter : this.adapters) {\n        if (adapter.supportsAdvice(advice)) {\n            interceptors.add(adapter.getInterceptor(advisor));\n        }\n    }\n    if (interceptors.isEmpty()) {\n        throw new UnknownAdviceTypeException(advisor.getAdvice());\n    }\n    return interceptors.toArray(new MethodInterceptor[0]);\n}\n```\n\nܽ£\n\n1.  `advice`  `MethodInterceptor`ֱӽת `MethodInterceptor`\n2. ϲ㣬ʹ `AdvisorAdapter`  advice ת `MethodInterceptor`.\n\n `adapters`spring Ϊṩ `Adapter`\n\n- MethodBeforeAdviceAdapter `@Before`\n- AfterReturningAdviceAdapter `@AfterReturning`\n- ThrowsAdviceAdapter `@AfterThrowing`\n\n `Adapter` ֻһܣ `advice` Ӧ `MethodInterceptor` `MethodBeforeAdviceAdapter` Ĵˣ\n\n```java\nclass MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {\n    /**\n     * Ƿܴǰadvice\n     */\n    @Override\n    public boolean supportsAdvice(Advice advice) {\n        return (advice instanceof MethodBeforeAdvice);\n    }\n\n    /**\n     * ضӦMethodInterceptor\n     */\n    @Override\n    public MethodInterceptor getInterceptor(Advisor advisor) {\n        MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();\n        return new MethodBeforeAdviceInterceptor(advice);\n    }\n}\n```\n\n `Adapter` ĹܼƣͲˣܽ¸עӦ `advice``methodInterceptor`\n\n| ע            | advice                      | methodInterceptor               |\n| --------------- | --------------------------- | ------------------------------- |\n| @Before         | AspectJMethodBeforeAdvice   | MethodBeforeAdviceInterceptor   |\n| @After          | AspectJAfterAdvice          | AspectJAfterAdvice              |\n| @Around         | AspectJAroundAdvice         | AspectJAroundAdvice             |\n| @AfterReturning | AspectJAfterReturningAdvice | AfterReturningAdviceInterceptor |\n| @AfterThrowing  | AspectJAfterThrowingAdvice  | ThrowsAdviceInterceptor         |\n\n#### ReflectiveMethodInvocation#proceed\n\nȡ `MethodInterceptor` 󣬾Ϳʼзִˣֱӽ `ReflectiveMethodInvocation#proceed` \n\n```java\npublic Object proceed() throws Throwable {\n    // ִеǿִĿ귽\n    // ʹģʽеãʾǰѾִе\n    if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {\n        return invokeJoinpoint();\n    }\n\n    // ȡһҪִе\n    Object interceptorOrInterceptionAdvice =\n           this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);\n    if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {\n        InterceptorAndDynamicMethodMatcher dm =\n                (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;\n        Class<?> targetClass = (this.targetClass != null \n                ? this.targetClass : this.method.getDeclaringClass());\n        if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {\n            // ƥ䣬͵ķҲ淽\n            //  MethodInterceptor#invokeٴ ReflectiveMethodInvocation#proceedֵ˵ǰ\n            return dm.interceptor.invoke(this);\n        }\n        else {\n            // ƥ䣬ݹõǰ\n            return proceed();\n        }\n    }\n    else {\n        // ע⣬Ĳ thisʾǰ\n        //  MethodInterceptor#invokeٴ ReflectiveMethodInvocation#proceedֵ˵ǰ\n        return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);\n    }\n}\n\n/**\n * Ŀ귽\n */\nprotected Object invokeJoinpoint() throws Throwable {\n    // ʹ÷Ŀ󷽷עﴫӦĿ󣬶Ǵ\n    return AopUtils.invokeJoinpointUsingReflection(this.target, this.method, this.arguments);\n}\n```\n\nϴĵʹģʽִ߼£\n\n1. жǷִе淽ǣִĿ귽ִһ\n2. ȡһжִܷУܣִе һ\n\n߼ͦ򵥣ôִеأ spring У֪ͨͣ`@Before``@After``@AfterReturning``@AfterThrowing`  `@Around`һһ֪ͨεõġ\n\n#### 1. `@Before`\n\n> MethodBeforeAdviceInterceptor#invoke\n\n```java\n@Override\npublic Object invoke(MethodInvocation mi) throws Throwable {\n    // ִǰ֪ͨ\n    this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());\n    // ִһ\n    return mi.proceed();\n}\n```\n\n `advice.before(xxx)` \n\n> AspectJMethodBeforeAdvice#before\n\n```java\n@Override\npublic void before(Method method, Object[] args, @Nullable Object target) throws Throwable {\n    invokeAdviceMethod(getJoinPointMatch(), null, null);\n}\n```\n\nȥ\n\n> AbstractAspectJAdvice#invokeAdviceMethod(JoinPointMatch, Object, Throwable)\n\n```java\nprotected Object invokeAdviceMethod(\n         @Nullable JoinPointMatch jpMatch, @Nullable Object returnValue, @Nullable Throwable ex)\n         throws Throwable {\n\n     return invokeAdviceMethodWithGivenArgs(argBinding(getJoinPoint(), jpMatch, returnValue, ex));\n}\n\n/**\n  *  ÷ִ\n  */\nprotected Object invokeAdviceMethodWithGivenArgs(Object[] args) throws Throwable {\n    Object[] actualArgs = args;\n    if (this.aspectJAdviceMethod.getParameterCount() == 0) {\n        actualArgs = null;\n    }\n    try {\n        // Ϥjdk\n        ReflectionUtils.makeAccessible(this.aspectJAdviceMethod);\n        return this.aspectJAdviceMethod.invoke(\n                this.aspectInstanceFactory.getAspectInstance(), actualArgs);\n    }\n    catch (...) {\n        ...\n}\n```\n\nԿǵ jdk õġ\n\n#### 2. `@After`\n\n> AspectJAfterAdvice#invoke\n\n```java\n@Override\npublic Object invoke(MethodInvocation mi) throws Throwable {\n    try {\n        // ִһ\n        return mi.proceed();\n    }\n    finally {\n        // 淽 finally 飬ʾһִУҲʹ÷\n        invokeAdviceMethod(getJoinPointMatch(), null, null);\n    }\n}\n```\n\n#### 3. `@AfterReturning`\n\n> AfterReturningAdviceInterceptor#invoke\n\n```java\n@Override\npublic Object invoke(MethodInvocation mi) throws Throwable {\n    // ִһ\n    Object retVal = mi.proceed();\n    // 淽¿\n    this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());\n    return retVal;\n}\n```\n\n> AspectJAfterReturningAdvice#afterReturning\n\n```java\npublic void afterReturning(@Nullable Object returnValue, Method method, \n            Object[] args, @Nullable Object target) throws Throwable {\n    if (shouldInvokeOnReturnValueOf(method, returnValue)) {\n        // 淽Ȼǵ÷ִ\n        invokeAdviceMethod(getJoinPointMatch(), returnValue, null);\n    }\n}\n```\n\n#### 4. `@AfterThrowing`\n\n> AspectJAfterThrowingAdvice#invoke\n\n```java\n@Override\npublic Object invoke(MethodInvocation mi) throws Throwable {\n    try {\n        //  ReflectiveMethodInvocation#proceed\n        return mi.proceed();\n    }\n    catch (Throwable ex) {\n        if (shouldInvokeOnThrowing(ex)) {\n            // 淽ֻ׳쳣ʱŻᱻ\n            invokeAdviceMethod(getJoinPointMatch(), null, ex);\n        }\n        throw ex;\n    }\n}\n```\n\n#### 5. `@Around`\n\n> AspectJAroundAdvice#invoke\n\n```java\n@Override\npublic Object invoke(MethodInvocation mi) throws Throwable {\n    if (!(mi instanceof ProxyMethodInvocation)) {\n        throw new IllegalStateException(...);\n    }\n    ProxyMethodInvocation pmi = (ProxyMethodInvocation) mi;\n    ProceedingJoinPoint pjp = lazyGetProceedingJoinPoint(pmi);\n    JoinPointMatch jpm = getJoinPointMatch(pmi);\n    // 淽\n    return invokeAdviceMethod(pjp, jpm, null, null);\n}\n```\n\nʵֻ֪ͨʱһʵ֣\n\n```java\n@Around(xxx)\npublic Object around(ProceedingJoinPoint p){\n    // ִĿ귽ǰĲ\n    ...\n\n    // ִĿ귽һǹؼ\n    // ʵﲢִĿ귽յõ ReflectiveMethodInvocation#proceed\n    // ִһִĿ귽\n    Object o = p.proceed();\n\n    // ִĿ귽Ĳ\n    ...\n    return o;\n}\n```\n\nspring ִ֪ͨУҪΪ֣\n\n1. ʹ÷䷽ʽִ淽Ҳν ǿ\n2.  `ReflectiveMethodInvocation#proceed` ִһִĿ귽\n\nֵĲִдִеλã\n\n- `@Before` ֪ͨ`1` ǰ`2` ں\n- `@AfterReturning` ֪ͨ`2` ǰ`1` ںִ `2` ʱ쳣`1` Ͳִˣ\n- `@AfterThrowing` ֪ͨ`2` ǰ`1` ں `1` Ƿ `catch` ִУֻз쳣`1` ŻִУ\n- `@After` ֪ͨ`2` ǰ`1` ں`1` Ƿ `finally` ִУ `finally` ԣʹ쳣`1` ִͬУ\n- `@Around` ֪ͨ淽ָ `2` ִʱ\n\nע `@AfterReturning``@AfterThrowing`  `@After` ִ֪ͨʱ\n\n⼸ִ֪ͨеġ\n\nͨԵķʽ spring ִ֪ͨ˳£\n\n1. ִ `@AfterThrowing` ֪ͨȵ `mi.proceed()` ִһȻ `catch` ִ淽ֻг쳣ʱ淽ŻִУ\n2. һУ `mi.proceed()` ʱִ `@AfterReturning` ִ֪ͨʱȵ `mi.proceed()` ִһȻִ淽\n3. һУ `mi.proceed()` ʱִ `@After` ִ֪ͨʱȵ `mi.proceed()` ִһȻ `finally` ִ淽ʹ쳣淽ǻִУ\n4. һУ `mi.proceed()` ʱִ `@Around` ִ֪ͨʱֱִ淽 `@Around` ֪ͨ淽 `ProceedingJoinPoint#proceed()`ջǻִһ\n5. һУ `mi.proceed()` ʱִ `@Before` ִ֪ͨʱִ淽ٵ `mi.proceed()` ִһ\n6. ִе󣬷ûпִеˣʱͿʼִĿ귽\n\nͼʾִ֪ͨй£\n\n![img](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-e88e0cec49c47648005e5d3160663425739.png)\n\nյִ˳\n\n![img](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-97026239d0b2dc02abbe87a9b76325c3cc0.png)\n\n### 4. ܽ\n\nҪ jdk ִ̬й̣˸ִ֪ͨ˳򡣱ľȵˣһƪ½ cglib ִй̡\n\n------\n\n*ԭӣhttps://my.oschina.net/funcy/blog/4696654 ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע*"
  },
  {
    "path": "docs/Spring全家桶/Spring源码分析/Spring事务/Spring事务（一）：认识事务组件.md",
    "content": "ǰ spring aop عܺ󣬱Ľ spring aop һӦ  \n\n### 1\\.  demo \n\nʽǰ˼£Լ spring aop һƣʵأû springǵһ㳤\n\n```\npublic void fun() {\n    // \n    start();\n    try {\n        // ҵ\n        xxx();\n        // ύ\n        commit();\n    } catch(Exception e) {\n        // ع\n        rollback();\n        throw e;\n    }\n}\n\n```\n\nĴύ񡢻ع񣬶ҵ޹أЩʹ spring aop ʵ֣˾ demo.\n\n#### demo01 `@Around` עʵ\n\nǿʹ `@Around` ע£\n\n1.  һע⣺`@MyTransactional`\n\n```\n@Target({ElementType.TYPE, ElementType.METHOD})\n@Retention(RetentionPolicy.RUNTIME)\n@Inherited\n@Documented\npublic @interface MyTransactional {\n}\n\n```\n\n1.   aop \n\n```\n@Aspect\n@Component\npublic class MyAopAspectj {\n    @Pointcut(\"@annotation(org.springframework.learn.tx.demo02.MyTransactional)\")\n    public void testAop(){\n\n    }\n\n    @Around(\"testAop()\")\n    public Object around(ProceedingJoinPoint p) throws Throwable {\n        System.out.println(\"ִǰ....\");\n        try {\n            Object o = p.proceed();\n            System.out.println(\"ִɣύ....\");\n            return o;\n        } catch (Throwable e) {\n            System.out.println(\"쳣쳣ͻع....\");\n            throw e;\n        } finally {\n            System.out.println(\"ִк....\");\n        }\n    }\n\n}\n\n```\n\n1.  configһЩҪ\n\n```\n@Configuration\n@ComponentScan(\"org.springframework.learn.tx.demo02\")\n@EnableAspectJAutoProxy(proxyTargetClass = true)\npublic class TxDemo02Config {\n\n}\n\n```\n\n1.  һ service ࣬һ `@MyTransactional` ע\n\n```\n@Service\npublic class TxTestService {\n\n    @MyTransactional\n    public void test01() {\n        System.out.println(\"ִtest01\");\n    }\n\n    public void test02() {\n        System.out.println(\"ִtest02\");\n    }\n\n}\n\n```\n\n1.  \n\n```\npublic class TxDemo02Main {\n\n    public static void main(String[] args) {\n        AnnotationConfigApplicationContext applicationContext\n                = new AnnotationConfigApplicationContext(TxDemo02Config.class);\n        TxTestService service = applicationContext.getBean(TxTestService.class);\n        System.out.println(\"-------------------\");\n        service.test01();\n        System.out.println(\"-------------------\");\n        service.test02();\n\n    }\n}\n\n```\n\nУ£\n\n```\n-------------------\nִǰ....\nִtest01\nִɣύ....\nִк....\n-------------------\nִtest02\n\n```\n\n demo Уʹ `@Around` עҵִǰԿ`@Around` עڴǰǳ쳣ʱһЩĲ\n\n#### demo02Զ `advisor` ʵ\n\nǻ spring aop  `@Around` עĴʵ `@Around` ջװΪ `InstantiationModelAwarePointcutAdvisorImpl` 󣬺Ĵ͸ `@Around` ޹ˣ`@Around`  `InstantiationModelAwarePointcutAdvisorImpl` Ḷ́ɲο [spring aop ֮ AnnotationAwareAspectJAutoProxyCreator ϣ](https://my.oschina.net/funcy/blog/4678817).\n\n`InstantiationModelAwarePointcutAdvisorImpl` ǸʲôأǸ `advisor`˵ǿڷǿ spring aop ҵӦڵǰ `advisor` ģɲο [spring aop ֮ AnnotationAwareAspectJAutoProxyCreator £](https://my.oschina.net/funcy/blog/4687961)\n\nͨϷṩһ˼·ǿʵ `advisor` ӿڣƻԼ߼£\n\n1.  ׼ `advice`\n\n```\n/**\n * adviceadvisorһԣ߼ﴦ\n */\npublic class MyAdvice implements MethodInterceptor {\n\n    @Override\n    public Object invoke(MethodInvocation invocation) throws Throwable {\n        System.out.println(\"ִǰ....\");\n        try {\n            Object val = invocation.proceed();\n            System.out.println(\"ִɣύ....\");\n            return val;\n        } catch (Throwable e) {\n            System.out.println(\"쳣쳣ͻع....\");\n            throw e;\n        } finally {\n            System.out.println(\"ִк....\");\n        }\n    }\n}\n\n```\n\n1.  ׼ `pointcut`\n\n```\n/**\n * е\n * жЩڸadvisor\n */\npublic class MyPointcut extends StaticMethodMatcherPointcut {\n    /**\n     * ƥ䷽ @MyTransactional 򷽷ͷtrue\n     */\n    @Override\n    public boolean matches(Method method, Class<?> targetClass) {\n        return null != AnnotationUtils.getAnnotation(method, MyTransactional.class)\n                || null != AnnotationUtils.getAnnotation(targetClass, MyTransactional.class);\n    }\n}\n\n```\n\n1.  ׼ `advisor`\n\n```\n/**\n * advisor ɿ advice  pointcut İװ\n */\n@Component\npublic class MyAdvisor extends AbstractBeanFactoryPointcutAdvisor {\n\n    private static final long serialVersionUID = 2651364800145442305L;\n\n    private MyPointcut pointcut;\n\n    public MyAdvisor() {\n        this.pointcut = new MyPointcut();\n        this.setAdvice(new MyAdvice());\n    }\n\n    @Override\n    public Pointcut getPointcut() {\n        return this.pointcut;\n    }\n\n}\n\n```\n\nǲͬעʵַʽĴעһˡ\n\n1.  ׼һע⣺`@MyTransactional`\n\n```\n@Target({ElementType.TYPE, ElementType.METHOD})\n@Retention(RetentionPolicy.RUNTIME)\n@Inherited\n@Documented\npublic @interface MyTransactional {\n}\n\n```\n\n1.  Ŀ\n\n```\n@Configuration\n@ComponentScan(\"org.springframework.learn.tx.demo01\")\n@EnableAspectJAutoProxy(proxyTargetClass = true)\npublic class TxDemo01Config {\n\n}\n\n```\n\n1.  ׼һ service\n\n```\n@Service\npublic class TxTestService {\n\n    @MyTransactional\n    public void test01() {\n        System.out.println(\"ִtest01\");\n    }\n\n    public void test02() {\n        System.out.println(\"ִtest02\");\n    }\n\n}\n\n```\n\n1.  \n\n```\npublic class TxDemo01Main {\n\n    public static void main(String[] args) {\n        AnnotationConfigApplicationContext applicationContext\n                = new AnnotationConfigApplicationContext(TxDemo01Config.class);\n        TxTestService service = applicationContext.getBean(TxTestService.class);\n        System.out.println(\"-------------------\");\n        service.test01();\n        System.out.println(\"-------------------\");\n        service.test02();\n\n    }\n}\n\n```\n\nУ£\n\n```\n-------------------\nִǰ....\nִtest01\nִɣύ....\nִк....\n-------------------\nִtest02\n\n```\n\n### 2\\. ʹ spring \n\nǰС demo Ϊθˣ spring һʶspring ڴʱʹõľǵڶַʽԶһ `advisor` ӵ spring С spring ʵľϸڣǴһ demoƽʱôʹġ\n\nΪ˽ݿӣҪݿӳأʹõ mysqlҪ `spring-learn.gradle` \n\n```\noptional(\"mysql:mysql-connector-java:5.1.48\")\n\n```\n\nžǴˡ\n\n1.  \n\n```\n@Configuration\n@ComponentScan(\"org.springframework.learn.tx.demo03\")\n@EnableTransactionManagement(proxyTargetClass = true)\npublic class TxDemo01Config {\n\n    /**\n     * Դ\n     * @return\n     * @throws Exception\n     */\n    @Bean\n    public DataSource dataSource() throws Exception {\n        Driver driver = new com.mysql.jdbc.Driver();\n        String url = \"jdbc:mysql://localhost:3306/test\";\n        String username = \"root\";\n        String password = \"123\";\n        return new SimpleDriverDataSource(driver, url, username, password);\n    }\n\n    /**\n     * jdbcTemplateݿĲ\n     * @param dataSource\n     * @return\n     */\n    @Bean\n    public JdbcTemplate jdbcTemplate(DataSource dataSource) {\n        return new JdbcTemplate(dataSource);\n    }\n\n    /**\n     * \n     * @param dataSource\n     * @return\n     */\n    @Bean\n    public DataSourceTransactionManager transactionManager(DataSource dataSource) {\n        return new DataSourceTransactionManager(dataSource);\n    }\n\n}\n\n```\n\n1.  ݿ\n\n```\n@Service\npublic class UserService {\n\n    @Autowired\n    private JdbcTemplate jdbcTemplate;\n\n    /**\n     * ݿʹ @Transactional \n     * @return\n     */\n    @Transactional(rollbackFor = Exception.class)\n    public int insert() {\n        String sql = \"insert into `user`(`login_name`, `nick`, `create_time`, `update_time`)\"\n                + \"values (?, ?, ?, ?)\";\n        int result = jdbcTemplate.update(sql, \"test\", \"test\", new Date(), new Date());\n        if(true) {\n            //throw new RuntimeException(\"׳쳣\");\n        }\n        System.out.println(result);\n        return result;\n    }\n\n}\n\n```\n\n1.  \n\n```\npublic class TxDemo01Main {\n\n    public static void main(String[] args) {\n        AnnotationConfigApplicationContext applicationContext\n                = new AnnotationConfigApplicationContext(TxDemo01Config.class);\n        UserService userService = applicationContext.getBean(UserService.class);\n        userService.insert();\n\n    }\n}\n\n```\n\ndemo У`DataSource` ʹ spring Դ `SimpleDriverDataSource``orm` Ҳ spring ṩ `jdbcTemplate`ʹõ `user`  sql £\n\n```\nCREATE TABLE `user` (\n  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',\n  `login_name` varchar(32) NOT NULL DEFAULT '0' COMMENT '¼',\n  `nick` varchar(32) NOT NULL DEFAULT '0' COMMENT 'ǳ',\n  `create_time` datetime DEFAULT NULL COMMENT 'ʱ',\n  `update_time` datetime DEFAULT NULL COMMENT 'ʱ',\n  PRIMARY KEY (`id`),\n  KEY `create_time` (`create_time`)\n) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='û';\n\n```\n\nִн£\n\nһβ׳쳣ݿ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-30bbe23a8e0491d1f59378469ad04703e03.png)\n\nڶ׳쳣ݿ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-edf98369ccef9735d83813cef7af7ea1dcd.png)\n\nβ׳쳣ݿ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-fab4169dbec7661f27bba203c5e77232f65.png)\n\nԿڶ׳쳣ʱعˡ\n\n demoйصĴ\n\n*   `@EnableTransactionManagement(proxyTargetClass = true)`\n*   `DataSourceTransactionManager`\n*   `@Transactional`ָķ\n\n aop  `@EnableAspectJAutoProxy``@EnableTransactionManagement` ڣǾʹע֣ spring ̡\n\n### 3. `@EnableTransactionManagement` ע\n\n```\n@Target(ElementType.TYPE)\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\n@Import(TransactionManagementConfigurationSelector.class)\npublic @interface EnableTransactionManagement {\n\n    /**\n     * ѧaopضѾϤ\n     * true: ʾǿʹcglib\n     * falseĿʵ˽ӿڣʹjdk̬ʹcglib\n     *  mode Ϊ PROXY Ч\n     */\n    boolean proxyTargetClass() default false;\n\n    /**\n     * adviceģʽʹôʹ aspectJ\n     */\n    AdviceMode mode() default AdviceMode.PROXY;\n\n    /**\n     * ִ˳򣬵һжǿʱʲô˳ִ\n     */\n    int order() default Ordered.LOWEST_PRECEDENCE;\n\n}\n\n```\n\nעⱾûʲôԣעѾȷˣǹؼǿעࣺ`TransactionManagementConfigurationSelector`\n\n```\npublic class TransactionManagementConfigurationSelector extends \n        AdviceModeImportSelector<EnableTransactionManagement> {\n    @Override\n    protected String[] selectImports(AdviceMode adviceMode) {\n        switch (adviceMode) {\n            case PROXY:\n                // ڴ\n                return new String[] {AutoProxyRegistrar.class.getName(),\n                        ProxyTransactionManagementConfiguration.class.getName()};\n            case ASPECTJ:\n                // aspectJ࣬Ĳ\n                return new String[] {determineTransactionAspectClass()};\n            default:\n                return null;\n        }\n    }\n    // ʡ\n    ...\n\n}\n\n```\n\nڴࣺ`AutoProxyRegistrar``ProxyTransactionManagementConfiguration`Ǿࡣ\n\n#### 3.1 `AutoProxyRegistrar`\n\n`AutoProxyRegistrar` һעǵǰ aop ע `AspectJAutoProxyRegistrar` һ·\n\n澿ɶ\n\n```\npublic class AutoProxyRegistrar implements ImportBeanDefinitionRegistrar {\n\n    private final Log logger = LogFactory.getLog(getClass());\n\n    @Override\n    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, \n            BeanDefinitionRegistry registry) {\n        boolean candidateFound = false;\n        Set<String> annTypes = importingClassMetadata.getAnnotationTypes();\n        for (String annType : annTypes) {\n            AnnotationAttributes candidate = AnnotationConfigUtils\n                    .attributesFor(importingClassMetadata, annType);\n            if (candidate == null) {\n                continue;\n            }\n            Object mode = candidate.get(\"mode\");\n            Object proxyTargetClass = candidate.get(\"proxyTargetClass\");\n            // ifģ @EnableTransactionManagement ע\n            if (mode != null && proxyTargetClass != null && AdviceMode.class == mode.getClass() &&\n                    Boolean.class == proxyTargetClass.getClass()) {\n                candidateFound = true;\n                if (mode == AdviceMode.PROXY) {\n                    // עע InfrastructureAdvisorAutoProxyCreator ࣬\n                    AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);\n                    if ((Boolean) proxyTargetClass) {\n                        // ʹcglib\n                        AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);\n                        return;\n                    }\n                }\n            }\n        }\n        if (!candidateFound && logger.isInfoEnabled()) {\n            String name = getClass().getSimpleName();\n            logger.info(...);\n        }\n    }\n}\n\n```\n\nдؼľֻ if ļУ˵ if `mode != null && proxyTargetClass != null && AdviceMode.class == mode.getClass() &&Boolean.class == proxyTargetClass.getClass()`ͨĶ `@EnableTransactionManagement`˵ľˣ `mode == AdviceMode.PROXY` `AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry)`ĵãǽȻ `proxyTargetClass`Ե `@EnableAspectJAutoProxy` е `proxyTargetClass` һ£Ҳǿǿʹ cglib \n\n `AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry)` Ḷ́룺\n\n> AopConfigUtils\n\n```\n    @Nullable\n    public static BeanDefinition registerAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry) {\n        // ¿\n        return registerAutoProxyCreatorIfNecessary(registry, null);\n    }\n\n    @Nullable\n    public static BeanDefinition registerAutoProxyCreatorIfNecessary(\n            BeanDefinitionRegistry registry, @Nullable Object source) {\n        //  InfrastructureAdvisorAutoProxyCreator ࣬\n        return registerOrEscalateApcAsRequired(InfrastructureAdvisorAutoProxyCreator.class, \n            registry, source);\n    }\n\n```\n\nǲϤУaop е `AspectJAnnotationAutoProxyCreator` Ҳ ôעģ `AopConfigUtils#registerOrEscalateApcAsRequired` \n\n```\n// AopConfigUtils ע඼\nprivate static final List<Class<?>> APC_PRIORITY_LIST = new ArrayList<>(3);\n\nstatic {\n    APC_PRIORITY_LIST.add(InfrastructureAdvisorAutoProxyCreator.class);\n    APC_PRIORITY_LIST.add(AspectJAwareAdvisorAutoProxyCreator.class);\n    APC_PRIORITY_LIST.add(AnnotationAwareAspectJAutoProxyCreator.class);\n}\n\n/**\n * ע\n */\nprivate static BeanDefinition registerOrEscalateApcAsRequired(\n        Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) {\n    Assert.notNull(registry, \"BeanDefinitionRegistry must not be null\");\n    //Ѵbean\n    if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {\n        BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);\n        //жȼȼϸ滻ԭȵbean\n        if (!cls.getName().equals(apcDefinition.getBeanClassName())) {\n            int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());\n            int requiredPriority = findPriorityForClass(cls);\n            // Ѵ ȼ СעģʹעģѴڵȼΪ\n            // 0: InfrastructureAdvisorAutoProxyCreator()\n            // 1: AspectJAwareAdvisorAutoProxyCreator(xmlaop)\n            // 2: AnnotationAwareAspectJAutoProxyCreator(עaop)\n            if (currentPriority < requiredPriority) {\n                apcDefinition.setBeanClassName(cls.getName());\n            }\n        }\n        return null;\n    }\n    //עXxxAutoProxyCreator\n    RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);\n    beanDefinition.setSource(source);\n    beanDefinition.getPropertyValues().add(\"order\", Ordered.HIGHEST_PRECEDENCE);\n    beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);\n    registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);\n    return beanDefinition;\n}\n\n/**\n * עȼ\n */\nprivate static int findPriorityForClass(@Nullable String className) {\n    for (int i = 0; i < APC_PRIORITY_LIST.size(); i++) {\n        Class<?> clazz = APC_PRIORITY_LIST.get(i);\n        if (clazz.getName().equals(className)) {\n            return i;\n        }\n    }\n    throw new IllegalArgumentException(\n            \"Class name [\" + className + \"] is not a known auto-proxy creator class\");\n}\n\n```\n\n`AopConfigUtils` ע\n\n*   `InfrastructureAdvisorAutoProxyCreator`\n*   `AspectJAwareAdvisorAutoProxyCreator` xml  aop\n*   `AnnotationAwareAspectJAutoProxyCreator`ע aop\n\nߵȼΪ `AnnotationAwareAspectJAutoProxyCreator` > `AspectJAwareAdvisorAutoProxyCreator` > `InfrastructureAdvisorAutoProxyCreator`עʱжעȼȼߵջᱻע뵽 spring С͵һ⣺**Ŀͬʱ aop (`@EnableAspectJAutoProxy`)  (`@EnableTransactionManagement`)ôע뵽Ľ `AnnotationAwareAspectJAutoProxyCreator`Ҳ˵`AnnotationAwareAspectJAutoProxyCreator` Ҳܴ** 仰ǳؼζĴ̣ʵϾͰǰ aop Ĺˣ\n\nҲ `InfrastructureAdvisorAutoProxyCreator`\n\n```\n// ̳ AbstractAdvisorAutoProxyCreatorǳؼ\npublic class InfrastructureAdvisorAutoProxyCreator extends AbstractAdvisorAutoProxyCreator {\n\n    @Nullable\n    private ConfigurableListableBeanFactory beanFactory;\n\n    @Override\n    protected void initBeanFactory(ConfigurableListableBeanFactory beanFactory) {\n        super.initBeanFactory(beanFactory);\n        this.beanFactory = beanFactory;\n    }\n\n    @Override\n    protected boolean isEligibleAdvisorBean(String beanName) {\n        return (this.beanFactory != null && \n                this.beanFactory.containsBeanDefinition(beanName) \n                &&  this.beanFactory.getBeanDefinition(beanName).getRole() \n                                == BeanDefinition.ROLE_INFRASTRUCTURE);\n    }\n\n}\n\n```\n\n`InfrastructureAdvisorAutoProxyCreator` ʵûʲô aop ص£̳һؼࣺ`AbstractAdvisorAutoProxyCreator`Ǵͷ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-2881f63ac07afc5095c449ddb9a0df2bb55.png)\n\nӼ̳йϵ̳ `AbstractAutoProxyCreator` `AbstractAutoProxyCreator`  - [spring aop ֮ AnnotationAwareAspectJAutoProxyCreator ϣ](https://my.oschina.net/funcy/blog/4678817)  [spring aop ֮ AnnotationAwareAspectJAutoProxyCreator £](https://my.oschina.net/funcy/blog/4687961)صġĲڣ\n\n `AnnotationAwareAspectJAutoProxyCreator``AspectJAwareAdvisorAutoProxyCreator` `InfrastructureAdvisorAutoProxyCreator` ߵĹϵ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-acd72335503eeb686abe81d930b45f1f3f0.png)\n\nԿ`AspectJAwareAdvisorAutoProxyCreator` `InfrastructureAdvisorAutoProxyCreator` ̳ `AbstractAdvisorAutoProxyCreator``AnnotationAwareAspectJAutoProxyCreator` ּ̳ `AspectJAwareAdvisorAutoProxyCreator`\n\nͨϷ`AutoProxyRegistrar`  spring ע `InfrastructureAdvisorAutoProxyCreator`(`aop` δõ) `aop`ע `AspectJAwareAdvisorAutoProxyCreator`( `xml`  `aop`)  `AnnotationAwareAspectJAutoProxyCreator`( `annotation`  `aop`)\n\n#### 3.2 `ProxyTransactionManagementConfiguration`\n\n `ProxyTransactionManagementConfiguration` ࡣǸࣺ\n\n```\n@Configuration(proxyBeanMethods = false)\npublic class ProxyTransactionManagementConfiguration \n        extends AbstractTransactionManagementConfiguration {\n\n    /**\n     * ȡSpring @Transactional ע⣬ӦԹSpringṹ\n     */\n    @Bean\n    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)\n    public TransactionAttributeSource transactionAttributeSource() {\n        return new AnnotationTransactionAttributeSource();\n    }\n\n    /**\n     * TransactionInterceptor̳AdviceǸadviceִв\n     * @param transactionAttributeSource transactionAttributeSource() \n     */\n    @Bean\n    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)\n    public TransactionInterceptor transactionInterceptor(\n            TransactionAttributeSource transactionAttributeSource) {\n        TransactionInterceptor interceptor = new TransactionInterceptor();\n        // Դ󣬾 @Transactional ע Ķȡ\n        interceptor.setTransactionAttributeSource(transactionAttributeSource);\n        if (this.txManager != null) {\n            interceptor.setTransactionManager(this.txManager);\n        }\n        return interceptor;\n    }\n\n    /**\n     * ǿ.\n     * @param transactionAttributeSource transactionAttributeSource() \n     * @param transactionInterceptor transactionInterceptor(...) \n     */\n    @Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)\n    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)\n    public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor(\n            TransactionAttributeSource transactionAttributeSource,\n            TransactionInterceptor transactionInterceptor) {\n        BeanFactoryTransactionAttributeSourceAdvisor advisor \n                = new BeanFactoryTransactionAttributeSourceAdvisor();\n        // ࣬ @Transactional \n        advisor.setTransactionAttributeSource(transactionAttributeSource);\n        // adviceadviceﴦ\n        advisor.setAdvice(transactionInterceptor);\n        if (this.enableTx != null) {\n            advisor.setOrder(this.enableTx.<Integer>getNumber(\"order\"));\n        }\n        return advisor;\n    }\n\n}\n\n```\n\nԿһ Щ `bean`\n\n*   `transactionAttributeSource`Ϊ `AnnotationTransactionAttributeSource` `@Transactional` ע⣻\n*   `transactionInterceptor`Ϊ `TransactionInterceptor``Advice` ࣬߼\n*   `transactionAdvisor`Ϊ `BeanFactoryTransactionAttributeSourceAdvisor`Ǹ `Advisor`߼ڲ`transactionAttributeSource`  `transactionInterceptor`\n\n`ProxyTransactionManagementConfiguration` ̳ `AbstractTransactionManagementConfiguration` `AbstractTransactionManagementConfiguration` ҲһЩ `bean`\n\n```\n@Configuration\npublic abstract class AbstractTransactionManagementConfiguration implements ImportAware {\n\n    @Nullable\n    protected AnnotationAttributes enableTx;\n\n    /**\n     * \n     */\n    @Nullable\n    protected TransactionManager txManager;\n\n    /**\n     *  ImportAware ӿڵķ\n     */\n    @Override\n    public void setImportMetadata(AnnotationMetadata importMetadata) {\n        this.enableTx = AnnotationAttributes.fromMap(importMetadata\n                .getAnnotationAttributes(EnableTransactionManagement.class.getName(), false));\n        if (this.enableTx == null) {\n            throw new IllegalArgumentException(\n                    \"@EnableTransactionManagement is not present on importing class \" \n                    + importMetadata.getClassName());\n        }\n    }\n\n    /**\n     * .\n     * עspringе TransactionManagementConfigurer \n     * TransactionManagementConfigurerֻһ\n     *  TransactionManager annotationDrivenTransactionManager()\n     * һ\n     */\n    @Autowired(required = false)\n    void setConfigurers(Collection<TransactionManagementConfigurer> configurers) {\n        if (CollectionUtils.isEmpty(configurers)) {\n            return;\n        }\n        if (configurers.size() > 1) {\n            throw new IllegalStateException(\"Only one TransactionManagementConfigurer may exist\");\n        }\n        TransactionManagementConfigurer configurer = configurers.iterator().next();\n        this.txManager = configurer.annotationDrivenTransactionManager();\n    }\n\n    /**\n     * ¼ @TransactionalEventListener עķ.\n     */\n    @Bean(name = TransactionManagementConfigUtils.TRANSACTIONAL_EVENT_LISTENER_FACTORY_BEAN_NAME)\n    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)\n    public static TransactionalEventListenerFactory transactionalEventListenerFactory() {\n        return new TransactionalEventListenerFactory();\n    }\n\n}\n\n```\n\n*   `void setConfigurers(Collection<TransactionManagementConfigurer> configurers)`ע `TransactionManagementConfigurer` 󣬾ڴע˵\n*   `TransactionalEventListenerFactory`Ϊ `TransactionalEventListenerFactory`¼Ҫ `@TransactionalEventListener` עķⲿֵݣľͲչˣ\n\nˣЩspring ͿԽˣЩƪٷ\n\n### 4\\. ܽ\n\nǴ demo ֣ʾԼһ spring aop νеģһ demo ʾʹ spring ṩܣȻ; spring ע `@EnableTransactionManagement` Ĺܡ\n\n`@EnableTransactionManagement`  spring ܵģ `AdviceMode` Ϊ `proxy` ģʽ£ע spring ࣺ`AutoProxyRegistrar``ProxyTransactionManagementConfiguration`£\n\n*   `AutoProxyRegistrar``aop` δõ£ spring ע `InfrastructureAdvisorAutoProxyCreator` `aop`ע `AspectJAwareAdvisorAutoProxyCreator`( `xml`  `aop`)  `AnnotationAwareAspectJAutoProxyCreator`( `annotation`  `aop`)඼ `AbstractAdvisorAutoProxyCreator` ࣬ɴ\n\n*   `ProxyTransactionManagementConfiguration`һ࣬ͨ `@Bean` עķһϵе bean߼Щ beanֻ˽⼴ɡ\n\nľȵˣƪ¼ơ\n\n* * *\n\n_ԭӣ[https://my.oschina.net/funcy/blog/4773454](https://my.oschina.net/funcy/blog/4773454) ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע_"
  },
  {
    "path": "docs/Spring全家桶/Spring源码分析/Spring事务/Spring事务（三）：事务的隔离级别与传播方式的处理01.md",
    "content": "һƪµᵽִ `TransactionAspectSupport#invokeWithinTransaction` УĽ̽ spring ơ\n\nעֵ̽ݿ `mysql`ݿܻ졣\n\n### 1. ظ\n\nʽԴǰһЩǰ֪ʶҪ˽һµģҪ spring ص\n\n#### 1.1 ĸ뼶\n\nĴ `ACID`о£\n\n- ԭԣ`Atomicity`\n- һԣ`Consistency`\n- ԣ`Isolation`\n- ־ԣ`Durability`\n\nĸ뼶ǶԸԣ`Isolation`Ľһ֣Щ뼶£\n\n- `δύ`\n- `ύ`\n- `ظ`\n- `л`\n\nЩĲص㻹ǹע spring صݣspring 뼶Ķ `org.springframework.transaction.annotation.Isolation` У£\n\n```\npublic enum Isolation {\n\n    /**\n    * Ĭֵø뼶ʹõݿõĸ뼶\n    */\n    DEFAULT(TransactionDefinition.ISOLATION_DEFAULT),\n\n    /**\n    * δύ\n    */\n    READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED),\n\n    /**\n    * ύ\n    */\n    READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED),\n\n    /**\n    * ظ\n    */\n    REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ),\n\n    /**\n    * л\n    */\n    SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE);\n\n    ...\n}\n```\n\nǿʹ `@Transactional` ע `isolation` ø뼶\n\n#### 1.2 ĳʱʱ\n\n- Ըָһִʱ䣬ִĵʱ䳬ָʱ䣬ͻ׳쳣Ӷع\n-  `@Transactional` ע `timeout` óʱʱ\n\n#### 1.3 ֻ\n\n- ԽΪ`ֻģʽ`ƽʱûûõ鵽һЩ˵Ϊ`ֻģʽ`еĸֻ񿴲ֻвдʵд֤\n- ǿʹ `@Transactional` ע `readOnly` ֻģʽ\n\n#### 1.4 Ĵ\n\nһ`A` `ʽB` `B` е`A``A` ִɺ`B` ˣʾ£\n\n```\nclass A {\n    // \n    @Transactional\n    public void methdA() {\n        // һЩ\n        ...\n    }\n}\n\nclass B {\n    // \n    @Transactional\n    public void methodB() {\n        // 1. һЩ\n        ...\n        // 2.  methodA()\n        a.methodA();\n        // 3. ﱨ\n        throw new RuntimeException();\n    }\n}\n```\n\nڿ`B` һعģô`A` ҪҪعأ\n\n- ǰ`A` `B` 񿴳ͬһ`A` ӦҲҪع\n- ǰ`A` `B` ִУ`A` ִ`B` ı޹أ`A` ͲӦûع\n\nΪ˴־ףspring `Ĵ`ĸǿʹ `@Transactional` ע `propagation` ֻģʽ\n\n```\npublic @interface Transactional {\n    ...\n\n    // ĬϵļΪ Propagation.REQUIRED\n    Propagation propagation() default Propagation.REQUIRED;\n\n}\n```\n\nspring һ 7 Ĵͣо£\n\n| 񴫲Ϊ                |                                                          |\n| --------------------------- | ------------------------------------------------------------ |\n| `PROPAGATION_REQUIRED`      | Ĭֵ衿ǰУǰ߳ûһµǰ߳Ѿ򷽷ڸС |\n| `PROPAGATION_MANDATORY`     | ǿơǰУǰ߳в**׳쳣** |\n| `PROPAGATION_SUPPORTS`      | ֧֡ǰʱҪ񣬵ǰ߳дʱ |\n| `PROPAGATION_REQUIRES_NEW`  | 񡿵ǰڶУǰ߳Ѿ򽫸¿һֱнٻָ֮ǰ |\n| `PROPAGATION_NESTED`        | ǶסǰУǰ߳д򽫸ע****γǶǶе쳣Ӱ쵽񱣴֮ǰĲ |\n| `PROPAGATION_NOT_SUPPORTED` | ֧֡ǰУǰ߳дֱн |\n| `PROPAGATION_NEVER`         | ǰУǰ߳д**׳쳣** |\n\nע͵\n\n1. `PROPAGATION_REQUIRED`  `PROPAGATION_MANDATORY`\n    - `PROPAGATION_REQUIRED`ҪУû****\n    - `PROPAGATION_MANDATORY`ҪУû**쳣**\n2. `PROPAGATION_NOT_SUPPORTED`  `PROPAGATION_NEVER`\n    - `PROPAGATION_NOT_SUPPORTED`У****\n    - `PROPAGATION_NEVER`У**쳣**\n3. `PROPAGATION_REQUIRES_NEW`  `PROPAGATION_NESTED`\n    - `PROPAGATION_REQUIRES_NEW`ִɺ󣬾񱨴ֻع񲻻عִб¾һع\n    - `PROPAGATION_NESTED`ִɺ󣬸񱨴ع㣻ִбҲǻع\n\n### 2. demo ׼\n\nȷϸ󣬽Ϳʼˣ׼򵥵 demo\n\n׼һЩã\n\n```\n@Configuration\n@ComponentScan(\"org.springframework.learn.tx.demo03\")\n@EnableTransactionManagement(proxyTargetClass = true)\npublic class TxDemo03Config {\n\n    /**\n     * Դ\n     */\n    @Bean\n    public DataSource dataSource() throws Exception {\n        Driver driver = new com.mysql.jdbc.Driver();\n        String url = \"jdbc:mysql://localhost:3306/test\";\n        String username = \"root\";\n        String password = \"123\";\n        return new SimpleDriverDataSource(driver, url, username, password);\n    }\n\n    /**\n     * jdbcTemplateݿĲ\n     */\n    @Bean\n    public JdbcTemplate jdbcTemplate(DataSource dataSource) {\n        return new JdbcTemplate(dataSource);\n    }\n\n    /**\n     * \n     */\n    @Bean\n    public DataSourceTransactionManager transactionManager(DataSource dataSource) {\n        return new DataSourceTransactionManager(dataSource);\n    }\n\n}\n```\n\nϴ˵£\n\n- Դʹõ spring ṩ `SimpleDriverDataSource`Դܲ࣬ʺ򵥵 demo\n-  jdbc زҲʹ spring ṩ `jdbcTemplate`Ϊһ򵥵 demo `mybatis``jpa` \n- ʹõҲ spring ṩ `DataSourceTransactionManager` ԵԴ˵ȫ\n\n׼һ mysql ĲҪ\n\n```\n@Service\npublic class UserService {\n\n    @Autowired\n    private JdbcTemplate jdbcTemplate;\n\n    /**\n     * ݿʹ @Transactional \n     */\n    @Transactional(rollbackFor = Exception.class)\n    public int insert() {\n        String sql = \"insert into `user`(`login_name`, `nick`, `create_time`, `update_time`)\" \n                + \"values (?, ?, ?, ?)\";\n        int result = jdbcTemplate.update(sql, \"test\", \"test\", new Date(), new Date());\n        if(true) {\n            //throw new RuntimeException(\"׳쳣\");\n        }\n        System.out.println(result);\n        return result;\n    }\n\n}\n```\n\nࣺ\n\n```\npublic class TxDemo03Main {\n\n    public static void main(String[] args) {\n        AnnotationConfigApplicationContext applicationContext\n                = new AnnotationConfigApplicationContext(TxDemo03Config.class);\n        UserService userService = applicationContext.getBean(UserService.class);\n        userService.insert();\n\n    }\n}\n```\n\n demo ʮּ򵥣Ͳˣǽͨ demo һЩȲ̽ spring ĸ뼶𡢴ʽĴ\n\n### 3. `TransactionAspectSupport#invokeWithinTransaction`\n\nһƪǾ˵Ĵ `TransactionAspectSupport#invokeWithinTransaction` ǽص\n\nϴ룺\n\n```\nprotected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,\n        final InvocationCallback invocation) throws Throwable {\n    TransactionAttributeSource tas = getTransactionAttributeSource();\n\n    // 1. ȡ @Transactional \n    final TransactionAttribute txAttr = (tas != null \n            ? tas.getTransactionAttribute(method, targetClass) : null);\n\n    // 2. ȡIOCлȡ\n    final TransactionManager tm = determineTransactionManager(txAttr);\n\n    // ⲿֵĴ TransactionManager  ReactiveTransactionManager \n    ...\n\n    // 3.  TransactionManager תΪ PlatformTransactionManager\n    PlatformTransactionManager ptm = asPlatformTransactionManager(tm);\n    // 4. ȡȫ޶ʽΪ\"..\"\n    final String joinpointIdentification \n            = methodIdentification(method, targetClass, txAttr);\n    if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {\n        // 5. ȡϢ￪\n        TransactionInfo txInfo = createTransactionIfNecessary(\n                ptm, txAttr, joinpointIdentification);\n        Object retVal;\n        try {\n            // 6. ִоҵ\n            retVal = invocation.proceedWithInvocation();\n        }\n        catch (Throwable ex) {\n            // 7. 쳣ع\n            completeTransactionAfterThrowing(txInfo, ex);\n            throw ex;\n        }\n        finally {\n            // 8. ϢǽϢΪɵ\n            cleanupTransactionInfo(txInfo);\n        }\n\n        if (vavrPresent && VavrDelegate.isVavrTry(retVal)) {\n            TransactionStatus status = txInfo.getTransactionStatus();\n            if (status != null && txAttr != null) {\n                retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);\n            }\n        }\n        // 9. ύ\n        commitTransactionAfterReturning(txInfo);\n        return retVal;\n    }\n    else {\n        //  CallbackPreferringPlatformTransactionManager ͵ TransactionManager\n        ...\n    }\n}\n```\n\n `TransactionAspectSupport#invokeWithinTransaction` ݣעϸ˵ΪЩִй̡\n\n#### 3.1 ȡ `@Transactional` \n\nǻȡ `UserService#insert` ϱǵ `@Transactional` ãõĽ£\n\n![img](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-aefbe56da2db53982f73092a191e587fcc8.png)\n\n#### 3.2 ȡ\n\nȡķΪ `TransactionAspectSupport#determineTransactionManager`ֱӿ룺\n\n```\nprotected TransactionManager determineTransactionManager(@Nullable TransactionAttribute txAttr) {\n    if (txAttr == null || this.beanFactory == null) {\n        return getTransactionManager();\n    }\n\n    //  @Transaction עָʹspringлȡ\n    String qualifier = txAttr.getQualifier();\n    if (StringUtils.hasText(qualifier)) {\n        return determineQualifiedTransactionManager(this.beanFactory, qualifier);\n    }\n    // ָƣҲǴspringлȡ\n    else if (StringUtils.hasText(this.transactionManagerBeanName)) {\n        return determineQualifiedTransactionManager(\n            this.beanFactory, this.transactionManagerBeanName);\n    }\n    else {\n        // ֱָӷ\n        TransactionManager defaultTransactionManager = getTransactionManager();\n        if (defaultTransactionManager == null) {\n            // ӻлȡĬϵ\n            defaultTransactionManager = this.transactionManagerCache\n                    .get(DEFAULT_TRANSACTION_MANAGER_KEY);\n            if (defaultTransactionManager == null) {\n                // ʹ spring лȡһ\n                defaultTransactionManager = this.beanFactory.getBean(TransactionManager.class);\n                this.transactionManagerCache.putIfAbsent(\n                        DEFAULT_TRANSACTION_MANAGER_KEY, defaultTransactionManager);\n            }\n        }\n        return defaultTransactionManager;\n    }\n}\n```\n\nȻе㳤߼ǳԸ÷ܽ£\n\n1.  `@Transaction` עָʹ spring лȡ\n2. ָƣʹ spring лȡ\n3. ֱָӷ\n4. ϶㣬ֱӴ spring лȡΪ `TransactionManager`  bean\n\n `TxDemo03Config` УΪ `DataSourceTransactionManager`\n\n```\npublic DataSourceTransactionManager transactionManager(DataSource dataSource) {\n    return new DataSourceTransactionManager(dataSource);\n}\n```\n\nõҲ `DataSourceTransactionManager`\n\n![img](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-34ac74501b184c44f95432b31b21a041619.png)\n\n#### 3.3  `TransactionManager` תΪ `PlatformTransactionManager`\n\nûɶ˵ģ`DataSourceTransactionManager`  `PlatformTransactionManager` ࣬һת\n\n```\nprivate PlatformTransactionManager asPlatformTransactionManager(\n        @Nullable Object transactionManager) {\n    if (transactionManager == null || transactionManager instanceof PlatformTransactionManager) {\n        return (PlatformTransactionManager) transactionManager;\n    } else {\n        // ׸쳣\n        ...\n    }\n}\n```\n\n#### 3.4 ȡȫ޶\n\nһõȫ޶ʽΪ\"͡\"Ҳûɶ˵ģһõĽ£\n\n![img](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-656a73b9eb1cd3120d3586fe4f1302de373.png)\n\nƪľȷˣƪǼ\n\n------\n\n*ԭӣhttps://my.oschina.net/funcy/blog/4773459 ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע*"
  },
  {
    "path": "docs/Spring全家桶/Spring源码分析/Spring事务/Spring事务（二）：事务的执行流程.md",
    "content": " [spring ֮ʶ](https://my.oschina.net/funcy/blog/4773454)һУͨһ demo ʾʹ spring ܣȻ `@EnableTransactionManagement` עĹܣĽ spring ش롣\n\n### 1\\. 󴴽\n\nspring ǻ aop ģʹôһϵвĽͨԵķʽĴ̡\n\n [spring ֮ʶ](https://my.oschina.net/funcy/blog/4773454)ͨ `@EnableTransactionManagement` ע⣬ָע spring ע `InfrastructureAdvisorAutoProxyCreator` `AbstractAdvisorAutoProxyCreator` ࣬ɴģڽ `InfrastructureAdvisorAutoProxyCreator` Ĵ̡\n\n>  `AbstractAdvisorAutoProxyCreator` ķԼɣ [spring aop ֮ AnnotationAwareAspectJAutoProxyCreator ϣ](https://my.oschina.net/funcy/blog/4678817)  [spring aop ֮ AnnotationAwareAspectJAutoProxyCreator £](https://my.oschina.net/funcy/blog/4687961)ѾϸҪ aop вĵطҪϸ˽ spring aop βС飬Ķƪ¡\n\nǽ `AbstractAutoProxyCreator#postProcessBeforeInitialization`\n\n```\npublic Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {\n    ...\n    if (...) {\n        //1\\. shouldSkip:\n        // - AspectJAwareAdvisorAutoProxyCreator  shouldSkip ᴦ @Aspect ע࣬\n        //   е@Before/@After/@AroundעװΪAdvisorٵø(Ҳ\n        //   AbstractAutoProxyCreator)shouldSkip\n        // - InfrastructureAdvisorAutoProxyCreatorֱִAbstractAutoProxyCreatorshouldSkip\n        if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {\n            this.advisedBeans.put(cacheKey, Boolean.FALSE);\n            return null;\n        }\n    }\n    if(...)  {\n        ...\n\n        // 2\\. getAdvicesAndAdvisorsForBeanȡڵǰadvisor\n        Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(\n            beanClass, beanName, targetSource);\n        Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);\n        ...\n        return proxy;\n    }\n    return null;\n}\n\n```\n\nͬ㣬ѾעеĲ죬 `shouldSkip`ûɶ˵ģصչ `getAdvicesAndAdvisorsForBean(...)` \n\n#### 1.1 `BeanFactoryAdvisorRetrievalHelper#findAdvisorBeans`\n\nһ· `getAdvicesAndAdvisorsForBean(...)` еĲ `AspectJAwareAdvisorAutoProxyCreator` Ĳ̫𣬲иΪҪǿ££\n\n> BeanFactoryAdvisorRetrievalHelper#findAdvisorBeans\n\n```\npublic List<Advisor> findAdvisorBeans() {\n    String[] advisorNames = this.cachedAdvisorBeanNames;\n    if (advisorNames == null) {\n        // ҵǰbeanFactory Advisor  bean class\n        // AdvisorûʵAdvisorؽӿڣҲxmlָ\n        advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(\n                this.beanFactory, Advisor.class, true, false);\n        this.cachedAdvisorBeanNames = advisorNames;\n    }\n    ...\n    List<Advisor> advisors = new ArrayList<>();\n    for (String name : advisorNames) {\n        ...\n        // advisorbean namespringлȡ bean\n        advisors.add(this.beanFactory.getBean(name, Advisor.class));\n        ...\n    }\n    ...\n    return advisors;\n}\n\n```\n\nҪǻȡ spring е `advisor`ʵ `AnnotationAwareAspectJAutoProxyCreator` Ҳôȡģֻڻȡǰ`AnnotationAwareAspectJAutoProxyCreator`  `shouldSkip(...)` а `@Aspect` а `@Befor/@After/@Around` עķװɶӦ `Advisor` `InfrastructureAdvisorAutoProxyCreator` 򲻻ᣬһʼҲᵽˡ\n\n [spring ֮ʶ](https://my.oschina.net/funcy/blog/4773454)һУ `@EnableTransactionManagement` ע⹦ʱǷעͨ `@Bean` ע spring  `BeanFactoryTransactionAttributeSourceAdvisor` bean ͻ `BeanFactoryAdvisorRetrievalHelper#findAdvisorBeans` ȡ\n\n#### 1.2 `AopUtils#canApply(...)`\n\nŷһ·ߣžж `advisor` ܷĿ `class` ĵطˣ\n\n```\n/**\n * жadvisorܷĿclass\n */\npublic static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) {\n    ...\n    // жǷΪ PointcutAdvisoradvisorΪBeanFactoryTransactionAttributeSourceAdvisor\n    // ʵPointcutAdvisorĴִ\n    else if (advisor instanceof PointcutAdvisor) {\n        PointcutAdvisor pca = (PointcutAdvisor) advisor;\n        //ʹ PointcutAdvisor ж\n        return canApply(pca.getPointcut(), targetClass, hasIntroductions);\n    }\n    ...\n}\n\n/**\n * жadvisorܷĿclass\n */\npublic static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {\n    Assert.notNull(pc, \"Pointcut must not be null\");\n    //1\\. еǷų\n    if (!pc.getClassFilter().matches(targetClass)) {\n        return false;\n    }\n    // ȡƥMethodMatcher.TRUE ΪĬϵ MethodMatcher \n    MethodMatcher methodMatcher = pc.getMethodMatcher();\n    if (methodMatcher == MethodMatcher.TRUE) {\n        return true;\n    }\n    ...\n    // classestargetClassObjectиࡢнӿ\n    Set<Class<?>> classes = new LinkedHashSet<>();\n    // ʡԻȡtargetClassĸಽ\n    ...\n    for (Class<?> clazz : classes) {\n        // ȡ clazz ķǰķObjectи෽ӿڵĬϷ\n        Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);\n        for (Method method : methods) {\n            // 2\\. ƥĹؼ\n            if (introductionAwareMethodMatcher != null ?\n                    introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) :\n                    methodMatcher.matches(method, targetClass)) {\n                return true;\n            }\n        }\n    }\n    return false;\n}\n\n```\n\nһĴ `AnnotationAwareAspectJAutoProxyCreator` һģһǶǵͬķжϣڴĵ `advisor` ͬյõľƥҲͬ\n\nӴķƥ߼ `Pointcut` У `Pointcut`  `Advisor`ɼ `Advisor` ʮֹؼ `Advisor` Ϊ `BeanFactoryTransactionAttributeSourceAdvisor`Ǿࡣ\n\n#### 1.3 `BeanFactoryTransactionAttributeSourceAdvisor` ƥ\n\nһС ڵķУ֪ж `targetClass` ܷӦõǰ `advisor` ĹԴ `advisor`  `pointcut``pointcut` طжϹ\n\n*   ƥࣺ`pc.getClassFilter().matches(targetClass)`\n*   ƥ䷽`pc.getMethodMatcher().matches(method, targetClass)`\n\nһС Ǵ `BeanFactoryTransactionAttributeSourceAdvisor` ֣һƥ\n\n```\npublic class BeanFactoryTransactionAttributeSourceAdvisor \n        extends AbstractBeanFactoryPointcutAdvisor {\n\n    @Nullable\n    private TransactionAttributeSource transactionAttributeSource;\n\n    /**\n     *  pointcut\n     */\n    private final TransactionAttributeSourcePointcut pointcut = \n            new TransactionAttributeSourcePointcut() {\n        @Override\n        @Nullable\n        protected TransactionAttributeSource getTransactionAttributeSource() {\n            return transactionAttributeSource;\n        }\n    };\n\n    /**\n     *  transactionAttributeSource\n     */\n    public void setTransactionAttributeSource(TransactionAttributeSource \n            transactionAttributeSource) {\n        this.transactionAttributeSource = transactionAttributeSource;\n    }\n\n    /**\n     *  ClassFilter\n     */\n    public void setClassFilter(ClassFilter classFilter) {\n        this.pointcut.setClassFilter(classFilter);\n    }\n\n    /**\n     * ȡ pointcut\n     */\n    @Override\n    public Pointcut getPointcut() {\n        return this.pointcut;\n    }\n}\n\n```\n\nĴؼѾעˣܽ£`BeanFactoryTransactionAttributeSourceAdvisor#getPointcut` õ `pointcut` Ϊ `TransactionAttributeSourcePointcut` `private final TransactionAttributeSourcePointcut pointcut = new TransactionAttributeSourcePointcut() {...}` дġ\n\n`BeanFactoryTransactionAttributeSourceAdvisor`  `transactionAttributeSource` ʲôأ `ProxyTransactionManagementConfiguration` д `transactionAdvisor` Ĵ룺\n\n```\npublic class ProxyTransactionManagementConfiguration \n        extends AbstractTransactionManagementConfiguration {\n\n    // ʡ\n    ...\n\n    /**\n     * ȡSpring @Transactional ע⣬ӦԹSpringṹ\n     */\n    @Bean\n    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)\n    public TransactionAttributeSource transactionAttributeSource() {\n        return new AnnotationTransactionAttributeSource();\n    }\n\n    /**\n     * ǿ.\n     * transactionAttributeSourcetransactionAttributeSource() صĶ\n     */\n    @Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)\n    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)\n    public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor(\n            TransactionAttributeSource transactionAttributeSource,\n            TransactionInterceptor transactionInterceptor) {\n        BeanFactoryTransactionAttributeSourceAdvisor advisor = \n                new BeanFactoryTransactionAttributeSourceAdvisor();\n        // ࣬ @Transactional \n        advisor.setTransactionAttributeSource(transactionAttributeSource);\n        ...\n        return advisor;\n    }\n\n}\n\n```\n\nɴ˿֪`BeanFactoryTransactionAttributeSourceAdvisor`  `transactionAttributeSource` Ϊ `AnnotationTransactionAttributeSource`.\n\nٻص `BeanFactoryTransactionAttributeSourceAdvisor`ķ֪`getPointcut()` õ `TransactionAttributeSourcePointcut` Ȼࣺ\n\n```\nabstract class TransactionAttributeSourcePointcut \n        extends StaticMethodMatcherPointcut implements Serializable {\n\n    protected TransactionAttributeSourcePointcut() {\n        // ڹ췽 ClassFilter\n        setClassFilter(new TransactionAttributeSourceClassFilter());\n    }\n\n    /**\n     * pointcut  matches \n     */\n    @Override\n    public boolean matches(Method method, Class<?> targetClass) {\n        // õĽΪAnnotationTransactionAttributeSource\n        TransactionAttributeSource tas = getTransactionAttributeSource();\n        return (tas == null || tas.getTransactionAttribute(method, targetClass) != null);\n    }\n\n    /**\n     *  BeanFactoryTransactionAttributeSourceAdvisor ָ\n     */\n    @Nullable\n    protected abstract TransactionAttributeSource getTransactionAttributeSource();\n\n    /**\n     * ڲ࣬ʵ ClassFilter\n     */\n    private class TransactionAttributeSourceClassFilter implements ClassFilter {\n\n        /**\n         * ClassFilter  matches\n         */\n        @Override\n        public boolean matches(Class<?> clazz) {\n            // ǷΪTransactionalProxyPlatformTransactionManagerPersistenceExceptionTranslatorʵ\n            if (TransactionalProxy.class.isAssignableFrom(clazz) ||\n                    PlatformTransactionManager.class.isAssignableFrom(clazz) ||\n                    PersistenceExceptionTranslator.class.isAssignableFrom(clazz)) {\n                return false;\n            }\n            //ж TransactionAttributeSource ȡǷΪ\n            // õĽΪAnnotationTransactionAttributeSource\n            TransactionAttributeSource tas = getTransactionAttributeSource();\n            return (tas == null || tas.isCandidateClass(clazz));\n        }\n    }\n\n}\n\n```\n\nķǵõһҪĹ\n\n*   ƥࣺ`pc.getClassFilter().matches(targetClass)``ClassFilter` Ϊ `TransactionAttributeSourceClassFilter`\n\nƥĹҵˣƥ䷽Ĺأǽ `TransactionAttributeSourcePointcut#getMethodMatcher()`  `StaticMethodMatcherPointcut`\n\n```\npublic abstract class StaticMethodMatcherPointcut \n        extends StaticMethodMatcher implements Pointcut {\n    // ʡһЩ\n    ...\n\n    @Override\n    public final MethodMatcher getMethodMatcher() {\n        return this;\n    }\n}\n\n```\n\nصľȻ `this`ǸɶҪţϸ `TransactionAttributeSourcePointcut`̳ `StaticMethodMatcherPointcut`\n\n```\nabstract class TransactionAttributeSourcePointcut \n        extends StaticMethodMatcherPointcut implements Serializable {\n    // ʡһЩ\n    ...\n}\n\n```\n\nԣ`pc.getMethodMatcher()` õľ `TransactionAttributeSourcePointcut` `mathes(...)`  `TransactionAttributeSourcePointcut#matches`.\n\nڱСڵܽ·Ľ\n\n*   ƥࣺ`pc.getClassFilter().matches(targetClass)``ClassFilter` Ϊ `TransactionAttributeSourceClassFilter`\n*   ƥ䷽`pc.getMethodMatcher().matches(method, targetClass)``methodMatcher` Ϊ `TransactionAttributeSourcePointcut`\n*   У `TransactionAttributeSourcePointcut#getTransactionAttributeSource`صĽΪ `AnnotationTransactionAttributeSource`.\n\n#### 1.4 ƥ\n\n 1.2 ֣֪ǰ `advisor` ܷӦĿ classҪͬʱƥ\n\n*   ƥࣺ`pc.getClassFilter().matches(targetClass)``ClassFilter` Ϊ `TransactionAttributeSourceClassFilter`\n*   ƥ䷽`pc.getMethodMatcher().matches(method, targetClass)``methodMatcher` Ϊ `TransactionAttributeSourcePointcut`\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-133740c93d470e16ec8dc9a34106adb8fc8.png)\n\n*   `TransactionAttributeSourceClassFilter#matches`жϵǰǷΪǷΪ `TransactionalProxy``PlatformTransactionManager``PersistenceExceptionTranslator` ʵ࣬Ȼ `AnnotationTransactionAttributeSource#isCandidateClass` жϣ\n*   `TransactionAttributeSourcePointcut#matches` `AnnotationTransactionAttributeSource#getTransactionAttribute`ڼ̳йϵʵʵõ `AbstractFallbackTransactionAttributeSource#getTransactionAttribute`жϡ\n\nǾ¾ƥ̡\n\n##### `AnnotationTransactionAttributeSource#isCandidateClass`\n\nֱ⣬ `isCandidateClass` \n\n> AnnotationTransactionAttributeSource#isCandidateClass\n\n```\n@Override\npublic boolean isCandidateClass(Class<?> targetClass) {\n    // ҵеannotationParsersѭƥ\n    for (TransactionAnnotationParser parser : this.annotationParsers) {\n        if (parser.isCandidateClass(targetClass)) {\n            return true;\n        }\n    }\n    return false;\n}\n\n```\n\nԿѭ `TransactionAnnotationParser`  `isCandidateClass` `this.annotationParsers` ɶأͨԣ£\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-5e16d6769e5a74c2f3064afdd090de08d42.png)\n\n`this.annotationParsers` ֻ `SpringTransactionAnnotationParser`ǽ `isCandidateClass` \n\n```\npublic class SpringTransactionAnnotationParser \n        implements TransactionAnnotationParser, Serializable {\n\n    /**\n     * жǷ @Transactional ע\n     */\n    @Override\n    public boolean isCandidateClass(Class<?> targetClass) {\n        return AnnotationUtils.isCandidateClass(targetClass, Transactional.class);\n    }\n}\n\n```\n\nյõ `AnnotationUtils.isCandidateClass`жָǷ `@Transactional` ע⡣\n\nǾˣ`TransactionAttributeSourceClassFilter#matches` ųһЩ (`TransactionalProxy`/`PlatformTransactionManager`/`PersistenceExceptionTranslator` ) ջƥ `@Transactional` עࡣ\n\n##### `AnnotationTransactionAttributeSource#getTransactionAttribute`\n\nķƥɹ󣬲ܱʾɹƥ䣬ƥ `TransactionAttributeSourcePointcut#matches`ͬʱŻƥɹ`TransactionAttributeSourcePointcut#matches`  `AnnotationTransactionAttributeSource#getTransactionAttribute` ƥģǸȥ\n\n```\npublic abstract class AbstractFallbackTransactionAttributeSource \n        implements TransactionAttributeSource {\n\n    /**\n     * ȡ @Transactional ע\n     */\n    public TransactionAttribute getTransactionAttribute(Method method, \n            @Nullable Class<?> targetClass) {\n        if (method.getDeclaringClass() == Object.class) {\n            return null;\n        }\n\n        // ʡԴӻлȡ\n        ...\n        else {\n            // ȡ Transaction ԣ @Transactional ע\n            TransactionAttribute txAttr = computeTransactionAttribute(method, targetClass);\n            // ʡԷ뻺\n            ...\n            return txAttr;\n        }\n    }\n}\n\n```\n\n`AnnotationTransactionAttributeSource`  `getTransactionAttribute` Ǽ̳ `AbstractFallbackTransactionAttributeSource` ģǽķ `AbstractFallbackTransactionAttributeSource#getTransactionAttribute`ȡϵ `@Transactional` עԣǸ `computeTransactionAttribute(...)`\n\n> AbstractFallbackTransactionAttributeSource\n\n```\nprotected TransactionAttribute computeTransactionAttribute(Method method, \n        @Nullable Class<?> targetClass) {\n    // ĬϱҪ public ֧\n    if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {\n        return null;\n    }\n    // 1\\. ȡȷеķ紫classIFooʵʵĵclassDefaultFoo\n    //    ôӦý IFoo#method תΪ DefaultFoo#method\n    Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);\n    // 2\\. ӷϻȡ @Transactional \n    TransactionAttribute txAttr = findTransactionAttribute(specificMethod);\n    if (txAttr != null) {\n        return txAttr;\n    }\n    // 3\\. ϻȡ @Transaction \n    txAttr = findTransactionAttribute(specificMethod.getDeclaringClass());\n    if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {\n        return txAttr;\n    }\n    if (specificMethod != method) {\n        // 4\\. ȷеķҲҴķϵ\n        txAttr = findTransactionAttribute(method);\n        if (txAttr != null) {\n            return txAttr;\n        }\n        // 5\\. ϶ûҵȷеϵ\n        txAttr = findTransactionAttribute(method.getDeclaringClass());\n        if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {\n            return txAttr;\n        }\n    }\n    // 6\\. ûлȡշnull\n    return null;\n}\n\n```\n\nϷ̣ܽȡ `@Transactional` £\n\n1.  ķתΪȷеķ紫 `class`  `IFoo`ʵʵĵ `class`  `DefaultFoo`ͻὫ `IFoo#method` תΪ `DefaultFoo#method`\n2.  ȷеķϻȡ `@Transactional` \n3.  ûлȡʹȷеĴϻȡ `@Transaction` \n4.  ûлȡʹķϻȡ `@Transaction` \n5.  ûлȡʹϻȡ `@Transaction` \n6.  ϶ûлȡͷ `null`\n\nspring δӷϻȡ `@Transactional` أȥ\n\n> AnnotationTransactionAttributeSource\n\n```\n    // ӷϻȡ @Transactional \n    protected TransactionAttribute findTransactionAttribute(Method method) {\n        return determineTransactionAttribute(method);\n    }\n\n    // ϻȡ @Transactional \n    protected TransactionAttribute findTransactionAttribute(Class<?> clazz) {\n        return determineTransactionAttribute(clazz);\n    }\n\n    // յõķ\n    protected TransactionAttribute determineTransactionAttribute(AnnotatedElement element) {\n        for (TransactionAnnotationParser parser : this.annotationParsers) {\n            //  @Transactional ע\n            TransactionAttribute attr = parser.parseTransactionAnnotation(element);\n            if (attr != null) {\n                return attr;\n            }\n        }\n        return null;\n    }\n\n```\n\nǶǵ `AnnotationTransactionAttributeSource#determineTransactionAttribute` ȡģ `AnnotationTransactionAttributeSource#determineTransactionAttribute`  `TransactionAnnotationParser#parseTransactionAnnotation`  `this.annotationParsers` ǰѾˣֻһࣺ`SpringTransactionAnnotationParser`Ǹȥ\n\n> SpringTransactionAnnotationParser\n\n```\n    /**\n     * ȡ Transactional ע⣬򷵻 null\n     */\n    public TransactionAttribute parseTransactionAnnotation(AnnotatedElement element) {\n        // ȡ Transactional ע⣬򷵻 null\n        AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(\n                element, Transactional.class, false, false);\n        if (attributes != null) {\n            return parseTransactionAnnotation(attributes);\n        }\n        else {\n            return null;\n        }\n    }\n\n    /**\n     *  Transactional עľ\n     */\n    protected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) {\n        RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();\n        // Ĵʽ\n        Propagation propagation = attributes.getEnum(\"propagation\");\n        rbta.setPropagationBehavior(propagation.value());\n        // ĸ뼶\n        Isolation isolation = attributes.getEnum(\"isolation\");\n        rbta.setIsolationLevel(isolation.value());\n        // ĳʱʱ\n        rbta.setTimeout(attributes.getNumber(\"timeout\").intValue());\n        // ǷΪֻ\n        rbta.setReadOnly(attributes.getBoolean(\"readOnly\"));\n        rbta.setQualifier(attributes.getString(\"value\"));\n        // ع쳣\n        List<RollbackRuleAttribute> rollbackRules = new ArrayList<>();\n        for (Class<?> rbRule : attributes.getClassArray(\"rollbackFor\")) {\n            rollbackRules.add(new RollbackRuleAttribute(rbRule));\n        }\n        for (String rbRule : attributes.getStringArray(\"rollbackForClassName\")) {\n            rollbackRules.add(new RollbackRuleAttribute(rbRule));\n        }\n        // ع쳣\n        for (Class<?> rbRule : attributes.getClassArray(\"noRollbackFor\")) {\n            rollbackRules.add(new NoRollbackRuleAttribute(rbRule));\n        }\n        for (String rbRule : attributes.getStringArray(\"noRollbackForClassName\")) {\n            rollbackRules.add(new NoRollbackRuleAttribute(rbRule));\n        }\n        rbta.setRollbackRules(rollbackRules);\n\n        return rbta;\n    }\n\n```\n\nԿ`Transactional` עĸԽ `RuleBasedTransactionAttribute`.\n\nˣǾˣ`TransactionAttributeSourcePointcut#matches` ж򷽷û `Transactional` ע⡣\n\n#### 1.5 Ĵ\n\nĴ `AbstractAutoProxyCreator#postProcessAfterInitialization` ɵģͬ aop һģһͲٷˣ˽Сɲ鿴 [spring aop ֮ AnnotationAwareAspectJAutoProxyCreator £](https://my.oschina.net/funcy/blog/4687961)\n\n### 2\\. ִ\n\nִз棬 aop ִ̲һͨ `Advisor` ҵӦ `Advice`ͨ `Advice` ҵӦ `methodInterceptor`ִе `MethodInterceptor#invoke`  `MethodInterceptor` Ϊ `TransactionInterceptor` `ProxyTransactionManagementConfiguration` ͨ `@Bean` עġ\n\n aop ̣ǲط [spring aop ֮ jdk ̬](https://my.oschina.net/funcy/blog/4696654) [ spring aop ֮ cglib ](https://my.oschina.net/funcy/blog/4696655) ϸȤСвģֱ `TransactionInterceptor#invoke` ִ̡\n\nĴ `TransactionInterceptor#invoke` У\n\n> TransactionInterceptor#invoke\n\n```\npublic Object invoke(MethodInvocation invocation) throws Throwable {\n    Class<?> targetClass = (invocation.getThis() != null \n        ? AopUtils.getTargetClass(invocation.getThis()) : null);\n    // ¿\n    return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);\n}\n\n```\n\nĴ߼ `TransactionAspectSupport#invokeWithinTransaction` У\n\n> TransactionAspectSupport#invokeWithinTransaction\n\n```\nprotected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,\n        final InvocationCallback invocation) throws Throwable {\n    TransactionAttributeSource tas = getTransactionAttributeSource();\n    // ȡ@Transactional\n    final TransactionAttribute txAttr = (tas != null \n        ? tas.getTransactionAttribute(method, targetClass) : null);\n    // ȡIOCлȡ\n    final TransactionManager tm = determineTransactionManager(txAttr);\n\n    // ʡ ReactiveTransactionManager Ĵ\n    ...\n\n    PlatformTransactionManager ptm = asPlatformTransactionManager(tm);\n    // ȡȫ޶ʽΪ\"..\"\n    final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);\n\n    // Ĵ߼ҲǽҪĵط\n    if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {\n        // 1\\. \n        TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);\n        Object retVal;\n        try {\n            // 2\\. ִоҵ\n            retVal = invocation.proceedWithInvocation();\n        }\n        catch (Throwable ex) {\n            // 3\\. 쳣ع\n            completeTransactionAfterThrowing(txInfo, ex);\n            throw ex;\n        }\n        finally {\n            // Ϣ\n            cleanupTransactionInfo(txInfo);\n        }\n        if (vavrPresent && VavrDelegate.isVavrTry(retVal)) {\n            TransactionStatus status = txInfo.getTransactionStatus();\n            if (status != null && txAttr != null) {\n                retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);\n            }\n        }\n        // 4\\. ύ񣬴лжǷ֧\n        commitTransactionAfterReturning(txInfo);\n        return retVal;\n    }\n    else {\n        // ʡ\n        ...\n    }\n}\n\n```\n\nϷȫˣ£\n\n1.  \n2.  ִҵ\n3.  쳣ع\n4.  ύ\n\nľǽһƪ·ֻҪĴд˽⼴ɡ\n\n### 3\\. ܽ\n\nҪдĴִ̣ʵЩͬ aop һ£ط aop ͬĲ֣\n\n*   ڴĴ棬жϵǰܷʹ `BeanFactoryTransactionAttributeSourceAdvisor`ص `TransactionAttributeSourceClassFilter#matches`  `TransactionAttributeSourcePointcut#matches` жϵĺڣ\n\n*   ڷִϵģԷ `TransactionInterceptor#invoke` ִ̣ЩĿύ쳣ع̸ƽʹõĲ𲻴󣬲ľϸǲûз\n\nص\bĴִִ̣еľϸƪٷ\n\n* * *\n\n_ԭӣ[https://my.oschina.net/funcy/blog/4773457](https://my.oschina.net/funcy/blog/4773457) ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע_"
  },
  {
    "path": "docs/Spring全家桶/Spring源码分析/Spring事务/Spring事务（五）：事务的隔离级别与传播方式的处理03.md",
    "content": "ǡĸ뼶봫ʽĴĵ 3 ƪģǼ\n\nᵽĿ`doBegin(...)``suspend(...)`봴㣨`createAndHoldSavepoint(...)`ĲĽЩʵ֡\n\n### 1. `doBegin(...)`µ\n\nķΪ `DataSourceTransactionManager#doBegin`£\n\n```\nprotected void doBegin(Object transaction, TransactionDefinition definition) {\n    DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;\n    Connection con = null;\n\n    try {\n        // 1\\. ȡݿ\n        if (!txObject.hasConnectionHolder() ||\n                txObject.getConnectionHolder().isSynchronizedWithTransaction()) {\n            // getConnection(): ȡݿӣobtainDataSource()ȡԴ\n            Connection newCon = obtainDataSource().getConnection();\n            txObject.setConnectionHolder(new ConnectionHolder(newCon), true);\n        }\n        // ｫ synchronizedWithTransaction Ϊtrue\n        txObject.getConnectionHolder().setSynchronizedWithTransaction(true);\n        con = txObject.getConnectionHolder().getConnection();\n\n        // 2\\. ĸ뼶\n\n        Integer previousIsolationLevel \n                = DataSourceUtils.prepareConnectionForTransaction(con, definition);\n        txObject.setPreviousIsolationLevel(previousIsolationLevel);\n        // ֻ\n        txObject.setReadOnly(definition.isReadOnly());\n\n        // 3\\. \n        if (con.getAutoCommit()) {\n            txObject.setMustRestoreAutoCommit(true);\n            // رԶύҲǿ\n            con.setAutoCommit(false);\n        }\n        // 4\\. ֻ\n        prepareTransactionalConnection(con, definition);\n        // ļ\n        txObject.getConnectionHolder().setTransactionActive(true);\n        // 5\\. ĳʱʱ\n        int timeout = determineTimeout(definition);\n        if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {\n            txObject.getConnectionHolder().setTimeoutInSeconds(timeout);\n        }\n\n        // 6\\. Դӵǰ߳\n        if (txObject.isNewConnectionHolder()) {\n            TransactionSynchronizationManager.bindResource(\n                    obtainDataSource(), txObject.getConnectionHolder());\n            }\n        }\n    } catch (Throwable ex) {\n        // 쳣رո\n        if (txObject.isNewConnectionHolder()) {\n            DataSourceUtils.releaseConnection(con, obtainDataSource());\n            txObject.setConnectionHolder(null, false);\n        }\n        throw new CannotCreateTransactionException(...);\n    }\n}\n\n```\n\nϴ뻹ͦģעҲȷˣԹؼһ\n\n#### 1.1 ȡݿ\n\nݿӵĻȡǺܼ򵥵ģ£\n\n```\nConnection newCon = obtainDataSource().getConnection();\n\n```\n\nʵǵ `javax.sql.DataSource#getConnection()` \n\n#### 1.2 ĸ뼶\n\n `@Transactional` Уǿʹ `isolation` ָĸ뼶\n\n```\npublic @interface Transactional {\n    /**\n     * ָĸ뼶\n     */\n    Isolation isolation() default Isolation.DEFAULT;\n    ...\n}\n\n```\n\nָʹĬϵĸ뼶Ҳʹݿõġ\n\nspring 뼶ķΪ `DataSourceUtils#prepareConnectionForTransaction`£\n\n```\npublic static Integer prepareConnectionForTransaction(Connection con, \n        @Nullable TransactionDefinition definition) throws SQLException {\n    Assert.notNull(con, \"No Connection specified\");\n    if (definition != null && definition.isReadOnly()) {\n        try {\n            // Ϊֻģʽ\n            con.setReadOnly(true);\n        }\n        catch (SQLException | RuntimeException ex) {\n            ...\n        }\n    }\n\n    Integer previousIsolationLevel = null;\n    if (definition != null && definition.getIsolationLevel() \n            != TransactionDefinition.ISOLATION_DEFAULT) {\n        int currentIsolation = con.getTransactionIsolation();\n        if (currentIsolation != definition.getIsolationLevel()) {\n            // õ֮ǰĸ뼶£ҪΪԭĸ뼶\n            previousIsolationLevel = currentIsolation;\n            // ݿĸ뼶𣬵õǣ\n            // java.sql.Connection.setTransactionIsolation\n            con.setTransactionIsolation(definition.getIsolationLevel());\n        }\n    }\n    return previousIsolationLevel;\n}\n\n```\n\n ã\n\n1.  Ϊֻģʽõ `java.sql.Connection#setReadOnly` \n2.  ø뼶𣺵õ `java.sql.Connection.setTransactionIsolation` \n\nֻģʽҲǿ `@Transactional` õģ\n\n```\npublic @interface Transactional {\n    /**\n     * ֻ\n     */\n    boolean readOnly() default false;\n    ...\n}\n\n```\n\n#### 1.3 \n\nĵʱˣǰ̵ô࣬ΪһĲ񡣿Ĵ£\n\n```\nif (con.getAutoCommit()) {\n    txObject.setMustRestoreAutoCommit(true);\n    // رԶύҲǿ\n    con.setAutoCommit(false);\n}\n\n```\n\nΪжԶύǷˣͽΪ falseõҲ `java.sql` ķ\n\n*   ȡԶύ״̬`java.sql.Connection#getAutoCommit`\n*   Զύ״̬`java.sql.Connection#setAutoCommit`\n\n#### 1.4 ֻ\n\nǰ `1.2 ĸ뼶`Уͨ `java.sql.Connection#setReadOnly` ΪֻˣﻹһãΪ `DataSourceTransactionManager#prepareTransactionalConnection`\n\n```\nprotected void prepareTransactionalConnection(Connection con, TransactionDefinition definition)\n        throws SQLException {\n    if (isEnforceReadOnly() && definition.isReadOnly()) {\n        try (Statement stmt = con.createStatement()) {\n            // ֻҪsql\n            stmt.executeUpdate(\"SET TRANSACTION READ ONLY\");\n        }\n    }\n}\n\n```\n\nһִͨ sql  `SET TRANSACTION READ ONLY` Ϊֻ\n\n#### 1.5 ĳʱʱ\n\n `@Transactional` עУǿʹ `timeout` ָĳʱʱ䣺\n\n```\npublic @interface Transactional {\n    /**\n     * óʱʱ\n     */\n    int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;\n    ...\n}\n\n```\n\nõĳʱʱõǽ `ResourceHolderSupport#setTimeoutInSeconds`\n\n```\npublic abstract class ResourceHolderSupport implements ResourceHolder {\n    /**\n     * ֹʱ\n     */\n    private Date deadline;\n\n    /**\n     * ʱ䣬λ\n     */\n    public void setTimeoutInSeconds(int seconds) {\n        setTimeoutInMillis(seconds * 1000L);\n    }\n\n    /**\n     * ʱ䣬λ\n     * תΪ ֹʱ\n     */\n    public void setTimeoutInMillis(long millis) {\n        this.deadline = new Date(System.currentTimeMillis() + millis);\n    }\n\n    /**\n     * ȡֹʱ\n     */\n    @Nullable\n    public Date getDeadline() {\n        return this.deadline;\n    }\n\n    /**\n     * ȡʣʱ䣬λ\n     */\n    public int getTimeToLiveInSeconds() {\n        double diff = ((double) getTimeToLiveInMillis()) / 1000;\n        int secs = (int) Math.ceil(diff);\n        checkTransactionTimeout(secs <= 0);\n        return secs;\n    }\n\n    /**\n     * ȡʣʱ䣬λ\n     */\n    public long getTimeToLiveInMillis() throws TransactionTimedOutException{\n        if (this.deadline == null) {\n            throw new IllegalStateException(\"No timeout specified for this resource holder\");\n        }\n        long timeToLive = this.deadline.getTime() - System.currentTimeMillis();\n        checkTransactionTimeout(timeToLive <= 0);\n        return timeToLive;\n    }\n\n    ...\n}\n\n```\n\n `ResourceHolderSupport` άһԱ `deadline`ֹʱ䣩ĳʱʱնתΪ `deadline`\n\nȡʣʱʱҲ `deadline` õصʣʱ֡\n\n`txObject.getConnectionHolder().setTimeoutInSeconds(timeout)` ֻǽʱʱõ `ConnectionHolder` ĳԱУ`ConnectionHolder`  `ResourceHolderSupport` ࣩƺݿûɶϵݿôʱأ\n\nò˵ʱĿеңͨҵģʱʱ `DataSourceUtils#applyTimeout` УпνǾǧɽˮ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-9fc1a75dc7b0644269b6dba16dfd5d0e676.png)\n\nлԹܣû֪Ҫòҵʱãʹõ `jdbcTemplate` `orm` £óʱʱӦûͬ \n\nǿ `DataSourceUtils#applyTimeout` ôóʱʱģ\n\n```\npublic static void applyTimeout(Statement stmt, @Nullable DataSource dataSource, int timeout) \n        throws SQLException {\n    Assert.notNull(stmt, \"No Statement specified\");\n    ConnectionHolder holder = null;\n    if (dataSource != null) {\n        holder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);\n    }\n    if (holder != null && holder.hasTimeout()) {\n        // ǻȡʣĳʱʱ䣬 ConnectionHolder.dateline õ\n        stmt.setQueryTimeout(holder.getTimeToLiveInSeconds());\n    }\n    else if (timeout >= 0) {\n        // jdbcTemplate Ҳòѯʱʱ\n        stmt.setQueryTimeout(timeout);\n    }\n}\n\n```\n\nյõ `java.sql.Statement#setQueryTimeout` óʱʱġ\n\n#### 1.6 Դӵǰ߳\n\n鴦ɺ󣬽ǰԴˣΪ `TransactionSynchronizationManager#bindResource`:\n\n```\n/**\n * resources ŵǰ߳еԴ\n * дŵΪһ MapMap  key ΪԴvalue ΪԴӦ\n */\nprivate static final ThreadLocal<Map<Object, Object>> resources =\n        new NamedThreadLocal<>(\"Transactional resources\");\n\n/**\n * 󶨲\n */\npublic static void bindResource(Object key, Object value) throws IllegalStateException {\n    Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);\n    Assert.notNull(value, \"Value must not be null\");\n    Map<Object, Object> map = resources.get();\n    if (map == null) {\n        map = new HashMap<>();\n        resources.set(map);\n    }\n    // ԴӴŵmap\n    Object oldValue = map.put(actualKey, value);\n    if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) {\n        oldValue = null;\n    }\n    if (oldValue != null) {\n        throw new IllegalStateException(\"Already value [\" + oldValue + \"] for key [\" +\n                actualKey + \"] bound to thread [\" + Thread.currentThread().getName() + \"]\");\n    }\n\n```\n\n һĲǱȽϼ򵥵ģǽԴӷŽ `resources` УӶ뵱ǰ̵߳İ󶨲\n\n### 2. `suspend(...)`\n\nĲΪ `AbstractPlatformTransactionManager#suspend`£\n\n```\nprotected final SuspendedResourcesHolder suspend(@Nullable Object transaction) \n        throws TransactionException {\n    // ͬȹͬ\n    if (TransactionSynchronizationManager.isSynchronizationActive()) {\n        List<TransactionSynchronization> suspendedSynchronizations = doSuspendSynchronization();\n        try {\n            Object suspendedResources = null;\n            if (transaction != null) {\n                // \n                suspendedResources = doSuspend(transaction);\n            }\n            // \n            String name = TransactionSynchronizationManager.getCurrentTransactionName();\n            TransactionSynchronizationManager.setCurrentTransactionName(null);\n            // ֻ״̬\n            boolean readOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();\n            TransactionSynchronizationManager.setCurrentTransactionReadOnly(false);\n            // ø뼶\n            Integer isolationLevel = TransactionSynchronizationManager\n                    .getCurrentTransactionIsolationLevel();\n            TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(null);\n            // 񼤻״̬\n            boolean wasActive = TransactionSynchronizationManager.isActualTransactionActive();\n            TransactionSynchronizationManager.setActualTransactionActive(false);\n            // ع\n            return new SuspendedResourcesHolder(\n                    suspendedResources, suspendedSynchronizations, name, readOnly, \n                    isolationLevel, wasActive);\n        }\n        catch (RuntimeException | Error ex) {\n            doResumeSynchronization(suspendedSynchronizations);\n            throw ex;\n        }\n    }\n    else if (transaction != null) {\n        Object suspendedResources = doSuspend(transaction);\n        return new SuspendedResourcesHolder(suspendedResources);\n    }\n    else {\n        return null;\n    }\n}\n\n```\n\n`suspend(...)` ҪľǹĲˣҲ `doSuspend(transaction)`÷ λ `` Уֱӿ룺\n\n```\nprotected Object doSuspend(Object transaction) {\n    DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;\n    txObject.setConnectionHolder(null);\n    // \n    return TransactionSynchronizationManager.unbindResource(obtainDataSource());\n}\n\n```\n\n `TransactionSynchronizationManager.unbindResource` \n\n```\n/**\n * 󶨲\n */\npublic static Object unbindResource(Object key) throws IllegalStateException {\n    Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);\n    // \n    Object value = doUnbindResource(actualKey);\n    if (value == null) {\n        throw new IllegalStateException(...);\n    }\n    return value;\n}\n\n/**\n * 󶨲\n */\nprivate static Object doUnbindResource(Object actualKey) {\n    Map<Object, Object> map = resources.get();\n    if (map == null) {\n        return null;\n    }\n    // ƳԴ\n    Object value = map.remove(actualKey);\n    if (map.isEmpty()) {\n        resources.remove();\n    }\n    if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {\n        value = null;\n    }\n    return value;\n}\n\n```\n\nڿʱǽԴӰ󶨵ǰ̣߳ʱ ǽԴӽ뵱ǰ̵߳İ󶨹ϵ\n\n### 3. `createAndHoldSavepoint(...)`\n\nĴ `AbstractTransactionStatus#createAndHoldSavepoint` д£\n\n```\n    // \n    private Object savepoint;\n\n    // \n    public void createAndHoldSavepoint() throws TransactionException {\n        setSavepoint(getSavepointManager().createSavepoint());\n    }\n\n    protected void setSavepoint(@Nullable Object savepoint) {\n        this.savepoint = savepoint;\n    }\n\n```\n\nţֻ``ı棨ҲǸֵ `AbstractTransactionStatus` ĳԱҪ˽ⱣĴÿ `getSavepointManager().createSavepoint()`뵽 `JdbcTransactionObjectSupport#createSavepoint`\n\n```\npublic Object createSavepoint() throws TransactionException {\n    ConnectionHolder conHolder = getConnectionHolderForSavepoint();\n    try {\n        if (!conHolder.supportsSavepoints()) {\n            throw new NestedTransactionNotSupportedException(...);\n        }\n        if (conHolder.isRollbackOnly()) {\n            throw new CannotCreateTransactionException(...);\n        }\n        // \n        return conHolder.createSavepoint();\n    }\n    catch (SQLException ex) {\n        throw new CannotCreateTransactionException(\"Could not create JDBC savepoint\", ex);\n    }\n}\n\n```\n\nõ `ConnectionHolder#createSavepoint` ԭ `ConnectionHolder` дİ\n\n```\n// ǰ׺\npublic static final String SAVEPOINT_NAME_PREFIX = \"SAVEPOINT_\";\n\n// \nprivate int savepointCounter = 0;\n\npublic Savepoint createSavepoint() throws SQLException {\n    this.savepointCounter++;\n    // ﴴ㣬õ java.sql.Connection#setSavepoint(java.lang.String) \n    return getConnection().setSavepoint(SAVEPOINT_NAME_PREFIX + this.savepointCounter);\n}\n\n```\n\nǰ׺Ϊ `SAVEPOINT_`ÿһ㣬`savepointCounter` ļͼ 1ձΪ `SAVEPOINT_1``SAVEPOINT_2`...\n\nյõķ `java.sql.Connection#setSavepoint(java.lang.String)`Ȼ jdk ṩķǻᷢĴ󲿷ֲ spring  jdk ķװ\n\nˣĵķȵˣύعع㡢ָȣƪ¼\n\n* * *\n\n_ԭӣ[https://my.oschina.net/funcy/blog/4947826](https://my.oschina.net/funcy/blog/4947826) ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע_"
  },
  {
    "path": "docs/Spring全家桶/Spring源码分析/Spring事务/Spring事务（六）：事务的隔离级别与传播方式的处理04.md",
    "content": "ǡĸ뼶봫ʽĴĵ 4 ƪģǼ\n\n#### 3.6 ִоҵ\n\n£\n\n```\nretVal = invocation.proceedWithInvocation();\n\n```\n\nջõҵ񷽷 `UserService#insert`Ĳ̽һùȥģҪ˽ù̵СԲο aop ز\n\n*   [spring aop ֮ jdk ̬](https://my.oschina.net/funcy/blog/4696654)\n*   [spring aop ֮ cglib ](https://my.oschina.net/funcy/blog/4696655)\n\n#### 3.7 쳣ع\n\n쳣ķΪ `TransactionAspectSupport#completeTransactionAfterThrowing`£\n\n```\nprotected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {\n    if (txInfo != null && txInfo.getTransactionStatus() != null) {\n        // 쳣ϲŻع\n        if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {\n            try {\n                txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());\n            }\n            catch (...) {\n                ...\n            }\n        }\n        else {\n            try {\n                // 쳣ϣʹִгҲύ\n                txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());\n            }\n            catch (...) {\n                ...\n            }\n        }\n    }\n}\n\n```\n\n쳣ֱӽлعģҪж쳣ͣҪع쳣Żع\n\n##### жϵǰ쳣ǷҪع\n\nж쳣ǷϵķΪ `RuleBasedTransactionAttribute#rollbackOn`\n\n```\npublic boolean rollbackOn(Throwable ex) {\n    RollbackRuleAttribute winner = null;\n    int deepest = Integer.MAX_VALUE;\n    if (this.rollbackRules != null) {\n        for (RollbackRuleAttribute rule : this.rollbackRules) {\n            // ȡ쳣\n            int depth = rule.getDepth(ex);\n            // ʾǰ쳣Ҫع\n            if (depth >= 0 && depth < deepest) {\n                deepest = depth;\n                winner = rule;\n            }\n        }\n    }\n    if (winner == null) {\n        return super.rollbackOn(ex);\n    }\n    return !(winner instanceof NoRollbackRuleAttribute);\n}\n\n```\n\nȡķΪ `RollbackRuleAttribute#getDepth(Throwable)`£\n\n```\npublic int getDepth(Throwable ex) {\n    return getDepth(ex.getClass(), 0);\n}\n\nprivate int getDepth(Class<?> exceptionClass, int depth) {\n    if (exceptionClass.getName().contains(this.exceptionName)) {\n        // Found it!\n        return depth;\n    }\n    // If we've gone as far as we can go and haven't found it...\n    if (exceptionClass == Throwable.class) {\n        return -1;\n    }\n    // ݹȡ\n    return getDepth(exceptionClass.getSuperclass(), depth + 1);\n}\n\n```\n\nʵֺܼ򵥣ǵݹȡ `exception` ĸ࣬ҵ˾ͷصݹĴҵ `Throwable`ͷ - 1.\n\n֮ʹжǷҪعԭûع쳣ʱ쳣ƣ\n\n```\npublic @interface Transactional {\n    ...\n\n    // עַ\n    String[] rollbackForClassName() default {};\n}\n\n```\n\n˲ʹ `ex instanceof Exception` ķʽжܷع\n\n`RuleBasedTransactionAttribute#rollbackOn` е `RollbackRuleAttribute`  `NoRollbackRuleAttribute` ɶأ `rollbackFor`  `noRollbackFor` İװ࣬ `SpringTransactionAnnotationParser#parseTransactionAnnotation(AnnotationAttributes)` ã£\n\n```\nprotected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) {\n    RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();\n    ...\n    // ع쳣\n    List<RollbackRuleAttribute> rollbackRules = new ArrayList<>();\n    for (Class<?> rbRule : attributes.getClassArray(\"rollbackFor\")) {\n        rollbackRules.add(new RollbackRuleAttribute(rbRule));\n    }\n    for (String rbRule : attributes.getStringArray(\"rollbackForClassName\")) {\n        rollbackRules.add(new RollbackRuleAttribute(rbRule));\n    }\n    // ع쳣\n    for (Class<?> rbRule : attributes.getClassArray(\"noRollbackFor\")) {\n        rollbackRules.add(new NoRollbackRuleAttribute(rbRule));\n    }\n    for (String rbRule : attributes.getStringArray(\"noRollbackForClassName\")) {\n        rollbackRules.add(new NoRollbackRuleAttribute(rbRule));\n    }\n    // \n    rbta.setRollbackRules(rollbackRules);\n    return rbta;\n}\n\n```\n\n##### ع\n\nع `AbstractPlatformTransactionManager#rollback` дݿĻع⣬һЩصǾͲˣֱӿؼĻع롣ǰķĴʱഫǻع񣬲 `PROPAGATION_NESTED` ⣬ǻع㣬Ƿֱؼ룺\n\n1. ع\n\n   عķΪ `DataSourceTransactionManager#doRollback`յõ `java.sql.Connection` ķ\n\n   ```\n   protected void doRollback(DefaultTransactionStatus status) {\n       DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();\n       // ȡӣConnection Ϊ java.sql.Connection\n       Connection con = txObject.getConnectionHolder().getConnection();\n       try {\n           con.rollback();\n       }\n       catch (SQLException ex) {\n           throw new TransactionSystemException(\"Could not roll back JDBC transaction\", ex);\n       }\n   }\n   \n   ```\n\n2. ع عĲ `AbstractTransactionStatus#rollbackToHeldSavepoint` У\n\n   ```\n   public void rollbackToHeldSavepoint() throws TransactionException {\n       Object savepoint = getSavepoint();\n       if (savepoint == null) {\n           throw new TransactionUsageException(...);\n       }\n       // ع\n       getSavepointManager().rollbackToSavepoint(savepoint);\n       // ͷű\n       getSavepointManager().releaseSavepoint(savepoint);\n       // Ϊnull\n       setSavepoint(null);\n   }\n   \n   ```\n\n   Ҫعͷű㡣\n\n   عĲ `JdbcTransactionObjectSupport#rollbackToSavepoint` \n\n   ```\n   public void rollbackToSavepoint(Object savepoint) throws TransactionException {\n       ConnectionHolder conHolder = getConnectionHolderForSavepoint();\n       try {\n           conHolder.getConnection().rollback((Savepoint) savepoint);\n           conHolder.resetRollbackOnly();\n       }\n       catch (Throwable ex) {\n           throw new TransactionSystemException(\"Could not roll back to JDBC savepoint\", ex);\n       }\n   }\n   \n   ```\n\n   ͷűĲ `JdbcTransactionObjectSupport#releaseSavepoint` \n\n   ```\n   public void releaseSavepoint(Object savepoint) throws TransactionException {\n       ConnectionHolder conHolder = getConnectionHolderForSavepoint();\n       try {\n           conHolder.getConnection().releaseSavepoint((Savepoint) savepoint);\n       }\n       catch (Throwable ex) {\n           logger.debug(\"Could not explicitly release JDBC savepoint\", ex);\n       }\n   }\n   \n   ```\n\n   նǵ `java.sql.Connection` ṩķɲ\n\n##### ύ\n\nύύĴΪ\n\n```\ntxInfo.getTransactionManager().commit(txInfo.getTransactionStatus());\n\n```\n\nǸһֱ `AbstractPlatformTransactionManager#processCommit`\n\n```\nprivate void processCommit(DefaultTransactionStatus status) throws TransactionException {\n    try {\n        ...\n\n        try {\n            if (status.hasSavepoint()) {\n                ...\n                unexpectedRollback = status.isGlobalRollbackOnly();\n                // 1\\. ͷű\n                status.releaseHeldSavepoint();\n            }\n            else if (status.isNewTransaction()) {\n                ...\n                unexpectedRollback = status.isGlobalRollbackOnly();\n                // 2\\. ύ\n                doCommit(status);\n            }\n            else if (isFailEarlyOnGlobalRollbackOnly()) {\n                ...\n            }\n\n            ...\n        }\n        catch (...) {\n            ...\n        }\n    }\n    finally {\n        // 3\\. ɲָ(ָݿ)\n        cleanupAfterCompletion(status);\n    }\n}\n\n```\n\nϷʡ˴룬 `TransactionSynchronization` صصģǾۼҪ\n\n1.  ͷű㣺ѾˣͲٷ\n2.  ύύУһǻᷢǵ `java.sql.Connection` ṩķ\n3.  ɲȽҪӵϢָӾе\n\nύֱӽմ룺`DataSourceTransactionManager#doCommit`\n\n```\nprotected void doCommit(DefaultTransactionStatus status) {\n    DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();\n    // ȡӣConnection Ϊ java.sql.Connection\n    Connection con = txObject.getConnectionHolder().getConnection();\n    try {\n        // ύ\n        con.commit();\n    }\n    catch (SQLException ex) {\n        throw new TransactionSystemException(\"Could not commit JDBC transaction\", ex);\n    }\n}\n\n```\n\nҲǵ `java.sql.Connection` ṩķ\n\nɲĴ `AbstractPlatformTransactionManager#cleanupAfterCompletion` \n\n```\nprivate void cleanupAfterCompletion(DefaultTransactionStatus status) {\n    status.setCompleted();\n    if (status.isNewSynchronization()) {\n        TransactionSynchronizationManager.clear();\n    }\n    if (status.isNewTransaction()) {\n        // ӣӣر\n        doCleanupAfterCompletion(status.getTransaction());\n    }\n    // йлָ\n    if (status.getSuspendedResources() != null) {\n        Object transaction = (status.hasTransaction() ? status.getTransaction() : null);\n        // ָ\n        resume(transaction, (SuspendedResourcesHolder) status.getSuspendedResources());\n    }\n}\n\n```\n\n `DataSourceTransactionManager#doCleanupAfterCompletion` :\n\n```\nprotected void doCleanupAfterCompletion(Object transaction) {\n    DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;\n\n    // ƳԴӵİ󶨹ϵ\n    if (txObject.isNewConnectionHolder()) {\n        TransactionSynchronizationManager.unbindResource(obtainDataSource());\n    }\n\n    // ӣǽϢִָǰ״̬\n    Connection con = txObject.getConnectionHolder().getConnection();\n    try {\n        if (txObject.isMustRestoreAutoCommit()) {\n            con.setAutoCommit(true);\n        }\n        DataSourceUtils.resetConnectionAfterTransaction(\n                con, txObject.getPreviousIsolationLevel(), txObject.isReadOnly());\n    }\n    catch (Throwable ex) {\n        logger.debug(\"Could not reset JDBC Connection after transaction\", ex);\n    }\n    // ӣر\n    if (txObject.isNewConnectionHolder()) {\n        // յõ java.sql.Connection#close\n        DataSourceUtils.releaseConnection(con, this.dataSource);\n    }\n\n    txObject.getConnectionHolder().clear();\n}\n\n```\n\nĻָҲ `resume(...)` 󣬷յõ `DataSourceTransactionManager#doResume` £\n\n```\n@Override\nprotected void doResume(@Nullable Object transaction, Object suspendedResources) {\n    // Դݿ뵱ǰ̰߳\n    TransactionSynchronizationManager.bindResource(obtainDataSource(), suspendedResources);\n}\n\n```\n\nһ󣬴ʱԴ֮ǰԴˡ\n\n#### 3.8 Ϣ\n\nĳЩʽ£ `PROPAGATION_REQUIRES_NEW`ҪǰȻ󴴽µִɺҪָԭϢǽǰϢָΪϢֻǻָϢݿӲָ\n\n```\npublic abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {\n\n    // ŵǰʹõϢ\n    private static final ThreadLocal<TransactionInfo> transactionInfoHolder =\n            new NamedThreadLocal<>(\"Current aspect-driven transaction\");\n\n    // ΪɵϢ\n    protected void cleanupTransactionInfo(@Nullable TransactionInfo txInfo) {\n        if (txInfo != null) {\n            txInfo.restoreThreadLocalStatus();\n        }\n    }\n\n    /**\n     * TransactionInfo: Ϣ\n     */\n    protected static final class TransactionInfo {\n\n        // ǰ״̬\n        @Nullable\n        private TransactionStatus transactionStatus;\n\n        // ɵϢҲǹϢ\n        @Nullable\n        private TransactionInfo oldTransactionInfo;\n\n        ...\n\n        private void restoreThreadLocalStatus() {\n            // ΪɵϢ\n            transactionInfoHolder.set(this.oldTransactionInfo);\n        }\n\n        ...\n    }\n\n}\n\n```\n\nԿ`TransactionInfo` ԻһϢ`oldTransactionInfo` ĳԱûһϢʱֻǼ򵥵ؽ `oldTransactionInfo` õΪ `transactionInfoHolder`  `ThreadLocal` \n\n#### 3.9 ύ\n\nύĴΪ `TransactionAspectSupport#commitTransactionAfterReturning`£\n\n```\nprotected void commitTransactionAfterReturning(@Nullable TransactionInfo txInfo) {\n    // ж״̬\n    if (txInfo != null && txInfo.getTransactionStatus() != null) {\n        // ύǰѾ\n        txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());\n    }\n}\n\n```\n\nڸ÷Уֿд룺\n\n```\ntxInfo.getTransactionManager().commit(txInfo.getTransactionStatus());\n\n```\n\nѾ**쳣ύ**зˣͲٷˡ\n\n* * *\n\n_ԭӣ[https://my.oschina.net/funcy/blog/4947800](https://my.oschina.net/funcy/blog/4947800) ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע_"
  },
  {
    "path": "docs/Spring全家桶/Spring源码分析/Spring事务/Spring事务（四）：事务的隔离级别与传播方式的处理02.md",
    "content": "ǡĸ뼶봫ʽĴĵ 2 ƪģǼ\n\n#### 3.5 ȡϢ\n\nϢ `TransactionAspectSupport#createTransactionIfNecessary` лȡǳҪǰܸ뼶𡢴ʽﴦ÷£\n\n```\nprotected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,\n        @Nullable TransactionAttribute txAttr, final String joinpointIdentification) {\n    // δָƣ򽫷\n    if (txAttr != null && txAttr.getName() == null) {\n        txAttr = new DelegatingTransactionAttribute(txAttr) {\n            @Override\n            public String getName() {\n                return joinpointIdentification;\n            }\n        };\n    }\n\n    TransactionStatus status = null;\n    if (txAttr != null) {\n        if (tm != null) {\n            // ȡ״̬ǰû񣬿ܻᴴ\n            status = tm.getTransaction(txAttr);\n        }\n    }\n    // ׼ϢǽǰõϢװ TransactionInfo\n    return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);\n}\n\n```\n\nҪ\n\n1.  ȡ״̬\n2.  ׼Ϣ\n\n»ȡ״̬̣Ϊ `AbstractPlatformTransactionManager#getTransaction`\n\n```\npublic final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)\n        throws TransactionException {\n\n    TransactionDefinition def = (definition != null ? \n            definition : TransactionDefinition.withDefaults());\n\n    // ȡ\n    Object transaction = doGetTransaction();\n    boolean debugEnabled = logger.isDebugEnabled();\n\n    // Ƿ񣬴򷵻\n    if (isExistingTransaction(transaction)) {\n        return handleExistingTransaction(def, transaction, debugEnabled);\n    }\n    // еǰû\n\n    // 鳬ʱʱǷ\n    if (def.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {\n        throw new InvalidTimeoutException(\"Invalid transaction timeout\", def.getTimeout());\n    }\n\n    // PROPAGATION_MANDATORYУûֱ쳣\n    // No existing transaction found -> check propagation behavior to find out how to proceed.\n    if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {\n        throw new IllegalTransactionStateException(...);\n    }\n    // ǰ񣬴\n    else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||\n            def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||\n            def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {\n        // suspend(...) nullͬͬ񣬷ʲôҲ\n        SuspendedResourcesHolder suspendedResources = suspend(null);\n        try {\n            boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);\n            // \n            DefaultTransactionStatus status = newTransactionStatus(\n                    def, transaction, true, newSynchronization, debugEnabled, suspendedResources);\n            // \n            doBegin(transaction, def);\n            //  TransactionSynchronizationManager \n            prepareSynchronization(status, def);\n            return status;\n        }\n        catch (RuntimeException | Error ex) {\n            resume(null, suspendedResources);\n            throw ex;\n        }\n    }\n    else {\n        boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);\n        return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);\n    }\n}\n\n```\n\nе㳤\n\n##### 1. `doGetTransaction(...)`ȡ\n\nȡķΪ `DataSourceTransactionManager#doGetTransaction`\n\n```\nprotected Object doGetTransaction() {\n    DataSourceTransactionObject txObject = new DataSourceTransactionObject();\n    txObject.setSavepointAllowed(isNestedTransactionAllowed());\n    // ȡϢobtainDataSource()ȡԴ\n    ConnectionHolder conHolder = \n            (ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource());\n    txObject.setConnectionHolder(conHolder, false);\n    return txObject;\n}\n\n```\n\n\n\n1.  ȡԴ\n2.  ȡ `ConnectionHolder`\n\nԴλȡģ\n\n```\npublic class DataSourceTransactionManager extends AbstractPlatformTransactionManager\n        implements ResourceTransactionManager, InitializingBean {\n\n    @Nullable\n    private DataSource dataSource;\n\n    /**\n     * 췽Դ\n     */\n    public DataSourceTransactionManager(DataSource dataSource) {\n        this();\n        setDataSource(dataSource);\n        afterPropertiesSet();\n    }\n\n    /**\n     * Դ\n     */\n    public void setDataSource(@Nullable DataSource dataSource) {\n        if (dataSource instanceof TransactionAwareDataSourceProxy) {\n            this.dataSource = ((TransactionAwareDataSourceProxy) dataSource)\n                    .getTargetDataSource();\n        }\n        else {\n            this.dataSource = dataSource;\n        }\n    }\n\n    @Nullable\n    public DataSource getDataSource() {\n        return this.dataSource;\n    }\n\n    /**\n     * ȡԴ\n     */\n    protected DataSource obtainDataSource() {\n        DataSource dataSource = getDataSource();\n        Assert.state(dataSource != null, \"No DataSource set\");\n        return dataSource;\n    }\n\n    ...\n}\n\n```\n\n`obtainDataSource()` ʵǵ `getDataSource()` ص `dataSource` Ա `dataSource`  `DataSourceTransactionManager` Ĺ췽ﴫģˣõĽǣȡԴ `DataSourceTransactionManager` ʱģ\n\n```\n@Configuration\npublic class TxDemo03Config {\n\n    /**\n     * Դ\n     * @return\n     * @throws Exception\n     */\n    @Bean\n    public DataSource dataSource() throws Exception {\n        Driver driver = new com.mysql.jdbc.Driver();\n        String url = \"jdbc:mysql://localhost:3306/test\";\n        String username = \"root\";\n        String password = \"123\";\n        return new SimpleDriverDataSource(driver, url, username, password);\n    }\n\n    /**\n     * \n     * @param dataSource\n     * @return\n     */\n    @Bean\n    public DataSourceTransactionManager transactionManager(DataSource dataSource) {\n        return new DataSourceTransactionManager(dataSource);\n    }\n\n    ...\n\n}\n\n```\n\nԴ `SimpleDriverDataSource`.\n\n `ConnectionHolder` Ļȡ÷Ϊ `TransactionSynchronizationManager#getResource` £\n\n```\n//  ThreadLocal   ConnectionHolder Ϣ\nprivate static final ThreadLocal<Map<Object, Object>> resources =\n        new NamedThreadLocal<>(\"Transactional resources\");\n\n/**\n * ȡ ConnectionHolder\n */\npublic static Object getResource(Object key) {\n    // װ´ key\n    Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);\n    // ȡϢ\n    Object value = doGetResource(actualKey);\n    return value;\n}\n\n/**\n * Ļȡ\n */\nprivate static Object doGetResource(Object actualKey) {\n    // ThreadLocalлȡ\n    Map<Object, Object> map = resources.get();\n    if (map == null) {\n        return null;\n    }\n    Object value = map.get(actualKey);\n    if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {\n        map.remove(actualKey);\n        if (map.isEmpty()) {\n            resources.remove();\n        }\n        value = null;\n    }\n    return value;\n}\n\n```\n\nӴ`TransactionSynchronizationManager` һ `ThreadLocal` ʵдһ `Map` `Map`  `key` Ϊ `datasource``value` Ϊ `ConnectionHolder`.\n\nô `ConnectionHolder` ʲôأԼ򵥵ؽΪ `Connection`(ݿ) İװ࣬ҪԾ `Connection` ˣ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-d8c4ae3884177f485fbb95d1828fdb39ae2.png)\n\nˣͰ `doGetTransaction(xxx)` ˣصĽ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-0944429e31a6c0b67e121674202dd5ec0fd.png)\n\n##### 2. `isExistingTransaction(...)`Ƿ\n\nȡ `DataSourceTransactionObject` 󣬽жǷˣжϷ `DataSourceTransactionManager#isExistingTransaction`£\n\n```\nprotected boolean isExistingTransaction(Object transaction) {\n    DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;\n    return (txObject.hasConnectionHolder() \n            && txObject.getConnectionHolder().isTransactionActive());\n}\n\n```\n\n`ConnectionHolder` һԱ `transactionActive`ǰ `ConnectionHolder` Ƿڼ״̬`isExistingTransaction(...)` ҪǸжϵǰǷġ\n\n##### 3\\. Ѵڵ`handleExistingTransaction(...)`\n\nǰspring ôģѴķΪ `AbstractPlatformTransactionManager#handleExistingTransaction`£\n\n```\nprivate TransactionStatus handleExistingTransaction(TransactionDefinition definition, \n        Object transaction, boolean debugEnabled) throws TransactionException {\n    // ʽΪʹʱ׳쳣\n    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {\n        throw new IllegalTransactionStateException(\n                \"Existing transaction found for transaction marked with propagation 'never'\");\n    }\n    // ʽΪ֧ʱǰȻ״̬\n    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {\n        // 1\\. suspend()\n        Object suspendedResources = suspend(transaction);\n        boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);\n        return prepareTransactionStatus(\n                definition, null, false, newSynchronization, debugEnabled, suspendedResources);\n    }\n\n    // ʽΪµСʱǰȻµ\n    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {\n        // \n        SuspendedResourcesHolder suspendedResources = suspend(transaction);\n        try {\n            boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);\n            DefaultTransactionStatus status = newTransactionStatus(definition, transaction, true,\n                    newSynchronization, debugEnabled, suspendedResources);\n            // 2\\. doBegin()µ\n            doBegin(transaction, definition);\n            prepareSynchronization(status, definition);\n            return status;\n        }\n        catch (RuntimeException | Error beginEx) {\n            resumeAfterBeginException(transaction, suspendedResources, beginEx);\n            throw beginEx;\n        }\n    }\n\n    // ʽΪǶִСʱ ı\n    // 񣬽ע㣬γǶ\n    // Ƕе쳣Ӱ쵽񱣴֮ǰĲ\n    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {\n        if (!isNestedTransactionAllowed()) {\n            throw new NestedTransactionNotSupportedException(...);\n        }\n        // 3\\. createAndHoldSavepoint(...)㣬عʱֻعñ\n        if (useSavepointForNestedTransaction()) {\n            DefaultTransactionStatus status = prepareTransactionStatus(definition, transaction,\n                    false, false, debugEnabled, null);\n            status.createAndHoldSavepoint();\n            return status;\n        }\n        else {\n            boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);\n            DefaultTransactionStatus status = newTransactionStatus(\n                    definition, transaction, true, newSynchronization, debugEnabled, null);\n            // ֱ֧㣬µ\n            doBegin(transaction, definition);\n            prepareSynchronization(status, definition);\n            return status;\n        }\n    }\n    if (isValidateExistingTransaction()) {\n        // ֤\n        ...\n    }\n    boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);\n    return prepareTransactionStatus(definition, transaction, false, \n            newSynchronization, debugEnabled, null);\n}\n\n```\n\nԿʹĸ뼶߼صĴѾעͣͲ˵ˣмҪر\n\n1.  `suspend()`\n2.  `doBegin()`µ\n3.  `createAndHoldSavepoint(...)`㣬عʱֻعñ\n\n⼸ͳһ\n\n##### 4\\.  `AbstractPlatformTransactionManager#getTransaction`\n\nٻص `AbstractPlatformTransactionManager#getTransaction` ʣµ̣\n\n```\npublic final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)\n        throws TransactionException {\n\n    // ǰѾˣʡ\n    ...\n\n    // еǰû\n\n    // 鳬ʱʱǷ\n    if (def.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {\n        throw new InvalidTimeoutException(\"Invalid transaction timeout\", def.getTimeout());\n    }\n\n    // PROPAGATION_MANDATORYУûֱ쳣\n    if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {\n        throw new IllegalTransactionStateException(...);\n    }\n    // ǰ񣬴\n    else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||\n            def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||\n            def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {\n        // suspend(...) nullͬͬ񣬷ʲôҲ\n        SuspendedResourcesHolder suspendedResources = suspend(null);\n        try {\n            boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);\n            // \n            DefaultTransactionStatus status = newTransactionStatus(\n                    def, transaction, true, newSynchronization, debugEnabled, suspendedResources);\n            // \n            doBegin(transaction, def);\n            //  TransactionSynchronizationManager \n            prepareSynchronization(status, def);\n            return status;\n        }\n        catch (RuntimeException | Error ex) {\n            resume(null, suspendedResources);\n            throw ex;\n        }\n    }\n    else {\n        boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);\n        return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);\n    }\n}\n\n```\n\n`handleExistingTransaction(...)` Ĺ**ʱЩҪô**`getTransaction(...)` ²ֵĹǣ**ʱЩҪô**ԿȻ `suspend(...)``doBegin(...)` ȷЩһҲͳһ\n\n##### 5\\. ׼ؽ`prepareTransactionStatus(...)`\n\n`handleExistingTransaction(...)`  `getTransaction(...)` ڴؽʱʹ `prepareTransactionStatus(...)` \n\n```\n// `handleExistingTransaction(...)`\nreturn prepareTransactionStatus(definition, transaction, false, \n            newSynchronization, debugEnabled, null);\n\n// `getTransaction(...)`\nreturn prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);\n\n```\n\nɶ `AbstractPlatformTransactionManager#prepareTransactionStatus`\n\n```\nprotected final DefaultTransactionStatus prepareTransactionStatus(\n        TransactionDefinition definition, @Nullable Object transaction, boolean newTransaction,\n        boolean newSynchronization, boolean debug, @Nullable Object suspendedResources) {\n\n    // һ DefaultTransactionStatus \n    DefaultTransactionStatus status = newTransactionStatus(\n            definition, transaction, newTransaction, newSynchronization, debug, suspendedResources);\n    // ׼ Synchronization\n    prepareSynchronization(status, definition);\n    return status;\n}\n\n/**\n *һ TransactionStatus ʵ\n */\nprotected DefaultTransactionStatus newTransactionStatus(\n        TransactionDefinition definition, @Nullable Object transaction, boolean newTransaction,\n        boolean newSynchronization, boolean debug, @Nullable Object suspendedResources) {\n\n    boolean actualNewSynchronization = newSynchronization &&\n            !TransactionSynchronizationManager.isSynchronizationActive();\n    //  DefaultTransactionStatus Ĺ췽\n    return new DefaultTransactionStatus(\n            transaction, newTransaction, actualNewSynchronization,\n            definition.isReadOnly(), debug, suspendedResources);\n}\n\n```\n\nҪΪ˴ `DefaultTransactionStatus` һн\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-a491ee7ca22238b2d9c698c55ae9dd6d005.png)\n\n##### 6\\. ׼Ϣ`TransactionAspectSupport#prepareTransactionInfo`\n\nص `TransactionAspectSupport#createTransactionIfNecessary` \n\n```\nprotected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,\n        @Nullable TransactionAttribute txAttr, final String joinpointIdentification) {\n    ...\n    TransactionStatus status = null;\n    if (txAttr != null) {\n        if (tm != null) {\n            // ȡ״̬ǰû񣬿ܻᴴ\n            status = tm.getTransaction(txAttr);\n        }\n    }\n    // ׼ϢǽǰõϢװ TransactionInfo\n    return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);\n}\n\n```\n\nǰôֻ࣬ǵõ `TransactionStatus`ٽ׼Ϣķ `prepareTransactionInfo(...)`\n\n```\nprotected TransactionInfo prepareTransactionInfo(@Nullable PlatformTransactionManager tm,\n        @Nullable TransactionAttribute txAttr, String joinpointIdentification,\n        @Nullable TransactionStatus status) {\n    TransactionInfo txInfo = new TransactionInfo(tm, txAttr, joinpointIdentification);\n\n    if (txAttr != null) {\n        txInfo.newTransactionStatus(status);\n    }\n\n    // ʡlogĴӡ\n    ...\n\n    // ̰߳\n    txInfo.bindToThread();\n    return txInfo;\n}\n\n```\n\nţͬ `prepareTransactionStatus(...)` ƣҲǴһ `TransactionInfo` 󣬲ҽ `TransactionInfo` 뵱ǰ̰߳󶨣󶨵Ĵ£\n\n```\npublic abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {\n\n    // ŵǰʹõϢ\n    private static final ThreadLocal<TransactionInfo> transactionInfoHolder =\n            new NamedThreadLocal<>(\"Current aspect-driven transaction\");\n\n    // ΪɵϢ\n    protected void cleanupTransactionInfo(@Nullable TransactionInfo txInfo) {\n        if (txInfo != null) {\n            txInfo.restoreThreadLocalStatus();\n        }\n    }\n\n    /**\n     * TransactionInfo: Ϣ\n     */\n    protected static final class TransactionInfo {\n\n        // ǰ״̬\n        @Nullable\n        private TransactionStatus transactionStatus;\n\n        // ɵϢҲǹϢ\n        @Nullable\n        private TransactionInfo oldTransactionInfo;\n\n        /**\n         * Ϣ󶨵ǰ߳\n         */\n        private void bindToThread() {\n            // õɵϢ\n            this.oldTransactionInfo = transactionInfoHolder.get();\n            // óµϢ\n            transactionInfoHolder.set(this);\n        }\n\n        /**\n         * ɺ󣬻ὫɵϢ󶨵ǰ߳\n         */\n        private void restoreThreadLocalStatus() {\n            // ΪɵϢ\n            transactionInfoHolder.set(this.oldTransactionInfo);\n        }\n\n        ...\n    }\n\n}\n\n```\n\n`TransactionAspectSupport` һ `ThreadLocal`ŵǰ `TransactionInfo` 󣬽̰߳ʱõɵϢ `TransactionInfo` ĳԱ `oldTransactionInfo` УȻµ `TransactionInfo`  `ThreadLocal` Уִɺ󣬻 `TransactionInfo` ĳԱ `oldTransactionInfo` õɵϢٽɵϢ `ThreadLocal` У \" -  - \" л.\n\nһõ `TransactionInfo` £\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-ab882613ce67ba3bf7afcbd2eab01c6cf27.png)\n\n `TransactionInfo` ĽṹһЩ˵\n\n*    `TransactionAspectSupport.TransactionInfo` `TransactionAspectSupport` һڲ࣬װһЩϢ\n*   `transactionManager`: õ `DataSourceTransactionManager`\n*   `transactionAttribute`: ֵ `@Transactional` עֵ\n*   `joinpointIdentification`: ȫ޶ʽΪ\"͡\"\n*   `transactionStatus`: ϿǼ¼״̬ʵ󲻽¼״̬ش£\n    *   `complete`: ״̬\n    *   `connectionHolder`: ǰеݿ\n    *   `suspendedResources`: ݿӣҪָʱܹöõݿ\n*   `oldTransactionInfo`: һҲǹ񣩵Ϣִ굱ǰ󣬻ָһִ\n\nһСдôˣľȷˣ`suspend(...)``doBegin(...)` ȷƪٷɡ\n\n* * *\n\n_ԭӣ[https://my.oschina.net/funcy/blog/4947799](https://my.oschina.net/funcy/blog/4947799) ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע_"
  },
  {
    "path": "docs/Spring全家桶/Spring源码分析/Spring启动流程/Spring启动流程（一）：启动流程概览.md",
    "content": "在前面的文章中，我们成功的编译了 spring 源码，也构建了第一个 spring 测试 demo，接下来我们就基于[第一个 spring 源码调试 demo](https://my.oschina.net/funcy/blog/4533250 \"第一个spring源码调试demo\") 中的代码，来对 spring 源码进行源码分析。\n\n### 1\\. spring 启动流程概览\n\n在前面 demo 的 `main()` 方法中，有这么一行：\n\n```\nApplicationContext context =\n        new AnnotationConfigApplicationContext(\"org.springframework.learn.demo01\");\n\n```\n\n这短短的一行就是 spring 的整个启动流程了。上面的代码中，声明了一个 `ApplicationContext` 类型的对象 `context`，右边使用其子类 `AnnotationConfigApplicationContext` 实例化，并在构造方法中传入了包名 `org.springframework.learn.demo01`，这个包名就表明了接下来要扫描哪些包。\n\n> 这里我们接触到了 spring 的第一个组件：`ApplicationContext`，关于 `ApplicationContext` 的分析，可以参考我的文章 [spring 组件（一）：ApplicationContext](https://my.oschina.net/funcy/blog/4597456 \"spring组件（一）：ApplicationContext\")。\n\n进入到 `AnnotationConfigApplicationContext`，代码如下：\n\n> AnnotationConfigApplicationContext#AnnotationConfigApplicationContext(String...)\n\n```\npublic AnnotationConfigApplicationContext(String... basePackages) {\n     // 1\\. 调用无参构造函数，会先调用父类GenericApplicationContext的构造函数\n     // 2\\. 父类的构造函数里面就是初始化DefaultListableBeanFactory，并且赋值给beanFactory\n     // 3\\. 本类的构造函数里面，初始化了一个读取器：AnnotatedBeanDefinitionReader read，\n     //    一个扫描器ClassPathBeanDefinitionScanner scanner\n     // 4\\. 这个scanner，就是下面 scan(basePackages) 调用的对象\n     this();\n\n     //对传入的包进行扫描，扫描完成后，会得到一个 BeanDefinition 的集合\n     scan(basePackages);\n\n     //启动spring，在这里完成spring容器的初始化操作，\n     //包括bean的实例化、属性注入，将bean保存到spring容器中等\n     refresh();\n}\n\n```\n\n这个类就三行，相关操作都已在代码中注释了，这里稍微再总结下，这段代码主要做了三件事：\n\n1.  调用无参构造，进行属性初始化\n2.  进行包扫描，得到 BeanDefinition\n3.  启用 spring 容器。\n\n接着，我们再来看看 spring 启动流程中，做了哪些事：\n\n> AbstractApplicationContext#refresh\n\n```\npublic void refresh() throws BeansException, IllegalStateException {\n    // 使用synchronized是为了避免refresh() 还没结束，再次发起启动或者销毁容器引起的冲突\n    synchronized (this.startupShutdownMonitor) {\n        // 做一些准备工作，记录容器的启动时间、标记“已启动”状态、检查环境变量等\n        prepareRefresh();\n\n        // 初始化BeanFactory容器、注册BeanDefinition, 最终获得了DefaultListableBeanFactory\n        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();\n\n        // 还是一些准备工作:\n        // 1\\. 设置了一个类加载器\n        // 2\\. 设置了bean表达式解析器\n        // 3\\. 添加了属性编辑器的支持\n        // 4\\. 添加了一个后置处理器：ApplicationContextAwareProcessor\n        // 5\\. 设置了一些忽略自动装配的接口\n        // 6\\. 设置了一些允许自动装配的接口，并且进行了赋值操作\n        // 7\\. 在容器中还没有XX的bean的时候，帮我们注册beanName为XX的singleton bean\n        prepareBeanFactory(beanFactory);\n\n        try {\n            // Spring的一个扩展点. 如果有Bean实现了BeanFactoryPostProcessor接口，\n            // 那么在容器初始化以后，Spring 会负责调用里面的 postProcessBeanFactory 方法。\n            // 具体的子类可以在这步的时候添加特殊的 BeanFactoryPostProcessor 的实现类，来做些事\n            postProcessBeanFactory(beanFactory);\n\n            // 调用BeanFactoryPostProcessor各个实现类的postProcessBeanFactory(factory) 方法\n            invokeBeanFactoryPostProcessors(beanFactory);\n\n            // 扩展点,注册 BeanPostProcessor 的实现类，注意不是BeanFactoryPostProcessor\n            registerBeanPostProcessors(beanFactory);\n\n            // 初始化当前 ApplicationContext 的 MessageSource，用在国际化操作中\n            initMessageSource();\n\n            // 这个方法主要为初始化当前 ApplicationContext 的事件广播器\n            initApplicationEventMulticaster();\n\n            // 这也是spring的一个扩展点\n            onRefresh();\n\n            // Check for listener beans and register them.\n            // 注册事件监听器\n            registerListeners();\n\n            // 初始化所有的 singleton beans\n            finishBeanFactoryInitialization(beanFactory);\n\n            // 完成启动，\n            finishRefresh();\n        }\n\n        catch (BeansException ex) {\n            if (logger.isWarnEnabled()) {\n                logger.warn(\"Exception encountered during context initialization - \" +\n                    \"cancelling refresh attempt: \" + ex);\n            }\n\n            // Destroy already created singletons to avoid dangling resources.\n            // 销毁已经初始化的的Bean\n            destroyBeans();\n\n            // Reset 'active' flag.\n            // 重置 'active' 状态\n            cancelRefresh(ex);\n\n            // Propagate exception to caller.\n            throw ex;\n        }\n\n        finally {\n            // Reset common introspection caches in Spring's core, since we\n            // might not ever need metadata for singleton beans anymore...\n            // 清除缓存\n            resetCommonCaches();\n        }\n    }\n}\n\n```\n\n这个方法虽然代码不多，但包含了 spring bean 的整个创建过程，每个方法做了些什么，在代码中都有注释，这里就不赘述了。\n\n实际上，`refresh()` 涵盖了 spring 整个创建 bean 的流程，在后面的文章中，我们也将重点展开这里面的方法来分析，在现阶段只需要大致了解这些方法做了什么事即可。\n\n整个流程总结如下：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-9307fefa65470e5c36ae6044631b5416aef.png)\n\n### 2\\. spring 启动中 `beanFactory` 的变化\n\n本文中的源码解读就到这里了，接下来我们来看看，spring 启动中 `beanFactory` 有些什么变化。\n\n> `beanFactory` 是 spring 的重要组件之一，直译为 spring bean 工厂，是 spring 生产 bean 与保存 bean 的地方，关于 `beanFactory` 的详细分析，可以查看 [spring BeanFactory 分析](https://my.oschina.net/funcy/blog/4597529 \"spring BeanFactory分析\")。\n\n我们将断点打在 `AnnotationConfigApplicationContext#AnnotationConfigApplicationContext(String...)` 的 `this()` 方法上，然后运行 demo01 的 `main()` 方法：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-c3c672a675d9b06f03ea29cb31f6ed5d012.png)\n\n此时的变量中，并没有 `beanFactory`，我们自己添加 `beanFactory` 到调度窗口的变量列表中：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-e9d8ae8fdd3b02b2279376303e3eae4cf2f.png)\n\n这样就能看到对应的值了：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-44d1ade26f0cb667425b0dd99d82666877f.png)\n\n可以看到，此时的 `beanFactory` 为 null，表明 `beanFactory` 并未实例化，我们继续运行：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-664780be9dfef73c12a3f163b349e7e54d8.png)\n\n当运行完 `this()` 后，发现 `beanFactory` 已经有值了，类型为 `DefaultListableBeanFactory`。但是，在查看 `beanFactory` 对象时，发现 `beanFactory` 的属性太多了，我们应该重点关注啥呢？\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-1b617cf7edda29c652a7661d4be3779ec85.png)\n\n我们这部分主要关注 spring bean 的创建，因此只需要关注 `beanFactory` 的两个属性就可以了：\n\n*   beanDefinitionMap：存放 beanDefinition 的 map.\n*   singletonObjects：存放 spring bean 的 map，spring bean 创建后都存放在这里，也即直观上理解的 `spring 容器`.\n\n> `BeanDefinition` 是 spring 重要组件之一，为‘spring bean 的描述’，简单来说，就是说明了一个 spring bean 应该如何创建。关于 `BeanDefinition` 的详细分析，可以查看 [spring BeanDefinition 分析](https://my.oschina.net/funcy/blog/4597536)。\n\n我们手动添加变量，如下：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-0c6368478258f8b9f76b47fc1c85b02f13f.png)\n\n可以看到，此时的 `beanDefinitionMap` 中已经有 4 个对象了，显然是在 `this()` 方法中添加的，关于这块我们后面会分析。\n\n接着运行，发现 `beanDefinitionMap` 又多了两个：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-a493061fbd4b4066f9a4d91e91ff61e8c4e.png)\n\n这里的 `beanObj1` 与 `beanObj2` 就是我们自己的类了，由此可以判断出 **spring 就是在 `AnnotationConfigApplicationContext#scan` 方法中对包进行扫描的**。\n\n接下来，代码执行进入 `AbstractApplicationContext#refresh` 方法，我们一行行运行下去，发现运行到 `prepareBeanFactory(beanFactory);` 时，`singletonObjects` 中第一次出现了对象：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-8cebcb82f5a8754fd1bb4bb3eb3c57dda2d.png)\n\n可以看到，这里出现了 3 个类，基本都跟系统、环境相关，如 `environment` 是 spring 当前使用的环境 (`profile`)，`systemProperties` 当前系统的属性（操作系统、操作系统版本等）。\n\n继续往下运行，发现代码运行到 `invokeBeanFactoryPostProcessors(beanFactory)` 时，又多了 4 个类：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-baa09e51272baa384418cb2c82b9dfb079b.png)\n\n关于这几个类的作用，我们后面的文章中会分析，这里先不必管。继续往下运行，发现在 `registerBeanPostProcessors(beanFactory);` 中，又多了一个对象：\n\n```\norg.springframework.context.annotation.internalAutowiredAnnotationProcessor\n\n```\n\n这里我们依旧不用管这个对象，接着运行下去，可以看到在运行 `initMessageSource()` 时，又多了一个对象：\n\n```\nmessageSource -> {DelegatingMessageSource@1847} \"Empty MessageSource\"\n\n```\n\n显然，这个对象是用来处理国际化问题的，不过由于 demo01 中并没有用到国际化，所以这里显示 `Empty MessageSource`。继续运行，发现运行到 `initApplicationEventMulticaster();` 时，又多了一个对象：\n\n```\napplicationEventMulticaster -> {SimpleApplicationEventMulticaster@1869} \n\n```\n\n显然，这个对象是用来处理 `ApplicationContext` 的广播事件的，我们的 demo 中并没有用到，暂时不必理会。继续下去，发现在运行完 `finishBeanFactoryInitialization(beanFactory);`，`singletonObjects` 中终于出现了我们期待的对象：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-68b1ee71e468ef8cf839230c07b64c45563.png)\n\n由此可见，对象就是在该方法中创建的。\n\n### 总结\n\n1.  spring 包的描述：`AnnotationConfigApplicationContext#scan`\n2.  spring bean 的创建：`AbstractApplicationContext#finishBeanFactoryInitialization`\n\n本文主要是了解 spring 启动流程，从整体上把握 spring 启动过程中的 beanFactory 的变化。本文意在了解 spring 的整体启动流程，后续的分析中，我们将对这些流程进行展开分析。\n\n* * *\n\n_本文原文链接：[https://my.oschina.net/funcy/blog/4597493](https://my.oschina.net/funcy/blog/4597493) ，限于作者个人水平，文中难免有错误之处，欢迎指正！原创不易，商业转载请联系作者获得授权，非商业转载请注明出处。_"
  },
  {
    "path": "docs/Spring全家桶/Spring源码分析/Spring启动流程/Spring启动流程（七）：国际化与事件处理.md",
    "content": "![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-ed6b80d76ba4b2ddb0f8d15e070a0c32df7.png)\n\nģǼ spring ̡\n\n### 7\\. ʻ: `initMessageSource()`\n\nʼ `MessageSource` ģ£\n\n```\npublic abstract class AbstractApplicationContext extends DefaultResourceLoader\n        implements ConfigurableApplicationContext {\n\n    ...\n\n    /**\n     * ʼ MessageSource\n     */\n    protected void initMessageSource() {\n        ConfigurableListableBeanFactory beanFactory = getBeanFactory();\n        // beanFactoryдMessageSource ParentMessageSource\n        if (beanFactory.containsLocalBean(MESSAGE_SOURCE_BEAN_NAME)) {\n            this.messageSource = beanFactory.getBean(\n                    MESSAGE_SOURCE_BEAN_NAME, MessageSource.class);\n            if (this.parent != null && this.messageSource instanceof HierarchicalMessageSource) {\n                HierarchicalMessageSource hms = (HierarchicalMessageSource) this.messageSource;\n                if (hms.getParentMessageSource() == null) {\n                    // ParentMessageSource\n                    hms.setParentMessageSource(getInternalParentMessageSource());\n                }\n            }\n        }\n        // beanFactoryвMessageSource --ע\n        else {\n            DelegatingMessageSource dms = new DelegatingMessageSource();\n            dms.setParentMessageSource(getInternalParentMessageSource());\n            this.messageSource = dms;\n            // ParentMessageSource\n            beanFactory.registerSingleton(MESSAGE_SOURCE_BEAN_NAME, this.messageSource);\n        }\n    }\n\n    /**\n     * ظ messageSource\n     */\n    @Nullable\n    protected MessageSource getInternalParentMessageSource() {\n        return (getParent() instanceof AbstractApplicationContext ?\n                ((AbstractApplicationContext) getParent()).messageSource : getParent());\n    }\n\n    ...\n}\n\n```\n\nԿҪǲ `MessageSource`Ҫ߼ΪѾ `MessageSource` ˣһЩԣʹ `MessageSource`Щ ԣעᵽ `beanFactory` С\n\n `MessageSource` ľãľͲչˡ\n\n### 8\\. ʼ¼㲥`initApplicationEventMulticaster()`\n\n`AbstractApplicationContext#initApplicationEventMulticaster` £\n\n```\nprotected void initApplicationEventMulticaster() {\n    ConfigurableListableBeanFactory beanFactory = getBeanFactory();\n    // ûԶ¼㲥ʹû\n    if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {\n        this.applicationEventMulticaster =\n                beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, \n                        ApplicationEventMulticaster.class);\n    }\n    else {\n        // ûûù㲥ʹĬϵ¼㲥\n        this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);\n        beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, \n                this.applicationEventMulticaster);\n    }\n}\n\n```\n\n߼Ҳܼ򵥣Ѵ¼㲥ʹѴڵģʹһ `ApplicationEventMulticaster`Ҫ㲥¼ģ¼ݣԲο [spring ֮̽ spring ¼](https://my.oschina.net/funcy/blog/4713339).\n\n### 9\\. չ㣺onRefresh ()\n\n`AbstractApplicationContext#onRefresh`  spring ṩһչ㣬ݣ\n\n```\nprotected void onRefresh() throws BeansException {\n\n}\n\n```\n\nҪضĲʵ֡\n\nǰʹõ `ApplicationContext`  `AnnotationConfigApplicationContext` `onRefresh()` Ͳˡ\n\n### 10\\. ע¼registerListeners ()\n\n`AbstractApplicationContext#registerListeners` ش£\n\n> AbstractApplicationContext\n\n```\n/** ż */\nprivate final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet<>();\n\n/** صǰеļ */\npublic Collection<ApplicationListener<?>> getApplicationListeners() {\n    return this.applicationListeners;\n}\n\n/**\n * Ӽ\n */\npublic void addApplicationListener(ApplicationListener<?> listener) {\n    Assert.notNull(listener, \"ApplicationListener must not be null\");\n    if (this.applicationEventMulticaster != null) {\n        this.applicationEventMulticaster.addApplicationListener(listener);\n    }\n    this.applicationListeners.add(listener);\n}\n\n/**\n * ע\n */\nprotected void registerListeners() {\n    // ֶsetһЩ\n    // getApplicationListeners() ȡļͨ addApplicationListener(...) ӵ\n    for (ApplicationListener<?> listener : getApplicationListeners()) {\n        getApplicationEventMulticaster().addApplicationListener(listener);\n    }\n    // ȡȡƣõ㲥\n    // ʱȡļǴ beanFactory лȡģspringͨɨõ\n    String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);\n    for (String listenerBeanName : listenerBeanNames) {\n        getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);\n    }\n    // Ӧ¼㲥\n    Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;\n    this.earlyApplicationEvents = null;\n    if (earlyEventsToProcess != null) {\n        for (ApplicationEvent earlyEvent : earlyEventsToProcess) {\n            //  㲥¼\n            getApplicationEventMulticaster().multicastEvent(earlyEvent);\n        }\n    }\n}\n\n```\n\n̴£\n\n1.   `AbstractApplicationContext#applicationListeners` еļ `ApplicationEventMulticaster` \n2.   `beanFactory` ȡ `beanName`ӵ `ApplicationEventMulticaster` \n3.  ¼ͽй㲥\n\n spring ¼Ĳչ˽࣬ɲο [spring ֮̽ spring ¼](https://my.oschina.net/funcy/blog/4713339).\n\nĵķ͵ˡ\n\n* * *\n\n_ԭӣ[https://my.oschina.net/funcy/blog/4892120](https://my.oschina.net/funcy/blog/4892120) ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע_"
  },
  {
    "path": "docs/Spring全家桶/Spring源码分析/Spring启动流程/Spring启动流程（三）：包的扫描流程.md",
    "content": "![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-3ed1cf4bd6fc2ef3f569376093a6462987d.png)\n\n [applicationContext Ĵ](https://my.oschina.net/funcy/blog/4608767)УǷ `applicationContext` Ĵ̣ڱУǽ spring νаɨġ\n\n `AnnotationConfigApplicationContext` Ĺ췽\n\n```\npublic AnnotationConfigApplicationContext(String... basePackages) {\n    this();\n    //Դİɨ裬ɨɺ󣬻õһ BeanDefinition ļ\n    scan(basePackages);\n    refresh();\n}\n\n```\n\nǽĿ `scan(basePackages);` ϣ÷\n\n> AnnotationConfigApplicationContext#scan\n\n```\npublic void scan(String... basePackages) {\n     Assert.notEmpty(basePackages, \"At least one base package must be specified\");\n     // scannerthis()д\n     this.scanner.scan(basePackages);\n}\n\n```\n\nؼ `this.scanner.scan(basePackages);` `scanner`  `this()` дĶ\n\n```\npublic AnnotationConfigApplicationContext() {\n    this.reader = new AnnotatedBeanDefinitionReader(this);\n    // scanner ﴴ\n    this.scanner = new ClassPathBeanDefinitionScanner(this);\n}\n\n```\n\n׷٣ǶԲҪķصעɨḶ́\n\n```\nAnnotationConfigApplicationContext#AnnotationConfigApplicationContext(String...)\n |-AnnotationConfigApplicationContext#scan\n  |-ClassPathBeanDefinitionScanner#scan\n   |-ClassPathBeanDefinitionScanner#doScan\n\n```\n\n`ClassPathBeanDefinitionScanner#doScan` £\n\n```\nprotected Set<BeanDefinitionHolder> doScan(String... basePackages) {\n    Assert.notEmpty(basePackages, \"At least one base package must be specified\");\n    Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();\n    //Ҫɨİ·\n    for (String basePackage : basePackages) {\n        //ȡзBeanDefinition\n        Set<BeanDefinition> candidates = findCandidateComponents(basePackage);\n        for (BeanDefinition candidate : candidates) {\n            //BeanDefinitionScope\n            ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);\n            candidate.setScope(scopeMetadata.getScopeName());\n            //鿴ǷǷָbeanƣûָʹĸСд\n            String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);\n            //ifǴlazyAutowireDependencyOninitMethodenforceInitMethoddestroyMethod\n            // enforceDestroyMethodPrimaryRoleDescriptionЩ߼\n            if (candidate instanceof AbstractBeanDefinition) {\n                postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);\n            }\n            if (candidate instanceof AnnotatedBeanDefinition) {\n                AnnotationConfigUtils.processCommonDefinitionAnnotations(\n                        (AnnotatedBeanDefinition) candidate);\n            }\n            //beanǷ\n            if (checkCandidate(beanName, candidate)) {\n                //ְװһ\n                BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);\n                //scopeǷ񴴽δд\n                definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(\n                        scopeMetadata, definitionHolder, this.registry);\n                beanDefinitions.add(definitionHolder);\n                //ע beanDefinition\n                registerBeanDefinition(definitionHolder, this.registry);\n            }\n        }\n    }\n    return beanDefinitions;\n}\n\n```\n\nδɵĹܺˣ¼£\n\n1.  ݰ·õ BeanDefinition\n2.   BeanDefinitionһḻ beanDefinition Ϣ\n3.   BeanDefinition ӵ beanFactory\n\n> BeanDefinition Ҳ spring Ҫ֮һ BeanDefinition ķɲο [spring ֮ BeanDefinition](https://my.oschina.net/funcy/blog/4597536 \"spring֮BeanDefinition\")\n\nҪĲ\n\n### 1\\. ݰ·õ BeanDefinition\n\nһҪ `Set<BeanDefinition> candidates = findCandidateComponents(basePackage);`ǸȥִУɶԲҪ÷ĵ£\n\n```\nAnnotationConfigApplicationContext#AnnotationConfigApplicationContext(String...)\n |-AnnotationConfigApplicationContext#scan\n  |-ClassPathBeanDefinitionScanner#scan\n   |-ClassPathBeanDefinitionScanner#doScan\n    |-ClassPathScanningCandidateComponentProvider#findCandidateComponents\n     |-ClassPathScanningCandidateComponentProvider#scanCandidateComponents\n\n```\n\nյõ `ClassPathScanningCandidateComponentProvider#scanCandidateComponents` (ɾ)\n\n```\nprivate Set<BeanDefinition> scanCandidateComponents(String basePackage) {\n    Set<BeanDefinition> candidates = new LinkedHashSet<>();\n    //װɨ·װɺָʽclasspath*:org/springframework/learn/demo01/**/*.class\n    String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +\n            resolveBasePackage(basePackage) + '/' + this.resourcePattern;\n    //·ȡԴ󣬼ɨ·µĵclassļõ Resource\n    Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);\n    for (Resource resource : resources) {\n        if (resource.isReadable()) {\n            //ԴȡԴMetadataReader\n            MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);\n            // £\n            // 1\\. ǷҪʼΪspring beanǷ @Component@Serviceע\n            // 2\\. 鿴Ƿ@Conditionalһϵеע⣬ȻǷעBean\n            if (isCandidateComponent(metadataReader)) {\n                ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);\n                sbd.setResource(resource);\n                sbd.setSource(resource);\n                if (isCandidateComponent(sbd)) {\n                    candidates.add(sbd);\n                }\n            }\n        }\n    }\n    return candidates;\n}\n\n```\n\nԿϴ£\n\n1.  ݴ basePackage õɨ·\n2.  ɨ·õ·µ class ļӦ Resource\n3.   Resource תΪ beanDefinition\n\nǾϴз\n\n#### 1.1  basePackage õɨ·\n\nһûɶ÷һַƴ滻 `org.springframework.learn.demo01` תΪ `classpath*:org/springframework/learn/demo01/**/*.class`شһУ\n\n```\nString packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +\n        resolveBasePackage(basePackage) + '/' + this.resourcePattern;\n\n```\n\n#### 1.2 ɨ·\n\nõɨ·󣬽ǽɨˡspring ɨʱɨ·µ class ļɨȻװ `Resource`\n\n```\nResource[] resources = getResourcePatternResolver().getResources(packageSearchPath);\n\n```\n\n룬ͬأǶԲҪķֻã\n\n```\nAnnotationConfigApplicationContext#AnnotationConfigApplicationContext(String...)\n |-AnnotationConfigApplicationContext#scan\n  |-ClassPathBeanDefinitionScanner#scan\n   |-ClassPathBeanDefinitionScanner#doScan\n    |-ClassPathScanningCandidateComponentProvider#findCandidateComponents\n     |-ClassPathScanningCandidateComponentProvider#scanCandidateComponents\n      |- GenericApplicationContext#getResources\n       |-AbstractApplicationContext#getResources\n        |-PathMatchingResourcePatternResolver#getResources\n         |-PathMatchingResourcePatternResolver#findPathMatchingResources\n\n```\n\nǽۼ `PathMatchingResourcePatternResolver#findPathMatchingResources`:\n\n```\nprotected Resource[] findPathMatchingResources(String locationPattern) throws IOException {\n    //  locationPattern  classpath*:org/springframework/learn/demo01/**/*.class\n    // rootDirPath  classpath*:org/springframework/learn/demo01/\n    String rootDirPath = determineRootDir(locationPattern);\n\n    // subPattern  **/*.class\n    String subPattern = locationPattern.substring(rootDirPath.length());\n\n    // ﷵص Resource  rootDirPath ľ·(urlʾ)\n    // URL [file:/xxx/spring-learn/build/classes/java/main/org/springframework/learn/demo01/]\n    Resource[] rootDirResources = getResources(rootDirPath);\n\n    Set<Resource> result = new LinkedHashSet<>(16);\n    for (Resource rootDirResource : rootDirResources) {\n        rootDirResource = resolveRootDirResource(rootDirResource);\n        URL rootDirUrl = rootDirResource.getURL();\n        if (equinoxResolveMethod != null && rootDirUrl.getProtocol().startsWith(\"bundle\")) {\n            URL resolvedUrl = (URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, rootDirUrl);\n            if (resolvedUrl != null) {\n                rootDirUrl = resolvedUrl;\n            }\n            rootDirResource = new UrlResource(rootDirUrl);\n        }\n        //  vfs Դ\n        if (rootDirUrl.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {\n            result.addAll(VfsResourceMatchingDelegate\n                    .findMatchingResources(rootDirUrl, subPattern, getPathMatcher()));\n        }\n        // jarļ\n        else if (ResourceUtils.isJarURL(rootDirUrl) || isJarResource(rootDirResource)) {\n            result.addAll(doFindPathMatchingJarResources(rootDirResource, rootDirUrl, subPattern));\n        }\n        // ļ·µļ\n        else {\n            result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));\n        }\n    }\n    return result.toArray(new Resource[0]);\n}\n\n```\n\nָͨĴ£\n\n1.  ͨ locationPattern õ pattern µ url ·װΪ Resource\n2.  ص· class ļװΪ Resource\n\n spring ν pattrn תΪ url ·ģǸ룺\n\n```\n|-PathMatchingResourcePatternResolver#getResources\n |-PathMatchingResourcePatternResolver#findAllClassPathResources\n  |-PathMatchingResourcePatternResolver#doFindAllClassPathResources\n\n```\n\nմ뵽 `PathMatchingResourcePatternResolver#doFindAllClassPathResources`:\n\n```\nprotected Set<Resource> doFindAllClassPathResources(String path) throws IOException {\n    Set<Resource> result = new LinkedHashSet<>(16);\n    ClassLoader cl = getClassLoader();\n    // pathӦurl\n    Enumeration<URL> resourceUrls = (cl != null ? cl.getResources(path) : \n            ClassLoader.getSystemResources(path));\n    while (resourceUrls.hasMoreElements()) {\n        URL url = resourceUrls.nextElement();\n        // urlתΪResourceӵ\n        result.add(convertClassLoaderURL(url));\n    }\n    if (\"\".equals(path)) {\n        addAllClassLoaderJarRoots(cl, result);\n    }\n    return result;\n}\n\n// urlתΪResource\nprotected Resource convertClassLoaderURL(URL url) {\n    return new UrlResource(url);\n}\n\n```\n\nʱ `path` Ϊ `org/springframework/learn/demo01/`Ӵ֪յ java  `ClassLoader` ȡ path Ӧ urlȻ url תΪ `Resource` ӵвء\n\nõľ·֮󣬽¾Ƕ·бõ class ļˡٻص `PathMatchingResourcePatternResolver#findPathMatchingResources`spring ɨʱݴ url ͣɨ 3 ط\n\n1.  vfs\n2.  jar \n3.  ļ·\n\n`vfs` ע˵ \"URL protocol for a general JBoss VFS resource\"ͨ JBoss VFS Դ URL Э飬ﲻĿ jar Ҫɨ jar е·ͻʹ jar ɨ跽ʽ class ļңڵʱ`demo01` ʹļʽɨģصļɨ跽ʽ jar ɨģȤСо¡\n\nǸ `findPathMatchingResources` \n\n```\n|-PathMatchingResourcePatternResolver#findPathMatchingResources\n |-PathMatchingResourcePatternResolver#doFindPathMatchingFileResources\n  |-PathMatchingResourcePatternResolver#doFindMatchingFileSystemResources\n\n```\n\n```\nprotected Set<Resource> doFindMatchingFileSystemResources(File rootDir, \n            String subPattern) throws IOException {\n    // ļ\n    Set<File> matchingFiles = retrieveMatchingFiles(rootDir, subPattern);\n    Set<Resource> result = new LinkedHashSet<>(matchingFiles.size());\n    for (File file : matchingFiles) {\n        result.add(new FileSystemResource(file));\n    }\n    return result;\n}\n\n```\n\n `PathMatchingResourcePatternResolver#doFindMatchingFileSystemResources` Уspring ɨ赽 File תΪ `FileSystemResource` 棬ĵڶ `Resource`  (ǰΪ `UrlResource`Ϊ `FileSystemResource`).\n\nصע `Set<File> matchingFiles = retrieveMatchingFiles(rootDir, subPattern);` spring ļҵģ\n\n```\n|-PathMatchingResourcePatternResolver#findPathMatchingResources\n |-PathMatchingResourcePatternResolver#doFindPathMatchingFileResources\n  |-PathMatchingResourcePatternResolver#doFindMatchingFileSystemResources\n   |-PathMatchingResourcePatternResolver#retrieveMatchingFiles\n    |-PathMatchingResourcePatternResolver#doRetrieveMatchingFiles\n\n```\n\n```\nprotected void doRetrieveMatchingFiles(String fullPattern, File dir, Set<File> result) \n        throws IOException {\n    for (File content : listDirectory(dir)) {\n        String currPath = StringUtils.replace(content.getAbsolutePath(), File.separator, \"/\");\n        if (content.isDirectory() && getPathMatcher().matchStart(fullPattern, currPath + \"/\")) {\n            if (!content.canRead()) {\n            }\n            else {\n                // ļУݹ\n                doRetrieveMatchingFiles(fullPattern, content, result);\n            }\n        }\n        // ļļ·\n        if (getPathMatcher().match(fullPattern, currPath)) {\n            result.add(content);\n        }\n    }\n}\n\n```\n\nϴȽϼ򵥣ƽļķʽһġ\n\nֵһǣ`getPathMatcher().match(fullPattern, currPath)` յõ `AntPathMatcher#doMatch`һ ant ·ƥ֤·д `*`紫 pattern  `/xxx/spring-framework/spring-learn/build/classes/java/main/org/springframework/learn/demo01/**/*.class`ʾƥ `/xxx/spring-framework/spring-learn/build/classes/java/main/org/springframework/learn/demo01/` ļ`.class` ļβļǰ path  `/xxx/spring-framework/spring-learn/build/classes/java/main/org/springframework/learn/demo01/BeanObj2.class`Ȼƥ䡣 `AntPathMatcher#doMatch` νƥģͲչˡ\n\nϲ裬ڵõ class ļӦ Resource .\n\n#### 1.3  Resource תΪ BeanDefinition\n\n Resource תΪ BeanDefinition\n\n> ClassPathScanningCandidateComponentProvider#scanCandidateComponents\n\n```\n//  resource õ MetadataReader\nMetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);\n\n// £\n// 1\\. ǷҪʼΪspring beanǷ @Component@Serviceע\n// 2\\. 鿴Ƿ@Conditionalһϵеע⣬ȻǷעBean\nif (isCandidateComponent(metadataReader)) {\n    //  metadataReader תΪ ScannedGenericBeanDefinitionҲBeanDefinitionеһԱ\n    ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);\n    ...\n}\n\n```\n\n##### 1\\.  Resource õ MetadataReader\n\n׷ `MetadataReader` Ļȡ\n\n```\n|-ClassPathScanningCandidateComponentProvider#scanCandidateComponents\n |-CachingMetadataReaderFactory#getMetadataReader\n  |-SimpleMetadataReaderFactory#getMetadataReader(Resource)\n   |-SimpleMetadataReader#SimpleMetadataReader\n\n```\n\nе `SimpleMetadataReader` Ĺ췽:\n\n```\nSimpleMetadataReader(Resource resource, @Nullable ClassLoader classLoader) throws IOException {\n    SimpleAnnotationMetadataReadingVisitor visitor \n        = new SimpleAnnotationMetadataReadingVisitor(classLoader);\n    // ﷢classļĶȡ\n    getClassReader(resource).accept(visitor, PARSING_OPTIONS);\n    this.resource = resource;\n    this.annotationMetadata = visitor.getMetadata();\n}\n\n```\n\nٽһ׷٣ class ļĶȡ `ClassReader` ࣺ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-595687d3a766ffaacf69f31216a5b09f9d5.png)\n\nʹ asm ȡ class ļȽϸӣͲˡ\n\nһֱҶΪ spring ͨȡϢģ֪**ԭ spring ͨ asm ֱӶȡ class ļȡϢ** \n\nµõ `MetadataReader` Ľ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-9df07587a3a8191231da87fe21d80052357.png)\n\nصע `annotations` ԣһ `annotations`  `mappings``annotations` Ϊ `@Service``mappings` һ飬Ϊ\n\n```\n0-@Service\n1-@Component\n2-@Index\n\n```\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-f5c7e8768b92b7a4303c4e1beb58863fe03.png)\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-38b493e0b4b7b73dda5f06f371b66f16d3e.png)\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-930a4e6844efd3f9f52c6d3f4e13a200337.png)\n\n`annotations` ˲² `BeanObj1` ϵע⣺\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-77271c438dde39f2bc905aa9f2cf9fb73d8.png)\n\n `mappings` ɶҲò²⣬ҲԴעзһЩߣ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-f31efb6252beb2108dc0ce93d482666f8d0.png)\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-a8ce5d11b4b02907f0ff497c6369a1c1dde.png)\n\n`@Service`  `@Component` ע⣬`@Component`  `@Indexed`߶ `mappings` У⿴רע֮ϵעģˣҾ͵⹦ܰɣ**ע⣺`mappings` ݺҪ**\n\n##### 2. `isCandidateComponent(MetadataReader)`жǷҪʵΪ spring bean\n\nһУǵõ basePackage **** `MetadataReader` ļע****ЩǲǶҪת `spring bean`йܵ spring أ `isCandidateComponent(MetadataReader)` Ĺˡϻ˵ϴ룺\n\n```\nprotected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {\n    // ʡԲִ\n    for (TypeFilter tf : this.includeFilters) {\n        // жǷҪйܵspring\n        if (tf.match(metadataReader, getMetadataReaderFactory())) {\n            // жǷ@Conditionalһϵеע\n            return isConditionMatch(metadataReader);\n        }\n    }\n    return false;\n}\n\n```\n\nҪжϣ\n\n*   ǷҪΪ spring bean\n*   Ƿ `@Conditional` һϵеע\n\nһжϡ\n\n spring У spring bean עкܶ࣬ `@Component``@Repository``@Service``@Controller``@Configuration`Լдעֻ࣬ҪЩע⣬\n\n```\n@Target(ElementType.TYPE)\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\n//  @Component  @Service  @Repository ֮һ\n@Component\npublic @interface MySpringBean {\n    ...\n}\n\n```\n\nܱ spring ʶ spring ṩע⣨`@Component``@Repository` ȣжǲ spring bean ʱֻҪ\n\n```\nif(annotation == Component.class || annotation == Repository.class) {\n    ...\n}\n\n```\n\nжϾˡԶע `@MySpringBean`spring ô֪ spring bean أǶ `@MySpringBean` ʱһҪ `@Component`  `@Service`  `@Repository` ֮һܱ spring ʶʲôأǸ `AbstractTypeHierarchyTraversingFilter#match(MetadataReader, MetadataReaderFactory)`ǶԲҪĴֻ\n\n```\n|-ClassPathScanningCandidateComponentProvider#isCandidateComponent(MetadataReader)\n |-AbstractTypeHierarchyTraversingFilter#match(MetadataReader, MetadataReaderFactory)\n  |-AnnotationTypeFilter#matchSelf\n\n```\n\nյ `AnnotationTypeFilter#matchSelf`:\n\n```\n@Override\nprotected boolean matchSelf(MetadataReader metadataReader) {\n    AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();\n    // annotationType @Component\n    return metadata.hasAnnotation(this.annotationType.getName()) ||\n        (this.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName()));\n}\n\n```\n\nؼˣ\n\n```\nmetadata.hasAnnotation(this.annotationType.getName())\n\nthis.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName())\n\n```\n\nȿ `metadata.hasAnnotation(this.annotationType.getName())` ıȽϣ\n\n```\n// AnnotationMetadata#hasAnnotation\ndefault boolean hasAnnotation(String annotationName) {\n    return getAnnotations().isDirectlyPresent(annotationName);\n}\n\n```\n\n `getAnnotations()` õĽ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-3e46b2ece5ddb2328342a469dc7dd554d9e.png)\n\nmappings \n\n```\n0-@Service\n1-@Component\n2-@Index\n\n```\n\nʵǰõ `MetadataReader` ݣ\n\n׷ȥ `isDirectlyPresent` ж `annotations`  `mappings` ûг `@Component`:\n\n```\nprivate boolean isPresent(Object requiredType, boolean directOnly) {\n    // ж annotations ûг @Component\n    for (MergedAnnotation<?> annotation : this.annotations) {\n        Class<? extends Annotation> type = annotation.getType();\n        if (type == requiredType || type.getName().equals(requiredType)) {\n            return true;\n        }\n    }\n    if (!directOnly) {\n        // ж mappings ûг @Component\n        for (AnnotationTypeMappings mappings : this.mappings) {\n            for (int i = 1; i < mappings.size(); i++) {\n                AnnotationTypeMapping mapping = mappings.get(i);\n                if (isMappingForType(mapping, requiredType)) {\n                    return true;\n                }\n            }\n        }\n    }\n    return false;\n}\n\n```\n\n `this.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName())`鿴ã\n\n```\n|-AnnotationTypeFilter#matchSelf\n |-AnnotationMetadata#hasMetaAnnotation\n  |-MergedAnnotationsCollection#get(String, Predicate)\n   |-MergedAnnotationsCollection#get(String, Predicate, MergedAnnotationSelector)\n    |-MergedAnnotationsCollection#find\n\n```\n\nյĲҷ `MergedAnnotationsCollection#find`:\n\n```\nprivate <A extends Annotation> MergedAnnotation<A> find(Object requiredType,\n        @Nullable Predicate<? super MergedAnnotation<A>> predicate,\n        @Nullable MergedAnnotationSelector<A> selector) {\n\n    MergedAnnotation<A> result = null;\n    for (int i = 0; i < this.annotations.length; i++) {\n        MergedAnnotation<?> root = this.annotations[i];\n        AnnotationTypeMappings mappings = this.mappings[i];\n        // mappings  mappings\n        for (int mappingIndex = 0; mappingIndex < mappings.size(); mappingIndex++) {\n            AnnotationTypeMapping mapping = mappings.get(mappingIndex);\n            if (!isMappingForType(mapping, requiredType)) {\n                continue;\n            }\n            // ҵ @Component ע\n            MergedAnnotation<A> candidate = (mappingIndex == 0\n                ? (MergedAnnotation<A>) root\n                : TypeMappedAnnotation.createIfPossible(mapping, root, IntrospectionFailureLogger.INFO));\n            if (candidate != null && (predicate == null || predicate.test(candidate))) {\n                if (selector.isBestCandidate(candidate)) {\n                    return candidate;\n                }\n                result = (result != null ? selector.select(result, candidate) : candidate);\n            }\n        }\n    }\n    return result;\n}\n\n```\n\nԿҷʽ `metadata.hasAnnotation(this.annotationType.getName())` ߶ơ\n\nϾ spring жǷ `@Service``@Component` ע߼ˡ\n\n `java` Уעǲܼ̳еģ\n\n```\n@Target({ElementType.TYPE, ElementType.METHOD})\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\npublic @interface Base {\n\n}\n\n@Target({ElementType.TYPE, ElementType.METHOD})\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\npublic @interface Child extends  Base {\n\n}\n\n```\n\n﷨ java вģspring ǲ`עע`ķʽʵڼ̳еĹܡ\n\n `ClassPathScanningCandidateComponentProvider#isConditionMatch` ʵϣжǷ `@Conditional` עģʶΪ spring beanյõ `ConditionEvaluator#shouldSkip(AnnotatedTypeMetadata, ConfigurationCondition.ConfigurationPhase)`:\n\n```\npublic boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {\n    // ʡһЩ\n\n    // õ condition \n    List<Condition> conditions = new ArrayList<>();\n        for (String[] conditionClasses : getConditionClasses(metadata)) {\n            for (String conditionClass : conditionClasses) {\n                Condition condition = getCondition(conditionClass, this.context.getClassLoader());\n                conditions.add(condition);\n            }\n        }\n    }\n\n    AnnotationAwareOrderComparator.sort(conditions);\n    // ж condition Ƿ\n    for (Condition condition : conditions) {\n        ConfigurationPhase requiredPhase = null;\n        if (condition instanceof ConfigurationCondition) {\n            requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();\n        }\n        if ((requiredPhase == null || requiredPhase == phase) \n                // ж condition Ƿһͷtrue\n                && !condition.matches(this.context, metadata)) {\n            return true;\n        }\n    }\n\n    return false;\n}\n\n// ͨȡ Condition \nprivate Condition getCondition(String conditionClassName, @Nullable ClassLoader classloader) {\n    Class<?> conditionClass = ClassUtils.resolveClassName(conditionClassName, classloader);\n    return (Condition) BeanUtils.instantiateClass(conditionClass);\n}\n\n```\n\n£\n\n1.  ȡ condition \n2.   condition 󣬵 `condition.matches()` жǷ\n\n##### 3\\.  `MetadataReader` õ `ScannedGenericBeanDefinition`\n\nһ򵥵ĸֵ `ScannedGenericBeanDefinition` Ĺ췽ˣ\n\n> ScannedGenericBeanDefinition#ScannedGenericBeanDefinition\n\n```\npublic ScannedGenericBeanDefinition(MetadataReader metadataReader) {\n    Assert.notNull(metadataReader, \"MetadataReader must not be null\");\n    this.metadata = metadataReader.getAnnotationMetadata();\n    setBeanClassName(this.metadata.getClassName());\n}\n\n```\n\nȽϼ򵥣Ͳˡ\n\n### 2\\. ḻ beanDefinition Ϣ\n\nǧգڵõ `beanDefinition`ʱ `beanDefinition` ḻǽһչ `beanDefinition` ϢˡЩϢ `bean``bean``@Lazy` ע⡢`@Primary` ע⡢`@DependsOn` עȣ£\n\n```\npublic abstract class AnnotationConfigUtils {\n\n    ...\n\n    /**\n     * һḻ BeanDefinition\n     */\n    static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd, \n            AnnotatedTypeMetadata metadata) {\n        //  @Lazy\n        AnnotationAttributes lazy = attributesFor(metadata, Lazy.class);\n        if (lazy != null) {\n            abd.setLazyInit(lazy.getBoolean(\"value\"));\n        }\n        else if (abd.getMetadata() != metadata) {\n            lazy = attributesFor(abd.getMetadata(), Lazy.class);\n            if (lazy != null) {\n                abd.setLazyInit(lazy.getBoolean(\"value\"));\n            }\n        }\n        //  @Primary\n        if (metadata.isAnnotated(Primary.class.getName())) {\n            abd.setPrimary(true);\n        }\n        //  @DependsOn\n        AnnotationAttributes dependsOn = attributesFor(metadata, DependsOn.class);\n        if (dependsOn != null) {\n            abd.setDependsOn(dependsOn.getStringArray(\"value\"));\n        }\n        //  @Role\n        AnnotationAttributes role = attributesFor(metadata, Role.class);\n        if (role != null) {\n            abd.setRole(role.getNumber(\"value\").intValue());\n        }\n        //  @Description\n        AnnotationAttributes description = attributesFor(metadata, Description.class);\n        if (description != null) {\n            abd.setDescription(description.getString(\"value\"));\n        }\n    }\n}\n\n```\n\n### 3.` registerBeanDefinition(definitionHolder, this.registry)`:  BeanDefinition  beanFactory\n\n `BeanDefinition`  `beanFactory` ĲȽϼ򵥣ؼĴ£\n\n```\n|-ClassPathBeanDefinitionScanner#registerBeanDefinition\n |-BeanDefinitionReaderUtils#registerBeanDefinition\n  |-GenericApplicationContext#registerBeanDefinition\n   |-DefaultListableBeanFactory#registerBeanDefinition\n\n```\n\n> DefaultListableBeanFactory#registerBeanDefinition\n\n```\nthis.beanDefinitionMap.put(beanName, beanDefinition);\n\n```\n\n `ClassPathBeanDefinitionScanner#registerBeanDefinition`  `DefaultListableBeanFactory#registerBeanDefinition`ȻһЩƣɲҵؼĴ롣\n\nˣϵ class ļ spring ɨ裬ڱ `BeanDefinition` `BeanFactory` ˡ\n\n### 4\\. ܽ\n\nıȽϳҪ spring ɨ·õ `beanDefinition` Ḷ́Ҫ£\n\n1.  ݰõ· `Resource`\n2.  · `Resouce` õ· class ļ `Resouce`\n3.   class ļ `Resouce` ͨ asm õ `MetadataReader`ע⣺ `MetadataReader`  class ļ `MetadataReader`\n4.   `MetadataReader` ҵҪ spring йܵ `MetadataReader`תΪ `ScannedGenericBeanDefinition``ScannedGenericBeanDefinition` Ϊ `BeanDefinition` ࣻ\n5.  һḻ `ScannedGenericBeanDefinition` Ϣ\n6.  õ `BeanDefinition` ӵ `BeanFactory` \n\nˣתΪ `BeanDefinition` ɡ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-f8ff2e4071ba3941e2a0664f6e478b78961.png)\n\nĻֵעĵط\n\n1.  spring ڻȡϵעʱͨ䣬ʹ asm ֱӽ class ļȻٻȡϵע\n2.  ڴעʱspring ͨ עע⡱ ʵһע̳еķʽҲ spring ʶ `@Component``@Service` ǿԶעԭ\n\nõ `BeanDefinition` 󣬽ž spring ĳʼˣƪټ\n\n* * *\n\n_ԭӣ[https://my.oschina.net/funcy/blog/4614071](https://my.oschina.net/funcy/blog/4614071) ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע_"
  },
  {
    "path": "docs/Spring全家桶/Spring源码分析/Spring启动流程/Spring启动流程（九）：单例bean的创建.md",
    "content": "![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-1f1ac8f3d8241fad9693d9684048ab7f3ae.png)\n\nģǷ `finishBeanFactoryInitialization(beanFactory)`Ľص bean Ĵ̡\n\nһƪУǽ `AbstractApplicationContext#finishBeanFactoryInitialization` ִй̣Ľϸڣ spring bean Ĵ̡\n\n```\n|-AnnotationConfigApplicationContext#AnnotationConfigApplicationContext(String...)\n |-AbstractApplicationContext#refresh\n  |-AbstractApplicationContext#finishBeanFactoryInitialization\n   |-DefaultListableBeanFactory#preInstantiateSingletons\n    |-AbstractBeanFactory#getBean(java.lang.String)\n     |-AbstractBeanFactory#doGetBean\n      |-AbstractAutowireCapableBeanFactory#createBean(String, RootBeanDefinition, Object[])\n\n```\n\nֱӿ `AbstractAutowireCapableBeanFactory#createBean(String, RootBeanDefinition, Object[])`Ĵ£\n\n```\n@Override\nprotected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)\n    \tthrows BeanCreationException {\n\n    RootBeanDefinition mbdToUse = mbd;\n\n    // ȷ BeanDefinition е Class \n    Class<?> resolvedClass = resolveBeanClass(mbd, beanName);\n    if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {\n        mbdToUse = new RootBeanDefinition(mbd);\n        mbdToUse.setBeanClass(resolvedClass);\n    }\n\n    // ׼дbeanж <lookup-method />  <replaced-method />\n    try {\n        mbdToUse.prepareMethodOverrides();\n    }\n    catch (BeanDefinitionValidationException ex) {\n        ...\n    }\n\n    try {\n        // дĻֱӷ\n        Object bean = resolveBeforeInstantiation(beanName, mbdToUse);\n        if (bean != null) {\n            return bean;\n        }\n    }\n    catch (Throwable ex) {\n        ...\n    }\n\n    try {\n        //  bean\n        Object beanInstance = doCreateBean(beanName, mbdToUse, args);\n        return beanInstance;\n    }\n    catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {\n        ...\n    }\n    catch (Throwable ex) {\n        ...\n    }\n}\n\n```\n\nԿ÷ļ£\n\n1.  ȷ class \n2.  д\n3.  Ҵ򷵻\n4.   spring bean\n\nǰĲעǽĸ `AbstractAutowireCapableBeanFactory#doCreateBean`:\n\n```\nprotected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, \n          final @Nullable Object[] args)  throws BeanCreationException {\n\n    BeanWrapper instanceWrapper = null;\n    if (mbd.isSingleton()) {\n        // factoryBeanӻɾ\n        instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);\n    }\n    if (instanceWrapper == null) {\n         // ʵ Beanյ\n         instanceWrapper = createBeanInstance(beanName, mbd, args);\n    }\n    //beanʵ\n    final Object bean = instanceWrapper.getWrappedInstance();\n    //bean\n    Class<?> beanType = instanceWrapper.getWrappedClass();\n    if (beanType != NullBean.class) {\n        mbd.resolvedTargetType = beanType;\n    }\n\n    synchronized (mbd.postProcessingLock) {\n        if (!mbd.postProcessed) {\n            try {\n                // ѭMergedBeanDefinitionPostProcessorpostProcessMergedBeanDefinition磬\n                // 1\\.  AutowiredAnnotationBeanPostProcessor.postProcessMergedBeanDefinition Ϊҵ\n                //  @Autowired@Value עԺͷ\n                // 2\\.  CommonAnnotationBeanPostProcessor.postProcessMergedBeanDefinition Ϊҵ\n                // \t@Resource עԺͷ\n                applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);\n            }\n            catch (Throwable ex) {\n                ...\n            }\n            mbd.postProcessed = true;\n        }\n    }\n\n    // ѭ, Ƿѭ, allowCircularReferencesĬΪtrueԹر\n    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&\n            isSingletonCurrentlyInCreation(beanName));\n    if (earlySingletonExposure) {\n        // ѭĽᵥ\n        ...\n    }\n\n    Object exposedObject = bean;\n    try {\n        // װ, Ҳǳ˵Զע룬Ҫ\n        populateBean(beanName, mbd, instanceWrapper);\n        // Ǵbeanʼɺĸֻص\n        // init-methodInitializingBean ӿڡBeanPostProcessor ӿ\n        exposedObject = initializeBean(beanName, exposedObject, mbd);\n    }\n    catch (Throwable ex) {\n        ...\n    }\n\n    //ͬģѭ\n    if (earlySingletonExposure) {\n        // ѭĽᵥ\n        ...\n    }\n\n    // beanעᵽӦScope\n    try {\n        registerDisposableBeanIfNecessary(beanName, bean, mbd);\n    }\n    catch (BeanDefinitionValidationException ex) {\n        ...\n    }\n\n    return exposedObject;\n}\n\n```\n\nԿspring ڴ bean ʱҪ£\n\n1.  ʵ\n2.  \n3.  ע\n4.  ʼ bean\n\nǾҪĸ衣\n\n#### 1\\. ʵ\n\nspring ʵķ `AbstractAutowireCapableBeanFactory#createBeanInstance`£\n\n```\nprotected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, \n        @Nullable Object[] args) {\n    // ȷѾ˴ class\n    Class<?> beanClass = resolveBeanClass(mbd, beanName);\n\n    // УķȨ\n    if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) \n                && !mbd.isNonPublicAccessAllowed()) {\n        throw new BeanCreationException(...);\n    }\n\n    // ǷbeanSupplierSupplierjava8ṩ࣬Դһlambdaʽ\n    //  AbstractBeanDefinition#setInstanceSupplier ָ\n    Supplier<?> instanceSupplier = mbd.getInstanceSupplier();\n    if (instanceSupplier != null) {\n        return obtainFromSupplier(instanceSupplier, beanName);\n    }\n\n    if (mbd.getFactoryMethodName() != null) {\n        // ùʵ\n        return instantiateUsingFactoryMethod(beanName, mbd, args);\n    }\n\n    // Ƿһ\n    boolean resolved = false;\n    // Ƿù캯ע\n    boolean autowireNecessary = false;\n    if (args == null) {\n        synchronized (mbd.constructorArgumentLock) {\n            if (mbd.resolvedConstructorOrFactoryMethod != null) {\n                resolved = true;\n                autowireNecessary = mbd.constructorArgumentsResolved;\n            }\n        }\n    }\n    if (resolved) {\n        if (autowireNecessary) {\n            return autowireConstructor(beanName, mbd, null, null);\n        }\n        else {\n            // ޲ι캯\n            return instantiateBean(beanName, mbd);\n        }\n    }\n\n    // Candidate constructors for autowiring?\n    // жǷвι캯\n    Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);\n    if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||\n            mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {\n        return autowireConstructor(beanName, mbd, ctors, args);\n    }\n\n    ctors = mbd.getPreferredConstructors();\n    if (ctors != null) {\n        // 캯ע\n        return autowireConstructor(beanName, mbd, ctors, null);\n    }\n\n    // ޲ι캯\n    return instantiateBean(beanName, mbd);\n}\n\n```\n\nϴspring ʵ bean ʱ 4 ַʽ\n\n1.  ʹʵobtainFromSupplier\n2.  ʹùinstantiateUsingFactoryMethod\n3.  ʹвι죺autowireConstructor\n4.  ʹ޲ι죺instantiateBean\n\nǰǿָķûɶ˵ģ spring ʵѡ췽ʵġһ bean ˵δṩκι췽ṩ޲ι췽 `AbstractAutowireCapableBeanFactory#instantiateBean` ʵ `AbstractAutowireCapableBeanFactory#autowireConstructor` ʵʵϣնִе `BeanUtils#instantiateClass(Constructor<T>, Object...)`:\n\n```\npublic static <T> T instantiateClass(Constructor<T> ctor, Object... args) \n            throws BeanInstantiationException {\n    Assert.notNull(ctor, \"Constructor must not be null\");\n    try {\n        ReflectionUtils.makeAccessible(ctor);\n        if (KotlinDetector.isKotlinReflectPresent() \n                   && KotlinDetector.isKotlinType(ctor.getDeclaringClass())) {\n            return KotlinDelegate.instantiateClass(ctor, args);\n        }\n        else {\n            Class<?>[] parameterTypes = ctor.getParameterTypes();\n            Assert.isTrue(args.length <= parameterTypes.length, \"...\");\n            Object[] argsWithDefaultValues = new Object[args.length];\n            for (int i = 0 ; i < args.length; i++) {\n                if (args[i] == null) {\n                    Class<?> parameterType = parameterTypes[i];\n                    argsWithDefaultValues[i] = (parameterType.isPrimitive() \n                                        ? DEFAULT_TYPE_VALUES.get(parameterType) : null);\n                }\n                else {\n                    argsWithDefaultValues[i] = args[i];\n                }\n            }\n            // ʵ\n            return ctor.newInstance(argsWithDefaultValues);\n        }\n    }\n    catch (...) {\n        ...\n    }\n}\n\n```\n\nԿյõ `java.lang.reflect.Constructor#newInstance` jdk ķˡ\n\n⣬ bean ṩ˶ 췽ʱspring һ׸ӵƶϻƣƶϳѵĹ췽Ͳչˡ\n\n#### 2\\. \n\n󴴽󣬽ǽעˣעǰҪ֪ЩҪע롣spring Բ `AbstractAutowireCapableBeanFactory#applyMergedBeanDefinitionPostProcessors` еúôвģ\n\n```\nprotected void applyMergedBeanDefinitionPostProcessors(RootBeanDefinition mbd, \n        Class<?> beanType, String beanName) {\n    for (BeanPostProcessor bp : getBeanPostProcessors()) {\n        if (bp instanceof MergedBeanDefinitionPostProcessor) {\n            // úô\n            MergedBeanDefinitionPostProcessor bdp = (MergedBeanDefinitionPostProcessor) bp;\n            bdp.postProcessMergedBeanDefinition(mbd, beanType, beanName);\n        }\n    }\n}\n\n```\n\nЩôУ\n\n1.   `AutowiredAnnotationBeanPostProcessor.postProcessMergedBeanDefinition` Ҵ `@Autowired``@Value` עԺͷ\n2.   `CommonAnnotationBeanPostProcessor.postProcessMergedBeanDefinition` Ҵ `@Resource` עԺͷ\n\nΪ˷˵ `@Autowired` ע̣ demo01  `org.springframework.learn.demo01.BeanObj1` д룺\n\n```\npackage org.springframework.learn.demo01;\n\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Service;\n\n@Service\npublic class BeanObj1 {\n\n    // Ϊ¼Ĵ\n    @Autowired\n    private BeanObj2 beanObj2;\n\n    public BeanObj1() {\n        System.out.println(\"beanObj1Ĺ췽\");\n    }\n\n    @Override\n    public String toString() {\n        return \"BeanObj1{}\";\n    }\n}\n\n```\n\n `AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition` \n\n```\n@Override\npublic void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, \n        Class<?> beanType, String beanName) {\n    // 뷽\n    InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);\n    // ֤ҵ뷽עᵽbeanDefinition\n    metadata.checkConfigMembers(beanDefinition);\n}\n\n```\n\nһ· `AutowiredAnnotationBeanPostProcessor#findAutowiringMetadata`  `AutowiredAnnotationBeanPostProcessor#buildAutowiringMetadata`:\n\n```\nprivate InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {\n     ...\n    // һѭ굱ǰҵǰĸֱ࣬Object\n    do {\n        final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();\n\n        // \n        ReflectionUtils.doWithLocalFields(targetClass, field -> {\n            MergedAnnotation<?> ann = findAutowiredAnnotation(field);\n            if (ann != null) {\n                if (Modifier.isStatic(field.getModifiers())) {\n                    return;\n                }\n                boolean required = determineRequiredStatus(ann);\n                // Էװ AutowiredFieldElement\n                currElements.add(new AutowiredFieldElement(field, required));\n            }\n        });\n\n        // ҷ\n        ReflectionUtils.doWithLocalMethods(targetClass, method -> {\n            Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);\n            if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {\n                return;\n            }\n            MergedAnnotation<?> ann = findAutowiredAnnotation(bridgedMethod);\n            if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {\n                if (Modifier.isStatic(method.getModifiers())) {\n                    return;\n                }\n                if (method.getParameterCount() == 0) {\n                }\n                boolean required = determineRequiredStatus(ann);\n                PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);\n                // װ AutowiredMethodElement\n                currElements.add(new AutowiredMethodElement(method, required, pd));\n            }\n        });\n\n        elements.addAll(0, currElements);\n        targetClass = targetClass.getSuperclass();\n    }\n    while (targetClass != null && targetClass != Object.class);\n\n    return InjectionMetadata.forElements(elements, clazz);\n}\n\n```\n\nϴǲҹ̣ܽ£\n\n*   ѭѯ࣬ӵǰ࿪ʼ굱ǰҵǰĸֱ࣬ Object Ϊֹ\n*   뷽ҵװΪ `AutowiredFieldElement`ҵװΪ `AutowiredMethodElement`\n*   ǲԻǷʹõĶ `findAutowiredAnnotation` \n\nǾͿ `findAutowiredAnnotation` νвҵġ `AutowiredAnnotationBeanPostProcessor#findAutowiredAnnotation` £\n\n```\nprivate final Set<Class<? extends Annotation>> autowiredAnnotationTypes \n        = new LinkedHashSet<>(4);\n\n// ĬϹ췽ָ@Autowired@Valueע\n@SuppressWarnings(\"unchecked\")\npublic AutowiredAnnotationBeanPostProcessor() {\n    this.autowiredAnnotationTypes.add(Autowired.class);\n    this.autowiredAnnotationTypes.add(Value.class);\n    ...\n}\n\n// ǲҷ\n@Nullable\nprivate MergedAnnotation<?> findAutowiredAnnotation(AccessibleObject ao) {\n    MergedAnnotations annotations = MergedAnnotations.from(ao);\n    // autowiredAnnotationTypes  @Autowired@Valueע\n    for (Class<? extends Annotation> type : this.autowiredAnnotationTypes) {\n        MergedAnnotation<?> annotation = annotations.get(type);\n        if (annotation.isPresent()) {\n            return annotation;\n        }\n    }\n    return null;\n}\n\n```\n\nϴעúˣͲ˵ˡ\n\nҵ뷽󣬽žעᵽ `beanDefinition` ˣ `InjectionMetadata#checkConfigMembers` Ĺ\n\n```\npublic void checkConfigMembers(RootBeanDefinition beanDefinition) {\n    Set<InjectedElement> checkedElements = new LinkedHashSet<>(this.injectedElements.size());\n    for (InjectedElement element : this.injectedElements) {\n        Member member = element.getMember();\n        if (!beanDefinition.isExternallyManagedConfigMember(member)) {\n            // עᵽ beanDefinition\n            beanDefinition.registerExternallyManagedConfigMember(member);\n            checkedElements.add(element);\n        }\n    }\n    this.checkedElements = checkedElements;\n}\n\n```\n\nνעᣬʵҲ൱򵥣 `beanDefinition` е `externallyManagedConfigMembers` һ¼\n\n> RootBeanDefinition#registerExternallyManagedConfigMember\n\n```\npublic void registerExternallyManagedConfigMember(Member configMember) {\n    synchronized (this.postProcessingLock) {\n        if (this.externallyManagedConfigMembers == null) {\n            this.externallyManagedConfigMembers = new HashSet<>(1);\n        }\n        // setһ¼\n        this.externallyManagedConfigMembers.add(configMember);\n    }\n}\n\n```\n\n#### 3\\. ע\n\nspring עڷ `AbstractAutowireCapableBeanFactory#populateBean` дģ£\n\n```\nprotected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {\n    if (bw == null) {\n        if (mbd.hasPropertyValues()) {\n            throw new BeanCreationException(...);\n        }\n        else {\n            return;\n        }\n    }\n\n    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {\n        for (BeanPostProcessor bp : getBeanPostProcessors()) {\n            if (bp instanceof InstantiationAwareBeanPostProcessor) {\n                // þĺô\n                // AutowiredAnnotationBeanPostProcessor.postProcessProperties  @Autowired@Value ע\n                // CommonAnnotationBeanPostProcessor.postProcessProperties  @Resourceע\n                InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;\n                //  falseҪкֵҲҪپ BeanPostProcessor Ĵ\n                if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {\n                    return;\n                }\n            }\n        }\n    }\n\n    // bean\n    PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);\n\n    int resolvedAutowireMode = mbd.getResolvedAutowireMode();\n    // עΪbyTypebyNameע@Autowired ȲbyTypeҲbyName,ﷵfalse\n    if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) {\n        MutablePropertyValues newPvs = new MutablePropertyValues(pvs);\n        // ͨҵֵ bean ȳʼ bean¼ϵ\n        if (resolvedAutowireMode == AUTOWIRE_BY_NAME) {\n            autowireByName(beanName, mbd, bw, newPvs);\n        }\n        // ͨװ䡣һЩ\n        if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) {\n            autowireByType(beanName, mbd, bw, newPvs);\n        }\n        pvs = newPvs;\n    }\n\n    boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors();\n    boolean needsDepCheck = (mbd.getDependencyCheck() != AbstractBeanDefinition.DEPENDENCY_CHECK_NONE);\n\n    PropertyDescriptor[] filteredPds = null;\n    if (hasInstAwareBpps) {\n        if (pvs == null) {\n            pvs = mbd.getPropertyValues();\n        }\n        for (BeanPostProcessor bp : getBeanPostProcessors()) {\n            if (bp instanceof InstantiationAwareBeanPostProcessor) {\n            // úôע͵\n            // @Autowiredע AutowiredAnnotationBeanPostProcessor\n            InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;\n            PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);\n            if (pvsToUse == null) {\n                if (filteredPds == null) {\n                    filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);\n                }\n                // Ϸᵽö@AutowiredһBeanPostProcessor\n                // б@Autowired@Value עԽֵ\n                pvsToUse = ibp.postProcessPropertyValues(pvs, filteredPds, \n                        bw.getWrappedInstance(), beanName);\n                if (pvsToUse == null) {\n                    return;\n                }\n            }\n            pvs = pvsToUse;\n            }\n        }\n    }\n    if (needsDepCheck) {\n        if (filteredPds == null) {\n             filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);\n        }\n        checkDependencies(beanName, mbd, filteredPds, pvs);\n    }\n\n    if (pvs != null) {\n        //  bean ʵֵ\n        applyPropertyValues(beanName, mbd, bw, pvs);\n    }\n}\n\n```\n\nspring ںôнеģ`@Autowired` ䷽Ϊ `AutowiredAnnotationBeanPostProcessor#postProcessProperties`:\n\n```\npublic PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {\n     //  findAutowiringMetadata ȷע @Autowired @Value ע뷽ȡɹ\n     // findAutowiringMetadata Է֣һ棬ֻеһβŻȥȡ֮󶼴ӻлȡ\n     InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);\n     try {\n         // ע\n         metadata.inject(bean, beanName, pvs);\n     }\n     catch (...) {\n         ...\n     }\n}\n\n```\n\nٽ `InjectionMetadata#inject`\n\n```\npublic void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) \n        throws Throwable {\n    Collection<InjectedElement> checkedElements = this.checkedElements;\n    Collection<InjectedElement> elementsToIterate =\n            (checkedElements != null ? checkedElements : this.injectedElements);\n    if (!elementsToIterate.isEmpty()) {\n        //  InjectedElement AutowiredAnnotationBeanPostProcessor#findAutowiringMetadata\n        // вҵģfield װΪ AutowiredFieldElementmethod װΪ AutowiredMethodElement\n        for (InjectedElement element : elementsToIterate) {\n            element.inject(target, beanName, pvs);\n        }\n    }\n}\n\n```\n\nٸ `element.inject(target, beanName, pvs)`õ `AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject`\n\n```\n@Override\nprotected void inject(Object bean, @Nullable String beanName, \n                @Nullable PropertyValues pvs) throws Throwable {\n        Field field = (Field) this.member;\n        Object value;\n        if (this.cached) {\n            value = resolvedCachedArgument(beanName, this.cachedFieldValue);\n        }\n        else {\n            DependencyDescriptor desc = new DependencyDescriptor(field, this.required);\n            desc.setContainingClass(bean.getClass());\n            Set<String> autowiredBeanNames = new LinkedHashSet<>(1);\n            Assert.state(beanFactory != null, \"No BeanFactory available\");\n            TypeConverter typeConverter = beanFactory.getTypeConverter();\n            try {\n                // һоǻȡעbean\n                value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);\n            }\n            catch (BeansException ex) {\n                ...\n            }\n            ...\n        }\n        // ֵͨ\n        if (value != null) {\n            ReflectionUtils.makeAccessible(field);\n            field.set(bean, value);\n        }\n    }\n}\n\n```\n\nϴеֻ࣬ҪҪעдˣ\n\n1.  ȡҪע bean: `value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter)`\n2.  ʹ÷ bean`ReflectionUtils.makeAccessible(field);field.set(bean, value);`\n\nڵ 2 㣬յõ jdk ṩķ䷽ûʲô˵ġص spring λȡҪע bean\n\n `beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter)`ԲҪĴ£\n\n```\n|-AnnotationConfigApplicationContext#AnnotationConfigApplicationContext(String...)\n |-AbstractApplicationContext#refresh\n  |-AbstractApplicationContext#finishBeanFactoryInitialization\n   |-DefaultListableBeanFactory#preInstantiateSingletons\n    |-AbstractBeanFactory#getBean(String)\n     |-AbstractBeanFactory#doGetBean\n      |-DefaultSingletonBeanRegistry#getSingleton(String, ObjectFactory<?>)\n       |-AbstractAutowireCapableBeanFactory#createBean(String, RootBeanDefinition, Object[])\n        |-AbstractAutowireCapableBeanFactory#doCreateBean\n         |-AbstractAutowireCapableBeanFactory#populateBean\n          |-AutowiredAnnotationBeanPostProcessor#postProcessProperties\n           |-InjectionMetadata#inject\n            |-AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject\n             |-DefaultListableBeanFactory#resolveDependency\n              |-DefaultListableBeanFactory#doResolveDependency\n\n```\n\nе `DefaultListableBeanFactory#doResolveDependency`:\n\n```\n@Nullable\npublic Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,\n         @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) \n         throws BeansException {\n\n    InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);\n    try {\n        ...\n        // 1\\.  ͲеbeanClassmapбΪbeanName -> Class\n        Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);\n           ...\n        String autowiredBeanName;\n        Object instanceCandidate;\n        // 2\\. ҵbeanж1\n        if (matchingBeans.size() > 1) {\n            // ȡע\n            autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);\n            if (autowiredBeanName == null) {\n                if (isRequired(descriptor) || !indicatesMultipleBeans(type)) {\n                    return descriptor.resolveNotUnique(descriptor.getResolvableType(), matchingBeans);\n                }\n                else {\n                    return null;\n                }\n            }\n            // 3\\. ע ҳmap<beanName, Class>ң\n            // õbeanClass\n            instanceCandidate = matchingBeans.get(autowiredBeanName);\n        }\n        else {\n            Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();\n            autowiredBeanName = entry.getKey();\n            instanceCandidate = entry.getValue();\n        }\n        if (autowiredBeanNames != null) {\n            autowiredBeanNames.add(autowiredBeanName);\n        }\n        if (instanceCandidate instanceof Class) {\n            // class ȡ beanbeanڣᴴbean\n            instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);\n        }\n        Object result = instanceCandidate;\n        if (result instanceof NullBean) {\n            if (isRequired(descriptor)) {\n                raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);\n            }\n            result = null;\n        }\n        if (!ClassUtils.isAssignableValue(type, result)) {\n            throw new BeanNotOfRequiredTypeException(...);\n        }\n        return result;\n    }\n    finally {\n        ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);\n    }\n}\n\n```\n\n bean Ļȡܽ£\n\n1.   bean ʹ spring лȡ bean  Class ע⣺õĶΪ Classһȡ Class  spring лȡ beanClass ж\n2.  õ Class жȡעҴӵ 1 õ Class вҷ 1  Class \n3.   Class  spring лȡ bean.\n\n磬ڽӿ `Inter`ʵ `A`  `B`߹ϵ£\n\n```\ninterface Inter {\n\n}\n\n@Service\nclass A implements Inter {\n\n}\n\n@Service\nclass B implements Inter {\n\n}\n\n```\n\n `C` Уע `Inter` ͣ\n\n```\n@Service\nclass C {\n    @Autowired\n    private Inter b;\n}\n\n```\n\nעʱ\n\n1.  spring ͨ `Inter.class` ңõࣺ`A.class`  `B.class`˶ bean Class \n2.  õע `b`Ȼʹ `b` ϵõ bean Class ҵϵ bean Class Ȼ`B.class` ϣ\n3.   spring лȡ `B.class` Ӧ bean.\n\nиС⣺ `C` ע:\n\n```\n@Service\nclass C {\n    @Autowired\n    private Inter inter;\n}\n\n```\n\n `Inter` Ϊ `inter` spring ûΪ `inter`  bean Class עɹ\n\nʵϣע벻ɹ spring ᱨ쳣ȤСԼԡ\n\nθ class  spring лȡӦ bean `instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this)`ȥ\n\n> DependencyDescriptor#resolveCandidate\n\n```\npublic Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory)\n        throws BeansException {\n    // õ AbstractBeanFactory#getBean(String)\n    return beanFactory.getBean(beanName);\n}\n\n```\n\n൱򵥣ֻһУ`beanFactory.getBean(beanName)`عµ\n\n```\n|-AnnotationConfigApplicationContext#AnnotationConfigApplicationContext(String...)\n |-AbstractApplicationContext#refresh\n  |-AbstractApplicationContext#finishBeanFactoryInitialization\n   |-DefaultListableBeanFactory#preInstantiateSingletons\n    // һεAbstractBeanFactory#getBean\n    // ʱĲΪbeanObj1\n    |-AbstractBeanFactory#getBean(String)\n     |-AbstractBeanFactory#doGetBean\n      |-DefaultSingletonBeanRegistry#getSingleton(String, ObjectFactory<?>)\n       |-AbstractAutowireCapableBeanFactory#createBean(String, RootBeanDefinition, Object[])\n        |-AbstractAutowireCapableBeanFactory#doCreateBean\n         |-AbstractAutowireCapableBeanFactory#populateBean\n          |-AutowiredAnnotationBeanPostProcessor#postProcessProperties\n           |-InjectionMetadata#inject\n            |-AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject\n             |-DefaultListableBeanFactory#resolveDependency\n              |-DefaultListableBeanFactory#doResolveDependency\n               |-DependencyDescriptor#resolveCandidate\n                // һεAbstractBeanFactory#getBean\n                // ʱĲΪbeanObj2\n                |-AbstractBeanFactory#getBean(String)\n                 .. 濪ʼ`beanObj2`ʵʼ\n\n```\n\nϵǰԿ\n\n*   ڻȡ `beanObj1` ʱһε `AbstractBeanFactory#getBean(String)`ڴʱ spring в `beanObj1`Ϳʼ `beanObj1` Ĵ\n*   `beanObj1` ʵɺ󣬽žͽע룬`beanObj1` и `BeanObj2 beanObj2`ͬ `AbstractBeanFactory#getBean(String)`  spring лȡ `beanObj2` \n*   ڴʱ spring в `beanObj2`žͽ `beanObj2` Ĵ̣ͬҲ `beanObj2` ע롢гʼȲ `beanObj1` Ĳƣ\n*   õ `beanObj2` ע뵽 `beanObj1`  `BeanObj2 beanObj2` УȻ `beanObj1` ĺ\n\nԿעʱܻʹ bean ʼǰͬʱζǳ A Ҫע BB Ҫע Cô A עʱʼ B B ڳʼʱֻʼ C\n\n bean ʵʼҪȷ£\n\n*   ʵָĲʹ new ƣδ spring ע뼰֮гʼ浽 spring һϵв\n*   ʼʵĶע롢гʼձ浽 spring \n\n#### 4\\. ʼ bean\n\nspring ʼ bean ķ `AbstractAutowireCapableBeanFactory#initializeBean(String, Object, RootBeanDefinition)`:\n\n```\nprotected Object initializeBean(final String beanName, final Object bean, \n             @Nullable RootBeanDefinition mbd) {\n    // 1\\.  invokeAwareMethods\n    if (System.getSecurityManager() != null) {\n        AccessController.doPrivileged((PrivilegedAction<Object>) () -> {\n             invokeAwareMethods(beanName, bean);\n             return null;\n        }, getAccessControlContext());\n    }\n    else {\n        invokeAwareMethods(beanName, bean);\n    }\n\n    // 2\\.  applyBeanPostProcessorsBeforeInitialization\n    Object wrappedBean = bean;\n    if (mbd == null || !mbd.isSynthetic()) {\n        // ִ spring еôxxxPostProcessor-------@PostConstruct\n        wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);\n    }\n\n    try {\n        // 3\\. ִ InitializingBean ʼ\n        invokeInitMethods(beanName, wrappedBean, mbd);\n    }\n    catch (Throwable ex) {\n        ...\n    }\n    if (mbd == null || !mbd.isSynthetic()) {\n        // 4\\.  applyBeanPostProcessorsAfterInitialization\n        wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);\n    }\n\n    return wrappedBean;\n}\n\n```\n\nϴҪ 4 \n\n1.  invokeAwareMethods(beanName, bean);\n2.  applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);\n3.  invokeInitMethods(beanName, wrappedBean, mbd);\n4.  applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);\n\n##### 4.1  Aware bean ķ\n\n`invokeAwareMethods` ִ Aware bean ط\n\n```\nprivate void invokeAwareMethods(final String beanName, final Object bean) {\n    if (bean instanceof Aware) {\n        if (bean instanceof BeanNameAware) {\n            ((BeanNameAware) bean).setBeanName(beanName);\n        }\n        if (bean instanceof BeanClassLoaderAware) {\n            ClassLoader bcl = getBeanClassLoader();\n            if (bcl != null) {\n                ((BeanClassLoaderAware) bean).setBeanClassLoader(bcl);\n            }\n        }\n        if (bean instanceof BeanFactoryAware) {\n            ((BeanFactoryAware) bean).setBeanFactory(AbstractAutowireCapableBeanFactory.this);\n        }\n    }\n}\n\n```\n\nϷȽϼ򵥣Ͳ˵ˡ\n\n##### 4.2 кô `postProcessBeforeInitialization` \n\n `AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInitialization` У\n\n```\npublic Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)\n         throws BeansException {\n    Object result = existingBean;\n    for (BeanPostProcessor processor : getBeanPostProcessors()) {\n        // úô\n        Object current = processor.postProcessBeforeInitialization(result, beanName);\n        if (current == null) {\n            return result;\n        }\n        result = current;\n    }\n    return result;\n}\n\n```\n\nҪ `ApplicationContextAwareProcessor#postProcessBeforeInitialization` £\n\n```\n@Override\n@Nullable\npublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {\n    if (!(bean instanceof EnvironmentAware || bean instanceof EmbeddedValueResolverAware ||\n              bean instanceof ResourceLoaderAware || bean instanceof ApplicationEventPublisherAware ||\n              bean instanceof MessageSourceAware || bean instanceof ApplicationContextAware)){\n        return bean;\n    }\n    AccessControlContext acc = null;\n    if (System.getSecurityManager() != null) {\n        acc = this.applicationContext.getBeanFactory().getAccessControlContext();\n    }\n    if (acc != null) {\n        AccessController.doPrivileged((PrivilegedAction<Object>) () -> {\n           invokeAwareInterfaces(bean);\n           return null;\n        }, acc);\n    }\n    else {\n        // з\n        invokeAwareInterfaces(bean);\n    }\n    return bean;\n}\n\nprivate void invokeAwareInterfaces(Object bean) {\n    if (bean instanceof EnvironmentAware) {\n        ((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());\n    }\n    if (bean instanceof EmbeddedValueResolverAware) {\n        ((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);\n    }\n    if (bean instanceof ResourceLoaderAware) {\n        ((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);\n    }\n    if (bean instanceof ApplicationEventPublisherAware) {\n        ((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);\n    }\n    if (bean instanceof MessageSourceAware) {\n        ((MessageSourceAware) bean).setMessageSource(this.applicationContext);\n    }\n    // װ ʵApplicationContextAware applicationContext\n    if (bean instanceof ApplicationContextAware) {\n        ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);\n    }\n}\n\n```\n\nʵǸ `XxxAware` ֵ `ApplicationContextAware` ֵҲС\n\n˳һǣ bean У `@PostConstruct` ע⣺\n\n```\n@PostConstruct\npublic void test() {\n    System.out.println(\"PostConstruct\");\n}\n\n```\n\n÷ `InitDestroyAnnotationBeanPostProcessor#postProcessBeforeInitialization` неãȽϼ򵥣Ҳ jdk ķƣͲ˵ˡ\n\n##### 4.3  bean ĳʼ\n\n `AbstractAutowireCapableBeanFactory#invokeInitMethods` :\n\n```\nprotected void invokeInitMethods(String beanName, final Object bean, \n        @Nullable RootBeanDefinition mbd)  throws Throwable {\n    // ִ InitializingBean#afterPropertiesSet \n    boolean isInitializingBean = (bean instanceof InitializingBean);\n    if (isInitializingBean && (mbd == null || \n                 !mbd.isExternallyManagedInitMethod(\"afterPropertiesSet\"))) {\n        if (System.getSecurityManager() != null) {\n            try {\n                AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {\n                    ((InitializingBean) bean).afterPropertiesSet();\n                    return null;\n                }, getAccessControlContext());\n            }\n            catch (PrivilegedActionException pae) {\n                throw pae.getException();\n            }\n        }\n        else {\n            // beanʵ InitializingBean ӿڣ afterPropertiesSet()\n            ((InitializingBean) bean).afterPropertiesSet();\n        }\n    }\n\n    // ִԶĳʼxmlУͨ init-method ָķ\n    if (mbd != null && bean.getClass() != NullBean.class) {\n        String initMethodName = mbd.getInitMethodName();\n        if (StringUtils.hasLength(initMethodName) &&\n                !(isInitializingBean && \"afterPropertiesSet\".equals(initMethodName)) &&\n                !mbd.isExternallyManagedInitMethod(initMethodName)) {\n            invokeCustomInitMethod(beanName, bean, mbd);\n        }\n    }\n}\n\n```\n\nҪ\n\n1.   bean ʵ InitializingBean ӿڣ afterPropertiesSet ()\n2.  ָ init-method `invokeCustomInitMethod` УҲͨ jdk еģͲ˵ˡ\n\n##### 4.4 кô `postProcessAfterInitialization` \n\nһҪ `BeanPostProcessor#postProcessAfterInitialization`е `BeanPostProcessor`  `ApplicationListenerDetector`:\n\n```\n@Override\npublic Object postProcessAfterInitialization(Object bean, String beanName) {\n    if (bean instanceof ApplicationListener) {\n        Boolean flag = this.singletonNames.get(beanName);\n        if (Boolean.TRUE.equals(flag)) {\n            this.applicationContext.addApplicationListener((ApplicationListener<?>) bean);\n        }\n        else if (Boolean.FALSE.equals(flag)) {\n            this.singletonNames.remove(beanName);\n        }\n    }\n    return bean;\n}\n\n```\n\n `ApplicationListener` ģ bean ʵ `ApplicationListener`ӵ spring ļбġ\n\n#### 5\\. ܽ\n\nҪ spring bean ̣ܽ£\n\n1.  ʵ beanָ˳ʼָ `factory method`ƶϹ췽ȷʽ\n\n2.  ѯעԻ򷽷ЩԻ򷽷ϱע `@Autowird`/`@Value`/`@Resource`\n\n    *   `AutowiredAnnotationBeanPostProcessor.postProcessMergedBeanDefinition` Ҵ @Autowired@Value עԺͷ\n    *   `CommonAnnotationBeanPostProcessor.postProcessMergedBeanDefinition` Ҵ @Resource עԺͷ\n3.  \n\n    *   `AutowiredAnnotationBeanPostProcessor.postProcessProperties`  `@Autowired``@Value` ע⣬`CommonAnnotationBeanPostProcessor.postProcessProperties`  `@Resource` ע\n    *    `@Autowired` ʱȸҵе `Class`жٸעԵƲҷϵ `Class` `beanFactory.getBean(...)`  spring ȡҪעĶͨΪӦֵ\n4.  ʼ bean\n\n    1.  ִ Aware bean ط\n    2.   `BeanPostProcessor#postProcessBeforeInitialization` \n        *    `ApplicationContextAwareProcessor#postProcessBeforeInitialization` ִ Aware bean ط\n        *    `InitDestroyAnnotationBeanPostProcessor#postProcessBeforeInitialization` д `@PostConstruct` ע\n    3.  ִгʼ\n        *   ִ `InitializingBean#afterPropertiesSet` \n        *   ִԶ `init-method` \n    4.   `BeanPostProcessor#postProcessAfterInitialization` \n        *    `ApplicationListenerDetector#postProcessAfterInitialization` д spring \n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-b7a4d4bc1bfbd76537e40cf843b0d18df93.png)\n\n spring bean Ĵ̾ȷˣ spring ڴ bean ĹУһ޷ӵѭⷽĴԲο\n\n*   [spring ֮̽ѭĽһۻʯ](https://my.oschina.net/funcy/blog/4659555)\n*   [spring ֮̽ѭĽԴ](https://my.oschina.net/funcy/blog/4815992)\n\n*   * *\n\n_ԭӣ[https://my.oschina.net/funcy/blog/4659524](https://my.oschina.net/funcy/blog/4659524) ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע_"
  },
  {
    "path": "docs/Spring全家桶/Spring源码分析/Spring启动流程/Spring启动流程（二）：ApplicationContext的创建.md",
    "content": "![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-07194ddebd25cb2b71ee5e422bf84e8a397.png)\n\nǰһƪУǷ spring ̣ƪʼ ǽеһЩؼз\n\nǻڵ `demo01`ֱӽ `ApplicationContext context = new AnnotationConfigApplicationContext(\"org.springframework.learn.demo01\");` ִУ\n\n> AnnotationConfigApplicationContext\n\n```\npublic AnnotationConfigApplicationContext(String... basePackages) {\n    this();\n    scan(basePackages);\n    refresh();\n}\n\n```\n\nֻУÿд [spring ̸](https://my.oschina.net/funcy/blog/4597493 \"spring̸\")˵ǽЩչϸݡ\n\n### 1. `beanFacotry` Ĵ\n\nֱӽ `this()` £\n\n> AnnotationConfigApplicationContext\n\n```\npublic AnnotationConfigApplicationContext() {\n    // AnnotatedBeanDefinitionReader  @Configuration\n    this.reader = new AnnotatedBeanDefinitionReader(this);\n    this.scanner = new ClassPathBeanDefinitionScanner(this);\n}\n\n```\n\nϿֻУ󡣵Ϥ java ﷨Ķ֪ڵù췽ʱȵøĹ췽ִ๹췽Ĵ룬ЩǻҪĸ๹췽ʲô\n\n> GenericApplicationContext\n\n```\npublic GenericApplicationContext() {\n    this.beanFactory = new DefaultListableBeanFactory();\n}\n\n```\n\nԿĹ췽һ£ `beanFactory`Դˣǿ֪**`AnnotationConfigApplicationContext` ʹõ `BeanFacotry` Ϊ `DefaultListableBeanFactory`**\n\nٻص `AnnotationConfigApplicationContext` Ĺ췽\n\n> AnnotationConfigApplicationContext\n\n```\npublic AnnotationConfigApplicationContext() {\n    // AnnotatedBeanDefinitionReader  @Configuration\n    this.reader = new AnnotatedBeanDefinitionReader(this);\n    this.scanner = new ClassPathBeanDefinitionScanner(this);\n}\n\n```\n\nȻֻУȴС `new AnnotatedBeanDefinitionReader(this);` £еķòҪṩ\n\n```\nAnnotationConfigApplicationContext#AnnotationConfigApplicationContext()\n |-AnnotatedBeanDefinitionReader#AnnotatedBeanDefinitionReader(BeanDefinitionRegistry)\n  |-AnnotatedBeanDefinitionReader#AnnotatedBeanDefinitionReader(BeanDefinitionRegistry, Environment)\n   |-AnnotationConfigUtils#registerAnnotationConfigProcessors(BeanDefinitionRegistry)\n    |-AnnotationConfigUtils#registerAnnotationConfigProcessors(BeanDefinitionRegistry, Object)\n\n```\n\nԿǵ `AnnotationConfigUtils#registerAnnotationConfigProcessors(BeanDefinitionRegistry, Object)`ǲ鿴¸÷Ϊֱ۲鿴ʡ˲ҪĴ룬ǽעҪ̼ɣ\n\n```\npublic static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(\n        BeanDefinitionRegistry registry, @Nullable Object source) {\n    // beanFactory\n    DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry);\n\n    // -------- beanFactoryӴ\n    if (beanFactory != null) {\n        if (!(beanFactory.getDependencyComparator() instanceof AnnotationAwareOrderComparator)) {\n        beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);\n        }\n        if (!(beanFactory.getAutowireCandidateResolver() \n                instanceof ContextAnnotationAutowireCandidateResolver)) {\n            beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());\n        }\n    }\n\n    Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8);\n\n    // ------------  beanFactorybeanDefinition\n    if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {\n        RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);\n        def.setSource(source);\n        beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));\n    }\n\n    if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {\n        RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);\n        def.setSource(source);\n        beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));\n    }\n\n    // Check for JSR-250 support, and if present add the CommonAnnotationBeanPostProcessor.\n    if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {\n        RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);\n        def.setSource(source);\n        beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));\n    }\n\n    // Check for JPA support, and if present add the PersistenceAnnotationBeanPostProcessor.\n    if (jpaPresent && !registry.containsBeanDefinition(PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)) {\n        RootBeanDefinition def = new RootBeanDefinition();\n        try {\n            def.setBeanClass(ClassUtils.forName(PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME,\n                AnnotationConfigUtils.class.getClassLoader()));\n        }\n        catch (ClassNotFoundException ex) {\n            throw new IllegalStateException(...);\n        }\n        def.setSource(source);\n        beanDefs.add(registerPostProcessor(registry, def, PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME));\n    }\n\n    if (!registry.containsBeanDefinition(EVENT_LISTENER_PROCESSOR_BEAN_NAME)) {\n        RootBeanDefinition def = new RootBeanDefinition(EventListenerMethodProcessor.class);\n        def.setSource(source);\n        beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_PROCESSOR_BEAN_NAME));\n    }\n\n    if (!registry.containsBeanDefinition(EVENT_LISTENER_FACTORY_BEAN_NAME)) {\n        RootBeanDefinition def = new RootBeanDefinition(DefaultEventListenerFactory.class);\n        def.setSource(source);\n        beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_FACTORY_BEAN_NAME));\n    }\n    return beanDefs;\n}\n\n```\n\nȻе㳤ȴ൱ֱף `beanFactory`  `annotation` صĴʵϣ [spring ̸](https://my.oschina.net/funcy/blog/4597493 \"spring̸\")ᵽ beanDefinitionMap  4 Ĭϵ beanDefinition ӵģ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-a490052bdc1379ef24a7754c65584214c1c.png)\n\nٹע `this.scanner = new ClassPathBeanDefinitionScanner(this)`ǳʼ `scanner` Ϊ `ClassPathBeanDefinitionScanner`ϿԿ `classPath` صģ`beanDefinition` ɨͨ׵˵**ɨ classPath · java class ļװ `beanDefinition` **\n\n### 2\\. ܽ\n\n`AnnotationConfigApplicationContext#AnnotationConfigApplicationContext(String...)`  `this()` ִоͷˣһ дҪ⼸£\n\n1.  Ϊ `DefaultListableBeanFactory`  `beanFactory`\n2.  Ϊ `AnnotatedBeanDefinitionReader`  reader䴴ĹУ `beanFactory`  annotation صĴ\n3.  Ϊ `ClassPathBeanDefinitionScanner`  `scanner`\n\nͼʾ£\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-b7a7a01b4d38769419a0e25e8f60037cbb5.png)\n\nľȵˣǼĴ롣"
  },
  {
    "path": "docs/Spring全家桶/Spring源码分析/Spring启动流程/Spring启动流程（五）：执行BeanFactoryPostProcessor.md",
    "content": "![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-c8c446fca94a382109bdc7b6babd6db4c0d.png)\n\nģǼ\n\n### 4\\. չ㣺`postProcessBeanFactory(beanFactory)`\n\n spring ṩչ㣬κιܣʵ֣`AbstractApplicationContext`  `postProcessBeanFactory` £\n\n```\n    protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {\n    }\n\n```\n\nǰʹõ `ApplicationContext`  `AnnotationConfigApplicationContext`ûʵ\n\n### 5\\. ִ `BeanFactoryPostProcessors`: `invokeBeanFactoryPostProcessors(beanFactory)`\n\n> `BeanFactoryPostProcessor` Ϊ beanFactory ĺô޸ beanFactory һЩΪ `BeanFactoryPostProcessor` ϸԲο [spring ֮ BeanFactoryPostProcessors](https://my.oschina.net/funcy/blog/4597545)\n\n `BeanFactoryPostProcessor`Ἰ㣺\n\n*   `BeanFactoryPostProcessor` Ϊ֣`BeanFactoryPostProcessor`  `BeanDefinitionRegistryPostProcessor`\n*   `BeanDefinitionRegistryPostProcessor`  `BeanFactoryPostProcessor` \n*   ִ `BeanDefinitionRegistryPostProcessor` ķִ `BeanFactoryPostProcessor` ķ\n\n˽ЩǸ룬ԲҪֻ£\n\n```\n|-AnnotationConfigApplicationContext#AnnotationConfigApplicationContext(String...)\n |-AbstractApplicationContext#refresh\n  |-AbstractApplicationContext#invokeBeanFactoryPostProcessors\n   |-PostProcessorRegistrationDelegate\n     #invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory, List<BeanFactoryPostProcessor>)\n\n```\n\nֱӿ `invokeBeanFactoryPostProcessors` \n\n```\npublic static void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory, \n            List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {\n\n    // дڵBeanDefinitionRegistryPostProcessor\n    Set<String> processedBeans = new HashSet<>();\n\n    //beanFactoryDefaultListableBeanFactoryBeanDefinitionRegistryʵ࣬Կ϶if\n    if (beanFactory instanceof BeanDefinitionRegistry) {\n        BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;\n\n        //regularPostProcessors BeanFactoryPostProcessor\n        List<BeanFactoryPostProcessor> regularPostProcessors = new ArrayList<>();\n\n        //registryProcessors BeanDefinitionRegistryPostProcessor\n        //BeanDefinitionRegistryPostProcessor չ BeanFactoryPostProcessor\n        List<BeanDefinitionRegistryPostProcessor> registryProcessors = new ArrayList<>();\n\n        // ѭbeanFactoryPostProcessors£beanFactoryPostProcessors϶û\n        // ΪbeanFactoryPostProcessorsǻֶӵģspringɨ\n        // ֶֻannotationConfigApplicationContext.addBeanFactoryPostProcessor(XXX)Ż\n        for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {\n            // жpostProcessorǲBeanDefinitionRegistryPostProcessor\n            // ΪBeanDefinitionRegistryPostProcessor չBeanFactoryPostProcessor\n            // ҪжǲBeanDefinitionRegistryPostProcessor, ǵĻֱִ\n            // postProcessBeanDefinitionRegistryȻѶװregistryProcessorsȥ\n            if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {\n                BeanDefinitionRegistryPostProcessor registryProcessor =\n                        (BeanDefinitionRegistryPostProcessor) postProcessor;\n                registryProcessor.postProcessBeanDefinitionRegistry(registry);\n                registryProcessors.add(registryProcessor);\n            }\n            else {\n                //ǵĻװregularPostProcessors\n                regularPostProcessors.add(postProcessor);\n            }\n        }\n\n        // һʱװBeanDefinitionRegistryPostProcessor\n        // BeanDefinitionRegistry̳PostProcessorBeanFactoryPostProcessor\n        List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();\n\n        // ʵBeanDefinitionRegistryPostProcessorӿڵBeanName\n        // springڲṩBeanDefinitionRegistryPostProcessor\n        // ԼԼʵֵBeanDefinitionRegistryPostProcessor\n        String[] postProcessorNames =\n            beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);\n        for (String ppName : postProcessorNames) {\n            if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {\n               //ConfigurationClassPostProcessor࣬ҷŵcurrentRegistryProcessors\n               //ConfigurationClassPostProcessorǺҪһ࣬ʵ\n               //BeanDefinitionRegistryPostProcessorӿڣ\n               //BeanDefinitionRegistryPostProcessorӿʵBeanFactoryPostProcessorӿ\n               //ConfigurationClassPostProcessorǼҪ, ִ\n               //ɨ @Bean@Import@ImportResource ȸֲ\n               currentRegistryProcessors.add(beanFactory.getBean(\n                   ppName, BeanDefinitionRegistryPostProcessor.class));\n               processedBeans.add(ppName);\n            }\n        }\n\n       //\n       sortPostProcessors(currentRegistryProcessors, beanFactory);\n\n       //ϲProcessorsΪʲôҪϲΪregistryProcessors\n       //װBeanDefinitionRegistryPostProcessor\n       //һʼʱspringִֻBeanDefinitionRegistryPostProcessorеķ\n       //ִBeanDefinitionRegistryPostProcessorķBeanFactoryProcessorķ\n       //ҪѴһУͳһִиķ\n       registryProcessors.addAll(currentRegistryProcessors);\n\n       //ΪִConfigurationClassPostProcessorpostProcessBeanDefinitionRegistry\n       invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);\n       //ΪcurrentRegistryProcessorsһʱҪ\n       currentRegistryProcessors.clear();\n\n       // ٴθBeanDefinitionRegistryPostProcessorBeanName\n       // BeanNameǷѾִйˣûʵOrderedӿ\n       // ûбִйҲʵOrderedӿڵĻѶ͵currentRegistryProcessors\n       // ͵processedBeans\n       // ûʵOrderedӿڵĻﲻݼӵcurrentRegistryProcessors\n       // processedBeansУ\n       // ſԻǶʵBeanDefinitionRegistryPostProcessorBean\n       postProcessorNames = beanFactory.getBeanNamesForType(\n              BeanDefinitionRegistryPostProcessor.class, true, false);\n       for (String ppName : postProcessorNames) {\n           if (!processedBeans.contains(ppName) && beanFactory.isTypeMatch(ppName, Ordered.class)) {\n              currentRegistryProcessors.add(beanFactory.getBean(\n                  ppName, BeanDefinitionRegistryPostProcessor.class));\n              processedBeans.add(ppName);\n           }\n       }\n       //\n       sortPostProcessors(currentRegistryProcessors, beanFactory);\n       //ϲProcessors\n       registryProcessors.addAll(currentRegistryProcessors);\n       //ִԶBeanDefinitionRegistryPostProcessor\n       invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);\n       //ʱ\n       currentRegistryProcessors.clear();\n       // ĴִʵOrderedӿڵBeanDefinitionRegistryPostProcessor\n       // ĴִûʵOrderedӿڵBeanDefinitionRegistryPostProcessor\n       boolean reiterate = true;\n       while (reiterate) {\n           reiterate = false;\n           postProcessorNames = beanFactory.getBeanNamesForType(\n                    BeanDefinitionRegistryPostProcessor.class, true, false);\n           for (String ppName : postProcessorNames) {\n                if (!processedBeans.contains(ppName)) {\n                    currentRegistryProcessors.add(beanFactory.getBean(\n                            ppName, BeanDefinitionRegistryPostProcessor.class));\n                    processedBeans.add(ppName);\n                    reiterate = true;\n                }\n           }\n           sortPostProcessors(currentRegistryProcessors, beanFactory);\n           registryProcessors.addAll(currentRegistryProcessors);\n           invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);\n           currentRegistryProcessors.clear();\n       }\n\n       // registryProcessorsװBeanDefinitionRegistryPostProcessor\n       // ĴִеķҪٰѸķҲִһ\n       invokeBeanFactoryPostProcessors(registryProcessors, beanFactory);\n       invokeBeanFactoryPostProcessors(regularPostProcessors, beanFactory);\n    }\n\n    else {\n        //regularPostProcessorsװBeanFactoryPostProcessorִBeanFactoryPostProcessorķ\n        //regularPostProcessorsһ£ǲݵģ\n        //ֶֻBeanFactoryPostProcessorŻ\n        invokeBeanFactoryPostProcessors(beanFactoryPostProcessors, beanFactory);\n    }\n\n    String[] postProcessorNames =\n            beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false);\n\n    List<BeanFactoryPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();\n    List<String> orderedPostProcessorNames = new ArrayList<>();\n    List<String> nonOrderedPostProcessorNames = new ArrayList<>();\n    //ѭBeanName\n    for (String ppName : postProcessorNames) {\n        //Beanִйˣ\n        if (processedBeans.contains(ppName)) {\n\n        }\n        //ʵPriorityOrderedӿڣ뵽priorityOrderedPostProcessors\n        else if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {\n            priorityOrderedPostProcessors.add(beanFactory\n                .getBean(ppName, BeanFactoryPostProcessor.class));\n        }\n        //ʵOrderedӿڣ뵽orderedPostProcessorNames\n        else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {\n            orderedPostProcessorNames.add(ppName);\n        }\n        //ûʵPriorityOrderedҲûʵOrdered뵽nonOrderedPostProcessorNames\n        else {\n            nonOrderedPostProcessorNames.add(ppName);\n        }\n    }\n\n    // priorityOrderedPostProcessorsʵPriorityOrderedӿڵBeanFactoryPostProcessor\n    sortPostProcessors(priorityOrderedPostProcessors, beanFactory);\n\n    // ִpriorityOrderedPostProcessors\n    invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory);\n\n    // ִʵOrderedӿڵBeanFactoryPostProcessor\n    List<BeanFactoryPostProcessor> orderedPostProcessors \n            = new ArrayList<>(orderedPostProcessorNames.size());\n    for (String postProcessorName : orderedPostProcessorNames) {\n        orderedPostProcessors.add(beanFactory.getBean(\n            postProcessorName, BeanFactoryPostProcessor.class));\n    }\n    sortPostProcessors(orderedPostProcessors, beanFactory);\n    invokeBeanFactoryPostProcessors(orderedPostProcessors, beanFactory);\n\n    // ִмûʵPriorityOrderedӿڣҲûʵOrderedӿڵBeanFactoryPostProcessor\n    List<BeanFactoryPostProcessor> nonOrderedPostProcessors \n            = new ArrayList<>(nonOrderedPostProcessorNames.size());\n    for (String postProcessorName : nonOrderedPostProcessorNames) {\n        nonOrderedPostProcessors.add(beanFactory.getBean(\n                postProcessorName, BeanFactoryPostProcessor.class));\n    }\n    invokeBeanFactoryPostProcessors(nonOrderedPostProcessors, beanFactory);\n\n    beanFactory.clearMetadataCache();\n}\n\n```\n\nǳǳѣִѭ⼸\n\n*   ִ `BeanDefinitionRegistryPostProcessor`ִ `BeanFactoryPostProcessor`\n*   ִ `BeanDefinitionRegistryPostProcessor` ˳£\n    1.  ִв `BeanDefinitionRegistryPostProcessor`\n    2.  ִ spring ڲṩģִһǳҪ `BeanDefinitionRegistryPostProcessor``ConfigurationClassPostProcessor`ᴦĿе `@ComponentScan``@Component``@Import``@Bean` ע⣬ûԶ `BeanDefinitionRegistryPostProcessor``BeanFactoryPostProcessor`\n    3.  ִʣµ `BeanDefinitionRegistryPostProcessor`Ҳһмص `BeanDefinitionRegistryPostProcessor`\n*   ִ `BeanFactoryPostProcessor` ˳£\n    1.  ִʵ `PriorityOrdered` ӿڵ `BeanFactoryPostProcessor`\n    2.  ִʵ `Ordered` ӿڵ `BeanFactoryPostProcessor`\n    3.  ִʣµ `BeanFactoryPostProcessor`\n*   `BeanDefinitionRegistryPostProcessor`  `BeanFactoryPostProcessor` ࣬ͬҪִ `BeanFactoryPostProcessor` ķ\n\n˵ִ `BeanDefinitionRegistryPostProcessor`ִָ `PostProcessorRegistrationDelegate#invokeBeanDefinitionRegistryPostProcessors`ִ `BeanFactoryPostProcessor` ִָ `BeanFactoryPostProcessor#postProcessBeanFactory`\n\nݺ󣬽ǶϷϸˣ\n\n1.  ִ `BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry`:\n    1.  ִпֶ `applicationContext.addBeanFactoryPostProcessor` ӵ `BeanDefinitionRegistryPostProcessor`  `postProcessBeanDefinitionRegistry` һ£ֶ߲ø÷;\n    2.  ִ spring ڲṩġʵ `PriorityOrdered` ӿڵ `BeanDefinitionRegistryPostProcessor`  `postProcessBeanDefinitionRegistry` \n    3.  ִʵ `Ordered` ӿδִй `BeanDefinitionRegistryPostProcessor`  `postProcessBeanDefinitionRegistry` \n    4.  ִδִй `BeanDefinitionRegistryPostProcessor`  `postProcessBeanDefinitionRegistry` \n2.  ִ `BeanFactoryPostProcessor#postProcessBeanFactory` \n    1.  ִпߵ `applicationContext.addBeanFactoryPostProcessor` ӵ `BeanDefinitionRegistryPostProcessor`  `postProcessBeanFactory` һ£ֶ߲ø÷;\n    2.  ִϴδִйġʵ `PriorityOrdered`  `BeanFactoryPostProcessor`  `postProcessBeanFactory` \n    3.  ִϴδִйġʵ `Ordered`  `BeanFactoryPostProcessor`  `postProcessBeanFactory` \n    4.  ִϴδִй `BeanFactoryPostProcessor`  `postProcessBeanFactory` \n\nԿʵʾΪִ`BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry`  `BeanFactoryPostProcessor#postProcessBeanFactory`Ҫעǣ`BeanDefinitionRegistryPostProcessor`  `BeanFactoryPostProcessor` ࣬ڵ `BeanFactoryPostProcessor#postProcessBeanFactory` ʱ ʵҲ `BeanDefinitionRegistryPostProcessor`  `postProcessBeanFactory` .\n\nϵ `BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry`  `BeanFactoryPostProcessor#postProcessBeanFactory` ִУִЩأͨ `demo01`ִеĴ£\n\n1.   `1.2` ʱִ `ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry`\n2.   `1.4` ʱִ `ConfigurationClassPostProcessor#postProcessBeanFactory`\n3.   `2.4` ʱִ `EventListenerMethodProcessor#postProcessBeanFactory`\n\nţǱչʲô\n\n#### 5.1 `ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry` ִ\n\nһȥԲҪķֻʾջ\n\n```\nAnnotationConfigApplicationContext#AnnotationConfigApplicationContext(java.lang.String...)\n |-AbstractApplicationContext#refresh\n  |-AbstractApplicationContext#invokeBeanFactoryPostProcessors\n   |-PostProcessorRegistrationDelegate\n      #invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory, List)\n    |-PostProcessorRegistrationDelegate#invokeBeanDefinitionRegistryPostProcessors\n     |-ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry\n      |-ConfigurationClassPostProcessor#processConfigBeanDefinitions\n\n```\n\nֱӽ `ConfigurationClassPostProcessor#processConfigBeanDefinitions`\n\n```\npublic void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {\n    List<BeanDefinitionHolder> configCandidates = new ArrayList<>();\n    String[] candidateNames = registry.getBeanDefinitionNames();\n\n    //ѭcandidateNames\n    for (String beanName : candidateNames) {\n    BeanDefinition beanDef = registry.getBeanDefinition(beanName);\n\n    // ڲλǷѾ\n    // һ֪ʶä\n    // עʱ򣬿ԲConfigurationע⣬\n    // ֱʹComponent ComponentScan Import ImportResourceע⣬֮ΪLite\n    // Configurationע⣬ͳ֮ΪFull\n    // עLite࣬getBean࣬ᷢԭǸ\n    // עFull࣬getBean࣬ᷢѾԭǸˣ\n    // Ѿcgilb\n    if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {\n            if (logger.isDebugEnabled()) {\n                logger.debug(...);\n            }\n        }\n        // жǷΪ࣬\n       // 1\\.  @Configuration ע  proxyBeanMethods != false ࣬spring Ϊ Full \n       // 2\\.  @Configuration ע  proxyBeanMethods == false,\n       //   @Component@ComponentScan@Import@ImportResource\n       // @Bean ֮һע࣬spring Ϊ Lite \n       // FullLitebeanDefбʶ\n       else if (ConfigurationClassUtils\n               .checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {\n           configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));\n       }\n    }\n\n    // ûֱ࣬ӷ\n    if (configCandidates.isEmpty()) {\n         return;\n    }\n\n    // \n    configCandidates.sort((bd1, bd2) -> {\n         int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());\n         int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());\n         return Integer.compare(i1, i2);\n    });\n\n    SingletonBeanRegistry sbr = null;\n    // DefaultListableBeanFactoryջʵSingletonBeanRegistryӿڣԿԽ뵽if\n    if (registry instanceof SingletonBeanRegistry) {\n        sbr = (SingletonBeanRegistry) registry;\n        if (!this.localBeanNameGeneratorSet) {\n            //springп޸ĬϵbeanʽǿûûԶbeanʽ\n            BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(\n                     AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);\n            if (generator != null) {\n                this.componentScanBeanNameGenerator = generator;\n                this.importBeanNameGenerator = generator;\n            }\n        }\n    }\n\n    if (this.environment == null) {\n         this.environment = new StandardEnvironment();\n    }\n\n    ConfigurationClassParser parser = new ConfigurationClassParser(\n            this.metadataReaderFactory, this.problemReporter, this.environment,\n            this.resourceLoader, this.componentScanBeanNameGenerator, registry);\n\n    Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);\n    Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());\n    do {\n        //࣬˺ܶ£\n        //磺@Component@PropertySources@ComponentScans@ImportResourceȵĴ\n        parser.parse(candidates);\n        parser.validate();\n\n        Set<ConfigurationClass> configClasses\n                = new LinkedHashSet<>(parser.getConfigurationClasses());\n        configClasses.removeAll(alreadyParsed);\n\n        if (this.reader == null) {\n            this.reader = new ConfigurationClassBeanDefinitionReader(\n                registry, this.sourceExtractor, this.resourceLoader, this.environment,\n                this.importBeanNameGenerator, parser.getImportRegistry());\n        }\n        // ֱһŰImport࣬@Bean @ImportRosource תBeanDefinition\n        this.reader.loadBeanDefinitions(configClasses);\n        // configClasses뵽alreadyParsed\n        alreadyParsed.addAll(configClasses);\n\n        candidates.clear();\n        // עBeanDefinition  candidateNamesбȽ\n        // ڵĻ˵µBeanDefinitionע\n        if (registry.getBeanDefinitionCount() > candidateNames.length) {\n            String[] newCandidateNames = registry.getBeanDefinitionNames();\n            Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));\n            Set<String> alreadyParsedClasses = new HashSet<>();\n            // ѭalreadyParsed뵽alreadyParsedClasses\n            for (ConfigurationClass configurationClass : alreadyParsed) {\n                alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());\n            }\n            for (String candidateName : newCandidateNames) {\n                if (!oldCandidateNames.contains(candidateName)) {\n                    BeanDefinition bd = registry.getBeanDefinition(candidateName);\n                    if (ConfigurationClassUtils\n                                .checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&\n                            !alreadyParsedClasses.contains(bd.getBeanClassName())) {\n                        candidates.add(new BeanDefinitionHolder(bd, candidateName));\n                    }\n                }\n            }\n            candidateNames = newCandidateNames;\n        }\n    }\n    while (!candidates.isEmpty());\n\n    if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {\n        sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());\n    }\n\n    if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {\n        ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();\n    }\n}\n\n```\n\nϷǶ `BeanDefinition` ϢĽһƣ `@Configuration``@PropertySources``@ComponentScans``@ImportResource` ȵĴ demo01 ûЩע⣬ǾͲչˣٷ\n\n#### 5.2 ִ `ConfigurationClassPostProcessor#postProcessBeanFactory` \n\n`ConfigurationClassPostProcessor`  `postProcessBeanFactory` Ƚϼ򵥣»Ƕ `@Configuration` ǿ\n\n```\n@Override\npublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {\n    // ʡ\n\n    //  ConfigurationClasses ǿ\n    enhanceConfigurationClasses(beanFactory);\n    beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));\n}\n\npublic void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {\n    // ʡ\n\n    // ȫࣺ\n    ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();\n    for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {\n        AbstractBeanDefinition beanDef = entry.getValue();\n        // If a @Configuration class gets proxied, always proxy the target class\n        beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);\n        // Set enhanced subclass of the user-specified bean class\n        // ǿ\n        Class<?> configClass = beanDef.getBeanClass();\n        Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);\n        if (configClass != enhancedClass) {\n            beanDef.setBeanClass(enhancedClass);\n        }\n    }\n}\n\n```\n\n demo01 û `@Configuration`Ͳչˣٷ\n\n#### 5.3 ִ `EventListenerMethodProcessor#postProcessBeanFactory` \n\n¼ģֱϴ룺\n\n```\n@Override\npublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {\n    this.beanFactory = beanFactory;\n    Map<String, EventListenerFactory> beans \n          = beanFactory.getBeansOfType(EventListenerFactory.class, false, false);\n\n    List<EventListenerFactory> factories = new ArrayList<>(beans.values());\n    AnnotationAwareOrderComparator.sort(factories);\n    this.eventListenerFactories = factories;\n}\n\n```\n\nԿ spring Уóе `EventListenerFactory`Ȼֵ `this.eventListenerFactories`Ͳչˡ\n\n#### 5.4 ܽ\n\nĽ `invokeBeanFactoryPostProcessors` ִ̣Ϊִ`BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry`  `BeanFactoryPostProcessor#postProcessBeanFactory`ִй£\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-b6188601112b2b2c031b0a70f64f2cc885f.png)\n\nͨԷ֣`invokeBeanFactoryPostProcessors` һִ `BeanFactoryPostProcessor`\n\n1.  `ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry`\n2.  `ConfigurationClassPostProcessor#postProcessBeanFactory`\n3.  `EventListenerMethodProcessor#postProcessBeanFactory`\n\nУ`ConfigurationClassPostProcessor` һǳǳҪ `BeanFactoryPostProcessor`ĽһԲο£\n\n*   [spring ֮̽ ConfigurationClassPostProcessor ֮ @ComponentScan ע](https://my.oschina.net/funcy/blog/4836178)\n*   [spring ֮̽ ConfigurationClassPostProcessor ֮ @Bean ע](https://my.oschina.net/funcy/blog/4492878)\n*   [spring ֮̽ ConfigurationClassPostProcessor ֮ @Import ע](https://my.oschina.net/funcy/blog/4678152)\n*   [spring ֮̽ ConfigurationClassPostProcessor ֮ @Conditional ע](https://my.oschina.net/funcy/blog/4873444)\n\n*   * *\n\n_ԭӣ[https://my.oschina.net/funcy/blog/4641114](https://my.oschina.net/funcy/blog/4641114) ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע_"
  },
  {
    "path": "docs/Spring全家桶/Spring源码分析/Spring启动流程/Spring启动流程（八）：完成BeanFactory的初始化.md",
    "content": "![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-1f1ac8f3d8241fad9693d9684048ab7f3ae.png)\n\nģļ spring  ̡\n\n### 11\\. ʼ bean: `finishBeanFactoryInitialization(beanFactory)`\n\nĽһ**ǳҪ**ķ `AbstractApplicationContext#finishBeanFactoryInitialization` ˡ\n\nĵ£\n\n```\n|-AnnotationConfigApplicationContext#AnnotationConfigApplicationContext(String...)\n |-AbstractApplicationContext#refresh\n  |-AbstractApplicationContext#finishBeanFactoryInitialization\n   |-DefaultListableBeanFactory#preInstantiateSingletons\n\n```\n\nֱӽ `DefaultListableBeanFactory#preInstantiateSingletons`:\n\n```\npublic void preInstantiateSingletons() throws BeansException {\n    // this.beanDefinitionNames е beanNames\n    List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);\n\n    for (String beanName : beanNames) {\n        // ϲ Bean еãע<bean id=\"\"  parent=\"\" /> е parent\n        RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);\n        // ǳࡢǵҲص\n        if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {\n            //  FactoryBean\n            if (isFactoryBean(beanName)) {\n                // beanName ǰϡ& \n                Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);\n                if (bean instanceof FactoryBean) {\n                     final FactoryBean<?> factory = (FactoryBean<?>) bean;\n                     // жϵǰ FactoryBean Ƿ SmartFactoryBean ʵ\n                     boolean isEagerInit;\n                     if (System.getSecurityManager() != null \n                            && factory instanceof SmartFactoryBean) {\n                        isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)\n                                ((SmartFactoryBean<?>) factory)::isEagerInit,\n                                getAccessControlContext());\n                     }\n                     else {\n                          isEagerInit = (factory instanceof SmartFactoryBean &&\n                             ((SmartFactoryBean<?>) factory).isEagerInit());\n                     }\n                     if (isEagerInit) {\n                           // FactoryBeanֱʹô˷гʼ\n                           getBean(beanName);\n                     }\n                }\n            }\n            else {\n                getBean(beanName);\n            }\n        }\n    }\n\n    // Trigger post-initialization callback for all applicable beans...\n    // beanʵ SmartInitializingSingleton ӿڵģôõص\n    for (String beanName : beanNames) {\n        Object singletonInstance = getSingleton(beanName);\n        if (singletonInstance instanceof SmartInitializingSingleton) {\n            final SmartInitializingSingleton smartSingleton = \n                    (SmartInitializingSingleton) singletonInstance;\n            if (System.getSecurityManager() != null) {\n                AccessController.doPrivileged((PrivilegedAction<Object>) () -> {\n                    smartSingleton.afterSingletonsInstantiated();\n                    return null;\n                }, getAccessControlContext());\n            }\n            else {\n                smartSingleton.afterSingletonsInstantiated();\n            }\n        }\n    }\n}\n\n```\n\nϴ룬ƺܶ࣬ؼɼ£\n\n```\nList<String> beanNames = new ArrayList<>(this.beanDefinitionNames);\nfor (String beanName : beanNames) {\n    RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);\n    if(... && bd.isSingleton() && ...) {\n        getBean(beanName);\n    }\n}\n\n```\n\n> ϴļУʡϸڣжǷʵʱҪжǷΪ࣬ǷΪǷΪصȣͬʱ bean ҲжǷΪͨ bean  `FactoryBean`ʵ `SmartInitializingSingleton` ӿڵ beanҪ⴦ȡǳĶ˵ѾۼҪ̾ˣһЩϸڣȲᣬ˽ϸڣڰҪ̵£ٿھϸڣԼץסص㣬ʧԴС\n\nһ򻯣Ϳ÷Ĺܣ\n\n1.  ȡ `beanFactory` е `beanNames` \n2.  ͨ `beanName` ȡ `BeanDefinition`жϣǷΪ\n3.   `getBean(beanName)`  bean ӵ spring С\n\nӼ򻯺ĴԿƽƽ `getBean(beanName)` spring ʵ bean ĹؼǻǺ룬ֻעҪ̣ȥ\n\n```\n|-AnnotationConfigApplicationContext#AnnotationConfigApplicationContext(String...)\n |-AbstractApplicationContext#refresh\n  |-AbstractApplicationContext#finishBeanFactoryInitialization\n   |-DefaultListableBeanFactory#preInstantiateSingletons\n    |-AbstractBeanFactory#getBean(java.lang.String)\n     |-AbstractBeanFactory#doGetBean\n\n```\n\n> AbstractBeanFactory#doGetBean\n\n```\nprotected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,\n         @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {\n\n    // Ҫ߼FactoryBeanͰ&ȥ,ǱͰѸݱȡʵ\n    final String beanName = transformedBeanName(name);\n    //ķֵ\n    Object bean;\n\n    // Ƿѳʼ\n    Object sharedInstance = getSingleton(beanName);\n    // Ѿʼˣûдargsʹgetֱȡ\n    if (sharedInstance != null && args == null) {\n        // ͨBean Ļֱӷأ FactoryBean ĻǸʵ\n        bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);\n    }\n    else {\n        // prototype͵bean\n        if (isPrototypeCurrentlyInCreation(beanName)) {\n            throw new BeanCurrentlyInCreationException(beanName);\n        }\n\n        // ǰBeanDefinitionbeanҾиBeanFactory\n        BeanFactory parentBeanFactory = getParentBeanFactory();\n        if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {\n            String nameToLookup = originalBeanName(name);\n            if (parentBeanFactory instanceof AbstractBeanFactory) {\n                return ((AbstractBeanFactory) parentBeanFactory).doGetBean(\n                   nameToLookup, requiredType, args, typeCheckOnly);\n            }\n            else if (args != null) {.\n                 // ظĲѯ\n                 return (T) parentBeanFactory.getBean(nameToLookup, args);\n            }\n            else if (requiredType != null) {\n                return parentBeanFactory.getBean(nameToLookup, requiredType);\n            }\n            else {\n                return (T) parentBeanFactory.getBean(nameToLookup);\n            }\n        }\n\n        if (!typeCheckOnly) {\n            // typeCheckOnly Ϊ falseǰ beanName һ alreadyCreated  Set С\n            markBeanAsCreated(beanName);\n        }\n\n        // Ҫbean\n        try {\n            final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);\n            checkMergedBeanDefinition(mbd, beanName, args);    \n            // ȳʼ Bean depends-on ж\n            String[] dependsOn = mbd.getDependsOn();\n            if (dependsOn != null) {\n                for (String dep : dependsOn) {\n                    // ǲѭ\n                    if (isDependent(beanName, dep)) {\n                        throw new BeanCreationException(...);\n                    }\n                    // עһϵ\n                    registerDependentBean(dep, beanName);\n                    try {\n                        // ȳʼ\n                        getBean(dep);\n                    }\n                    catch (NoSuchBeanDefinitionException ex) {\n                        throw new BeanCreationException(...);\n                    }\n                }\n            }\n\n            // ǵ\n            if (mbd.isSingleton()) {\n                sharedInstance = getSingleton(beanName, () -> {\n                    try {\n                        // ִд Bean\n                        return createBean(beanName, mbd, args);\n                    }\n                    catch (BeansException ex) {\n                        destroySingleton(beanName);\n                        throw ex;\n                    }\n                });\n                bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);\n            }\n            // prototype\n            else if (mbd.isPrototype()) {\n                Object prototypeInstance = null;\n                try {\n                    beforePrototypeCreation(beanName);\n                    // ִд Bean\n                    prototypeInstance = createBean(beanName, mbd, args);\n                }\n                finally {\n                    afterPrototypeCreation(beanName);\n                }\n                bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);\n            }\n            //  singleton  prototype, ôԶscope(WebĿеsession)\n            // ͽԶscopeӦ÷ȥʵ\n            else {\n                String scopeName = mbd.getScope();\n                final Scope scope = this.scopes.get(scopeName);\n                if (scope == null) {\n                  throw new IllegalStateException(...);\n                }\n                try {\n                    Object scopedInstance = scope.get(beanName, () -> {\n                        beforePrototypeCreation(beanName);\n                        try {\n                            // ִд Bean\n                            return createBean(beanName, mbd, args);\n                        }\n                        finally {\n                            afterPrototypeCreation(beanName);\n                        }\n                    });\n                    bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);\n                }\n                catch (IllegalStateException ex) {\n                  throw new BeanCreationException(...);\n                }\n            }\n        }\n        catch (BeansException ex) {\n            cleanupAfterBeanCreationFailure(beanName);\n            throw ex;\n        }\n    }\n\n    //bean\n    if (requiredType != null && !requiredType.isInstance(bean)) {\n        try {\n            T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType);\n            if (convertedBean == null) {\n                throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());\n            }\n            return convertedBean;\n        }\n        catch (TypeMismatchException ex) {\n            throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());\n        }\n    }\n    return (T) bean;\n}\n\n```\n\nĴϸעͣ Ͳ ˡspring ܱȽϸӣǵĶҲȽ϶࣬жϣӦԶǽ demo01  (`singleton` )ϴؼ£\n\n```\n//ķֵ\nObject bean;\n\n// \n// 1\\. ȡͬʱҲṩһlambdaʽbeanĴ\nsharedInstance = getSingleton(beanName, () -> {\n    try {\n        // ִд Bean\n        return createBean(beanName, mbd, args);\n    }\n    catch (BeansException ex) {\n        destroySingleton(beanName);\n        throw ex;\n    }\n});\n\n// 2\\. һsharedInstanceȻ󷵻beanʵϣҪǣ\n// FactoryBeanͷطǸʵ󣬷ֱӷ\nbean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);\n\nreturn (T) bean;\n\n```\n\nĴ룬 spring  bean ̡Ǿͷֱ𿴿ݣɾһЩҪĴ룺\n\n> `DefaultSingletonBeanRegistry#getSingleton(String, ObjectFactory<?>)`\n\n```\npublic Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {\n    synchronized (this.singletonObjects) {\n        boolean newSingleton = false;\n        try {\n            // д\n            singletonObject = singletonFactory.getObject();\n            newSingleton = true;\n        }\n        catch (... ex) {\n            ...\n        }\n        finally {\n            // ɺһЩжϲ봴̹ϵ\n            afterSingletonCreation(beanName);\n        }\n        if (newSingleton) {\n            // ӵ  beanFactory \n            addSingleton(beanName, singletonObject);\n        }\n        return singletonObject;\n    }\n}\n\n```\n\nϴԿbean Ĵ `singletonFactory.getObject()`ִʲôǻӦý `AbstractBeanFactory#doGetBean`.\n\nȣǽ `ObjectFactory#getObject`ִ£\n\n```\n@FunctionalInterface\npublic interface ObjectFactory<T> {\n    T getObject() throws BeansException;\n}\n\n```\n\nһʽ̽ӿڣjdk8 ṩ﷨ٿ `AbstractBeanFactory#doGetBean` Ķ\n\n```\nsharedInstance = getSingleton(beanName, () -> {\n    try {\n        // ִд Bean\n        return createBean(beanName, mbd, args);\n    }\n    catch (BeansException ex) {\n        destroySingleton(beanName);\n        throw ex;\n    }\n});\n\n```\n\nﴫһ lambda ʽִ `singletonFactory.getObject()` ʱʵִе\n\n```\ntry {\n    // ִд Bean\n    return createBean(beanName, mbd, args);\n}\ncatch (BeansException ex) {\n    destroySingleton(beanName);\n    throw ex;\n}\n\n```\n\n `AbstractAutowireCapableBeanFactory#createBean(String, RootBeanDefinition, Object[])`ǰô࣬spring Ҫ bean Ĵˣ\n\n spring Ĵǻں¼ֻע `AbstractBeanFactory#getBean`  `DefaultSingletonBeanRegistry#getSingleton`ϷǶһܽ᣺\n\n*   `AbstractBeanFactory#getBean` scope Ϊ `PropertyType`  bean ˵÷ֱӴ bean scope Ϊ `singleton`  bean ˵÷ж `beanFactory` Ƿڸ beanֱӷأȴٷء\n\n*   `DefaultSingletonBeanRegistry#getSingleton`Ǵ `beanFactory` ȡ singleton bean ķֱӷأȴٷء\n\nľȷˣƪٷ spring bean ̡\n\n* * *\n\n_ԭӣ[https://my.oschina.net/funcy/blog/4658230](https://my.oschina.net/funcy/blog/4658230) ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע_"
  },
  {
    "path": "docs/Spring全家桶/Spring源码分析/Spring启动流程/Spring启动流程（六）：注册BeanPostProcessor.md",
    "content": "![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-be7f7797a27a2dc5ab1ad8d11327b140c90.png)\n\nģǼ\n\n### 6\\. ע `BeanPostProcessor`: `registerBeanPostProcessors(beanFactory)`\n\nʽǰҪȷ\n\n*   `BeanFactoryPostProcessor`Ϊ `BeanFactory` ĺôԶ `BeanFactory` һЩ\n*   `BeanPostProcessor`Ϊ `Bean` ĺôԶ `Bean` һЩ\n\nҪĵ `BeanPostProcessor` ǰ `BeanFactoryPostProcessor` Ūˡ\n\nҪǶ `BeanPostProcessor`  `register`  (`registerBeanPostProcessors(beanFactory)`) `BeanPostProcessor` עᵽ `BeanFactory` УôʲôʱأȻǶ `Bean` ĲȻ bean ֮ˡ\n\n> `BeanPostProcessor` Ҳ spring һҪڸϸԲο [spring ֮ BeanPostProcessors](https://my.oschina.net/funcy/blog/4597551)\n\nϻ˵ֱϴ룬ͬأԲҪķֻ\n\n```\n|-AbstractApplicationContext#refresh\n |-AbstractApplicationContext#registerBeanPostProcessors\n  |-PostProcessorRegistrationDelegate\n    #registerBeanPostProcessors(ConfigurableListableBeanFactory, AbstractApplicationContext)\n\n```\n\nյõ `PostProcessorRegistrationDelegate#registerBeanPostProcessors`£\n\n```\npublic static void registerBeanPostProcessors(\n        ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) {\n\n    // ȡspringе BeanPostProcessorһbean: \n    // org.springframework.context.annotation.internalAutowiredAnnotationProcessor\n    //  AutowiredAnnotationBeanPostProcessor\n    String[] postProcessorNames = beanFactory\n            .getBeanNamesForType(BeanPostProcessor.class, true, false);\n\n    int beanProcessorTargetCount = beanFactory.getBeanPostProcessorCount() \n            + 1 + postProcessorNames.length;\n    beanFactory.addBeanPostProcessor(\n            new BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount));\n\n    List<BeanPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();\n    List<BeanPostProcessor> internalPostProcessors = new ArrayList<>();\n    List<String> orderedPostProcessorNames = new ArrayList<>();\n    List<String> nonOrderedPostProcessorNames = new ArrayList<>();\n\n    // ȻȡʵPriorityOrderedBeanPostProcessor\n    // ٻȡʵOrderedBeanPostProcessor\n    // ٻȡBeanPostProcessor\n    for (String ppName : postProcessorNames) {\n        if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {\n            BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);\n            priorityOrderedPostProcessors.add(pp);\n            if (pp instanceof MergedBeanDefinitionPostProcessor) {\n                internalPostProcessors.add(pp);\n            }\n        }\n        else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {\n            orderedPostProcessorNames.add(ppName);\n        }\n        else {\n            nonOrderedPostProcessorNames.add(ppName);\n        }\n    }\n\n    // priorityOrderedPostProcessorȻӵbeanFactory\n    sortPostProcessors(priorityOrderedPostProcessors, beanFactory);\n    registerBeanPostProcessors(beanFactory, priorityOrderedPostProcessors);\n\n    List<BeanPostProcessor> orderedPostProcessors \n            = new ArrayList<>(orderedPostProcessorNames.size());\n    for (String ppName : orderedPostProcessorNames) {\n        BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);\n        orderedPostProcessors.add(pp);\n        if (pp instanceof MergedBeanDefinitionPostProcessor) {\n            internalPostProcessors.add(pp);\n        }\n    }\n    // orderedPostProcessorȻӵbeanFactory\n    sortPostProcessors(orderedPostProcessors, beanFactory);\n    registerBeanPostProcessors(beanFactory, orderedPostProcessors);\n\n    List<BeanPostProcessor> nonOrderedPostProcessors \n            = new ArrayList<>(nonOrderedPostProcessorNames.size());\n    for (String ppName : nonOrderedPostProcessorNames) {\n        BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);\n        nonOrderedPostProcessors.add(pp);\n        if (pp instanceof MergedBeanDefinitionPostProcessor) {\n            internalPostProcessors.add(pp);\n        }\n    }\n    // µBeanPostProcessorȻӵbeanFactory\n    registerBeanPostProcessors(beanFactory, nonOrderedPostProcessors);    \n    // internalPostProcessorȻӵbeanFactory\n    // AutowiredAnnotationBeanPostProcessorʵMergedBeanDefinitionPostProcessor\n    // ٴע\n    sortPostProcessors(internalPostProcessors, beanFactory);\n    registerBeanPostProcessors(beanFactory, internalPostProcessors);\n\n    beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext));\n}\n\n```\n\nҪǶ `BeanFactoryPostProcessor` ע£\n\n*   ʵ `PriorityOrdered`  `BeanPostProcessor` עᵽ `beanFactory` У\n*   ʵ `Ordered`  `BeanPostProcessor` עᵽ `beanFactory` У\n*    `BeanPostProcessor` עᵽ `beanFactory` У\n*   ʵ `MergedBeanDefinitionPostProcessor`  `BeanPostProcessor` ٴעᵽ `beanFactory` С\n\nʵϣ demo01 ԣע bean ֻһ`AutowiredAnnotationBeanPostProcessor`ͬʱʵ `MergedBeanDefinitionPostProcessor`  `PriorityOrdered`˻עΡ\n\nע˶ `AutowiredAnnotationBeanPostProcessor`ֻһǽ `registerBeanPostProcessors`  spring עģһ·ȥ뵽 `AbstractBeanFactory#addBeanPostProcessor`:\n\n> AbstractBeanFactory#addBeanPostProcessor\n\n```\nprivate final List<BeanPostProcessor> beanPostProcessors = new CopyOnWriteArrayList<>();\n\n@Override\npublic void addBeanPostProcessor(BeanPostProcessor beanPostProcessor) {\n    Assert.notNull(beanPostProcessor, \"BeanPostProcessor must not be null\");\n    // ȽƳ˶עbeanPostProcessorsҲֻһ\n    this.beanPostProcessors.remove(beanPostProcessor);\n    if (beanPostProcessor instanceof InstantiationAwareBeanPostProcessor) {\n        this.hasInstantiationAwareBeanPostProcessors = true;\n    }\n    if (beanPostProcessor instanceof DestructionAwareBeanPostProcessor) {\n        this.hasDestructionAwareBeanPostProcessors = true;\n    }\n    this.beanPostProcessors.add(beanPostProcessor);\n}\n\n```\n\nԿνעᵽ `BeanFactory`ʵǰ `beanPostProcessor` 뵽 `BeanFactory`  `beanPostProcessors` С\n\nĽ `beanPostProcessor` עᣬ `beanPostProcessor` ĵãķᵽĵķȵɣ\n\n* * *\n\n_ԭӣ[https://my.oschina.net/funcy/blog/4657181](https://my.oschina.net/funcy/blog/4657181) ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע_"
  },
  {
    "path": "docs/Spring全家桶/Spring源码分析/Spring启动流程/Spring启动流程（十一）：启动流程总结.md",
    "content": "spring Ƿˣʹһͼ̣ܽ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-72a4008f2ad3401de6b4f2d5c7f697923a3.png)\n\nϵд `AnnotationConfigApplicationContext#AnnotationConfigApplicationContext(String...)`  `AnnotationConfigApplicationContext` ޲ι췽ɨ̵̡ȣⲿݱȽ϶࣬ص£\n\n1.  ɨ\n2.   `beanFactoryProcessor`\n3.  ʼ bean\n\nڷУǺϸڣעҪ̣ϸڸȤСԸṩӽӦĶ\n\n* * *\n\n_ԭӣ[https://my.oschina.net/funcy/blog/4659519](https://my.oschina.net/funcy/blog/4659519) ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע_"
  },
  {
    "path": "docs/Spring全家桶/Spring源码分析/Spring启动流程/Spring启动流程（十）：启动完成的处理.md",
    "content": "\n\nģ spring ̡\n\n12. : finishRefresh()\n\nAbstractApplicationContext#finishRefresh £\n\n    protected void finishRefresh() {\n        // ־֪ˣʼһϵвʹõԴ\n        clearResourceCaches();\n        // ʼLifecycleProcessor\n        initLifecycleProcessor();\n        // ڲʵʵLifecycleӿڵbean\n        getLifecycleProcessor().onRefresh();\n        // ContextRefreshedEvent¼\n        publishEvent(new ContextRefreshedEvent(this));\n        // spring.liveBeansView.mbeanDomainǷڣоͻᴴһMBeanServer\n        LiveBeansView.registerApplicationContext(this);\n    }\n\n\n벻࣬ͼǷֱ\n\n1. Դ棺clearResourceCaches()\n\nclearResourceCaches() £\n\n    public class DefaultResourceLoader implements ResourceLoader {\n    \n        private final Map<Class<?>, Map<Resource, ?>> resourceCaches\n            = new ConcurrentHashMap<>(4);\n    \n        public void clearResourceCaches() {\n            this.resourceCaches.clear();\n        }\n    \n        // ʡĺö\n        ...\n    \n    }\n\n\n resourceCaches ģǸ Map Resource\n\nʲô Resource أǰɨĹУǻȰ class ļȡת Resource ٽһ Resource  FileSystemResourceUrlResource ȣresourceCaches ǴЩ Resource ġ\n\n2.  LifecycleProcessor\n\nʲô LifecycleProcessor\n\n    /**\n     * رղ\n     */\n    public interface LifecycleProcessor extends Lifecycle {\n    \n        /**\n         * ʱ\n         */\n        void onRefresh();\n    \n        /**\n         * رʱ\n         */\n        void onClose();\n    \n    }\n\n\nӿرղԼʵָýӿڣȻд onRefresh()  onClose()ԱرʱһЩ\n\n    @Component\n    public class MyLifecycleProcessor implements LifecycleProcessor {\n    \n        @Override\n        public void onRefresh() {\n            System.out.println(\"\");\n        }\n    \n        @Override\n        public void onClose() {\n            System.out.println(\"ر\");\n        }\n    }\n\n\n LifecycleProcessor صķinitLifecycleProcessor()getLifecycleProcessor()һһΪ\n\nAbstractApplicationContext\n\n    private LifecycleProcessor lifecycleProcessor;\n    \n    /**\n     * ʼ LifecycleProcessor\n     */\n    protected void initLifecycleProcessor() {\n        ConfigurableListableBeanFactory beanFactory = getBeanFactory();\n        // ڣֱʹ\n        if (beanFactory.containsLocalBean(LIFECYCLE_PROCESSOR_BEAN_NAME)) {\n            this.lifecycleProcessor =\n                    beanFactory.getBean(LIFECYCLE_PROCESSOR_BEAN_NAME, LifecycleProcessor.class);\n        }\n        // 򴴽ĬʹDefaultLifecycleProcessor\n        else {\n            DefaultLifecycleProcessor defaultProcessor = new DefaultLifecycleProcessor();\n            defaultProcessor.setBeanFactory(beanFactory);\n            this.lifecycleProcessor = defaultProcessor;\n            beanFactory.registerSingleton(LIFECYCLE_PROCESSOR_BEAN_NAME, this.lifecycleProcessor);\n        }\n    }\n    \n    /**\n     *  lifecycleProcessor\n     */\n    LifecycleProcessor getLifecycleProcessor() throws IllegalStateException {\n        if (this.lifecycleProcessor == null) {\n            throw new IllegalStateException(...);\n        }\n        return this.lifecycleProcessor;\n    }\n\n\ninitLifecycleProcessor ľ AbstractApplicationContext#lifecycleProcessor ԣ beanFactory д initLifecycleProcessor ֱʹãʹһ\n\ngetLifecycleProcessor() ֻǷ AbstractApplicationContext#lifecycleProcessor ԡ\n\n getLifecycleProcessor().onRefresh() У onRefresh() һ DefaultLifecycleProcessor#onRefresh ʲô\n\n    @Override\n    public void onRefresh() {\n        startBeans(true);\n        this.running = true;\n    }\n\n\nӱֻǸһ״̬\n\n3.  ContextRefreshedEvent ¼\n\n publishEvent(new ContextRefreshedEvent(this))  ContextRefreshedEventԼҲ¼¼Ĳ룬 spring ¼ϸԲο spring ֮̽ spring ¼ơ\n\n13. : resetCommonCaches()\n\n÷£\n\n    protected void resetCommonCaches() {\n        ReflectionUtils.clearCache();\n        AnnotationUtils.clearCache();\n        ResolvableType.clearCache();\n        CachedIntrospectionResults.clearClassLoader(getClassLoader());\n    }\n\n\nӷִиֻ棬ִбȽϼ򵥣Ͳˡ\n\n---\n\nԭӣhttps://my.oschina.net/funcy/blog/4892555 ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע\n"
  },
  {
    "path": "docs/Spring全家桶/Spring源码分析/Spring启动流程/Spring启动流程（四）：启动前的准备工作.md",
    "content": "ɰɨ󣬽žͿʼ spring ˣ `AbstractApplicationContext#refresh` ÷һ 13 Ҳ spring ̣\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-88c3a2486c24ccd0ad390ba9b62b986a6b2.png)\n\nϵдӱĿʼ𲽷 13 ̽ spring ̡\n\n### 1\\. ǰ׼`prepareRefresh()`\n\n `prepareRefresh()` £\n\n```\n|-AnnotationConfigApplicationContext#AnnotationConfigApplicationContext(String...)\n |-AbstractApplicationContext#refresh\n  |-AbstractApplicationContext#prepareRefresh\n\n```\n\n£\n\n```\nprotected void prepareRefresh() {\n    // Switch to active.\n    this.startupDate = System.currentTimeMillis();\n    this.closed.set(false);\n    this.active.set(true);\n\n    // ʼļûоʵ֣һûչ\n    initPropertySources();\n\n    // 黷\n    getEnvironment().validateRequiredProperties();\n\n    if (this.earlyApplicationListeners == null) {\n        this.earlyApplicationListeners = new LinkedHashSet<>(this.applicationListeners);\n    } else {\n        this.applicationListeners.clear();\n        this.applicationListeners.addAll(this.earlyApplicationListeners);\n    }\n\n    this.earlyApplicationEvents = new LinkedHashSet<>();\n}\n\n```\n\nδȽϼ򵥣ʱ䡢״̬ļ顢Եĳʼȡ\n\n### 2\\. ȡ `beanFactory: obtainFreshBeanFactory()`\n\nٸ `obtainFreshBeanFactory()` £\n\n> AbstractApplicationContext#obtainFreshBeanFactory\n\n```\nprotected ConfigurableListableBeanFactory obtainFreshBeanFactory() {\n    refreshBeanFactory();\n    // ظոմ BeanFactory\n    return getBeanFactory();\n}\n\n```\n\n `refresh`  `BeanFactory`Ȼٷ `BeanFactory`Ǽ `refreshBeanFactory()`\n\n> GenericApplicationContext#refreshBeanFactory\n\n```\n@Override\nprotected final void refreshBeanFactory() throws IllegalStateException {\n    // ʡһЩжϴ\n    this.beanFactory.setSerializationId(getId());\n}\n\n```\n\nؼֻһУ beanFactory  `SerializationId`.\n\nٻعͷ `getBeanFactory()` \n\n> GenericApplicationContext#getBeanFactory\n\n```\npublic final ConfigurableListableBeanFactory getBeanFactory() {\n    return this.beanFactory;\n}\n\n```\n\n͸ˣ˵ǰ `beanFactory` `beanFactory` ڷ `AnnotationConfigApplicationContext` 췽ʱģΪ `DefaultListableBeanFactory`.\n\n### 3\\. ׼ `beanFactory: prepareBeanFactory(beanFactory)`\n\nǼ `prepareBeanFactory(beanFactory)` \n\n> AbstractApplicationContext#prepareBeanFactory\n\n```\nprotected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {\n    // ΪصǰApplicationContext\n    beanFactory.setBeanClassLoader(getClassLoader());\n    //  BeanExpressionResolverbeanʽ\n    beanFactory.setBeanExpressionResolver(\n            new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));\n    // Ա༭֧\n    beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));\n\n    // Springһչ\n    // ʵAwareӿڵbeanڳʼʱ processorص\n    // ǺܳãǻΪ˻ȡ ApplicationContext  implement ApplicationContextAware\n    // ע⣺ص ApplicationContextAwareḺص EnvironmentAwareResourceLoaderAware \n    beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));\n\n    // 漸е˼ǣĳ bean ¼ӿڵʵ࣬\n    // ԶװʱǣSpring ͨʽЩ\n    beanFactory.ignoreDependencyInterface(EnvironmentAware.class);\n    beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class);\n    beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);\n    beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);\n    beanFactory.ignoreDependencyInterface(MessageSourceAware.class);\n    beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);\n\n    // 漸оΪļ bean ֵ bean ¼עӦֵ\n    beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);\n    beanFactory.registerResolvableDependency(ResourceLoader.class, this);\n    beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);\n    beanFactory.registerResolvableDependency(ApplicationContext.class, this);\n\n     // һôApplicationListenerDetector˺ôʵBeanPostProcessorӿ\n     beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));\n\n    // beanΪloadTimeWeaverbeanעһBeanPostProcessor\n    if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {\n        beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));\n        // Set a temporary ClassLoader for type matching.\n        beanFactory.setTempClassLoader(\n                new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));\n    }\n\n    // ûж \"environment\"  beanô Spring  \"ֶ\" עһ\n    if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) {\n        beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());\n    }\n    // ûж \"systemProperties\"  beanô Spring  \"ֶ\" עһ\n    if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) {\n        beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, \n                getEnvironment().getSystemProperties());\n    }\n    // ûж \"systemEnvironment\"  beanô Spring  \"ֶ\" עһ\n    if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) {\n        beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, \n                getEnvironment().getSystemEnvironment());\n    }\n}\n\n```\n\nǶ beanFactory һЩ׼һЩԣһЩ bean ȣ붼ע⣬Ͳظ˵ˡ\n\n beanFactory  `ApplicationListenerDetector` Ҫ£شΪ `beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));`ǿ `ApplicationListenerDetector` ࣺ\n\n> org.springframework.context.support.ApplicationContextAwareProcessor\n\n```\nclass ApplicationContextAwareProcessor implements BeanPostProcessor {\n    @Override\n    @Nullable\n    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {\n    // ʡһЩ\n    AccessControlContext acc = null;\n    if (System.getSecurityManager() != null) {\n        acc = this.applicationContext.getBeanFactory().getAccessControlContext();\n    }\n    if (acc != null) {\n        AccessController.doPrivileged((PrivilegedAction<Object>) () -> {\n            invokeAwareInterfaces(bean);\n            return null;\n        }, acc);\n    } else {\n        invokeAwareInterfaces(bean);\n    }\n        return bean;\n    }\n\n    // ص Awareӿ\n    private void invokeAwareInterfaces(Object bean) {\n        //  EnvironmentAware#setEnvironment \n        if (bean instanceof EnvironmentAware) {\n            ((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());\n        }\n        //  EmbeddedValueResolverAware#setEmbeddedValueResolver \n        if (bean instanceof EmbeddedValueResolverAware) {\n            ((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);\n        }\n        //  ResourceLoaderAware#setResourceLoader \n        if (bean instanceof ResourceLoaderAware) {\n            ((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);\n        }\n        //  ApplicationEventPublisherAware#setApplicationEventPublisher \n        if (bean instanceof ApplicationEventPublisherAware) {\n            ((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);\n        }\n        //  MessageSourceAware#setMessageSource \n        if (bean instanceof MessageSourceAware) {\n            ((MessageSourceAware) bean).setMessageSource(this.applicationContext);\n        }\n        //  ApplicationContextAware#setApplicationContext \n        if (bean instanceof ApplicationContextAware) {\n            ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);\n        }\n    }\n\n    // ʡ\n}\n\n```\n\nԿ\n\n1.  ʵ `BeanPostProcessor` ӿڣ\n2.   `postProcessBeforeInitialization`  `BeanPostProcessor` ṩΪؼĴ` invokeAwareInterfaces(bean);`\n3.  `invokeAwareInterfaces` ֻһϵеķ\n\n `BeanPostProcessor` ĵķԲο [spring ֮ BeanPostProcessors ](https://my.oschina.net/funcy/blog/4597551)ڸã\n\nˣĵķ͵ˣĽ spring ʱ beanFactory ׼ݽϼ򵥣һͼܽ±ݣ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-1e10d7aff080b2e0bbfbef5d79c56cc54c9.png)\n\n* * *\n\n_ԭӣ[https://my.oschina.net/funcy/blog/4633169](https://my.oschina.net/funcy/blog/4633169) ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע_"
  },
  {
    "path": "docs/Spring全家桶/Spring源码分析/Spring源码剖析：AOP实现原理详解.md",
    "content": "# 目录\n  * [AOP实现原理——找到Spring处理AOP的源头](#aop实现原理找到spring处理aop的源头)\n  * [解析增强器advisor](#解析增强器advisor)\n  * [解析切面的过程](#解析切面的过程)\n  * [AOP为Bean生成代理的时机分析](#aop为bean生成代理的时机分析)\n  * [代理对象实例化过程](#代理对象实例化过程)\n  * [平时我们说AOP原理三句话就能概括：](#平时我们说aop原理三句话就能概括：)\n  * [代理方法调用原理](#代理方法调用原理)\n  * [CGLIB代理实现](#cglib代理实现)\n\n\n本文转自五月的仓颉 https://www.cnblogs.com/xrq730\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章将同步到我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《Spring和SpringMVC源码分析》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从spring基础入手，一步步地学习spring基础和springmvc的框架知识，并上手进行项目实战，spring框架是每一个Java工程师必须要学习和理解的知识点，进一步来说，你还需要掌握spring甚至是springmvc的源码以及实现原理，才能更完整地了解整个spring技术体系，形成自己的知识框架。\n\n后续还会有springboot和springcloud的技术专题，陆续为大家带来，敬请期待。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供部分知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n前言\n\n前面写了六篇文章详细地分析了Spring Bean加载流程，这部分完了之后就要进入一个比较困难的部分了，就是AOP的实现原理分析。为了探究AOP实现原理，首先定义几个类，一个Dao接口：\n````\npublic interface Dao {\npublic void select();\npublic void insert();\n}\nDao接口的实现类DaoImpl：\n\n\npublic class DaoImpl implements Dao {\n\n    @Override\n    public void select() {\n        System.out.println(\"Enter DaoImpl.select()\");\n    }\n\n    @Override\n    public void insert() {\n        System.out.println(\"Enter DaoImpl.insert()\");\n    }\n\n}\n\n````\n\n定义一个TimeHandler，用于方法调用前后打印时间，在AOP中，这扮演的是横切关注点的角色：\n\n```\npublic class TimeHandler {\n\n    public void printTime() {\n        System.out.println(\"CurrentTime:\" + System.currentTimeMillis());\n    }\n\n}\n\n```\n\n定义一个XML文件aop.xml：\n\n```\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<beans xmlns=\"http://www.springframework.org/schema/beans\"\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xmlns:aop=\"http://www.springframework.org/schema/aop\"\n    xmlns:tx=\"http://www.springframework.org/schema/tx\"\n    xsi:schemaLocation=\"http://www.springframework.org/schema/beans\n\nhttp://www.springframework.org/schema/beans/spring-beans-3.0.xsd\n\nhttp://www.springframework.org/schema/aop\n\nhttp://www.springframework.org/schema/aop/spring-aop-3.0.xsd\">\n\n    <bean id=\"daoImpl\" class=\"org.xrq.action.aop.DaoImpl\" />\n    <bean id=\"timeHandler\" class=\"org.xrq.action.aop.TimeHandler\" />\n\n \n</beans>\n\n```\n\n写一段测试代码TestAop.java：\n\n```\npublic class TestAop {\n\n    @Test\n    public void testAop() {\n        ApplicationContext ac = new ClassPathXmlApplicationContext(\"spring/aop.xml\");\n\n        Dao dao = (Dao)ac.getBean(\"daoImpl\");\n        dao.select();\n    }\n\n}\n\n```\n\n代码运行结果就不看了，有了以上的内容，我们就可以根据这些跟一下代码，看看Spring到底是如何实现AOP的。\n\n## AOP实现原理——找到Spring处理AOP的源头\n\n有很多朋友不愿意去看AOP源码的一个很大原因是因为找不到AOP源码实现的入口在哪里，这个确实是。不过我们可以看一下上面的测试代码，就普通Bean也好、AOP也好，最终都是通过getBean方法获取到Bean并调用方法的，getBean之后的对象已经前后都打印了TimeHandler类printTime()方法里面的内容，可以想见它们已经是被Spring容器处理过了。\n\n既然如此，那无非就两个地方处理：\n\n加载Bean定义的时候应该有过特殊的处理\ngetBean的时候应该有过特殊的处理\n因此，本文围绕【1.加载Bean定义的时候应该有过特殊的处理】展开，先找一下到底是哪里Spring对AOP做了特殊的处理。代码直接定位到DefaultBeanDefinitionDocumentReader的parseBeanDefinitions方法：\n\n```\nprotected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {\n    if (delegate.isDefaultNamespace(root)) {\n        NodeList nl = root.getChildNodes();\n        for (int i = 0; i < nl.getLength(); i++) {\n            Node node = nl.item(i);\n            if (node instanceof Element) {\n                Element ele = (Element) node;\n                if (delegate.isDefaultNamespace(ele)) {\n                    parseDefaultElement(ele, delegate);\n                }\n                else {\n                    delegate.parseCustomElement(ele);\n                }\n            }\n        }\n    }\n    else {\n        delegate.parseCustomElement(root);\n    }\n}\n\n```\n\n正常来说，遇到<bean id=”daoImpl”…>、<bean id=”timeHandler”…>这两个标签的时候，都会执行第9行的代码，因为<bean>标签是默认的Namespace。但是在遇到后面的标签的时候就不一样了，并不是默认的Namespace，因此会执行第12行的代码，看一下：\n\n```\npublic BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {\n    String namespaceUri = getNamespaceURI(ele);\n    NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);\n    if (handler == null) {\n        error(\"Unable to locate Spring NamespaceHandler for XML schema namespace [\" + namespaceUri + \"]\", ele);\n        return null;\n    }\n    return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));\n}\n\n```\n\n因为之前把整个XML解析为了org.w3c.dom.Document，org.w3c.dom.Document以树的形式表示整个XML，具体到每一个节点就是一个Node。\n\n首先第2行从这个Node（参数Element是Node接口的子接口）中拿到Namespace=”[http://www.springframework.org/schema/aop](https://links.jianshu.com/go?to=http%3A%2F%2Fwww.springframework.org%2Fschema%2Faop)“，第3行的代码根据这个Namespace获取对应的NamespaceHandler即Namespace处理器，具体到aop这个Namespace的NamespaceHandler是org.springframework.aop.config.AopNamespaceHandler类，也就是第3行代码获取到的结果。具体到AopNamespaceHandler里面，有几个Parser，是用于具体标签转换的，分别为：\n\nconfig–>ConfigBeanDefinitionParser\naspectj-autoproxy–>AspectJAutoProxyBeanDefinitionParser\nscoped-proxy–>ScopedProxyBeanDefinitionDecorator\nspring-configured–>SpringConfiguredBeanDefinitionParser\n接着，就是第8行的代码，利用AopNamespaceHandler的parse方法，解析下的内容了。\n\n## 解析增强器advisor\n\nAOP Bean定义加载——根据织入方式将、转换成名为adviceDef的RootBeanDefinition\n上面经过分析，已经找到了Spring是通过AopNamespaceHandler处理的AOP，那么接着进入AopNamespaceHandler的parse方法源代码：\n\n```\npublic BeanDefinition parse(Element element, ParserContext parserContext) {\n    return findParserForElement(element, parserContext).parse(element, parserContext);\n}   \n\n```\n\n首先获取具体的Parser，因为当前节点是，上一部分最后有列，config是通过ConfigBeanDefinitionParser来处理的，因此findParserForElement(element, parserContext)这一部分代码获取到的是ConfigBeanDefinitionParser，接着看ConfigBeanDefinitionParser的parse方法：\n\n```\npublic BeanDefinition parse(Element element, ParserContext parserContext) {\n    CompositeComponentDefinition compositeDef =\n            new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));\n    parserContext.pushContainingComponent(compositeDef);\n\n    configureAutoProxyCreator(parserContext, element);\n\n    List<Element> childElts = DomUtils.getChildElements(element);\n    for (Element elt: childElts) {\n        String localName = parserContext.getDelegate().getLocalName(elt);\n        if (POINTCUT.equals(localName)) {\n            parsePointcut(elt, parserContext);\n        }\n        else if (ADVISOR.equals(localName)) {\n            parseAdvisor(elt, parserContext);\n        }\n        else if (ASPECT.equals(localName)) {\n            parseAspect(elt, parserContext);\n        }\n    }\n\n    parserContext.popAndRegisterContainingComponent();\n    return null;\n}\n\n```\n\n重点先提一下第6行的代码，该行代码的具体实现不跟了但它非常重要，configureAutoProxyCreator方法的作用我用几句话说一下：\n\n向Spring容器注册了一个BeanName为org.springframework.aop.config.internalAutoProxyCreator的Bean定义，可以自定义也可以使用Spring提供的（根据优先级来）\nSpring默认提供的是org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator，这个类是AOP的核心类，留在下篇讲解\n在这个方法里面也会根据配置proxy-target-class和expose-proxy，设置是否使用CGLIB进行代理以及是否暴露最终的代理。\n下的节点为，想见必然是执行第18行的代码parseAspect，跟进去：\n\n```\nprivate void parseAspect(Element aspectElement, ParserContext parserContext) {\n    String aspectId = aspectElement.getAttribute(ID);\n    String aspectName = aspectElement.getAttribute(REF);\n\n    try {\n        this.parseState.push(new AspectEntry(aspectId, aspectName));\n        List<BeanDefinition> beanDefinitions = new ArrayList<BeanDefinition>();\n        List<BeanReference> beanReferences = new ArrayList<BeanReference>();\n\n        List<Element> declareParents = DomUtils.getChildElementsByTagName(aspectElement, DECLARE_PARENTS);\n        for (int i = METHOD_INDEX; i < declareParents.size(); i++) {\n            Element declareParentsElement = declareParents.get(i);\n            beanDefinitions.add(parseDeclareParents(declareParentsElement, parserContext));\n        }\n\n        // We have to parse \"advice\" and all the advice kinds in one loop, to get the\n        // ordering semantics right.\n        NodeList nodeList = aspectElement.getChildNodes();\n        boolean adviceFoundAlready = false;\n        for (int i = 0; i < nodeList.getLength(); i++) {\n            Node node = nodeList.item(i);\n            if (isAdviceNode(node, parserContext)) {\n                if (!adviceFoundAlready) {\n                    adviceFoundAlready = true;\n                    if (!StringUtils.hasText(aspectName)) {\n                        parserContext.getReaderContext().error(\n                                \" tag needs aspect bean reference via 'ref' attribute when declaring advices.\",\n                                aspectElement, this.parseState.snapshot());\n                        return;\n                    }\n                    beanReferences.add(new RuntimeBeanReference(aspectName));\n                }\n                AbstractBeanDefinition advisorDefinition = parseAdvice(\n                        aspectName, i, aspectElement, (Element) node, parserContext, beanDefinitions, beanReferences);\n                beanDefinitions.add(advisorDefinition);\n            }\n        }\n\n        AspectComponentDefinition aspectComponentDefinition = createAspectComponentDefinition(\n                aspectElement, aspectId, beanDefinitions, beanReferences, parserContext);\n        parserContext.pushContainingComponent(aspectComponentDefinition);\n\n        List<Element> pointcuts = DomUtils.getChildElementsByTagName(aspectElement, POINTCUT);\n        for (Element pointcutElement : pointcuts) {\n            parsePointcut(pointcutElement, parserContext);\n        }\n\n        parserContext.popAndRegisterContainingComponent();\n    }\n    finally {\n        this.parseState.pop();\n    }\n}\n\n```\n\n从第20行~第37行的循环开始关注这个方法。这个for循环有一个关键的判断就是第22行的ifAdviceNode判断，看下ifAdviceNode方法做了什么：\n\n```\nprivate boolean isAdviceNode(Node aNode, ParserContext parserContext) {\n    if (!(aNode instanceof Element)) {\n        return false;\n    }\n    else {\n        String name = parserContext.getDelegate().getLocalName(aNode);\n        return (BEFORE.equals(name) || AFTER.equals(name) || AFTER_RETURNING_ELEMENT.equals(name) ||\n                AFTER_THROWING_ELEMENT.equals(name) || AROUND.equals(name));\n    }\n}\n\n```\n\n即这个for循环只用来处理标签下的、、、、这五个标签的。\n\n接着，如果是上述五种标签之一，那么进入第33行~第34行的parseAdvice方法：\n\n```\nprivate AbstractBeanDefinition parseAdvice(\n    String aspectName, int order, Element aspectElement, Element adviceElement, ParserContext parserContext,\n    List<BeanDefinition> beanDefinitions, List<BeanReference> beanReferences) {\n    try {\n        this.parseState.push(new AdviceEntry(parserContext.getDelegate().getLocalName(adviceElement)));\n        // create the method factory bean\n        RootBeanDefinition methodDefinition = new RootBeanDefinition(MethodLocatingFactoryBean.class);\n        methodDefinition.getPropertyValues().add(\"targetBeanName\", aspectName);\n        methodDefinition.getPropertyValues().add(\"methodName\", adviceElement.getAttribute(\"method\"));\n        methodDefinition.setSynthetic(true);\n        // create instance factory definition\n        RootBeanDefinition aspectFactoryDef =\n        new RootBeanDefinition(SimpleBeanFactoryAwareAspectInstanceFactory.class);\n        aspectFactoryDef.getPropertyValues().add(\"aspectBeanName\", aspectName);\n        aspectFactoryDef.setSynthetic(true);\n\n        // register the pointcut\n        AbstractBeanDefinition adviceDef = createAdviceDefinition(\n            adviceElement, parserContext, aspectName, order, methodDefinition, aspectFactoryDef,\n            beanDefinitions, beanReferences);\n\n        // configure the advisor\n        RootBeanDefinition advisorDefinition = new RootBeanDefinition(AspectJPointcutAdvisor.class);\n        advisorDefinition.setSource(parserContext.extractSource(adviceElement));\n        advisorDefinition.getConstructorArgumentValues().addGenericArgumentValue(adviceDef);\n        if (aspectElement.hasAttribute(ORDER_PROPERTY)) {\n            advisorDefinition.getPropertyValues().add(\n                ORDER_PROPERTY, aspectElement.getAttribute(ORDER_PROPERTY));\n        }\n\n        // register the final advisor\n        parserContext.getReaderContext().registerWithGeneratedName(advisorDefinition);\n\n        return advisorDefinition;\n    }\n    finally {\n        this.parseState.pop();\n    }\n}\n\n```\n\n方法主要做了三件事：\n\n根据织入方式（before、after这些）创建RootBeanDefinition，名为adviceDef即advice定义\n\n将上一步创建的RootBeanDefinition写入一个新的RootBeanDefinition，构造一个新的对象，名为advisorDefinition，即advisor定义\n将advisorDefinition注册到DefaultListableBeanFactory中\n下面来看做的第一件事createAdviceDefinition方法定义：\n\n```\nprivate AbstractBeanDefinition createAdviceDefinition(\n        Element adviceElement, ParserContext parserContext, String aspectName, int order,\n        RootBeanDefinition methodDef, RootBeanDefinition aspectFactoryDef,\n        List<BeanDefinition> beanDefinitions, List<BeanReference> beanReferences) {\n\n    RootBeanDefinition adviceDefinition = new RootBeanDefinition(getAdviceClass(adviceElement, parserContext));\n    adviceDefinition.setSource(parserContext.extractSource(adviceElement));\n        adviceDefinition.getPropertyValues().add(ASPECT_NAME_PROPERTY, aspectName);\n    adviceDefinition.getPropertyValues().add(DECLARATION_ORDER_PROPERTY, order);\n\n    if (adviceElement.hasAttribute(RETURNING)) {\n        adviceDefinition.getPropertyValues().add(\n                RETURNING_PROPERTY, adviceElement.getAttribute(RETURNING));\n    }\n    if (adviceElement.hasAttribute(THROWING)) {\n        adviceDefinition.getPropertyValues().add(\n                THROWING_PROPERTY, adviceElement.getAttribute(THROWING));\n    }\n    if (adviceElement.hasAttribute(ARG_NAMES)) {\n        adviceDefinition.getPropertyValues().add(\n                ARG_NAMES_PROPERTY, adviceElement.getAttribute(ARG_NAMES));\n    }\n\n    ConstructorArgumentValues cav = adviceDefinition.getConstructorArgumentValues();\n    cav.addIndexedArgumentValue(METHOD_INDEX, methodDef);\n\n    Object pointcut = parsePointcutProperty(adviceElement, parserContext);\n    if (pointcut instanceof BeanDefinition) {\n        cav.addIndexedArgumentValue(POINTCUT_INDEX, pointcut);\n        beanDefinitions.add((BeanDefinition) pointcut);\n    }\n    else if (pointcut instanceof String) {\n        RuntimeBeanReference pointcutRef = new RuntimeBeanReference((String) pointcut);\n        cav.addIndexedArgumentValue(POINTCUT_INDEX, pointcutRef);\n        beanReferences.add(pointcutRef);\n    }\n\n    cav.addIndexedArgumentValue(ASPECT_INSTANCE_FACTORY_INDEX, aspectFactoryDef);\n\n    return adviceDefinition;\n}\n\n```\n\n首先可以看到，创建的AbstractBeanDefinition实例是RootBeanDefinition，这和普通Bean创建的实例为GenericBeanDefinition不同。然后进入第6行的getAdviceClass方法看一下：\n\n```\nprivate Class getAdviceClass(Element adviceElement, ParserContext parserContext) {\n    String elementName = parserContext.getDelegate().getLocalName(adviceElement);\n    if (BEFORE.equals(elementName)) {\n        return AspectJMethodBeforeAdvice.class;\n    }\n    else if (AFTER.equals(elementName)) {\n        return AspectJAfterAdvice.class;\n    }\n    else if (AFTER_RETURNING_ELEMENT.equals(elementName)) {\n        return AspectJAfterReturningAdvice.class;\n    }\n    else if (AFTER_THROWING_ELEMENT.equals(elementName)) {\n        return AspectJAfterThrowingAdvice.class;\n    }\n    else if (AROUND.equals(elementName)) {\n        return AspectJAroundAdvice.class;\n    }\n    else {\n        throw new IllegalArgumentException(\"Unknown advice kind [\" + elementName + \"].\");\n    }\n}\n\n```\n\n既然创建Bean定义，必然该Bean定义中要对应一个具体的Class，不同的切入方式对应不同的Class：\n\nbefore对应AspectJMethodBeforeAdvice\nAfter对应AspectJAfterAdvice\nafter-returning对应AspectJAfterReturningAdvice\nafter-throwing对应AspectJAfterThrowingAdvice\naround对应AspectJAroundAdvice\n\n\ncreateAdviceDefinition方法剩余逻辑没什么，就是判断一下标签里面的属性并设置一下相应的值而已，至此、两个标签对应的AbstractBeanDefinition就创建出来了。\n\nAOP Bean定义加载——将名为adviceDef的RootBeanDefinition转换成名为advisorDefinition的RootBeanDefinition\n下面我们看一下第二步的操作，将名为adviceDef的RootBeanD转换成名为advisorDefinition的RootBeanDefinition，跟一下上面一部分ConfigBeanDefinitionParser类parseAdvice方法的第26行~32行的代码：\n\n```\nRootBeanDefinition advisorDefinition = new RootBeanDefinition(AspectJPointcutAdvisor.class);\nadvisorDefinition.setSource(parserContext.extractSource(adviceElement));\nadvisorDefinition.getConstructorArgumentValues().addGenericArgumentValue(adviceDef);\nif (aspectElement.hasAttribute(ORDER_PROPERTY)) {\n    advisorDefinition.getPropertyValues().add(\n            ORDER_PROPERTY, aspectElement.getAttribute(ORDER_PROPERTY));\n}\n\n```\n\n这里相当于将上一步生成的RootBeanDefinition包装了一下，new一个新的RootBeanDefinition出来，Class类型是org.springframework.aop.aspectj.AspectJPointcutAdvisor。\n\n第4行~第7行的代码是用于判断标签中有没有”order”属性的，有就设置一下，”order”属性是用来控制切入方法优先级的。\n\nAOP Bean定义加载——将BeanDefinition注册到DefaultListableBeanFactory中\n\n最后一步就是将BeanDefinition注册到DefaultListableBeanFactory中了，代码就是前面ConfigBeanDefinitionParser的parseAdvice方法的最后一部分了：\n\n```\n// register the final advisor\nparserContext.getReaderContext().registerWithGeneratedName(advisorDefinition);\n...\n跟一下registerWithGeneratedName方法的实现：\n\n```\npublic String registerWithGeneratedName(BeanDefinition beanDefinition) {\n    String generatedName = generateBeanName(beanDefinition);\n    getRegistry().registerBeanDefinition(generatedName, beanDefinition);\n    return generatedName;\n}\n\n```\n\n第2行获取注册的名字BeanName，和<bean>的注册差不多，使用的是Class全路径+”#”+全局计数器的方式，其中的Class全路径为org.springframework.aop.aspectj.AspectJPointcutAdvisor，依次类推，每一个BeanName应当为org.springframework.aop.aspectj.AspectJPointcutAdvisor#0、org.springframework.aop.aspectj.AspectJPointcutAdvisor#1、org.springframework.aop.aspectj.AspectJPointcutAdvisor#2这样下去。\n\n第3行向DefaultListableBeanFactory中注册，BeanName已经有了，剩下的就是Bean定义，Bean定义的解析流程之前已经看过了，就不说了。\n```\n\n## 解析切面的过程\n\nAOP Bean定义加载——AopNamespaceHandler处理流程\n回到ConfigBeanDefinitionParser的parseAspect方法：\n\n    private void parseAspect(Element aspectElement, ParserContext parserContext) {\n    \n            ...  \n    \n            AspectComponentDefinition aspectComponentDefinition = createAspectComponentDefinition(\n                    aspectElement, aspectId, beanDefinitions, beanReferences, parserContext);\n            parserContext.pushContainingComponent(aspectComponentDefinition);\n    \n            List<Element> pointcuts = DomUtils.getChildElementsByTagName(aspectElement, POINTCUT);\n            for (Element pointcutElement : pointcuts) {\n                parsePointcut(pointcutElement, parserContext);\n            }\n    \n            parserContext.popAndRegisterContainingComponent();\n        }\n        finally {\n            this.parseState.pop();\n        }\n    }\n\n\n省略号部分表示是解析的是、这种标签，上部分已经说过了，就不说了，下面看一下解析部分的源码。\n\n第5行~第7行的代码构建了一个Aspect标签组件定义，并将Apsect标签组件定义推到ParseContext即解析工具上下文中，这部分代码不是关键。\n\n第9行的代码拿到所有下的pointcut标签，进行遍历，由parsePointcut方法进行处理：\n\n```\nprivate AbstractBeanDefinition parsePointcut(Element pointcutElement, ParserContext parserContext) {\n    String id = pointcutElement.getAttribute(ID);\n    String expression = pointcutElement.getAttribute(EXPRESSION);\n\n    AbstractBeanDefinition pointcutDefinition = null;\n\n    try {\n        this.parseState.push(new PointcutEntry(id));\n        pointcutDefinition = createPointcutDefinition(expression);\n        pointcutDefinition.setSource(parserContext.extractSource(pointcutElement));\n\n        String pointcutBeanName = id;\n        if (StringUtils.hasText(pointcutBeanName)) {\n            parserContext.getRegistry().registerBeanDefinition(pointcutBeanName, pointcutDefinition);\n        }\n        else {\n            pointcutBeanName = parserContext.getReaderContext().registerWithGeneratedName(pointcutDefinition);\n        }\n\n        parserContext.registerComponent(\n                new PointcutComponentDefinition(pointcutBeanName, pointcutDefinition, expression));\n    }\n    finally {\n        this.parseState.pop();\n    }\n\n    return pointcutDefinition;\n}\n\n```\n\n第2行~第3行的代码获取标签下的”id”属性与”expression”属性。\n\n第8行的代码推送一个PointcutEntry，表示当前Spring上下文正在解析Pointcut标签。\n\n第9行的代码创建Pointcut的Bean定义，之后再看，先把其他方法都看一下。\n\n第10行的代码不管它，最终从NullSourceExtractor的extractSource方法获取Source，就是个null。\n\n第12行~第18行的代码用于注册获取到的Bean定义，默认pointcutBeanName为标签中定义的id属性：\n\n如果标签中配置了id属性就执行的是第13行~第15行的代码，pointcutBeanName=id\n如果标签中没有配置id属性就执行的是第16行~第18行的代码，和Bean不配置id属性一样的规则，pointcutBeanName=org.springframework.aop.aspectj.AspectJExpressionPointcut#序号（从0开始累加）\n第20行~第21行的代码向解析工具上下文中注册一个Pointcut组件定义\n\n第23行~第25行的代码，finally块在标签解析完毕后，让之前推送至栈顶的PointcutEntry出栈，表示此次标签解析完毕。\n\n最后回头来一下第9行代码createPointcutDefinition的实现，比较简单：\n\n```\nprotected AbstractBeanDefinition createPointcutDefinition(String expression) {\n    RootBeanDefinition beanDefinition = new RootBeanDefinition(AspectJExpressionPointcut.class);\n    beanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE);\n    beanDefinition.setSynthetic(true);\n    beanDefinition.getPropertyValues().add(EXPRESSION, expression);\n    return beanDefinition;\n}\n\n```\n\n关键就是注意一下两点：\n\n标签对应解析出来的BeanDefinition是RootBeanDefinition，且RootBenaDefinitoin中的Class是org.springframework.aop.aspectj.AspectJExpressionPointcut\n标签对应的Bean是prototype即原型的\n这样一个流程下来，就解析了标签中的内容并将之转换为RootBeanDefintion存储在Spring容器中。\n\n## AOP为Bean生成代理的时机分析\n\n上篇文章说了，org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator这个类是Spring提供给开发者的AOP的核心类，就是AspectJAwareAdvisorAutoProxyCreator完成了【类/接口–>代理】的转换过程，首先我们看一下AspectJAwareAdvisorAutoProxyCreator的层次结构：\n\n这里最值得注意的一点是最左下角的那个方框，我用几句话总结一下：\n\nAspectJAwareAdvisorAutoProxyCreator是BeanPostProcessor接口的实现类\npostProcessBeforeInitialization方法与postProcessAfterInitialization方法实现在父类AbstractAutoProxyCreator中\npostProcessBeforeInitialization方法是一个空实现\n逻辑代码在postProcessAfterInitialization方法中\n基于以上的分析，将Bean生成代理的时机已经一目了然了：在每个Bean初始化之后，如果需要，调用AspectJAwareAdvisorAutoProxyCreator中的postProcessBeforeInitialization为Bean生成代理。\n\n代理对象实例化—-判断是否为<bean>生成代理\n上文分析了Bean生成代理的时机是在每个Bean初始化之后，下面把代码定位到Bean初始化之后，先是AbstractAutowireCapableBeanFactory的initializeBean方法进行初始化：\n\n```\nprotected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {\n    if (System.getSecurityManager() != null) {\n        AccessController.doPrivileged(new PrivilegedAction<Object>() {\n            public Object run() {\n                invokeAwareMethods(beanName, bean);\n                return null;\n            }\n        }, getAccessControlContext());\n    }\n    else {\n        invokeAwareMethods(beanName, bean);\n    }\n\n    Object wrappedBean = bean;\n    if (mbd == null || !mbd.isSynthetic()) {\n        wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);\n    }\n\n    try {\n    invokeInitMethods(beanName, wrappedBean, mbd);\n    }\n    catch (Throwable ex) {\n        throw new BeanCreationException(\n                (mbd != null ? mbd.getResourceDescription() : null),\n                beanName, \"Invocation of init method failed\", ex);\n    }\n\n    if (mbd == null || !mbd.isSynthetic()) {\n        wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);\n    }\n    return wrappedBean;\n}\n\n```\n\n初始化之前是第16行的applyBeanPostProcessorsBeforeInitialization方法，初始化之后即29行的applyBeanPostProcessorsAfterInitialization方法：\n\n```\npublic Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)\n        throws BeansException {\n\n    Object result = existingBean;\n    for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {\n        result = beanProcessor.postProcessAfterInitialization(result, beanName);\n        if (result == null) {\n            return result;\n        }\n    }\n    return result;\n}\n\n```\n\n这里调用每个BeanPostProcessor的postProcessBeforeInitialization方法。按照之前的分析，看一下AbstractAutoProxyCreator的postProcessAfterInitialization方法实现：\n\n```\npublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {\n    if (bean != null) {\n        Object cacheKey = getCacheKey(bean.getClass(), beanName);\n        if (!this.earlyProxyReferences.contains(cacheKey)) {\n            return wrapIfNecessary(bean, beanName, cacheKey);\n        }\n    }\n    return bean;\n}\n\n```\n\n跟一下第5行的方法wrapIfNecessary：\n\n```\nprotected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {\n    if (this.targetSourcedBeans.contains(beanName)) {\n        return bean;\n    }\n    if (this.nonAdvisedBeans.contains(cacheKey)) {\n        return bean;\n    }\n    if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {\n        this.nonAdvisedBeans.add(cacheKey);\n        return bean;\n    }\n\n    // Create proxy if we have advice.\n    Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);\n    if (specificInterceptors != DO_NOT_PROXY) {\n        this.advisedBeans.add(cacheKey);\n        Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));\n        this.proxyTypes.put(cacheKey, proxy.getClass());\n        return proxy;\n    }\n\n    this.nonAdvisedBeans.add(cacheKey);\n    return bean;\n}\n\n```\n\n第2行~第11行是一些不需要生成代理的场景判断，这里略过。首先我们要思考的第一个问题是：哪些目标对象需要生成代理？因为配置文件里面有很多Bean，肯定不能对每个Bean都生成代理，因此需要一套规则判断Bean是不是需要生成代理，这套规则就是第14行的代码getAdvicesAndAdvisorsForBean：\n\n```\n    protected List<Advisor> findEligibleAdvisors(Class beanClass, String beanName) {\n        List<Advisor> candidateAdvisors = findCandidateAdvisors();\n        List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);\n        extendAdvisors(eligibleAdvisors);\n        if (!eligibleAdvisors.isEmpty()) {\n            eligibleAdvisors = sortAdvisors(eligibleAdvisors);\n        }\n        return eligibleAdvisors;\n    }\n\n```\n\n顾名思义，方法的意思是为指定class寻找合适的Advisor。\n\n第2行代码，寻找候选Advisors，根据上文的配置文件，有两个候选Advisor，分别是节点下的和这两个，这两个在XML解析的时候已经被转换生成了RootBeanDefinition。\n\n跳过第3行的代码，先看下第4行的代码extendAdvisors方法，之后再重点看一下第3行的代码。第4行的代码extendAdvisors方法作用是向候选Advisor链的开头（也就是List.get(0)的位置）添加一个org.springframework.aop.support.DefaultPointcutAdvisor。\n\n第3行代码，根据候选Advisors，寻找可以使用的Advisor，跟一下方法实现：\n\n```\npublic static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {\n    if (candidateAdvisors.isEmpty()) {\n        return candidateAdvisors;\n    }\n    List<Advisor> eligibleAdvisors = new LinkedList<Advisor>();\n    for (Advisor candidate : candidateAdvisors) {\n        if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {\n            eligibleAdvisors.add(candidate);\n        }\n    }\n    boolean hasIntroductions = !eligibleAdvisors.isEmpty();\n    for (Advisor candidate : candidateAdvisors) {\n        if (candidate instanceof IntroductionAdvisor) {\n            // already processed\n            continue;\n        }\n        if (canApply(candidate, clazz, hasIntroductions)) {\n            eligibleAdvisors.add(candidate);\n        }\n    }\n    return eligibleAdvisors;\n}\n\n```\n\n整个方法的主要判断都围绕canApply展开方法：\n\n```\npublic static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) {\n    if (advisor instanceof IntroductionAdvisor) {\n        return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);\n    }\n    else if (advisor instanceof PointcutAdvisor) {\n        PointcutAdvisor pca = (PointcutAdvisor) advisor;\n        return canApply(pca.getPointcut(), targetClass, hasIntroductions);\n    }\n    else {\n        // It doesn't have a pointcut so we assume it applies.\n        return true;\n    }\n}\n\n```\n\n第一个参数advisor的实际类型是AspectJPointcutAdvisor，它是PointcutAdvisor的子类，因此执行第7行的方法：\n\n```\npublic static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {\n    if (!pc.getClassFilter().matches(targetClass)) {\n        return false;\n    }\n\n    MethodMatcher methodMatcher = pc.getMethodMatcher();\n    IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;\n    if (methodMatcher instanceof IntroductionAwareMethodMatcher) {\n        introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;\n    }\n\n    Set<Class> classes = new HashSet<Class>(ClassUtils.getAllInterfacesForClassAsSet(targetClass));\n    classes.add(targetClass);\n    for (Class<?> clazz : classes) {\n        Method[] methods = clazz.getMethods();\n        for (Method method : methods) {\n            if ((introductionAwareMethodMatcher != null &&\n                introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions)) ||\n                    methodMatcher.matches(method, targetClass)) {\n                return true;\n            }\n        }\n    }\n    return false;\n}\n\n```\n\n这个方法其实就是拿当前Advisor对应的expression做了两层判断：\n\n目标类必须满足expression的匹配规则\n目标类中的方法必须满足expression的匹配规则，当然这里方法不是全部需要满足expression的匹配规则，有一个方法满足即可\n如果以上两条都满足，那么容器则会判断该<bean>满足条件，需要被生成代理对象，具体方式为返回一个数组对象，该数组对象中存储的是<bean>对应的Advisor。\n\n## 代理对象实例化过程\n\n代理对象实例化—-为<bean>生成代理代码上下文梳理\n上文分析了为<bean>生成代理的条件，现在就正式看一下Spring上下文是如何为<bean>生成代理的。回到AbstractAutoProxyCreator的wrapIfNecessary方法：\n\n```\nprotected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {\n    if (this.targetSourcedBeans.contains(beanName)) {\n        return bean;\n    }\n    if (this.nonAdvisedBeans.contains(cacheKey)) {\n        return bean;\n    }\n    if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {\n        this.nonAdvisedBeans.add(cacheKey);\n        return bean;\n    }\n\n    // Create proxy if we have advice.\n    Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);\n    if (specificInterceptors != DO_NOT_PROXY) {\n        this.advisedBeans.add(cacheKey);\n        Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));\n        this.proxyTypes.put(cacheKey, proxy.getClass());\n        return proxy;\n    }\n\n    this.nonAdvisedBeans.add(cacheKey);\n    return bean;\n}\n\n```\n\n第14行拿到<bean>对应的Advisor数组，第15行判断只要Advisor数组不为空，那么就会通过第17行的代码为<bean>创建代理：\n\n```\nprotected Object createProxy(\n        Class<?> beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource) {\n\n    ProxyFactory proxyFactory = new ProxyFactory();\n    // Copy our properties (proxyTargetClass etc) inherited from ProxyConfig.\n    proxyFactory.copyFrom(this);\n\n    if (!shouldProxyTargetClass(beanClass, beanName)) {\n        // Must allow for introductions; can't just set interfaces to\n        // the target's interfaces only.\n        Class<?>[] targetInterfaces = ClassUtils.getAllInterfacesForClass(beanClass, this.proxyClassLoader);\n        for (Class<?> targetInterface : targetInterfaces) {\n            proxyFactory.addInterface(targetInterface);\n        }\n    }\n\n    Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);\n    for (Advisor advisor : advisors) {\n        proxyFactory.addAdvisor(advisor);\n    }\n\n    proxyFactory.setTargetSource(targetSource);\n    customizeProxyFactory(proxyFactory);\n\n    proxyFactory.setFrozen(this.freezeProxy);\n    if (advisorsPreFiltered()) {\n        proxyFactory.setPreFiltered(true);\n    }\n\n    return proxyFactory.getProxy(this.proxyClassLoader);\n}\n\n```\n\n第4行~第6行new出了一个ProxyFactory，Proxy，顾名思义，代理工厂的意思，提供了简单的方式使用代码获取和配置AOP代理。\n\n第8行的代码做了一个判断，判断的内容是这个节点中proxy-target-class=”false”或者proxy-target-class不配置，即不使用CGLIB生成代理。如果满足条件，进判断，获取当前Bean实现的所有接口，讲这些接口Class对象都添加到ProxyFactory中。\n\n第17行~第28行的代码没什么看的必要，向ProxyFactory中添加一些参数而已。重点看第30行proxyFactory.getProxy(this.proxyClassLoader)这句：\n\n```\npublic Object getProxy(ClassLoader classLoader) {\nreturn createAopProxy().getProxy(classLoader);\n}\n\n```\n\n实现代码就一行，但是却明确告诉我们做了两件事情：\n\n创建AopProxy接口实现类\n通过AopProxy接口的实现类的getProxy方法获取<bean>对应的代理\n就从这两个点出发，分两部分分析一下。\n\n代理对象实例化—-创建AopProxy接口实现类\n看一下createAopProxy()方法的实现，它位于DefaultAopProxyFactory类中：\n\n```\nprotected final synchronized AopProxy createAopProxy() {\nif (!this.active) {\nactivate();\n}\nreturn getAopProxyFactory().createAopProxy(this);\n}\n\n```\n\n前面的部分没什么必要看，直接进入重点即createAopProxy方法：\n\n```\npublic AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {\n    if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {\n        Class targetClass = config.getTargetClass();\n        if (targetClass == null) {\n            throw new AopConfigException(\"TargetSource cannot determine target class: \" +\n                    \"Either an interface or a target is required for proxy creation.\");\n        }\n        if (targetClass.isInterface()) {\n            return new JdkDynamicAopProxy(config);\n        }\n        if (!cglibAvailable) {\n            throw new AopConfigException(\n                    \"Cannot proxy target class because CGLIB2 is not available. \" +\n                    \"Add CGLIB to the class path or specify proxy interfaces.\");\n        }\n        return CglibProxyFactory.createCglibProxy(config);\n    }\n    else {\n        return new JdkDynamicAopProxy(config);\n    }\n}\n\n```\n\n## 平时我们说AOP原理三句话就能概括：\n\n对类生成代理使用CGLIB\n对接口生成代理使用JDK原生的Proxy\n可以通过配置文件指定对接口使用CGLIB生成代理\n这三句话的出处就是createAopProxy方法。看到默认是第19行的代码使用JDK自带的Proxy生成代理，碰到以下三种情况例外：\n\nProxyConfig的isOptimize方法为true，这表示让Spring自己去优化而不是用户指定\nProxyConfig的isProxyTargetClass方法为true，这表示配置了proxy-target-class=”true”\nProxyConfig满足hasNoUserSuppliedProxyInterfaces方法执行结果为true，这表示<bean>对象没有实现任何接口或者实现的接口是SpringProxy接口\n在进入第2行的if判断之后再根据目标<bean>的类型决定返回哪种AopProxy。简单总结起来就是：\n\nproxy-target-class没有配置或者proxy-target-class=”false”，返回JdkDynamicAopProxy\nproxy-target-class=”true”或者<bean>对象没有实现任何接口或者只实现了SpringProxy接口，返回Cglib2AopProxy\n当然，不管是JdkDynamicAopProxy还是Cglib2AopProxy，AdvisedSupport都是作为构造函数参数传入的，里面存储了具体的Advisor。\n\n代理对象实例化—-通过getProxy方法获取<bean>对应的代理\n其实代码已经分析到了JdkDynamicAopProxy和Cglib2AopProxy，剩下的就没什么好讲的了，无非就是看对这两种方式生成代理的熟悉程度而已。\n\nCglib2AopProxy生成代理的代码就不看了，对Cglib不熟悉的朋友可以看Cglib及其基本使用一文。\n\nJdkDynamicAopProxy生成代理的方式稍微看一下：\n\n    public Object getProxy(ClassLoader classLoader) {\n    if (logger.isDebugEnabled()) {\n    logger.debug(\"Creating JDK dynamic proxy: target source is \" + this.advised.getTargetSource());\n    }\n    Class[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised);\n    findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);\n    return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);\n    }\n    \n这边解释一下第5行和第6行的代码，第5行代码的作用是拿到所有要代理的接口，第6行代码的作用是尝试寻找这些接口方法里面有没有equals方法和hashCode方法，同时都有的话打个标记，寻找结束，equals方法和hashCode方法有特殊处理。\n\n最终通过第7行的Proxy.newProxyInstance方法获取接口/类对应的代理对象，Proxy是JDK原生支持的生成代理的方式。\n\n## 代理方法调用原理\n前面已经详细分析了为接口/类生成代理的原理，生成代理之后就要调用方法了，这里看一下使用JdkDynamicAopProxy调用方法的原理。\n\n由于JdkDynamicAopProxy本身实现了InvocationHandler接口，因此具体代理前后处理的逻辑在invoke方法中：\n\n```\npublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {\n    MethodInvocation invocation;\n    Object oldProxy = null;\n    boolean setProxyContext = false;\n\n    TargetSource targetSource = this.advised.targetSource;\n    Class targetClass = null;\n    Object target = null;\n\n    try {\n        if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {\n            // The target does not implement the equals(Object) method itself.\n            return equals(args[0]);\n        }\n        if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {\n            // The target does not implement the hashCode() method itself.\n            return hashCode();\n        }\n        if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&\n                method.getDeclaringClass().isAssignableFrom(Advised.class)) {\n            // Service invocations on ProxyConfig with the proxy config...\n            return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);\n        }\n\n        Object retVal;\n\n        if (this.advised.exposeProxy) {\n            // Make invocation available if necessary.\n            oldProxy = AopContext.setCurrentProxy(proxy);\n            setProxyContext = true;\n        }\n\n        // May be null. Get as late as possible to minimize the time we \"own\" the target,\n        // in case it comes from a pool.\n        target = targetSource.getTarget();\n        if (target != null) {\n            targetClass = target.getClass();\n        }\n\n        // Get the interception chain for this method.\n        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);\n\n        // Check whether we have any advice. If we don't, we can fallback on direct\n        // reflective invocation of the target, and avoid creating a MethodInvocation.\n        if (chain.isEmpty()) {\n            // We can skip creating a MethodInvocation: just invoke the target directly\n            // Note that the final invoker must be an InvokerInterceptor so we know it does\n            // nothing but a reflective operation on the target, and no hot swapping or fancy proxying.\n            retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);\n        }\n        else {\n            // We need to create a method invocation...\n            invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);\n            // Proceed to the joinpoint through the interceptor chain.\n            retVal = invocation.proceed();\n        }\n\n        // Massage return value if necessary.\n        if (retVal != null && retVal == target && method.getReturnType().isInstance(proxy) &&\n                !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {\n            // Special case: it returned \"this\" and the return type of the method\n            // is type-compatible. Note that we can't help if the target sets\n            // a reference to itself in another returned object.\n            retVal = proxy;\n        }\n        return retVal;\n    }\n    finally {\n        if (target != null && !targetSource.isStatic()) {\n            // Must have come from TargetSource.\n            targetSource.releaseTarget(target);\n        }\n        if (setProxyContext) {\n            // Restore old proxy.\n            AopContext.setCurrentProxy(oldProxy);\n        }\n    }\n}\n\n```\n\n第11行~第18行的代码，表示equals方法与hashCode方法即使满足expression规则，也不会为之产生代理内容，调用的是JdkDynamicAopProxy的equals方法与hashCode方法。至于这两个方法是什么作用，可以自己查看一下源代码。\n\n第19行~第23行的代码，表示方法所属的Class是一个接口并且方法所属的Class是AdvisedSupport的父类或者父接口，直接通过反射调用该方法。\n\n第27行~第30行的代码，是用于判断是否将代理暴露出去的，由标签中的expose-proxy=”true/false”配置。\n\n第41行的代码，获取AdvisedSupport中的所有拦截器和动态拦截器列表，用于拦截方法，具体到我们的实际代码，列表中有三个Object，分别是：\n\nchain.get(0)：ExposeInvocationInterceptor，这是一个默认的拦截器，对应的原Advisor为DefaultPointcutAdvisor\nchain.get(1)：MethodBeforeAdviceInterceptor，用于在实际方法调用之前的拦截，对应的原Advisor为AspectJMethodBeforeAdvice\nchain.get(2)：AspectJAfterAdvice，用于在实际方法调用之后的处理\n第45行~第50行的代码，如果拦截器列表为空，很正常，因为某个类/接口下的某个方法可能不满足expression的匹配规则，因此此时通过反射直接调用该方法。\n\n第51行~第56行的代码，如果拦截器列表不为空，按照注释的意思，需要一个ReflectiveMethodInvocation，并通过proceed方法对原方法进行拦截，proceed方法感兴趣的朋友可以去看一下，里面使用到了递归的思想对chain中的Object进行了层层的调用。\n\n## CGLIB代理实现\n\n下面我们来看一下CGLIB代理的方式，这里需要读者去了解一下CGLIB以及其创建代理的方式：\n\n\n\n\n\n![](https://upload-images.jianshu.io/upload_images/5447660-9ab9ff5a18b6a429.jpeg?imageMogr2/auto-orient/strip|imageView2/2/w/915/format/webp)\n\n\n\n\n\n\n\n\n\n![](https://upload-images.jianshu.io/upload_images/5447660-f3812e8cf27245fa.jpeg?imageMogr2/auto-orient/strip|imageView2/2/w/711/format/webp)\n\n\n\n\n\n\n\n\n\n![](https://upload-images.jianshu.io/upload_images/5447660-2b8fafa06c78cacf.jpeg?imageMogr2/auto-orient/strip|imageView2/2/w/910/format/webp)\n\n\n\n\n\n这里将拦截器链封装到了DynamicAdvisedInterceptor中，并加入了Callback，DynamicAdvisedInterceptor实现了CGLIB的MethodInterceptor，所以其核心逻辑在intercept方法中：\n\n\n\n\n\n![](https://upload-images.jianshu.io/upload_images/5447660-c9dde35cfeb0faee.jpeg?imageMogr2/auto-orient/strip|imageView2/2/w/933/format/webp)\n\n\n\n\n\n这里我们看到了与JDK动态代理同样的获取拦截器链的过程，并且CglibMethodInvokcation继承了我们在JDK动态代理看到的ReflectiveMethodInvocation，但是并没有重写其proceed方法，只是重写了执行目标方法的逻辑，所以整体上是大同小异的。\n\n到这里，整个Spring 动态AOP的源码就分析完了，Spring还支持静态AOP，这里就不过多赘述了，有兴趣的读者可以查阅相关资料来学习。\n\n\n## 微信公众号\n\n### 个人公众号：黄小斜\n\n黄小斜是跨考软件工程的 985 硕士，自学 Java 两年，拿到了 BAT 等近十家大厂 offer，从技术小白成长为阿里工程师。\n\n作者专注于 JAVA 后端技术栈，热衷于分享程序员干货、学习经验、求职心得和程序人生，目前黄小斜的CSDN博客有百万+访问量，知乎粉丝2W+，全网已有10W+读者。\n\n黄小斜是一个斜杠青年，坚持学习和写作，相信终身学习的力量，希望和更多的程序员交朋友，一起进步和成长！\n\n**原创电子书:**\n关注公众号【黄小斜】后回复【原创电子书】即可领取我原创的电子书《菜鸟程序员修炼手册：从技术小白到阿里巴巴Java工程师》\n\n**程序员3T技术学习资源：** 一些程序员学习技术的资源大礼包，关注公众号后，后台回复关键字 **“资料”** 即可免费无套路获取。\n\n**考研复习资料：**\n计算机考研大礼包，都是我自己考研复习时用的一些复习资料,包括公共课和专业的复习视频，这里也推荐给大家，关注公众号后，后台回复关键字 **“考研”** 即可免费获取。\n\n![](https://img-blog.csdnimg.cn/20190829222750556.jpg)\n\n\n### 技术公众号：Java技术江湖\n\n如果大家想要实时关注我更新的文章以及分享的干货的话，可以关注我的公众号【Java技术江湖】一位阿里 Java 工程师的技术小站，作者黄小斜，专注 Java 相关技术：SSM、SpringBoot、MySQL、分布式、中间件、集群、Linux、网络、多线程，偶尔讲点Docker、ELK，同时也分享技术干货和学习经验，致力于Java全栈开发！\n\n**Java工程师必备学习资源:** 一些Java工程师常用学习资源，关注公众号后，后台回复关键字 **“Java”** 即可免费无套路获取。\n\n![我的公众号](https://img-blog.csdnimg.cn/20190805090108984.jpg)\n \n"
  },
  {
    "path": "docs/Spring全家桶/Spring源码分析/Spring源码剖析：JDK和cglib动态代理原理详解.md",
    "content": "#目录\n* [前言](#前言)\n* [Java代理介绍](#java代理介绍)\n* [静态代理](#静态代理)\n* [JDK动态代理](#jdk动态代理)\n    * [JDK动态代理实现原理](#jdk动态代理实现原理)\n    * [Proxy类中的newProxyInstance](#proxy类中的newproxyinstance)\n    * [字节码生成](#字节码生成)\n* [代理类的方法调用](#代理类的方法调用)\n* [深入理解CGLIB动态代理机制](#深入理解cglib动态代理机制)\n    * [CGLIB动态代理示例](#cglib动态代理示例)\n    * [生成代理类对象](#生成代理类对象)\n    * [对委托类进行代理](#对委托类进行代理)\n\n\n转自 https://www.jianshu.com/u/668d0795a95b\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章将同步到我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《Spring和SpringMVC源码分析》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从spring基础入手，一步步地学习spring基础和springmvc的框架知识，并上手进行项目实战，spring框架是每一个Java工程师必须要学习和理解的知识点，进一步来说，你还需要掌握spring甚至是springmvc的源码以及实现原理，才能更完整地了解整个spring技术体系，形成自己的知识框架。\n\n后续还会有springboot和springcloud的技术专题，陆续为大家带来，敬请期待。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供部分知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->  \n## 前言\nAOP的基础是Java动态代理，了解和使用两种动态代理能让我们更好地理解 AOP，在讲解AOP之前，让我们先来看看Java动态代理的使用方式以及底层实现原理。\n\n本文是基于jdk1.8来对动态代理的底层机制进行探究的\n\n## Java代理介绍\n\nJava中代理的实现一般分为三种：JDK静态代理、JDK动态代理以及CGLIB动态代理。在Spring的AOP实现中，主要应用了JDK动态代理以及CGLIB动态代理。但是本文着重介绍JDK动态代理机制，CGLIB动态代理后面会接着探究。\n\n代理一般实现的模式为JDK静态代理：创建一个接口，然后创建被代理的类实现该接口并且实现该接口中的抽象方法。之后再创建一个代理类，同时使其也实现这个接口。在代理类中持有一个被代理对象的引用，而后在代理类方法中调用该对象的方法。\n\n其实就是代理类为被代理类预处理消息、过滤消息并在此之后将消息转发给被代理类，之后还能进行消息的后置处理。代理类和被代理类通常会存在关联关系(即上面提到的持有的被带离对象的引用)，代理类本身不实现服务，而是通过调用被代理类中的方法来提供服务。\n\n## 静态代理\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825140729721-1002455386.png)\n\n接口\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825140729978-492174823.png)\n\n被代理类\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825140730312-501564524.png)\n\n代理类\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825140730518-1275832673.png)\n\n测试类以及输出结果\n\n我们可以看出，使用JDK静态代理很容易就完成了对一个类的代理操作。但是JDK静态代理的缺点也暴露了出来：由于代理只能为一个类服务，如果需要代理的类很多，那么就需要编写大量的代理类，比较繁琐。\n\n下面我们使用JDK动态代理来做同样的事情\n\n## JDK动态代理\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825140729721-1002455386.png)\n\n接口\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825140729978-492174823.png)\n\n被代理类\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825140731035-1119549632.png)\n\n代理类\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825140731317-1929161422.png)\n\n测试类以及输出结果\n\n### JDK动态代理实现原理\n\nJDK动态代理其实也是基本接口实现的。因为通过接口指向实现类实例的多态方式，可以有效地将具体实现与调用解耦，便于后期的修改和维护。\n\n通过上面的介绍，我们可以发现JDK静态代理与JDK动态代理之间有些许相似，比如说都要创建代理类，以及代理类都要实现接口等。但是不同之处也非常明显----在静态代理中我们需要对哪个接口和哪个被代理类创建代理类，所以我们在编译前就需要代理类实现与被代理类相同的接口，并且直接在实现的方法中调用被代理类相应的方法；但是动态代理则不同，我们不知道要针对哪个接口、哪个被代理类创建代理类，因为它是在运行时被创建的。\n\n让我们用一句话来总结一下JDK静态代理和JDK动态代理的区别，然后开始探究JDK动态代理的底层实现机制：  \nJDK静态代理是通过直接编码创建的，而JDK动态代理是利用反射机制在运行时创建代理类的。  \n其实在动态代理中，核心是InvocationHandler。每一个代理的实例都会有一个关联的调用处理程序(InvocationHandler)。对待代理实例进行调用时，将对方法的调用进行编码并指派到它的调用处理器(InvocationHandler)的invoke方法。所以对代理对象实例方法的调用都是通过InvocationHandler中的invoke方法来完成的，而invoke方法会根据传入的代理对象、方法名称以及参数决定调用代理的哪个方法。\n\n我们从JDK动态代理的测试类中可以发现代理类生成是通过Proxy类中的newProxyInstance来完成的，下面我们进入这个函数看一看：\n\n### Proxy类中的newProxyInstance\n\n```  \n public static Object newProxyInstance(ClassLoader loader,                                          Class<?>[] interfaces,                                          InvocationHandler h)        throws IllegalArgumentException    {        //如果h为空将抛出异常  \n        Objects.requireNonNull(h);  \n        final Class<?>[] intfs = interfaces.clone();//拷贝被代理类实现的一些接口，用于后面权限方面的一些检查  \n        final SecurityManager sm = System.getSecurityManager();        if (sm != null) {            //在这里对某些安全权限进行检查，确保我们有权限对预期的被代理类进行代理  \n            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);        }  \n        /*         * 下面这个方法将产生代理类  \n         */        Class<?> cl = getProxyClass0(loader, intfs);  \n        /*         * 使用指定的调用处理程序获取代理类的构造函数对象  \n         */        try {            if (sm != null) {                checkNewProxyPermission(Reflection.getCallerClass(), cl);            }  \n            final Constructor<?> cons = cl.getConstructor(constructorParams);            final InvocationHandler ih = h;            //假如代理类的构造函数是private的，就使用反射来set accessible  \n            if (!Modifier.isPublic(cl.getModifiers())) {                AccessController.doPrivileged(new PrivilegedAction<Void>() {                    public Void run() {                        cons.setAccessible(true);                        return null;                    }                });            }            //根据代理类的构造函数来生成代理类的对象并返回  \n            return cons.newInstance(new Object[]{h});        } catch (IllegalAccessException|InstantiationException e) {            throw new InternalError(e.toString(), e);        } catch (InvocationTargetException e) {            Throwable t = e.getCause();            if (t instanceof RuntimeException) {                throw (RuntimeException) t;            } else {                throw new InternalError(t.toString(), t);            }        } catch (NoSuchMethodException e) {            throw new InternalError(e.toString(), e);        }    }  \n```  \n\n所以代理类其实是通过getProxyClass方法来生成的：\n\n```  \n /**     * 生成一个代理类，但是在调用本方法之前必须进行权限检查  \n     */    private static Class<?> getProxyClass0(ClassLoader loader,                                           Class<?>... interfaces) {        //如果接口数量大于65535，抛出非法参数错误  \n        if (interfaces.length > 65535) {            throw new IllegalArgumentException(\"interface limit exceeded\");        }  \n        // 如果在缓存中有对应的代理类，那么直接返回  \n        // 否则代理类将有 ProxyClassFactory 来创建  \n        return proxyClassCache.get(loader, interfaces);    }  \n```  \n\n那么ProxyClassFactory是什么呢？\n\n```  \n   /**     *  里面有一个根据给定ClassLoader和Interface来创建代理类的工厂函数    \n     *  \n     */    private static final class ProxyClassFactory        implements BiFunction<ClassLoader, Class<?>[], Class<?>>    {        // 代理类的名字的前缀统一为“$Proxy”  \n        private static final String proxyClassNamePrefix = \"$Proxy\";  \n        // 每个代理类前缀后面都会跟着一个唯一的编号，如$Proxy0、$Proxy1、$Proxy2  \n        private static final AtomicLong nextUniqueNumber = new AtomicLong();  \n        @Override        public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {  \n            Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);            for (Class<?> intf : interfaces) {                /*                 * 验证类加载器加载接口得到对象是否与由apply函数参数传入的对象相同  \n                 */                Class<?> interfaceClass = null;                try {                    interfaceClass = Class.forName(intf.getName(), false, loader);                } catch (ClassNotFoundException e) {                }                if (interfaceClass != intf) {                    throw new IllegalArgumentException(                        intf + \" is not visible from class loader\");                }                /*                 * 验证这个Class对象是不是接口  \n                 */                if (!interfaceClass.isInterface()) {                    throw new IllegalArgumentException(                        interfaceClass.getName() + \" is not an interface\");                }                /*                 * 验证这个接口是否重复  \n                 */                if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {                    throw new IllegalArgumentException(                        \"repeated interface: \" + interfaceClass.getName());                }            }  \n            String proxyPkg = null;     // 声明代理类所在的package  \n            int accessFlags = Modifier.PUBLIC | Modifier.FINAL;  \n            /*             * 记录一个非公共代理接口的包，以便在同一个包中定义代理类。同时验证所有非公共  \n             * 代理接口都在同一个包中  \n             */            for (Class<?> intf : interfaces) {                int flags = intf.getModifiers();                if (!Modifier.isPublic(flags)) {                    accessFlags = Modifier.FINAL;                    String name = intf.getName();                    int n = name.lastIndexOf('.');                    String pkg = ((n == -1) ? \"\" : name.substring(0, n + 1));                    if (proxyPkg == null) {                        proxyPkg = pkg;                    } else if (!pkg.equals(proxyPkg)) {                        throw new IllegalArgumentException(                            \"non-public interfaces from different packages\");                    }                }            }  \n            if (proxyPkg == null) {                // 如果全是公共代理接口，那么生成的代理类就在com.sun.proxy package下  \n                proxyPkg = ReflectUtil.PROXY_PACKAGE + \".\";            }  \n            /*             * 为代理类生成一个name  package name + 前缀+唯一编号  \n             * 如 com.sun.proxy.$Proxy0.class             */            long num = nextUniqueNumber.getAndIncrement();            String proxyName = proxyPkg + proxyClassNamePrefix + num;  \n            /*             * 生成指定代理类的字节码文件  \n             */            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(                proxyName, interfaces, accessFlags);            try {                return defineClass0(loader, proxyName,                                    proxyClassFile, 0, proxyClassFile.length);            } catch (ClassFormatError e) {                /*                 * A ClassFormatError here means that (barring bugs in the                 * proxy class generation code) there was some other                 * invalid aspect of the arguments supplied to the proxy                 * class creation (such as virtual machine limitations                 * exceeded).                 */                throw new IllegalArgumentException(e.toString());            }        }    }  \n```  \n\n### 字节码生成\n\n由上方代码byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);可以看到，其实生成代理类字节码文件的工作是通过 ProxyGenerate类中的generateProxyClass方法来完成的。\n\n```  \n public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {        ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);       // 真正用来生成代理类字节码文件的方法在这里  \n        final byte[] var4 = var3.generateClassFile();       // 保存代理类的字节码文件  \n        if(saveGeneratedFiles) {            AccessController.doPrivileged(new PrivilegedAction() {                public Void run() {                    try {                        int var1 = var0.lastIndexOf(46);                        Path var2;                        if(var1 > 0) {                            Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar),                                                                                   new String[0]);  \n                            Files.createDirectories(var3, new FileAttribute[0]);                            var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + \".class\");                        } else {                            var2 = Paths.get(var0 + \".class\", new String[0]);                        }  \n                        Files.write(var2, var4, new OpenOption[0]);                        return null;                    } catch (IOException var4x) {                        throw new InternalError(\"I/O exception saving generated file: \" + var4x);                    }                }            });        }  \n        return var4;    }  \n```  \n\n下面来看看真正用于生成代理类字节码文件的generateClassFile方法：\n\n```  \nprivate byte[] generateClassFile() {  \n        //下面一系列的addProxyMethod方法是将接口中的方法和Object中的方法添加到代理方法中(proxyMethod)  \n        this.addProxyMethod(hashCodeMethod, Object.class);        this.addProxyMethod(equalsMethod, Object.class);        this.addProxyMethod(toStringMethod, Object.class);        Class[] var1 = this.interfaces;        int var2 = var1.length;  \n        int var3;        Class var4;       //获得接口中所有方法并添加到代理方法中  \n        for(var3 = 0; var3 < var2; ++var3) {            var4 = var1[var3];            Method[] var5 = var4.getMethods();            int var6 = var5.length;  \n            for(int var7 = 0; var7 < var6; ++var7) {                Method var8 = var5[var7];                this.addProxyMethod(var8, var4);            }        }  \n        Iterator var11 = this.proxyMethods.values().iterator();        //验证具有相同方法签名的方法的返回类型是否一致  \n        List var12;        while(var11.hasNext()) {            var12 = (List)var11.next();            checkReturnTypes(var12);        }  \n        //后面一系列的步骤用于写代理类Class文件  \n        Iterator var15;        try {             //生成代理类的构造函数  \n            this.methods.add(this.generateConstructor());            var11 = this.proxyMethods.values().iterator();  \n            while(var11.hasNext()) {                var12 = (List)var11.next();                var15 = var12.iterator();  \n                while(var15.hasNext()) {                    ProxyGenerator.ProxyMethod var16 = (ProxyGenerator.ProxyMethod)var15.next();                    //将代理类字段声明为Method，并且字段修饰符为 private static.                   //因为 10 是 ACC_PRIVATE和ACC_STATIC的与运算 故代理类的字段都是 private static Method ***                    this.fields.add(new ProxyGenerator.FieldInfo(var16.methodFieldName,                                   \"Ljava/lang/reflect/Method;\", 10));  \n                   //生成代理类的方法  \n                    this.methods.add(var16.generateMethod());                }            }           //为代理类生成静态代码块对某些字段进行初始化  \n            this.methods.add(this.generateStaticInitializer());        } catch (IOException var10) {            throw new InternalError(\"unexpected I/O Exception\", var10);        }  \n        if(this.methods.size() > '\\uffff') { //代理类中的方法数量超过65535就抛异常  \n            throw new IllegalArgumentException(\"method limit exceeded\");        } else if(this.fields.size() > '\\uffff') {// 代理类中字段数量超过65535也抛异常  \n            throw new IllegalArgumentException(\"field limit exceeded\");        } else {            // 后面是对文件进行处理的过程  \n            this.cp.getClass(dotToSlash(this.className));            this.cp.getClass(\"java/lang/reflect/Proxy\");            var1 = this.interfaces;            var2 = var1.length;  \n            for(var3 = 0; var3 < var2; ++var3) {                var4 = var1[var3];                this.cp.getClass(dotToSlash(var4.getName()));            }  \n            this.cp.setReadOnly();            ByteArrayOutputStream var13 = new ByteArrayOutputStream();            DataOutputStream var14 = new DataOutputStream(var13);  \n            try {                var14.writeInt(-889275714);                var14.writeShort(0);                var14.writeShort(49);                this.cp.write(var14);                var14.writeShort(this.accessFlags);                var14.writeShort(this.cp.getClass(dotToSlash(this.className)));                var14.writeShort(this.cp.getClass(\"java/lang/reflect/Proxy\"));                var14.writeShort(this.interfaces.length);                Class[] var17 = this.interfaces;                int var18 = var17.length;  \n                for(int var19 = 0; var19 < var18; ++var19) {                    Class var22 = var17[var19];                    var14.writeShort(this.cp.getClass(dotToSlash(var22.getName())));                }  \n                var14.writeShort(this.fields.size());                var15 = this.fields.iterator();  \n                while(var15.hasNext()) {                    ProxyGenerator.FieldInfo var20 = (ProxyGenerator.FieldInfo)var15.next();                    var20.write(var14);                }  \n                var14.writeShort(this.methods.size());                var15 = this.methods.iterator();  \n                while(var15.hasNext()) {                    ProxyGenerator.MethodInfo var21 = (ProxyGenerator.MethodInfo)var15.next();                    var21.write(var14);                }  \n                var14.writeShort(0);                return var13.toByteArray();            } catch (IOException var9) {                throw new InternalError(\"unexpected I/O Exception\", var9);            }        }    }  \n```  \n\n## 代理类的方法调用\n\n下面是将接口与Object中一些方法添加到代理类中的addProxyMethod方法：\n\n```  \nprivate void addProxyMethod(Method var1, Class<?> var2) {  \n        String var3 = var1.getName();//获得方法名称  \n        Class[] var4 = var1.getParameterTypes();//获得方法参数类型  \n        Class var5 = var1.getReturnType();//获得方法返回类型  \n        Class[] var6 = var1.getExceptionTypes();//异常类型  \n        String var7 = var3 + getParameterDescriptors(var4);//获得方法签名  \n        Object var8 = (List)this.proxyMethods.get(var7);//根据方法前面获得proxyMethod的value  \n        if(var8 != null) {//处理多个代理接口中方法重复的情况  \n            Iterator var9 = ((List)var8).iterator();  \n            while(var9.hasNext()) {                ProxyGenerator.ProxyMethod var10 = (ProxyGenerator.ProxyMethod)var9.next();                if(var5 == var10.returnType) {                    ArrayList var11 = new ArrayList();                    collectCompatibleTypes(var6, var10.exceptionTypes, var11);                    collectCompatibleTypes(var10.exceptionTypes, var6, var11);                    var10.exceptionTypes = new Class[var11.size()];                    var10.exceptionTypes = (Class[])var11.toArray(var10.exceptionTypes);                    return;                }            }        } else {            var8 = new ArrayList(3);            this.proxyMethods.put(var7, var8);        }  \n        ((List)var8).add(new ProxyGenerator.ProxyMethod(var3, var4, var5, var6, var2, null));    }```  \n  \n这就是最终真正的代理类，它继承自Proxy并实现了我们定义的Subject接口。我们通过  \n  \n```  \n````\nHelloInterface helloInterface = (HelloInterface ) Proxy.newProxyInstance(loader, interfaces, handler);\n````  \n  \n*   1  \n  \n得到的最终代理类对象就是上面这个类的实例。那么我们执行如下语句：  \n  \n```  \nhelloInterface.hello(\"Tom\");\n```  \n  \n*   1  \n  \n实际上就是执行上面类的相应方法，也就是：  \n  \n```  \npublic final void hello(String paramString)  {    try    {      this.h.invoke(this, m3, new Object[] { paramString });      //就是调用我们自定义的InvocationHandlerImpl的 invoke方法：  \nreturn;    }    catch (Error|RuntimeException localError)    {      throw localError;    }    catch (Throwable localThrowable)    {      throw new UndeclaredThrowableException(localThrowable);    }  }```\n\n注意这里的`this.h.invoke`中的h，它是类Proxy中的一个属性\n\n```  \n protected InvocationHandler h;```  \n  \n因为这个代理类继承了Proxy，所以也就继承了这个属性，而这个属性值就是我们定义的  \n  \n```  \nInvocationHandler handler = new InvocationHandlerImpl(hello);\n```  \n  \n*   1  \n  \n同时我们还发现，invoke方法的第一参数在底层调用的时候传入的是`this`，也就是最终生成的代理对象ProxySubject，这是JVM自己动态生成的，而不是我们自己定义的代理对象。  \n  \n## 深入理解CGLIB动态代理机制  \n  \nCglib是什么  \n  \nCglib是一个强大的、高性能的代码生成包，它广泛被许多AOP框架使用，为他们提供方法的拦截。下图是我网上找到的一张Cglib与一些框架和语言的关系：  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/801753-20170403122105941-1116862243.gif)  \n  \n对此图总结一下：  \n  \n*   最底层的是字节码Bytecode，字节码是Java为了保证“一次编译、到处运行”而产生的一种虚拟指令格式，例如iload_0、iconst_1、if_icmpne、dup等  \n*   位于字节码之上的是ASM，这是一种直接操作字节码的框架，应用ASM需要对Java字节码、Class结构比较熟悉  \n*   位于ASM之上的是CGLIB、Groovy、BeanShell，后两种并不是Java体系中的内容而是脚本语言，它们通过ASM框架生成字节码变相执行Java代码，这说明在JVM中执行程序并不一定非要写Java代码----只要你能生成Java字节码，JVM并不关心字节码的来源，当然通过Java代码生成的JVM字节码是通过编译器直接生成的，算是最“正统”的JVM字节码  \n*   位于CGLIB、Groovy、BeanShell之上的就是Hibernate、Spring AOP这些框架了，这一层大家都比较熟悉  \n*   最上层的是Applications，即具体应用，一般都是一个Web项目或者本地跑一个程序  \n  \n本文是基于CGLIB 3.1进行探究的  \n  \ncglib is a powerful, high performance and quality Code Generation Library, It is used to extend JAVA classes and implements interfaces at runtime.  \n  \n在Spring AOP中，通常会用它来生成AopProxy对象。不仅如此，在Hibernate中PO(Persistant Object 持久化对象)字节码的生成工作也要靠它来完成。  \n  \n本文将深入探究CGLIB动态代理的实现机制，配合下面这篇文章一起食用口味更佳：  \n[深入理解JDK动态代理机制](https://www.jianshu.com/p/471c80a7e831)  \n  \n### CGLIB动态代理示例  \n  \n下面由一个简单的示例开始我们对CGLIB动态代理的介绍：  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825140731697-1665999459.png)  \n  \n为了后续编码的顺利进行，我们需要使用Maven引入CGLIB的包  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825140732067-1529903146.png)  \n  \n图1.1 被代理类  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825140732309-293903217.png)  \n  \n图1.2 实现MethodInterceptor接口生成方法拦截器  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825140732746-1527907182.png)  \n  \n图1.3 生成代理类对象并打印在代理类对象调用方法之后的执行结果  \n  \nJDK代理要求被代理的类必须实现接口，有很强的局限性。而CGLIB动态代理则没有此类强制性要求。简单的说，CGLIB会让生成的代理类继承被代理类，并在代理类中对代理方法进行强化处理(前置处理、后置处理等)。在CGLIB底层，其实是借助了ASM这个非常强大的Java字节码生成框架。  \n  \n### 生成代理类对象  \n  \n从图1.3中我们看到，代理类对象是由Enhancer类创建的。Enhancer是CGLIB的字节码增强器，可以很方便的对类进行拓展，如图1.3中的为类设置Superclass。  \n  \n创建代理对象的几个步骤:  \n  \n*   生成代理类的二进制字节码文件；  \n*   加载二进制字节码，生成Class对象( 例如使用Class.forName()方法 )；  \n*   通过反射机制获得实例构造，并创建代理类对象  \n  \n我们来看看将代理类Class文件反编译之后的Java代码  \n  \n````\npackage proxy;\n\nimport java.lang.reflect.Method;  \nimport net.sf.cglib.core.ReflectUtils;  \nimport net.sf.cglib.core.Signature;  \nimport net.sf.cglib.proxy.Callback;  \nimport net.sf.cglib.proxy.Factory;  \nimport net.sf.cglib.proxy.MethodInterceptor;  \nimport net.sf.cglib.proxy.MethodProxy;\n\npublic class HelloServiceImpl$EnhancerByCGLIB$82ef2d06  \nextends HelloServiceImpl  implements Factory{  \nprivate boolean CGLIB$BOUND;  private static final ThreadLocal CGLIB$THREAD_CALLBACKS;  private static final Callback[] CGLIB$STATIC_CALLBACKS;  private MethodInterceptor CGLIB$CALLBACK_0;  private static final Method CGLIB$sayHello$0$Method;  private static final MethodProxy CGLIB$sayHello$0$Proxy;  private static final Object[] CGLIB$emptyArgs;  private static final Method CGLIB$finalize$1$Method;  private static final MethodProxy CGLIB$finalize$1$Proxy;  private static final Method CGLIB$equals$2$Method;  private static final MethodProxy CGLIB$equals$2$Proxy;  private static final Method CGLIB$toString$3$Method;  private static final MethodProxy CGLIB$toString$3$Proxy;  private static final Method CGLIB$hashCode$4$Method;  private static final MethodProxy CGLIB$hashCode$4$Proxy;  private static final Method CGLIB$clone$5$Method;  private static final MethodProxy CGLIB$clone$5$Proxy;  \nstatic void CGLIB$STATICHOOK1()  {    CGLIB$THREAD_CALLBACKS = new ThreadLocal();    CGLIB$emptyArgs = new Object[0];    Class localClass1 = Class.forName(\"proxy.HelloServiceImpl$EnhancerByCGLIB$82ef2d06\");    Class localClass2;    Method[] tmp95_92 = ReflectUtils.findMethods(new String[] { \"finalize\", \"()V\", \"equals\", \"(Ljava/lang/Object;)Z\", \"toString\", \"()Ljava/lang/String;\", \"hashCode\", \"()I\", \"clone\", \"()Ljava/lang/Object;\" }, (localClass2 = Class.forName(\"java.lang.Object\")).getDeclaredMethods());    CGLIB$finalize$1$Method = tmp95_92[0];    CGLIB$finalize$1$Proxy = MethodProxy.create(localClass2, localClass1, \"()V\", \"finalize\", \"CGLIB$finalize$1\");    Method[] tmp115_95 = tmp95_92;    CGLIB$equals$2$Method = tmp115_95[1];    CGLIB$equals$2$Proxy = MethodProxy.create(localClass2, localClass1, \"(Ljava/lang/Object;)Z\", \"equals\", \"CGLIB$equals$2\");    Method[] tmp135_115 = tmp115_95;    CGLIB$toString$3$Method = tmp135_115[2];    CGLIB$toString$3$Proxy = MethodProxy.create(localClass2, localClass1, \"()Ljava/lang/String;\", \"toString\", \"CGLIB$toString$3\");    Method[] tmp155_135 = tmp135_115;    CGLIB$hashCode$4$Method = tmp155_135[3];    CGLIB$hashCode$4$Proxy = MethodProxy.create(localClass2, localClass1, \"()I\", \"hashCode\", \"CGLIB$hashCode$4\");    Method[] tmp175_155 = tmp155_135;    CGLIB$clone$5$Method = tmp175_155[4];    CGLIB$clone$5$Proxy = MethodProxy.create(localClass2, localClass1, \"()Ljava/lang/Object;\", \"clone\", \"CGLIB$clone$5\");    tmp175_155;    Method[] tmp223_220 = ReflectUtils.findMethods(new String[] { \"sayHello\", \"()V\" }, (localClass2 = Class.forName(\"proxy.HelloServiceImpl\")).getDeclaredMethods());    CGLIB$sayHello$0$Method = tmp223_220[0];    CGLIB$sayHello$0$Proxy = MethodProxy.create(localClass2, localClass1, \"()V\", \"sayHello\", \"CGLIB$sayHello$0\");    tmp223_220;    return;  }  \nfinal void CGLIB$sayHello$0()  {    super.sayHello();  }  \npublic final void sayHello()  {    MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;    if (tmp4_1 == null)    {      tmp4_1;      CGLIB$BIND_CALLBACKS(this);    }    if (this.CGLIB$CALLBACK_0 != null) {      return;    }    super.sayHello();  }  \nfinal void CGLIB$finalize$1()    throws Throwable  {    super.finalize();  }  \nprotected final void finalize()    throws Throwable  {    MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;    if (tmp4_1 == null)    {      tmp4_1;      CGLIB$BIND_CALLBACKS(this);    }    if (this.CGLIB$CALLBACK_0 != null) {      return;    }    super.finalize();  }  \nfinal boolean CGLIB$equals$2(Object paramObject)  {    return super.equals(paramObject);  }  \npublic final boolean equals(Object paramObject)  {    MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;    if (tmp4_1 == null)    {      tmp4_1;      CGLIB$BIND_CALLBACKS(this);    }    MethodInterceptor tmp17_14 = this.CGLIB$CALLBACK_0;    if (tmp17_14 != null)    {      Object tmp41_36 = tmp17_14.intercept(this, CGLIB$equals$2$Method, new Object[] { paramObject }, CGLIB$equals$2$Proxy);      tmp41_36;      return tmp41_36 == null ? false : ((Boolean)tmp41_36).booleanValue();    }    return super.equals(paramObject);  }  \nfinal String CGLIB$toString$3()  {    return super.toString();  }  \npublic final String toString()  {    MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;    if (tmp4_1 == null)    {      tmp4_1;      CGLIB$BIND_CALLBACKS(this);    }    MethodInterceptor tmp17_14 = this.CGLIB$CALLBACK_0;    if (tmp17_14 != null) {      return (String)tmp17_14.intercept(this, CGLIB$toString$3$Method, CGLIB$emptyArgs, CGLIB$toString$3$Proxy);    }    return super.toString();  }  \nfinal int CGLIB$hashCode$4()  {    return super.hashCode();  }  \npublic final int hashCode()  {    MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;    if (tmp4_1 == null)    {      tmp4_1;      CGLIB$BIND_CALLBACKS(this);    }    MethodInterceptor tmp17_14 = this.CGLIB$CALLBACK_0;    if (tmp17_14 != null)    {      Object tmp36_31 = tmp17_14.intercept(this, CGLIB$hashCode$4$Method, CGLIB$emptyArgs, CGLIB$hashCode$4$Proxy);      tmp36_31;      return tmp36_31 == null ? 0 : ((Number)tmp36_31).intValue();    }    return super.hashCode();  }  \nfinal Object CGLIB$clone$5()    throws CloneNotSupportedException  {    return super.clone();  }  \nprotected final Object clone()    throws CloneNotSupportedException  {    MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;    if (tmp4_1 == null)    {      tmp4_1;      CGLIB$BIND_CALLBACKS(this);    }    MethodInterceptor tmp17_14 = this.CGLIB$CALLBACK_0;    if (tmp17_14 != null) {      return tmp17_14.intercept(this, CGLIB$clone$5$Method, CGLIB$emptyArgs, CGLIB$clone$5$Proxy);    }    return super.clone();  }  \npublic static MethodProxy CGLIB$findMethodProxy(Signature paramSignature)  {    String tmp4_1 = paramSignature.toString();    switch (tmp4_1.hashCode())    {    case -1574182249:      if (tmp4_1.equals(\"finalize()V\")) {  \nreturn CGLIB$finalize$1$Proxy;      }      break;    }  }  \npublic HelloServiceImpl$EnhancerByCGLIB$82ef2d06()  {    CGLIB$BIND_CALLBACKS(this);  }  \npublic static void CGLIB$SET_THREAD_CALLBACKS(Callback[] paramArrayOfCallback)  {    CGLIB$THREAD_CALLBACKS.set(paramArrayOfCallback);  }  \npublic static void CGLIB$SET_STATIC_CALLBACKS(Callback[] paramArrayOfCallback)  {    CGLIB$STATIC_CALLBACKS = paramArrayOfCallback;  }  \nprivate static final void CGLIB$BIND_CALLBACKS(Object paramObject)  {    82ef2d06 local82ef2d06 = (82ef2d06)paramObject;    if (!local82ef2d06.CGLIB$BOUND)    {      local82ef2d06.CGLIB$BOUND = true;      Object tmp23_20 = CGLIB$THREAD_CALLBACKS.get();      if (tmp23_20 == null)      {        tmp23_20;        CGLIB$STATIC_CALLBACKS;      }      local82ef2d06.CGLIB$CALLBACK_0 = (// INTERNAL ERROR //\n````\n  \n### 对委托类进行代理  \n  \n我们上面贴出了生成的代理类源码。以我们上面的例子为参考，下面我们总结一下CGLIB在进行代理的时候都进行了哪些工作呢  \n  \n*   生成的代理类HelloServiceImpl$EnhancerByCGLIB$82ef2d06继承被代理类HelloServiceImpl。在这里我们需要注意一点：如果委托类被final修饰，那么它不可被继承，即不可被代理；同样，如果委托类中存在final修饰的方法，那么该方法也不可被代理；  \n*   代理类会为委托方法生成两个方法，一个是重写的sayHello方法，另一个是CGLIB$sayHello$0方法，我们可以看到它是直接调用父类的sayHello方法；  \n*   当执行代理对象的sayHello方法时，会首先判断一下是否存在实现了MethodInterceptor接口的CGLIB$CALLBACK_0;，如果存在，则将调用MethodInterceptor中的intercept方法，如图2.1。  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825140733103-2101709172.png)  \n  \n图2.1 intercept方法  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825140733381-1955520660.png)  \n  \n图2.2 代理类为每个委托方法都会生成两个方法  \n  \n在intercept方法中，我们除了会调用委托方法，还会进行一些增强操作。在Spring AOP中，典型的应用场景就是在某些敏感方法执行前后进行操作日志记录。  \n  \n我们从图2.1中看到，调用委托方法是通过代理方法的MethodProxy对象调用invokeSuper方法来执行的，下面我们看看invokeSuper方法中的玄机：  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825140733825-816756585.png)  \n  \n图2.3 invokeSuper方法  \n  \n在这里好像不能直接看出代理方法的调用。没关系，我会慢慢介绍。  \n我们知道，在JDK动态代理中方法的调用是通过反射来完成的。如果有对此不太了解的同学，可以看下我之前的博客----[深入理解JDK动态代理机制](https://www.jianshu.com/p/471c80a7e831)。但是在CGLIB中，方法的调用并不是通过反射来完成的，而是直接对方法进行调用：FastClass对Class对象进行特别的处理，比如将会用数组保存method的引用，每次调用方法的时候都是通过一个index下标来保持对方法的引用。比如下面的getIndex方法就是通过方法签名来获得方法在存储了Class信息的数组中的下标。  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825140734172-226519980.png)  \n  \n图2.4 getIndex方法  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825140734361-1715137603.png)  \n  \n图2.5 FastClassInfo类中持有两个FastClass对象的引用.png  \n  \n以我们上面的sayHello方法为例，f1指向委托类对象，f2指向代理类对象，i1和i2分别代表着sayHello方法以及CGLIB$sayHello$0方法在对象信息数组中的下标。  \n  \n到此为止CGLIB动态代理机制就介绍完了，下面给出三种代理方式之间对比。  \n\n\n| 代理方式 | 实现 | 优点 | 缺点 | 特点 | \n| --- | --- | --- | --- | --- |  \n| JDK静态代理 | 代理类与委托类实现同一接口，并且在代理类中需要硬编码接口 | 实现简单，容易理解 | 代理类需要硬编码接口，在实际应用中可能会导致重复编码，浪费存储空间并且效率很低 | 好像没啥特点 |  \n| JDK动态代理 | 代理类与委托类实现同一接口，主要是通过代理类实现InvocationHandler并重写invoke方法来进行动态代理的，在invoke方法中将对方法进行增强处理 | 不需要硬编码接口，代码复用率高 | 只能够代理实现了接口的委托类 | 底层使用反射机制进行方法的调用 |  \n| CGLIB动态代理 | 代理类将委托类作为自己的父类并为其中的非final委托方法创建两个方法，一个是与委托方法签名相同的方法，它在方法中会通过super调用委托方法；另一个是代理类独有的方法。在代理方法中，它会判断是否存在实现了MethodInterceptor接口的对象，若存在则将调用intercept方法对委托方法进行代理 | 可以在运行时对类或者是接口进行增强操作，且委托类无需实现接口 | 不能对final类以及final方法进行代理 | 底层将方法全部存入一个数组中，通过数组索引直接进行方法调用 |  \n  \n  \n## 微信公众号  \n  \n### 个人公众号：黄小斜  \n  \n黄小斜是跨考软件工程的 985 硕士，自学 Java 两年，拿到了 BAT 等近十家大厂 offer，从技术小白成长为阿里工程师。  \n  \n作者专注于 JAVA 后端技术栈，热衷于分享程序员干货、学习经验、求职心得和程序人生，目前黄小斜的CSDN博客有百万+访问量，知乎粉丝2W+，全网已有10W+读者。  \n  \n黄小斜是一个斜杠青年，坚持学习和写作，相信终身学习的力量，希望和更多的程序员交朋友，一起进步和成长！  \n  \n**原创电子书:**  \n关注公众号【黄小斜】后回复【原创电子书】即可领取我原创的电子书《菜鸟程序员修炼手册：从技术小白到阿里巴巴Java工程师》  \n  \n**程序员3T技术学习资源：** 一些程序员学习技术的资源大礼包，关注公众号后，后台回复关键字 **“资料”** 即可免费无套路获取。  \n  \n**考研复习资料：**  \n计算机考研大礼包，都是我自己考研复习时用的一些复习资料,包括公共课和专业的复习视频，这里也推荐给大家，关注公众号后，后台回复关键字 **“考研”** 即可免费获取。  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20190829222750556.jpg)  \n  \n  \n### 技术公众号：Java技术江湖  \n  \n如果大家想要实时关注我更新的文章以及分享的干货的话，可以关注我的公众号【Java技术江湖】一位阿里 Java 工程师的技术小站，作者黄小斜，专注 Java 相关技术：SSM、SpringBoot、MySQL、分布式、中间件、集群、Linux、网络、多线程，偶尔讲点Docker、ELK，同时也分享技术干货和学习经验，致力于Java全栈开发！  \n  \n**Java工程师必备学习资源:** 一些Java工程师常用学习资源，关注公众号后，后台回复关键字 **“Java”** 即可免费无套路获取。  \n  \n![我的公众号](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20190805090108984.jpg)"
  },
  {
    "path": "docs/Spring全家桶/Spring源码分析/Spring源码剖析：SpringAOP概述.md",
    "content": "# 目录\n  * [我们为什么要使用 AOP](#我们为什么要使用-aop)\n  * [使用装饰器模式](#使用装饰器模式)\n  * [使用代理模式](#使用代理模式)\n  * [使用CGLIB](#使用cglib)\n  * [使用AOP](#使用aop)\n  * [AOP总结](#aop总结)\n\n\n本文转自五月的仓颉 https://www.cnblogs.com/xrq730\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章将同步到我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《Spring和SpringMVC源码分析》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从spring基础入手，一步步地学习spring基础和springmvc的框架知识，并上手进行项目实战，spring框架是每一个Java工程师必须要学习和理解的知识点，进一步来说，你还需要掌握spring甚至是springmvc的源码以及实现原理，才能更完整地了解整个spring技术体系，形成自己的知识框架。\n\n后续还会有springboot和springcloud的技术专题，陆续为大家带来，敬请期待。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供部分知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n## 我们为什么要使用 AOP\n\n\n一年半前写了一篇文章Spring3：AOP，是当时学习如何使用Spring AOP的时候写的，比较基础。这篇文章最后的推荐以及回复认为我写的对大家有帮助的评论有很多，但是现在从我个人的角度来看，这篇文章写得并不好，甚至可以说是没有太多实质性的内容，因此这些推荐和评论让我觉得受之有愧。\n\n基于以上原因，更新一篇文章，从最基础的原始代码–>使用设计模式（装饰器模式与代理）–>使用AOP三个层次来讲解一下为什么我们要使用AOP，希望这篇文章可以对网友朋友们有益。\n\n原始代码的写法\n既然要通过代码来演示，那必须要有例子，这里我的例子为：\n\n\n有一个接口Dao有insert、delete、update三个方法，在insert与update被调用的前后，打印调用前的毫秒数与调用后的毫秒数\n首先定义一个Dao接口：\n\n```\n/**\n * @author 五月的仓颉http://www.cnblogs.com/xrq730/p/7003082.html\n */\npublic interface Dao {\n\n    public void insert();\n\n    public void delete();\n\n    public void update();\n\n}\n```\n\n然后定义一个实现类DaoImpl：\n\n```\n/**\n * @author 五月的仓颉http://www.cnblogs.com/xrq730/p/7003082.html\n */\npublic class DaoImpl implements Dao {\n\n    @Override\n    public void insert() {\n        System.out.println(\"DaoImpl.insert()\");\n    }\n\n    @Override\n    public void delete() {\n        System.out.println(\"DaoImpl.delete()\");\n    }\n\n    @Override\n    public void update() {\n        System.out.println(\"DaoImpl.update()\");\n    }\n\n}\n```\n\n最原始的写法，我要在调用insert()与update()方法前后分别打印时间，就只能定义一个新的类包一层，在调用insert()方法与update()方法前后分别处理一下，新的类我命名为ServiceImpl，其实现为：\n\n```\n/**\n * @author 五月的仓颉http://www.cnblogs.com/xrq730/p/7003082.html\n */\npublic class ServiceImpl {\n\n    private Dao dao = new DaoImpl();\n\n    public void insert() {\n        System.out.println(\"insert()方法开始时间：\" + System.currentTimeMillis());\n        dao.insert();\n        System.out.println(\"insert()方法结束时间：\" + System.currentTimeMillis());\n    }\n\n    public void delete() {\n        dao.delete();\n    }\n\n    public void update() {\n        System.out.println(\"update()方法开始时间：\" + System.currentTimeMillis());\n        dao.update();\n        System.out.println(\"update()方法结束时间：\" + System.currentTimeMillis());\n    }\n\n}\n```\n\n这是最原始的写法，这种写法的缺点也是一目了然：\n\n方法调用前后输出时间的逻辑无法复用，如果有别的地方要增加这段逻辑就得再写一遍\n\n如果Dao有其它实现类，那么必须新增一个类去包装该实现类，这将导致类数量不断膨胀\n\n## 使用装饰器模式\n接着我们使用上设计模式，先用装饰器模式，看看能解决多少问题。装饰器模式的核心就是实现Dao接口并持有Dao接口的引用，我将新增的类命名为LogDao，其实现为：\n\n```\n/**\n * @author 五月的仓颉http://www.cnblogs.com/xrq730/p/7003082.html\n */\npublic class LogDao implements Dao {\n\n    private Dao dao;\n\n    public LogDao(Dao dao) {\n        this.dao = dao;\n    }\n\n    @Override\n    public void insert() {\n        System.out.println(\"insert()方法开始时间：\" + System.currentTimeMillis());\n        dao.insert();\n        System.out.println(\"insert()方法结束时间：\" + System.currentTimeMillis());\n    }\n\n    @Override\n    public void delete() {\n        dao.delete();\n    }\n\n    @Override\n    public void update() {\n        System.out.println(\"update()方法开始时间：\" + System.currentTimeMillis());\n        dao.update();\n        System.out.println(\"update()方法结束时间：\" + System.currentTimeMillis());\n    }\n\n}\n```\n\n在使用的时候，可以使用”Dao dao = new LogDao(new DaoImpl())”的方式，这种方式的优点为：\n\n透明，对调用方来说，它只知道Dao，而不知道加上了日志功能\n类不会无限膨胀，如果Dao的其它实现类需要输出日志，只需要向LogDao的构造函数中传入不同的Dao实现类即可\n不过这种方式同样有明显的缺点，缺点为：\n\n输出日志的逻辑还是无法复用\n输出日志的逻辑与代码有耦合，如果我要对delete()方法前后同样输出时间，需要修改LogDao\n但是，这种做法相比最原始的代码写法，已经有了很大的改进。\n\n## 使用代理模式\n接着我们使用代理模式尝试去实现最原始的功能，使用代理模式，那么我们就要定义一个InvocationHandler，我将它命名为LogInvocationHandler，其实现为：\n\n```\n/**\n * @author 五月的仓颉http://www.cnblogs.com/xrq730/p/7003082.html\n */\npublic class LogInvocationHandler implements InvocationHandler {\n\n    private Object obj;\n\n    public LogInvocationHandler(Object obj) {\n        this.obj = obj;\n    }\n\n    @Override\n    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {\n        String methodName = method.getName();\n        if (\"insert\".equals(methodName) || \"update\".equals(methodName)) {\n            System.out.println(methodName + \"()方法开始时间：\" + System.currentTimeMillis());\n            Object result = method.invoke(obj, args);\n            System.out.println(methodName + \"()方法结束时间：\" + System.currentTimeMillis());\n\n            return result;\n        }\n\n        return method.invoke(obj, args);\n    }\n\n}\n```\n\n其调用方式很简单，我写一个main函数：\n\n```\n/**\n * @author 五月的仓颉http://www.cnblogs.com/xrq730/p/7003082.html\n */\npublic static void main(String[] args) {\n    Dao dao = new DaoImpl();\n\n    Dao proxyDao = (Dao)Proxy.newProxyInstance(LogInvocationHandler.class.getClassLoader(), new Class<?>[]{Dao.class}, new LogInvocationHandler(dao));\n\n    proxyDao.insert();\n    System.out.println(\"----------分割线----------\");\n    proxyDao.delete();\n    System.out.println(\"----------分割线----------\");\n    proxyDao.update();\n}\n```\n\n结果就不演示了，这种方式的优点为：\n\n输出日志的逻辑被复用起来，如果要针对其他接口用上输出日志的逻辑，只要在newProxyInstance的时候的第二个参数增加Class<?>数组中的内容即可\n\n这种方式的缺点为：\n\nJDK提供的动态代理只能针对接口做代理，不能针对类做代理\n代码依然有耦合，如果要对delete方法调用前后打印时间，得在LogInvocationHandler中增加delete方法的判断\n\n## 使用CGLIB\n接着看一下使用CGLIB的方式，使用CGLIB只需要实现MethodInterceptor接口即可：\n\n```\n/**\n * @author 五月的仓颉http://www.cnblogs.com/xrq730/p/7003082.html\n */\npublic class DaoProxy implements MethodInterceptor {\n\n    @Override\n    public Object intercept(Object object, Method method, Object[] objects, MethodProxy proxy) throws Throwable {\n        String methodName = method.getName();\n\n        if (\"insert\".equals(methodName) || \"update\".equals(methodName)) {\n            System.out.println(methodName + \"()方法开始时间：\" + System.currentTimeMillis());\n            proxy.invokeSuper(object, objects);\n            System.out.println(methodName + \"()方法结束时间：\" + System.currentTimeMillis());\n\n            return object;\n        }\n\n        proxy.invokeSuper(object, objects);\n        return object;\n    }\n\n}\n```\n\n代码调用方式为：\n\n```\n/**\n * @author 五月的仓颉http://www.cnblogs.com/xrq730/p/7003082.html\n */\npublic static void main(String[] args) {\n    DaoProxy daoProxy = new DaoProxy();\n\n    Enhancer enhancer = new Enhancer();\n    enhancer.setSuperclass(DaoImpl.class);\n    enhancer.setCallback(daoProxy);\n\n    Dao dao = (DaoImpl)enhancer.create();\n    dao.insert();\n    System.out.println(\"----------分割线----------\");\n    dao.delete();\n    System.out.println(\"----------分割线----------\");\n    dao.update();\n}\n```\n\n使用CGLIB解决了JDK的Proxy无法针对类做代理的问题，但是这里要专门说明一个问题：使用装饰器模式可以说是对使用原生代码的一种改进，使用Java代理可以说是对于使用装饰器模式的一种改进，但是使用CGLIB并不是对于使用Java代理的一种改进。\n\n前面的可以说改进是因为使用装饰器模式比使用原生代码更好，使用Java代理又比使用装饰器模式更好，但是Java代理与CGLIb的对比并不能说改进，因为使用CGLIB并不一定比使用Java代理更好，这两种各有优缺点，像Spring框架就同时支持Java Proxy与CGLIB两种方式。\n\n从目前看来代码又更好了一些，但是我认为还有两个缺点：\n\n无论使用Java代理还是使用CGLIB，编写这部分代码都稍显麻烦\n代码之间的耦合还是没有解决，像要针对delete()方法加上这部分逻辑就必须修改代码\n\n## 使用AOP\n\n最后来看一下使用AOP的方式，首先定义一个时间处理类，我将它命名为TimeHandler：\n\n```\n/**\n * @author 五月的仓颉http://www.cnblogs.com/xrq730/p/7003082.html\n */\npublic class TimeHandler {\n\n    public void printTime(ProceedingJoinPoint pjp) {\n        Signature signature = pjp.getSignature();\n        if (signature instanceof MethodSignature) {\n            MethodSignature methodSignature = (MethodSignature)signature;\n            Method method = methodSignature.getMethod();\n            System.out.println(method.getName() + \"()方法开始时间：\" + System.currentTimeMillis());\n\n            try {\n                pjp.proceed();\n                System.out.println(method.getName() + \"()方法结束时间：\" + System.currentTimeMillis());\n            } catch (Throwable e) {\n\n            }\n        }\n    }\n\n}\n```\n\n到第8行的代码与第12行的代码分别打印方法开始执行时间与方法结束执行时间。我这里写得稍微复杂点，使用了的写法，其实也可以拆分为与两种，这个看个人喜好。\n\n这里多说一句，切面方法printTime本身可以不用定义任何的参数，但是有些场景下需要获取调用方法的类、方法签名等信息，此时可以在printTime方法中定义JointPoint，Spring会自动将参数注入，可以通过JoinPoint获取调用方法的类、方法签名等信息。由于这里我用的，要保证方法的调用，这样才能在方法调用前后输出时间，因此不能直接使用JoinPoint，因为JoinPoint没法保证方法调用。此时可以使用ProceedingJoinPoint，ProceedingPointPoint的proceed()方法可以保证方法调用，但是要注意一点，ProceedingJoinPoint只能和搭配，换句话说，如果aop.xml中配置的是，然后printTime的方法参数又是ProceedingJoinPoint的话，Spring容器启动将报错。\n\n接着看一下aop.xml的配置：\n\n```\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<beans xmlns=\"http://www.springframework.org/schema/beans\"\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xmlns:aop=\"http://www.springframework.org/schema/aop\"\n    xmlns:tx=\"http://www.springframework.org/schema/tx\"\n    xsi:schemaLocation=\"http://www.springframework.org/schema/beans\n\nhttp://www.springframework.org/schema/beans/spring-beans-3.0.xsd\n\nhttp://www.springframework.org/schema/aop\n\nhttp://www.springframework.org/schema/aop/spring-aop-3.0.xsd\">\n\n    <bean id=\"daoImpl\" class=\"org.xrq.spring.action.aop.DaoImpl\" />\n    <bean id=\"timeHandler\" class=\"org.xrq.spring.action.aop.TimeHandler\" />\n\n\n</beans>\n```\n\n我不大会写expression，也懒得去百度了，因此这里就拦截Dao下的所有方法了。测试代码很简单：\n\n\n    /**\n    * @author 五月的仓颉http://www.cnblogs.com/xrq730/p/7003082.html\n    */\n    public class AopTest {\n    \n    ```\n        @Test\n        @SuppressWarnings(\"resource\")\n        public void testAop() {\n            ApplicationContext ac = new ClassPathXmlApplicationContext(\"spring/aop.xml\");\n    \n            Dao dao = (Dao)ac.getBean(\"daoImpl\");\n            dao.insert();\n            System.out.println(\"----------分割线----------\");\n            dao.delete();\n            System.out.println(\"----------分割线----------\");\n            dao.update();\n        }\n    \n    }\n    ```\n\n## AOP总结\n\n结果就不演示了。到此我总结一下使用AOP的几个优点：\n\n切面的内容可以复用，比如TimeHandler的printTime方法，任何地方需要打印方法执行前的时间与方法执行后的时间，都可以使用TimeHandler的printTime方法\n避免使用Proxy、CGLIB生成代理，这方面的工作全部框架去实现，开发者可以专注于切面内容本身\n代码与代码之间没有耦合，如果拦截的方法有变化修改配置文件即可\n下面用一张图来表示一下AOP的作用：\n\n我们传统的编程方式是垂直化的编程，即A–>B–>C–>D这么下去，一个逻辑完毕之后执行另外一段逻辑。但是AOP提供了另外一种思路，它的作用是在业务逻辑不知情（即业务逻辑不需要做任何的改动）的情况下对业务代码的功能进行增强，这种编程思想的使用场景有很多，例如事务提交、方法执行之前的权限检测、日志打印、方法调用事件等等。\n\nAOP使用场景举例\n上面的例子纯粹为了演示使用，为了让大家更加理解AOP的作用，这里以实际场景作为例子。\n\n第一个例子，我们知道MyBatis的事务默认是不会自动提交的，因此在编程的时候我们必须在增删改完毕之后调用SqlSession的commit()方法进行事务提交，这非常麻烦，下面利用AOP简单写一段代码帮助我们自动提交事务（这段代码我个人测试过可用）：\n\n```\n/**\n * @author 五月的仓颉http://www.cnblogs.com/xrq730/p/7003082.html\n */\npublic class TransactionHandler {\n\n    public void commit(JoinPoint jp) {\n        Object obj = jp.getTarget();\n        if (obj instanceof MailDao) {\n            Signature signature = jp.getSignature();\n            if (signature instanceof MethodSignature) {\n                SqlSession sqlSession = SqlSessionThrealLocalUtil.getSqlSession();               \n\n                MethodSignature methodSignature = (MethodSignature)signature;\n                Method method = methodSignature.getMethod();\n\n                String methodName = method.getName();\n                if (methodName.startsWith(\"insert\") || methodName.startsWith(\"update\") || methodName.startsWith(\"delete\")) {\n                    sqlSession.commit();\n                }\n\n                sqlSession.close();\n            }\n        }\n    }\n\n}\n```\n\n这种场景下我们要使用的aop标签为，即切在方法调用之后。\n\n这里我做了一个SqlSessionThreadLocalUtil，每次打开会话的时候，都通过SqlSessionThreadLocalUtil把当前会话SqlSession放到ThreadLocal中，看到通过TransactionHandler，可以实现两个功能：\n\ninsert、update、delete操作事务自动提交\n对SqlSession进行close()，这样就不需要在业务代码里面关闭会话了，因为有些时候我们写业务代码的时候会忘记关闭SqlSession，这样可能会造成内存句柄的膨胀，因此这部分切面也一并做了\n整个过程，业务代码是不知道的，而TransactionHandler的内容可以充分再多处场景下进行复用。\n\n第二个例子是权限控制的例子，不管是从安全角度考虑还是从业务角度考虑，我们在开发一个Web系统的时候不可能所有请求都对所有用户开放，因此这里就需要做一层权限控制了，大家看AOP作用的时候想必也肯定会看到AOP可以做权限控制，这里我就演示一下如何使用AOP做权限控制。我们知道原生的Spring MVC，Java类是实现Controller接口的，基于此，利用AOP做权限控制的大致代码如下（这段代码纯粹就是一段示例，我构建的Maven工程是一个普通的Java工程，因此没有验证过）：\n\n```\n/**\n * @author 五月的仓颉http://www.cnblogs.com/xrq730/p/7003082.html\n */\npublic class PermissionHandler {\n\n    public void hasPermission(JoinPoint jp) throws Exception {\n        Object obj = jp.getTarget();\n\n        if (obj instanceof Controller) {\n            Signature signature = jp.getSignature();\n            MethodSignature methodSignature = (MethodSignature)signature;\n\n            // 获取方法签名\n            Method method = methodSignature.getMethod();\n            // 获取方法参数\n            Object[] args = jp.getArgs();\n\n            // Controller中唯一一个方法的方法签名ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;\n            // 这里对这个方法做一层判断\n            if (\"handleRequest\".equals(method.getName()) && args.length == 2) {\n                Object firstArg = args[0];\n                if (obj instanceof HttpServletRequest) {\n                    HttpServletRequest request = (HttpServletRequest)firstArg;\n                    // 获取用户id\n                    long userId = Long.parseLong(request.getParameter(\"userId\"));\n                    // 获取当前请求路径\n                    String requestUri = request.getRequestURI();\n\n                    if(!PermissionUtil.hasPermission(userId, requestUri)) {\n                        throw new Exception(\"没有权限\");\n                    }\n                }\n            }\n        }\n\n    }\n\n}\n```\n\n毫无疑问这种场景下我们要使用的aop标签为。这里我写得很简单，获取当前用户id与请求路径，根据这两者，判断该用户是否有权限访问该请求，大家明白意思即可。\n\n后记\n文章演示了从原生代码到使用AOP的过程，一点一点地介绍了每次演化的优缺点，最后以实际例子分析了AOP可以做什么事情。\n\n\n\n## 微信公众号\n\n### 个人公众号：黄小斜\n\n黄小斜是跨考软件工程的 985 硕士，自学 Java 两年，拿到了 BAT 等近十家大厂 offer，从技术小白成长为阿里工程师。\n\n作者专注于 JAVA 后端技术栈，热衷于分享程序员干货、学习经验、求职心得和程序人生，目前黄小斜的CSDN博客有百万+访问量，知乎粉丝2W+，全网已有10W+读者。\n\n黄小斜是一个斜杠青年，坚持学习和写作，相信终身学习的力量，希望和更多的程序员交朋友，一起进步和成长！\n\n**原创电子书:**\n关注公众号【黄小斜】后回复【原创电子书】即可领取我原创的电子书《菜鸟程序员修炼手册：从技术小白到阿里巴巴Java工程师》\n\n**程序员3T技术学习资源：** 一些程序员学习技术的资源大礼包，关注公众号后，后台回复关键字 **“资料”** 即可免费无套路获取。\n\n**考研复习资料：**\n计算机考研大礼包，都是我自己考研复习时用的一些复习资料,包括公共课和专业的复习视频，这里也推荐给大家，关注公众号后，后台回复关键字 **“考研”** 即可免费获取。\n\n![](https://img-blog.csdnimg.cn/20190829222750556.jpg)\n\n\n### 技术公众号：Java技术江湖\n\n如果大家想要实时关注我更新的文章以及分享的干货的话，可以关注我的公众号【Java技术江湖】一位阿里 Java 工程师的技术小站，作者黄小斜，专注 Java 相关技术：SSM、SpringBoot、MySQL、分布式、中间件、集群、Linux、网络、多线程，偶尔讲点Docker、ELK，同时也分享技术干货和学习经验，致力于Java全栈开发！\n\n**Java工程师必备学习资源:** 一些Java工程师常用学习资源，关注公众号后，后台回复关键字 **“Java”** 即可免费无套路获取。\n\n![我的公众号](https://img-blog.csdnimg.cn/20190805090108984.jpg)\n \n"
  },
  {
    "path": "docs/Spring全家桶/Spring源码分析/Spring源码剖析：SpringIOC容器的加载过程.md",
    "content": "# 目录\n* [spring ioc 容器的加载流程](#spring-ioc-容器的加载流程)\n* [Spring Ioc Demo](#spring-ioc-demo)\n* [ApplicationContext 继承结构](#applicationcontext-继承结构)\n* [Spring Ioc容器加载过程源码详解](#spring-ioc容器加载过程源码详解)\n* [refresh()方法](#refresh方法)\n  * [调试栈截图](#调试栈截图)\n  * [整体流程](#整体流程)\n  * [bean.xml的处理](#beanxml的处理)\n  * [loadBeanDefinitions](#loadbeandefinitions)\n    * [loadBeanDefinitions: 源码阅读](#loadbeandefinitions-源码阅读)\n* [loadBeanDefinitions](#loadbeandefinitions-1)\n\n\n本文转自五月的仓颉 https://www.cnblogs.com/xrq730\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章将同步到我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《Spring和SpringMVC源码分析》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从spring基础入手，一步步地学习spring基础和springmvc的框架知识，并上手进行项目实战，spring框架是每一个Java工程师必须要学习和理解的知识点，进一步来说，你还需要掌握spring甚至是springmvc的源码以及实现原理，才能更完整地了解整个spring技术体系，形成自己的知识框架。\n\n后续还会有springboot和springcloud的技术专题，陆续为大家带来，敬请期待。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供部分知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->  \n\n## spring ioc 容器的加载流程\n\n**1.目标：**熟练使用spring，并分析其源码，了解其中的思想。这篇主要介绍spring ioc 容器的加载\n\n**2.前提条件：**会使用debug\n\n**3.源码分析方法：**Intellj idea debug 模式下源码追溯  \n通过ClassPathXmlApplicationContext 进行xml 件的读取，从每个堆栈中读取程序的运行信息\n\n**4.注意：**由于Spring的类继承体系比较复杂,不能全部贴图，所以只将分析源码之后发现的最主要的类继承结构类图贴在下方。\n\n**5.关于Spring Ioc  \nDemo：**我们从demo入手一步步进行代码追溯。\n\n## Spring Ioc Demo\n\n* * *  \n\n> 1.定义数据访问接口IUserDao.java\n\n```  \npublic interface IUserDao {    \n    public void InsertUser(String username,String password);  \n}  \n  \n```  \n\n2.定义IUserDao.java实现类IUserDaoImpl.java\n\n```  \npublic class UserDaoImpl implements IUserDao {      \n    @Override      \n    public void InsertUser(String username, String password) {   \n        System.out.println(\"----UserDaoImpl --addUser----\");      \n    }  \n}  \n  \n```  \n\n3.定义业务逻辑接口UserService.java\n\n```  \npublic interface UserService {      \n    public void addUser(String username,String password);  \n}  \n  \n```  \n\n4.定义UserService.java实现类UserServiceImpl.java\n\n```  \npublic class UserServiceImpl implements UserService {      \n    private     IUserDao  userDao;    //set方法    \n    public void  setUserDao(IUserDao  userDao) {          \n        this.userDao = userDao;     \n    }      \n    @Override      \n    public void addUser(String username,String password) {   \n        userDao.InsertUser(username,password);      \n    }  \n}  \n  \n```  \n\nbean.xml配置文件\n\n```  \n<beans xmlns=\"http://www.springframework.org/schema/beans\"    \n       xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"      \nxsi:schemaLocation=\"http://www.springframework.org/schema/beans  http://www.springframework.org/schema/beans/spring-beans-3.0.xsd         \">    \n <!--id名字自己取，class表示他代表的类，如果在包里的话需要加上包名-->      \n <bean id=\"userService\"  class=\"UserServiceImpl\" >        \n        <!--property代表是通过set方法注入,ref的值表示注入的内容-->  \n        <property  name=\"userDao\"  ref=\"userDao\"/> </bean>      \n  <bean id=\"userDao\"  class=\"UserDaoImpl\"/>  \n</beans>  \n  \n```  \n\n## ApplicationContext 继承结构\n\n* * *  \n\n> 1.顶层接口：ApplicationContext  \n> 2.ClassPathXmlApplicationContext实现类继承AbstractXmlApplication 抽象类  \n> 3.AbstractXmlApplication 继承AbstractRefreshableConfigApplicationContext  \n> 4.AbstractRefreshableConfigApplicationContext抽象类继承AbstractRefreshableApplicationContext  \n> 5.AbstractRefreshableApplicationContext 继承 AbstractApplicationContext  \n> 6.AbstractApplicationContext 实现ConfigurableApplicationContext 接口  \n> 7.ConfigurableApplicationContext 接口继承  \n> ApplicationContext接口  \n> 总体来说继承实现结构较深，内部使用了大量适配器模式。  \n> 以ClassPathXmlApplicationContext为例，继承类图如下图所示：\n>\n> ![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825134835291-1628501369.png)\n\n## Spring Ioc容器加载过程源码详解\n\n* * *  \n\n在开始之前，先介绍一个整体的概念。即spring ioc容器的加载，大体上经过以下几个过程：  \n资源文件定位、解析、注册、实例化\n\n> **1.资源文件定位**  \n> 其中资源文件定位，一般是在ApplicationContext的实现类里完成的，因为ApplicationContext接口继承ResourcePatternResolver 接口，ResourcePatternResolver接口继承ResourceLoader接口，ResourceLoader其中的getResource()方法，可以将外部的资源，读取为Resource类。\n\n* * *  \n\n**2.解析**DefaultBeanDefinitionDocumentReader，  \n解析主要是在BeanDefinitionReader中完成的，最常用的实现类是XmlBeanDefinitionReader，其中的loadBeanDefinitions()方法，负责读取Resource，并完成后续的步骤。ApplicationContext完成资源文件定位之后，是将解析工作委托给XmlBeanDefinitionReader来完成的  \n解析这里涉及到很多步骤，最常见的情况，资源文件来自一个XML配置文件。首先是BeanDefinitionReader，将XML文件读取成w3c的Document文档。\n\n\nDefaultBeanDefinitionDocumentReader对Document进行进一步解析。然后DefaultBeanDefinitionDocumentReader又委托给BeanDefinitionParserDelegate进行解析。如果是标准的xml namespace元素，会在Delegate内部完成解析，如果是非标准的xml namespace元素，则会委托合适的NamespaceHandler进行解析最终解析的结果都封装为BeanDefinitionHolder，至此解析就算完成。  \n**后续会进行细致讲解。**\n\n* * *  \n\n**3.注册**  \n然后bean的注册是在BeanFactory里完成的，BeanFactory接口最常见的一个实现类是DefaultListableBeanFactory，它实现了BeanDefinitionRegistry接口，所以其中的registerBeanDefinition()方法，可以对BeanDefinition进行注册这里附带一提，最常见的XmlWebApplicationContext不是自己持有BeanDefinition的，它继承自AbstractRefreshableApplicationContext，其持有一个DefaultListableBeanFactory的字段，就是用它来保存BeanDefinition  \n所谓的注册，其实就是将BeanDefinition的name和实例，保存到一个Map中。\n\n\n刚才说到，最常用的实现DefaultListableBeanFactory，其中的字段就是beanDefinitionMap，是一个ConcurrentHashMap。  \n代码如下：  \n**>1.DefaultListableBeanFactory继承实现关系**\n\n```  \npublic class DefaultListableBeanFactory  \nextends AbstractAutowireCapableBeanFactory   implements  \nConfigurableListableBeanFactory, BeanDefinitionRegistry,  \nSerializable {   \n     // DefaultListableBeanFactory的实例中最终保存了所有注册的bean    beanDefinitionMap  \n     /** Map of bean definition objects, keyed by bean name */     private final Map<String, BeanDefinition> beanDefinitionMap     = new ConcurrentHashMap<String, BeanDefinition>(64);   \n     //实现BeanDefinitionRegistry中定义的registerBeanDefinition()抽象方法  \n     public void registerBeanDefinition(String beanName, BeanDefinition    beanDefinition)      throws BeanDefinitionStoreException {     }  \n```  \n\n**>2.BeanDefinitionRegistry接口**\n\n```  \npublic interface BeanDefinitionRegistry extends AliasRegistry {     \n    //定义注册BeanDefinition实例的抽象方法  \n    void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)         throws BeanDefinitionStoreException;  \n```  \n\n**4.实例化**\n\n* * *  \n\n注册也完成之后，在BeanFactory的getBean()方法之中，会完成初始化，也就是依赖注入的过程  \n大体上的流程就是这样。\n\n# refresh()方法\n\n> **1.目标：**  \n> 这篇记录debug 追溯源码的过程，大概分三个篇幅，这是第一篇，现整体了解一下运行流程，定位资源加载，资源解析，bean 注册发生的位置。  \n> 2.**记录结构：**  \n> 1.调试栈截图  \n> 2.整体流程  \n> 3.bean.xml的处理  \n> **每段代码下面有相应的讲解**\n\n## 调试栈截图\n\n* * *  \n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825134839933-520783623.png)\n\n每个栈帧中方法的行号都有标明，按照行号追溯源码，然后配合教程能够快速学习。\n\n## 整体流程\n\n* * *  \n\nioc容器实例化代码\n\n```  \nApplicationContext applicationContext = new ClassPathXmlApplicationContext(\"bean.xml\");  \n  \n```  \n\n进入代码中一步步追溯，发现重要方法：refresh();  \n如下所示：\n\n```  \npublic void refresh() throws BeansException, IllegalStateException {  \n  \n        synchronized (this.startupShutdownMonitor) {            // Prepare this context for refreshing.            prepareRefresh();            //beanFactory实例化方法 单步调试入口  \n            // Tell the subclass to refresh the internal bean factory.            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();  \n            // Prepare the bean factory for use in this context.            prepareBeanFactory(beanFactory);  \n            try {                // Allows post-processing of the bean factory in context subclasses.                postProcessBeanFactory(beanFactory);  \n                // Invoke factory processors registered as beans in the context.                invokeBeanFactoryPostProcessors(beanFactory);  \n                // Register bean processors that intercept bean creation.                registerBeanPostProcessors(beanFactory);  \n                // Initialize message source for this context.                initMessageSource();  \n                // Initialize event multicaster for this context.                initApplicationEventMulticaster();  \n                // Initialize other special beans in specific context subclasses.                onRefresh();  \n                // Check for listener beans and register them.                registerListeners();  \n                // Instantiate all remaining (non-lazy-init) singletons.                finishBeanFactoryInitialization(beanFactory);  \n                // Last step: publish corresponding event.                finishRefresh();            }  \n            catch (BeansException ex) {                // Destroy already created singletons to avoid dangling resources.                destroyBeans();  \n                // Reset 'active' flag.                cancelRefresh(ex);  \n                // Propagate exception to caller.                throw ex;            }        }    }  \n```  \n\n首先这个方法是同步的，以避免重复刷新。然后刷新的每个步骤，都放在单独的方法里，比较清晰，可以按顺序一个个看\n\n首先是prepareRefresh()方法\n\n```  \nprotected void prepareRefresh() {  \n        this.startupDate = System.currentTimeMillis();  \n        synchronized (this.activeMonitor) {            this.active = true;        }  \n        if (logger.isInfoEnabled()) {            logger.info(\"Refreshing \" + this);        }  \n        // Initialize any placeholder property sources in the context environment        initPropertySources();  \n        // Validate that all properties marked as required are resolvable        // see ConfigurablePropertyResolver#setRequiredProperties        this.environment.validateRequiredProperties();    }  \n```  \n\n这个方法里做的事情不多，记录了开始时间，输出日志，另外initPropertySources()方法和validateRequiredProperties()方法一般都没有做什么事。\n\n然后是核心的obtainFreshBeanFactory()方法，这个方法是初始化BeanFactory，是整个refresh()方法的核心，其中完成了配置文件的加载、解析、注册，后面会专门详细说 。\n\n这里要说明一下，ApplicationContext实现了BeanFactory接口，并实现了ResourceLoader、MessageSource等接口，可以认为是增强的BeanFactory。但是ApplicationContext并不自己重复实现BeanFactory定义的方法，而是委托给DefaultListableBeanFactory来实现。这种设计思路也是值得学习的。  \n后面的 prepareBeanFactory()、postProcessBeanFactory()、invokeBeanFactoryPostProcessors()、registerBeanPostProcessors()、initMessageSource()、initApplicationEventMulticaster()、onRefresh()、registerListeners()、finishBeanFactoryInitialization()、finishRefresh()等方法，是添加一些后处理器、广播、拦截器等，就不一个个细说了\n\n其中的关键方法是finishBeanFactoryInitialization()，在这个方法中，会对刚才注册的Bean（不延迟加载的），进行实例化，所以也是一个核心方法。\n\n## bean.xml的处理\n\n* * *  \n\n从整体上介绍完了流程，接下来就重点看obtainFreshBeanFactory()方法，上文说到，在这个方法里，完成了配置文件的加载、解析、注册\n\n```  \nprotected ConfigurableListableBeanFactory obtainFreshBeanFactory() {  \n        refreshBeanFactory();        ConfigurableListableBeanFactory beanFactory = getBeanFactory();        if (logger.isDebugEnabled()) {            logger.debug(\"Bean factory for \" + getDisplayName() + \": \" + beanFactory);        }        return beanFactory;    }  \n```  \n\n这个方法做了2件事，首先通过refreshBeanFactory()方法，创建了DefaultListableBeanFactory的实例，并进行初始化。\n\n```  \nprotected final void refreshBeanFactory() throws BeansException {  \n        if (hasBeanFactory()) {            destroyBeans();            closeBeanFactory();        }        try {            DefaultListableBeanFactory beanFactory = createBeanFactory();            beanFactory.setSerializationId(getId());            customizeBeanFactory(beanFactory);            loadBeanDefinitions(beanFactory);            synchronized (this.beanFactoryMonitor) {                this.beanFactory = beanFactory;            }        }        catch (IOException ex) {            throw new ApplicationContextException(\"I/O error parsing bean definition source for \" + getDisplayName(), ex);        }    }  \n```  \n\n首先如果已经有BeanFactory实例，就先清空。然后通过createBeanFactory()方法，创建一个DefaultListableBeanFactory的实例\n\n```  \nprotected DefaultListableBeanFactory createBeanFactory() {  \n        return new DefaultListableBeanFactory(getInternalParentBeanFactory());    }  \n```  \n\n接下来设置ID唯一标识\n\n```  \nbeanFactory.setSerializationId(getId());  \n  \n```  \n\n然后允许用户进行一些自定义的配置\n\n```  \nprotected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {  \n        if (this.allowBeanDefinitionOverriding != null) {            beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);        }        if (this.allowCircularReferences != null) {            beanFactory.setAllowCircularReferences(this.allowCircularReferences);        }        beanFactory.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver());    }  \n```  \n\n最后，就是核心的loadBeanDefinitions()方法\n\n```  \nprotected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {  \n        // Create a new XmlBeanDefinitionReader for the given BeanFactory.        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);  \n        // Configure the bean definition reader with this context's        // resource loading environment.        beanDefinitionReader.setEnvironment(this.getEnvironment());        beanDefinitionReader.setResourceLoader(this);        beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));  \n        // Allow a subclass to provide custom initialization of the reader,        // then proceed with actually loading the bean definitions.        initBeanDefinitionReader(beanDefinitionReader);        loadBeanDefinitions(beanDefinitionReader);    }  \n```  \n\n这里首先会创建一个XmlBeanDefinitionReader的实例，然后进行初始化。这个XmlBeanDefinitionReader中其实传递的BeanDefinitionRegistry类型的实例，为什么可以传递一个beanFactory呢，因为DefaultListableBeanFactory实现了BeanDefinitionRegistry接口，这里是多态的使用。\n\n```  \nprotected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {  \n        // Create a new XmlBeanDefinitionReader for the given BeanFactory.        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);  \n        // Configure the bean definition reader with this context's        // resource loading environment.        beanDefinitionReader.setEnvironment(this.getEnvironment());        beanDefinitionReader.setResourceLoader(this);        beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));  \n        // Allow a subclass to provide custom initialization of the reader,        // then proceed with actually loading the bean definitions.        initBeanDefinitionReader(beanDefinitionReader);}  \n  \n```  \n\n这里要说明一下，ApplicationContext并不自己负责配置文件的加载、解析、注册，而是将这些工作委托给XmlBeanDefinitionReader来做。\n\n```  \nloadBeanDefinitions(beanDefinitionReader);  \n  \n```  \n\n这行代码，就是Bean定义读取实际发生的地方。这里的工作，主要是XmlBeanDefinitionReader来完成的，下一篇博客会详细介绍这个过程。\n\n## loadBeanDefinitions\n\n### loadBeanDefinitions: 源码阅读\n\n* * *  \n\n入口是loadBeanDefinitions方法\n\n```  \nprotected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {  \n        String[] configLocations = getConfigLocations();        if (configLocations != null) {            for (String configLocation : configLocations) {                reader.loadBeanDefinitions(configLocation);            }        }}  \n  \n```  \n\n这是解析过程最外围的代码，首先要获取到配置文件的路径，这在之前已经完成了。  \n然后将每个配置文件的路径，作为参数传给BeanDefinitionReader的loadBeanDefinitions方法里\n\n```  \npublic int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {  \n        return loadBeanDefinitions(location, null);}  \n  \n```  \n\n这个方法又调用了重载方法\n\n```  \npublic int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {  \n        ResourceLoader resourceLoader = getResourceLoader();        if (resourceLoader == null) {            throw new BeanDefinitionStoreException(                    \"Cannot import bean definitions from location [\" + location + \"]: no ResourceLoader available\");        }  \n        if (resourceLoader instanceof ResourcePatternResolver) {            // Resource pattern matching available.            try {                Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);                int loadCount = loadBeanDefinitions(resources);                if (actualResources != null) {                    for (Resource resource : resources) {                        actualResources.add(resource);                    }                }                if (logger.isDebugEnabled()) {                    logger.debug(\"Loaded \" + loadCount + \" bean definitions from location pattern [\" + location + \"]\");                }                return loadCount;            }            catch (IOException ex) {                throw new BeanDefinitionStoreException(                        \"Could not resolve bean definition resource pattern [\" + location + \"]\", ex);            }        }        else {            // Can only load single resources by absolute URL.            Resource resource = resourceLoader.getResource(location);            int loadCount = loadBeanDefinitions(resource);            if (actualResources != null) {                actualResources.add(resource);            }            if (logger.isDebugEnabled()) {                logger.debug(\"Loaded \" + loadCount + \" bean definitions from location [\" + location + \"]\");            }            return loadCount;        }    }  \n```  \n\n首先getResourceLoader()的实现的前提条件是因为XmlBeanDefinitionReader在实例化的时候已经确定了创建了实例ResourceLoader实例, 代码位于 AbstractBeanDefinitionReader\n\n```  \nprotected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {     \n     Assert.notNull(registry, \"BeanDefinitionRegistry must not be null\");   \n     this.registry = registry;     \n     // Determine ResourceLoader to use.    \n     if (this.registry instanceof ResourceLoader) {       \n         this.resourceLoader = (ResourceLoader) this.registry;     \n      }  else {        \n         this.resourceLoader = new PathMatchingResourcePatternResolver();    \n      }     \n     // Inherit Environment if possible     \n     if (this.registry instanceof EnvironmentCapable) {        \n          this.environment = ((EnvironmentCapable)this.registry).getEnvironment();    \n      }  else {        \n          this.environment = new StandardEnvironment();   \n      }  \n}  \n  \n```  \n\n这个方法比较长，BeanDefinitionReader不能直接加载配置文件，需要把配置文件封装成Resource，然后才能调用重载方法loadBeanDefinitions()。所以这个方法其实就是2段，第一部分是委托ResourceLoader将配置文件封装成Resource，第二部分是调用loadBeanDefinitions()，对Resource进行解析\n\n而这里的ResourceLoader，就是前面的XmlWebApplicationContext，因为ApplicationContext接口，是继承自ResourceLoader接口的\n\nResource也是一个接口体系，在web环境下，这里就是ServletContextResource\n\n接下来进入重载方法loadBeanDefinitions()\n\n```  \npublic int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {  \n        Assert.notNull(resources, \"Resource array must not be null\");        int counter = 0;        for (Resource resource : resources) {            counter += loadBeanDefinitions(resource);        }        return counter;    }  \n```  \n\n这里就不用说了，就是把每一个Resource作为参数，继续调用重载方法。读spring源码，会发现重载方法特别多。\n\n```  \npublic int loadBeanDefinitions(Resource resource)  throws  \n BeanDefinitionStoreException {        return loadBeanDefinitions(new EncodedResource(resource));}  \n  \n```  \n\n还是重载方法，不过这里对传进来的Resource又进行了一次封装，变成了编码后的Resource。\n\n```  \npublic int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {  \n        Assert.notNull(encodedResource, \"EncodedResource must not be null\");        if (logger.isInfoEnabled()) {            logger.info(\"Loading XML bean definitions from \" + encodedResource.getResource());        }  \n        Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();        if (currentResources == null) {            currentResources = new HashSet<EncodedResource>(4);            this.resourcesCurrentlyBeingLoaded.set(currentResources);        }        if (!currentResources.add(encodedResource)) {            throw new BeanDefinitionStoreException(                    \"Detected cyclic loading of \" + encodedResource + \" - check your import definitions!\");        }        try {            InputStream inputStream = encodedResource.getResource().getInputStream();            try {                InputSource inputSource = new InputSource(inputStream);                if (encodedResource.getEncoding() != null) {                    inputSource.setEncoding(encodedResource.getEncoding());                }                return doLoadBeanDefinitions(inputSource, encodedResource.getResource());            }            finally {                inputStream.close();            }        }        catch (IOException ex) {            throw new BeanDefinitionStoreException(                    \"IOException parsing XML document from \" + encodedResource.getResource(), ex);        }        finally {            currentResources.remove(encodedResource);            if (currentResources.isEmpty()) {                this.resourcesCurrentlyBeingLoaded.remove();            }        }    }  \n```  \n\n这个就是loadBeanDefinitions()的最后一个重载方法，比较长，可以拆看来看。\n\n```  \nAssert.notNull(encodedResource, \"EncodedResource must not be null\");  \n        if (logger.isInfoEnabled()) {            logger.info(\"Loading XML bean definitions from \" + encodedResource.getResource());        }  \n        Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();        if (currentResources == null) {            currentResources = new HashSet<EncodedResource>(4);            this.resourcesCurrentlyBeingLoaded.set(currentResources);        }        if (!currentResources.add(encodedResource)) {            throw new BeanDefinitionStoreException(                    \"Detected cyclic loading of \" + encodedResource + \" - check your import definitions!\");        }  \n```  \n\n这第一部分，是处理线程相关的工作，把当前正在解析的Resource，设置为当前Resource。\n\n```  \ntry {  \n            InputStream inputStream = encodedResource.getResource().getInputStream();            try {                InputSource inputSource = new InputSource(inputStream);                if (encodedResource.getEncoding() != null) {                    inputSource.setEncoding(encodedResource.getEncoding());                }                return doLoadBeanDefinitions(inputSource, encodedResource.getResource());            }            finally {                inputStream.close();            }        }  \n```  \n\n这里是第二部分，是核心，首先把Resource还原为InputStream，然后调用实际解析的方法doLoadBeanDefinitions()。**可以看到，这种命名方式是很值得学习的，一种业务方法，比如parse()，可能需要做一些外围的工作，然后实际解析的方法，可以命名为doParse()。这种doXXX()的命名方法，在很多开源框架中都有应用，比如logback等。**  \n接下来就看一下这个doLoadBeanDefinitions()方法\n\n```  \nprotected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)  \n            throws BeanDefinitionStoreException {        try {            Document doc = doLoadDocument(inputSource, resource);return registerBeanDefinitions(doc, resource);            return registerBeanDefinitions(doc, resource);        }        catch (BeanDefinitionStoreException ex) {            throw ex;        }        catch (SAXParseException ex) {            throw new XmlBeanDefinitionStoreException(resource.getDescription(),                    \"Line \" + ex.getLineNumber() + \" in XML document from \" + resource + \" is invalid\", ex);        }        catch (SAXException ex) {            throw new XmlBeanDefinitionStoreException(resource.getDescription(),                    \"XML document from \" + resource + \" is invalid\", ex);        }        catch (ParserConfigurationException ex) {            throw new BeanDefinitionStoreException(resource.getDescription(),                    \"Parser configuration exception parsing XML from \" + resource, ex);        }        catch (IOException ex) {            throw new BeanDefinitionStoreException(resource.getDescription(),                    \"IOException parsing XML document from \" + resource, ex);        }        catch (Throwable ex) {            throw new BeanDefinitionStoreException(resource.getDescription(),                    \"Unexpected exception parsing XML document from \" + resource, ex);        }    }  \n```  \n\n抛开异常处理：核心代码如下：\n\n```  \n Document doc = doLoadDocument(inputSource, resource); return  registerBeanDefinitions(doc, resource);  \n```  \n\ndoLoadDocument方法将InputStream读取成标准的Document对象，然后调用registerBeanDefinitions()，进行解析工作。\n\n```  \nprotected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {     \n    return this.documentLoader.loadDocument(inputSource,    \n                                            getEntityResolver(), this.errorHandler,    \n                                            getValidationModeForResource(resource),    \n                                            isNamespaceAware());  \n}  \n  \n```  \n\n接下来就看一下这个核心方法registerBeanDefinitions\n\n```  \npublic int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {  \n        //创建的其实是DefaultBeanDefinitionDocumentReader 的实例，利用反射创建的。  \n        BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();        documentReader.setEnvironment(this.getEnvironment());        int countBefore = getRegistry().getBeanDefinitionCount();        documentReader.registerBeanDefinitions(doc, createReaderContext(resource));        return getRegistry().getBeanDefinitionCount() - countBefore;}  \n  \n```  \n\n**这里注意两点 :**\n\n> **1.Document对象**  \n> 首先这个Document对象，是W3C定义的标准XML对象，跟spring无关。其次这个registerBeanDefinitions方法，我觉得命名有点误导性。因为这个时候实际上解析还没有开始，怎么直接就注册了呢。比较好的命名，我觉得可以是parseAndRegisterBeanDefinitions()。  \n> **2.documentReader的创建时使用反射创建的，代码如下**\n\n```  \nprotected BeanDefinitionDocumentReader      \n createBeanDefinitionDocumentReader() {     \n          return BeanDefinitionDocumentReader.class.cast(BeanUtils.  \n            instantiateClass(this.documentReaderClass));}  \n  \n```  \n\ninstantiateClass方法中传入了一个Class类型的参数。追溯发现下述代码：\n\n```  \nprivate Class<?> documentReaderClass = DefaultBeanDefinitionDocumentReader.class;  \n  \n```  \n\n所以创建的documentReaderClass是DefaultBeanDefinitionDocumentReader类的实例。  \n接下来就进入BeanDefinitionDocumentReader 中定义的registerBeanDefinitions()方法看看\n\n```  \npublic void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {  \n        this.readerContext = readerContext;        logger.debug(\"Loading bean definitions\");        Element root = doc.getDocumentElement();        doRegisterBeanDefinitions(root);    }  \n```  \n\n处理完外围事务之后，进入doRegisterBeanDefinitions()方法，这种命名规范，上文已经介绍过了\n\n```  \nprotected void doRegisterBeanDefinitions(Element root) {  \n        String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);        if (StringUtils.hasText(profileSpec)) {            Assert.state(this.environment != null, \"environment property must not be null\");            String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);            if (!this.environment.acceptsProfiles(specifiedProfiles)) {                return;            }        }        // any nested <beans> elements will cause recursion in this method. In        // order to propagate and preserve <beans> default-* attributes correctly,        // keep track of the current (parent) delegate, which may be null. Create        // the new (child) delegate with a reference to the parent for fallback purposes,        // then ultimately reset this.delegate back to its original (parent) reference.        // this behavior emulates a stack of delegates without actually necessitating one.        BeanDefinitionParserDelegate parent = this.delegate;        this.delegate = createHelper(readerContext, root, parent);        preProcessXml(root);        parseBeanDefinitions(root, this.delegate);        postProcessXml(root);        this.delegate = parent;}  \n  \n```  \n\n这个方法也比较长，拆开来看\n\n```  \nString profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);  \n        if (StringUtils.hasText(profileSpec)) {            Assert.state(this.environment != null, \"environment property must not be null\");            String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);            if (!this.environment.acceptsProfiles(specifiedProfiles)) {                return;            }}  \n  \n```  \n\n如果配置文件中元素，配有profile属性，就会进入这一段，不过一般都是不会的\n\n```  \n        BeanDefinitionParserDelegate parent = this.delegate;        this.delegate = createHelper(readerContext, root, parent);        preProcessXml(root);        parseBeanDefinitions(root, this.delegate);        postProcessXml(root);        this.delegate = parent;  \n```  \n\n然后这里创建了BeanDefinitionParserDelegate对象，preProcessXml()和postProcessXml()都是空方法，核心就是parseBeanDefinitions()方法。这里又把BeanDefinition解析和注册的工作，委托给了BeanDefinitionParserDelegate对象，在parseBeanDefinitions()方法中完成  \n总的来说，解析工作的委托链是这样的：ClassPathXmlApplicationContext，XmlBeanDefinitionReader，DefaultBeanDefinitionDocumentReader，BeanDefinitionParserDelegate  \nClassPathXmlApplicationContext作为最外围的组件，发起解析的请求  \nXmlBeanDefinitionReader将配置文件路径封装为Resource，读取出w3c定义的Document对象，然后委托给DefaultBeanDefinitionDocumentReader  \nDefaultBeanDefinitionDocumentReader就开始做实际的解析工作了，但是涉及到bean的具体解析，它还是会继续委托给BeanDefinitionParserDelegate来做。  \n接下来在parseBeanDefinitions()方法中发生了什么，以及BeanDefinitionParserDelegate类完成的工作，在下一篇博客中继续介绍。\n\n# loadBeanDefinitions\n\n> BeanDefinition的解析,已经走到了DefaultBeanDefinitionDocumentR  \n> eader里，这时候配置文件已经被加载，并解析成w3c的Document对象。这篇博客就接着介绍，DefaultBeanDefinitionDocumentReader和BeanDefinitionParserDelegate类，是怎么协同完成bean的解析和注册的。\n\n```  \n        BeanDefinitionParserDelegate parent = this.delegate;        this.delegate = createHelper(readerContext, root, parent);        preProcessXml(root);        parseBeanDefinitions(root, this.delegate);        postProcessXml(root);        this.delegate = parent;  \n```  \n\n这段代码，创建了一个BeanDefinitionParserDelegate组件，然后就是preProcessXml()、parseBeanDefinitions()、postProcessXml()方法  \n其中preProcessXml()和postProcessXml()默认是空方法，接下来就看下parseBeanDefinitions()方法\n\n```  \nprotected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {  \n        if (delegate.isDefaultNamespace(root)) {            NodeList nl = root.getChildNodes();            for (int i = 0; i < nl.getLength(); i++) {                Node node = nl.item(i);                if (node instanceof Element) {                    Element ele = (Element) node;                    if (delegate.isDefaultNamespace(ele)) {                        parseDefaultElement(ele, delegate);                    }                    else {                        delegate.parseCustomElement(ele);                    }                }            }        }        else {            delegate.parseCustomElement(root);        }    }  \n```  \n\n从这个方法开始，BeanDefinitionParserDelegate就开始发挥作用了，判断当前解析元素是否属于默认的命名空间，如果是的话，就调用parseDefaultElement()方法，否则调用delegate上parseCustomElement()方法\n\n```  \npublic boolean isDefaultNamespace(String namespaceUri) {  \n        return (!StringUtils.hasLength(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri));    }    public boolean isDefaultNamespace(Node node) {        return isDefaultNamespace(getNamespaceURI(node));    }  \n```  \n\n只有**[http://www.springframework.org/schema/beans](https://link.jianshu.com/?t=http://www.springframework.org/schema/beans)**，会被认为是默认的命名空间。也就是说，beans、bean这些元素，会认为属于默认的命名空间，而像task:scheduled这些，就认为不属于默认命名空间。  \n根节点beans的一个子节点bean，是属于默认命名空间的，所以会进入parseDefaultElement()方法\n\n```  \nprivate void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {  \n        if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {            importBeanDefinitionResource(ele);        }        else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {            processAliasRegistration(ele);        }        else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {            processBeanDefinition(ele, delegate);        }        else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {            // recurse            doRegisterBeanDefinitions(ele);        }    }  \n```  \n\n这里可能会有4种情况，import、alias、bean、beans，分别有一个方法与之对应，这里解析的是bean元素，所以会进入processBeanDefinition()方法\n\n```  \nprotected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {  \n        BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);        if (bdHolder != null) {            bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);            try {                // Register the final decorated instance.                BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());            }            catch (BeanDefinitionStoreException ex) {                getReaderContext().error(\"Failed to register bean definition with name '\" +                        bdHolder.getBeanName() + \"'\", ele, ex);            }            // Send registration event.            getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));        }    }  \n```  \n\n这里主要有3个步骤，先是委托delegate对bean进行解析，然后委托delegate对bean进行装饰，最后由一个工具类来完成BeanDefinition的注册  \n可以看出来，DefaultBeanDefinitionDocumentReader不负责任何具体的bean解析，它面向的是xml Document对象，根据其元素的命名空间和名称，起一个类似路由的作用（不过，命名空间的判断，也是委托给delegate来做的）。所以这个类的命名，是比较贴切的，突出了其面向Document的特性。具体的工作，是由BeanDefinitionParserDelegate来完成的  \n下面就看下parseBeanDefinitionElement()方法\n\n```  \npublic BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {  \n        String id = ele.getAttribute(ID_ATTRIBUTE);        String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);        List<String> aliases = new ArrayList<String>();        if (StringUtils.hasLength(nameAttr)) {            String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);            aliases.addAll(Arrays.asList(nameArr));        }        String beanName = id;        if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {            beanName = aliases.remove(0);            if (logger.isDebugEnabled()) {                logger.debug(\"No XML 'id' specified - using '\" + beanName +                        \"' as bean name and \" + aliases + \" as aliases\");            }        }        if (containingBean == null) {            checkNameUniqueness(beanName, aliases, ele);        }        AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);        if (beanDefinition != null) {            if (!StringUtils.hasText(beanName)) {                try {                    if (containingBean != null) {                        beanName = BeanDefinitionReaderUtils.generateBeanName(                                beanDefinition, this.readerContext.getRegistry(), true);                    }                    else {                        beanName = this.readerContext.generateBeanName(beanDefinition);                        // Register an alias for the plain bean class name, if still possible,                        // if the generator returned the class name plus a suffix.                        // This is expected for Spring 1.2/2.0 backwards compatibility.                        String beanClassName = beanDefinition.getBeanClassName();                        if (beanClassName != null &&                                beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&                      !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {                            aliases.add(beanClassName);                        }                    }                    if (logger.isDebugEnabled()) {                        logger.debug(\"Neither XML 'id' nor 'name' specified - \" +                                \"using generated bean name [\" + beanName + \"]\");                    }                }                catch (Exception ex) {                    error(ex.getMessage(), ele);                    return null;                }            }            String[] aliasesArray = StringUtils.toStringArray(aliases);            return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);        }        return null;    }  \n```  \n\n这个方法很长，可以分成三段来看\n\n```  \nString id = ele.getAttribute(ID_ATTRIBUTE);  \n        String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);        List<String> aliases = new ArrayList<String>();        if (StringUtils.hasLength(nameAttr)) {            String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);            aliases.addAll(Arrays.asList(nameArr));        }        String beanName = id;        if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {            beanName = aliases.remove(0);            if (logger.isDebugEnabled()) {                logger.debug(\"No XML 'id' specified - using '\" + beanName +                        \"' as bean name and \" + aliases + \" as aliases\");            }        }        if (containingBean == null) {            checkNameUniqueness(beanName, aliases, ele);        }  \n```  \n\n这一段，主要是处理一些跟alias，id等标识相关的东西\n\n```  \nAbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);  \n  \n```  \n\n这一行是核心，进行实际的解析\n\n```  \nif (beanDefinition != null) {  \n            if (!StringUtils.hasText(beanName)) {                try {                    if (containingBean != null) {                        beanName = BeanDefinitionReaderUtils.generateBeanName(                                beanDefinition, this.readerContext.getRegistry(), true);                    }                    else {                        beanName = this.readerContext.generateBeanName(beanDefinition);                        // Register an alias for the plain bean class name, if still possible,                        // if the generator returned the class name plus a suffix.                        // This is expected for Spring 1.2/2.0 backwards compatibility.                        String beanClassName = beanDefinition.getBeanClassName();                        if (beanClassName != null &&                                beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&                                !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {                            aliases.add(beanClassName);                        }                    }                    if (logger.isDebugEnabled()) {                        logger.debug(\"Neither XML 'id' nor 'name' specified - \" +                                \"using generated bean name [\" + beanName + \"]\");                    }                }                catch (Exception ex) {                    error(ex.getMessage(), ele);                    return null;                }            }            String[] aliasesArray = StringUtils.toStringArray(aliases);            return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);        }  \n```  \n\n这段是后置处理，对beanName进行处理  \n前置处理和后置处理，不是核心，就不细看了，重点看下核心的那一行调用\n\n```  \npublic AbstractBeanDefinition parseBeanDefinitionElement(  \n            Element ele, String beanName, BeanDefinition containingBean) {        this.parseState.push(new BeanEntry(beanName));        String className = null;        if (ele.hasAttribute(CLASS_ATTRIBUTE)) {            className = ele.getAttribute(CLASS_ATTRIBUTE).trim();        }        try {            String parent = null;            if (ele.hasAttribute(PARENT_ATTRIBUTE)) {                parent = ele.getAttribute(PARENT_ATTRIBUTE);            }            AbstractBeanDefinition bd = createBeanDefinition(className, parent);            parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);            bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));            parseMetaElements(ele, bd);            parseLookupOverrideSubElements(ele, bd.getMethodOverrides());            parseReplacedMethodSubElements(ele,   bd.getMethodOverrides());            parseConstructorArgElements(ele, bd);            parsePropertyElements(ele, bd);            parseQualifierElements(ele, bd);            bd.setResource(this.readerContext.getResource());            bd.setSource(extractSource(ele));            return bd;        }        catch (ClassNotFoundException ex) {            error(\"Bean class [\" + className + \"] not found\", ele, ex);        }        catch (NoClassDefFoundError err) {            error(\"Class that bean class [\" + className + \"] depends on not found\", ele, err);        }        catch (Throwable ex) {            error(\"Unexpected failure during bean definition parsing\", ele, ex);        }        finally {            this.parseState.pop();        }        return null;    }  \n```  \n\n这个方法也挺长的，拆开看看\n\n```  \nthis.parseState.push(new BeanEntry(beanName));  \n        String className = null;        if (ele.hasAttribute(CLASS_ATTRIBUTE)) {            className = ele.getAttribute(CLASS_ATTRIBUTE).trim();        }  \n```  \n\n这段是从配置中抽取出类名。接下来的长长一段，把异常处理先抛开，看看实际的业务\n\n```  \n            String parent = null;            if (ele.hasAttribute(PARENT_ATTRIBUTE)) {                parent = ele.getAttribute(PARENT_ATTRIBUTE);            }            AbstractBeanDefinition bd = createBeanDefinition(className, parent);            parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);            bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));  \n            parseMetaElements(ele, bd);            parseLookupOverrideSubElements(ele, bd.getMethodOverrides());            parseReplacedMethodSubElements(ele, bd.getMethodOverrides());            parseConstructorArgElements(ele, bd);            parsePropertyElements(ele, bd);            parseQualifierElements(ele, bd);            bd.setResource(this.readerContext.getResource());            bd.setSource(extractSource(ele));            return bd;  \n```  \n\n这里每个方法的命名，就说明了是要干什么，可以一个个跟进去看，本文就不细说了。总之，经过这里的解析，就得到了一个完整的BeanDefinitionHolder。只是说明一下，如果在配置文件里，没有对一些属性进行设置，比如autowire-candidate等，那么这个解析生成的BeanDefinition，都会得到一个默认值  \n**然后，对这个Bean做一些必要的装饰**\n\n```  \npublic BeanDefinitionHolder decorateBeanDefinitionIfRequired(  \n            Element ele, BeanDefinitionHolder definitionHolder, BeanDefinition containingBd) {        BeanDefinitionHolder finalDefinition = definitionHolder;        // Decorate based on custom attributes first.        NamedNodeMap attributes = ele.getAttributes();        for (int i = 0; i < attributes.getLength(); i++) {            Node node = attributes.item(i);            finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);        }        // Decorate based on custom nested elements.        NodeList children = ele.getChildNodes();        for (int i = 0; i < children.getLength(); i++) {            Node node = children.item(i);            if (node.getNodeType() == Node.ELEMENT_NODE) {                finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);            }        }        return finalDefinition;    }  \n```  \n\n持续单步调试，代码继续运行到DefaultBeanDefinitionDocumentReader中的processBeanDefinition中的registerBeanDefinition()\n\n```  \nBeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());  \n  \n```  \n\n单步进入代码发现BeanDefinitionReaderUtils静态方法registerBeanDefinition()\n\n```  \npublic static void registerBeanDefinition(  \n            BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)            throws BeanDefinitionStoreException {        // Register bean definition under primary name.        String beanName = definitionHolder.getBeanName();        // 其实调用的是DefaultListableBeanFactory中的registerBeanDefinition方法  \n        registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());        // Register aliases for bean name, if any.        String[] aliases = definitionHolder.getAliases();        if (aliases != null) {            for (String aliase : aliases) {                registry.registerAlias(beanName, aliase);            }        }    }  \n```  \n\n解释一下**其实调用的是DefaultListableBeanFactory中的registerBeanDefinition方法**这句话，因为DefaultListableBeanFactory实现BeanDefinitionRegistry接口，BeanDefinitionRegistry接口中定义了registerBeanDefinition()方法  \n看下DefaultListableBeanFactory中registerBeanDefinition()实例方法的具体实现：\n\n```  \npublic void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)  \n            throws BeanDefinitionStoreException {        Assert.hasText(beanName, \"Bean name must not be empty\");        Assert.notNull(beanDefinition, \"BeanDefinition must not be null\");        if (beanDefinition instanceof AbstractBeanDefinition) {            try {                ((AbstractBeanDefinition) beanDefinition).validate();            }            catch (BeanDefinitionValidationException ex) {                throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,                        \"Validation of bean definition failed\", ex);            }        }        synchronized (this.beanDefinitionMap) {            Object oldBeanDefinition = this.beanDefinitionMap.get(beanName);            if (oldBeanDefinition != null) {                if (!this.allowBeanDefinitionOverriding) {                    throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,                            \"Cannot register bean definition [\" + beanDefinition + \"] for bean '\" + beanName +                            \"': There is already [\" + oldBeanDefinition + \"] bound.\");                }                else {                    if (this.logger.isInfoEnabled()) {                        this.logger.info(\"Overriding bean definition for bean '\" + beanName +                                \"': replacing [\" + oldBeanDefinition + \"] with [\" + beanDefinition + \"]\");                    }                }            }            else {                this.beanDefinitionNames.add(beanName);                this.frozenBeanDefinitionNames = null;            }            this.beanDefinitionMap.put(beanName, beanDefinition);            resetBeanDefinition(beanName);        }    }  \n```  \n\n代码追溯之后发现这个方法里，最关键的是以下2行：\n\n```  \nthis.beanDefinitionNames.add(beanName);  \nthis.beanDefinitionMap.put(beanName, beanDefinition);  \n  \n```  \n\n前者是把beanName放到队列里，后者是把BeanDefinition放到map中，到此注册就完成了。在后面实例化的时候，就是把beanDefinitionMap中的BeanDefinition取出来，逐一实例化  \nBeanFactory准备完毕之后，代码又回到了ClassPathXmlApplicationContext里\n\n```  \npublic void refresh() throws BeansException, IllegalStateException {  \n        synchronized (this.startupShutdownMonitor) {            // Prepare this context for refreshing.            prepareRefresh();            // Tell the subclass to refresh the internal bean factory.            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();            // Prepare the bean factory for use in this context.            prepareBeanFactory(beanFactory);            try {                // Allows post-processing of the bean factory in context subclasses.                postProcessBeanFactory(beanFactory);                // Invoke factory processors registered as beans in the context.                invokeBeanFactoryPostProcessors(beanFactory);                // Register bean processors that intercept bean creation.                registerBeanPostProcessors(beanFactory);                // Initialize message source for this context.                initMessageSource();                // Initialize event multicaster for this context.                initApplicationEventMulticaster();                // Initialize other special beans in specific context subclasses.                onRefresh();                // Check for listener beans and register them.                registerListeners();                // Instantiate all remaining (non-lazy-init) singletons.                finishBeanFactoryInitialization(beanFactory);                // Last step: publish corresponding event.                finishRefresh();            }            catch (BeansException ex) {                // Destroy already created singletons to avoid dangling resources.                destroyBeans();                // Reset 'active' flag.                cancelRefresh(ex);                // Propagate exception to caller.                throw ex;            }        }    }  \n```  \n\n也就是obtainFreshBeanFactory()方法执行之后，再进行下面的步骤。  \n总结来说，ApplicationContext将解析配置文件的工作委托给BeanDefinitionReader，然后BeanDefinitionReader将配置文件读取为xml的Document文档之后，又委托给BeanDefinitionDocumentReader  \nBeanDefinitionDocumentReader这个组件是根据xml元素的命名空间和元素名，起到一个路由的作用，实际的解析工作，是委托给BeanDefinitionParserDelegate来完成的。\n\n\nBeanDefinitionParserDelegate的解析工作完成以后，会返回BeanDefinitionHolder给BeanDefinitionDocumentReader，在这里，会委托给DefaultListableBeanFactory完成bean的注册  \nXmlBeanDefinitionReader（计数、解析XML文档），BeanDefinitionDocumentReader（依赖xml文档，进行解析和注册），BeanDefinitionParserDelegate（实际的解析工作）。\n\n\n可以看出，在解析bean的过程中，这3个组件的分工是比较清晰的，各司其职，这种设计思想值得学习  \n到此为止，bean的解析、注册、spring ioc 容器的实例化过程就基本分析结束了。\n\n\n## 微信公众号\n\n### 个人公众号：黄小斜\n\n黄小斜是跨考软件工程的 985 硕士，自学 Java 两年，拿到了 BAT 等近十家大厂 offer，从技术小白成长为阿里工程师。\n\n作者专注于 JAVA 后端技术栈，热衷于分享程序员干货、学习经验、求职心得和程序人生，目前黄小斜的CSDN博客有百万+访问量，知乎粉丝2W+，全网已有10W+读者。\n\n黄小斜是一个斜杠青年，坚持学习和写作，相信终身学习的力量，希望和更多的程序员交朋友，一起进步和成长！\n\n**原创电子书:**  \n关注公众号【黄小斜】后回复【原创电子书】即可领取我原创的电子书《菜鸟程序员修炼手册：从技术小白到阿里巴巴Java工程师》\n\n**程序员3T技术学习资源：** 一些程序员学习技术的资源大礼包，关注公众号后，后台回复关键字 **“资料”** 即可免费无套路获取。\n\n**考研复习资料：**  \n计算机考研大礼包，都是我自己考研复习时用的一些复习资料,包括公共课和专业的复习视频，这里也推荐给大家，关注公众号后，后台回复关键字 **“考研”** 即可免费获取。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20190829222750556.jpg)\n\n\n### 技术公众号：Java技术江湖\n\n如果大家想要实时关注我更新的文章以及分享的干货的话，可以关注我的公众号【Java技术江湖】一位阿里 Java 工程师的技术小站，作者黄小斜，专注 Java 相关技术：SSM、SpringBoot、MySQL、分布式、中间件、集群、Linux、网络、多线程，偶尔讲点Docker、ELK，同时也分享技术干货和学习经验，致力于Java全栈开发！\n\n**Java工程师必备学习资源:** 一些Java工程师常用学习资源，关注公众号后，后台回复关键字 **“Java”** 即可免费无套路获取。\n\n![我的公众号](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20190805090108984.jpg)"
  },
  {
    "path": "docs/Spring全家桶/Spring源码分析/Spring源码剖析：Spring事务概述.md",
    "content": "# 目录\n  * [数据库事务概述](#数据库事务概述)\n    * [事务类型](#事务类型)\n    * [Spring提供的事务管理](#spring提供的事务管理)\n    * [spring事务特性](#spring事务特性)\n  * [事务隔离级别](#事务隔离级别)\n  * [事务传播行为](#事务传播行为)\n    * [事务超时](#事务超时)\n    * [事务只读属性](#事务只读属性)\n    * [概述](#概述)\n    * [内置事务管理器实现](#内置事务管理器实现)\n  * [声明式事务](#声明式事务)\n    * [声明式事务概述](#声明式事务概述)\n    * [声明式实现事务管理](#声明式实现事务管理)\n    * [@Transactional实现事务管理](#transactional实现事务管理)\n\n原文出处：[张开涛](http://sishuok.com/forum/blogPost/list/0/2508.html)\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章将同步到我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《Spring和SpringMVC源码分析》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从spring基础入手，一步步地学习spring基础和springmvc的框架知识，并上手进行项目实战，spring框架是每一个Java工程师必须要学习和理解的知识点，进一步来说，你还需要掌握spring甚至是springmvc的源码以及实现原理，才能更完整地了解整个spring技术体系，形成自己的知识框架。\n\n后续还会有springboot和springcloud的技术专题，陆续为大家带来，敬请期待。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供部分知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n## 数据库事务概述\n\n事务首先是一系列操作组成的工作单元，该工作单元内的操作是不可分割的，即要么所有操作都做，要么所有操作都不做，这就是事务。\n\n事务必需满足ACID（原子性、一致性、隔离性和持久性）特性，缺一不可：\n\n*   原子性（Atomicity）：即事务是不可分割的最小工作单元，事务内的操作要么全做，要么全不做；\n*   一致性（Consistency）：在事务执行前数据库的数据处于正确的状态，而事务执行完成后数据库的数据还是处于正确的状态，即数据完整性约束没有被破坏；如银行转帐，A转帐给B，必须保证A的钱一定转给B，一定不会出现A的钱转了但B没收到，否则数据库的数据就处于不一致（不正确）的状态。\n*   隔离性（Isolation）：并发事务执行之间无影响，在一个事务内部的操作对其他事务是不产生影响，这需要事务隔离级别来指定隔离性；\n*   持久性（Durability）：事务一旦执行成功，它对数据库的数据的改变必须是永久的，不会因比如遇到系统故障或断电造成数据不一致或丢失。\n\n在实际项目开发中数据库操作一般都是并发执行的，即有多个事务并发执行，并发执行就可能遇到问题，目前常见的问题如下：\n\n*   丢失更新：两个事务同时更新一行数据，最后一个事务的更新会覆盖掉第一个事务的更新，从而导致第一个事务更新的数据丢失，这是由于没有加锁造成的；\n*   脏读：一个事务看到了另一个事务未提交的更新数据；\n*   不可重复读：在同一事务中，多次读取同一数据却返回不同的结果；也就是有其他事务更改了这些数据；\n*   幻读：一个事务在执行过程中读取到了另一个事务已提交的插入数据；即在第一个事务开始时读取到一批数据，但此后另一个事务又插入了新数据并提交，此时第一个事务又读取这批数据但发现多了一条，即好像发生幻觉一样。\n\n为了解决这些并发问题，需要通过数据库隔离级别来解决，在标准SQL规范中定义了四种隔离级别：\n\n*   未提交读（Read Uncommitted）：最低隔离级别，一个事务能读取到别的事务未提交的更新数据，很不安全，可能出现丢失更新、脏读、不可重复读、幻读；\n*   提交读（Read Committed）：一个事务能读取到别的事务提交的更新数据，不能看到未提交的更新数据，不可能可能出现丢失更新、脏读，但可能出现不可重复读、幻读；\n*   可重复读（Repeatable Read）：保证同一事务中先后执行的多次查询将返回同一结果，不受其他事务影响，可能可能出现丢失更新、脏读、不可重复读，但可能出现幻读；\n*   序列化（Serializable）：最高隔离级别，不允许事务并发执行，而必须串行化执行，最安全，不可能出现更新、脏读、不可重复读、幻读。\n\n隔离级别越高，数据库事务并发执行性能越差，能处理的操作越少。因此在实际项目开发中为了考虑并发性能一般使用提交读隔离级别，它能避免丢失更新和脏读，尽管不可重复读和幻读不能避免，但可以在可能出现的场合使用悲观锁或乐观锁来解决这些问题。\n\n### 事务类型\n\n数据库事务类型有本地事务和分布式事务：\n\n*   本地事务：就是普通事务，能保证单台数据库上的操作的ACID，被限定在一台数据库上；\n*   分布式事务：涉及两个或多个数据库源的事务，即跨越多台同类或异类数据库的事务（由每台数据库的本地事务组成的），分布式事务旨在保证这些本地事务的所有操作的ACID，使事务可以跨越多台数据库；\n\nJava事务类型有JDBC事务和JTA事务：\n\n*   JDBC事务：就是数据库事务类型中的本地事务，通过Connection对象的控制来管理事务；\n*   JTA事务：JTA指Java事务API(Java Transaction API)，是Java EE数据库事务规范， JTA只提供了事务管理接口，由应用程序服务器厂商（如WebSphere Application Server）提供实现，JTA事务比JDBC更强大，支持分布式事务。\n\nJava EE事务类型有本地事务和全局事务：\n\n*   本地事务：使用JDBC编程实现事务；\n*   全局事务：由应用程序服务器提供，使用JTA事务；\n\n按是否通过编程实现事务有声明式事务和编程式事务；\n\n*   声明式事务： 通过注解或XML配置文件指定事务信息；\n*   编程式事务：通过编写代码实现事务。\n\n### Spring提供的事务管理\n\nSpring框架最核心功能之一就是事务管理，而且提供一致的事务管理抽象，这能帮助我们：\n\n*   提供一致的编程式事务管理API，不管使用Spring JDBC框架还是集成第三方框架使用该API进行事务编程；\n*   无侵入式的声明式事务支持。\n\nSpring支持声明式事务和编程式事务事务类型。\n\n### spring事务特性\n\nspring所有的事务管理策略类都继承自org.springframework.transaction.PlatformTransactionManager接口\n\n其中TransactionDefinition接口定义以下特性：\n\n## 事务隔离级别\n\n 隔离级别是指若干个并发的事务之间的隔离程度。TransactionDefinition 接口中定义了五个表示隔离级别的常量：\n\n*   TransactionDefinition.ISOLATION_DEFAULT：这是默认值，表示使用底层数据库的默认隔离级别。对大部分数据库而言，通常这值就是TransactionDefinition.ISOLATION_READ_COMMITTED。\n*   TransactionDefinition.ISOLATION_READ_UNCOMMITTED：该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读，不可重复读和幻读，因此很少使用该隔离级别。比如PostgreSQL实际上并没有此级别。\n*   TransactionDefinition.ISOLATION_READ_COMMITTED：该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读，这也是大多数情况下的推荐值。\n*   TransactionDefinition.ISOLATION_REPEATABLE_READ：该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询，并且每次返回的记录都相同。该级别可以防止脏读和不可重复读。\n*   TransactionDefinition.ISOLATION_SERIALIZABLE：所有的事务依次逐个执行，这样事务之间就完全不可能产生干扰，也就是说，该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。\n\n## 事务传播行为\n\n   所谓事务的传播行为是指，如果在开始当前事务之前，一个事务上下文已经存在，此时有若干选项可以指定一个事务性方法的执行行为。在TransactionDefinition定义中包括了如下几个表示传播行为的常量：\n\n*   TransactionDefinition.PROPAGATION_REQUIRED：如果当前存在事务，则加入该事务；如果当前没有事务，则创建一个新的事务。这是默认值。\n*   TransactionDefinition.PROPAGATION_REQUIRES_NEW：创建一个新的事务，如果当前存在事务，则把当前事务挂起。\n*   TransactionDefinition.PROPAGATION_SUPPORTS：如果当前存在事务，则加入该事务；如果当前没有事务，则以非事务的方式继续运行。\n*   TransactionDefinition.PROPAGATION_NOT_SUPPORTED：以非事务方式运行，如果当前存在事务，则把当前事务挂起。\n*   TransactionDefinition.PROPAGATION_NEVER：以非事务方式运行，如果当前存在事务，则抛出异常。\n*   TransactionDefinition.PROPAGATION_MANDATORY：如果当前存在事务，则加入该事务；如果当前没有事务，则抛出异常。\n*   TransactionDefinition.PROPAGATION_NESTED：如果当前存在事务，则创建一个事务作为当前事务的嵌套事务来运行；如果当前没有事务，则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。\n\n### 事务超时\n\n  所谓事务超时，就是指一个事务所允许执行的最长时间，如果超过该时间限制但事务还没有完成，则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间，其单位是秒。\n\n 默认设置为底层事务系统的超时值，如果底层数据库事务系统没有设置超时值，那么就是none，没有超时限制。\n\n### 事务只读属性\n\n  只读事务用于客户代码只读但不修改数据的情形，只读事务用于特定情景下的优化，比如使用Hibernate的时候。\n\n默认为读写事务。\n\n### 概述\n\nSpring框架支持事务管理的核心是事务管理器抽象，对于不同的数据访问框架（如Hibernate）通过实现策略接口PlatformTransactionManager，从而能支持各种数据访问框架的事务管理，PlatformTransactionManager接口定义如下：\n\njava代码：\n\n```\n\npublic interface PlatformTransactionManager {\n       TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;\n       void commit(TransactionStatus status) throws TransactionException;\n       void rollback(TransactionStatus status) throws TransactionException;\n}\n\n```\n\n*   getTransaction()：返回一个已经激活的事务或创建一个新的事务（根据给定的TransactionDefinition类型参数定义的事务属性），返回的是TransactionStatus对象代表了当前事务的状态，其中该方法抛出TransactionException（未检查异常）表示事务由于某种原因失败。\n*   commit()：用于提交TransactionStatus参数代表的事务，具体语义请参考Spring Javadoc；\n*   rollback()：用于回滚TransactionStatus参数代表的事务，具体语义请参考Spring Javadoc。\n\nTransactionDefinition接口定义如下：\n\njava代码：\n\n```\n\npublic interface TransactionDefinition {\n       int getPropagationBehavior();\n       int getIsolationLevel();\n       int getTimeout();\n       boolean isReadOnly();\n       String getName();\n}\n\n```\n\n*   getPropagationBehavior()：返回定义的事务传播行为；\n*   getIsolationLevel()：返回定义的事务隔离级别；\n*   getTimeout()：返回定义的事务超时时间；\n*   isReadOnly()：返回定义的事务是否是只读的；\n*   getName()：返回定义的事务名字。\n\nTransactionStatus接口定义如下：\n\njava代码：\n\n```\npublic interface TransactionStatus extends SavepointManager {\n       boolean isNewTransaction();\n       boolean hasSavepoint();\n       void setRollbackOnly();\n       boolean isRollbackOnly();\n       void flush();\n       boolean isCompleted();\n}\n\n```\n\n*   isNewTransaction()：返回当前事务状态是否是新事务；\n*   hasSavepoint()：返回当前事务是否有保存点；\n*   setRollbackOnly()：设置当前事务应该回滚；\n*   isRollbackOnly(()：返回当前事务是否应该回滚；\n*   flush()：用于刷新底层会话中的修改到数据库，一般用于刷新如Hibernate/JPA的会话，可能对如JDBC类型的事务无任何影响；\n*   isCompleted():当前事务否已经完成。\n\n### 内置事务管理器实现\n\nSpring提供了许多内置事务管理器实现：\n\n*   DataSourceTransactionManager：位于org.springframework.jdbc.datasource包中，数据源事务管理器，提供对单个javax.sql.DataSource事务管理，用于Spring JDBC抽象框架、iBATIS或MyBatis框架的事务管理；\n*   JdoTransactionManager：位于org.springframework.orm.jdo包中，提供对单个javax.jdo.PersistenceManagerFactory事务管理，用于集成JDO框架时的事务管理；\n*   JpaTransactionManager：位于org.springframework.orm.jpa包中，提供对单个javax.persistence.EntityManagerFactory事务支持，用于集成JPA实现框架时的事务管理；\n*   HibernateTransactionManager：位于org.springframework.orm.hibernate3包中，提供对单个org.hibernate.SessionFactory事务支持，用于集成Hibernate框架时的事务管理；该事务管理器只支持Hibernate3+版本，且Spring3.0+版本只支持Hibernate 3.2+版本；\n*   JtaTransactionManager：位于org.springframework.transaction.jta包中，提供对分布式事务管理的支持，并将事务管理委托给Java EE应用服务器事务管理器；\n*   OC4JjtaTransactionManager：位于org.springframework.transaction.jta包中，Spring提供的对OC4J10.1.3+应用服务器事务管理器的适配器，此适配器用于对应用服务器提供的高级事务的支持；\n*   WebSphereUowTransactionManager：位于org.springframework.transaction.jta包中，Spring提供的对WebSphere 6.0+应用服务器事务管理器的适配器，此适配器用于对应用服务器提供的高级事务的支持；\n*   WebLogicJtaTransactionManager：位于org.springframework.transaction.jta包中，Spring提供的对WebLogic 8.1+应用服务器事务管理器的适配器，此适配器用于对应用服务器提供的高级事务的支持。\n\nSpring不仅提供这些事务管理器，还提供对如JMS事务管理的管理器等，Spring提供一致的事务抽象如图9-1所示。\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405190404.png)\n图9-1 Spring事务管理器\n\n接下来让我们学习一下如何在Spring配置文件中定义事务管理器：\n\n一、声明对本地事务的支持：\n\na)JDBC及iBATIS、MyBatis框架事务管理器\n\njava代码：\n\n```\n\n<bean id=\"txManager\" class=\"org.springframework.jdbc.datasource.DataSourceTransactionManager\">\n    <property name=\"dataSource\" ref=\"dataSource\"/>\n</bean>\n\n```\n\n通过dataSource属性指定需要事务管理的单个javax.sql.DataSource对象。\n\nb)Jdo事务管理器\n\njava代码：\n\n```\n\n<bean id=\"txManager\" class=\"org.springframework.orm.jdo.JdoTransactionManager\">\n    <property name=\"persistenceManagerFactory\" ref=\"persistenceManagerFactory\"/>\n</bean>\n\n```\n\n通过persistenceManagerFactory属性指定需要事务管理的javax.jdo.PersistenceManagerFactory对象。\n\nc)Jpa事务管理器\n\njava代码：\n\n```\nbean id=\"txManager\" class=\"org.springframework.orm.jpa.JpaTransactionManager\">\n    <property name=\"entityManagerFactory\" ref=\"entityManagerFactory\"/>\n</bean>\n\n```\n\n通过entityManagerFactory属性指定需要事务管理的javax.persistence.EntityManagerFactory对象。\n\n还需要为entityManagerFactory对象指定jpaDialect属性，该属性所对应的对象指定了如何获取连接对象、开启事务、关闭事务等事务管理相关的行为。\n\njava代码：\n\n```\n<bean id=\"entityManagerFactory\" class=\"org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean\">\n        ……\n        <property name=\"jpaDialect\" ref=\"jpaDialect\"/>\n</bean>\n<bean id=\"jpaDialect\" class=\"org.springframework.orm.jpa.vendor.HibernateJpaDialect\"/>\n\n```\n\nd)Hibernate事务管理器\n\njava代码：\n\n```\n<bean id=\"txManager\" class=\"org.springframework.orm.hibernate3.HibernateTransactionManager\">\n    <property name=\"sessionFactory\" ref=\"sessionFactory\"/>\n</bean>\n\n```\n\n通过entityManagerFactory属性指定需要事务管理的org.hibernate.SessionFactory对象。\n\n## 声明式事务\n\n### 声明式事务概述\n\n从上节编程式实现事务管理可以深刻体会到编程式事务的痛苦，即使通过代理配置方式也是不小的工作量。\n\n本节将介绍声明式事务支持，使用该方式后最大的获益是简单，事务管理不再是令人痛苦的，而且此方式属于无侵入式，对业务逻辑实现无影响。\n\n接下来先来看看声明式事务如何实现吧。\n\n### 声明式实现事务管理\n\n1、定义业务逻辑实现，此处使用ConfigUserServiceImpl和ConfigAddressServiceImpl：\n\n2、定义配置文件（chapter9/service/ applicationContext-service-declare.xml）：\n\n2.1、XML命名空间定义，定义用于事务支持的tx命名空间和AOP支持的aop命名空间：\n\n```\njava代码：\n<beans xmlns=\"http://www.springframework.org/schema/beans\"\n      xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xmlns:tx=\"http://www.springframework.org/schema/tx\"\n      xmlns:aop=\"http://www.springframework.org/schema/aop\"\n      xsi:schemaLocation=\"\n\nhttp://www.springframework.org/schema/beans\n\nhttp://www.springframework.org/schema/beans/spring-beans-3.0.xsd\n\nhttp://www.springframework.org/schema/tx\n\nhttp://www.springframework.org/schema/tx/spring-tx-3.0.xsd\n\nhttp://www.springframework.org/schema/aop\n\nhttp://www.springframework.org/schema/aop/spring-aop-3.0.xsd\">\n```\n\n2.2、业务实现配置，非常简单，使用以前定义的非侵入式业务实现：\n\n```\njava代码：\n<bean id=\"userService\" class=\"cn.javass.spring.chapter9.service.impl.ConfigUserServiceImpl\">\n    <property name=\"userDao\" ref=\"userDao\"/>\n    <property name=\"addressService\" ref=\"addressService\"/>\n</bean>\n<bean id=\"addressService\" class=\"cn.javass.spring.chapter9.service.impl.ConfigAddressServiceImpl\">\n    <property name=\"addressDao\" ref=\"addressDao\"/>\n</bean>\n```\n\n2.3、事务相关配置：\n\n```\njava代码：\n<tx:advice id=\"txAdvice\" transaction-manager=\"txManager\">\n    <tx:attributes>\n        <tx:method name=\"save*\" propagation=\"REQUIRED\" isolation=\"READ_COMMITTED\"/>\n        <tx:method name=\"*\" propagation=\"REQUIRED\" isolation=\"READ_COMMITTED\" read-only=\"true\"/>\n    </tx:attributes>\n</tx:advice>\n```\n\njava代码：\n\n```\n\n<tx:advice>：事务通知定义，用于指定事务属性，其中“transaction-manager”属性指定事务管理器，并通过< tx:attributes >指定具体需要拦截的方法；\n <tx:method name=”save*”>：表示将拦截以save开头的方法，被拦截的方法将应用配置的事务属性：propagation=”REQUIRED”表示传播行为是Required，isolation=”READ_COMMITTED”表示隔离级别是提交读；\n<tx:method name=”*”>：表示将拦截其他所有方法，被拦截的方法将应用配置的事务属性：propagation=”REQUIRED”表示传播行为是Required，isolation=”READ_COMMITTED”表示隔离级别是提交读，read-only=”true”表示事务只读；\n：AOP相关配置：\n：切入点定义，定义名为”serviceMethod”的aspectj切入点，切入点表达式为”execution(* cn..chapter9.service..*.*(..))”表示拦截cn包及子包下的chapter9\\. service包及子包下的任何类的任何方法；\n：Advisor定义，其中切入点为serviceMethod，通知为txAdvice。\n从配置中可以看出，将对cn包及子包下的chapter9\\. service包及子包下的任何类的任何方法应用“txAdvice”通知指定的事务属性。\n\n```\n\n3、修改测试方法并测试该配置方式是否好用：\n\n将TransactionTest 类的testServiceTransaction测试方法拷贝一份命名为testDeclareTransaction：\n\n并在testDeclareTransaction测试方法内将：\n\n4、执行测试，测试正常通过，说明该方式能正常工作，当调用save方法时将匹配到事务通知中定义的“<tx:method name=”save_”>”中指定的事务属性，而调用countAll方法时将匹配到事务通知中定义的“<tx:method name=”_”>”中指定的事务属性。\n\n声明式事务是如何实现事务管理的呢？还记不记得TransactionProxyFactoryBean实现配置式事务管理，配置式事务管理是通过代理方式实现，而声明式事务管理同样是通过AOP代理方式实现。\n\n声明式事务通过AOP代理方式实现事务管理，利用环绕通知TransactionInterceptor实现事务的开启及关闭，而TransactionProxyFactoryBean内部也是通过该环绕通知实现的，因此可以认为是<tx:tags/>帮你定义了TransactionProxyFactoryBean，从而简化事务管理。\n\n了解了实现方式后，接下来详细学习一下配置吧：\n\n9.4.4 <tx:advice/>配置详解\n声明式事务管理通过配置<tx:advice/>来定义事务属性，配置方式如下所示：\n\n```\njava代码：\n<tx:advice id=\"……\" transaction-manager=\"……\">\n<tx:attributes>\n        <tx:method name=\"……\"\n                           propagation=\" REQUIRED\"\n                           isolation=\"READ_COMMITTED\"\n                           timeout=\"-1\"\n                           read-only=\"false\"\n                           no-rollback-for=\"\"\n                           rollback-for=\"\"/>\n        ……\n    </tx:attributes>\n</tx:advice>\n<tx:advice>：id用于指定此通知的名字， transaction-manager用于指定事务管理器，默认的事务管理器名字为“transactionManager”；\n<tx:method>：用于定义事务属性即相关联的方法名；\n\n```\n\nname：定义与事务属性相关联的方法名，将对匹配的方法应用定义的事务属性，可以使用“_”通配符来匹配一组或所有方法，如“save_”将匹配以save开头的方法，而“*”将匹配所有方法；\n\npropagation：事务传播行为定义，默认为“REQUIRED”，表示Required，其值可以通过TransactionDefinition的静态传播行为变量的“PROPAGATION_”后边部分指定，如“TransactionDefinition.PROPAGATION_REQUIRED”可以使用“REQUIRED”指定；\n\nisolation：事务隔离级别定义；默认为“DEFAULT”，其值可以通过TransactionDefinition的静态隔离级别变量的“ISOLATION_”后边部分指定，如“TransactionDefinition. ISOLATION_DEFAULT”可以使用“DEFAULT”指定：\n\ntimeout：事务超时时间设置，单位为秒，默认-1，表示事务超时将依赖于底层事务系统；\n\nread-only：事务只读设置，默认为false，表示不是只读；\n\nrollback-for：需要触发回滚的异常定义，以“，”分割，默认任何RuntimeException 将导致事务回滚，而任何Checked Exception 将不导致事务回滚；异常名字定义和TransactionProxyFactoryBean中含义一样\n\nno-rollback-for：不被触发进行回滚的 Exception(s)；以“，”分割；异常名字定义和TransactionProxyFactoryBean中含义一样；\n\n记不记得在配置方式中为了解决“自我调用”而导致的不能设置正确的事务属性问题，使用“((IUserService)AopContext.currentProxy()).otherTransactionMethod()”方式解决，在声明式事务要得到支持需要使用来开启。\n\n9.4.5 多事务语义配置及最佳实践\n什么是多事务语义？说白了就是为不同的Bean配置不同的事务属性，因为我们项目中不可能就几个Bean，而可能很多，这可能需要为Bean分组，为不同组的Bean配置不同的事务语义。在Spring中，可以通过配置多切入点和多事务通知并通过不同方式组合使用即可。\n\n```\n   1、首先看下声明式事务配置的最佳实践吧：\n\n<tx:advice id=\"txAdvice\" transaction-manager=\"txManager\">\n<tx:attributes>\n           <tx:method name=\"save*\" propagation=\"REQUIRED\" />\n           <tx:method name=\"add*\" propagation=\"REQUIRED\" />\n           <tx:method name=\"create*\" propagation=\"REQUIRED\" />\n           <tx:method name=\"insert*\" propagation=\"REQUIRED\" />\n           <tx:method name=\"update*\" propagation=\"REQUIRED\" />\n           <tx:method name=\"merge*\" propagation=\"REQUIRED\" />\n           <tx:method name=\"del*\" propagation=\"REQUIRED\" />\n           <tx:method name=\"remove*\" propagation=\"REQUIRED\" />\n           <tx:method name=\"put*\" propagation=\"REQUIRED\" />\n           <tx:method name=\"get*\" propagation=\"SUPPORTS\" read-only=\"true\" />\n           <tx:method name=\"count*\" propagation=\"SUPPORTS\" read-only=\"true\" />\n          <tx:method name=\"find*\" propagation=\"SUPPORTS\" read-only=\"true\" />\n          <tx:method name=\"list*\" propagation=\"SUPPORTS\" read-only=\"true\" />\n          <tx:method name=\"*\" propagation=\"SUPPORTS\" read-only=\"true\" />\n       </tx:attributes>\n</tx:advice>\n\n       \n       \n\n```\n\n该声明式事务配置可以应付常见的CRUD接口定义，并实现事务管理，我们只需修改切入点表达式来拦截我们的业务实现从而对其应用事务属性就可以了，如果还有更复杂的事务属性直接添加即可，即\n\n如果我们有一个batchSaveOrUpdate方法需要“REQUIRES_NEW”事务传播行为，则直接添加如下配置即可：\n\njava代码：\n1\n<tx:method name=\"batchSaveOrUpdate\" propagation=\"REQUIRES_NEW\" />\n2、接下来看一下多事务语义配置吧，声明式事务最佳实践中已经配置了通用事务属性，因此可以针对需要其他事务属性的业务方法进行特例化配置：\n\n```\njava代码：\n<tx:advice id=\"noTxAdvice\" transaction-manager=\"txManager\">\n    <tx:attributes>\n           <tx:method name=\"*\" propagation=\"NEVER\" />\n    </tx:attributes>\n</tx:advice>\n\n       \n       \n\n```\n\n该声明将对切入点匹配的方法所在事务应用“Never”传播行为。\n\n多事务语义配置时，切入点一定不要叠加，否则将应用两次事务属性，造成不必要的错误及麻烦。\n\n### @Transactional实现事务管理\n\n对声明式事务管理，Spring提供基于@Transactional注解方式来实现，但需要Java 5+。\n\n注解方式是最简单的事务配置方式，可以直接在Java源代码中声明事务属性，且对于每一个业务类或方法如果需要事务都必须使用此注解。\n\n接下来学习一下注解事务的使用吧：\n\n1、定义业务逻辑实现：\n\n```\npackage cn.javass.spring.chapter9.service.impl;\n//省略import\npublic class AnnotationUserServiceImpl implements IUserService {\n    private IUserDao userDao;\n    private IAddressService addressService;\n    public void setUserDao(IUserDao userDao) {\n        this.userDao = userDao;\n    }\n    public void setAddressService(IAddressService addressService) {\n        this.addressService = addressService;\n    }\n    @Transactional(propagation=Propagation.REQUIRED, isolation=Isolation.READ_COMMITTED)\n    @Override\n    public void save(final UserModel user) {\n        userDao.save(user);\n        user.getAddress().setUserId(user.getId());\n        addressService.save(user.getAddress());\n    }\n    @Transactional(propagation=Propagation.REQUIRED, isolation=Isolation.READ_COMMITTED, readOnly=true)\n    @Override\n    public int countAll() {\n        return userDao.countAll();\n    }\n}\n```\n\n2、定义配置文件（chapter9/service/ applicationContext-service-annotation.xml）：\n\n2.1、XML命名空间定义，定义用于事务支持的tx命名空间和AOP支持的aop命名空间：\n\njava代码：\n\n```\n    <beans xmlns=\"http://www.springframework.org/schema/beans\"\n          xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n        xmlns:tx=\"http://www.springframework.org/schema/tx\"\n          xmlns:aop=\"http://www.springframework.org/schema/aop\"\n          xsi:schemaLocation=\"\n\n    http://www.springframework.org/schema/beans\n\n    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd\n\n    http://www.springframework.org/schema/tx\n\n    http://www.springframework.org/schema/tx/spring-tx-3.0.xsd\n\n    http://www.springframework.org/schema/aop\n\n    http://www.springframework.org/schema/aop/spring-aop-3.0.xsd\">\n```\n\n2.2、业务实现配置，非常简单，使用以前定义的非侵入式业务实现：\n\n```\njava代码：\n<bean id=\"userService\" class=\"cn.javass.spring.chapter9.service.impl.ConfigUserServiceImpl\">\n    <property name=\"userDao\" ref=\"userDao\"/>\n    <property name=\"addressService\" ref=\"addressService\"/>\n</bean>\n<bean id=\"addressService\" class=\"cn.javass.spring.chapter9.service.impl.ConfigAddressServiceImpl\">\n    <property name=\"addressDao\" ref=\"addressDao\"/>\n</bean>\n```\n\n2.3、事务相关配置：\n\n```\n\njava代码：\n1\n<tx:annotation-driven transaction-manager=\"txManager\"/>\n使用如上配置已支持声明式事务。\n\n3、修改测试方法并测试该配置方式是否好用：\n\n将TransactionTest 类的testServiceTransaction测试方法拷贝一份命名为testAnntationTransactionTest：\n\nclasspath:chapter9/service/applicationContext-service-annotation.xml\"\n\nuserService.save(user);\ntry {\n    userService.save(user);\n    Assert.fail();\n} catch (RuntimeException e) {\n}\nAssert.assertEquals(0, userService.countAll());\nAssert.assertEquals(0, addressService.countAll());\n```\n\n4、执行测试，测试正常通过，说明该方式能正常工作，因为在AnnotationAddressServiceImpl类的save方法中抛出异常，因此事务需要回滚，所以两个countAll操作都返回0。\n\n9.4.7 @Transactional配置详解\nSpring提供的<tx:annotation-driven/>用于开启对注解事务管理的支持，从而能识别Bean类上的@Transactional注解元数据，其具有以下属性：\n\ntransaction-manager：指定事务管理器名字，默认为transactionManager，当使用其他名字时需要明确指定；\nproxy-target-class：表示将使用的代码机制，默认false表示使用JDK代理，如果为true将使用CGLIB代理\norder：定义事务通知顺序，默认Ordered.LOWEST_PRECEDENCE，表示将顺序决定权交给AOP来处理。\nSpring使用@Transactional 来指定事务属性，可以在接口、类或方法上指定，如果类和方法上都指定了@Transactional ，则方法上的事务属性被优先使用，具体属性如下：\n\nvalue：指定事务管理器名字，默认使用<tx:annotation-driven/>指定的事务管理器，用于支持多事务管理器环境；\npropagation：指定事务传播行为，默认为Required，使用Propagation.REQUIRED指定；\nisolation：指定事务隔离级别，默认为“DEFAULT”，使用Isolation.DEFAULT指定；\nreadOnly：指定事务是否只读，默认false表示事务非只读；\ntimeout：指定事务超时时间，以秒为单位，默认-1表示事务超时将依赖于底层事务系统；\nrollbackFor：指定一组异常类，遇到该类异常将回滚事务；\nrollbackForClassname：指定一组异常类名字，其含义与<tx:method>中的rollback-for属性语义完全一样；\nnoRollbackFor：指定一组异常类，即使遇到该类异常也将提交事务，即不回滚事务；\nnoRollbackForClassname：指定一组异常类名字，其含义与<tx:method>中的no-rollback-for属性语义完全一样；\nSpring提供的@Transactional 注解事务管理内部同样利用环绕通知TransactionInterceptor实现事务的开启及关闭。\n\n使用@Transactional注解事务管理需要特别注意以下几点：\n\n如果在接口、实现类或方法上都指定了@Transactional 注解，则优先级顺序为方法>实现类>接口；\n建议只在实现类或实现类的方法上使用@Transactional，而不要在接口上使用，这是因为如果使用JDK代理机制是没问题，因为其使用基于接口的代理；而使用使用CGLIB代理机制时就会遇到问题，因为其使用基于类的代理而不是接口，这是因为接口上的@Transactional注解是“不能继承的”；\n\n```\n具体请参考基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务（@Trasactional）到底有什么区别。\n```\n\n在Spring代理机制下(不管是JDK动态代理还是CGLIB代理)，“自我调用”同样不会应用相应的事务属性，其语义和<tx:tags>中一样；\n\n\n默认只对RuntimeException异常回滚；\n\n在使用Spring代理时，默认只有在public可见度的方法的@Transactional 注解才是有效的，其它可见度（protected、private、包可见）的方法上即使有@Transactional\n注解也不会应用这些事务属性的，Spring也不会报错，如果你非要使用非公共方法注解事务管理的话，可考虑使用AspectJ。\n\n\n## 微信公众号\n\n### 个人公众号：黄小斜\n\n黄小斜是跨考软件工程的 985 硕士，自学 Java 两年，拿到了 BAT 等近十家大厂 offer，从技术小白成长为阿里工程师。\n\n作者专注于 JAVA 后端技术栈，热衷于分享程序员干货、学习经验、求职心得和程序人生，目前黄小斜的CSDN博客有百万+访问量，知乎粉丝2W+，全网已有10W+读者。\n\n黄小斜是一个斜杠青年，坚持学习和写作，相信终身学习的力量，希望和更多的程序员交朋友，一起进步和成长！\n\n**原创电子书:**\n关注公众号【黄小斜】后回复【原创电子书】即可领取我原创的电子书《菜鸟程序员修炼手册：从技术小白到阿里巴巴Java工程师》\n\n**程序员3T技术学习资源：** 一些程序员学习技术的资源大礼包，关注公众号后，后台回复关键字 **“资料”** 即可免费无套路获取。\n\n**考研复习资料：**\n计算机考研大礼包，都是我自己考研复习时用的一些复习资料,包括公共课和专业的复习视频，这里也推荐给大家，关注公众号后，后台回复关键字 **“考研”** 即可免费获取。\n\n![](https://img-blog.csdnimg.cn/20190829222750556.jpg)\n\n\n### 技术公众号：Java技术江湖\n\n如果大家想要实时关注我更新的文章以及分享的干货的话，可以关注我的公众号【Java技术江湖】一位阿里 Java 工程师的技术小站，作者黄小斜，专注 Java 相关技术：SSM、SpringBoot、MySQL、分布式、中间件、集群、Linux、网络、多线程，偶尔讲点Docker、ELK，同时也分享技术干货和学习经验，致力于Java全栈开发！\n\n**Java工程师必备学习资源:** 一些Java工程师常用学习资源，关注公众号后，后台回复关键字 **“Java”** 即可免费无套路获取。\n\n![我的公众号](https://img-blog.csdnimg.cn/20190805090108984.jpg)\n \n"
  },
  {
    "path": "docs/Spring全家桶/Spring源码分析/Spring源码剖析：Spring事务源码剖析.md",
    "content": "# 目录\n* [声明式事务使用](#声明式事务使用)\n* [TxNamespaceHandler](#txnamespacehandler)\n* [注册事务功能bean](#注册事务功能bean)\n* [使用bean的后处理方法获取增强器](#使用bean的后处理方法获取增强器)\n* [Spring获取匹配的增强器](#spring获取匹配的增强器)\n* [Transactional注解](#transactional注解)\n* [开启事务过程](#开启事务过程)\n\n\n转自：http://www.linkedkeeper.com/detail/blog.action?bid=1045\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章将同步到我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《Spring和SpringMVC源码分析》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从spring基础入手，一步步地学习spring基础和springmvc的框架知识，并上手进行项目实战，spring框架是每一个Java工程师必须要学习和理解的知识点，进一步来说，你还需要掌握spring甚至是springmvc的源码以及实现原理，才能更完整地了解整个spring技术体系，形成自己的知识框架。\n\n后续还会有springboot和springcloud的技术专题，陆续为大家带来，敬请期待。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供部分知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->  \n## 声明式事务使用\n\nSpring事务是我们日常工作中经常使用的一项技术，Spring提供了编程、注解、aop切面三种方式供我们使用Spring事务，其中编程式事务因为对代码入侵较大所以不被推荐使用，注解和aop切面的方式可以基于需求自行选择，我们以注解的方式为例来分析Spring事务的原理和源码实现。\n\n首先我们简单看一下Spring事务的使用方式，配置：\n\n```  \n<tx:annotation-driven transaction-manager=\"transactionManager\"/>  \n <bean id=\"transactionManager\"         class=\"org.springframework.jdbc.datasource.DataSourceTransactionManager\">  \n     <property name=\"dataSource\" ref=\"dataSource\"/> </bean>```  \n  \n在需要开启事务的方法上加上@Transactional注解即可，这里需要注意的是，当<tx:annotation-driven>标签在不指定transaction-manager属性的时候，会默认寻找id固定名为transactionManager的bean作为事务管理器，如果没有id为transactionManager的bean并且在使用@Transactional注解时也没有指定value（事务管理器），程序就会报错。当我们在配置两个以上的<tx:annotation-driven>标签时，如下：</tx:annotation-driven></tx:annotation-driven>  \n```\n```\n    <tx:annotation-driven transaction-manager=\"transactionManager1\"/><bean id=\"transactionManager1\"   \n        class=\"org.springframework.jdbc.datasource.DataSourceTransactionManager\">  \n    <property name=\"dataSource\" ref=\"dataSource1\"/></bean>  \n<tx:annotation-driven transaction-manager=\"transactionManager2\"/>  \n<bean id=\"transactionManager2\"   \nclass=\"org.springframework.jdbc.datasource.DataSourceTransactionManager\">  \n<property name=\"dataSource\" ref=\"dataSource2\"/></bean>\n```  \n  \n这时第一个<tx:annotation-driven>会生效，也就是当我们使用@Transactional注解时不指定事务管理器，默认使用的事务管理器是transactionManager1，后文分析源码时会具体提到这些注意点。</tx:annotation-driven>  \n  \n下面我们开始分析Spring的相关源码，首先看一下对<tx:annotation-driven>标签的解析，这里需要读者对Spring自定义标签解析的过程有一定的了解，笔者后续也会出相关的文章。锁定TxNamespaceHandler：</tx:annotation-driven>  \n  \n## TxNamespaceHandler  \n  \n(右键可查看大图)  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142033158-801757952.jpg)  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142035853-1216561547.jpg)  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142050711-1788774364.jpg)  \n  \n## 注册事务功能bean  \n  \n这个方法比较长，关键的部分做了标记，最外围的if判断限制了<tx:annotation-driven>标签只能被解析一次，所以只有第一次被解析的标签会生效。蓝色框的部分分别注册了三个BeanDefinition，分别为AnnotationTransactionAttributeSource、TransactionInterceptor和BeanFactoryTransactionAttributeSourceAdvisor，并将前两个BeanDefinition添加到第三个BeanDefinition的属性当中，这三个bean支撑了整个事务功能，后面会详细说明。我们先来看红色框的第个方法：</tx:annotation-driven>  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142052962-1382540518.jpg)  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142056340-212555152.jpg)  \n  \n还记得当<tx:annotation-driven>标签在不指定transaction-manager属性的时候，会默认寻找id固定名为transactionManager的bean作为事务管理器这个注意事项么，就是在这里实现的。下面我们来看红色框的第二个方法：</tx:annotation-driven>  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142058845-60551057.jpg)  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142100099-521338949.jpg)  \n  \n这两个方法的主要目的是注册InfrastructureAdvisorAutoProxyCreator，注册这个类的目的是什么呢？我们看下这个类的层次：  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142103625-1672142980.jpg)  \n  \n## 使用bean的后处理方法获取增强器  \n  \n我们发现这个类间接实现了BeanPostProcessor接口，我们知道，Spring会保证所有bean在实例化的时候都会调用其postProcessAfterInitialization方法，我们可以使用这个方法包装和改变bean，而真正实现这个方法是在其父类AbstractAutoProxyCreator类中：  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142105124-1636877804.jpg)  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142112838-373935120.jpg)  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142114588-713887840.jpg)  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142118835-2064313605.jpg)  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142119676-1959254739.jpg)  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142131238-2064733556.jpg)  \n  \n上面这个方法相信大家已经看出了它的目的，先找出所有对应Advisor的类的beanName，再通过beanFactory.getBean方法获取这些bean并返回。不知道大家还是否记得在文章开始的时候提到的三个类，其中BeanFactoryTransactionAttributeSourceAdvisor实现了Advisor接口，所以这个bean就会在此被提取出来，而另外两个bean被织入了BeanFactoryTransactionAttributeSourceAdvisor当中，所以也会一起被提取出来，下图为BeanFactoryTransactionAttributeSourceAdvisor类的层次：  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142136219-556507874.jpg)  \n  \n## Spring获取匹配的增强器  \n  \n下面让我们来看Spring如何在所有候选的增强器中获取匹配的增强器：  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142138356-655521572.jpg)  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142145927-1806310557.jpg)  \n  \n上面的方法中提到引介增强的概念，在此做简要说明，引介增强是一种比较特殊的增强类型，它不是在目标方法周围织入增强，而是为目标类创建新的方法和属性，所以引介增强的连接点是类级别的，而非方法级别的。通过引介增强，我们可以为目标类添加一个接口的实现，即原来目标类未实现某个接口，通过引介增强可以为目标类创建实现该接口的代理，使用方法可以参考文末的引用链接。另外这个方法用两个重载的canApply方法为目标类寻找匹配的增强器，其中第一个canApply方法会调用第二个canApply方法并将第三个参数传为false：  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142150013-1860771847.jpg)  \n  \n在上面BeanFactoryTransactionAttributeSourceAdvisor类的层次中我们看到它实现了PointcutAdvisor接口，所以会调用红框中的canApply方法进行判断，第一个参数pca.getPointcut()也就是调用BeanFactoryTransactionAttributeSourceAdvisor的getPointcut方法：  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142156819-1296874034.jpg)  \n  \n这里的transactionAttributeSource也就是我们在文章开始看到的为BeanFactoryTransactionAttributeSourceAdvisor织入的两个bean中的AnnotationTransactionAttributeSource，我们以TransactionAttributeSourcePointcut作为第一个参数继续跟踪canApply方法：  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142204505-259300568.jpg)  \n  \n我们跟踪pc.getMethodMatcher()方法也就是TransactionAttributeSourcePointcut的getMethodMatcher方法是在它的父类中实现：  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142208118-661649181.jpg)  \n  \n发现方法直接返回this，也就是下面methodMatcher.matches方法就是调用TransactionAttributeSourcePointcut的matches方法：  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142210658-52846138.jpg)  \n  \n在上面我们看到其实这个tas就是AnnotationTransactionAttributeSource，这里的目的其实也就是判断我们的业务方法或者类上是否有@Transactional注解，跟踪AnnotationTransactionAttributeSource的getTransactionAttribute方法：  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142219647-1040649746.jpg)  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142229849-1651021110.jpg)  \n  \n方法中的事务声明优先级最高，如果方法上没有声明则在类上寻找：  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142231850-1851923561.jpg)  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142233762-127153561.jpg)  \n  \nthis.annotationParsers是在AnnotationTransactionAttributeSource类初始化的时候初始化的：  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142238658-1305833682.jpg)  \n  \n所以annotationParser.parseTransactionAnnotation就是调用SpringTransactionAnnotationParser的parseTransactionAnnotation方法：  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142241626-1898573511.jpg)  \n  \n至此，我们终于看到的Transactional注解，下面无疑就是解析注解当中声明的属性了：  \n  \n## Transactional注解  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142250086-1593469718.jpg)  \n  \n在这个方法中我们看到了在Transactional注解中声明的各种常用或者不常用的属性的解析，至此，事务的初始化工作算是完成了，下面开始真正的进入执行阶段。  \n  \n在上文AbstractAutoProxyCreator类的wrapIfNecessary方法中，获取到目标bean匹配的增强器之后，会为bean创建代理，这部分内容我们会在Spring AOP的文章中进行详细说明，在此简要说明方便大家理解，在执行代理类的目标方法时，会调用Advisor的getAdvice获取MethodInterceptor并执行其invoke方法，而我们本文的主角BeanFactoryTransactionAttributeSourceAdvisor的getAdvice方法会返回我们在文章开始看到的为其织入的另外一个bean，也就是TransactionInterceptor，它实现了MethodInterceptor，所以我们分析其invoke方法：  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142251964-1897702955.jpg)  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142253742-81370531.jpg)  \n  \n这个方法很长，但是整体逻辑还是非常清晰的，首选获取事务属性，这里的getTransactionAttrubuteSource()方法的返回值同样是在文章开始我们看到的被织入到TransactionInterceptor中的AnnotationTransactionAttributeSource，在事务准备阶段已经解析过事务属性并保存到缓存中，所以这里会直接从缓存中获取，接下来获取配置的TransactionManager，也就是determineTransactionManager方法，这里如果配置没有指定transaction-manager并且也没有默认id名为transactionManager的bean，就会报错，然后是针对声明式事务和编程式事务的不同处理，创建事务信息，执行目标方法，最后根据执行结果进行回滚或提交操作，我们先分析创建事务的过程。在分析之前希望大家能先去了解一下Spring的事务传播行为，有助于理解下面的源码，这里做一个简要的介绍，更详细的信息请大家自行查阅Spring官方文档，里面有更新详细的介绍。  \n  \nSpring的事务传播行为定义在Propagation这个枚举类中，一共有七种，分别为：  \n  \nREQUIRED：业务方法需要在一个容器里运行。如果方法运行时，已经处在一个事务中，那么加入到这个事务，否则自己新建一个新的事务，是默认的事务传播行为。  \n  \nNOT_SUPPORTED：声明方法不需要事务。如果方法没有关联到一个事务，容器不会为他开启事务，如果方法在一个事务中被调用，该事务会被挂起，调用结束后，原先的事务会恢复执行。  \n  \nREQUIRESNEW：不管是否存在事务，该方法总汇为自己发起一个新的事务。如果方法已经运行在一个事务中，则原有事务挂起，新的事务被创建。  \n  \nMANDATORY：该方法只能在一个已经存在的事务中执行，业务方法不能发起自己的事务。如果在没有事务的环境下被调用，容器抛出例外。  \n  \nSUPPORTS：该方法在某个事务范围内被调用，则方法成为该事务的一部分。如果方法在该事务范围外被调用，该方法就在没有事务的环境下执行。  \n  \nNEVER：该方法绝对不能在事务范围内执行。如果在就抛例外。只有该方法没有关联到任何事务，才正常执行。  \n  \nNESTED：如果一个活动的事务存在，则运行在一个嵌套的事务中。如果没有活动事务，则按REQUIRED属性执行。它使用了一个单独的事务，这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效。  \n  \n## 开启事务过程  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142254272-1473500584.jpg)  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142256723-2146158353.jpg)  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142257278-1878221643.jpg)  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142257769-599399453.jpg)  \n  \n判断当前线程是否存在事务就是判断记录的数据库连接是否为空并且transactionActive状态为true。  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142259232-1015967872.jpg)  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142301922-198146239.jpg)  \n  \nREQUIRESNEW会开启一个新事务并挂起原事务，当然开启一个新事务就需要一个新的数据库连接：  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142303284-1121880076.jpg)  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142303540-922946646.jpg)  \n  \nsuspend挂起操作主要目的是将当前connectionHolder置为null，保存原有事务信息，以便于后续恢复原有事务，并将当前正在进行的事务信息进行重置。下面我们看Spring如何开启一个新事务：  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142304817-1631199195.jpg)  \n  \n这里我们看到了数据库连接的获取，如果是新事务需要获取新一个新的数据库连接，并为其设置了隔离级别、是否只读等属性，下面就是将事务信息记录到当前线程中：  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142305129-2058808100.jpg)  \n  \n接下来就是记录事务状态并返回事务信息：  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142306070-647878384.jpg)  \n  \n然后就是我们目标业务方法的执行了，根据执行结果的不同做提交或回滚操作，我们先看一下回滚操作：  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142307229-1348831577.jpg)  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142307451-1327327647.jpg)  \n  \n其中回滚条件默认为RuntimeException或Error，我们也可以自行配置。  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142307619-1218769460.jpg)  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142309139-1058770763.jpg)  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142310021-1140102079.jpg)  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142310191-1124633997.jpg)  \n  \n保存点一般用于嵌入式事务，内嵌事务的回滚不会引起外部事务的回滚。下面我们来看新事务的回滚：  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142310897-1920299450.jpg)  \n  \n很简单，就是获取当前线程的数据库连接并调用其rollback方法进行回滚，使用的是底层数据库连接提供的API。最后还有一个清理和恢复挂起事务的操作：  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142311558-94141948.jpg)  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142312884-396652627.jpg)  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142313851-102126465.jpg)  \n  \n如果事务执行前有事务挂起，那么当前事务执行结束后需要将挂起的事务恢复，挂起事务时保存了原事务信息，重置了当前事务信息，所以恢复操作就是将当前的事务信息设置为之前保存的原事务信息。到这里事务的回滚操作就结束了，下面让我们来看事务的提交操作：  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142314316-220782134.jpg)  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142315709-533683607.jpg)  \n  \n在上文分析回滚流程中我们提到了如果当前事务不是独立的事务，也没有保存点，在回滚的时候只是设置一个回滚标记，由外部事务提交时统一进行整体事务的回滚。  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142316748-2061067152.jpg)  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190825142317597-660647393.jpg)  \n  \n提交操作也是很简单的调用数据库连接底层API的commit方法。  \n  \n参考链接：  \n  \nhttp://blog.163.com/asd_wll/blog/static/2103104020124801348674/  \n  \nhttps://docs.spring.io/spring/docs/current/spring-framework-reference/data-access.html#spring-data-tier  \n  \n  \n  \n## 微信公众号  \n  \n### 个人公众号：黄小斜  \n  \n黄小斜是跨考软件工程的 985 硕士，自学 Java 两年，拿到了 BAT 等近十家大厂 offer，从技术小白成长为阿里工程师。  \n  \n作者专注于 JAVA 后端技术栈，热衷于分享程序员干货、学习经验、求职心得和程序人生，目前黄小斜的CSDN博客有百万+访问量，知乎粉丝2W+，全网已有10W+读者。  \n  \n黄小斜是一个斜杠青年，坚持学习和写作，相信终身学习的力量，希望和更多的程序员交朋友，一起进步和成长！  \n  \n**原创电子书:**  \n关注公众号【黄小斜】后回复【原创电子书】即可领取我原创的电子书《菜鸟程序员修炼手册：从技术小白到阿里巴巴Java工程师》  \n  \n**程序员3T技术学习资源：** 一些程序员学习技术的资源大礼包，关注公众号后，后台回复关键字 **“资料”** 即可免费无套路获取。  \n  \n**考研复习资料：**  \n计算机考研大礼包，都是我自己考研复习时用的一些复习资料,包括公共课和专业的复习视频，这里也推荐给大家，关注公众号后，后台回复关键字 **“考研”** 即可免费获取。  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20190829222750556.jpg)  \n  \n  \n### 技术公众号：Java技术江湖  \n  \n如果大家想要实时关注我更新的文章以及分享的干货的话，可以关注我的公众号【Java技术江湖】一位阿里 Java 工程师的技术小站，作者黄小斜，专注 Java 相关技术：SSM、SpringBoot、MySQL、分布式、中间件、集群、Linux、网络、多线程，偶尔讲点Docker、ELK，同时也分享技术干货和学习经验，致力于Java全栈开发！  \n  \n**Java工程师必备学习资源:** 一些Java工程师常用学习资源，关注公众号后，后台回复关键字 **“Java”** 即可免费无套路获取。  \n  \n![我的公众号](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20190805090108984.jpg)"
  },
  {
    "path": "docs/Spring全家桶/Spring源码分析/Spring源码剖析：初探SpringIOC核心流程.md",
    "content": "# 目录\n* [前言](#前言)\n* [准备](#准备)\n* [读取](#读取)\n* [解析](#解析)\n* [注册](#注册)\n\n\n本文转载自互联网，侵删\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章将同步到我的个人博客：\n\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《Spring和SpringMVC源码分析》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从spring基础入手，一步步地学习spring基础和springmvc的框架知识，并上手进行项目实战，spring框架是每一个Java工程师必须要学习和理解的知识点，进一步来说，你还需要掌握spring甚至是springmvc的源码以及实现原理，才能更完整地了解整个spring技术体系，形成自己的知识框架。\n\n后续还会有springboot和springcloud的技术专题，陆续为大家带来，敬请期待。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供部分知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n## 前言\n\n本文大致地介绍了IOC容器的初始化过程，只列出了比较重要的过程和代码，可以从中看出IOC容器执行的大致流程。\n\n接下来的文章会更加深入剖析Bean容器如何解析xml，注册和初始化bean，以及如何获取bean实例等详细的过程。\n\n转自：[http://www.importnew.com/19243.html](http://www.importnew.com/19243.html)\n\n1\\. 初始化\n\n大致单步跟了下Spring IOC的初始化过程，整个脉络很庞大，初始化的过程主要就是读取XML资源，并解析，最终注册到Bean Factory中：\n\n[![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/032179be-070b-11e5-9ecf-d7befc804e9d.png \"flow\")](https://cloud.githubusercontent.com/assets/1736354/7897341/032179be-070b-11e5-9ecf-d7befc804e9d.png \"flow\")\n\n在完成初始化的过程后，Bean们就在BeanFactory中蓄势以待地等调用了。下面通过一个具体的例子，来详细地学习一下初始化过程，例如当加载下面一个bean：\n\n```\n<bean id=\"XiaoWang\" class=\"com.springstudy.talentshow.SuperInstrumentalist\">\n    <property name=\"instruments\">\n        <list>\n            <ref bean=\"piano\"/>\n            <ref bean=\"saxophone\"/>\n        </list>\n    </property>\n</bean>\n```\n\n加载时需要读取、解析、注册bean，这个过程具体的调用栈如下所示：\n[![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/8a488060-06e6-11e5-9ad9-4ddd3375984f.png \"load\")](https://cloud.githubusercontent.com/assets/1736354/7896285/8a488060-06e6-11e5-9ad9-4ddd3375984f.png \"load\")\n\n下面对每一步的关键的代码进行详细分析：\n\n## 准备\n\n保存配置位置，并刷新\n在调用ClassPathXmlApplicationContext后，先会将配置位置信息保存到configLocations，供后面解析使用，之后，会调用`AbstractApplicationContext`的refresh方法进行刷新：\n\n```\npublic ClassPathXmlApplicationContext(String[] configLocations, boolean refresh,\n        ApplicationContext parent) throws BeansException {\n\n    super(parent);\n    // 保存位置信息，比如`com/springstudy/talentshow/talent-show.xml`\n    setConfigLocations(configLocations);\n    if (refresh) {\n        // 刷新\n        refresh();\n    }\n}\n\npublic void refresh() throws BeansException, IllegalStateException {\n    synchronized (this.startupShutdownMonitor) {\n        // Prepare this context for refreshing.\n        prepareRefresh();\n        // Tell the subclass to refresh the internal bean factory.\n        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();\n        // Prepare the bean factory for use in this context.\n        prepareBeanFactory(beanFactory);\n        try {\n            // Allows post-processing of the bean factory in context subclasses.\n            postProcessBeanFactory(beanFactory);\n            // Invoke factory processors registered as beans in the context.\n            invokeBeanFactoryPostProcessors(beanFactory);\n            // Register bean processors that intercept bean creation.\n            registerBeanPostProcessors(beanFactory);\n            // Initialize message source for this context.\n            initMessageSource();\n            // Initialize event multicaster for this context.\n            initApplicationEventMulticaster();\n            // Initialize other special beans in specific context subclasses.\n            onRefresh();\n            // Check for listener beans and register them.\n            registerListeners();\n            // Instantiate all remaining (non-lazy-init) singletons.\n            finishBeanFactoryInitialization(beanFactory);\n            // Last step: publish corresponding event.\n            finishRefresh();\n        }\n        catch (BeansException ex) {\n            // Destroy already created singletons to avoid dangling resources.\n            destroyBeans();\n            // Reset 'active' flag.\n            cancelRefresh(ex);\n            // Propagate exception to caller.\n            throw ex;\n        }\n    }\n}\n```\n\n创建载入BeanFactory\n\n```\nprotected final void refreshBeanFactory() throws BeansException {\n    // ... ...\n    DefaultListableBeanFactory beanFactory = createBeanFactory();\n    // ... ...\n    loadBeanDefinitions(beanFactory);\n    // ... ...\n}\n```\n\n创建XMLBeanDefinitionReader\n\n```\nprotected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)\n     throws BeansException, IOException {\n    // Create a new XmlBeanDefinitionReader for the given BeanFactory.\n    XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);\n    // ... ...\n    // Allow a subclass to provide custom initialization of the reader,\n    // then proceed with actually loading the bean definitions.\n    initBeanDefinitionReader(beanDefinitionReader);\n    loadBeanDefinitions(beanDefinitionReader);\n```\n\n## 读取\n\n创建处理每一个resource\n\n```\npublic int loadBeanDefinitions(String location, Set<Resource> actualResources)\n     throws BeanDefinitionStoreException {\n    // ... ...\n    // 通过Location来读取Resource\n    Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);\n    int loadCount = loadBeanDefinitions(resources);\n    // ... ...\n}\n\npublic int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {\n    Assert.notNull(resources, \"Resource array must not be null\");\n    int counter = 0;\n    for (Resource resource : resources) {\n        // 载入每一个resource\n        counter += loadBeanDefinitions(resource);\n    }\n    return counter;\n}\n```\n\n处理XML每个元素\n\n```\nprotected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {\n    // ... ...\n    NodeList nl = root.getChildNodes();\n    for (int i = 0; i < nl.getLength(); i++) {\n        Node node = nl.item(i);\n        if (node instanceof Element) {\n            Element ele = (Element) node;\n            if (delegate.isDefaultNamespace(ele)) {\n                // 处理每个xml中的元素，可能是import、alias、bean\n                parseDefaultElement(ele, delegate);\n            }\n            else {\n                delegate.parseCustomElement(ele);\n            }\n        }\n    }\n    // ... ...\n}\n```\n\n解析和注册bean\n\n```\nprotected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {\n    // 解析\n    BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);\n    if (bdHolder != null) {\n        bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);\n        try {\n            // 注册\n            // Register the final decorated instance.\n            BeanDefinitionReaderUtils.registerBeanDefinition(\n                bdHolder, getReaderContext().getRegistry());\n        }\n        catch (BeanDefinitionStoreException ex) {\n            getReaderContext().error(\"Failed to register bean definition with name '\" +\n                    bdHolder.getBeanName() + \"'\", ele, ex);\n        }\n        // Send registration event.\n        getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));\n    }\n}\n```\n\n本步骤中，通过parseBeanDefinitionElement将XML的元素解析为BeanDefinition，然后存在BeanDefinitionHolder中，然后再利用BeanDefinitionHolder将BeanDefinition注册，实质就是把BeanDefinition的实例put进BeanFactory中，和后面将详细的介绍解析和注册过程。\n\n## 解析\n\n[![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/eae02bc6-06e6-11e5-941a-d1f59e3b363f.png \"process\")](https://cloud.githubusercontent.com/assets/1736354/7896302/eae02bc6-06e6-11e5-941a-d1f59e3b363f.png \"process\")\n\n处理每个Bean的元素\n\n```\npublic AbstractBeanDefinition parseBeanDefinitionElement(\n        Element ele, String beanName, BeanDefinition containingBean) {\n\n    // ... ...\n    // 创建beandefinition\n    AbstractBeanDefinition bd = createBeanDefinition(className, parent);\n\n    parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);\n    bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));\n\n    parseMetaElements(ele, bd);\n    parseLookupOverrideSubElements(ele, bd.getMethodOverrides());\n    parseReplacedMethodSubElements(ele, bd.getMethodOverrides());\n    // 处理“Constructor”\n    parseConstructorArgElements(ele, bd);\n    // 处理“Preperty”\n    parsePropertyElements(ele, bd);\n    parseQualifierElements(ele, bd);\n    // ... ...\n}\n```\n\n处理属性的值\n\n```\npublic Object parsePropertyValue(Element ele, BeanDefinition bd, String propertyName) {\n    String elementName = (propertyName != null) ?\n                    \"<property> element for property '\" + propertyName + \"'\" :\n                    \"<constructor-arg> element\";\n\n    // ... ...\n    if (hasRefAttribute) {\n    // 处理引用\n        String refName = ele.getAttribute(REF_ATTRIBUTE);\n        if (!StringUtils.hasText(refName)) {\n            error(elementName + \" contains empty 'ref' attribute\", ele);\n        }\n        RuntimeBeanReference ref = new RuntimeBeanReference(refName);\n        ref.setSource(extractSource(ele));\n        return ref;\n    }\n    else if (hasValueAttribute) {\n    // 处理值\n        TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE));\n        valueHolder.setSource(extractSource(ele));\n        return valueHolder;\n    }\n    else if (subElement != null) {\n    // 处理子类型（比如list、map等）\n        return parsePropertySubElement(subElement, bd);\n    }\n    // ... ...\n}\n\n```\n\n1.4 注册\n\n```\npublic static void registerBeanDefinition(\n        BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)\n        throws BeanDefinitionStoreException {\n\n    // Register bean definition under primary name.\n    String beanName = definitionHolder.getBeanName();\n    registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());\n\n    // Register aliases for bean name, if any.\n    String[] aliases = definitionHolder.getAliases();\n    if (aliases != null) {\n        for (String alias : aliases) {\n            registry.registerAlias(beanName, alias);\n        }\n    }\n}\n\npublic void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)\n        throws BeanDefinitionStoreException {\n\n    // ......\n\n    // 将beanDefinition注册\n    this.beanDefinitionMap.put(beanName, beanDefinition);\n\n    // ......\n}\n\n```\n\n注册过程中，最核心的一句就是：this.beanDefinitionMap.put(beanName, beanDefinition)，也就是说注册的实质就是以beanName为key，以beanDefinition为value，将其put到HashMap中。\n\n## 注册\n\n```\n    public static void registerBeanDefinition(\n        BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)\n        throws BeanDefinitionStoreException {\n\n    // Register bean definition under primary name.\n    String beanName = definitionHolder.getBeanName();\n    registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());\n\n    // Register aliases for bean name, if any.\n    String[] aliases = definitionHolder.getAliases();\n    if (aliases != null) {\n        for (String alias : aliases) {\n            registry.registerAlias(beanName, alias);\n        }\n    }\n}\n\npublic void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)\n        throws BeanDefinitionStoreException {\n\n    // ......\n\n    // 将beanDefinition注册\n    this.beanDefinitionMap.put(beanName, beanDefinition);\n\n    // ......\n\n```\n\n理解了以上两个过程，我们就可以自己实现一个简单的Spring框架了。于是，我根据自己的理解实现了一个简单的IOC框架Simple Spring，有兴趣可以看看。\n\n\n\n注册过程中，最核心的一句就是：`this.beanDefinitionMap.put(beanName, beanDefinition)`，也就是说注册的实质就是以beanName为key，以beanDefinition为value，将其put到HashMap中。\n\n### 注入依赖\n\n当完成初始化IOC容器后，如果bean没有设置lazy-init(延迟加载)属性，那么bean的实例就会在初始化IOC完成之后，及时地进行初始化。初始化时会先建立实例，然后根据配置利用反射对实例进行进一步操作，具体流程如下所示：\n[![](https://cloud.githubusercontent.com/assets/1736354/7929429/615570ea-0930-11e5-8097-ae982ef7709d.png \"bean_flow\")](https://cloud.githubusercontent.com/assets/1736354/7929429/615570ea-0930-11e5-8097-ae982ef7709d.png \"bean_flow\")\n\n创建bean的实例\n创建bean的实例过程函数调用栈如下所示：\n[![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/cec01bcc-092f-11e5-81ad-88c285f33845.png \"create_bean\")](https://cloud.githubusercontent.com/assets/1736354/7929379/cec01bcc-092f-11e5-81ad-88c285f33845.png \"create_bean\")\n\n注入bean的属性\n注入bean的属性过程函数调用栈如下所示：\n[![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/db58350e-092f-11e5-82a4-caaf349291ea.png \"inject_property\")](https://cloud.githubusercontent.com/assets/1736354/7929381/db58350e-092f-11e5-82a4-caaf349291ea.png \"inject_property\")\n\n在创建bean和注入bean的属性时，都是在doCreateBean函数中进行的，我们重点看下：\n\n```\nprotected Object doCreateBean(final String beanName, final RootBeanDefinition mbd,\n            final Object[] args) {\n        // Instantiate the bean.\n        BeanWrapper instanceWrapper = null;\n        if (mbd.isSingleton()) {\n            instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);\n        }\n        if (instanceWrapper == null) {\n            // 创建bean的实例\n            instanceWrapper = createBeanInstance(beanName, mbd, args);\n        }\n\n        // ... ...\n\n        // Initialize the bean instance.\n        Object exposedObject = bean;\n        try {\n            // 初始化bean的实例，如注入属性\n            populateBean(beanName, mbd, instanceWrapper);\n            if (exposedObject != null) {\n                exposedObject = initializeBean(beanName, exposedObject, mbd);\n            }\n        }\n\n        // ... ...\n    }\n\n```\n\n理解了以上两个过程，我们就可以自己实现一个简单的Spring框架了。于是，我根据自己的理解实现了一个简单的IOC框架[Simple Spring](https://github.com/Yikun/simple-spring)，有兴趣可以看看。\n\n\n\n## 微信公众号\n\n### 个人公众号：黄小斜\n\n黄小斜是跨考软件工程的 985 硕士，自学 Java 两年，拿到了 BAT 等近十家大厂 offer，从技术小白成长为阿里工程师。\n\n作者专注于 JAVA 后端技术栈，热衷于分享程序员干货、学习经验、求职心得和程序人生，目前黄小斜的CSDN博客有百万+访问量，知乎粉丝2W+，全网已有10W+读者。\n\n黄小斜是一个斜杠青年，坚持学习和写作，相信终身学习的力量，希望和更多的程序员交朋友，一起进步和成长！\n\n**原创电子书:**\n关注公众号【黄小斜】后回复【原创电子书】即可领取我原创的电子书《菜鸟程序员修炼手册：从技术小白到阿里巴巴Java工程师》\n\n**程序员3T技术学习资源：** 一些程序员学习技术的资源大礼包，关注公众号后，后台回复关键字 **“资料”** 即可免费无套路获取。\n\n**考研复习资料：**\n计算机考研大礼包，都是我自己考研复习时用的一些复习资料,包括公共课和专业的复习视频，这里也推荐给大家，关注公众号后，后台回复关键字 **“考研”** 即可免费获取。\n\n![](https://img-blog.csdnimg.cn/20190829222750556.jpg)\n\n\n### 技术公众号：Java技术江湖\n\n如果大家想要实时关注我更新的文章以及分享的干货的话，可以关注我的公众号【Java技术江湖】一位阿里 Java 工程师的技术小站，作者黄小斜，专注 Java 相关技术：SSM、SpringBoot、MySQL、分布式、中间件、集群、Linux、网络、多线程，偶尔讲点Docker、ELK，同时也分享技术干货和学习经验，致力于Java全栈开发！\n\n**Java工程师必备学习资源:** 一些Java工程师常用学习资源，关注公众号后，后台回复关键字 **“Java”** 即可免费无套路获取。\n\n![我的公众号](https://img-blog.csdnimg.cn/20190805090108984.jpg)\n \n\n\n"
  },
  {
    "path": "docs/Spring全家桶/Spring源码分析/Spring源码剖析：懒加载的单例Bean获取过程分析.md",
    "content": "# 目录\n\n* [前言](#前言)\n* [step1:](#step1)\n* [step2:](#step2)\n* [step3:](#step3)\n\n\n本文转自五月的仓颉 https://www.cnblogs.com/xrq730\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章将同步到我的个人博客：\n\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《Spring和SpringMVC源码分析》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从spring基础入手，一步步地学习spring基础和springmvc的框架知识，并上手进行项目实战，spring框架是每一个Java工程师必须要学习和理解的知识点，进一步来说，你还需要掌握spring甚至是springmvc的源码以及实现原理，才能更完整地了解整个spring技术体系，形成自己的知识框架。\n\n后续还会有springboot和springcloud的技术专题，陆续为大家带来，敬请期待。\n\n为了更好地总结和检验你的学习成果，本系列文章也会提供部分知识点对应的面试题以及参考答案。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n## 前言\n\nxml的读取应该是Spring的重要功能，因为Spring的大部分功能都是以配置做为切入点的。\n\n我们在静态代码块中读取配置文件可以这样做：\n\n```\n   //这样来加载配置文件    \n   XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource(\"beans.xml\")); \n```\n\n（1）XmlBeanFactory 继承 AbstractBeanDefinitionReader ，使用ResourceLoader 将资源文件路径转换为对应的Resource文件。\n\n（2）通过DocumentLoader 对 Resource 文件进行转换，将 Resource 文件转换为 Document 文件。\n\n（3）通过实现接口 BeanDefinitionDocumentReader 的 DefaultBeanDefinitionDocumentReader 类对Document 进行解析，并且使用 BeanDefinitionParserDelegate对Element进行解析。\n\n## step1:\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/bb0bf7543226c4ada238d93363f864d39da8e3e8.png)\n\n在平常开发中，我们也可以使用Resource 获取 资源文件：\n\n```\n  Resource resource = new ClassPathResource(\"application.xml\");\n  InputStream in = resource.getInputStream();\n```\n\n## step2:\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/13bd511377c0957e4ef8daebdf457585a9acabea.png)\n\n在资源实现加载之前，调用了 super(parentBeanFactory) -- /**Ignore the given dependency interface for autowiring.(忽略接口的自动装配功能)*/\n\n调用XmlBeanDefinitionReader 的 loadBeanDefinitions（）方法进行加载资源：\n\n（1） 对Resource资源进行编码\n\n（2） 通过SAX读取XML文件来创建InputSource对象\n\n（3） 核心处理\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405185226.png)\n\n可以很直观的看出来是这个function是在解析xml文件从而获得对应的Document对象。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405185240.png)\n\n在doLoadDocument方法里面还存一个方法getValidationModeForResource（）用来读取xml的验证模式。（和我关心的没什么关系，暂时不看了~）\n\n转换成document也是最常用的方法：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405185253.png)\n\n## step3\n\n/**Register the bean definitions contained in the given DOM document*/\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405185301.png)\n\n参数doc是doLoadBeanDefinitions（）方法传进来的 loadDocument 加载过来的。这边就很好的体现出了面向对象的单一全责原则，将逻辑处理委托給单一的类去处理。\n\n在这边单一逻辑处理类是： BeanDefinitionDocumentReader\n\n核心方法：<font color=\"#FF0000\">documentReader.registerBeanDefinitions(doc, createReaderContext(resource));</font>\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405185308.png)\n\n<font color=\"#FF0000\">开始解析：</font>\n\n![image-20230405185319712](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230405185319712.png)\n\n-------------\n\n在Spring的xml配置中有两种方式来声明bean:\n\n一种是默认的： <bean id = \" \" class = \" \" />\n\n还有一种是自定义的： < tx : annotation-driven / >\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405185328.png)\n\n通过xml配置文件的默认配置空间来判断：http://www.springframework.org/schema/beans\n\n对于默认标签的解析：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405185340.png)\n\n对Bean 配置的解析：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405185347.png)\n\n**BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); 返回BeanDefinitionHolder**\n\n![image-20230405185356720](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230405185356720.png)\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405185402.png)\n\n这边代码大致看下来：\n\n1.  提取元素中的id和name属性\n2.  进一步解析将其他属性封装到 BeanDefinition 的实现类中\n3.  如果没有指定beanName 变使用默认规则生成beanName\n4.  封装类BeanDefinitionHolder\n\n可以先了解一下 BeanDefinition 这个类的作用。\n\nBeanDefinition是一个接口，对应着配置文件中<bean>里面的所有配置，在Spring中存在着三个实现类：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405185457.png)\n\n在配置文件中，可以定义父<bean>和子<bean>，父<bean>是用RootDefinition来表示，子<bean>是用ChildBeanDefinition来表示。\n\nSpring 通过BeanDefiniton将配置文件中的<bean>配置信息转换为容器内部表示，并且将这些BeanDefinition注册到BeanDefinitonRegistry中。\n\nSpring容器的BeanDefinitonRegistry就像是Spring配置信息的内存数据库，主要是以map的形式保存的。\n\n因此解析属性首先要创建用于承载属性的实例：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405185505.png)\n\n然后就是各种对属性的解析的具体方法：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405185520.png)\n\n\n## 微信公众号\n\n### 个人公众号：黄小斜\n\n黄小斜是跨考软件工程的 985 硕士，自学 Java 两年，拿到了 BAT 等近十家大厂 offer，从技术小白成长为阿里工程师。\n\n作者专注于 JAVA 后端技术栈，热衷于分享程序员干货、学习经验、求职心得和程序人生，目前黄小斜的CSDN博客有百万+访问量，知乎粉丝2W+，全网已有10W+读者。\n\n黄小斜是一个斜杠青年，坚持学习和写作，相信终身学习的力量，希望和更多的程序员交朋友，一起进步和成长！\n\n**原创电子书:**\n关注公众号【黄小斜】后回复【原创电子书】即可领取我原创的电子书《菜鸟程序员修炼手册：从技术小白到阿里巴巴Java工程师》\n\n**程序员3T技术学习资源：** 一些程序员学习技术的资源大礼包，关注公众号后，后台回复关键字 **“资料”** 即可免费无套路获取。\n\n**考研复习资料：**\n计算机考研大礼包，都是我自己考研复习时用的一些复习资料,包括公共课和专业的复习视频，这里也推荐给大家，关注公众号后，后台回复关键字 **“考研”** 即可免费获取。\n\n![](https://img-blog.csdnimg.cn/20190829222750556.jpg)\n\n\n### 技术公众号：Java技术江湖\n\n如果大家想要实时关注我更新的文章以及分享的干货的话，可以关注我的公众号【Java技术江湖】一位阿里 Java 工程师的技术小站，作者黄小斜，专注 Java 相关技术：SSM、SpringBoot、MySQL、分布式、中间件、集群、Linux、网络、多线程，偶尔讲点Docker、ELK，同时也分享技术干货和学习经验，致力于Java全栈开发！\n\n**Java工程师必备学习资源:** 一些Java工程师常用学习资源，关注公众号后，后台回复关键字 **“Java”** 即可免费无套路获取。\n\n![我的公众号](https://img-blog.csdnimg.cn/20190805090108984.jpg)\n\n"
  },
  {
    "path": "docs/Spring全家桶/Spring源码分析/Spring组件分析/Spring组件之ApplicationContext.md",
    "content": "### 1. `ApplicationContext` \n\n spring ʱһ\n\n```\nApplicationContext context = new AnnotationConfigApplicationContext(Main.class);\n\n```\n\n\n\n```\nApplicationContext context = new AnnotationConfigWebApplicationContext();\ncontext.register(MvcConfig.class);\ncontext.refresh();\n\n```\n\n `AnnotationConfigApplicationContext`  `AnnotationConfigWebApplicationContext`  `ApplicationContext`սл `AbstractApplicationContext#refresh`  spring \n\n`ApplicationContext` Ϊ **spring Ӧ**Իȡ spring ڼĸϢ `BeanFactory``Environment` ȣ spring Ҫһࡣ\n\n`ApplicationContext` ̳еĽӿ£\n\n```\npublic interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, \n        HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, \n        ResourcePatternResolver {\n    ...\n}\n\n```\n\nԿ`ApplicationContext` ҲӿڵӽӿڣЩӿڵĹ£\n\n*   `EnvironmentCapable`ṩ˻ùܣ`applicationContext` ʵлһ `Environment` ͵ĳԱͨ `EnvironmentCapable#getEnvironment()` ȡ\n*   `ListableBeanFactory``BeanFactory` ӽӿڣṩо `BeanFactory`  `bean` ķ\n*   `HierarchicalBeanFactory``BeanFactory` ӽӿڣṩ `BeanFactory` Ƽ̳еԻȡ `BeanFactory`\n*   `MessageSource`ָϢԴʵֹʻ\n*   `ApplicationEventPublisher`¼ṩ `publishEvent(...)` ¼\n*   `ResourcePatternResolver`Դṩ˻ȡԴ`Resource`ķ`getResources(...)`\n\n `ApplicationContext` ṩķ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-82de66bd51d650aa8a1fda29bdda3efd6b4.png)\n\nԿķࡣ\n\n### 2. `ApplicationContext` ̳нṹ\n\n `ApplicationContext` У`ApplicationContext` ҪΪϵǼ£\n\n*    web ͵ `ApplicationContext`ͨ java Ӧõ `ApplicationContext`Ϊ `AnnotationConfigApplicationContext`\n*   web ͵ `ApplicationContext` web Ӧõ `ApplicationContext`Ϊ `AnnotationConfigWebApplicationContext`ֻ `servlet` ͵ web `reactive`  web\n\n `AnnotationConfigApplicationContext` ļ̳нṹ\n\n![ͼƬ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-6e20477e8f5948894a5f241cd41038cfa15.png)\n\n `AnnotationConfigWebApplicationContext` ļ̳нṹ\n\n![ͼƬ](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-23a6c84d82370afe39245a568cbcf918209.png)\n\n### 3\\.  bean лȡ `ApplicationContext`\n\n spring bean лȡ `ApplicationContext`ͨ `ApplicationContextAware` ӿ\n\n```\n@Component\npublic class TestBean implements ApplicationContextAware {\n\n    private ApplicationContext applicationContext;\n\n    @Override\n    public void setApplicationContext(ApplicationContext applicationContext) \n            throws BeansException {\n        this.applicationContext = applicationContext;\n    }\n\n    // \n\n}\n\n```\n\n̳ `ApplicationContextAware` `ApplicationContextAwareProcessor` ڳʼɺ `setApplicationContext(xxx)` ֻҪ `TestBean` άһԱ `applicationContext` 漴ɡ\n\n### 4. `ApplicationContextAwareProcessor`\n\n`ApplicationContextAwareProcessor` һ `BeanPostProcessor`Ҫע `ApplicationContextAwareProcessor#postProcessBeforeInitialization` £\n\n```\n@Override\n@Nullable\npublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {\n    /**\n     * applicationContext  EnvironmentResourceLoader\n     * ApplicationEventPublisherMessageSource ȵ࣬Щawareӿڵĵã\n     * ͨ applicationContext \n     */\n    if (!(bean instanceof EnvironmentAware || bean instanceof EmbeddedValueResolverAware ||\n            bean instanceof ResourceLoaderAware || bean instanceof ApplicationEventPublisherAware ||\n            bean instanceof MessageSourceAware || bean instanceof ApplicationContextAware)){\n        return bean;\n    }\n    AccessControlContext acc = null;\n    if (System.getSecurityManager() != null) {\n        acc = this.applicationContext.getBeanFactory().getAccessControlContext();\n    }\n    if (acc != null) {\n        AccessController.doPrivileged((PrivilegedAction<Object>) () -> {\n            invokeAwareInterfaces(bean);\n            return null;\n        }, acc);\n    }\n    else {\n        invokeAwareInterfaces(bean);\n    }\n    return bean;\n}\n\n/**\n *  Aware ӿڵķ\n * EmbeddedValueResolverAware⣬Ĵ this.applicationContext\n */\nprivate void invokeAwareInterfaces(Object bean) {\n    if (bean instanceof EnvironmentAware) {\n        ((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());\n    }\n    if (bean instanceof EmbeddedValueResolverAware) {\n        // עembeddedValueResolverĻȡ£\n        // new EmbeddedValueResolver(applicationContext.getBeanFactory());\n        ((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);\n    }\n    if (bean instanceof ResourceLoaderAware) {\n        ((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);\n    }\n    if (bean instanceof ApplicationEventPublisherAware) {\n        ((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(\n                this.applicationContext);\n    }\n    if (bean instanceof MessageSourceAware) {\n        ((MessageSourceAware) bean).setMessageSource(this.applicationContext);\n    }\n    // װ ʵApplicationContextAware applicationContext\n    if (bean instanceof ApplicationContextAware) {\n        ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);\n    }\n}\n\n```\n\nǱȽϼ򵥵ģж bean ͣȻת÷\n\n### 5. `ApplicationContext`  `BeanFactory` Ĺϵ\n\n `ApplicationContext`  `BeanFactory` ߵĹϵһʼ˵ `ApplicationContext` ̳ `BeanFactory` ĽӿڣΪǼ̳йϵ˼̳йϵ⣬ǻϹϵ`ApplicationContext`  `BeanFactory` Ķֱӿ룺\n\n `AnnotationConfigApplicationContext``beanFactory` ֵ£\n\n```\npublic class GenericApplicationContext extends AbstractApplicationContext \n        implements BeanDefinitionRegistry {\n\n    // ǳе beanFactory \n    private final DefaultListableBeanFactory beanFactory;\n\n    public GenericApplicationContext() {\n        this.beanFactory = new DefaultListableBeanFactory();\n    }\n\n    @Override\n    public final ConfigurableListableBeanFactory getBeanFactory() {\n        return this.beanFactory;\n    }\n\n    ...\n\n}\n\n```\n\n `AnnotationConfigWebApplicationContext``beanFactory` ֵ£\n\n```\npublic abstract class AbstractRefreshableApplicationContext extends AbstractApplicationContext {\n\n    // ǳе beanFactory \n    @Nullable\n    private DefaultListableBeanFactory beanFactory;\n\n    @Override\n    protected final void refreshBeanFactory() throws BeansException {\n        // жϵǰApplicationContextǷBeanFactoryڵĻ Beanر BeanFactory\n        if (hasBeanFactory()) {\n            destroyBeans();\n            closeBeanFactory();\n        }\n        try {\n            // ʼDefaultListableBeanFactoryĴ\n            DefaultListableBeanFactory beanFactory = createBeanFactory();\n            beanFactory.setSerializationId(getId());\n\n            //  BeanFactory ԣǷ Bean ǡǷѭ\n            customizeBeanFactory(beanFactory);\n\n            //  Bean  BeanFactory \n            loadBeanDefinitions(beanFactory);\n            synchronized (this.beanFactoryMonitor) {\n                this.beanFactory = beanFactory;\n            }\n        }\n        catch (IOException ex) {\n            ...\n        }\n    }\n\n    //  beanFactory\n    protected DefaultListableBeanFactory createBeanFactory() {\n        // ָbeanFactory\n        return new DefaultListableBeanFactory(getInternalParentBeanFactory());\n    }\n\n    // ȡ beanFactory\n    @Override\n    public final ConfigurableListableBeanFactory getBeanFactory() {\n        synchronized (this.beanFactoryMonitor) {\n            if (this.beanFactory == null) {\n                ...\n            }\n            return this.beanFactory;\n        }\n    }\n\n    ...\n\n}\n\n```\n\n`BeanFactory` طʵ£\n\n```\npublic abstract class AbstractApplicationContext extends DefaultResourceLoader\n        implements ConfigurableApplicationContext {### 1\\. ʲô `BeanDefinition`\n\n`BeanDefinition`  `bean` spring bean Ϣ\n\n java УһԪϢ췽ԱԱȣʹõ `Class` ࣬һ`.class` ļص jvm 󣬶һ `Class` ڶʵʱ͸ `Class` Ϣɡ\n\n spring УҲôһ bean Ϣ `BeanDefinition` spring bean ɣγʼٵȣֵ֧Ĳַ\n\n```\npublic interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {\n\n    /**\n     * ø BeanDefinition\n     * BeanDefinition иƼ̳еĸָ˸BeanDefinition\n     * ʵbeanʱϲBeanDefinition\n     */\n    void setParentName(@Nullable String parentName);\n\n    /**\n     * ȡBean\n     */\n    @Nullable\n    String getParentName();\n\n    /**\n     * beanClass\n     * ʵʱʵ Class Ķ\n     */\n    void setBeanClassName(@Nullable String beanClassName);\n\n    /**\n     * ȡbeanClass\n     */\n    @Nullable\n    String getBeanClassName();\n\n    /**\n     * bean÷Χԭ\n     */\n    void setScope(@Nullable String scope);\n\n    /**\n     * ȡbean÷Χԭ\n     */\n    @Nullable\n    String getScope();\n\n    /**\n     * \n     */\n    void setLazyInit(boolean lazyInit);\n\n    /**\n     * ǷΪ\n     */\n    boolean isLazyInit();\n\n    /**\n     * øBeanBean\n     *  @DependsOn ָbean\n     */\n    void setDependsOn(@Nullable String... dependsOn);\n\n    /**\n     * ظBean\n     */\n    @Nullable\n    String[] getDependsOn();\n\n    /**\n     * ǷΪԶעĺѡ\n     */\n    void setAutowireCandidate(boolean autowireCandidate);\n\n    /**\n     * ǷΪԶעĺѡ\n     */\n    boolean isAutowireCandidate();\n\n    /**\n     * ǷΪҪģдڶͬ͵beanʱֻҪ\n     *  @Primary \n     */\n    void setPrimary(boolean primary);\n\n    /**\n     * ǷΪҪbean\n     */\n    boolean isPrimary();\n\n    /**\n     * factoryBean\n     * ָfactoryBean\n     */\n    void setFactoryBeanName(@Nullable String factoryBeanName);\n\n    /**\n     * factoryBean\n     * ȡfactoryBean\n     */\n    @Nullable\n    String getFactoryBeanName();\n\n    /**\n     * ù\n     *  @Bean ǵķ\n     */\n    void setFactoryMethodName(@Nullable String factoryMethodName);\n\n    /**\n     * ع\n     *  @Bean ǵķ\n     */\n    @Nullable\n    String getFactoryMethodName();\n\n    /**\n     * ȡȥĲֵ\n     */\n    ConstructorArgumentValues getConstructorArgumentValues();\n\n    /**\n     * 췽Ƿв\n     */\n    default boolean hasConstructorArgumentValues() {\n        return !getConstructorArgumentValues().isEmpty();\n    }\n\n    /**\n     * ȡֵ\n     * ֵΪ췽Ĳ\n     */\n    MutablePropertyValues getPropertyValues();\n\n    /**\n     * Ƿֵ\n     */\n    default boolean hasPropertyValues() {\n        return !getPropertyValues().isEmpty();\n    }\n\n    /**\n     * óʼ\n     */\n    void setInitMethodName(@Nullable String initMethodName);\n\n    /**\n     * ȡʼ\n     */\n    @Nullable\n    String getInitMethodName();\n\n    /**\n     * ٷ\n     */\n    void setDestroyMethodName(@Nullable String destroyMethodName);\n\n    /**\n     * ȡٷ\n     */\n    @Nullable\n    String getDestroyMethodName();\n\n    /**\n     * ǷΪbean\n     */\n    boolean isSingleton();\n\n    /**\n     * ǷΪԭbean\n     */\n    boolean isPrototype();\n\n    /**\n     *  Bean ǱΪ abstractôʵΪ bean ڼ̳\n     */\n    boolean isAbstract();\n\n    ...\n\n}\n\n```\n\nԿ`BeanDefinition` ֵ֧ķǳ࣬кܶƽʱʹʱָģ\n\n*   `setScope(...)` bean ÷Χ `@Scope` ָ\n*   `setLazyInit(...)`أ `@Lazy` ָ\n*   `setDependsOn(...)` bean  `@DependsOn` ָ\n*   `setPrimary(...)`ΪҪ bean `@Primary` ָ\n*   `setFactoryMethodName(...)`ùƣ `@Bean` ǵķ\n\nעʱָģЩ `xml` ʱָģ磺\n\n*   `setInitMethodName(...)`óʼ  `init-method` ָ\n*   `setDestroyMethodName(...)`ٷ `destroy-method` ָ\n\n### 2\\. spring ṩЩ `BeanDefinition`\n\n`BeanDefinition` һӿڣǵȻֱʹã Spring ṩЩ `BeanDefinition`\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-49cbc9cb32badc1db52717cd19a9447eca7.png)\n\nspring ṩ `BeanDefinition` ͼʾļˣҪ֣\n\n*   `RootBeanDefinition`\n*   `ChildBeanDefinition`\n*   `GenericBeanDefinition`\n*   `ScannedGenericBeanDefinition`\n*   `AnnotatedGenericBeanDefinition`\n\n#### 2.1 `RootBeanDefinition`  `ChildBeanDefinition`\n\nǰᵽ `BeanDefinition` ӵĸ̳еģһ˵ǿ `RootBeanDefinition` 幫Ȼ `ChildBeanDefinition` жԵݣʾ£\n\n```\npublic static void main(String[] args) {\nAnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();\n// RootBeanDefinition\nRootBeanDefinition root = new RootBeanDefinition();\nroot.setBeanClass(User.class);\nroot.getPropertyValues().add(\"name\", \"123\");\n// ע᷽չʾʵĿвʹ\n// ʹĿʹõ BeanDefinitionRegistryPostProcessor ṩķ\ncontext.registerBeanDefinition(\"root\", root);\n\n    // ChildBeanDefinition\n    ChildBeanDefinition child1 = new ChildBeanDefinition(\"root\");\n    child1.getPropertyValues().add(\"age\", \"11\");\n    // ע᷽չʾʵĿвʹ\n    // ʹĿʹõ BeanDefinitionRegistryPostProcessor ṩķ\n    context.registerBeanDefinition(\"child1\", child1);\n\n    // ChildBeanDefinition\n    ChildBeanDefinition child2 = new ChildBeanDefinition(\"root\");\n    child2.getPropertyValues().add(\"age\", \"12\");\n    // ע᷽չʾʵĿвʹ\n    // ʹĿʹõ BeanDefinitionRegistryPostProcessor ṩķ\n    context.registerBeanDefinition(\"child2\", child2);\n    // \n    context.refresh();\n\n    User rootUser = (User) context.getBean(\"root\");\n    User child1User = (User) context.getBean(\"child1\");\n    User child2User = (User) context.getBean(\"child2\");\n    System.out.println(rootUser);\n    System.out.println(child1User);\n    System.out.println(child2User);\n}\n\n```\n\nн\n\n```\nUser{name='123', age=null}\nUser{name='123', age=11}\nUser{name='123', age=12}\n\n```\n\nԿ`child1`  `child1` гɹش `RootBeanDefinition` ̳еԡ\n\n#### 2.2 `GenericBeanDefinition`\n\nǸͨõ `BeanDefinition`ֱӼ̳ `AbstractBeanDefinition`ṩķ£\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-5ed980070b301dc84926fac2494093d4fc6.png)\n\nԿṩķ̳࣬ `AbstractBeanDefinition`һ£ҪԼ `BeanDefinition` ʱֻҪʹͿˣҲṩһʾ\n\n```\npublic static void main(String[] args) {\nAnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();\n\n    GenericBeanDefinition userBeanDefinition = new GenericBeanDefinition();\n    userBeanDefinition.setBeanClass(User.class);\n    userBeanDefinition.getPropertyValues().add(\"name\", \"123\");\n    userBeanDefinition.getPropertyValues().add(\"age\", \"11\");\n    // ע᷽չʾʵĿвʹ\n    // ʹĿʹõ BeanDefinitionRegistryPostProcessor ṩķ\n    context.registerBeanDefinition(\"user\", userBeanDefinition);\n\n    // \n    context.refresh();\n\n    User user = (User) context.getBean(\"user\");\n    System.out.println(user);\n}\n\n```\n\n### 2.3 `ScannedGenericBeanDefinition`\n\n`ScannedGenericBeanDefinition` ̳ `GenericBeanDefinition`ͬʱҲʵ `AnnotatedBeanDefinition` ӿڣṩķࣺ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-9ed3925090a9ae5fc1d4218ed5a59c2ea19.png)\n\n `GenericBeanDefinition`Ͳṩʾˡ\n\n### 2.4 `AnnotatedGenericBeanDefinition`\n\n`AnnotatedGenericBeanDefinition` ̳ `GenericBeanDefinition`ͬʱҲʵ `AnnotatedBeanDefinition` ӿڣṩķࣺ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-b2e7f49fcdcae4f7272e2670b4fbb92766a.png)\n\n `GenericBeanDefinition`Ͳṩʾˡ\n\n### 3\\.  spring е `BeanDefinition`\n\nӶ spring  `BeanDefinition`Ҫβ spring е `BeanDefinition` أ\n\n#### 3.1 demo ׼\n\n׼һ demo\n\n׼ `service`\n\n```\n@Service\npublic class Service01 {\n\n    private String name;\n\n    public void hello() {\n        System.out.println(\"hello \" + name + \", from service01\");\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n}\n\n@Service\npublic class Service02 {\n\n    private String name;\n\n    public void hello() {\n        System.out.println(\"hello \" + name + \", from service02\");\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n}\n\n```\n\nҪࣺ\n\n```\n@ComponentScan\npublic class Demo02Main {\n\n    public static void main(String[] args) {\n        AnnotationConfigApplicationContext context\n                = new AnnotationConfigApplicationContext();\n        context.register(Demo02Main.class);\n        context.refresh();\n\n        Service01 service01 = (Service01) context.getBean(\"service01\");\n        Service02 service02 = (Service02) context.getBean(\"service02\");\n        service01.hello();\n        service02.hello();\n\n    }\n}\n\n```\n\n £\n\n```\nhello null, from service01\nhello null, from service02\n\n```\n\n˵ǵѾɹˣ`service01`  `service02` Ҳʼɹˡ\n\n#### 3.2 ɹĳ\n\nǷ£`service01`  `service02` Ѿʼɹˣ˵бȻ `service01`  `service02` Ӧ `beanDefifnition` `beanDefifnition`ͱҪȻȡ `beanDefifnition`\n\nλȡ spring Ѿڵ `beanDefifnition` أο 2 ڵʾΪ `context.refresh()` ǰȡ\n\n```\npublic static void main(String[] args) {\nAnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();\ncontext.register(Demo02Main.class);\n// ȡ beanDefinitionᱨ\nBeanDefinition service01Bd = context.getBeanDefinition(\"service01\");\nservice01Bd.getPropertyValues().addPropertyValue(\"name\", \"123\");\n\n    context.refresh();\n\n    Service01 service01 = (Service01) context.getBean(\"service01\");\n    Service02 service02 = (Service02) context.getBean(\"service02\");\n    service01.hello();\n    service02.hello();\n}\n\n```\n\nУֻᱨ\n\n```\nException in thread \"main\" org.springframework.beans.factory\n.NoSuchBeanDefinitionException: No bean named 'service01' available\n\n```\n\n㣬һ뵽 `context.refresh()` ǰȡᱨ֮أ\n\n```\npublic static void main(String[] args) {\nAnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();\ncontext.register(Demo02Main.class);\ncontext.refresh();\n\n    // ȡ beanDefinition޸Ĳ\n    BeanDefinition service01Bd = context.getBeanDefinition(\"service01\");\n    service01Bd.getPropertyValues().addPropertyValue(\"name\", \"123\");\n\n    Service01 service01 = (Service01) context.getBean(\"service01\");\n    Service02 service02 = (Service02) context.getBean(\"service02\");\n    service01.hello();\n    service02.hello();\n\n}\n\n```\n\nУ£\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-268bb546197c4eb9d1686118a12fabb0009.png)\n\nȷʵûбǵ޸ҲûáڴǸ `service01`  `name` ֵָΪ `123`н `null`ûõԭ `service01`  `context.refresh()` гʼ ģô `BeanDefinition` ޸ģҲֲϡ\n\nôҪôأ\n\n#### 3.2 `BeanDefinitionRegistryPostProcessor`ƻ `beanDefinition`\n\nҪųˣоǣ`BeanFactoryPostProcessor`ĽܣĽܣԲο [spring ֮ BeanFactoryPostProcessor](https://my.oschina.net/funcy/blog/4597545)ֱӸ½ۣ\n\n> `BeanFactoryPostProcessor`  `spring beanFactory ĺô`ƻ `beanFactory` һЩΪspring Ϊṩ `BeanFactoryPostProcessor`\n>\n> *   `BeanFactoryPostProcessor`ƻ `beanFactory` Ϊ\n> *   `BeanDefinitionRegistryPostProcessor`ƻ `beanDefinition` Ϊ\n\nԣӦʹ `BeanDefinitionRegistryPostProcessor`ֱʵӿڣ\n\n```\n@Component\npublic class MyBeanDefinitionRegistryPostProcessor\nimplements BeanDefinitionRegistryPostProcessor {\n\n    @Override\n    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) \n            throws BeansException {\n        BeanDefinition service01Bd = registry.getBeanDefinition(\"service01\");\n        service01Bd.getPropertyValues().addPropertyValue(\"name\", \"123\");\n    }\n\n    @Override\n    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) \n            throws BeansException {\n        // BeanDefinitionRegistryPostProcessor  BeanFactoryPostProcessor ӽӿ\n        // postProcessBeanFactory(...)  BeanFactoryPostProcessorﲻ\n    }\n}\n\n```\n\n`main` һ£\n\n```\npublic static void main(String[] args) {\nAnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();\ncontext.register(Demo02Main.class);\ncontext.refresh();\n\n    Service01 service01 = (Service01) context.getBean(\"service01\");\n    Service02 service02 = (Service02) context.getBean(\"service02\");\n    service01.hello();\n    service02.hello();\n\n}\n\n```\n\nУ£\n\n```\nhello 123, from service01\nhello null, from service02\n\n```\n\nԿ `service01`  `name` ȷʵ `123` ˡ\n\nʵϣ`BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry` Ҫʹ `BeanDefinitionRegistry`  `BeanDefinition` Ĳֵ֧ķ£\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-5812a2cac994c5940d57c7e6ab55c23a63e.png)\n\nҪ£\n\n*   `getBeanDefinition(...)`ȡ `BeanDefinition`򷵻أ򱨴õ `BeanDefinition` 󣬾ͿԶиֲ\n*   `registerBeanDefinition(...)`ע `BeanDefinition`Զ `BeanDefinition` Ȼø÷עᵽУǰУ `context.refresh()` ǰ `context.registerBeanDefinition`Ҫ˵ǰķɣе£õ `context.refresh()`  `context` `springboot` УƼʹ `BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry` ע `BeanDefinition`\n*   `removeBeanDefinition(...)`Ƴ `BeanDefinition`һӦòõ\n*   `containsBeanDefinition(...)`жǷĳ `BeanDefinition`\n\n### 4\\. ܽ\n\nҪ `BeanDefinition` ã\n\n1.  `BeanDefinition` ķ\n2.  ͵ `BeanDefinition` ʹ\n3.  ʹ `BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry`  `BeanDefinition`Ҫעᡢ޸ `BeanDefinition`\n\n* * *\n\n_ԭӣ[https://my.oschina.net/funcy/blog/4597536](https://my.oschina.net/funcy/blog/4597536) ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע_\n\n    ...\n    @Override\n    public <T> T getBean(Class<T> requiredType) throws BeansException {\n        assertBeanFactoryActive();\n        return getBeanFactory().getBean(requiredType);\n    }\n\n    @Override\n    public <T> T getBean(Class<T> requiredType, Object... args) throws BeansException {\n        assertBeanFactoryActive();\n        return getBeanFactory().getBean(requiredType, args);\n    }\n\n    ...\n}\n\n```\n\nЩʵʱǵ `getBeanFactory()` ȡ `BeanFactory` Ȼֱӵ `BeanFactory` ķ`getBeanFactory()` õľ `GenericApplicationContext`  `AnnotationConfigWebApplicationContext`  `getBeanFactory()` \n\n `ApplicationContext` ݾͽܵˡ\n\n* * *\n\n_ԭӣ[https://my.oschina.net/funcy/blog/4597456](https://my.oschina.net/funcy/blog/4597456) ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע_"
  },
  {
    "path": "docs/Spring全家桶/Spring源码分析/Spring组件分析/Spring组件之BeanDefinition.md",
    "content": ""
  },
  {
    "path": "docs/Spring全家桶/Spring源码分析/Spring组件分析/Spring组件之BeanFactory.md",
    "content": "public interface BeanFactory {\n\n    /**\n     * factoryBeanʹ\n     */\n    String FACTORY_BEAN_PREFIX = \"&\";\n\n    /**\n     * ƻȡbean\n     */\n    Object getBean(String name) throws BeansException;\n\n    /**\n     * ƻȡbean\n     */\n    <T> T getBean(String name, Class<T> requiredType) throws BeansException;\n\n    /**\n     * ƻȡbean\n     */\n    Object getBean(String name, Object... args) throws BeansException;\n\n    /**\n     * ͻȡbean\n     */\n    <T> T getBean(Class<T> requiredType) throws BeansException;\n\n    /**\n     * ͻȡbean\n     */\n    <T> T getBean(Class<T> requiredType, Object... args) throws BeansException;\n\n    /**\n     * ȡBeanProvider\n     */\n    <T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);\n\n    /**\n     * ȡBeanProvider\n     */\n    <T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);\n\n    /**\n     * Ƿbean\n     */\n    boolean containsBean(String name);\n\n    /**\n     * ǷΪbean\n     */\n    boolean isSingleton(String name) throws NoSuchBeanDefinitionException;\n\n    /**\n     * ǷΪԭbean\n     */\n    boolean isPrototype(String name) throws NoSuchBeanDefinitionException;\n\n    /**\n     * жǷƥ\n     */\n    boolean isTypeMatch(String name, ResolvableType typeToMatch) \n            throws NoSuchBeanDefinitionException;\n\n    /**\n     * жǷƥ\n     */\n    boolean isTypeMatch(String name, Class<?> typeToMatch) \n            throws NoSuchBeanDefinitionException;\n\n    /**\n     * ƻȡbean\n     */\n    @Nullable\n    Class<?> getType(String name) throws NoSuchBeanDefinitionException;\n\n    /**\n     * ƻȡbean\n     */\n    @Nullable\n    Class<?> getType(String name, boolean allowFactoryBeanInit) \n            throws NoSuchBeanDefinitionException;\n\n    /**\n     * beanƻȡbeanı\n     */\n    String[] getAliases(String name);\n\n}\n"
  },
  {
    "path": "docs/Spring全家桶/Spring源码分析/Spring组件分析/Spring组件之BeanFactoryPostProcessor.md",
    "content": "### 1\\. ʲô `BeanFactoryPostProcessor`\n\n`BeanFactoryPostProcessor`  spring beanFactory ĺôƻ beanFactory һЩΪ\n\nspring Ϊṩ `BeanFactoryPostProcessor`\n\n* `org.springframework.beans.factory.config.BeanFactoryPostProcessor`\n\n  ```\n  /**\n   * beanFactory ĺôԸı beanFactory һЩΪ\n   */\n  public interface BeanFactoryPostProcessor {\n  \n      /**\n       *  beanFactory ķΪ beanFactoryʵ DefaultListableBeanFactory\n       */\n      void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) \n              throws BeansException;\n  \n  }\n  \n  ```\n\n* `org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor`\n\n  ```\n  /**\n   * BeanDefinition עע beanDefinition \n   * ̳ BeanFactoryPostProcessor ӿڣ\n   * Ҳд BeanFactoryPostProcessor#postProcessBeanFactory \n   * \n   */\n  public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {\n  \n      /**\n       * 1\\. ÷ BeanDefinitionRegistryPostProcessor#postProcessBeanFactory ִ\n       * 2\\. Ϊ registryʵ DefaultListableBeanFactoryҲʹ beanFactory Ĳ\n       */\n      void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) \n              throws BeansException;\n  \n  }\n  \n  ```\n\n`BeanFactoryPostProcessor`  `AbstractApplicationContext#invokeBeanFactoryPostProcessors` бִУִʱִ `BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry`ִ `BeanFactoryPostProcessor#postProcessBeanFactory`ķԲο [spring ִ֮ BeanFactoryPostProcessor](https://my.oschina.net/funcy/blog/4641114) һġ\n\n### 2. `BeanFactoryPostProcessor` ṩĹ\n\n `BeanFactoryPostProcessor` ṩĹܡ\n\n#### 2.1 `BeanFactoryPostProcessor` \n\n̽ǰ˽ `BeanFactoryPostProcessor` ṩ\n\n```\nvoid postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) \n        throws BeansException;\n\n```\n\n÷ֻһ`ConfigurableListableBeanFactory`˽ `BeanFactoryPostProcessor` ΪʲôҪ֪ṩЩܣ\n\n `set`  ![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-63b39c81bcae0b10c60a2f847c6b47af932.png)\n\n֮⣬ `register`  ![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-50c48da9b50dcf18abcd99db09142644c6c.png)\n\nЩǾͿԶƻ `beanFactory` һЩΪˡ\n\n#### 2.2 `BeanDefinitionRegistryPostProcessor` \n\nҲ˽ `BeanDefinitionRegistryPostProcessor` ṩķ\n\n```\nvoid postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) \n        throws BeansException;\n\n```\n\nĲ `BeanDefinitionRegistry`Ǹ `BeanDefinition ע`ṩ·\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-5812a2cac994c5940d57c7e6ab55c23a63e.png)\n\nԿҪΧ `BeanDefinition` ȽҪķо£\n\n*   `BeanDefinitionRegistry#containsBeanDefinition`ǷָƵ `BeanDefinition`\n*   `BeanDefinitionRegistry#getBeanDefinition`ȡָƵ `BeanDefinition`\n*   `BeanDefinitionRegistry#registerBeanDefinition`עһ `BeanDefinition`\n*   `BeanDefinitionRegistry#removeBeanDefinition`Ƴ `BeanDefinition`\n\n### 3\\. spring ṩ `BeanFactoryPostProcessor`\n\nspring һ `BeanFactoryPostProcessor` ʵ࣬Ϣ£\n\n*   `EventListenerMethodProcessor`\n\n    *   ʵ `BeanFactoryPostProcessor``SmartInitializingSingleton` ӿ\n    *    `BeanDefinitionRegistryPostProcessor#postProcessBeanFactory` лȡ `EventListenerFactory`\n    *    `SmartInitializingSingleton#afterSingletonsInstantiated` д `@EventListener` ע\n*   `ConfigurationClassPostProcessor`\n\n    *    `AnnotationConfigUtils#registerAnnotationConfigProcessors(BeanDefinitionRegistry, Object)` ע\n    *   ʵ `BeanDefinitionRegistryPostProcessor` ӿ\n    *    `@Conditional` ע\n    *    `@Component` ע\n    *    `@PropertySource/@PropertySources` ע\n    *    `@ComponentScan/@ComponentScans` ע\n    *    `@Import` ע\n    *    `@ImportResource` ע\n    *    `@Bean` ע\n    *    `@Configuration` ע\n\n `EventListenerMethodProcessor`  `@EventListener` ķԲο[spring Դspring ֮̽ע @EventListener](https://my.oschina.net/funcy/blog/4926344).\n\n `ConfigurationClassPostProcessor` ע̣Բο\n\n*   [ConfigurationClassPostProcessorһ @ComponentScan ע](https://my.oschina.net/funcy/blog/4836178)\n*   [ConfigurationClassPostProcessor @Bean ע](https://my.oschina.net/funcy/blog/4492878)\n*   [ConfigurationClassPostProcessor @Import ע](https://my.oschina.net/funcy/blog/4678152)\n*   [ConfigurationClassPostProcessorģ @Conditional ע](https://my.oschina.net/funcy/blog/4873444)\n\n### 4\\. ܽ\n\nĽ `BeanFactoryPostProcessor` ĸ˵ `BeanFactoryPostProcessor` ʹãԼ spring ṩ `BeanFactoryPostProcessor` ʵࣺ`EventListenerMethodProcessor`  `ConfigurationClassPostProcessor`\n\n* * *\n\n_ԭӣ[https://my.oschina.net/funcy/blog/4597545](https://my.oschina.net/funcy/blog/4597545) ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע_"
  },
  {
    "path": "docs/Spring全家桶/Spring源码分析/Spring组件分析/Spring组件之BeanPostProcessor.md",
    "content": "`BeanPostProcessor` Ϊ spring bean ĺô `BeanFactoryPostProcessor``BeanPostProcessor` Զ bean в\n\nspring `BeanPostProcessor`  bean ĴִУִʱ:\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-b7a4d4bc1bfbd76537e40cf843b0d18df93.png)\n\n bean ĴУ`BeanPostProcessor` һִй 8 Σ\n\n1.  ɴ\n2.  ƶϹ췽\n3.  ȡע\n4.  \n5.  ǷҪע\n6.  \n7.  ʼǰ\n8.  ʼ\n\nһЩִе `BeanPostProcessor`\n\n### 1\\. ʲô `BeanPostProcessor`\n\n `BeanPostProcessor` ǰʲô `BeanPostProcessor`£\n\n```\npublic interface BeanPostProcessor {\n\n    /**\n     * ʼǰִ\n     */\n    @Nullable\n    default Object postProcessBeforeInitialization(Object bean, String beanName) \n            throws BeansException {\n        return bean;\n    }\n\n    /**\n     * ʼִ\n     */\n    @Nullable\n    default Object postProcessAfterInitialization(Object bean, String beanName) \n            throws BeansException {\n        return bean;\n    }\n\n}\n\n```\n\n`BeanPostProcessor` һӿڣ bean ʼǰһЩǿʵӿڣдͿ bean ʼǰһЩˡ\n\n`BeanPostProcessor`  `AbstractApplicationContext#registerBeanPostProcessors` עᡣ\n\nʵϣ`BeanPostProcessor` ڶӽӿڣЩǶͳΪ `BeanPostProcessor`ĵҪĿľЩ `BeanPostProcessor`\n\n### 2. `BeanPostProcessor` \n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-102ca0d1e4db82a28871661241b05bc3956.png)\n\n#### 2.1 ɴ\n\n*   λã`AbstractAutowireCapableBeanFactory#resolveBeforeInstantiation`\n*   ִз`InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation`\n*   `AbstractAutoProxyCreator#postProcessBeforeInstantiation`ɴ\n\n#### 2.2 ƶϹ췽\n\n*   λã`AbstractAutowireCapableBeanFactory#determineConstructorsFromBeanPostProcessors`\n*   ִз`SmartInstantiationAwareBeanPostProcessor#determineCandidateConstructors`\n*   `AutowiredAnnotationBeanPostProcessor#determineCandidateConstructors`ƶϹ췽\n\n#### 2.3 ȡע\n\n*   λã`AbstractAutowireCapableBeanFactory#applyMergedBeanDefinitionPostProcessors`\n*   ִз`MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition`\n*   `ApplicationListenerDetector#postProcessMergedBeanDefinition`ռ `ApplicationListener`\n*   `AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition`ұ `@Autowired``@Value``@Inject` ǵ뷽\n*   `CommonAnnotationBeanPostProcessor#postProcessMergedBeanDefinition`ұ `@Resource` ǵ뷽\n*   `InitDestroyAnnotationBeanPostProcessor#postProcessMergedBeanDefinition`ұ `@PostConstruct``@PreDestroy` ǵķ\n\n#### 2.4 \n\n*   λãûִУ`AbstractAutowireCapableBeanFactory#doCreateBean`\n*   ִз`SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference`\n*   `AbstractAutoProxyCreator#getEarlyBeanReference`ǰɴ\n\n#### 2.5 ǷҪע\n\n*   λã`AbstractAutowireCapableBeanFactory#populateBean`\n*   ִз`InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation`\n\n#### 2.6 \n\n*   λã`AbstractAutowireCapableBeanFactory#populateBean`\n*   ִз`InstantiationAwareBeanPostProcessor#postProcessProperties`\n*   `AutowiredAnnotationBeanPostProcessor#postProcessProperties`䱻 `@Autowired``@Value``@Inject` ǵ뷽\n*   `CommonAnnotationBeanPostProcessor#postProcessProperties`䱻 `@Resource` ǵ뷽\n*   `ImportAwareBeanPostProcessor#postProcessProperties`Ϊ `EnhancedConfiguration` ʵ `beanFactory`\n\n#### 2.7 ʼǰ\n\n*   λã`AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInitialization`\n*   ִз`BeanPostProcessor#postProcessBeforeInitialization`\n*   `ApplicationContextAwareProcessor#postProcessBeforeInitialization` `XxxAware` ӿڵķ\n*   `BeanValidationPostProcessor#postProcessBeforeInitialization` `JSR-303` У\n*   `ImportAwareBeanPostProcessor#postProcessBeforeInitialization` `ImportAware` ӿڵķ\n*   `InitDestroyAnnotationBeanPostProcessor#postProcessBeforeInitialization`ñ `@PostConstruct` ǵķ\n*   `LoadTimeWeaverAwareProcessor#postProcessBeforeInitialization` `LoadTimeWeaverAware` ӿڵķ\n*   `ServletContextAwareProcessor#postProcessBeforeInitialization` `ServletContextAware` ӿڵķ `servletContext`  `servletConfig`\n\n#### 2.8 ʼ\n\n*   λã`AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsAfterInitialization`\n*   ִз`BeanPostProcessor#postProcessAfterInitialization`\n*   `AbstractAdvisingBeanPostProcessor#postProcessAfterInitialization` `AopInfrastructureBean`\n*   `AbstractAutoProxyCreator#postProcessAfterInitialization`ɴ\n*   `AdvisorAdapterRegistrationManager#postProcessAfterInitialization`ǰ bean  `AdvisorAdapter`ע\n*   `ApplicationListenerDetector#postProcessAfterInitialization`ǰ bean  `ApplicationListener`ӵ¼\n*   `BeanPostProcessorChecker#postProcessAfterInitialization`˸ log\n*   `BeanValidationPostProcessor#postProcessAfterInitialization` `JSR-303` У\n*   `JmsListenerAnnotationBeanPostProcessor#postProcessAfterInitialization` `@JmsListener` ע\n*   `ScheduledAnnotationBeanPostProcessor#postProcessAfterInitialization` `@Scheduled` ע\n*   `SimpleServletPostProcessor#postProcessAfterInitialization` `Servlet` ʵ÷ `Servlet#init(ServletConfig)`\n\n### 3\\. ܽ\n\nһܽЩ `BeanPostProcessor`:\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-b1117b66c4881f366669dab69b332164d8f.png)\n\n* * *\n\n_ԭӣ[https://my.oschina.net/funcy/blog/4597551](https://my.oschina.net/funcy/blog/4597551) ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע_"
  },
  {
    "path": "docs/Spring全家桶/Spring源码分析/Spring重要机制探秘/ConfigurationClassPostProcessor（一）：处理@ComponentScan注解.md",
    "content": " [spring ִ֮ BeanFactoryPostProcessor](https://my.oschina.net/funcy/blog/4641114) һУִ `BeanFactoryPostProcessor` УһҪᱻִе `ConfigurationClassPostProcessor`ǳҪᴦ spring ࣬ `@Component``@PropertySources``@ComponentScans``@ImportResource` ע⣬ϵ½ͨʵԴĽǶȷ spring ⼸עĴ\n\n## 1\\. ع `BeanFactoryPostProcessor` ִ\n\n `BeanFactoryPostProcessor` ִУ:\n\n```\nAnnotationConfigApplicationContext#AnnotationConfigApplicationContext(java.lang.String...)\n |-AbstractApplicationContext#refresh\n  |-AbstractApplicationContext#invokeBeanFactoryPostProcessors\n   |-PostProcessorRegistrationDelegate\n     #invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory, List)\n\n```\n\n `PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory, List)` Уε `ConfigurationClassPostProcessor` ķ\n\n*   `invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry)`õ `BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry` \n*   `invokeBeanFactoryPostProcessors(registryProcessors, beanFactory)`õ `BeanFactoryPostProcessor#postProcessBeanFactory` \n\n`ConfigurationClassPostProcessor` ͬʱʵ `BeanDefinitionRegistryPostProcessor`  `BeanFactoryPostProcessor`ִе `ConfigurationClassPostProcessor` \n\n```\n/**\n * ִ postProcessBeanDefinitionRegistry(...)\n */\n@Override\npublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {\n    int registryId = System.identityHashCode(registry);\n    if (this.registriesPostProcessed.contains(registryId)) {\n        throw new IllegalStateException(...);\n    }\n    if (this.factoriesPostProcessed.contains(registryId)) {\n        throw new IllegalStateException(...);\n    }\n    this.registriesPostProcessed.add(registryId);\n    // ֵһ\n    processConfigBeanDefinitions(registry);\n}\n\n/**\n * ִ postProcessBeanDefinitionRegistry(...) ִ postProcessBeanFactory(...)\n */\n@Override\npublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {\n    int factoryId = System.identityHashCode(beanFactory);\n    if (this.factoriesPostProcessed.contains(factoryId)) {\n        throw new IllegalStateException(\n                \"postProcessBeanFactory already called on this post-processor against \" + beanFactory);\n    }\n    this.factoriesPostProcessed.add(factoryId);\n    if (!this.registriesPostProcessed.contains(factoryId)) {\n        //  beanFactory ûбִһ processConfigBeanDefinitions \n        // һ£ﲻᱻִе\n        processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);\n    }\n    // ǿ\n    enhanceConfigurationClasses(beanFactory);\n    // Ӵ ImportAware ص BeanPostProcessor뱾ϵ󣬲\n    beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));\n}\n\n```\n\n˵£\n\n*    `PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory, List)` ִ߼ִ `postProcessBeanDefinitionRegistry(...)`ִ `postProcessBeanDefinitionRegistry(...)`\n*   `postProcessBeanDefinitionRegistry(...)` Ҫǵ `processConfigBeanDefinitions(...)` \n*   `postProcessBeanFactory(...)` жϵǰ beanFactory Ƿִй `processConfigBeanDefinitions(...)` ûУִ `processConfigBeanDefinitions(...)` ֮ `enhanceConfigurationClasses(...)` ǿ\n\nϷյõ `processConfigBeanDefinitions(...)`  `enhanceConfigurationClasses(...)`ǽص㡣\n\n## 2\\. spring δ `@ComponentScan` עģ\n\n### 2.1 demo ׼\n\nڷǰ׼ demo:\n\n׼һ࣬ `@ComponentScan` ע⣺\n\n```\n@ComponentScan(\"org.springframework.learn.explore.demo02\")\npublic class BeanConfigs {\n\n}\n\n```\n\n׼ `Bean`\n\n```\n@Component\npublic class BeanObj1 {\n\n    public BeanObj1() {\n        System.out.println(\"beanObj1Ĺ췽\");\n    }\n\n    @Override\n    public String toString() {\n        return \"BeanObj1{}\";\n    }\n}\n\n@Component\npublic class BeanObj2 {\n\n    public BeanObj2() {\n        System.out.println(\"beanObj2Ĺ췽\");\n    }\n\n    @Override\n    public String toString() {\n        return \"BeanObj2{}\";\n    }\n}\n\n```\n\nࣺ\n\n```\npublic class Demo05Main {\n    public static void main(String[] args) {\n        ApplicationContext context = new AnnotationConfigApplicationContext(BeanConfigs.class);\n        Object obj1 = context.getBean(\"beanObj1\");\n        Object obj2 = context.getBean(\"beanObj2\");\n        System.out.println(\"obj1:\" + obj1);\n        System.out.println(\"obj2:\" + obj2);\n        System.out.println(context.getBean(\"beanConfigs\"));\n    }\n}\n\n```\n\nֻ demo Ҫ֣ demo  [gitee/funcy](https://gitee.com/funcy/spring-framework/tree/v5.2.2.RELEASE_learn/spring-learn/src/main/java/org/springframework/learn/explore/demo05).\n\nУ£\n\n```\nbeanObj1Ĺ췽\nbeanObj2Ĺ췽\nobj1:BeanObj1{}\nobj2:BeanObj2{}\norg.springframework.learn.explore.demo05.BeanConfigs@13eb8acf\n\n```\n\n demo Ϊһз\n\n### 2.2 `ApplicationContext` Ĺ췽`AnnotationConfigApplicationContext(Class)`\n\nǽ `AnnotationConfigApplicationContext` Ĺ췽:\n\n```\npublic AnnotationConfigApplicationContext(Class<?>... componentClasses) {\n    this();\n    register(componentClasses);\n    // spring \n    refresh();\n}\n\n/**\n * this() ĵ\n */\npublic AnnotationConfigApplicationContext() {\n    // Աиֵ\n    // õ`AnnotationConfigApplicationContext(Class)` Բõ\n    // õ`AnnotationConfigApplicationContext(String)` ԲŻõ\n    this.reader = new AnnotatedBeanDefinitionReader(this);\n    this.scanner = new ClassPathBeanDefinitionScanner(this);\n}\n\n/**\n * register(...) \n */\n@Override\npublic void register(Class<?>... componentClasses) {\n    Assert.notEmpty(componentClasses, \"At least one component class must be specified\");\n    this.reader.register(componentClasses);\n}\n\n```\n\n`AnnotationConfigApplicationContext` Ĺ췽£\n\n*   `this()`޲ι췽ҪǸ `reader`  `scanner` Աֵ\n*   `register(componentClasses)`ע `component` ൽ `beanFactory` Уõ `reader.register(...)` \n*   `refresh()`spring Ͳ\n\nִ `register(componentClasses);` ǰ`beanFactory` ڵ `BeanDefinitionMap` £\n\nִǰ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-c317168bfde886f7817e6e9a11301c84b52.png)\n\nִк\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-cc2f7bcdb4b56f67893ac29774bb8119892.png)\n\nԿ`beanConfigs` Ѿעᵽ `beanDefinitionNames` ˡ\n\nspring ֻǰ `beanConfigs` עᵽ `beanDefinitionNames``BeanConfigs`  `new AnnotationConfigApplicationContext(BeanConfigs.class)` ģûɨ `@ComponentSacn` עָİҲ `org.springframework.learn.explore.demo05`ôɨеأӦ `ConfigurationClassPostProcessor` УǼ¿\n\n### 2.3 ࣺ`ConfigurationClassPostProcessor#processConfigBeanDefinitions`\n\nݿƪķֱӽ `ConfigurationClassPostProcessor#processConfigBeanDefinitions` £\n\n```\nAnnotationConfigApplicationContext#AnnotationConfigApplicationContext(Class)\n |-AbstractApplicationContext#refresh\n  |-AbstractApplicationContext#invokeBeanFactoryPostProcessors\n   |-PostProcessorRegistrationDelegate\n      #invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory, List)\n    |-PostProcessorRegistrationDelegate#invokeBeanDefinitionRegistryPostProcessors\n     |-ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry\n      |-ConfigurationClassPostProcessor#processConfigBeanDefinitions\n\n```\n\n£\n\n```\npublic void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {\n    List<BeanDefinitionHolder> configCandidates = new ArrayList<>();\n    // 1\\. ȡBeanDefinition\n    String[] candidateNames = registry.getBeanDefinitionNames();\n    // 2\\. ѭcandidateNames飬ʶFullLite\n    for (String beanName : candidateNames) {\n        BeanDefinition beanDef = registry.getBeanDefinition(beanName);\n        // жϵǰBeanDefinitionѾˣ˾Ͳٴ\n        if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {\n            // ֻǴ˸logʡ\n            ...\n        }\n        // жǷΪ࣬\n        //  1\\.  @Configuration ע proxyBeanMethods != false ࣬spring Ϊ Full \n        //  2\\.  @Configuration ע proxyBeanMethods == false,  @Component@ComponentScan\n        //     @Import@ImportResource@Bean ֮һע࣬spring Ϊ Lite \n        // FullLiteбʶ\n        else if (ConfigurationClassUtils\n                 .checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {\n            configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));\n        }\n    }\n    // ûֱ࣬ӷ\n    if (configCandidates.isEmpty()) {\n        return;\n    }\n\n    // ʡ޹صĴ\n    ...\n\n    // ǳҪ @Component@Importע\n    ConfigurationClassParser parser = new ConfigurationClassParser(\n            this.metadataReaderFactory, this.problemReporter, this.environment,\n            this.resourceLoader, this.componentScanBeanNameGenerator, registry);\n    // ṹcandidatesҪ࣬alreadyParsedɽ\n    Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);\n    Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());\n    do {\n        // 3\\. ࣬˺ܶ£\n        // 磺@Component@PropertySources@ComponentScans@ImportResourceע\n        // עһԽеcandidates\n        parser.parse(candidates);\n        parser.validate();\n        Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());\n        configClasses.removeAll(alreadyParsed);\n        //  readerreaderǰApplicationContextеreaderͬһ\n        if (this.reader == null) {\n            this.reader = new ConfigurationClassBeanDefinitionReader(\n                    registry, this.sourceExtractor, this.resourceLoader, this.environment,\n                    this.importBeanNameGenerator, parser.getImportRegistry());\n        }\n        // 4\\. ν\n        // ǰ@Importࡢд@Beanķ@ImportResourceԴתBeanDefinition\n        this.reader.loadBeanDefinitions(configClasses);\n        // configClasses뵽alreadyParsed\n        alreadyParsed.addAll(configClasses);\n\n        // ɺ󣬻candidatesգӵġδFullӵcandidates\n        candidates.clear();\n        // 5\\. ؽӵΪ Full δͰӵcandidatesУ´ѭʱٽ\n        // עBeanDefinition  candidateNamesбȽ\n        // ڵĻ˵µBeanDefinitionע\n        if (registry.getBeanDefinitionCount() > candidateNames.length) {\n            String[] newCandidateNames = registry.getBeanDefinitionNames();\n            Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));\n            Set<String> alreadyParsedClasses = new HashSet<>();\n            // ѭalreadyParsed뵽alreadyParsedClasses\n            for (ConfigurationClass configurationClass : alreadyParsed) {\n                alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());\n            }\n            for (String candidateName : newCandidateNames) {\n                if (!oldCandidateNames.contains(candidateName)) {\n                    BeanDefinition bd = registry.getBeanDefinition(candidateName);\n                    // ¼ӵΪ࣬δͰӵcandidatesУȴ´ѭ\n                    if (ConfigurationClassUtils\n                            .checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&\n                            !alreadyParsedClasses.contains(bd.getBeanClassName())) {\n                        candidates.add(new BeanDefinitionHolder(bd, candidateName));\n                    }\n                }\n            }\n            candidateNames = newCandidateNames;\n        }\n    }\n\n    // ʡ뱾޹صĴ\n    ...\n}\n\n```\n\nʽϷǰȷ spring ļ\n\n*   ࣺ `@Configuration``@Component``@ComponentScan``@Import``@ImportResource` ֮һעࣻ\n*   `Full` ࣺ `@Configuration` ע `proxyBeanMethods != false` ࣬`spring` Ϊ `Full` ࣻ\n*   `Lite` ࣺ `@Configuration` ע `proxyBeanMethods == false`,  `@Component``@ComponentScan``@Import``@ImportResource` ֮һע࣬`spring` Ϊ `Lite` ࡣ\n\nϷе㳤ܽ¼£\n\n1. ȡ `BeanDefinition` ⲽִɺ󣬽£\n\n   ![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-df320c2911c4222a6867a0f0a4fe7d9987d.png)\n\n2. ѭ `candidateNames` 飬ʶΪ `Full`  `Lite`һĹǶӦ `BeanDefinition` бʶڱʶʲôãں `@Configuration` עʱٷ`beanConfigs` û `@Configuration` ע⣬ `Lite` ࡣһõ `configCandidates` £\n\n   ![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-81a68ec905807d77153158af4c86d48a1d3.png)\n\n3. ࣬ `@Component``@PropertySources``@ComponentScans``@ImportResource` עע࣬ǳҪص\n\n4. ν࣬ @Import ࡢд @Bean ķ@ImportResource Դת BeanDefinitionص `BeanDefinitionMap` У\n\n5. ؽӵΪ `Full` ࣬δͰӵ `candidates` У´ѭʱٽ\n\nϷ̾ˣǾĽҲĵ 3 \n\n### 2.4 `ConfigurationClassParser#parse(Set<BeanDefinitionHolder>)`\n\n```\npublic void parse(Set<BeanDefinitionHolder> configCandidates) {\n    // ѭ\n    for (BeanDefinitionHolder holder : configCandidates) {\n        BeanDefinition bd = holder.getBeanDefinition();\n        try {\n            // BeanDefinitionAnnotatedBeanDefinitionʵ\n            // ǰõ beanConfigsAnnotatedBeanDefinitionʵifķִ\n            if (bd instanceof AnnotatedBeanDefinition) {\n                parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());\n            }\n            else if (bd instanceof AbstractBeanDefinition \n                    && ((AbstractBeanDefinition) bd).hasBeanClass()) {\n                parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());\n            }\n            else {\n                parse(bd.getBeanClassName(), holder.getBeanName());\n            }\n        }\n        catch (...) {\n            ...\n        }\n    }\n    this.deferredImportSelectorHandler.process();\n}\n\n/**\n * parseн\n */\nprotected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {\n    // ConfigurationClassmetadatabeanNameİװ\n    processConfigurationClass(new ConfigurationClass(metadata, beanName));\n}\n\n```\n\nǰ洫 `BeanConfigs` ᱻװ `AnnotatedGenericBeanDefinition` `AnnotatedBeanDefinition` ʵȻͻ `ConfigurationClassParser#parse(String, String)`ʵûʲôʵԵĹ `processConfigurationClass(...)` \n\n```\n/**\n * ж֤಻ظ\n * ʵʸɻ doProcessConfigurationClass(...)\n */\nprotected void processConfigurationClass(ConfigurationClass configClass) throws IOException {\n    // жǷҪ @Conditional ע⣬жǷ\n    if (this.conditionEvaluator.shouldSkip(configClass.getMetadata() ,\n             ConfigurationPhase.PARSE_CONFIGURATION)) {\n        return;\n    }\n    ConfigurationClass existingClass = this.configurationClasses.get(configClass);\n    // жǷͲˣݹϵʡ\n    if (existingClass != null) {\n        ...\n    }\n    // SourceClass ͬǰ ConfigurationClass һҲǶmetadatabeanNameİװ\n    SourceClass sourceClass = asSourceClass(configClass);\n    do {\n        // doXxx(...) ɻ\n        // صݲΪգٴѭ\n        sourceClass = doProcessConfigurationClass(configClass, sourceClass);\n    }\n    while (sourceClass != null);\n    this.configurationClasses.put(configClass, configClass);\n}\n\n```\n\nжǷִȻ `do-while` ѭִ `doProcessConfigurationClass(...)`ѭ `doProcessConfigurationClass(...)` صݲΪգǼ¿\n\n### 2.5 ࣺ`ConfigurationClassParser#doProcessConfigurationClass`\n\n```\n/**\n * ķ\n */\nprotected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, \n        SourceClass sourceClass) throws IOException {\n    // 1\\.  @Component ע⣬ݹ鴦ڲ࣬Ĳע\n    ...\n\n    // 2\\. @PropertySourceע⣬Ĳע\n    ...\n\n    // 3\\.  @ComponentScan/@ComponentScans ע\n    // 3.1 ȡϵ @ComponentScan/@ComponentScans ע\n    Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(\n            sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);\n    // ûдComponentScan߱@ConditionͲٽif\n    if (!componentScans.isEmpty() && !this.conditionEvaluator\n            .shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {\n        // ѭcomponentScansҲϵ@ComponentScanע\n        for (AnnotationAttributes componentScan : componentScans) {\n            // 3.2 componentScanParser.parse(...)componentScanĲ\n            // componentScan@ComponentScanϵľݣ\n            // sourceClass.getMetadata().getClassName()\n            Set<BeanDefinitionHolder> scannedBeanDefinitions = this.componentScanParser\n                    .parse(componentScan, sourceClass.getMetadata().getClassName());\n            // 3.3 ѭõ BeanDefinitionӦ࣬ݹparse(...)\n            // componentScanб@Beanǵķ@ComponentScanע\n            for (BeanDefinitionHolder holder : scannedBeanDefinitions) {\n                BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();\n                if (bdCand == null) {\n                    bdCand = holder.getBeanDefinition();\n                }\n                // жBeanDefinitionӦǷΪ\n                if (ConfigurationClassUtils\n                        .checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {\n                    // Եõ࣬parse(...)ٴνн\n                    parse(bdCand.getBeanClassName(), holder.getBeanName());\n                }\n            }\n        }\n    }\n    // 4\\. @Importע⣬Ĳע\n    ...\n\n    // 5\\. @ImportResourceע⣬Ĳע\n    ...\n\n    // 6\\. @Beanע⣬Ĳע\n    ...\n\n    // 7\\. ĸ࣬ processConfigurationClass(...) һѭʱ\n    // sourceClass.getMetadata()\n    if (sourceClass.getMetadata().hasSuperClass()) {\n        String superclass = sourceClass.getMetadata().getSuperClassName();\n        if (superclass != null && !superclass.startsWith(\"java\") &&\n                !this.knownSuperclasses.containsKey(superclass)) {\n            this.knownSuperclasses.put(superclass, configClass);\n            return sourceClass.getSuperClass();\n        }\n    }\n    return null;\n}\n\n/**\n * ٴεprocessConfigurationClass(...)н\n * ĿǣҲпб@BeanǵķComponentScanע\n */\nprotected final void parse(@Nullable String className, String beanName) throws IOException {\n    Assert.notNull(className, \"No bean class name for configuration class bean definition\");\n    MetadataReader reader = this.metadataReaderFactory.getMetadataReader(className);\n    // ֵ processConfigurationClass(...) \n    processConfigurationClass(new ConfigurationClass(reader, beanName));\n}\n\n```\n\n`ConfigurationClassParser#doProcessConfigurationClass` Ƕ `@PropertySource``@ComponentScan``@Import``@ImportResource``@Bean` ע⣬ǽע `@ComponentScan` עĴ£\n\n1.  ȡϵ `@ComponentScan/@ComponentScans` ע⣻\n2.  `componentScanParser.parse(...)` `componentScan` Ĳص\n3.  ѭõ `BeanDefinition`Ӧ࣬ݹ `parse(...)` ɨ赽 `@Import``@Bean``@ComponentScan` ע⣬ݹ `parse(...)` ʱᱻ\n\nĲڴѾע͵úˣͲ˵ˣֱ `@ComponentScan` Ľ.\n\n### 2.6 ĵط`ComponentScanAnnotationParser#parse`\n\n`@ComponentScan` Ľ `ComponentScanAnnotationParser#parse` У£\n\n```\npublic Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {\n    // 1\\. һɨɨ\n    ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,\n            componentScan.getBoolean(\"useDefaultFilters\"), this.environment, this.resourceLoader);\n    // 2\\. жǷдĬϵ\n    Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass(\"nameGenerator\");\n    boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);\n    scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :\n            BeanUtils.instantiateClass(generatorClass));\n    // 3\\.  @ComponentScan ע\n    // 3.1  @ComponentScan  scopedProxy \n    ScopedProxyMode scopedProxyMode = componentScan.getEnum(\"scopedProxy\");\n    if (scopedProxyMode != ScopedProxyMode.DEFAULT) {\n        scanner.setScopedProxyMode(scopedProxyMode);\n    }\n    else {\n        Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass(\"scopeResolver\");\n        scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass));\n    }\n    // 3.2  @ComponentScan  resourcePattern \n    scanner.setResourcePattern(componentScan.getString(\"resourcePattern\"));\n\n    // 3.3  @ComponentScan  includeFilters \n    // addIncludeFilter addExcludeFilter,List<TypeFilter>\n    for (AnnotationAttributes filter : componentScan.getAnnotationArray(\"includeFilters\")) {\n        for (TypeFilter typeFilter : typeFiltersFor(filter)) {\n            scanner.addIncludeFilter(typeFilter);\n        }\n    }\n    // 3.4  @ComponentScan  excludeFilters \n    for (AnnotationAttributes filter : componentScan.getAnnotationArray(\"excludeFilters\")) {\n        for (TypeFilter typeFilter : typeFiltersFor(filter)) {\n            scanner.addExcludeFilter(typeFilter);\n        }\n    }\n    boolean lazyInit = componentScan.getBoolean(\"lazyInit\");\n    if (lazyInit) {\n        scanner.getBeanDefinitionDefaults().setLazyInit(true);\n    }\n    Set<String> basePackages = new LinkedHashSet<>();\n    // 3.5\\. @ComponentScan ָ basePackages ԣԵString\n    String[] basePackagesArray = componentScan.getStringArray(\"basePackages\");\n    for (String pkg : basePackagesArray) {\n        String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),\n                ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);\n        Collections.addAll(basePackages, tokenized);\n    }\n    // 3.6\\. @ComponentScan ָ basePackageClasses ԵClass\n    //    ֻҪ⼸ͬģ⼸¼ĶԱɨ赽\n    for (Class<?> clazz : componentScan.getClassArray(\"basePackageClasses\")) {\n        basePackages.add(ClassUtils.getPackageName(clazz));\n    }\n    // 3.7 ϶ûָĬϻڵİΪɨ·\n    if (basePackages.isEmpty()) {\n        basePackages.add(ClassUtils.getPackageName(declaringClass));\n    }\n    // 3.8 ųͰעųִƥʱ򣬻ų\n    scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {\n        @Override\n        protected boolean matchClassName(String className) {\n            return declaringClass.equals(className);\n        }\n    });\n    // 4\\. ſʼɨ @ComponentScan ָİ\n    // ɨɺ󣬶Է࣬springὫӵbeanFactoryBeanDefinitionMap\n    return scanner.doScan(StringUtils.toStringArray(basePackages));\n}\n\n```\n\nϷִ£\n\n1.  һɨɨ\n2.  жǷдĬϵ\n3.   `@ComponentScan` עԣɨ\n    1.   `@ComponentScan`  `scopedProxy` \n    2.   `@ComponentScan`  `resourcePattern` \n    3.   `@ComponentScan`  `includeFilters` \n    4.   `@ComponentScan`  `excludeFilters` \n    5.   `@ComponentScan`  `basePackages` ԣԵ `String`\n    6.   `@ComponentScan`  `basePackageClasses` ԣ Ե `Class`\n    7.  ûָɨĬϻڵİΪɨ·\n    8.  ųͰעųִƥʱ򣬻ų\n4.   `ClassPathBeanDefinitionScanner#doScan` ɨ\n\nգ `ClassPathBeanDefinitionScanner#doScan` ɰ [spring ֮ɨ](https://my.oschina.net/funcy/blog/4614071)ѾϸˣͲٷˡ\n\nǻص `ConfigurationClassPostProcessor#processConfigBeanDefinitions` \n\n```\npublic void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {\n    ...\n    parser.parse(candidates);\n    ....\n}\n\n```\n\nִǰ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-85e400f7d45b4fe5e2353e359f034b1a726.png)\n\nִк\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-a6d02c305f562414326ca7836c281ebf472.png)\n\nԿ`BeanObj1``BeanObj2` Ѿ `BeanFactory`  `BeanDefinitionMap`  \n\n### 2.7 ܽ\n\nspring  `@ComponentScan` ̵ͽˣλ `ConfigurationClassParser#doProcessConfigurationClass` ˽ `@ComponentScan` `@Bean``@Import` ע⣬ֻ `@ComponentScan` Ĵ̣ܽ£\n\n1.  ȡϵ `@ComponentScan/@ComponentScans` ע\n2.  н `@ComponentScan` ĲʱȶһȻ `@ComponentScan` ԣ䣬Щ֮󣬾Ϳʼаɨ\n3.  ɨõ࣬Ϊ࣬ͨ `parse(...)` һε `ConfigurationClassParser#doProcessConfigurationClass` нһǳҪͱ֤ɨõе `@Bean``@Import``@ComponentScan` עõ\n\nˣľȵˣƪ» `ConfigurationClassPostProcessor` עĴ\n\n* * *\n\n_ԭӣ[https://my.oschina.net/funcy/blog/4836178](https://my.oschina.net/funcy/blog/4836178) ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע_"
  },
  {
    "path": "docs/Spring全家桶/Spring源码分析/Spring重要机制探秘/ConfigurationClassPostProcessor（三）：处理@Import注解.md",
    "content": " `ConfigurationClassPostProcessor` ĵƪҪǷ spring  `@Import` עĴ̡\n\n## 4. spring δ @Import עģ\n\nнģǼ spring  `@Import` עĴ̡\n\n### 4.1 ˽ `@Import` ע\n\n `@Import` עĶ壺\n\n```\n@Target(ElementType.TYPE)\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\npublic @interface Import {\n    /**\n     * {@link Configuration @Configuration}, {@link ImportSelector},\n     * {@link ImportBeanDefinitionRegistrar}, or regular component classes to import.\n     */\n    Class<?>[] value();\n}\n```\n\n`@Import` һ`value()`ֵ֧ `Class`ĵ֧ 4 ֣\n\n-  `@Configuration` עǵ\n- ʵ `ImportSelector` \n- ʵ `ImportBeanDefinitionRegistrar` \n- ͨ\n\nͨһ demo չʾʹ `@Import` νർ뵽 spring С\n\n### 4.2 demo ׼\n\n1. ׼ 4  bean\n\n```\n/**\n * Element01\n */\npublic class Element01 {\n    public String desc() {\n        return \"this is element 01\";\n    }\n}\n\n/**\n * Element02\n */\npublic class Element02 {\n    public String desc() {\n        return \"this is element 02\";\n    }\n}\n\n/**\n * Element03\n */\npublic class Element03 {\n    public String desc() {\n        return \"this is element 03\";\n    }\n}\n\n/**\n * Element04\n */\npublic class Element04 {\n    public String desc() {\n        return \"this is element 04\";\n    }\n}\n```\n\n1. ׼ʵ `ImportBeanDefinitionRegistrar` ࣬ `element02` ע\n\n```\npublic class Element02ImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {\n\n    /**\n     *  registerBeanDefinitions עelement02ӦBeanDefinition\n     * Ҳǰ Element02 Ӧ beanDefinition ֶעᵽbeanFactory \n     *  beanDefinitionMap \n     */\n    @Override\n    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, \n            BeanDefinitionRegistry registry) {\n        registry.registerBeanDefinition(\"element02\", new RootBeanDefinition(Element02.class));\n    }\n}\n```\n\n1. ׼ʵ `ImportSelector` ࣬ `selectImports(...)` У `Element03`  \"\"\n\n```\npublic class Element03Selector implements ImportSelector {\n    /**\n     * String Ϊ .\n     * ںҪõ䣬˱\".\"\n     * @param importingClassMetadata\n     * @return\n     */\n    @Override\n    public String[] selectImports(AnnotationMetadata importingClassMetadata) {\n        return new String[] {Element03.class.getName()};\n    }\n}\n```\n\n1. ׼һ `@Configuration` ע࣬ͨб `@Bean` ǵķ `Element04`\n\n```\n@Configuration\npublic class Element04Configuration {\n    @Bean\n    public Element04 element04() {\n        return new Element04();\n    }\n\n}\n```\n\n1.  `@EnableElement` ע⣬е `@Import` ע `Element01.class``Element02ImportBeanDefinitionRegistrar.class``Element03Selector.class``Element04Configuration.class`\n\n```\n@Target(ElementType.TYPE)\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\n@Import({\n        // ͨ \n        Element01.class,\n        // ʵ ImportBeanDefinitionRegistrar \n        Element02ImportBeanDefinitionRegistrar.class,\n        // ʵ ImportSelector \n        Element03Selector.class,\n        //  @Configuration ǵ\n        Element04Configuration.class\n})\npublic @interface EnableElement {\n\n}\n```\n\n1. ࣺ\n\n```\n// ֻҪ @EnableElement ע\n@EnableElement\npublic class Demo04Main {\n\n    public static void main(String[] args) {\n        //  Demo04Main.class\n        ApplicationContext context = new AnnotationConfigApplicationContext(Demo04Main.class);\n\n        Element01 element01 = context.getBean(Element01.class);\n        System.out.println(element01.desc());\n\n        Element02 element02 = context.getBean(Element02.class);\n        System.out.println(element02.desc());\n\n        Element03 element03 = context.getBean(Element03.class);\n        System.out.println(element03.desc());\n\n        Element04 element04 = context.getBean(Element04.class);\n        System.out.println(element04.desc());\n    }\n}\n```\n\n [gitee/funcy](https://gitee.com/funcy/spring-framework/tree/v5.2.2.RELEASE_learn/spring-learn/src/main/java/org/springframework/learn/explore/demo04).\n\nУ£\n\n```\nthis is element 01\nthis is element 02\nthis is element 03\nthis is element 04\n```\n\nԿ4  bean ɹ spring ͨ spring δġ\n\nע `ConfigurationClassPostProcessor` ĵƪǰƪ£[ @ComponentScan ע](https://my.oschina.net/funcy/blog/4836178)[ @Bean ע](https://my.oschina.net/funcy/blog/4492878)ͬĴ룬Ļһʴˡ\n\n### 4.3 ࣺ`ConfigurationClassPostProcessor#processConfigBeanDefinitions`\n\nḶ̌ǰƪѾᵽˣֱӿؼ룺\n\n```\npublic void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {\n    ...\n    Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);\n    Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());\n    do {\n        // 1. , @Component@PropertySources@ComponentScans\n        // @ImportResourceȵĽ\n        parser.parse(candidates);\n        parser.validate();\n        // ɺ󣬵õ࣬ౣ parser  configurationClasses \n        Set<ConfigurationClass> configClasses \n                = new LinkedHashSet<>(parser.getConfigurationClasses());\n        ...\n\n        // 2.  @Import ࡢд@Beanķ\n        // @ImportResource ԴתBeanDefinition\n        this.reader.loadBeanDefinitions(configClasses);\n        ...\n        // עBeanDefinition  candidateNamesбȽ\n        // ڵĻ˵µBeanDefinitionע\n        if (registry.getBeanDefinitionCount() > candidateNames.length) {\n            ...\n            for (String candidateName : newCandidateNames) {\n                // ˱BeanDefinition\n                if (!oldCandidateNames.contains(candidateName)) {\n                    BeanDefinition bd = registry.getBeanDefinition(candidateName);\n                    // 3. ӵ࣬࣬δӵcandidatesУȴ´ѭ\n                    if (ConfigurationClassUtils.checkConfigurationClassCandidate(\n                            bd, this.metadataReaderFactory) \n                            &&!alreadyParsedClasses.contains(bd.getBeanClassName())) {\n                        candidates.add(new BeanDefinitionHolder(bd, candidateName));\n                    }\n                }\n            }\n            candidateNames = newCandidateNames;\n        }\n    }\n    while (!candidates.isEmpty());\n    ...\n}\n```\n\nϴ뾭һЩֻ˴ `@Import` ؼ裬ܽ£\n\n1. ࣬ `@Component``@PropertySources``@ComponentScans``@ImportResource` ȵĽ ǰƪҲˣǾۼ `@Import` ٴηһҪֻһ `main()` ע `Demo04.class`\n\n   ![img](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-840c14685be569012b54d94aad67ee48825.png)\n\n2.  `@Import` ࡢд `@Bean` ķ`@ImportResource` Դת `BeanDefinition`ǰ `@Bean` ʱҲˣҲ\n\n3. ӵ࣬࣬δӵ `candidates` Уȴ´ѭ\n\n### 4.4 ࣺ`ConfigurationClassParser#doProcessConfigurationClass`\n\n `@Import` νģ `ConfigurationClassParser#doProcessConfigurationClass`\n\n```\n/**\n * ķ\n */\nprotected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, \n        SourceClass sourceClass) throws IOException {\n    // 1.  @Component ע⣬ݹ鴦ڲ࣬Ĳע\n    ...\n\n    // 2. @PropertySourceע⣬Ĳע\n    ...\n\n    // 3.  @ComponentScan/@ComponentScans ע⣬Ĳע\n    ...\n\n    // 4. @Importע\n    processImports(configClass, sourceClass, getImports(sourceClass), true);\n\n    // 5. @ImportResourceע⣬Ĳע\n    ...\n\n    // 6. @Beanע⣬Ĳע\n    ...\n\n    // 7. ĸ࣬ processConfigurationClass(...) һѭʱ\n    ...\n    return null;\n}\n```\n\n `@Import` עõ `processImports(...)` Ǽ\n\n#### 1. ȡ `@Import` \n\nǰĿŵ `processImports(...)` \n\n```\nprocessImports(configClass, sourceClass, getImports(sourceClass), true);\n```\n\n 4 \n\n- `configClass`࣬ `demo04Main` Ӧࣻ\n- `sourceClass` `demo04Main` ϵעİװ\n- `getImports(sourceClass)``getImports(...)` ȡ `sourceClass`  `@Import` עࣻ\n- `true`ֵǷѭ롣\n\nУȡ `@Import` ע `getImports(...)` Ĺܣλȡģ\n\n```\nprivate Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {\n    Set<SourceClass> imports = new LinkedHashSet<>();\n    Set<SourceClass> visited = new LinkedHashSet<>();\n    // ȡ\n    collectImports(sourceClass, imports, visited);\n    return imports;\n}\n\n/**\n * Ļȡ\n */\nprivate void collectImports(SourceClass sourceClass, Set<SourceClass> imports, \n        Set<SourceClass> visited) throws IOException {\n    if (visited.add(sourceClass)) {\n        for (SourceClass annotation : sourceClass.getAnnotations()) {\n            String annName = annotation.getMetadata().getClassName();\n            if (!annName.equals(Import.class.getName())) {\n                // annotationƲimportݹ collectImports(...) \n                collectImports(annotation, imports, visited);\n            }\n        }\n        // ȡǰ @Import ע\n        imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), \"value\"));\n    }\n}\n```\n\nȡ `@Import` ķ `ConfigurationClassParser#collectImports`ȡȡʽ£\n\n1. ȡע⣻\n2. Щע⣬ `@Import` ע⣬ȡ `@Import`  `value` ֵ򣬻صһ\n\n֮ `demo04Main` ϵ `@EnableElement` עᱻȡעⲻ `@Import` ע⣬ͼȡ `@EnableElement` ϵע⣬ʱ `@EnableElement`  `@Import` ע⣬ʱͻȡ `@Import`  `value()` ֵҲ `@Import` עࡣ\n\nк õĽ£\n\n![img](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-05c7fb442b4bb84d13ff6688e5fbf31cfa5.png)\n\n![img](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-4e7153f9cbb922496e7c303cc3c35e7eceb.png)\n\n![img](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-b238838641af43b783fc9b14c4226f5c1eb.png)\n\n![img](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-d7e2063676e6e19850c35893b62f4ab76a9.png)\n\nõĽΪ `LinkedHashSet`һνͼ㣬˷Ϊ 4 ͼԿ`@Import` ע 4 ඼ȡˣ\n\n![img](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-d615198a8f6dbef1ac6a800526311f36078.png)\n\n#### 2.  `@Import` \n\nȡ `@Import` ע⵼ `processImports(...)` \n\n```\nprivate void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,\n        Collection<SourceClass> importCandidates, boolean checkForCircularImports) {\n    ...\n    for (SourceClass candidate : importCandidates) {\n        // 1.  ImportSelector\n        if (candidate.isAssignable(ImportSelector.class)) {\n            Class<?> candidateClass = candidate.loadClass();\n            // ʵ ImportSelectorһִ Aware ӿڷ\n            // ֵ֧AwareBeanClassLoaderAwareBeanFactoryAwareEnvironmentAwareResourceLoaderAware\n            ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, \n                    ImportSelector.class, this.environment, this.resourceLoader, this.registry);\n            if (selector instanceof DeferredImportSelector) {\n                this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);\n            }\n            else {\n                // ִ selectImports ȡ࣬Ϊ\".\"\n                String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());\n                Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);\n                // ݹ processImports(...) һδ\n                processImports(configClass, currentSourceClass, importSourceClasses, false);\n            }\n        }\n        // 2.  ImportBeanDefinitionRegistrar\n        else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {\n            Class<?> candidateClass = candidate.loadClass();\n            // ʵ ImportBeanDefinitionRegistrarִ Aware ӿڵķ\n            // ֵ֧AwareBeanClassLoaderAwareBeanFactoryAwareEnvironmentAwareResourceLoaderAware\n            ImportBeanDefinitionRegistrar registrar = ParserStrategyUtils\n                    .instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class, \n                    this.environment, this.resourceLoader, this.registry);\n            //  ImportBeanDefinitionRegistrar \n            configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());\n        }\n        // 3. ߣ processConfigurationClass(...) ֱӽ\n        // ࣨ @Component@Configuration@Import ע⣩н\n        else {\n            this.importStack.registerImport(\n                    currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());\n            processConfigurationClass(candidate.asConfigClass(configClass));\n        }\n    }\n\n    ...\n}\n```\n\nǾĴ룬ֻ `@Import` Ĺؼ裺\n\n1.  `ImportSelector`£\n\n    1. ʹ÷ʵ `ImportSelector`ִ֮ `Aware` ӿڷڴ `ImportSelector` ʱʵ `Aware` ӿڣֵ֧ `Aware`  `BeanClassLoaderAware``BeanFactoryAware``EnvironmentAware``ResourceLoaderAware`\n    2. ִ `ImportSelector` ʵ `selectImports` һΪ˻ȡ࣬Ϊ `Class[]` \"\"`Element03Selector` и÷£\n\n   ```\n   @Override\n   public String[] selectImports(AnnotationMetadata importingClassMetadata) {\n       return new String[] {Element03.class.getName()};\n   }\n   ```\n\n   һȡ `Element03.class`; 3. ȡ `Class` 飬ת `SourceClass` ϣһε `processImports(...)`ڵڶεʱ `Element03`\n\n2.  `ImportBeanDefinitionRegistrar`£\n\n    1. ʵ `ImportBeanDefinitionRegistrar`ִ `Aware` ӿڵķһͬ `ImportSelector` ʵһ׸\n    2. һõ `ImportBeanDefinitionRegistrar` ʵ浽 `configClass` Уٷʵδģ\n\n3. Ͳߣ `processConfigurationClass(...)` ֱӽǰƪѾἰˣ `@Component``@Import``@ComponentScan``@Configuration``@Bean` עģһΪ˽еЩע⣬ `Element01``Element02``Element03`( 1 ᵽģ `Element03Selector` лȡ `Element03.class` 󣬽ת `SourceClass`ٴε `processImports(...)` Ĺ) һġ\n\nǷ֣ `ImportBeanDefinitionRegistrar` ķʽ⣬뷽ʽͨࡢࡢ`ImportSelector` ʵࣩһĴʽǰ**Ľʽ**յõ `processConfigurationClass(...)` һ ǰƪѾηˣͲٷˣ `ImportBeanDefinitionRegistrar` Ĵ\n\n### 4.5  `BeanDefinitions``ConfigurationClassBeanDefinitionReader#loadBeanDefinitions`\n\nڷ `@Bean` ʱǷ `ConfigurationClassBeanDefinitionReader#loadBeanDefinitions`  `@Bean` ̣ `@Import` ֱ̣ӽؼ `ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClass`\n\n```\nprivate void loadBeanDefinitionsForConfigurationClass(\n        ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {\n    ...\n\n    //  @Import \n    if (configClass.isImported()) {\n        registerBeanDefinitionForImportedConfigurationClass(configClass);\n    }\n    //  @Bean \n    for (BeanMethod beanMethod : configClass.getBeanMethods()) {\n        loadBeanDefinitionsForBeanMethod(beanMethod);\n    }\n    //  @ImportResource Դ\n    loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());\n    //  @Import  ImportBeanDefinitionRegistrar\n    // ǰ汣configClassеImportBeanDefinitionRegistrarsʹ\n    loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());\n}\n```\n\n `@Import` ĵط\n\n1.  `@Import` \n2.  `@Import`  `ImportBeanDefinitionRegistrar`\n\nǷֱδġ\n\n#### 1.  `@Import` \n\n `@Import` شΪ\n\n```\n    //  @Import \n    if (configClass.isImported()) {\n        registerBeanDefinitionForImportedConfigurationClass(configClass);\n    }\n```\n\nǽһ̽\n\n```\nprivate void registerBeanDefinitionForImportedConfigurationClass(ConfigurationClass configClass) {\n    AnnotationMetadata metadata = configClass.getMetadata();\n    //  BeanDefinitionΪ AnnotatedGenericBeanDefinition\n    AnnotatedGenericBeanDefinition configBeanDef = new AnnotatedGenericBeanDefinition(metadata);\n    // һϵе\n    ScopeMetadata scopeMetadata = scopeMetadataResolver.resolveScopeMetadata(configBeanDef);\n    configBeanDef.setScope(scopeMetadata.getScopeName());\n    String configBeanName = this.importBeanNameGenerator.generateBeanName(configBeanDef, this.registry);\n    AnnotationConfigUtils.processCommonDefinitionAnnotations(configBeanDef, metadata);\n    BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(configBeanDef, configBeanName);\n    definitionHolder = AnnotationConfigUtils\n            .applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);\n    // ע\n    this.registry.registerBeanDefinition(definitionHolder.getBeanName(), \n            definitionHolder.getBeanDefinition());\n    configClass.setBeanName(configBeanName);\n}\n```\n\nһעᵽ `BeanDefinition` еḶ́ʹõ `BeanDefinition`  `AnnotatedGenericBeanDefinition`\n\n#### 2.  `@Import`  `ImportBeanDefinitionRegistrar`\n\nù̵ķΪ `loadBeanDefinitionsFromRegistrars(...)`£\n\n```\nprivate void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, \n        AnnotationMetadata> registrars) {\n    registrars.forEach((registrar, metadata) ->\n            registrar.registerBeanDefinitions(metadata, this.registry, \n                    this.importBeanNameGenerator));\n}\n```\n\n÷Ǳ `ImportBeanDefinitionRegistrar` ϣȻһе `ImportBeanDefinitionRegistrar#registerBeanDefinitions(AnnotationMetadata, BeanDefinitionRegistry, BeanNameGenerator)` ÷λ `ImportBeanDefinitionRegistrar` ӿڣ£\n\n```\npublic interface ImportBeanDefinitionRegistrar {\n\n    /**\n     * ĬϷĬʵֽһ\n     */\n    default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, \n            BeanDefinitionRegistry registry,BeanNameGenerator importBeanNameGenerator) {\n        registerBeanDefinitions(importingClassMetadata, registry);\n    }\n\n    /**\n     *  Element02ImportBeanDefinitionRegistrar ʵֵķ\n     */\n    default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, \n            BeanDefinitionRegistry registry) {\n    }\n\n}\n```\n\n`ImportBeanDefinitionRegistrar#registerBeanDefinitions(AnnotationMetadata, BeanDefinitionRegistry, BeanNameGenerator)` һãյõ `ImportBeanDefinitionRegistrar#registerBeanDefinitions(AnnotationMetadata, BeanDefinitionRegistry)`ǵ `Element02ImportBeanDefinitionRegistrar` ʵ˸÷\n\n```\npublic class Element02ImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {\n    @Override\n    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, \n            BeanDefinitionRegistry registry) {\n        registry.registerBeanDefinition(\"element02\", new RootBeanDefinition(Element02.class));\n    }\n}\n```\n\n˴죬շ `ImportBeanDefinitionRegistrar` עᵽ `beanDefinitionMap` ߼Լдģ\n\n#### 3. лȡ `ElementXx`\n\n`Element01``Element02``Element03``Element04` ͵עᵽ `beanDefinitionMap` ˣǿһ `beanDefinitionNames` еݣ\n\n![img](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-68171a4af503b3e80b23d10a91834e231aa.png)\n\nԷ֣`Element01`  `Element03` beanName ͬѰ bean 뷽ʽΪ\n\n![img](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-0e07154a732c2a884803531e54aa10a0ef9.png)\n\nʹ `beanFactory.get(beanName)` ʱҪע⣺\n\n```\n// ȡᱨ\nbeanFactory.get(\"element01\");\n// ܻȡ\nbeanFactory.get(\"element02\");\n// ȡᱨ\nbeanFactory.get(\"element03\");\n// ܻȡ\nbeanFactory.get(\"element04\");\n```\n\nʹ `beanFactory.get(beanName)` ȡ `element01`  `element03` Ҫȡ\n\n```\n// ܻȡ\nbeanFactory.get(\"org.springframework.learn.explore.demo04.element.Element01\");\n// ܻȡ\nbeanFactory.get(\"org.springframework.learn.explore.demo04.element.Element03\");\n```\n\nȻҲʹ `beanFactory.get(Class)` ķʽȡ\n\n```\n// ܻȡ\nbeanFactory.get(Element01.class);\n// ܻȡ\nbeanFactory.get(Element02.class);\n// ܻȡ\nbeanFactory.get(Element03.class);\n// ܻȡ\nbeanFactory.get(Element04.class);\n```\n\n### 4.6 䣺`DeferredImportSelector` Ĵ\n\nڷ `ConfigurationClassParser#processImports` ʱ `ImportSelector` ʱôһδ룺\n\n![img](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-4fb0dddcbf45d84ad5260c43a5b55e85a0a.png)\n\nδж `selector` ǷΪ `DeferredImportSelector` ʵǾͰ `DeferredImportSelector` ͵ʵдͰͨ `ImportSelector`  `DeferredImportSelector` ͨ `ImportSelector` кβͬ\n\n`DeferredImportSelector` Ĵ£\n\n```\n/**\n * DeferredImportSelector  ImportSelectorӽӿ\n */\npublic interface DeferredImportSelector extends ImportSelector {\n\n    /**\n     * ص\n     */\n    @Nullable\n    default Class<? extends Group> getImportGroup() {\n        return null;\n    }\n\n\n    /**\n     * Ķ\n     */\n    interface Group {\n\n        /**\n         * ÷Ĵ\n         */\n        void process(AnnotationMetadata metadata, DeferredImportSelector selector);\n\n        /**\n         * ظ÷\n         */\n        Iterable<Entry> selectImports();\n\n\n        /**\n         * Ԫض\n         */\n        class Entry {\n\n            /**\n             * ע\n             */\n            private final AnnotationMetadata metadata;\n\n            /**\n             * \n             */\n            private final String importClassName;\n\n            public Entry(AnnotationMetadata metadata, String importClassName) {\n                this.metadata = metadata;\n                this.importClassName = importClassName;\n            }\n\n            // ʡ get/set ʡ equals/toString/hashCode \n            ...\n\n        }\n    }\n}\n```\n\nĴԿ\n\n- `DeferredImportSelector`  `ImportSelector` ӽӿڣ߱ `ImportSelector` Ĺ\n- `DeferredImportSelector` ṩһ`Class<? extends Group> getImportGroup()`÷صǵǰ `DeferredImportSelector` ʵڵķ顣\n\nעд룺\n\n```\nthis.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);\n```\n\nдʲô `ConfigurationClassParser#handler`\n\n```\nclass ConfigurationClassParser {\n\n    ...\n\n    private class DeferredImportSelectorHandler {\n\n        @Nullable\n        private List<DeferredImportSelectorHolder> deferredImportSelectors = new ArrayList<>();\n\n        public void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) {\n            //  configClass  importSelector װ DeferredImportSelectorHolder\n            DeferredImportSelectorHolder holder = new DeferredImportSelectorHolder(\n                    configClass, importSelector);\n            if (this.deferredImportSelectors == null) {\n                DeferredImportSelectorGroupingHandler handler \n                    = new DeferredImportSelectorGroupingHandler();\n                handler.register(holder);\n                handler.processGroupImports();\n            }\n            else {\n                // ӵ deferredImportSelectors \n                this.deferredImportSelectors.add(holder);\n            }\n        }\n        ...\n    }\n    ...\n}\n```\n\nԿϴȽ `configClass`  `importSelector` װ `DeferredImportSelector`Ȼӵ `deferredImportSelectors`\n\nĿǰΪֹ`DeferredImportSelector` ಢûндô `DeferredImportSelector` ﴦأǻص `ConfigurationClassParser#parse(Set<BeanDefinitionHolder>)` \n\n```\npublic class  ConfigurationClassParser {\n    public void parse(Set<BeanDefinitionHolder> configCandidates) {\n        // ѭ\n        for (BeanDefinitionHolder holder : configCandidates) {\n            BeanDefinition bd = holder.getBeanDefinition();\n            try {\n                // BeanDefinitionAnnotatedBeanDefinitionʵ\n                if (bd instanceof AnnotatedBeanDefinition) {\n                    parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());\n                }\n                ...\n            }\n            catch (BeanDefinitionStoreException ex) {\n                throw ex;\n            }\n            catch (Throwable ex) {\n                ...\n            }\n        }\n        // ﴦ DeferredImportSelector\n        this.deferredImportSelectorHandler.process();\n    }\n    ...\n}\n```\n\nԿڴĽ `DeferredImportSelector`Ҳӵ `deferredImportSelectors` ݣõ `deferredImportSelectorHandler.process()`.\n\nǼ\n\n```\nclass ConfigurationClassParser {\n\n    private class DeferredImportSelectorHandler {\n        ...\n        public void process() {\n            List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;\n            this.deferredImportSelectors = null;\n            try {\n                if (deferredImports != null) {\n                    DeferredImportSelectorGroupingHandler handler \n                        = new DeferredImportSelectorGroupingHandler();\n                    // DeferredImportSelector ָ˳@Order/Orderd\n                    deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);\n                    //  DeferredImportSelectorGroupingHandler#register \n                    deferredImports.forEach(handler::register);\n                    // \n                    handler.processGroupImports();\n                }\n            }\n            finally {\n                this.deferredImportSelectors = new ArrayList<>();\n            }\n        }\n        ...\n    }\n    ...\n}\n```\n\n`process()` £\n\n1. ҪǸ `@Order` ע⣬ʵ `Orderd` ӿ\n2.  `DeferredImportSelectorGroupingHandler#register` ʵǽ `deferredImports` еԪעᵽ `handler` У\n3.  `handler.processGroupImports()` 롣\n\n `handler.processGroupImports()` \n\n```\nclass ConfigurationClassParser {\n\n    ...\n\n    private class DeferredImportSelectorGroupingHandler {\n        ...\n        /**\n         * յĴ\n         * ﴦ鵼\n         */\n        public void processGroupImports() {\n            for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {\n                // 飬 grouping.getImports()ǹؼ\n                grouping.getImports().forEach(entry -> {\n                    ConfigurationClass configurationClass = this.configurationClasses.get(\n                            entry.getMetadata());\n                    try {\n                        // ͬImportSelectorʵһҲǵ processImports(...) \n                        // ע entry.getImportClassName()һε processImports(...) Ĳ\n                        // ImportSelector\n                        processImports(configurationClass, asSourceClass(configurationClass),\n                                asSourceClasses(entry.getImportClassName()), false);\n                    }\n                    catch (...) {\n                        ...\n                    }\n                });\n            }\n        }\n        ...\n    }\n    ...\n\n}\n```\n\n`processGroupImports(...)` Ҫ߼£\n\n1. \n2.  `grouping.getImports()` ȡ\n3.  `processImports` ĵ룬ͬ `ImportSelector` ӿһ\n\n `grouping.getImports()` ÷Ϊ `ConfigurationClassParser.DeferredImportSelectorGrouping#getImports`£\n\n```\npublic Iterable<Group.Entry> getImports() {\n    for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {\n        // ִ Group#process\n        this.group.process(deferredImport.getConfigurationClass().getMetadata(),\n                deferredImport.getImportSelector());\n    }\n    // ִ Group#selectImports\n    return this.group.selectImports();\n}\n```\n\n `DeferredImportSelector.Group` \n\n- `DeferredImportSelector.Group#process`\n- `DeferredImportSelector.Group#selectImports`\n\nСڵһʼ뼰עͣ `DeferredImportSelector.Group#selectImports` ص.\n\nϰ죬ܽ `DeferredImportSelector`  `ImportSelector` ߵ\n\n- `DeferredImportSelector` ָ飺ڴʱԸݷͳһ\n- `DeferredImportSelector` ʱ֮ٴ\n- `DeferredImportSelector` ķأͬ `ImportSelector` `ImportSelector#selectImports` أ `DeferredImportSelector.Group#selectImports` ء\n\n### 4.7 ܽ\n\nҪ `ConfigurationClassPostProcessor`  `@Import` עĴܽ:\n\n1. `@Import` ɵ 4 ֱ֣ͨࡢࡢʵ `ImportSelector` Լʵ `ImportBeanDefinitionRegistrar` ࣻ\n2. ȡ `@Import` ע⣺spring ڻȡϵ `@Import` ʱȻȡϵע⣬Ȼһжϣǰע `@Import`ȡ `@Import`  (Ϊ `Import#value`)ȡǰעϵע⣬ظϴ\n3.  `@Import` ࣺͨࡢͳһʵ `ImportSelector` ࣬ `selectImports(...)` ص  õ classȻҲǰʵ `ImportBeanDefinitionRegistrar` ࣬ѶӦʵ浽ǰУעᵽ `beanDefinitionMap` ʱǴȡģ\n4. עᵽ `beanDefinitionMap`ʵ `ImportSelector` յõһͨ࣬ͬͨһֱע᣻ʵ `ImportBeanDefinitionRegistrar` ࣬ `ImportBeanDefinitionRegistrar#registerBeanDefinitions(AnnotationMetadata, BeanDefinitionRegistry)` עᣬע߼ʵж塣\n\n------\n\n*ԭӣhttps://my.oschina.net/funcy/blog/4678152 ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע*"
  },
  {
    "path": "docs/Spring全家桶/Spring源码分析/Spring重要机制探秘/ConfigurationClassPostProcessor（二）：处理@Bean注解.md",
    "content": " `ConfigurationClassPostProcessor` ĵڶƪҪǷ spring  `@Bean` עĴ̡\n\n## 3\\. spring δ `@Bean` עģ\n\nнģǼ spring  `@Bean` עĴ̡\n\n### 3.1 demo ׼\n\nΪ˵⣬ֱϴ룺\n\n׼ Bean:\n\n```\npublic class BeanObj1 {\n    public BeanObj1() {\n        System.out.println(\"beanObj1Ĺ췽\");\n    }\n\n    @Override\n    public String toString() {\n        return \"BeanObj1{}\";\n    }\n}\n\npublic class BeanObj2 {\n    public BeanObj2() {\n        System.out.println(\"beanObj2Ĺ췽\");\n    }\n\n    @Override\n    public String toString() {\n        return \"BeanObj2{}\";\n    }\n}\n\n```\n\nע⣺඼û `Component``@Service` ע⡣\n\n׼һ࣬ͨ `@Bean` עķ bean\n\n```\n@Component\npublic class BeanConfigs {\n\n    @Bean\n    public BeanObj1 beanObj1() {\n        return new BeanObj1();\n    }\n\n    @Bean\n    public BeanObj2 beanObj2() {\n        //  beanObj1() \n        beanObj1();\n        return new BeanObj2();\n    }\n\n}\n\n```\n\nࣺ\n\n```\n@ComponentScan\npublic class Demo02Main {\n\n    public static void main(String[] args) {\n        ApplicationContext context \n                = new AnnotationConfigApplicationContext(Demo02Main.class);\n        Object obj1 = context.getBean(\"beanObj1\");\n        Object obj2 = context.getBean(\"beanObj2\");\n        System.out.println(\"obj1:\" + obj1);\n        System.out.println(\"obj2:\" + obj2);\n        System.out.println(context.getBean(\"beanConfigs\"));\n    }\n}\n\n```\n\n 룬¼Ҫ˵\n\n*   `BeanConfigs` ʹõע `@Component`[һƪ](https://my.oschina.net/funcy/blog/4836178)ķ`@Component` Ҳ࣬ͬ `@Configuration` ע⣻\n*    `Demo02Main` У `AnnotationConfigApplicationContext` Ϊ `Demo02Main`һע `@ComponentScan`עûָɨ·[һƪ](https://my.oschina.net/funcy/blog/4836178)ķָɨ·spring Ĭɨڰ\n*   ǲûֱӰ `BeanConfigs` עᵽУ `new AnnotationConfigApplicationContext(BeanConfigs.class)` [һƪ](https://my.oschina.net/funcy/blog/4836178)ķ֪spring Ƚ `Demo02Main` ࣬ϵ `@Component` ע⣬Ӷɨ赽 `BeanConfigs` ࣬Ȼ `BeanConfigs`ڲ `@Bean` ǽҲͨԵķʽ֤\n\nϴ룬£\n\n```\nbeanObj1Ĺ췽\nbeanObj1Ĺ췽\nbeanObj2Ĺ췽\nobj1:BeanObj1{}\nobj2:BeanObj2{}\norg.springframework.learn.explore.demo05.BeanConfigs@2b71e916\n\n```\n\n demo з\n\n**ע** `ConfigurationClassPostProcessor` ĵڶƪ[һƪ](https://my.oschina.net/funcy/blog/4836178)ͬĴ룬ֻһʴٽϸ\n\n### 3.2 ࣺConfigurationClassPostProcessor#processConfigBeanDefinitions\n\nֱӽ `ConfigurationClassPostProcessor#processConfigBeanDefinitions` £\n\n```\nAnnotationConfigApplicationContext#AnnotationConfigApplicationContext(Class)\n |-AbstractApplicationContext#refresh\n  |-AbstractApplicationContext#invokeBeanFactoryPostProcessors\n   |-PostProcessorRegistrationDelegate\n      #invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory, List)\n    |-PostProcessorRegistrationDelegate#invokeBeanDefinitionRegistryPostProcessors\n     |-ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry\n      |-ConfigurationClassPostProcessor#processConfigBeanDefinitions\n\n```\n\nʱ `candidates` ֻһ Ԫأ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-0d50f32d5ea1d2b79b8e17612d30693f753.png)\n\n### 3.2  `demo02Main`ConfigurationClassParser#doProcessConfigurationClass\n\nһǽ `@ComponentScan` עḶ́[һƪ](https://my.oschina.net/funcy/blog/4836178)ϸֻջ\n\n```\nAnnotationConfigApplicationContext#AnnotationConfigApplicationContext(Class)\n |-AbstractApplicationContext#refresh\n  |-AbstractApplicationContext#invokeBeanFactoryPostProcessors\n   |-PostProcessorRegistrationDelegate\n      #invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory, List)\n    |-PostProcessorRegistrationDelegate#invokeBeanDefinitionRegistryPostProcessors\n     |-ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry\n      |-ConfigurationClassPostProcessor#processConfigBeanDefinitions\n       |-ConfigurationClassParser#parse(Set<BeanDefinitionHolder>)\n        |-ConfigurationClassParser#parse(AnnotationMetadata, String)\n          |-ConfigurationClassParser#processConfigurationClass\n           |-ConfigurationClassParser#doProcessConfigurationClass\n\n```\n\n `Demo02Main`  `@ComponentScan` ֮󣬿Կ `beanConfigs` Ѿɨ赽ˣ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-7fef6007424562f63dda7f2a0b25e9c293b.png)\n\n `beanConfigs` ࣬˻н\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-fba3821df9cf2926b16bd064cf164e83471.png)\n\nջǻص `ConfigurationClassParser#doProcessConfigurationClass`еĵ£\n\n```\nAnnotationConfigApplicationContext#AnnotationConfigApplicationContext(Class)\n |-AbstractApplicationContext#refresh\n  |-AbstractApplicationContext#invokeBeanFactoryPostProcessors\n   |-PostProcessorRegistrationDelegate\n      #invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory, List)\n    |-PostProcessorRegistrationDelegate#invokeBeanDefinitionRegistryPostProcessors\n     |-ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry\n      |-ConfigurationClassPostProcessor#processConfigBeanDefinitions\n       |-ConfigurationClassParser#parse(Set<BeanDefinitionHolder>)\n        |-ConfigurationClassParser#parse(AnnotationMetadata, String)\n          |-ConfigurationClassParser#processConfigurationClass\n           |-ConfigurationClassParser#doProcessConfigurationClass\n            |-ConfigurationClassParser#parse(String, String)\n             |-ConfigurationClassParser#processConfigurationClass\n              |-ConfigurationClassParser#doProcessConfigurationClass\n\n```\n\nʱ `ConfigurationClassParser#doProcessConfigurationClass`ǾͲ `demo02Main` `beanConfigs` ˡ\n\n### 3.3  `beanConfigs`ConfigurationClassParser#doProcessConfigurationClass\n\n `ConfigurationClassParser#doProcessConfigurationClass`  `@Bean` עĴ£\n\n```\n/**\n * ķ\n */\nprotected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, \n        SourceClass sourceClass) throws IOException {\n    // 1\\.  @Component ע⣬ݹ鴦ڲ࣬Ĳע\n    ...\n\n    // 2\\. @PropertySourceע⣬Ĳע\n    ...\n\n    // 3\\.  @ComponentScan/@ComponentScans ע⣬Ĳע\n    ...\n\n    // 4\\. @Importע⣬Ĳע\n    ...\n\n    // 5\\. @ImportResourceע⣬Ĳע\n    ...\n\n    // 6\\. @Beanע\n    // Ľ\n    Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);\n    for (MethodMetadata methodMetadata : beanMethods) {\n        // ӵ configClass Уٴ\n        configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));\n    }\n\n    // 7\\. ĸ࣬ processConfigurationClass(...) һѭʱ\n    ...\n    return null;\n}\n\n```\n\nȡ `@Bean` ķõ `retrieveBeanMethodMetadata(...)`Ǹȥ\n\n```\nprivate Set<MethodMetadata> retrieveBeanMethodMetadata(SourceClass sourceClass) {\n    AnnotationMetadata original = sourceClass.getMetadata();\n    // ȡ @Bean עķ\n    Set<MethodMetadata> beanMethods = original.getAnnotatedMethods(Bean.class.getName());\n    ...\n    return beanMethods;\n}\n\n```\n\nٸȥյõ `StandardAnnotationMetadata#getAnnotatedMethods`:\n\n```\npublic Set<MethodMetadata> getAnnotatedMethods(String annotationName) {\n    Set<MethodMetadata> annotatedMethods = null;\n    if (AnnotationUtils.isCandidateClass(getIntrospectedClass(), annotationName)) {\n        try {\n            // 1\\. ͨĻȡеķ\n            Method[] methods = ReflectionUtils.getDeclaredMethods(getIntrospectedClass());\n            for (Method method : methods) {\n                // 2\\. жǷ @Bean ע\n                if (isAnnotatedMethod(method, annotationName)) {\n                    if (annotatedMethods == null) {\n                        annotatedMethods = new LinkedHashSet<>(4);\n                    }\n                    annotatedMethods.add(new StandardMethodMetadata(\n                        method, this.nestedAnnotationsAsMap));\n                }\n            }\n        }\n        catch (Throwable ex) {\n            throw new IllegalStateException();\n        }\n    }\n    return annotatedMethods != null ? annotatedMethods : Collections.emptySet();\n}\n\n```\n\nܺ⣬ؼ\n\n1.  ͨȡз\n2.  õķһжϸ÷Ƿ `@Bean` ע⣻\n\n`beanConfigs` еڻȡˣ `ConfigurationClass`  `beanMethods` ԣ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-121f4d518fa57c8932e6b7fd3e7def8704b.png)\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-a4d91c1f52703b8c911bdb9d9afaf697d92.png)\n\n### 3.4  `@Bean` ǵķص `BeanDefinitionMap`\n\nȡ `beanMethod` ʱֻ `ConfigurationClass`  `beanMethods` Уûмص `beanFactory`  `BeanDefinitionMap` УС̽Ǻʱ뵽 `BeanDefinitionMap` С\n\nǵ `ConfigurationClassPostProcessor#processConfigBeanDefinition` ôһд룺\n\n```\npublic void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {\n    ...\n    // ν\n    //  @Import ࡢд@Beanķ@ImportResource ԴתBeanDefinition\n    this.reader.loadBeanDefinitions(configClasses);\n    ...\n}\n\n```\n\nǼ `BeanDefinition` ĵط `@Import` ࡢд `@Bean` ķ`@ImportResource` Դת `BeanDefinition`صע `@Bean` Ĵ£\n\n> ConfigurationClassBeanDefinitionReader\n\n```\n    public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {\n        TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();\n        //  configurationModel\n        for (ConfigurationClass configClass : configurationModel) {\n            loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);\n        }\n    }\n\n    /**\n     * ظ ConfigurationClass  BeanDefinition\n     * 1\\. @Import \n     * 2\\. е @Bean \n     * 3\\. @ImportResource Դ\n     * 4\\. @Import  ImportBeanDefinitionRegistrar\n     */\n    private void loadBeanDefinitionsForConfigurationClass(\n            ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {\n\n        if (trackedConditionEvaluator.shouldSkip(configClass)) {\n            ...\n        }\n\n        //  @Import \n        if (configClass.isImported()) {\n            registerBeanDefinitionForImportedConfigurationClass(configClass);\n        }\n        //  @Bean \n        for (BeanMethod beanMethod : configClass.getBeanMethods()) {\n            loadBeanDefinitionsForBeanMethod(beanMethod);\n        }\n        //  @ImportResource Դ\n        loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());\n        //  @Import  ImportBeanDefinitionRegistrar\n        loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());\n    }\n\n    /**\n     *  @Bean \n     * 1\\. BeanDefinitionbeanMethod ʹõ ConfigurationClassBeanDefinition\n     * 2\\.  @Bean ĸԣõ BeanDefinition \n     * 3\\.  BeanDefinition עᵽ beanFactory \n     */\n    private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {\n        ConfigurationClass configClass = beanMethod.getConfigurationClass();\n        MethodMetadata metadata = beanMethod.getMetadata();\n        String methodName = metadata.getMethodName();\n\n        ...\n\n        // 1\\. beanMethodʹõConfigurationClassBeanDefinition\n        ConfigurationClassBeanDefinition beanDef = \n                new ConfigurationClassBeanDefinition(configClass, metadata);\n        beanDef.setResource(configClass.getResource());\n        beanDef.setSource(this.sourceExtractor.extractSource(metadata, configClass.getResource()));\n\n        // 2\\.  @Bean ĸ\n        if (metadata.isStatic()) {\n            // ̬ @Bean \n            if (configClass.getMetadata() instanceof StandardAnnotationMetadata) {\n                beanDef.setBeanClass(\n                    ((StandardAnnotationMetadata) configClass.getMetadata()).getIntrospectedClass());\n            }\n            else {\n                beanDef.setBeanClassName(configClass.getMetadata().getClassName());\n            }\n            beanDef.setUniqueFactoryMethodName(methodName);\n        }\n        else {\n            // ͨ @Bean \n            beanDef.setFactoryBeanName(configClass.getBeanName());\n            beanDef.setUniqueFactoryMethodName(methodName);\n        }\n\n        if (metadata instanceof StandardMethodMetadata) {\n            beanDef.setResolvedFactoryMethod(\n                ((StandardMethodMetadata) metadata).getIntrospectedMethod());\n        }\n\n        beanDef.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);\n        beanDef.setAttribute(\n                org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor.\n                SKIP_REQUIRED_CHECK_ATTRIBUTE, Boolean.TRUE);\n\n        AnnotationConfigUtils.processCommonDefinitionAnnotations(beanDef, metadata);\n\n        Autowire autowire = bean.getEnum(\"autowire\");\n        if (autowire.isAutowire()) {\n            beanDef.setAutowireMode(autowire.value());\n        }\n\n        boolean autowireCandidate = bean.getBoolean(\"autowireCandidate\");\n        if (!autowireCandidate) {\n            beanDef.setAutowireCandidate(false);\n        }\n\n        String initMethodName = bean.getString(\"initMethod\");\n        if (StringUtils.hasText(initMethodName)) {\n            beanDef.setInitMethodName(initMethodName);\n        }\n\n        String destroyMethodName = bean.getString(\"destroyMethod\");\n        beanDef.setDestroyMethodName(destroyMethodName);\n\n        ...\n\n        // 3\\. BeanDefinitionעᵽbeanFactory\n        this.registry.registerBeanDefinition(beanName, beanDefToRegister);\n    }\n\n```\n\n`ConfigurationClassBeanDefinitionReader#loadBeanDefinitions` ֵմ `ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForBeanMethod`ⲿ߼£\n\n1.   `configClass` ϣÿ `configClass`  `Definitions` ش\n2.   `configClass`  `Definitions` شʱһ `@Import`/`@Bean`/`@ImportResource` ע⣬Сǽע `@Bean` Ĵ\n3.   `@Bean` ʱȴ `BeanDefinition`(`@Bean` Ӧ `BeanDefinition`  `ConfigurationClassBeanDefinition`)Ȼ `@Bean` ԣõ `BeanDefinition` Уǰ `BeanDefinition` עᵽ `beanFactory` .\n\n ˣִ `ConfigurationClassBeanDefinitionReader#loadBeanDefinitions` `BeanDefinition` ͼص `beanFactory` ˣӦ `BeanDefinition`  `ConfigurationClassBeanDefinition`.\n\n### 3.5 `@Bean` ʵ\n\nʵĴͬͨ `@Component` һ£ͬͨ `@Component` õǹ췽 `@Bean` ʹõ `factoryMethod`£\n\n> AbstractAutowireCapableBeanFactory#createBeanInstance\n\n```\n/**\n * ʵĴʽ\n * 1\\. ʹ instanceSupplierSupplierjava8ṩ࣬Դһlambdaʽ\n * 2\\. ʹù @Bean עӦķ\n * 3\\. ʹõǹ췽ע룬췽 @Autowired ע\n * 4\\. 췽ע룬޲ι죬Ҳвι\n *\n */\nprotected BeanWrapper createBeanInstance(String beanName, \n        RootBeanDefinition mbd, @Nullable Object[] args) {\n    // ȷѾ˴ class\n    Class<?> beanClass = resolveBeanClass(mbd, beanName);\n    ...\n\n    // ǷbeanSupplierSupplierjava8ṩ࣬Դһlambdaʽ\n    //  AbstractBeanDefinition#setInstanceSupplier ָ\n    Supplier<?> instanceSupplier = mbd.getInstanceSupplier();\n    if (instanceSupplier != null) {\n        return obtainFromSupplier(instanceSupplier, beanName);\n    }\n    if (mbd.getFactoryMethodName() != null) {\n        // ùʵ\n        return instantiateUsingFactoryMethod(beanName, mbd, args);\n    }\n    // Ƿһ\n    boolean resolved = false;\n    // Ƿù캯ע\n    boolean autowireNecessary = false;\n    ...\n    if (resolved) {\n        if (autowireNecessary) {\n            return autowireConstructor(beanName, mbd, null, null);\n        }\n        else {\n            // ޲ι캯\n            return instantiateBean(beanName, mbd);\n        }\n    }\n    // Candidate constructors for autowiring?\n    // жǷвι캯\n    Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(\n            beanClass, beanName);\n    if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||\n            mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {\n        return autowireConstructor(beanName, mbd, ctors, args);\n    }\n    ctors = mbd.getPreferredConstructors();\n    if (ctors != null) {\n        // 캯ע\n        return autowireConstructor(beanName, mbd, ctors, null);\n    }\n    // ޲ι캯\n    return instantiateBean(beanName, mbd);\n}\n\n```\n\nӴspring ʵķʽ 4 ֣\n\n1.  ʹ `instanceSupplier``Supplier`  `java8` ṩ࣬Դһ `lambda` ʽ\n2.  ʹù `@Bean` עӦķ\n3.  ʹõǹ췽ע룬췽 `@Autowired` ע\n4.  췽ע룬޲ι죬Ҳвι\n\nҪע `@Bean` ʵʽҲǹʵʽǽȥ£\n\n```\npublic BeanWrapper instantiateUsingFactoryMethod(String beanName, \n             RootBeanDefinition mbd, @Nullable Object[] explicitArgs) {\n    BeanWrapperImpl bw = new BeanWrapperImpl();\n    this.beanFactory.initBeanWrapper(bw);\n    Object factoryBean;\n    Class<?> factoryClass;\n    boolean isStatic;\n    String factoryBeanName = mbd.getFactoryBeanName();\n    if (factoryBeanName != null) {\n        factoryBean = this.beanFactory.getBean(factoryBeanName);\n        factoryClass = factoryBean.getClass();\n        isStatic = false;\n    }\n    ...\n\n    Method factoryMethodToUse = null;\n    ArgumentsHolder argsHolderToUse = null;\n    Object[] argsToUse = null;\n    //  factoryMethod Ĳ\n    if (explicitArgs != null) {\n        argsToUse = explicitArgs;\n    }\n    else {\n        ...\n    }\n\n    if (factoryMethodToUse == null || argsToUse == null) {\n        factoryClass = ClassUtils.getUserClass(factoryClass);\n        List<Method> candidates = null;\n        if (mbd.isFactoryMethodUnique) {\n            if (factoryMethodToUse == null) {\n                factoryMethodToUse = mbd.getResolvedFactoryMethod();\n            }\n            if (factoryMethodToUse != null) {\n                candidates = Collections.singletonList(factoryMethodToUse);\n            }\n        }\n        // ʡ˺ö\n        ...\n    }\n    bw.setBeanInstance(instantiate(beanName, mbd, \n            factoryBean, factoryMethodToUse, argsToUse));\n    return bw;\n}\n\n```\n\nϷǾĴ룬ԭȽ϶࣬󲿷ִڴ `argsToUse`  `factoryMethodToUse` ϸڷǳ࣬ͲչˣҪע¼\n\n1.  `factoryBean` `@Bean` ʵ `beanConfig`;\n2.  `factoryMethodToUse`: ʵõķҲǱ `@Bean` עķ `BeanConfigs#beanObj1`;\n3.  `argsToUse` `@Bean` עķҪõĲ `BeanConfigs#beanObj1` ûָ null;\n\nʵıʵʽҲ뵽ˣʵˣǵ÷ʵˣ\n\n> ConstructorResolver#instantiate(...)\n\n```\nprivate Object instantiate(String beanName, RootBeanDefinition mbd,\n        @Nullable Object factoryBean, Method factoryMethod, Object[] args) {\n    try {\n\n        return this.beanFactory.getInstantiationStrategy().instantiate(\n                mbd, beanName, this.beanFactory, factoryBean, factoryMethod, args);\n    }\n    catch (Throwable ex) {\n        ...\n    }\n}\n\n```\n\n> SimpleInstantiationStrategy#instantiate(...)\n\n```\n@Override\npublic Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,\n        @Nullable Object factoryBean, final Method factoryMethod, Object... args) {\n    try {\n        ...\n        try {\n            currentlyInvokedFactoryMethod.set(factoryMethod);\n            // ൱ڵ beanConfigs.beanObj1()\n            Object result = factoryMethod.invoke(factoryBean, args);\n            if (result == null) {\n                result = new NullBean();\n            }\n            return result;\n        }\n        finally {\n            ...\n        }\n    }\n    catch (...) {\n        ...\n    }\n}\n\n```\n\n `SimpleInstantiationStrategy#instantiate(...)` нзʵˣ\n\nʵɺ󣬺ע롢ʼȶͨ spring bean һ£Ͳٷˡ\n\n### 3.6 `@Configuration`  `@Bean` ʹ\n\n `@Component`  `@Bean` ʹʱĴ\n\n```\n@Component\npublic class BeanConfigs {\n    @Bean\n    public Xxx xxx() {\n        ...\n    }\n}\n\n```\n\nʵϣʹõ `@Configuration`  `@Bean` ϣ\n\n```\n@Configuration\npublic class BeanConfigs {\n    @Bean\n    public Xxx xxx() {\n        ...\n    }\n}\n\n```\n\nǰʹõ `@Component` кβأǾ¡\n\n#### 1\\. demo ׼\n\ndemo ׼\n\n```\n//@Component\n@Configuration\npublic class BeanConfigs {\n\n    @Bean\n    public BeanObj1 beanObj1() {\n        return new BeanObj1();\n    }\n\n    @Bean\n    public BeanObj2 beanObj2() {\n        //  beanObj1() \n        beanObj1();\n        return new BeanObj2();\n    }\n\n}\n\n```\n\n demo ֻǽ `@Component` 滻Ϊ `@Configuration`ִ££\n\n```\nbeanObj1Ĺ췽\nbeanObj2Ĺ췽\nobj1:BeanObj1{}\nobj2:BeanObj2{}\norg.springframework.learn.explore.demo02.BeanConfigs$$EnhancerBySpringCGLIB$$dca1c55b@75c072cb\n\n```\n\nǽ֮ǰִҲ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-88e5047479e4f30e70d09397d2631c0c6a5.png)\n\nȽϣͬ\n\n1.  `beanObj1` Ĺ췽һΣ\n2.  `beanConfigs` Ӧ `BeanConfigs$$EnhancerBySpringCGLIB$$dca1c55b@75c072cb`˵һʹ cglib \n\nʵϣͬ㶼ԹΪһԭspring  `beanConfigs` ˴ `BeanConfigs#beanObj1` ʵʵõǴ **spring Ա `@Configuration` ǵ cglib **\n\nôôеأǼ̽\n\n#### 2\\. Ĵ`ConfigurationClassPostProcessor#enhanceConfigurationClasses`\n\n[һƪ](https://my.oschina.net/funcy/blog/4836178)ĿƪǾᵽ `ConfigurationClassPostProcessor` ִʱõ`processConfigBeanDefinitions(...)`  `enhanceConfigurationClasses(...)``processConfigBeanDefinitions(...)`  spring  `@Import``@Configuration` עĽǰѾˣ `enhanceConfigurationClasses(...)` Ǳ `@Configuration` ǵĹؼڣ\n\n`enhanceConfigurationClasses(...)` £\n\n> ConfigurationClassPostProcessor#enhanceConfigurationClasses\n\n```\npublic void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {\n    Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();\n    for (String beanName : beanFactory.getBeanDefinitionNames()) {\n        BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);\n        Object configClassAttr = beanDef.getAttribute(\n            ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE);\n        ...\n        // 1\\. жǷΪһȫ\n        if (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) {\n            ...\n            configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);\n        }\n    }\n    // ȫࣺ\n    ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();\n    for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {\n        AbstractBeanDefinition beanDef = entry.getValue();\n        beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);\n        //  BeanClass\n        Class<?> configClass = beanDef.getBeanClass();\n        // 2\\.  enhancedClass\n        Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);\n        if (configClass != enhancedClass) {\n            // 3\\.  BeanClassֵΪenhancedClass\n            beanDef.setBeanClass(enhancedClass);\n        }\n    }\n}\n\n```\n\nȽϼ򵥣£\n\n1.  жǷΪȫ࣬[һƪ](https://my.oschina.net/funcy/blog/4836178)Уᵽ spring Ѵ `@Configuration` ע `proxyBeanMethods != false` Ϊ `Full` ࣬ǸǰıжϷΪȫ࣬ԣʱ `beanConfigs` һȫࣻ\n2.  ȫ࣬ `configClass` ɶӦ `enhancedClass`\n3.  ɵ `enhancedClass` õ `beanDefinition`  `beanClass` С\n\nִ˷`beanConfigs` Ӧ `beanDefinition`  `beanClass` Ǵˣ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-f617fc93f72b13b8f785ccf5a12624dec0b.png)\n\n洴 `beanConfigs` ʵˡ\n\n#### 3\\. ִдķ\n\nɴ󣬴ִеأ spring ִ `beanConfigs.beanObj1()` أ˵Ҫ̸ cglib ķִˡֱɣ `enhancer.enhance(configClass, this.beanClassLoader)`\n\n```\npublic Class<?> enhance(Class<?> configClass, @Nullable ClassLoader classLoader) {\n    if (EnhancedConfiguration.class.isAssignableFrom(configClass)) {\n        return configClass;\n    }\n    // newEnhancer(...) ǹؼ\n    Class<?> enhancedClass = createClass(newEnhancer(configClass, classLoader));\n    return enhancedClass;\n}\n\n```\n\n룺\n\n```\nprivate static final Callback[] CALLBACKS = new Callback[] {\n        // ֤ @Bean ĵ\n        new BeanMethodInterceptor(),\n        new BeanFactoryAwareMethodInterceptor(),\n        NoOp.INSTANCE\n};\n\n//  CallbackFilterĶΪCallback\nprivate static final ConditionalCallbackFilter CALLBACK_FILTER \n        = new ConditionalCallbackFilter(CALLBACKS);\n\n// cglibǿ\nprivate Enhancer newEnhancer(Class<?> configSuperClass, @Nullable ClassLoader classLoader) {\n    Enhancer enhancer = new Enhancer();\n    enhancer.setSuperclass(configSuperClass);\n    enhancer.setInterfaces(new Class<?>[] {EnhancedConfiguration.class});\n    enhancer.setUseFactory(false);\n    enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);\n    enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));\n    // callbackFilter\n    enhancer.setCallbackFilter(CALLBACK_FILTER);\n    enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());\n    return enhancer;\n}\n\n```\n\n cglib ݣ [spring aop ֮ cglib ](https://my.oschina.net/funcy/blog/4696655)һϸˣⲻٷֱ˵ۣcglib ִдʱִе `Enhancer`  `callbackFilter` Ե `MethodInterceptor#intercept`  `CALLBACKS` е `BeanMethodInterceptor`Ǿݣ\n\n> ConfigurationClassEnhancer.BeanMethodInterceptor\n\n```\nprivate static class BeanMethodInterceptor implements MethodInterceptor, ConditionalCallback {\n    @Override\n    @Nullable\n    public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,\n                MethodProxy cglibMethodProxy) throws Throwable {\n        ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);\n        String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod);\n        ...\n        // ǵõǰ factoryMethod ֱӵøķ\n        if (isCurrentlyInvokedFactoryMethod(beanMethod)) {\n            // øķҲĿ귽\n            return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);\n        }\n        // ֱӻȡ beanFactoryеĶ\n        return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);\n    }\n\n    private Object resolveBeanReference(Method beanMethod, Object[] beanMethodArgs,\n            ConfigurableBeanFactory beanFactory, String beanName) {\n        try {\n            ...\n            // õĵ beanFactory.getBean(...) ѾǳϤ\n            Object beanInstance = (useArgs ? beanFactory.getBean(beanName, beanMethodArgs) :\n                    beanFactory.getBean(beanName));\n            ...\n            return beanInstance;\n        }\n        finally {\n            ...\n        }\n    }\n\n    /**\n     * жִܷеǰ MethodInterceptor\n     */\n    @Override\n    public boolean isMatch(Method candidateMethod) {\n        return (candidateMethod.getDeclaringClass() != Object.class &&\n                !BeanFactoryAwareMethodInterceptor.isSetBeanFactory(candidateMethod) &&\n                BeanAnnotationHelper.isBeanAnnotated(candidateMethod));\n    }\n}\n\n```\n\n`BeanMethodInterceptor` ʵ `MethodInterceptor`  `ConditionalCallback``ConditionalCallback#isMatch` жϵǰ `MethodInterceptor` ִܷУ`MethodInterceptor#intercept` ִеķݣִ߼Ϊ\n\n1.  ֱӵõǰ `factoryMethod` ֱӵøķҲ `beanConfigs.beanObj1()`̻ʵ `beanObj1` ʱã\n2.  ֱӵõǰ `factoryMethod` ڱķеã `beanFactory.getBean(...)` ȡ bean̻ʵ `beanObj1` ʱ á\n\nϾΪʲô `beanObj1` Ĺ췽ֻһΣԼΪʲô `beanConfigs` Ǵԭˡ\n\nһ䣬`@Configuration` ṩ `proxyBeanMethods()` ѡǷĴĬֵ trueã\n\n```\n@Configuration(proxyBeanMethods=false)\npublic class BeanConfigs {\n    ...\n}\n\n```\n\n`BeanConfigs` Ͳдˣнͬ `@Component` עһͲչʾˡ\n\n#### 4\\. С\n\n##### 1\\. ڲҲܱ\n\nʾУõģ\n\n```\n    @Bean\n    public BeanObj2 beanObj2() {\n        //  beanObj1() \n        beanObj1();\n        return new BeanObj2();\n    }\n\n```\n\n `beanObj2()` е `beanObj1()`ڲã`beanObj1()` Ҳܱ\n\n**ش**cglib ĵ÷֣\n\n```\n@Override\npublic Object intercept(Object proxyObj, Method method, Object[] objects, \n            MethodProxy proxy) throws Throwable {\n    // 1 ʹĿֱӵĿķ\n    // return proxy.invoke(target, objects);\n    // 2 ʹô󣬵丸ķ\n    return proxy.invokeSuper(proxyObj, objects);\n}\n\n```\n\n`beanObj2()` ĵʹ`2`Ҳʹô `beanObj2()``beanObj2()`  `this` Ϊ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-7ef891450ace815d22a5bf5c8e78e65c2db.png)\n\n `beanObj2()` ֱӵ `beanObj1()`൱ʹô `beanObj1()`Ȼܱˡ\n\nӡУڲòܱΪ spring ڴ Aop ʱʹõǷ 1 ĵ÷ʽʱ `this` Ϊԭʼ󣬵Ȼܱˡ\n\n##### 2\\. ˽ע룿\n\n磬һ `BeanObj3`:\n\n```\n@Component\npublic class BeanObj3 {\n\n    public BeanObj3() {\n        System.out.println(\"beanObj3Ĺ췽\");\n    }\n\n    @Override\n    public String toString() {\n        return \"BeanObj3{}\";\n    }\n}\n\n```\n\nȻ `BeanConfigs` ע룺\n\n```\n@Configuration\npublic class BeanConfigs {\n\n    @Autowired\n    private BeanObj3 beanObj3;\n\n    @Bean\n    public BeanObj1 beanObj1() {\n        return new BeanObj1();\n    }\n\n    @Bean\n    public BeanObj2 beanObj2() {\n        //  beanObj1() \n        beanObj1();\n        System.out.println(\"beanObj3\" + this.beanObj3);\n        return new BeanObj2();\n    }\n\n}\n\n```\n\n `BeanConfigs` Զע `beanObj3` ԣȻ `beanObj2()` ִӡ `beanObj3` ԡУ£\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-e21bf76da41fcf12e674fa155de4b636253.png)\n\nԿע `beanObj3` Ҳܻȡˡиˣ`beanObj3` Ŀģ `this` ǴѲɴõĿ˽ԣ\n\nȣӵ `beanFactory`  `beanDefinitionMap` е `BeanConfigs$$EnhancerBySpringCGLIB$$Xxx` ࣩࣨ `BeanConfigs`spring ڽעʱҵǰ༰丸еעԽע룬ˣȻӵ spring е  `BeanConfigs$$EnhancerBySpringCGLIB$$Xxx` ࣬ `BeanConfigs` е `beanObj3` һᱻע룬ԭ cglib Ĵϵ`BeanConfigs`  `BeanConfigs$$EnhancerBySpringCGLIB$$Xxx` ĸࡣ\n\n `BeanConfigs$$EnhancerBySpringCGLIB$$Xxx` ̳ `beanObj3` ֱӿнɣ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-27ebdebfd8c958f1de362ca1b30f2d0fc7c.png)\n\nյõ `beanConfigs` 󣬿Կһ `beanObj3` ԣһֵ\n\n### 3.7 ܽ\n\nҪ `ConfigurationClassPostProcessor`  `@Bean` עḶ́ܽ£\n\n1.  ࣬ͨȡб `@Bean` ǵķ\n2.  Щװһ `BeanDefinition` עᵽ `beanFactory` УӦ `BeanDefinition` Ϊ `ConfigurationClassBeanDefinition`\n3.  ȫ࣬ cglib \n4.  ʵʱʹ÷öӦķʵõʵspring ٶע롢ʼȣ\n5.  ڱ `@Bean` еõǰ `@Bean` ʱǰ `@Bean` ڵȫ࣬ȥ `beanFactory` вҶӦ `bean`(ҵĹǣҵ򷵻أҲ򴴽ٷأ**ص bean  spring bean **) cglib ɣǰ `@Bean` ڵ಻ȫ࣬ᰴͨķã bean ʵأ**ص bean û spring bean **\n\nĵķ͵ˣǼ `ConfigurationClassPostProcessor` ע̡\n\n* * *\n\n_ԭӣ[https://my.oschina.net/funcy/blog/4492878](https://my.oschina.net/funcy/blog/4492878) ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע_"
  },
  {
    "path": "docs/Spring全家桶/Spring源码分析/Spring重要机制探秘/ConfigurationClassPostProcessor（四）：处理@Conditional注解.md",
    "content": " `ConfigurationClassPostProcessor` ĵƪҪǷ spring  `@Conditional` עĴ̡\n\n## 5\\. spring δ @Conditional עģ\n\n### 5.1 `@Conditional` Ĵ\n\nǰ `ConfigurationClassParser#processConfigurationClass` ʱôһУ\n\n```\nclass ConfigurationClassParser {\n    protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {\n        // жǷҪ @Conditional ע⣬жǷ\n        if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), \n                ConfigurationPhase.PARSE_CONFIGURATION)) {\n            return;\n        }\n        ...\n    }\n    ...\n}\n\n```\n\n`conditionEvaluator.shouldSkip(...)`  `@Conditional` עģĴ̣ٷʲô `@Conditional` ע⣺\n\n```\n@Target({ElementType.TYPE, ElementType.METHOD})\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\npublic @interface Conditional {\n\n    /**\n     * \n     */\n    Class<? extends Condition>[] value();\n\n}\n\n```\n\n`@Conditional` עǳ򵥣һԣֵ `Class[]`ұ `Condition` ࡣ `Condition`\n\n```\npublic interface Condition {\n\n    /**\n     * ָƥ߼\n     */\n    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);\n\n}\n\n```\n\n`Condition` ӿڽһ `matches` ǿָƥ߼\n\n `conditionEvaluator.shouldSkip(...)` Ĵ̣\n\n```\nclass ConditionEvaluator {\n    public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, \n            @Nullable ConfigurationPhase phase) {\n        // Ƿ @Conditional\n        if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {\n            return false;\n        }\n        // жϴ phase\n        if (phase == null) {\n            if (metadata instanceof AnnotationMetadata &&\n                    ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {\n                return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);\n            }\n            return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);\n        }\n\n        // ʵ condition conditions \n        List<Condition> conditions = new ArrayList<>();\n        // 1\\. getConditionClasses(metadata)ȡ @Conditional ָж\n        for (String[] conditionClasses : getConditionClasses(metadata)) {\n            for (String conditionClass : conditionClasses) {\n                // 2\\. ʵõĻǷ䣩ͳһŵ conditions \n                Condition condition = getCondition(conditionClass, this.context.getClassLoader());\n                conditions.add(condition);\n            }\n        }\n        // 3\\. õ condition ʵ\n        AnnotationAwareOrderComparator.sort(conditions);\n        // õ conditions\n        for (Condition condition : conditions) {\n            ConfigurationPhase requiredPhase = null;\n            if (condition instanceof ConfigurationCondition) {\n                requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();\n            }\n            // 4\\.  Condition#matches ж\n            if ((requiredPhase == null || requiredPhase == phase) && \n                    !condition.matches(this.context, metadata)) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    ...\n}\n\n```\n\n÷Ĵ£\n\n1.  ȡ `@Conditional` ָж࣬ `@Conditional`  `value` ֵ\n2.  ʹ ʵ 1  õж࣬ 浽 `conditions`Ǹ `List` У\n3.  Ե 2 õ `conditions` \n4.   3 õ `conditions` `Condition#matches` ƥ䡣\n\n`@Conditional` ĴǷǳ򵥵ģʹʾ\n\n### 5.2 `@Conditional` ʹʾ\n\n#### ʾ 1ָʱŴ spring bean\n\nʵָܣָʱŽ spring bean Ĵʼ£\n\n1. ׼һ򵥵 bean:\n\n   ```\n   public class BeanObj {\n   \n   }\n   \n   ```\n\n2. ʵ `Condition` ӿڣﴦж߼\n\n   ```\n   public class MyCondition implements Condition {\n       @Override\n       public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {\n           String className = \"java.lang.Object\";\n           try {\n               // жǷ\n               Class.forName(className);\n               return true;\n           } catch (ClassNotFoundException e) {\n               return false;\n           }\n       }\n   }\n   \n   ```\n\n    `matches(...)`  ָ `className` Ϊ `java.lang.Object`ȻжǷڣжϷʽҲ ʮּ ʹ java ķƣ`Class.forName(...)`಻ʱ׳ `ClassNotFoundException`ǿԲ쳣Ӷ֪ڲˡ\n\n3. ׼\n\n   ```\n   @ComponentScan\n   public class BeanConfigs {\n       @Bean\n       @Conditional(MyCondition.class)\n       public BeanObj beanObj() {\n           return new BeanObj();\n       }\n   }\n   \n   ```\n\n   Ƚϼ򵥣Ҫע `beanObj()` ϵ `@Conditional` ע⣬ָƥ `MyCondition`ƥнеġ\n\n4. \n\n   ```\n   public class Demo06Main {\n       public static void main(String[] args) {\n           ApplicationContext context \n                   = new AnnotationConfigApplicationContext(BeanConfigs.class);\n           try {\n               Object obj = context.getBean(\"beanObj\");\n               System.out.println(\"beanObj ڣ\");\n           } catch (Exception e) {\n               System.out.println(\"beanObj ڣ\");\n           }\n       }\n   }\n   \n   ```\n\nУ£\n\n```\nbeanObj ڣ\n\n```\n\n `MyCondition#matches` УжϵǵǰĿǷ `java.lang.Object`ȻǴڵģ `beanObj`  spring Уǻ `className`:\n\n```\npublic class MyCondition implements Condition {\n    @Override\n    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {\n        // \n        String className = \"java.lang.Object111\";\n        ...\n    }\n}\n\n```\n\nȻ`java.lang.Object111` ǲڵǰĿеģУ£\n\n```\nbeanObj ڣ\n\n```\n\n#### ʾ 2Ľʾ 1 \n\nʾ 1 Уͨ `MyCondition#matches` ޸ `className` ı `beanObj` еĴĿзǳҪĴмأʵֶ `MyCondition` \n\n磬 `A` Ҫ `A1` ĴжǷгʼ `B` Ҫ `B1` ĴжǷгʼ `C` Ҫ `C1` ĴжǷгʼ... ǷҪֱΪ`A``B``C` ʵ `Condition`ڸԵ `match(...)` нж\n\nʵϣǲҪôͨ spring עķϹܡ\n\n1. ׼һ beanʾ 1 \n\n   ```\n   public class BeanObj {\n   \n   }\n   \n   ```\n\n2. ׼ע `@ConditionalForClass`ע `@Conditional` ĹܣƥΪ `MyCondition``className` ԾǱڵࣺ\n\n```\n@Target({ElementType.TYPE, ElementType.METHOD})\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\n//  @Conditional ĹܣƥΪ MyCondition\n@Conditional(MyCondition.class)\npublic @interface ConditionalForClass {\n\n    /**\n     * ָڵ\n     */\n    String className();\n\n}\n\n```\n\n1. ׼ `MyCondition`עʾĲڣ`className` ڸ÷ж壬 `@ConditionalForClass` 룺\n\n   ```\n   public class MyCondition implements Condition {\n       /**\n        * ﴦƥעʾ1е\n        */\n       @Override\n       public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {\n           // ȡ @ConditionalForClass עֵ\n           Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(\n                   ConditionalForClass.class.getName());\n           // ȡclassNameֵ @ConditionalForClass  className \n           String className = (String)annotationAttributes.get(\"className\");\n           if(null == className || className.length() <= 0) {\n               return true;\n           }\n           try {\n               // жǷ\n               Class.forName(className);\n               return true;\n           } catch (ClassNotFoundException e) {\n               return false;\n           }\n       }\n   }\n   \n   ```\n\n2. ׼࣬ʱעΪ `@ConditionalForClass`\n\n   ```\n   @ComponentScan\n   public class BeanConfigs {\n       @Bean\n       /**\n        *  @ConditionalForClass ָ\n        */\n       @ConditionalForClass(className = \"java.lang.Object\")\n       public BeanObj beanObj() {\n           return new BeanObj();\n       }\n   }\n   \n   ```\n\n3. ࣬ʾ 1 \n\n   ```\n   public class Demo07Main {\n   \n       public static void main(String[] args) {\n           ApplicationContext context \n                   = new AnnotationConfigApplicationContext(BeanConfigs.class);\n           try {\n               Object obj = context.getBean(\"beanObj\");\n               System.out.println(\"beanObj ڣ\");\n           } catch (Exception e) {\n               System.out.println(\"beanObj ڣ\");\n           }\n       }\n   }\n   \n   ```\n\nͨԶע `@ConditionalForClass` ָ `java.lang.Object` ʱ`beanObj` Żᱻӵ spring УȻУ£\n\n```\nbeanObj ڣ\n\n```\n\n `@ConditionalForClass`  `className` ֵ\n\n```\n@ComponentScan\npublic class BeanConfigs {\n    @Bean\n    // ޸@ConditionalForClassclassNameֵ\n    @ConditionalForClass(className = \"java.lang.Object1111\")\n    public BeanObj beanObj() {\n        return new BeanObj();\n    }\n}\n\n```\n\nｫ `@ConditionalForClass`  `className` ֵΪ `java.lang.Object1111`ȻಢڵǰĿУн£\n\n```\nbeanObj ڣ\n\n```\n\nҲǵһ¡\n\nǻصڿͷ⣺磬 `A` Ҫ `A1` ĴжǷгʼ `B` Ҫ `B1` ĴжǷгʼ `C` Ҫ `C1` ĴжǷгʼ... ǷҪֱΪ`A``B``C` ʵ `Condition`ڸԵ `match(...)` нж\n\n `@ConditionalForClass` עǲҪô鷳ֻҪڸԵ `@Bean`  `@ConditionalForClass` ˣ\n\n```\n@Bean\n@ConditionalForClass(className = \"A1\")\npublic A a() {\n    return new A();\n}\n\n@Bean\n@ConditionalForClass(className = \"B1\")\npublic B b() {\n    return new B();\n}\n\n@Bean\n@ConditionalForClass(className = \"B1\")\npublic C c() {\n    return new C();\n}\n\n...\n\n```\n\nע `@ConditionalForClass` ʵ֣springboot е `@ConditionalOnClass` ǰ˼·ʵֵġ\n\n### 5.3 ܽ\n\nҪ spring  `@Conditional` ̣߼Ƚϼ򵥣յõ `Condition#matches` ƥģƥ `Condition` ʵָ\n\nΪ˸õ˵ `@Conditional` ʹã׼ʹʾرʾ 2Ҫرᣬspringboot е `@ConditionalOnClass` ǻʾ 2 ˼·ʵֵģ⣬springboot еĶעҲǶ `@Conditional` չ\n\n* * *\n\n_ԭӣ[https://my.oschina.net/funcy/blog/4873444](https://my.oschina.net/funcy/blog/4873444) ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע_"
  },
  {
    "path": "docs/Spring全家桶/Spring源码分析/Spring重要机制探秘/Spring探秘之AOP的执行顺序.md",
    "content": "spring aop ִʱ˳ģθıִеȼĽԴ̽ aop ִ˳ܡ\n\n [spring aop ֮ AnnotationAwareAspectJAutoProxyCreator ϣ](https://my.oschina.net/funcy/blog/4678817)  [spring aop ֮ AnnotationAwareAspectJAutoProxyCreator £](https://my.oschina.net/funcy/blog/4687961)Уݹ spring aop ִй̣һι aop \n\n*   `ReflectiveAspectJAdvisorFactory#getAdvisorMethods` `aspect` е `@Around/@Before/@After` ȷ\n*   `AspectJAwareAdvisorAutoProxyCreator#sortAdvisors` `advisor` \n\nص\n\n#### 1 `ReflectiveAspectJAdvisorFactory#getAdvisorMethods`\n\nһ `ReflectiveAspectJAdvisorFactory#getAdvisorMethods`ýṹ£\n\n```\n|-AbstractAutoProxyCreator#postProcessBeforeInstantiation\n  |-AspectJAwareAdvisorAutoProxyCreator#shouldSkip\n   |-AnnotationAwareAspectJAutoProxyCreator#findCandidateAdvisors\n    |-BeanFactoryAspectJAdvisorsBuilder#buildAspectJAdvisors\n     |-ReflectiveAspectJAdvisorFactory#getAdvisors\n      |-ReflectiveAspectJAdvisorFactory#getAdvisorMethods\n\n```\n\n£\n\n> ReflectiveAspectJAdvisorFactory\n\n```\n// ȡ @Aspect еķ\nprivate List<Method> getAdvisorMethods(Class<?> aspectClass) {\n    final List<Method> methods = new ArrayList<>();\n    // ʡԻȡĲ\n    ...\n\n    //Եõз\n    methods.sort(METHOD_COMPARATOR);\n    return methods;\n}\n\n```\n\nĲ [spring aop ֮ AnnotationAwareAspectJAutoProxyCreator ϣ](https://my.oschina.net/funcy/blog/4678817) һϸǽע\n\n> ReflectiveAspectJAdvisorFactory\n\n```\n/**\n * METHOD_COMPARATOR \n */\nprivate static final Comparator<Method> METHOD_COMPARATOR;\nstatic {\n    Comparator<Method> adviceKindComparator = new ConvertingComparator<>(\n            // Ƚ˳бȽ\n            new InstanceComparator<>(\n                    Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class),\n            // תתΪ @Around, @Before, @After, @AfterReturning, @AfterThrowing ע\n            (Converter<Method, Annotation>) method -> {\n                AspectJAnnotation<?> annotation =\n                    AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(method);\n                return (annotation != null ? annotation.getAnnotation() : null);\n            });\n    // תȽ\n    // 1\\. תķ(Method)תΪ(String)\n    // 2\\. ȽϣͽбȽϣﴫĵΪStringԭתMethodתString\n    Comparator<Method> methodNameComparator = new ConvertingComparator<>(Method::getName);\n    /*\n     * METHOD_COMPARATOR ȽϹ\n     * 1\\. ʶע,  @Around, @Before, @After, @AfterReturning,\n     *       @AfterThrowing ˳ (`adviceKindComparator`)\n     * 2\\. ûбʶЩע⣬򰴷Ƶַ(`methodNameComparator`)\n     */\n    METHOD_COMPARATOR = adviceKindComparator.thenComparing(methodNameComparator);\n}\n\n```\n\nܽ£\n\n1.  Ķͬһ `@Aspect` еķ\n2.  淽£`@Around`, `@Before`, `@After`, `@AfterReturning`, `@AfterThrowing`\n3.  ڷ淽String \n\n÷ǰı仯£\n\nǰ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-23df6d6a46f37badb1017ceee8dcfa6533e.png)\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-85bdb16953c1c6348d39578de6b6144c1cc.png)\n\nǷ `@Around, @Before, @After, @AfterReturning,@AfterThrowing` ˳һ¡\n\nõ `List<Method>` 󣬽ŻЩ `method`װΪһ `advisor`\n\n> ReflectiveAspectJAdvisorFactory\n\n```\npublic List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) {\n    // ʡһЩ\n    ...\n\n    List<Advisor> advisors = new ArrayList<>();\n    //ȡеǿ\n    for (Method method : getAdvisorMethods(aspectClass)) {\n        // ǿʵadvisors.size() Ϊ 012... \n        // declarationOrderInAspect ֵõ\n        Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, \n                advisors.size(), aspectName);\n        if (advisor != null) {\n            advisors.add(advisor);\n        }\n    }\n\n    // ʡһЩ\n    ...\n}\n\n```\n\nװ `Advisor` ʱᴫ `declarationOrderInAspect` ֵֵΪ `advisors.size()`Ϊ `012...`ֵںлõ\n\n### 2\\. advisor `AspectJAwareAdvisorAutoProxyCreator#sortAdvisors`\n\n`@Aspect` е淽װ `advisor` 󣬻߻ȡԶ `advisor` 󣬽žͽ˵ڶ`AspectJAwareAdvisorAutoProxyCreator#sortAdvisors`ĵ£\n\n```\n|-AbstractAutoProxyCreator#postProcessAfterInitialization\n |-AbstractAutoProxyCreator#wrapIfNecessary\n  |-AbstractAdvisorAutoProxyCreator#getAdvicesAndAdvisorsForBean\n   |-AbstractAdvisorAutoProxyCreator#findEligibleAdvisors\n    |-AspectJAwareAdvisorAutoProxyCreator#sortAdvisors\n\n```\n\n> AspectJAwareAdvisorAutoProxyCreator\n\n```\nprotected List<Advisor> sortAdvisors(List<Advisor> advisors) {\n    List<PartiallyComparableAdvisorHolder> partiallyComparableAdvisors \n            = new ArrayList<>(advisors.size());\n    for (Advisor element : advisors) {\n        partiallyComparableAdvisors.add(\n            // ȽϹΪ DEFAULT_PRECEDENCE_COMPARATOR,ʵAspectJPrecedenceComparator\n            new PartiallyComparableAdvisorHolder(element, DEFAULT_PRECEDENCE_COMPARATOR));\n    }\n    // ıȽϲȽϹ AspectJPrecedenceComparator ṩ\n    List<PartiallyComparableAdvisorHolder> sorted \n            = PartialOrder.sort(partiallyComparableAdvisors);\n    if (sorted != null) {\n        List<Advisor> result = new ArrayList<>(advisors.size());\n        for (PartiallyComparableAdvisorHolder pcAdvisor : sorted) {\n            result.add(pcAdvisor.getAdvisor());\n        }\n        return result;\n    }\n    else {\n        return super.sortAdvisors(advisors);\n    }\n}\n\n```\n\nһ `PartialOrder.sort(...)`һ `super.sortAdvisors(...)` `PartialOrder.sort(...)`\n\n#### 2.1 `PartialOrder.sort(...)` ıȽ`AspectJPrecedenceComparator`\n\nʵϣ`PartialOrder.sort(...)` ֻҪһѣûɶģҪӦǴҲ `DEFAULT_PRECEDENCE_COMPARATOR`\n\n```\nprivate static final Comparator<Advisor> DEFAULT_PRECEDENCE_COMPARATOR \n        = new AspectJPrecedenceComparator();\n\n```\n\n`DEFAULT_PRECEDENCE_COMPARATOR`  `AspectJPrecedenceComparator`ֱӲ鿴 `compare(xxx)` \n\n> AspectJPrecedenceComparator\n\n```\n@Override\npublic int compare(Advisor o1, Advisor o2) {\n    // ȽϹAnnotationAwareOrderComparator\n    int advisorPrecedence = this.advisorComparator.compare(o1, o2);\n    // ˳ͬԴͬһ aspect comparePrecedenceWithinAspect ٴαȽ\n    if (advisorPrecedence == SAME_PRECEDENCE && declaredInSameAspect(o1, o2)) {\n        // Ƚ˳һafter֪ͨȼߣȼ\n        advisorPrecedence = comparePrecedenceWithinAspect(o1, o2);\n    }\n    return advisorPrecedence;\n}\n\n```\n\n`AspectJPrecedenceComparator#compare` Ƚϼ򵥣£\n\n1.   `advisorComparator.compare` бȽϣȽϹǽ\n2.  ȽϹõȼͬ `advisor` ͬһ aspect жģ `comparePrecedenceWithinAspect` Ƚ.\n\n##### `this.advisorComparator.compare`\n\n `this.advisorComparator.compare` ıȽϹ\n\n```\nprivate final Comparator<? super Advisor> advisorComparator;\n\npublic AspectJPrecedenceComparator() {\n    this.advisorComparator = AnnotationAwareOrderComparator.INSTANCE;\n}\n\npublic int compare(Advisor o1, Advisor o2) {\n    // ȽϹAnnotationAwareOrderComparator\n    int advisorPrecedence = this.advisorComparator.compare(o1, o2);\n    ...\n}\n\n```\n\n`this.advisorComparator.compare` ıȽϹ `AnnotationAwareOrderComparator` ṩ\n\n```\npublic int compare(@Nullable Object o1, @Nullable Object o2) {\n    return doCompare(o1, o2, null);\n}\n\n/**\n * ıȽϲȱȽ PriorityOrderedٱȽ Ordered\n */\nprivate int doCompare(@Nullable Object o1, @Nullable Object o2, \n        @Nullable OrderSourceProvider sourceProvider) {\n    // ֮һΪ PriorityOrdered˭PriorityOrdered˭ȼ\n    boolean p1 = (o1 instanceof PriorityOrdered);\n    boolean p2 = (o2 instanceof PriorityOrdered);\n    if (p1 && !p2) {\n        return -1;\n    }\n    else if (p2 && !p1) {\n        return 1;\n    }\n    // orderֵȲOrderedӿڣûҵٲ @Order ע\n    int i1 = getOrder(o1, sourceProvider);\n    int i2 = getOrder(o2, sourceProvider);\n    // IntegerбȽ\n    return Integer.compare(i1, i2);\n}\n\n```\n\nĴ֪ȱȽ `PriorityOrdered`ٱȽ `Ordered`ȽϹ£\n\n1.  `PriorityOrdered` Ƚϣ֮Уֻһʵ `PriorityOrdered` ӿڣôΪ `PriorityOrdered` ȼߣ  `Ordered` ĹȽϣ\n2.  `Ordered` ȽϹ\n    1.  ʵ `Ordered`  `PriorityOrdered` ӿڣ `getOrder()` ֵбȽϣֵԽСȼԽߣ\n    2.  ע `@Order/@Priority` ע⣬ `value()` ֵбȽϣֵԽСȼԽߣ\n    3.  ûʵ `Ordered/PriorityOrdered`Ҳûбע `@Order/@Priority` ע⣬Ϊȼ (`Integer.MAX_VALUE`).\n\n##### `comparePrecedenceWithinAspect`\n\n `@Aspect` ע࣬ͬһ `aspect` ﶨͬ `advice`spring aop ҲṩһױȽϹ\n\n```\n/**\n *  @Aspect ͬһaspectﶨͬ adviceٴαȽ\n */\nprivate int comparePrecedenceWithinAspect(Advisor advisor1, Advisor advisor2) {\n    boolean oneOrOtherIsAfterAdvice = (AspectJAopUtils.isAfterAdvice(advisor1) \n            || AspectJAopUtils.isAfterAdvice(advisor2));\n    int adviceDeclarationOrderDelta = getAspectDeclarationOrder(advisor1) \n            - getAspectDeclarationOrder(advisor2);\n    // һafter֪ͨdeclarationOrderInAspectȼ\n    if (oneOrOtherIsAfterAdvice) {\n        if (adviceDeclarationOrderDelta < 0) {\n            return LOWER_PRECEDENCE;\n        }\n        else if (adviceDeclarationOrderDelta == 0) {\n            return SAME_PRECEDENCE;\n        }\n        else {\n            return HIGHER_PRECEDENCE;\n        }\n    }\n    // ߶after֪ͨdeclarationOrderInAspectСȼ\n    else {\n        if (adviceDeclarationOrderDelta < 0) {\n            return HIGHER_PRECEDENCE;\n        }\n        else if (adviceDeclarationOrderDelta == 0) {\n            return SAME_PRECEDENCE;\n        }\n        else {\n            return LOWER_PRECEDENCE;\n        }\n    }\n}\n\n```\n\nȽϹ£Ƚߵ `declarationOrderInAspect` ֵ֮һΪ `after` ֪ͨ`declarationOrderInAspect` ȼߣ߶ `after` ֪ͨ`declarationOrderInAspect` Сȼߡ\n\n `declarationOrderInAspect` ʲôأһСᵽ `advisor.size()`£\n\n> ReflectiveAspectJAdvisorFactory\n\n```\npublic List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) {\n    // ʡһЩ\n    ...\n\n    List<Advisor> advisors = new ArrayList<>();\n    //ȡеǿ\n    for (Method method : getAdvisorMethods(aspectClass)) {\n        // ǿʵadvisors.size() Ϊ 012... \n        // declarationOrderInAspect ֵõ\n        Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, \n                advisors.size(), aspectName);\n        if (advisor != null) {\n            advisors.add(advisor);\n        }\n    }\n\n    // ʡһЩ\n    ...\n}\n\n```\n\nرǿǣֻͬһ `@Aspect` ඨġ֪ͬͨ磺\n\n```\n@Aspect\npublic class AspectTest {\n    @Before\n    public void before1() {\n        ...\n    }\n\n    @Before\n    public void before2() {\n        ...\n    }\n\n}\n\n```\n\n `before1()`  `before2()` Ӧ `advisor`  `comparePrecedenceWithinAspect` 򣬶´Ͳˣԭڲͬ `@Aspect` жģ\n\n```\n@Aspect\npublic class AspectTest1 {\n    @Before\n    public void before() {\n        ...\n    }\n\n}\n\n@Aspect\npublic class AspectTest2 {\n    @Before\n    public void before() {\n        ...\n    }\n\n}\n\n```\n\n#### 2. `super.sortAdvisors`\n\nٻعͷ `super.sortAdvisors(advisors)`:\n\n> AspectJAwareAdvisorAutoProxyCreator\n\n```\nprotected List<Advisor> sortAdvisors(List<Advisor> advisors) {\n    ...\n    else {\n        return super.sortAdvisors(advisors);\n    }\n}\n\n```\n\nǸȥ\n\n> AbstractAdvisorAutoProxyCreator\n\n```\n protected List<Advisor> sortAdvisors(List<Advisor> advisors) {\n     AnnotationAwareOrderComparator.sort(advisors);\n     return advisors;\n }\n\n```\n\nʹõ `AnnotationAwareOrderComparator.sort(advisors)`ʵϣ `this.advisorComparator.compare` ıȽϹͲٷˡ\n\n### 3. `getOrder()` ֵ\n\n#### `BeanFactoryTransactionAttributeSourceAdvisor#getOrder()`\n\n`BeanFactoryTransactionAttributeSourceAdvisor` û `@Order/@Priority`ʵ `Ordered` ӿڣִ˳ `getOrder()` ķֵӦ `getOrder()` £\n\n```\n    /**\n     * ȡ order£\n     * 1\\. ָ orderֱӷأ\n     * 2\\. ȡ advisor  advice advice ʵ Ordered ӿڣ getOrder()\n     * 3\\. ϶㣬򷵻 Ordered.LOWEST_PRECEDENCE (ȼ)\n     * @return\n     */\n    @Override\n    public int getOrder() {\n        if (this.order != null) {\n            return this.order;\n        }\n        Advice advice = getAdvice();\n        if (advice instanceof Ordered) {\n            return ((Ordered) advice).getOrder();\n        }\n        return Ordered.LOWEST_PRECEDENCE;\n    }\n\n```\n\n`Ordered.LOWEST_PRECEDENCE` Ϊ `Integer.MAX_VALUE` `2147483647`ٿ `BeanFactoryTransactionAttributeSourceAdvisor`  `getOrder()` صֵ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-d05044b7dcd41855ab6e59727c4be74bdb9.png)\n\nɼ`BeanFactoryTransactionAttributeSourceAdvisor` ִ˳Ĭϵ `Integer.MAX_VALUE`ȵĻֵ `return this.order` صģ\n\n```\npublic int getOrder() {\n    // ͨȷ֣this.order Ϊnull\n    if (this.order != null) {\n        return this.order;\n    }\n    // ʡһЩ\n    ...\n}\n\n```\n\nôֵǴأطڴ `BeanFactoryTransactionAttributeSourceAdvisor` ʱ `BeanFactoryTransactionAttributeSourceAdvisor#setOrder` õģ\n\n> ProxyTransactionManagementConfiguration\n\n```\n@Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)\n@Role(BeanDefinition.ROLE_INFRASTRUCTURE)\npublic BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor() {\n    BeanFactoryTransactionAttributeSourceAdvisor advisor \n            = new BeanFactoryTransactionAttributeSourceAdvisor();\n    advisor.setTransactionAttributeSource(transactionAttributeSource());\n    advisor.setAdvice(transactionInterceptor());\n    if (this.enableTx != null) {\n        // ȡ @EnableTransactionManagement ע order() ֵ\n        advisor.setOrder(this.enableTx.<Integer>getNumber(\"order\"));\n    }\n    return advisor;\n}\n\n```\n\nǾˣ advisor ִ˳ `@EnableTransactionManagement` ָ\n\n```\npublic @interface EnableTransactionManagement {\n\n    boolean proxyTargetClass() default false;\n\n    AdviceMode mode() default AdviceMode.PROXY;\n\n    /**\n     * ָadvisorִ˳Ĭȼ\n     */\n    int order() default Ordered.LOWEST_PRECEDENCE;\n\n}\n\n```\n\nۣ`@EnableTransactionManagement` ע `order()` ָ `advisor` ִ˳\n\n#### `InstantiationModelAwarePointcutAdvisorImpl#getOrder()`\n\nǰķ֪`@Aspect` еÿһնתΪ `advisor`Ϊ `InstantiationModelAwarePointcutAdvisorImpl`Ҳʵ `Ordered` ӿڣִ˳Ҳ `InstantiationModelAwarePointcutAdvisorImpl#getOrder()`  `getOrder()` £\n\n> InstantiationModelAwarePointcutAdvisorImpl\n\n```\n@Override\npublic int getOrder() {\n    return this.aspectInstanceFactory.getOrder();\n}\n\n```\n\n [spring aop ֮ AnnotationAwareAspectJAutoProxyCreator ϣ](https://my.oschina.net/funcy/blog/4678817)Ѿϸ `method`  `advisor` ת̣ܴӴҵ `aspectInstanceFactory` ͣǾͲһԴˣֱͨԵķȡ `aspectInstanceFactory` ͣ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-e2784fc573474f56e8555ef60d57a07bcbf.png)\n\nӵԵĽ`aspectInstanceFactory` Ϊ `LazySingletonAspectInstanceFactoryDecorator`Ǹ `getOrder()` \n\n> LazySingletonAspectInstanceFactoryDecorator\n\n```\n@Override\npublic int getOrder() {\n    return this.maaif.getOrder();\n}\n\n```\n\nȻʹõԵķʽȡ `maaif` ͣ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-2a4b87b6c8c824c55c4a3d86d736796def0.png)\n\n`maaif` Ϊ `BeanFactoryAspectInstanceFactory`Ǽ\n\n> BeanFactoryAspectInstanceFactory\n\n```\npublic int getOrder() {\n    // this.name ָǱע @Aspect ע\n    Class<?> type = this.beanFactory.getType(this.name);\n    if (type != null) {\n        // ʵ Ordered ӿڣ͵ getOrder() ȡ\n        // PriorityOrdered  Ordered ӽӿڣҲ getOrder() Ҳȡ\n        if (Ordered.class.isAssignableFrom(type) && this.beanFactory.isSingleton(this.name)) {\n            return ((Ordered) this.beanFactory.getBean(this.name)).getOrder();\n        }\n        // 1\\. Ƿ @Order ע⣬У򷵻 @Order עֵָ\n        // 2\\. ѯǷ @Priority ע⣬У򷵻 @Priority עֵָ\n        // 3\\. ϶㣬 Ordered.LOWEST_PRECEDENCEֵΪ Integer.MAX_VALUE\n        return OrderUtils.getOrder(type, Ordered.LOWEST_PRECEDENCE);\n    }\n    return Ordered.LOWEST_PRECEDENCE;\n}\n\n```\n\nӴ`getOrder()` ߼£\n\n1.  ͨƻȡ࣬ҲǱע `@Aspect` ࣻ\n2.  ʵ `Ordered` ӿڣ͵ `getOrder()` ȡأֵһǣ`PriorityOrdered`  `Ordered` ӽӿڣҲ `getOrder()` Ҳȡ\n3.  ûлȡǷ `@Order` ע⣬У򷵻 `@Order` עֵָûУǷ `@Priority` ע⣬У򷵻 `@Priority` עֵָ\n4.  ûлȡֵͷĬֵ`Ordered.LOWEST_PRECEDENCE`\n\nˣ`@Aspect` ͨʵ `Ordered/PriorityOrdered` ӿִָȼҲͨ `@Order/@Priority` עִָȼ\n\n**Ҫرָ** `getOrder()` ⲿ ֻ ǰ `PriorityOrdered/@Priority`  `Order` ȼ `Ordered/@Order` ߡҲ˵ `AspectA` ע˵ `@Priority``AspectB` ע˵ `@Order``AspectA` ȼһ `AspectB` ߣȼע  `value()` ֵ\n\n### 4\\. Զȼ\n\nԼдʱָȼأ\n\n1. ʵ `advisor`ʵ `Ordered` ӿڣҲ `advisor` ϱע `@Order` ע⣺\n\n   ```\n   public class MyAdvisor extends AbstractBeanFactoryPointcutAdvisor implements Ordered {\n   \n       @Override\n       public int getOrder() {\n           return xxx;\n       }\n   \n   }\n   \n   @Order(xxx)\n   public class MyAdvisor extends AbstractBeanFactoryPointcutAdvisor {\n   \n       @Override\n       public int getOrder() {\n           return xxx;\n       }\n   \n   }\n   \n   ```\n\n2. ǵ (`@Aspect` ע)ظ `@Around/@Before/@After` \n\n   ```\n   @Aspect\n   public class MyAspectj {\n   \n       @Around(\"xxx\")\n       public Object around(ProceedingJoinPoint p){\n           ...\n       }\n   \n       @Before(\"xxx\")\n       public void before(JoinPoint p) {\n           ...\n       }\n   \n       @After(\"xxx\")\n       public void after(JoinPoint p) {\n           ...\n       }\n   \n       @AfterReturning(\"xxx\")\n       public void afterReturning(JoinPoint p) {\n           ...\n       }\n   \n       @AfterThrowing(\"xxx\")\n       public void afterThrowing(JoinPoint p) {\n           ...\n       }\n   }\n   \n   ```\n\n   ͬһĲ֪ͬͨspring Ѿúִ˳޴Ӹģִ˳Ϊ `Around, Before, After, AfterReturning, AfterThrowing`.\n\n3.  (`@Aspect` ע) ظ `@Around/@Before/@After` ȣ£\n\n   ```\n   @Aspect\n   public class MyAspectj {\n   \n       @Around(\"xxx\")\n       public Object around(ProceedingJoinPoint p){\n           ...\n       }\n   \n       @Before(\"xxx\")\n       public void before(JoinPoint p) {\n           ...\n       }\n   \n       @Around(\"xxx\")\n       public Object around(ProceedingJoinPoint p){\n           ...\n       }\n   \n       @Before(\"xxx\")\n       public void before(JoinPoint p) {\n           ...\n       }\n   \n   }\n   \n   ```\n\n    `AspectJPrecedenceComparator#comparePrecedenceWithinAspect` ʱзõĽǣȽߵ `declarationOrderInAspect` ֵ֮һΪ `after` ֪ͨ`declarationOrderInAspect` ȼߣ߶ `after` ֪ͨ`declarationOrderInAspect` Сȼߡ `declarationOrderInAspect` ȫ jdk ķƣȻȡĸĸ `declarationOrderInAspect` Сͬ jdK 汾֮䣬Ա֤õ˳һ¡\n\n4.  (`@Aspect` ע) ִ˳ͨ `@Order` ע⣬ʵ `Ordered` ӿָ\n\n   ```\n   @Order(xxx)\n   public class MyAspectj1 {\n       ...\n   }\n   \n   @Order(xxx)\n   public class MyAspectj2 {\n       ...\n   }\n   \n   ```\n\n⣬`getOrder()` صֵ `@Order(xxx)` ֵָԽСȼԽߡ\n\n* * *\n\n_ԭӣ[https://my.oschina.net/funcy/blog/4784828](https://my.oschina.net/funcy/blog/4784828) ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע_"
  },
  {
    "path": "docs/Spring全家桶/Spring源码分析/Spring重要机制探秘/Spring探秘之Spring事件机制.md",
    "content": "### 1\\. demo ׼\n\nʽ¼ǰ׼һ demo\n\n1.  ׼һ¼ `MyApplicationEvent`\n\n```\npublic class MyApplicationEvent extends ApplicationEvent {\n\n    private static final long serialVersionUID = -1L;\n\n    public MyApplicationEvent(Object source) {\n        super(source);\n    }\n}\n\n```\n\n1.  ׼һ `MyApplicationEventListener`¼ `MyApplicationEvent` м\n\n```\n@Component\npublic class MyApplicationEventListener \n        implements ApplicationListener<MyApplicationEvent> {\n\n    @Override\n    public void onApplicationEvent(MyApplicationEvent event) {\n        System.out.println(Thread.currentThread().getName() + \" | \" + event.getSource());\n    }\n}\n\n```\n\n׼һ࣬Ȳָݣ\n\n```\n@Configuration\n@ComponentScan\npublic class Demo08Config {\n\n}\n\n```\n\nࣺ\n\n```\n@ComponentScan\npublic class Demo08Main {\n\n    public static void main(String[] args) {\n        AnnotationConfigApplicationContext context \n            = new AnnotationConfigApplicationContext(Demo08Config.class);\n        // ¼\n        context.publishEvent(\n            new MyApplicationEvent(Thread.currentThread().getName() + \" | Զ¼ ...\"));\n    }\n\n}\n\n```\n\nϴ붨һ¼ `MyApplicationEvent`Ȼһ `MyApplicationEventListener` `MyApplicationEvent` ¼Ȼ `main()` У `context.publishEvent(...)` ¼\n\nУ£\n\n```\nmain | main | Զ¼ ...\n\n```\n\nԿ¼ɹˡ\n\nп֪¼ķ߳Ϊ `main`¼ļ߳Ҳ `main`\n\n### 2\\. ¼\n\nʽǰ¼ص¼ص 4 \n\n1.  ¼\n2.  ¼\n3.  㲥շ¼յ¼㲥\n4.  ¼\n\nһһ\n\n#### 2.1 ¼\n\nspring ṩ¼Ϊ `ApplicationEvent`һ̳࣬ jdk ṩ `EventObject` ࣬Զ¼ʱɼ̳ `ApplicationEvent`\n\n```\npublic abstract class ApplicationEvent extends EventObject {\n\n    private static final long serialVersionUID = 7099057708183571937L;\n\n    /**  timestamp */\n    private final long timestamp;\n\n    public ApplicationEvent(Object source) {\n        super(source);\n        this.timestamp = System.currentTimeMillis();\n    }\n\n    public final long getTimestamp() {\n        return this.timestamp;\n    }\n}\n\n```\n\n`ApplicationEvent`  `EventObject`Ǽ\n\n```\n/**\n * EventObject jdkṩλ java.util \n */\npublic class EventObject implements java.io.Serializable {\n\n    private static final long serialVersionUID = 5516075349620653480L;\n\n    // ¼\n    protected transient Object  source;\n\n    public EventObject(Object source) {\n        if (source == null)\n            throw new IllegalArgumentException(\"null source\");\n\n        this.source = source;\n    }\n\n    /**\n     * ȡ¼\n     */\n    public Object getSource() {\n        return source;\n    }\n\n    public String toString() {\n        return getClass().getName() + \"[source=\" + source + \"]\";\n    }\n}\n\n```\n\n `ApplicationEvent`  `EventObject` `ApplicationEvent` ṩԣ\n\n*   `source` `EventObject` ԣ¼ݣ\n*   `timestamp`: ʱ¼¼ʱ䡣\n\nʵϣspring ܷ `ApplicationEvent` ͵¼⣬Է `Object` ͵¼鿴ͿԷ һ㡣\n\n#### 2.2 \n\nspring ṩķΪ `ApplicationEventPublisher`£\n\n```\npublic interface ApplicationEventPublisher {\n\n    /**\n     * ApplicationEvent͵¼\n     */\n    default void publishEvent(ApplicationEvent event) {\n        publishEvent((Object) event);\n    }\n\n    /**\n     * Object͵¼\n     */\n    void publishEvent(Object event);\n\n}\n\n```\n\nǸӿڣڶ\n\n*   `void publishEvent(ApplicationEvent event)`:  `ApplicationEvent` ͵¼\n*   `void publishEvent(Object event)`:  `Object` ͵¼\n\n`AbstractApplicationContext`  `ApplicationEventPublisher` ࣬ǿֱӵ `AbstractApplicationContext#publishEvent` ¼\n\n `AbstractApplicationContext` ʵ֣Ǻʱپ\n\n#### 2.3 㲥\n\n㲥ǽշ¼Ȼ¼㲥£\n\n```\npublic interface ApplicationEventMulticaster {\n\n    /**\n     * Ӽ\n     */\n    void addApplicationListener(ApplicationListener<?> listener);\n\n    /**\n     * Ӽ beanName\n     */\n    void addApplicationListenerBean(String listenerBeanName);\n\n    /**\n     * Ƴ\n     */\n    void removeApplicationListener(ApplicationListener<?> listener);\n\n    /**\n     * Ƴ beanName\n     */\n    void removeApplicationListenerBean(String listenerBeanName);\n\n    /**\n     * Ƴеļ\n     */\n    void removeAllListeners();\n\n    /**\n     * 㲥¼\n     */\n    void multicastEvent(ApplicationEvent event);\n\n    /**\n     * 㲥¼\n     */\n    void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType);\n\n}\n\n```\n\nӴ㲥Ҫ\n\n1.  άԶԼɾ\n2.  㲥¼\n\nspring ĬϵĹ㲥Ϊ `SimpleApplicationEventMulticaster`Ǻٷ\n\n#### 2.4 \n\n¼ȻһЩ£\n\n```\n@FunctionalInterface\npublic interface ApplicationListener<E extends ApplicationEvent> extends EventListener {\n\n    /**\n     * ¼\n     */\n    void onApplicationEvent(E event);\n\n}\n\n```\n\nڴ¼ʱҪʵ `ApplicationListener`Ȼ `onApplicationEvent(...)` бдǵ¼߼\n\n### 3\\. عˣ`㲥ĳʼ``ע`\n\nع`¼㲥ĳʼ``ע`̡\n\nڴ spring  `AbstractApplicationContext#refresh` У`¼㲥ĳʼ``ע`ֱڵ 8  10 \n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-c4d4ac83c23e41a706b7ba545fd8d0f7681.png)\n\nش£\n\n```\npublic abstract class AbstractApplicationContext extends DefaultResourceLoader\n        implements ConfigurableApplicationContext {\n\n    /**\n     * ʼ㲥\n     * ¼㲥ʹõģʹĬϵ¼㲥\n     */\n    protected void initApplicationEventMulticaster() {\n        ConfigurableListableBeanFactory beanFactory = getBeanFactory();\n        // ûԶ¼㲥ʹû\n        if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {\n            this.applicationEventMulticaster =\n                    beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, \n                            ApplicationEventMulticaster.class);\n        }\n        else {\n            // ûûù㲥ʹĬϵ¼㲥\n            this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);\n            beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, \n                    this.applicationEventMulticaster);\n        }\n    }\n\n    /**\n     * ע\n     */\n    protected void registerListeners() {\n        // 1\\. Ƚֶӵļŵ㲥\n        // AbstractApplicationContext#addApplicationListener\n        for (ApplicationListener<?> listener : getApplicationListeners()) {\n            getApplicationEventMulticaster().addApplicationListener(listener);\n        }\n\n        // 2\\. beanFactoryлȡȡƣӵ㲥\n        String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);\n        for (String listenerBeanName : listenerBeanNames) {\n            getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);\n        }\n\n        // 3\\. Ӧ¼\n        Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;\n        //  earlyApplicationEvents Ϊ nullٷ¼\n        this.earlyApplicationEvents = null;\n        if (earlyEventsToProcess != null) {\n            for (ApplicationEvent earlyEvent : earlyEventsToProcess) {\n                getApplicationEventMulticaster().multicastEvent(earlyEvent);\n            }\n        }\n    }\n\n    ...\n\n}\n\n```\n\n㲥ĳʼ߼ܼ򵥣¼㲥ʹõģʹĬϵ¼㲥Ĭϵ¼㲥 `SimpleApplicationEventMulticaster`\n\nע£\n\n1.  Ƚֶӵļŵ㲥УǿԵ `AbstractApplicationContext#addApplicationListener` Ӽ\n2.   `beanFactory` лȡȡƣӵ㲥Уע⣺ʱ `beanFactory` е bean ûгʼֻ `beanName`;\n3.  ¼ͷ¼\n\nع̺ǴҪ㣺\n\n1.  㲥ʼ\n2.  עᵽ㲥е\n\n### 4\\. ¼\n\nǰڵ̵棬Ƕ¼Ѿ˸ŵĸҲ¼㲥ĳʼע̣ Ǿʽ¼ķˡ\n\n demo Уǵ `context.publishEvent(...)` ¼Ǹ\n\n```\npublic abstract class AbstractApplicationContext extends DefaultResourceLoader\n        implements ConfigurableApplicationContext {\n\n    @Override\n    public void publishEvent(ApplicationEvent event) {\n        publishEvent(event, null);\n    }\n\n    protected void publishEvent(Object event, @Nullable ResolvableType eventType) {\n        Assert.notNull(event, \"Event must not be null\");\n        // ¼\n        ApplicationEvent applicationEvent;\n        if (event instanceof ApplicationEvent) {\n            applicationEvent = (ApplicationEvent) event;\n        }\n        else {\n            applicationEvent = new PayloadApplicationEvent<>(this, event);\n            if (eventType == null) {\n                eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();\n            }\n        }\n\n        // earlyApplicationEvents Ϊ nullapplicationEventӵ earlyApplicationEvents\n        // ע󣬻 \bearlyApplicationEvents Ϊ null\n        if (this.earlyApplicationEvents != null) {\n            this.earlyApplicationEvents.add(applicationEvent);\n        }\n        else {\n            // Ƿ¼Ĳ\n            getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);\n        }\n\n        // ڸһ\n        if (this.parent != null) {\n            if (this.parent instanceof AbstractApplicationContext) {\n                ((AbstractApplicationContext) this.parent).publishEvent(event, eventType);\n            }\n            else {\n                this.parent.publishEvent(event);\n            }\n        }\n    }\n\n}\n\n```\n\nܼ򵥣ؼΪ\n\n```\n// Ƿ¼Ĳ\ngetApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);\n\n```\n\nдȻȡ¼㲥Ȼ㲥¼\n\nǰᵽspring ṩĬϵ¼㲥 `SimpleApplicationEventMulticaster`ǽ `SimpleApplicationEventMulticaster#multicastEvent(ApplicationEvent, ResolvableType)` ¼Ĺ㲥̣\n\n```\n/**\n * 㲥¼\n */\npublic void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {\n    ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));\n    // 1\\. ȡ TaskExecutor\n    Executor executor = getTaskExecutor();\n    // 2\\. getApplicationListeners(...) ȡܼ¼ļ\n    for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {\n        // 3\\. һ invokeListener(...) \n        if (executor != null) {\n            executor.execute(() -> invokeListener(listener, event));\n        }\n        else {\n            invokeListener(listener, event);\n        }\n    }\n}\n\n```\n\nϷ 3 \n\n1.  ȡִ`getTaskExecutor()`\n2.  ȡ¼ļ`getApplicationListeners(...)`\n3.  üļ`invokeListener(...)`\n\nϲ¼㲥̣ú÷¡\n\n#### 1\\. ȡִ`getTaskExecutor()`\n\n`taskExecutor`  `SimpleApplicationEventMulticaster` һԣ`getTaskExecutor()`  `taskExecutor`  `getter` \n\n```\n    private Executor taskExecutor;\n\n    public void setTaskExecutor(@Nullable Executor taskExecutor) {\n        this.taskExecutor = taskExecutor;\n    }\n\n    @Nullable\n    protected Executor getTaskExecutor() {\n        return this.taskExecutor;\n    }\n\n```\n\nspring Ϊṩ͵ `taskExecutor`\n\n1. `SyncTaskExecutor`ͬ `taskExecutor` `execute(...)` Ϊ\n\n   ```\n    @Override\n    public void execute(Runnable task) {\n        Assert.notNull(task, \"Runnable must not be null\");\n        task.run();\n    }\n   \n   ```\n\n   ԿȷʵǸֱͬӵ `Runnable#run` ûµ߳\n\n2. `SimpleAsyncTaskExecutor`첽 `taskExecutor` `execute(...)` Ϊ\n\n   ```\n   @Override\n    public void execute(Runnable task, long startTimeout) {\n        Assert.notNull(task, \"Runnable must not be null\");\n        Runnable taskToUse = (this.taskDecorator != null \n                ? this.taskDecorator.decorate(task) : task);\n        // doExecute(...) ɻķ\n        if (isThrottleActive() && startTimeout > TIMEOUT_IMMEDIATE) {\n            this.concurrencyThrottle.beforeAccess();\n            doExecute(new ConcurrencyThrottlingRunnable(taskToUse));\n        }\n        else {\n            doExecute(taskToUse);\n        }\n    }\n   \n    /**\n     * ɻķ\n     * Ӵᴴ´ִ񣬵ûʹ̳߳\n     */\n    protected void doExecute(Runnable task) {\n        // Կﴴ̣߳߳\n        Thread thread = (this.threadFactory != null \n                ? this.threadFactory.newThread(task) : createThread(task));\n        thread.start();\n    }\n   \n   ```\n\n   Կ `SimpleAsyncTaskExecutor` Уᴴµִ߳\n\nܻܲȡִأͨԷִ֣ĻȡΪ `null`\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-2e6ba6389e603a6373287dac26a89a24109.png)\n\nִ `invokeListener(...)` ʱֱӵõģ\n\n```\n...\n        else {\n            invokeListener(listener, event);\n        }\n...\n\n```\n\n#### 2\\. ȡ¼ļ`getApplicationListeners(...)`\n\n׼˵ȡܼ¼ļΪ `AbstractApplicationEventMulticaster#getApplicationListeners(ApplicationEvent, ResolvableType)`\n\n```\n/**\n * 裺\n * 1\\. ӻлȡܻȡֱӷ\n * 2\\. ܴӻлȡ retrieveApplicationListeners(...) ȡ\n */\nprotected Collection<ApplicationListener<?>> getApplicationListeners(\n        ApplicationEvent event, ResolvableType eventType) {\n    Object source = event.getSource();\n    Class<?> sourceType = (source != null ? source.getClass() : null);\n    ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);\n    // 1\\. ӻлȡ\n    ListenerRetriever retriever = this.retrieverCache.get(cacheKey);\n    if (retriever != null) {\n        return retriever.getApplicationListeners();\n    }\n    if (this.beanClassLoader == null ||\n            (ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&\n            (sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {\n        synchronized (this.retrievalMutex) {\n            retriever = this.retrieverCache.get(cacheKey);\n            if (retriever != null) {\n                return retriever.getApplicationListeners();\n            }\n            retriever = new ListenerRetriever(true);\n            // 2\\. ȡǹؼ\n            Collection<ApplicationListener<?>> listeners =\n                    retrieveApplicationListeners(eventType, sourceType, retriever);\n            this.retrieverCache.put(cacheKey, retriever);\n            return listeners;\n        }\n    }\n    else {\n        return retrieveApplicationListeners(eventType, sourceType, null);\n    }\n}\n\n```\n\nųؼ\n\n1.  ӻлȡܻȡֱӷ\n2.  ܴӻлȡ `retrieveApplicationListeners(...)` ȡ\n\nǼ `retrieveApplicationListeners(...)`\n\n```\n/**\n * ȡ\n *\n */\nprivate Collection<ApplicationListener<?>> retrieveApplicationListeners(\n        ResolvableType eventType, @Nullable Class<?> sourceType, \n        @Nullable ListenerRetriever retriever) {\n    List<ApplicationListener<?>> allListeners = new ArrayList<>();\n    Set<ApplicationListener<?>> listeners;\n    Set<String> listenerBeans;\n    synchronized (this.retrievalMutex) {\n        listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners);\n        listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);\n    }\n    //  listeners лȡܴǰ¼ lister\n    for (ApplicationListener<?> listener : listeners) {\n        // жϵǰlistenerǷִ֧event\n        if (supportsEvent(listener, eventType, sourceType)) {\n            if (retriever != null) {\n                retriever.applicationListeners.add(listener);\n            }\n            allListeners.add(listener);\n        }\n    }\n    //  listenerBeans лȡܴǰ¼ lister\n    if (!listenerBeans.isEmpty()) {\n        ConfigurableBeanFactory beanFactory = getBeanFactory();\n        for (String listenerBeanName : listenerBeans) {\n            try {\n                // жϵǰ listenerBeanName Ƿܼ¼\n                if (supportsEvent(beanFactory, listenerBeanName, eventType)) {\n                    // лȡӦbean¼ǰʼ\n                    ApplicationListener<?> listener = beanFactory.getBean(\n                            listenerBeanName, ApplicationListener.class);\n                    if (!allListeners.contains(listener) \n                            && supportsEvent(listener, eventType, sourceType)) {\n                        if (retriever != null) {\n                            // עⵥǵ\n                            if (beanFactory.isSingleton(listenerBeanName)) {\n                                retriever.applicationListeners.add(listener);\n                            }\n                            else {\n                                retriever.applicationListenerBeans.add(listenerBeanName);\n                            }\n                        }\n                        allListeners.add(listener);\n                    }\n                }\n                else {\n                    Object listener = beanFactory.getSingleton(listenerBeanName);\n                    if (retriever != null) {\n                        retriever.applicationListeners.remove(listener);\n                    }\n                    allListeners.remove(listener);\n                }\n            }\n            catch (NoSuchBeanDefinitionException ex) {\n            }\n        }\n    }\n    // \n    AnnotationAwareOrderComparator.sort(allListeners);\n    if (retriever != null && retriever.applicationListenerBeans.isEmpty()) {\n        retriever.applicationListeners.clear();\n        retriever.applicationListeners.addAll(allListeners);\n    }\n    return allListeners;\n}\n...\n\n}\n\n```\n\n `AbstractApplicationContext#registerListeners` Уעʱ ע\n\n1.  עֶӵļһʵ\n2.  лȡ beanNameע᣻\n\n`retrieveApplicationListeners()` гֵ `listeners`  `listenerBeans` Ǵ͵ļġ\n\nϷȻе㳤߼ǳ\n\n1.   `listeners`һ `supportsEvent(listener, eventType, sourceType)` жϵǰ listener ܷ¼\n2.   `listenerBeans`һ `supportsEvent(beanFactory, listenerBeanName, eventType)` жϵǰ listener ܷ¼\n\nĴȽϸӣͲһһзˣĴ˼·\n\n1. Ӵ `listener`  `listenerBeanName` ȡ `listener`  Class`listener` ֻ `listener.getClass()` ɣ`listenerBeanName` ͨ `beanFactory.getType(listenerBeanName)` ȡ\n\n2. ȡ `listener` ¼ͣһģ\n\n   ```\n   public class MyApplicationEventListener \n           implements ApplicationListener<MyApplicationEvent> {\n   \n       @Override\n       public void onApplicationEvent(MyApplicationEvent event) {\n           ...\n       }\n   }\n   \n   ```\n\n   ǿȡ `MyApplicationEvent`\n\n   ```\n   // Щ඼jdkṩ\n   ParameterizedType parameterizedType = (ParameterizedType) \n           MyApplicationEventListener.class.getGenericInterfaces()[0];\n       Class<?> type = (Class)parameterizedType.getActualTypeArguments()[0];\n   \n   ```\n\n   Ȼspring ڴⲿ߼ʱرӣϾ `MyApplicationEventListener` ͬʱʵ˶ӿڣ `MyApplicationEventListener` ĸ -  -  -... ӿڲ `ApplicationListener`ЩҪǵ\n\n3. ȡܼ¼ˣжϵǰжϼǷܼ¼ˣspring ƥķΪ `ResolvableType#isAssignableFrom(ResolvableType)`עʵ `Class.isAssignableFrom` ĹܣͬʱԴϵķҲʵ `Class.isAssignableFrom` Ĺܡ\n\n#### 3\\. ü`invokeListener(...)`\n\nڵüˣ£\n\n```\n/**\n * ִм\n */\nprotected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {\n    ErrorHandler errorHandler = getErrorHandler();\n    // յõ doInvokeListener(...)\n    if (errorHandler != null) {\n        try {\n            doInvokeListener(listener, event);\n        }\n        catch (Throwable err) {\n            errorHandler.handleError(err);\n        }\n    }\n    else {\n        doInvokeListener(listener, event);\n    }\n}\n\n/**\n * ִвյõ ApplicationListener#onApplicationEvent \n */\nprivate void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {\n    try {\n        listener.onApplicationEvent(event);\n    }\n    catch (ClassCastException ex) {\n        String msg = ex.getMessage();\n        if (msg == null || matchesClassCastMessage(msg, event.getClass())) {\n            // ʡ־ӡ\n        }\n        else {\n            throw ex;\n        }\n    }\n}\n\n```\n\nһܼ򵥣Ǳһȡļһ `onApplicationEvent(...)` \n\nҪעǣǰ`ȡִ`ķУᵽ `getTaskExecutor()` ĽΪ `null` `invokeListener(...)` ֱִеģûһִ߳УҪע⡣\n\n### 5\\. ¼Ӧ\n\n#### 5.1 ¼\n\n spring Уɻᷢ `ContextRefreshed` ¼\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-f60ea2710c59e2ce9e1401f1b54cbd9d700.png)\n\nҪ¼Ҳʮּ򵥣Ӧ `Listener` £\n\n```\n@Component\npublic class ContextRefreshedListener \n        implements ApplicationListener<ContextRefreshedEvent> {\n\n    @Override\n    public void onApplicationEvent(ContextRefreshedEvent event) {\n        System.out.println(\"\");\n    }\n\n}\n\n```\n\n#### 5.2 ¼\n\nһʼΪ¼\n\n```\nAnnotationConfigApplicationContext context \n        = new AnnotationConfigApplicationContext();\ncontext.register(Demo08Config.class);\n// ¼ǰ\ncontext.publishEvent(new MyApplicationEvent(\n        Thread.currentThread().getName() + \" | Զ¼ ...\"));\ncontext.refresh();\n\n```\n\nк󣬻ᱨ ![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-4fc18c95f69d8c3d65be2a0f603d42e19ce.png)\n\nӴϢ˵㲥δʼ\n\nô㲥ʼأǰķ֪ `refresh()` ̵ĵ 8 ¼ķڵ 10 һ¼ֻڵ 9 ˣ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-1fecd516c6d58d628937b20ebd806065891.png)\n\nԿ¼Ϊ `onRefresh()` չ׼ģ\n\n¼ķҲͼˣ\n\n```\nApplicationContext context = \n        new AnnotationConfigApplicationContext(Demo08Config.class) {\n    @Override\n    public void onRefresh() {\n        // ﷢¼\n        publishEvent(new MyApplicationEvent(\n                Thread.currentThread().getName() + \" | Զ¼ ...\"));\n    }\n};\n\n```\n\n#### 5.3 첽㲥¼\n\nǰԴ¼ִͬһ߳неģ demo нҲܿ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-afed97c136df717ba3d9397ff142ae0675f.png)\n\nǰѾ㲥¼ʱȻȡ `executor` `executor` ڣ `executor` ִУֱִУ㲥е `executor` Ϊ `null`˺׽ۣҪʵ첽ִУҪڹ㲥 `executor` ԡ\n\nǰͬҲspring ڳʼ㲥ʱжǷڹ㲥`beanName` Ϊ `applicationEventMulticaster`ʹ `SimpleApplicationEventMulticaster` ĬϵĹ㲥\n\nһֻҪԶ `beanName` Ϊ `applicationEventMulticaster`  bean У\n\n```\n@Configuration\n@ComponentScan\npublic class Demo08Config {\n\n    /**\n     * Զ㲥\n     * ע⣺ƱΪ applicationEventMulticaster\n     */\n    @Bean\n    public ApplicationEventMulticaster applicationEventMulticaster() {\n        SimpleApplicationEventMulticaster applicationEventMulticaster\n                = new SimpleApplicationEventMulticaster();\n        // SimpleAsyncTaskExecutor springṩ첽ִ\n        applicationEventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor());\n        return applicationEventMulticaster;\n    }\n}\n\n```\n\n`SimpleApplicationEventMulticaster` ʹ spring ṩ첽ִ`SimpleAsyncTaskExecutor`У£\n\n```\nSimpleAsyncTaskExecutor-2 | main | Զ¼ ...\n\n```\n\nԿ̲߳߳ͬһˡ\n\n鵽⻹꣬ǰѾ `SimpleAsyncTaskExecutor` ִйִ̣ʱ᲻ϴ̣߳\n\n```\n   protected void doExecute(Runnable task) {\n        // Կﴴ̣߳߳\n        Thread thread = (this.threadFactory != null \n                ? this.threadFactory.newThread(task) : createThread(task));\n        thread.start();\n    }\n\n```\n\n 100 ¼\n\n```\nfor(int i = 0; i < 100; i++) {\n    context.publishEvent(new MyApplicationEvent(\n            Thread.currentThread().getName() + \" | Զ¼ ...\"));\n}\n\n```\n\nн\n\n```\nSimpleAsyncTaskExecutor-2 | main | Զ¼ ...\nSimpleAsyncTaskExecutor-3 | main | Զ¼ ...\nSimpleAsyncTaskExecutor-4 | main | Զ¼ ...\n...\nSimpleAsyncTaskExecutor-99 | main | Զ¼ ...\nSimpleAsyncTaskExecutor-100 | main | Զ¼ ...\nSimpleAsyncTaskExecutor-101 | main | Զ¼ ...\n\n```\n\nԿÿһ¼ᴴһִ߳У̱߳˱ԴƵشһ˷ѣ߳أ̳߳ء\n\nǿ `SimpleApplicationEventMulticaster#setTaskExecutor` Ĳ `java.util.concurrent.Executor`ͱüˣֱʹ jdk ṩ̳߳أ\n\n```\n@Bean\npublic ApplicationEventMulticaster applicationEventMulticaster() {\n    SimpleApplicationEventMulticaster applicationEventMulticaster\n            = new SimpleApplicationEventMulticaster();\n    // ʹjdkṩ̳߳\n    applicationEventMulticaster.setTaskExecutor(Executors.newFixedThreadPool(4));\n    return applicationEventMulticaster;\n}\n\n```\n\nʹõ jdK ṩ `newFixedThreadPool` 4 ̣߳У£\n\n```\npool-1-thread-2 | main | Զ¼ ...\npool-1-thread-3 | main | Զ¼ ...\npool-1-thread-4 | main | Զ¼ ...\npool-1-thread-4 | main | Զ¼ ...\npool-1-thread-1 | main | Զ¼ ...\npool-1-thread-3 | main | Զ¼ ...\npool-1-thread-3 | main | Զ¼ ...\npool-1-thread-2 | main | Զ¼ ...\npool-1-thread-1 | main | Զ¼ ...\npool-1-thread-3 | main | Զ¼ ...\n...\n\n```\n\nԿʼֻ 4 ߳ڹ㲥¼\n\nЩ淶Уǽֱֹʹ jdk ṩ̵߳ĳأǸᳫԶ̳߳أ㣬ڱĲ̳߳ؼģ˾ͼ򵥵ʹ jdk ṩ߳ʾ¡\n\n#### 5.4 Զ¼\n\nʵϣǰṩʾ demo Զ¼Ͳظˡ\n\n### 6\\. ܽ\n\nķ spring ¼ƣ¼Ĵ¼㲥ҴԴĳʼ¼ķ̡\n\n* * *\n\n_ԭӣ[https://my.oschina.net/funcy/blog/4713339](https://my.oschina.net/funcy/blog/4713339) ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע_"
  },
  {
    "path": "docs/Spring全家桶/Spring源码分析/Spring重要机制探秘/Spring探秘之循环依赖的解决（一）：理论基石.md",
    "content": "### 1\\. ʲôѭ\n\nspring עʱܻ໥ע\n\n```\n@Service\npublic class Service1 {\n    @Autowired\n    private Service2 service2;\n\n}\n\n@Service\npublic class Service2 {\n    @Autowired\n    private Service1 service1;\n\n}\n\n```\n\nϴ룬 `Service1` ͨ `@Autowird` ע `Service2` `Service2` ͨ `@Autowird` ע `Service1`໥עͽѭ\n\n### 2\\. ѭʲô\n\nʵϣ `ABBҲA`java ȫֵ֧ģ\n\n```\n/**\n * ׼service1\n */\npublic class Service1 {\n    private Service2 service2;\n\n    public void setService2(Service2 service2) {\n        this.service2 = service2;\n    }\n\n    public Service2 getService2() {\n        return this.service2;\n    }\n}\n\n/**\n * ׼service2\n */\npublic class Service2 {\n    private Service1 service1;\n\n    public void setService1(Service1 service1) {\n        this.service1 = service1;\n    }\n\n    public Service1 getService1() {\n        return this.service1;\n    }\n}\n\n/**\n * е\n */\npublic class Main {\n    public void main(String[] args) {\n        // ׼\n        Service1 service1 = new Service1();\n        Service2 service2 = new Service2();\n        // ໥\n        service1.setService2(service2);\n        service2.setService1(service1);\n    }\n}\n\n```\n\nô spring У໥עԷʵʲôأ `spring bean` Ĵ̣**ע⣺ǽ `bean`  `scope` Ϊ `singleton` Ҳ `scope` Ϊ``**\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-ca36e17077b1b191834645b3c8e588ff6c4.png)\n\nмҪ˵£\n\n1.  ʵʹ jdk ṩķƴ java Ե 1 ᵽ `Service1` ΪɼΪ `Service1 service = new Service1()`\n2.  ע󣺻Ե 1 ᵽ `Service1` Ϊ`Service1` ͨ `@Autowired` Զע `Service2`һǸ `Service2` ֵḶ́ɼΪ `service1.setService2(service2)`\n3.  `singletonObjects`һ java ͱһ spring beanȻ󱣴浽 `singletonObjects` ˣǸ `map``key`  bean ƣ`value`  beanֻ `spring bean`ֻ java ʵ\n\nʵϣ`java`  `spring bean`ֻע룬гʼִ `beanPorcessor` ȣ**ڱǷ `spring bean` ѭģصעѭصĲ衣**\n\n#### 2.1 ѭ\n\n˽ spring bean Ĳ֮󣬽Ǿѭ⣬ʽǰȷ\n\n*   `java`ʵϣjava һж󶼿Գ֮Ϊ `java` Ϊ˵㣬ᵽ `java`ָʵɡδ spring bean ڶ\n*   `spring bean`һ java 󣬲ҽ spring bean ڶ\n\nspring bean Ĵ£\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-b55a211447b5fabeaa0c3ef0bfee0920c82.png)\n\nͼ˵£\n\n1.   `service1` 󴴽ɺ`spring`  `service1` Ҫע `service2`Ȼȥ `singletonObjects` в `service2`ʱҲ `service2`ȻͿʼ `service2` Ĵ̣\n2.   `service2` 󴴽ɺ`spring`  `service2` Ҫע `service1`Ȼȥ `singletonObjects` в `service1`ʱҲ `service1`Ϊһ `service1` ûдɹ ȻͿʼ `service1` Ĵ̣\n3.  ص `1`ٴοʼ `service1` Ĵע̡\n\nǾϲط֣ѭˣ\n\n#### 2.2  `earlySingletonObjects` ѭ\n\nǷ£ѭֵԭڣ `service2` ȡ `service1` ʱ `singletonObjects` дʱ `service1`˻ `service1` Ĵ̣´ `service1`ˣи󵨵뷨 `service1` ʵͰ `service1` ʱͷδע `service1`\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-c0eaecbe82b144a6fcd9fd048f2cca53497.png)\n\nͼУ `earlySingletonObjects`ҲǸ mapͬ `singletonObjects` һ`key`  bean ƣ`value` һδעĶ\n\nͼ˵£\n\n1.   `service1` 󴴽ɺȽ `service1`  `earlySingletonObjects`Ȼע룻\n2.   `service1` עʱ`spring`  `service1` Ҫע `service2`Ȼȥ `earlySingletonObjects`  `service2`δҵȥ `singletonObjects` в `service2`δҵǾͿʼ `service2` Ĵ̣\n3.   `service2` 󴴽ɺȽ `service2`  `earlySingletonObjects`Ȼע룻\n4.   `service2` עʱ`spring`  `service2` Ҫע `service1`Ȼȥ `earlySingletonObjects`  `service1`ҵˣͽ `service1` ע뵽 `service2` Уʱ `service2` һ `spring bean` ˣ䱣浽 `singletonObjects` У\n5.   4 ǵõ `service2`Ȼע뵽 `service1` Уʱ `service1` Ҳһ `spring bean`䱣浽 `singletonObjects` С\n\nϲ裬Ƿ֣ѭõ˽\n\n#### 2.2 aop µѭ\n\nķǷֻҪһ `earlySingletonObjects` ѭܵõǣѭĵõ˽spring  ioc ⣬һشܣaop aop ³ѭ\n\n##### 1\\. aop Ĵ\n\nʽ aop µѭǰȷ\n\n*   `ԭʼ`ڴָδй aop Ķ󣬿 java Ҳδ aop  spring bean\n*   ``й aop Ķ󣬿 java й aop õĶ (й aopδע룬Ҳδгʼ)Ҳǽй aop  `spring bean`.\n\n aop δģ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-e92991c1c173bbd5579be3001a977555be7.png)\n\n `2.1` е̣aop  \"ɴ\" Ĳձ浽 `singletonObjects` еĶҲǴ\n\nԭʼ֮ʲôϵأôʾ££\n\n```\npublic class ProxyObj extends Obj {\n\n    // ԭʼ\n    private Obj obj;\n\n    ...\n}\n\n```\n\nʵϣ֮Ĺϵûô򵥣Ϊ˵⣬߹ϵ˼򻯣СֻҪף**ԭʼ**ɡ\n\nԭʼαɴģԲο [spring aop ֮ AnnotationAwareAspectJAutoProxyCreator £](https://my.oschina.net/funcy/blog/4687961)\n\nϴ̣ java ģ£\n\n```\n/**\n * ׼һ\n */\npublic class Obj1 {\n\n}\n\n/**\n * ׼һ࣬ڲһ Obj1\n */\npublic class Obj2 {\n\n    private Obj1 obj1;\n\n    // ʡ\n    ...\n\n}\n\n/**\n * ׼Obj2Ĵ࣬ڲobj2Ķ\n */\npublic class ProxyObj2 extends Obj2 {\n\n    private Obj2 obj2;\n\n    public ProxyObj2(Obj2 obj2) {\n        this.obj2 = obj2;\n    }\n\n    // ʡ\n    ...\n\n}\n\n```\n\nţģ  --> ע --> ɴ --> 浽С  ˣ\n\n```\npublic static main(String[] args) {\n     // ׼һﱣڵĶ\n     // 1\\. ԪԭʼöѾע \n     // 2\\. ԪǴöеԭжѾע \n     Collection<?> collection = new ArrayList();\n\n     // ʼ Obj2 Ĵ\n     // 1\\.  Obj2 \n     Obj2 obj2 = new Obj2();\n\n     // 2\\.  Obj2 ע obj1ʱûobj1Ҫobj1ٽע뵽Obj2\n     Obj1 obj1 = new Obj1();\n     obj2.setObj1(obj1);\n\n     // 3\\. Obj2Ĵ󣬴г Obj2ԭʼ\n     ProxyObj2 proxyObj2 = new ProxyObj2(obj2);\n\n     // 4\\. proxyObj2Ѿڣ˽ӵʱ\n     collection.add(proxyObj2); \n\n}\n\n```\n\nУ\n\n*    `new Obj2()` ģĴ\n*    `obj2.setObj1(xxx)` ģע\n*    `new ProxyObj2(xxx)` ģ\n*    `collection.add(xxx)` ģӵеĹ\n\nģ£\n\n1.   `obj2` \n2.   `Obj2` ע `obj1`ʱû `obj1`Ҫ `obj1`ٽע뵽 `Obj2` \n3.   `Obj2` Ĵ `proxyObj2``proxyObj2` г `Obj2` ԭʼ\n4.  `proxyObj2` Ѿڣ˽ӵʱ\n\nϸĲ裬ͻ֣ᷢĵ 2  3 ȫ˳Ҳû⣬ģ£\n\n```\npublic static main(String[] args) {\n     // ׼һﱣڵĶ\n     // 1\\. ԪԭʼöѾע \n     // 2\\. ԪǴöеԭжѾע \n     Collection<?> collection = new ArrayList();\n\n     // ʼ Obj2 Ĵ\n     // 1\\.  Obj2 \n     Obj2 obj2 = new Obj2();\n\n     // 2\\. Obj2Ĵ󣬴г Obj2ԭʼ\n     ProxyObj2 proxyObj2 = new ProxyObj2(obj2);\n\n     // 3\\.  obj2 ע obj1ʱûobj1Ҫobj1ٽע뵽Obj2\n     Obj1 obj1 = new Obj1();\n     // ע뵽ԭʼ\n     obj2.setObj1(obj1);\n\n     // 4\\. proxyObj2Ѿڣ˽ӵʱ\n     collection.add(proxyObj2); \n\n}\n\n```\n\n£\n\n1.   obj2 \n2.   Obj2 Ĵ󣬴г Obj2 ԭʼ\n3.   Obj2 ע obj1ʱû obj1Ҫ obj1ٽע뵽 Obj2\n4.  proxyObj2 Ѿڣ˽ӵʱ\n\nӴϿ`proxyObj2()` г `ob2(ԭʼ)`ɴ󣬼ԭʼע룬ȻӰմеԭʼҲע룬ͼʾ£\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-8ff5579425f73dd5c2d321a86d0303390ac.png)\n\nٴ java  spring bean Ĳкö࣬ǽעѭصĲ裬˽ spring bean ϸĳʼ̣ɲ鿴 [spring ̸֮](https://my.oschina.net/funcy/blog/4597493)\n\n̽ڿ֣\n\n*    --> ע --> ɴ --> 󱣴浽\n*    (ԭʼ)--> ɴ (ǰ aop)--> ԭʼע --> 󱣴浽\n\nֶܴﵽĿģ**浽еǴҴӦԭʼע**μ̣Ǻ aop ѭĺģ˵ˣ**aop µѭ֮ܽΪǰ aop **\n\n##### 2\\. Ϊʲô `earlySingletonObjects` ޷ѭ\n\nǰҪ˵˴Ĵ̣ aop £ʹ `earlySingletonObjects` ѭʲô⣺\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-d3d00d76ab0c72339faccb7ccd853723d6c.png)\n\nͼ̣\n\n1.   `service1` 󴴽ɺȽ `service1`  `earlySingletonObjects`Ȼע룻\n2.   `service1` עʱ`spring`  `service1` Ҫע `service2`Ȼȥ `earlySingletonObjects`  `service2`δҵȥ `singletonObjects` в `service2`δҵǾͿʼ `service2` Ĵ̣\n3.   `service2` 󴴽ɺȽ `service2`  `earlySingletonObjects`Ȼע룻\n4.   `service2` עʱ`spring`  `service2` Ҫע `service1`Ȼȥ `earlySingletonObjects`  `service1`ҵˣͽ `service1` ע뵽 `service2` УȻٽ aopʱ `service2` һ󣬽䱣浽 `singletonObjects` У\n5.   4 ǵõ `service2` ĴȻע뵽 `service1` Уٶ `service1`  aopʱ `service1` Ҳһ `spring bean`䱣浽 `singletonObjects` С\n\nʲôأϸ 4 ͻ֣ᷢ**ע뵽 `service2`  `service1` Ǵ**ݹȫ֣յõ `service1`  `service2` Ǵע뵽 `service2`  `service1` ӦҲǴŶԡˣ aop £ѭֳˣ\n\n#### 2.3 spring Ľ\n\nǰᵽ aop £ `earlySingletonObjects` ܽѭ⣬ spring ôأspring ٴһ `map` ⣬Ҳǳ˵ **spring ** `map` ˵£\n\n*   һ `singletonObjects`Ϊ `ConcurrentHashMap<String, Object>`λ `DefaultSingletonBeanRegistry` У`key` Ϊ `beanName``value`  `spring bean`ע롢ʼ bean bean Ҫ aop洢ľǴ\n*    `earlySingletonObjects`Ϊ `HashMap<String, Object>`λ `DefaultSingletonBeanRegistry` У`key` Ϊ `beanName``value` ʵɣδע `bean` `bean` Ҫ `aop`洢ľǴֻеԭʼδע룻\n*    `singletonFactories`Ϊ `HashMap<String, ObjectFactory>`λ `DefaultSingletonBeanRegistry` У`key` Ϊ `beanName``value` 洢һ `lambda` ʽ`() -> getEarlyBeanReference(beanName, mbd, bean)` `getEarlyBeanReference` е `bean` Ǹմɵ `java bean`ûн spring ע룬Ҳû aop ( `lambda` ʽ)\n\nΪ˵㣬 `singletonObjects``earlySingletonObjects`  `singletonFactories` ֱΪ**һ**********\n\nspring  aop µѭ£\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-dc325a87b321e4c246a1b2f14169821a75a.png)\n\nͼűȽϸӣʵֿͱȽϼˣУ`1~8` ǻȡ `service1` ̣`5.1~5.8` ǻȡ `service2` ̣`5.5.1` ٴλȡ `service1` ֻ̣ڴ `service1` ĳʼУᴥ `service2` ĳʼ̣ `service2` ĳʼʱֻ `service1`˲ſһ𣬱Ƚϸӡ\n\nͼḶ́˵£飺̱Ƚϸӣȿ `1~8` Ĳٿ `5.1~5.8` Ĳࣩܶ\n\n*   1.  `service1`ȡ `service1`һлȡʱǻȡģ\n*   1.  `service1` `service1` ʵ\n*   1.  `service1`ȡҪע뷽ԭʼϽлȡ\n*   1.  `service1`֧ѭãͽ `service1` ŵУǷ֧ѭǿõģ\n*   1.  `service1` `service1` ע룬Ҫ `service2`ȻͿʼ `service2` Ļȡ̣\n*   5.1 `service2`ȡ `service2`һлȡʱǻȡģ\n*   5.2 `service2` `service2` ʵ\n*   5.3 `service2`ȡҪע뷽ԭʼϽлȡ\n*   5.4 `service2`֧ѭãͽ `service2` ŵУǷ֧ѭǿõģ\n*   5.5 `service2` `service2` ע룬Ҫ `service1`ȻͿʼ `service1` Ļȡ̣\n*   5.5.1 `service1`: ȡ `service1`һлȡȡʱ `service1` ڴУǼӶлȡմлȡˣ档ȡĹУ**ж `service1` ǷҪ aopȻʼ aop **˷е `service1` ǰ aop ǽѭĹؼ\n*   5.6 `service2`õ `service1`  `service1` Ǵ󣩣ע뵽 `service2` УŶ `service2`  aopõ `service2` Ĵ\n*   5.7 `service2`֧ѭȴһٴλȡ `service2`δȡʹõǰ `service2`ǰ `service2` Ǵ)\n*   5.8 `service2` service2 ĴһУɾ棬ˣ`service2` ʼɣע `service1` Ǵһе `service2` ҲǴ\n*   1.  `service1`ص `service1` ڣõ `service2` `service2` Ǵ󣩺󣬽ע뵽 `service1``service1` עɣгʼж `service1` ǷҪ aopȻ `service1` Ҫ aop ģ `5.5.1` Ѿй aop ˣˣֱӷأһ`service1` ԭʼ󣩣\n*   1.  `service1`֧ѭȴһлȡ `service1`ȡٴӶлȡ `service1`Իȡ `5.5.1` ֪ `service1` 󣩣أ\n*   1.  `service1`лȡĶעᵽһУɾ棬ˣ`service1` ʼɣע `service2` Ǵһе `service1` ҲǴ\n\ṇȻ϶࣬ `service1`  `service2` ĻȡͬģֻҪŪ֮һĻȡ̣һ bean Ļȡ̾ͺͬˡ\n\nУݽṹҪ˵£\n\n*   `singletonsCurrentlyInCreation`Ϊ `SetFromMap<String>`λ `DefaultSingletonBeanRegistry`ʽΪ `Collections.newSetFromMap(new ConcurrentHashMap<>(16))`Ǹ `ConcurrentHashMap` ʵֵ set洢ڴеĶ**жϵǰǷڴоͨҵǰǷ set **ģ\n*   `earlyProxyReferences`Ϊ `ConcurrentHashMap<Object, Object>`λ `AbstractAutoProxyCreator`洢ǰ aop Ķ**һǰ aopںٴ aop ʱͨж϶Ƿ `earlyProxyReferences` жȷҪҪ aopԴ֤ÿֻһ aop**\n\nˣspring һṩ 5 ݽṹѭ⣬ܽ£\n\n| ṹ                            | ˵                                                         |\n| ------------------------------- | ------------------------------------------------------------ |\n| `singletonObjects`              | **һ**Ϊ `ConcurrentHashMap<String, Object>`λ `DefaultSingletonBeanRegistry` У`key` Ϊ `beanName``value`  `spring bean`ע롢ʼ bean bean Ҫ aop洢ľǴ |\n| `earlySingletonObjects`         | ****Ϊ `HashMap<String, Object>`λ `DefaultSingletonBeanRegistry` У`key` Ϊ `beanName``value` ʵɣδע `bean`** `bean` Ҫ `aop`洢ľǴֻеԭʼδע** |\n| `singletonFactories`            | ****Ϊ `HashMap<String, ObjectFactory>`λ `DefaultSingletonBeanRegistry` У`key` Ϊ `beanName``value` 洢һ `lambda` ʽ`() -> getEarlyBeanReference(beanName, mbd, bean)``getEarlyBeanReference(xxx)` е `bean` Ǹմɵ `java bean`ûн spring ע룬Ҳû aop |\n| `singletonsCurrentlyInCreation` | Ϊ `SetFromMap<String>`λ `DefaultSingletonBeanRegistry`ʽΪ `Collections.newSetFromMap(new ConcurrentHashMap<>(16))`Ǹ `ConcurrentHashMap` ʵֵ set洢ڴеĶ󣬿**жϵǰǷڴ** |\n| `earlyProxyReferences`          | Ϊ `ConcurrentHashMap<Object, Object>`λ `AbstractAutoProxyCreator`洢ǰ aop Ķ󣬿**ж bean Ƿй aop֤ÿֻһ aop** |\n\nϾ spring ѭˡ\n\n### 3\\. ģ\n\nʽԴǰģѭḶ́£\n\n```\n/**\n * ׼һ࣬ڲһ Obj2\n */\npublic class Obj1 {\n    // Ҫע obj2\n    private Obj2 obj2;\n\n    // ʡ\n    ...\n}\n\n/**\n * ׼һ࣬ڲһ Obj1\n */\npublic class Obj2 {\n    // Ҫע ob1\n    private Obj1 obj1;\n\n    // ʡ\n    ...\n\n}\n\n/**\n * ׼Obj2Ĵ࣬ڲobj2Ķ\n */\npublic class ProxyObj2 extends Obj2 {\n    // obj2ڲobj2ԭʼ\n    private Obj2 obj2;\n\n    public ProxyObj2(Obj2 obj2) {\n        this.obj2 = obj2;\n    }\n\n    // ʡ\n    ...\n\n}\n\n/**\n * ׼Obj1Ĵ࣬ڲobj1Ķ\n */\npublic class ProxyObj1 extends Obj1 {\n    // obj2ڲobj1ԭʼ\n    private Obj1 obj1;\n\n    public ProxyObj1(Obj1 obj1) {\n        this.obj1 = obj1;\n    }\n\n    // ʡ\n    ...\n\n}\n\n```\n\n*   ׼ࣺ`Obj1`  `Obj2`  `Obj1` иΪ `Obj2``Obj2` иΪ `Obj1`\n*   ׼ `Obj1`  `Obj2` Ĵ `ProxyObj1``ProxyObj2` `ProxyObj1``ProxyObj2` ֱһԣ`Obj1`  `Obj2`\n*    `new ObjX()` ģĴ\n*    `objX.setObjX(xxx)` ģע룻\n*    `new ProxyObjX(xxx)` ģɣ\n*    `collection.add(xxx)` ģӵеḶ́\n\nģյõĽΪ\n\n*   շĶֱ `proxyObj1``proxyObj2`\n*   ע뵽 `obj1` е `proxyObj2`ע뵽 `obj2` е `proxyObj2`\n\n׼ѾˣǾͿʼģˡ\n\n#### 3.1 ģ 1\n\nҪ\n\n*   Obj1  Obj2 ϸ  --> ע --> ɴ --> 浽С ̴\n*   Ĵ̿Խ\n\nĿ꣺\n\n*   շĶֱ `proxyObj1``proxyObj2`\n*   ע뵽 `obj1` е `proxyObj2`ע뵽 `obj2` е `proxyObj2`\n\n£\n\n```\npublic static main(String[] args) {\n     // ׼һﱣڵĶ\n     // 1\\. ԪԭʼöѾע \n     // 2\\. ԪǴöеԭжѾע \n     Collection<?> collection = new ArrayList();\n\n     // 1\\.  Obj1 \n     Obj1 obj1 = new Obj1();\n\n     // Ҫobj2Ĵע뵽obj1Уʱвûobj2Ĵлobj2Ĵ\n     // һ.  Obj2 \n     Obj2 obj2 = new Obj2();\n\n     // obj2Ҫעobj1Ĵ󣬵ʱвûobj2ĴҪеobj1Ĵ\n\n}\n\n```\n\nִ ִ Obj2 ̾ͽвȥˣ\n\n*   `obj1` Ҫע `obj2` Ĵ󣬵Ҳл `obj2` Ĵ̣\n*   `obj2` Ҫע `obj1` Ĵ󣬵Ҳл `obj1` Ĵ̣\n*   `obj1` Ҫע `obj2` Ĵ󣬵Ҳл `obj2` Ĵ̣\n*   ...\n\nѭ\n\nģδﵽԤĿ꣬ģʧܡ\n\n#### 3.1 ģ 2\n\nҪ\n\n*   Obj1  Obj2 ֮һ\n    *    --> ע --> ɴ --> 浽С ̴\n    *    (ԭʼ)--> ɴ --> ԭʼע --> 󱣴浽С ̴\n*   Ĵ̿Խ\n\nĿ꣺\n\n*   շĶֱ `proxyObj1``proxyObj2`\n*   ע뵽 `obj1` е `proxyObj2`ע뵽 `obj2` е `proxyObj2`### 1\\. ʲôѭ\n\nspring עʱܻ໥ע\n\n```\n@Service\npublic class Service1 {\n    @Autowired\n    private Service2 service2;\n\n}\n\n@Service\npublic class Service2 {\n    @Autowired\n    private Service1 service1;\n\n}\n\n```\n\nϴ룬 `Service1` ͨ `@Autowird` ע `Service2` `Service2` ͨ `@Autowird` ע `Service1`໥עͽѭ\n\n### 2\\. ѭʲô\n\nʵϣ `ABBҲA`java ȫֵ֧ģ\n\n```\n/**\n * ׼service1\n */\npublic class Service1 {\n    private Service2 service2;\n\n    public void setService2(Service2 service2) {\n        this.service2 = service2;\n    }\n\n    public Service2 getService2() {\n        return this.service2;\n    }\n}\n\n/**\n * ׼service2\n */\npublic class Service2 {\n    private Service1 service1;\n\n    public void setService1(Service1 service1) {\n        this.service1 = service1;\n    }\n\n    public Service1 getService1() {\n        return this.service1;\n    }\n}\n\n/**\n * е\n */\npublic class Main {\n    public void main(String[] args) {\n        // ׼\n        Service1 service1 = new Service1();\n        Service2 service2 = new Service2();\n        // ໥\n        service1.setService2(service2);\n        service2.setService1(service1);\n    }\n}\n\n```\n\nô spring У໥עԷʵʲôأ `spring bean` Ĵ̣**ע⣺ǽ `bean`  `scope` Ϊ `singleton` Ҳ `scope` Ϊ``**\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-ca36e17077b1b191834645b3c8e588ff6c4.png)\n\nмҪ˵£\n\n1.  ʵʹ jdk ṩķƴ java Ե 1 ᵽ `Service1` ΪɼΪ `Service1 service = new Service1()`\n2.  ע󣺻Ե 1 ᵽ `Service1` Ϊ`Service1` ͨ `@Autowired` Զע `Service2`һǸ `Service2` ֵḶ́ɼΪ `service1.setService2(service2)`\n3.  `singletonObjects`һ java ͱһ spring beanȻ󱣴浽 `singletonObjects` ˣǸ `map``key`  bean ƣ`value`  beanֻ `spring bean`ֻ java ʵ\n\nʵϣ`java`  `spring bean`ֻע룬гʼִ `beanPorcessor` ȣ**ڱǷ `spring bean` ѭģصעѭصĲ衣**\n\n#### 2.1 ѭ\n\n˽ spring bean Ĳ֮󣬽Ǿѭ⣬ʽǰȷ\n\n*   `java`ʵϣjava һж󶼿Գ֮Ϊ `java` Ϊ˵㣬ᵽ `java`ָʵɡδ spring bean ڶ\n*   `spring bean`һ java 󣬲ҽ spring bean ڶ\n\nspring bean Ĵ£\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-b55a211447b5fabeaa0c3ef0bfee0920c82.png)\n\nͼ˵£\n\n1.   `service1` 󴴽ɺ`spring`  `service1` Ҫע `service2`Ȼȥ `singletonObjects` в `service2`ʱҲ `service2`ȻͿʼ `service2` Ĵ̣\n2.   `service2` 󴴽ɺ`spring`  `service2` Ҫע `service1`Ȼȥ `singletonObjects` в `service1`ʱҲ `service1`Ϊһ `service1` ûдɹ ȻͿʼ `service1` Ĵ̣\n3.  ص `1`ٴοʼ `service1` Ĵע̡\n\nǾϲط֣ѭˣ\n\n#### 2.2  `earlySingletonObjects` ѭ\n\nǷ£ѭֵԭڣ `service2` ȡ `service1` ʱ `singletonObjects` дʱ `service1`˻ `service1` Ĵ̣´ `service1`ˣи󵨵뷨 `service1` ʵͰ `service1` ʱͷδע `service1`\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-c0eaecbe82b144a6fcd9fd048f2cca53497.png)\n\nͼУ `earlySingletonObjects`ҲǸ mapͬ `singletonObjects` һ`key`  bean ƣ`value` һδעĶ\n\nͼ˵£\n\n1.   `service1` 󴴽ɺȽ `service1`  `earlySingletonObjects`Ȼע룻\n2.   `service1` עʱ`spring`  `service1` Ҫע `service2`Ȼȥ `earlySingletonObjects`  `service2`δҵȥ `singletonObjects` в `service2`δҵǾͿʼ `service2` Ĵ̣\n3.   `service2` 󴴽ɺȽ `service2`  `earlySingletonObjects`Ȼע룻\n4.   `service2` עʱ`spring`  `service2` Ҫע `service1`Ȼȥ `earlySingletonObjects`  `service1`ҵˣͽ `service1` ע뵽 `service2` Уʱ `service2` һ `spring bean` ˣ䱣浽 `singletonObjects` У\n5.   4 ǵõ `service2`Ȼע뵽 `service1` Уʱ `service1` Ҳһ `spring bean`䱣浽 `singletonObjects` С\n\nϲ裬Ƿ֣ѭõ˽\n\n#### 2.2 aop µѭ\n\nķǷֻҪһ `earlySingletonObjects` ѭܵõǣѭĵõ˽spring  ioc ⣬һشܣaop aop ³ѭ\n\n##### 1\\. aop Ĵ\n\nʽ aop µѭǰȷ\n\n*   `ԭʼ`ڴָδй aop Ķ󣬿 java Ҳδ aop  spring bean\n*   ``й aop Ķ󣬿 java й aop õĶ (й aopδע룬Ҳδгʼ)Ҳǽй aop  `spring bean`.\n\n aop δģ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-e92991c1c173bbd5579be3001a977555be7.png)\n\n `2.1` е̣aop  \"ɴ\" Ĳձ浽 `singletonObjects` еĶҲǴ\n\nԭʼ֮ʲôϵأôʾ££\n\n```\npublic class ProxyObj extends Obj {\n\n    // ԭʼ\n    private Obj obj;\n\n    ...\n}\n\n```\n\nʵϣ֮Ĺϵûô򵥣Ϊ˵⣬߹ϵ˼򻯣СֻҪף**ԭʼ**ɡ\n\nԭʼαɴģԲο [spring aop ֮ AnnotationAwareAspectJAutoProxyCreator £](https://my.oschina.net/funcy/blog/4687961)\n\nϴ̣ java ģ£\n\n```\n/**\n * ׼һ\n */\npublic class Obj1 {\n\n}\n\n/**\n * ׼һ࣬ڲһ Obj1\n */\npublic class Obj2 {\n\n    private Obj1 obj1;\n\n    // ʡ\n    ...\n\n}\n\n/**\n * ׼Obj2Ĵ࣬ڲobj2Ķ\n */\npublic class ProxyObj2 extends Obj2 {\n\n    private Obj2 obj2;\n\n    public ProxyObj2(Obj2 obj2) {\n        this.obj2 = obj2;\n    }\n\n    // ʡ\n    ...\n\n}\n\n```\n\nţģ  --> ע --> ɴ --> 浽С  ˣ\n\n```\npublic static main(String[] args) {\n     // ׼һﱣڵĶ\n     // 1\\. ԪԭʼöѾע \n     // 2\\. ԪǴöеԭжѾע \n     Collection<?> collection = new ArrayList();\n\n     // ʼ Obj2 Ĵ\n     // 1\\.  Obj2 \n     Obj2 obj2 = new Obj2();\n\n     // 2\\.  Obj2 ע obj1ʱûobj1Ҫobj1ٽע뵽Obj2\n     Obj1 obj1 = new Obj1();\n     obj2.setObj1(obj1);\n\n     // 3\\. Obj2Ĵ󣬴г Obj2ԭʼ\n     ProxyObj2 proxyObj2 = new ProxyObj2(obj2);\n\n     // 4\\. proxyObj2Ѿڣ˽ӵʱ\n     collection.add(proxyObj2); \n\n}\n\n```\n\nУ\n\n*    `new Obj2()` ģĴ\n*    `obj2.setObj1(xxx)` ģע\n*    `new ProxyObj2(xxx)` ģ\n*    `collection.add(xxx)` ģӵеĹ\n\nģ£\n\n1.   `obj2` \n2.   `Obj2` ע `obj1`ʱû `obj1`Ҫ `obj1`ٽע뵽 `Obj2` \n3.   `Obj2` Ĵ `proxyObj2``proxyObj2` г `Obj2` ԭʼ\n4.  `proxyObj2` Ѿڣ˽ӵʱ\n\nϸĲ裬ͻ֣ᷢĵ 2  3 ȫ˳Ҳû⣬ģ£\n\n```\npublic static main(String[] args) {\n     // ׼һﱣڵĶ\n     // 1\\. ԪԭʼöѾע \n     // 2\\. ԪǴöеԭжѾע \n     Collection<?> collection = new ArrayList();\n\n     // ʼ Obj2 Ĵ\n     // 1\\.  Obj2 \n     Obj2 obj2 = new Obj2();\n\n     // 2\\. Obj2Ĵ󣬴г Obj2ԭʼ\n     ProxyObj2 proxyObj2 = new ProxyObj2(obj2);\n\n     // 3\\.  obj2 ע obj1ʱûobj1Ҫobj1ٽע뵽Obj2\n     Obj1 obj1 = new Obj1();\n     // ע뵽ԭʼ\n     obj2.setObj1(obj1);\n\n     // 4\\. proxyObj2Ѿڣ˽ӵʱ\n     collection.add(proxyObj2); \n\n}\n\n```\n\n£\n\n1.   obj2 \n2.   Obj2 Ĵ󣬴г Obj2 ԭʼ\n3.   Obj2 ע obj1ʱû obj1Ҫ obj1ٽע뵽 Obj2\n4.  proxyObj2 Ѿڣ˽ӵʱ\n\nӴϿ`proxyObj2()` г `ob2(ԭʼ)`ɴ󣬼ԭʼע룬ȻӰմеԭʼҲע룬ͼʾ£\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-8ff5579425f73dd5c2d321a86d0303390ac.png)\n\nٴ java  spring bean Ĳкö࣬ǽעѭصĲ裬˽ spring bean ϸĳʼ̣ɲ鿴 [spring ̸֮](https://my.oschina.net/funcy/blog/4597493)\n\n̽ڿ֣\n\n*    --> ע --> ɴ --> 󱣴浽\n*    (ԭʼ)--> ɴ (ǰ aop)--> ԭʼע --> 󱣴浽\n\nֶܴﵽĿģ**浽еǴҴӦԭʼע**μ̣Ǻ aop ѭĺģ˵ˣ**aop µѭ֮ܽΪǰ aop **\n\n##### 2\\. Ϊʲô `earlySingletonObjects` ޷ѭ\n\nǰҪ˵˴Ĵ̣ aop £ʹ `earlySingletonObjects` ѭʲô⣺\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-d3d00d76ab0c72339faccb7ccd853723d6c.png)\n\nͼ̣\n\n1.   `service1` 󴴽ɺȽ `service1`  `earlySingletonObjects`Ȼע룻\n2.   `service1` עʱ`spring`  `service1` Ҫע `service2`Ȼȥ `earlySingletonObjects`  `service2`δҵȥ `singletonObjects` в `service2`δҵǾͿʼ `service2` Ĵ̣\n3.   `service2` 󴴽ɺȽ `service2`  `earlySingletonObjects`Ȼע룻\n4.   `service2` עʱ`spring`  `service2` Ҫע `service1`Ȼȥ `earlySingletonObjects`  `service1`ҵˣͽ `service1` ע뵽 `service2` УȻٽ aopʱ `service2` һ󣬽䱣浽 `singletonObjects` У\n5.   4 ǵõ `service2` ĴȻע뵽 `service1` Уٶ `service1`  aopʱ `service1` Ҳһ `spring bean`䱣浽 `singletonObjects` С\n\nʲôأϸ 4 ͻ֣ᷢ**ע뵽 `service2`  `service1` Ǵ**ݹȫ֣յõ `service1`  `service2` Ǵע뵽 `service2`  `service1` ӦҲǴŶԡˣ aop £ѭֳˣ\n\n#### 2.3 spring Ľ\n\nǰᵽ aop £ `earlySingletonObjects` ܽѭ⣬ spring ôأspring ٴһ `map` ⣬Ҳǳ˵ **spring ** `map` ˵£\n\n*   һ `singletonObjects`Ϊ `ConcurrentHashMap<String, Object>`λ `DefaultSingletonBeanRegistry` У`key` Ϊ `beanName``value`  `spring bean`ע롢ʼ bean bean Ҫ aop洢ľǴ\n*    `earlySingletonObjects`Ϊ `HashMap<String, Object>`λ `DefaultSingletonBeanRegistry` У`key` Ϊ `beanName``value` ʵɣδע `bean` `bean` Ҫ `aop`洢ľǴֻеԭʼδע룻\n*    `singletonFactories`Ϊ `HashMap<String, ObjectFactory>`λ `DefaultSingletonBeanRegistry` У`key` Ϊ `beanName``value` 洢һ `lambda` ʽ`() -> getEarlyBeanReference(beanName, mbd, bean)` `getEarlyBeanReference` е `bean` Ǹմɵ `java bean`ûн spring ע룬Ҳû aop ( `lambda` ʽ)\n\nΪ˵㣬 `singletonObjects``earlySingletonObjects`  `singletonFactories` ֱΪ**һ**********\n\nspring  aop µѭ£\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-dc325a87b321e4c246a1b2f14169821a75a.png)\n\nͼűȽϸӣʵֿͱȽϼˣУ`1~8` ǻȡ `service1` ̣`5.1~5.8` ǻȡ `service2` ̣`5.5.1` ٴλȡ `service1` ֻ̣ڴ `service1` ĳʼУᴥ `service2` ĳʼ̣ `service2` ĳʼʱֻ `service1`˲ſһ𣬱Ƚϸӡ\n\nͼḶ́˵£飺̱Ƚϸӣȿ `1~8` Ĳٿ `5.1~5.8` Ĳࣩܶ\n\n*   1.  `service1`ȡ `service1`һлȡʱǻȡģ\n*   1.  `service1` `service1` ʵ\n*   1.  `service1`ȡҪע뷽ԭʼϽлȡ\n*   1.  `service1`֧ѭãͽ `service1` ŵУǷ֧ѭǿõģ\n*   1.  `service1` `service1` ע룬Ҫ `service2`ȻͿʼ `service2` Ļȡ̣\n*   5.1 `service2`ȡ `service2`һлȡʱǻȡģ\n*   5.2 `service2` `service2` ʵ\n*   5.3 `service2`ȡҪע뷽ԭʼϽлȡ\n*   5.4 `service2`֧ѭãͽ `service2` ŵУǷ֧ѭǿõģ\n*   5.5 `service2` `service2` ע룬Ҫ `service1`ȻͿʼ `service1` Ļȡ̣\n*   5.5.1 `service1`: ȡ `service1`һлȡȡʱ `service1` ڴУǼӶлȡմлȡˣ档ȡĹУ**ж `service1` ǷҪ aopȻʼ aop **˷е `service1` ǰ aop ǽѭĹؼ\n*   5.6 `service2`õ `service1`  `service1` Ǵ󣩣ע뵽 `service2` УŶ `service2`  aopõ `service2` Ĵ\n*   5.7 `service2`֧ѭȴһٴλȡ `service2`δȡʹõǰ `service2`ǰ `service2` Ǵ)\n*   5.8 `service2` service2 ĴһУɾ棬ˣ`service2` ʼɣע `service1` Ǵһе `service2` ҲǴ\n*   1.  `service1`ص `service1` ڣõ `service2` `service2` Ǵ󣩺󣬽ע뵽 `service1``service1` עɣгʼж `service1` ǷҪ aopȻ `service1` Ҫ aop ģ `5.5.1` Ѿй aop ˣˣֱӷأһ`service1` ԭʼ󣩣\n*   1.  `service1`֧ѭȴһлȡ `service1`ȡٴӶлȡ `service1`Իȡ `5.5.1` ֪ `service1` 󣩣أ\n*   1.  `service1`лȡĶעᵽһУɾ棬ˣ`service1` ʼɣע `service2` Ǵһе `service1` ҲǴ\n\ṇȻ϶࣬ `service1`  `service2` ĻȡͬģֻҪŪ֮һĻȡ̣һ bean Ļȡ̾ͺͬˡ\n\nУݽṹҪ˵£\n\n*   `singletonsCurrentlyInCreation`Ϊ `SetFromMap<String>`λ `DefaultSingletonBeanRegistry`ʽΪ `Collections.newSetFromMap(new ConcurrentHashMap<>(16))`Ǹ `ConcurrentHashMap` ʵֵ set洢ڴеĶ**жϵǰǷڴоͨҵǰǷ set **ģ\n*   `earlyProxyReferences`Ϊ `ConcurrentHashMap<Object, Object>`λ `AbstractAutoProxyCreator`洢ǰ aop Ķ**һǰ aopںٴ aop ʱͨж϶Ƿ `earlyProxyReferences` жȷҪҪ aopԴ֤ÿֻһ aop**\n\nˣspring һṩ 5 ݽṹѭ⣬ܽ£\n\n| ṹ                            | ˵                                                         |\n| ------------------------------- | ------------------------------------------------------------ |\n| `singletonObjects`              | **һ**Ϊ `ConcurrentHashMap<String, Object>`λ `DefaultSingletonBeanRegistry` У`key` Ϊ `beanName``value`  `spring bean`ע롢ʼ bean bean Ҫ aop洢ľǴ |\n| `earlySingletonObjects`         | ****Ϊ `HashMap<String, Object>`λ `DefaultSingletonBeanRegistry` У`key` Ϊ `beanName``value` ʵɣδע `bean`** `bean` Ҫ `aop`洢ľǴֻеԭʼδע** |\n| `singletonFactories`            | ****Ϊ `HashMap<String, ObjectFactory>`λ `DefaultSingletonBeanRegistry` У`key` Ϊ `beanName``value` 洢һ `lambda` ʽ`() -> getEarlyBeanReference(beanName, mbd, bean)``getEarlyBeanReference(xxx)` е `bean` Ǹմɵ `java bean`ûн spring ע룬Ҳû aop |\n| `singletonsCurrentlyInCreation` | Ϊ `SetFromMap<String>`λ `DefaultSingletonBeanRegistry`ʽΪ `Collections.newSetFromMap(new ConcurrentHashMap<>(16))`Ǹ `ConcurrentHashMap` ʵֵ set洢ڴеĶ󣬿**жϵǰǷڴ** |\n| `earlyProxyReferences`          | Ϊ `ConcurrentHashMap<Object, Object>`λ `AbstractAutoProxyCreator`洢ǰ aop Ķ󣬿**ж bean Ƿй aop֤ÿֻһ aop** |\n\nϾ spring ѭˡ\n\n### 3\\. ģ\n\nʽԴǰģѭḶ́£\n\n```\n/**\n * ׼һ࣬ڲһ Obj2\n */\npublic class Obj1 {\n    // Ҫע obj2\n    private Obj2 obj2;\n\n    // ʡ\n    ...\n}\n\n/**\n * ׼һ࣬ڲһ Obj1\n */\npublic class Obj2 {\n    // Ҫע ob1\n    private Obj1 obj1;\n\n    // ʡ\n    ...\n\n}\n\n/**\n * ׼Obj2Ĵ࣬ڲobj2Ķ\n */\npublic class ProxyObj2 extends Obj2 {\n    // obj2ڲobj2ԭʼ\n    private Obj2 obj2;\n\n    public ProxyObj2(Obj2 obj2) {\n        this.obj2 = obj2;\n    }\n\n    // ʡ\n    ...\n\n}\n\n/**\n * ׼Obj1Ĵ࣬ڲobj1Ķ\n */\npublic class ProxyObj1 extends Obj1 {\n    // obj2ڲobj1ԭʼ\n    private Obj1 obj1;\n\n    public ProxyObj1(Obj1 obj1) {\n        this.obj1 = obj1;\n    }\n\n    // ʡ\n    ...\n\n}\n\n```\n\n*   ׼ࣺ`Obj1`  `Obj2`  `Obj1` иΪ `Obj2``Obj2` иΪ `Obj1`\n*   ׼ `Obj1`  `Obj2` Ĵ `ProxyObj1``ProxyObj2` `ProxyObj1``ProxyObj2` ֱһԣ`Obj1`  `Obj2`\n*    `new ObjX()` ģĴ\n*    `objX.setObjX(xxx)` ģע룻\n*    `new ProxyObjX(xxx)` ģɣ\n*    `collection.add(xxx)` ģӵеḶ́\n\nģյõĽΪ\n\n*   շĶֱ `proxyObj1``proxyObj2`\n*   ע뵽 `obj1` е `proxyObj2`ע뵽 `obj2` е `proxyObj2`\n\n׼ѾˣǾͿʼģˡ\n\n#### 3.1 ģ 1\n\nҪ\n\n*   Obj1  Obj2 ϸ  --> ע --> ɴ --> 浽С ̴\n*   Ĵ̿Խ\n\nĿ꣺\n\n*   շĶֱ `proxyObj1``proxyObj2`\n*   ע뵽 `obj1` е `proxyObj2`ע뵽 `obj2` е `proxyObj2`\n\n£\n\n```\npublic static main(String[] args) {\n     // ׼һﱣڵĶ\n     // 1\\. ԪԭʼöѾע \n     // 2\\. ԪǴöеԭжѾע \n     Collection<?> collection = new ArrayList();\n\n     // 1\\.  Obj1 \n     Obj1 obj1 = new Obj1();\n\n     // Ҫobj2Ĵע뵽obj1Уʱвûobj2Ĵлobj2Ĵ\n     // һ.  Obj2 \n     Obj2 obj2 = new Obj2();\n\n     // obj2Ҫעobj1Ĵ󣬵ʱвûobj2ĴҪеobj1Ĵ\n\n}\n\n```\n\nִ ִ Obj2 ̾ͽвȥˣ\n\n*   `obj1` Ҫע `obj2` Ĵ󣬵Ҳл `obj2` Ĵ̣\n*   `obj2` Ҫע `obj1` Ĵ󣬵Ҳл `obj1` Ĵ̣\n*   `obj1` Ҫע `obj2` Ĵ󣬵Ҳл `obj2` Ĵ̣\n*   ...\n\nѭ\n\nģδﵽԤĿ꣬ģʧܡ\n\n#### 3.1 ģ 2\n\nҪ\n\n*   Obj1  Obj2 ֮һ\n    *    --> ע --> ɴ --> 浽С ̴\n    *    (ԭʼ)--> ɴ --> ԭʼע --> 󱣴浽С ̴\n*   Ĵ̿Խ\n\nĿ꣺\n\n*   շĶֱ `proxyObj1``proxyObj2`\n*   ע뵽 `obj1` е `proxyObj2`ע뵽 `obj2` е `proxyObj2`\n\nʾ£\n\n```\n public static main(String[] args) {\n      // ׼һﱣڵĶ\n      // 1\\. ԪԭʼöѾע \n      // 2\\. ԪǴöеԭжѾע \n      Collection<?> collection = new ArrayList();\n\n      // 1\\.  Obj1 \n      Obj1 obj1 = new Obj1();\n\n      // Ҫobj2Ĵע뵽obj1Уʱвûobj2Ĵлobj2Ĵ\n      // һ.  Obj2 \n      Obj2 obj2 = new Obj2();\n\n      // 2\\.  Obj1 ǰ\n      ProxyObj1 proxyObj1 = new ProxyObj1(obj1);\n\n      // .  proxyObj1 ע뵽 obj2 \n      obj2.setObj1(proxyObj1);\n\n      // .  obj2Ĵ\n      ProxyObj2 proxyObj2 = new ProxyObj2(obj2);\n\n      // . proxyObj2 Ѿڣӵʱ\n      collection.add(proxyObj2);\n\n      // ʱѾ obj2 Ĵˣobj1\n      // 3\\.  proxyObj2 ע뵽 obj1 \n      obj1.setObj2(proxyObj2);\n\n      // 4\\. proxyObj1 Ѿڣӵʱ\n      collection.add(proxyObj1);\n }\n\n```\n\nĴУobj1  1234 ʶobj2  һġ ʶ£\n\n*   obj1 (ԭʼ)--> ɴ --> ԭʼע --> 󱣴浽С\n*   obj2 --> ע --> ɴ --> 浽С\n\n߶УﵽԤڵĿꡣ\n\n#### 3.3 ģеõĽ\n\nԱģ룬ģ 2 ֮ ܴﵽԤĿ꣬ҪΪע `obj2`  `obj1` ʱǰ `obj1` Ĵ `proxyObj1`ʹ `obj2` ̡ٴ֤ṩ aop ѭĽҪã\n\nƪľȵˣҪѭĲ spring ѭĲ裬ͨδģѭĽһƪǽ spring Դ spring νѭġ\n\n* * *\n\n_ԭӣ[https://my.oschina.net/funcy/blog/4659555](https://my.oschina.net/funcy/blog/4659555) ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע_\n\nʾ£\n\n```\n public static main(String[] args) {\n      // ׼һﱣڵĶ\n      // 1\\. ԪԭʼöѾע \n      // 2\\. ԪǴöеԭжѾע \n      Collection<?> collection = new ArrayList();\n\n      // 1\\.  Obj1 \n      Obj1 obj1 = new Obj1();\n\n      // Ҫobj2Ĵע뵽obj1Уʱвûobj2Ĵлobj2Ĵ\n      // һ.  Obj2 \n      Obj2 obj2 = new Obj2();\n\n      // 2\\.  Obj1 ǰ\n      ProxyObj1 proxyObj1 = new ProxyObj1(obj1);\n\n      // .  proxyObj1 ע뵽 obj2 \n      obj2.setObj1(proxyObj1);\n\n      // .  obj2Ĵ\n      ProxyObj2 proxyObj2 = new ProxyObj2(obj2);\n\n      // . proxyObj2 Ѿڣӵʱ\n      collection.add(proxyObj2);\n\n      // ʱѾ obj2 Ĵˣobj1\n      // 3\\.  proxyObj2 ע뵽 obj1 \n      obj1.setObj2(proxyObj2);\n\n      // 4\\. proxyObj1 Ѿڣӵʱ\n      collection.add(proxyObj1);\n }\n\n```\n\nĴУobj1  1234 ʶobj2  һġ ʶ£\n\n*   obj1 (ԭʼ)--> ɴ --> ԭʼע --> 󱣴浽С\n*   obj2 --> ע --> ɴ --> 浽С\n\n߶УﵽԤڵĿꡣ\n\n#### 3.3 ģеõĽ\n\nԱģ룬ģ 2 ֮ ܴﵽԤĿ꣬ҪΪע `obj2`  `obj1` ʱǰ `obj1` Ĵ `proxyObj1`ʹ `obj2` ̡ٴ֤ṩ aop ѭĽҪã\n\nƪľȵˣҪѭĲ spring ѭĲ裬ͨδģѭĽһƪǽ spring Դ spring νѭġ\n\n* * *\n\n_ԭӣ[https://my.oschina.net/funcy/blog/4659555](https://my.oschina.net/funcy/blog/4659555) ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע_"
  },
  {
    "path": "docs/Spring全家桶/Spring源码分析/Spring重要机制探秘/Spring探秘之循环依赖的解决（二）：源码分析.md",
    "content": " [spring ֮̽ѭһۻʯ](https://my.oschina.net/funcy/blog/4659555 \"spring֮̽ѭһۻʯ\")һ ᵽ spring ѭ:\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-dc325a87b321e4c246a1b2f14169821a75a.png)\n\nΪ˳Уspring ṩ 5 ݽṹвĹؼϢ 5 ݽṹ£Ľ 5 ݽṹΪ **5 ṹ**\n\n| ṹ                            | ˵                                                         |\n| ------------------------------- | ------------------------------------------------------------ |\n| `singletonObjects`              | **һ**Ϊ `ConcurrentHashMap<String, Object>`λ `DefaultSingletonBeanRegistry` У`key` Ϊ `beanName``value`  `spring bean`ע롢ʼ bean bean Ҫ aop洢ľǴ |\n| `earlySingletonObjects`         | ****Ϊ `HashMap<String, Object>`λ `DefaultSingletonBeanRegistry` У`key` Ϊ `beanName``value` ʵɣδע `bean`** `bean` Ҫ `aop`洢ľǴֻеԭʼδע** |\n| `singletonFactories`            | ****Ϊ `HashMap<String, ObjectFactory>`λ `DefaultSingletonBeanRegistry` У`key` Ϊ `beanName``value` 洢һ `lambda` ʽ`() -> getEarlyBeanReference(beanName, mbd, bean)``getEarlyBeanReference(xxx)` е `bean` Ǹմɵ `java bean`ûн spring ע룬Ҳû aop |\n| `singletonsCurrentlyInCreation` | Ϊ `SetFromMap<String>`λ `DefaultSingletonBeanRegistry`ʽΪ `Collections.newSetFromMap(new ConcurrentHashMap<>(16))`Ǹ `ConcurrentHashMap` ʵֵ set洢ڴеĶ󣬿**жϵǰǷڴ** |\n| `earlyProxyReferences`          | Ϊ `ConcurrentHashMap<Object, Object>`λ `AbstractAutoProxyCreator`洢ǰ aop Ķ󣬿**ж bean Ƿй aop֤ÿֻһ aop** |\n\n˽Щ֮󣬽ǾʽʼԴˡ\n\n### 1\\. ׼ demo\n\nеʾ demo λ [gitee.com/funcy](https://gitee.com/funcy/spring-framework/tree/v5.2.2.RELEASE_learn/spring-learn/src/main/java/org/springframework/learn/explore/demo03 \"gitee.com/funcy\")ؼ롣\n\n׼ serviceservice1service2 service ﶼһ\n\n```\n@Service\npublic class Service1 {\n\n    @Autowired\n    private Service2 service2;\n\n    public Service1() {\n        System.out.println(\"service1Ĺ췽\");\n    }\n\n    /**\n     * ע @AopAnnotation ˣҪ\n     */\n    @AopAnnotation\n    public void printAutowired() {\n        System.out.println(\"Service1 Autowired:\" + service2.getClass());\n    }\n\n    @Override\n    public String toString() {\n        return \"Service1:\" + getClass();\n    }\n}\n\n@Component\npublic class Service2 {\n\n    @Autowired\n    private Service1 service1;\n\n    public Service2() {\n        System.out.println(\"service2Ĺ췽\");\n    }\n\n    /**\n     * ע @AopAnnotation ˣҪ\n     */\n    @AopAnnotation\n    public void printAutowired() {\n        System.out.println(\"Service2 Autowired:\" + service1.getClass());\n    }\n\n    @Override\n    public String toString() {\n        return \"Service2:\" + this.getClass();\n    }\n}\n\n```\n\nࣺ\n\n```\npublic class Demo03Main {\n\n    public static void main(String[] args) {\n        ApplicationContext context = \n                new AnnotationConfigApplicationContext(AopAnnotationConfig.class);\n        Object obj1 = context.getBean(\"service1\");\n        Object obj2 = context.getBean(\"service2\");\n        ((Service1)obj1).printAutowired();\n        ((Service2)obj2).printAutowired();\n    }\n}\n\n```\n\n `Service1` УҪע `service2` `Service2` УҪע `service1` `Service1``Service2` Ҫд `main()` ִн£\n\n```\nservice1Ĺ췽\nservice2Ĺ췽\nDisconnected from the target VM, address: 'localhost:55518', transport: 'socket'\nConnected to the target VM, address: '127.0.0.1:55507', transport: 'socket'\n@Around: before execute...\nService1 Autowired:class org.springframework.learn.explore.demo03.Service2$$EnhancerBySpringCGLIB$$e7e367ab\n@Around: after execute...\n@Around: before execute...\nService2 Autowired:class org.springframework.learn.explore.demo03.Service1$$EnhancerBySpringCGLIB$$d447df08\n@Around: after execute...\n\n```\n\nõ obj1obj2 ֱΪ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-201844d13a7b489d1a18a8218d6440d6068.png)\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-3781280ce3f4da91504b0665ebfd3aed05b.png)\n\nݣѵó½ۣ\n\n*   `service1`  `service2`  `printAutowired()` 淽ݣ߶ɹˣ\n*   лȡĵ `service1`  `service2` Ǵ\n*   `service1` Ĵ `service1` ԭʼ`service2` Ĵ `service2` ԭʼ\n*   `service1` ԭʼע `service2` Ϊ`service2` ԭʼע `service1` Ϊ\n\nǾʹԴ spring һУյõġ\n\n> `service1`  `service2` Ĵ `cglib` ģⲿ뱾޹أͲˡ\n\n### 2\\. һε `AbstractBeanFactory#getBean(String)`ȡ `service1`\n\nspring bean ĴעǴ `AbstractBeanFactory#getBean(String)` ʼһɲο [spring ֮ BeanFactory ĳʼ](https://my.oschina.net/funcy/blog/4658230)ǵԴҲ֡\n\nһʼᵽ spring Ϊѭ 5 ṹչʾ£\n\n| ṹ                            |  |\n| ------------------------------- | ---- |\n| `singletonObjects`              |      |\n| `earlySingletonObjects`         |      |\n| `singletonFactories`            |      |\n| `singletonsCurrentlyInCreation` |      |\n| `earlyProxyReferences`          |      |\n\nһʼ5 ṹй `service1`  `service2` ʲôݶû һ߷룬һ߹ע⼸ṹеݡ\n\n> ʵϣ5 ṹݵģ spring ڲṩһЩ beanֻע `service1`  `service2` صݣﲻչʾ\n\n### 2.1 `AbstractBeanFactory#doGetBean`\n\nȡ `service1` Ĵ `AbstractBeanFactory#getBean(String)`Ļȡȴ `AbstractBeanFactory#doGetBean` У£\n\n```\n|-AbstractBeanFactory#getBean(String)\n |-AbstractBeanFactory#doGetBean\n\n```\n\n£\n\n```\n    protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,\n            @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {\n\n        ...\n        // 1\\. ǷѳʼbeanNamesingletonsCurrentlyInCreation\n        Object sharedInstance = getSingleton(beanName);\n        ...\n        // ǵ\n        if (mbd.isSingleton()) {\n            // 2\\. getSingleton(): ɾ\n            sharedInstance = getSingleton(beanName, () -> {\n                try {\n                    // ִд Bean\n                    return createBean(beanName, mbd, args);\n                }\n                catch (BeansException ex) {\n                    destroySingleton(beanName);\n                    throw ex;\n                }\n            });\n            // ͨBean Ļֱӷأ FactoryBean Ļ\n            // Ǹʵ\n            bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);\n        }\n        ...\n    }\n\n```\n\nϴ뾭˾ֻҪ̣УҲᾫ룬ֻҪ̣ʡ޹ش룩طҪ\n\n1.  `getSingleton(beanName)`ȡֻлȡ\n2.  `getSingleton(beanName, () -> { ... })`ȡ󣬴ﴴ\n\n#### 2.2 `DefaultSingletonBeanRegistry#getSingleton(String, boolean)`\n\n `getSingleton(beanName)`£\n\n```\n|-AbstractBeanFactory#getBean(String)\n |-AbstractBeanFactory#doGetBean\n  |-DefaultSingletonBeanRegistry#getSingleton(String)\n   |-DefaultSingletonBeanRegistry#getSingleton(String, boolean)\n\n```\n\nֱӿ룺\n\n```\n/*\n * beanName: ֵΪ service1\n * allowEarlyReferenceֵΪ true\n */\nprotected Object getSingleton(String beanName, boolean allowEarlyReference) {\n    // 1\\. һлȡǻȡ\n    Object singletonObject = this.singletonObjects.get(beanName);\n    // 2\\. isSingletonCurrentlyInCreation(...) жservice1ǷڴУ false\n    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {\n        // ʡĴ\n        ...\n    }\n    return singletonObject;\n}\n\n```\n\n£\n\n1. лȡʱ `singletonObjects` ﲢû `service1`ȡ\n\n2. ж `service1` ǷڴУжϷʽ£\n\n   ```\n   public boolean isSingletonCurrentlyInCreation(String beanName) {\n       return this.singletonsCurrentlyInCreation.contains(beanName);\n   }\n   \n   ```\n\n   Ȼ`singletonsCurrentlyInCreation` û `service1` ģҲ false.\n\n`DefaultSingletonBeanRegistry#getSingleton(String, boolean)` еͷˣķǾȲˡ\n\n#### 2.3 `DefaultSingletonBeanRegistry#getSingleton(String, ObjectFactory<?>)`\n\nǻص `AbstractBeanFactory#doGetBean`ŷ `getSingleton(beanName, () -> { ... })`£\n\n```\n    /**\n     * beanNameservice1\n     * singletonFactorylambdaʽֵΪ\n     *           () -> {\n     *               try {\n     *                   // Ǵbean\n     *                   return createBean(beanName, mbd, args);\n     *               }\n     *               catch (BeansException ex) {\n     *                   destroySingleton(beanName);\n     *                   throw ex;\n     *               }\n     *           }\n     */\n    public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {\n        Assert.notNull(beanName, \"Bean name must not be null\");\n        synchronized (this.singletonObjects) {\n            // ٴжbeanǷѴ\n            Object singletonObject = this.singletonObjects.get(beanName);\n            if (singletonObject == null) {\n                // 1\\. жϵǰʵbeanǷڴ\n                // beanNameӵ singletonsCurrentlyInCreation \n                beforeSingletonCreation(beanName);\n                try {\n                    // 2\\. beanĴ\n                    singletonObject = singletonFactory.getObject();\n                }\n                catch (...) {\n                    ...\n                }\n                ...\n            }\n            return singletonObject;\n        }\n    }\n\n```\n\nմеעݣǷҪ裺\n\n1. жϵǰʵ bean ǷڴУжϵǰǷ `singletonsCurrentlyInCreation` У׳쳣ӵ `singletonsCurrentlyInCreation` У\n\n   ֮һд5 ṹе£\n\n   | ṹ                            |      |\n      | ------------------------------- | -------- |\n   | `singletonObjects`              |          |\n   | `earlySingletonObjects`         |          |\n   | `singletonFactories`            |          |\n   | `singletonsCurrentlyInCreation` | service1 |\n   | `earlyProxyReferences`          |          |\n\n2.  bean Ĵ̣`singletonFactory.getObject()` еʵǴ lambda ʽ\n\n   ```\n   () -> {\n       try {\n           // ִд Bean\n           return createBean(beanName, mbd, args);\n       }\n       catch (BeansException ex) {\n           destroySingleton(beanName);\n           throw ex;\n       }\n   }\n   \n   ```\n\n   Ҳ `AbstractAutowireCapableBeanFactory#createBean(String, RootBeanDefinition, Object[])` ǽص㣻\n\n#### 2.4 `AbstractAutowireCapableBeanFactory#doCreateBean`\n\nӴ`AbstractAutowireCapableBeanFactory#createBean(String, RootBeanDefinition, Object[])` ûʲôʵݣǾֱ `AbstractAutowireCapableBeanFactory#doCreateBean` ˣоǧɽˮ£\n\n```\n|-AbstractBeanFactory#getBean(String)\n |-AbstractBeanFactory#doGetBean\n  |-DefaultSingletonBeanRegistry#getSingleton(String, ObjectFactory<?>)\n   |-AbstractAutowireCapableBeanFactory#createBean(String, RootBeanDefinition, Object[])\n    |-AbstractAutowireCapableBeanFactory#doCreateBean\n\n```\n\n`AbstractAutowireCapableBeanFactory#doCreateBean` £\n\n```\nprotected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, \n        final @Nullable Object[] args) throws BeanCreationException {\n\n    BeanWrapper instanceWrapper = null;\n    if (instanceWrapper == null) {\n        // 1\\. ʵ Beanjavaʵ\n        instanceWrapper = createBeanInstance(beanName, mbd, args);\n    }\n    //beanʵ\n    final Object bean = instanceWrapper.getWrappedInstance();\n    //bean\n    Class<?> beanType = instanceWrapper.getWrappedClass();\n    if (beanType != NullBean.class) {\n        mbd.resolvedTargetType = beanType;\n    }\n\n    synchronized (mbd.postProcessingLock) {\n        if (!mbd.postProcessed) {\n            try {\n                //  AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition \n                // 2\\. ȡҪע뷽ע @Autowired ע⣩\n                applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);\n            }\n            catch (Throwable ex) {\n                throw new BeanCreationException(mbd.getResourceDescription(), beanName,\n                        \"Post-processing of merged bean definition failed\", ex);\n            }\n            mbd.postProcessed = true;\n        }\n    }\n    // ѭ, Ƿѭ, allowCircularReferencesĬΪtrueԹر\n    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&\n            isSingletonCurrentlyInCreation(beanName));\n    if (earlySingletonExposure) {\n        // 3\\.  bean ӵ singletonFactories\n        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));\n    }\n\n    Object exposedObject = bean;\n    try {\n        // 4\\. ע룬Ҫע service2Ȼٵ getBean(\"service2\")\n        populateBean(beanName, mbd, instanceWrapper);\n        ...\n    }\n    catch (Throwable ex) {\n        ...\n    }\n}\n\n```\n\nеĲ£\n\n1. ʵ Bean java ʵͲ˵ˣʵ `service1` £Կ `service2`  `null`δע룩\n\n   ![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-8b2f5fdf0efd6beb7f62d4f35f5c5562b83.png)\n\n2. ȡҪע뷽**ԭʼвܻȡǻȡ** `@Autowired`  `beanPostProcessor`  `AutowiredAnnotationBeanPostProcessor`\n\n3.  bean ӵ `singletonFactories` Уӹ£\n\n   ```\n   protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {\n       Assert.notNull(singletonFactory, \"Singleton factory must not be null\");\n       synchronized (this.singletonObjects) {\n           // صвڲŻadd\n           if (!this.singletonObjects.containsKey(beanName)) {\n               // ѹputsingletonFactories\n               this.singletonFactories.put(beanName, singletonFactory);\n               // ɾеĶ\n               this.earlySingletonObjects.remove(beanName);\n               this.registeredSingletons.add(beanName);\n           }\n       }\n   }\n   \n   ```\n\n   ĶΪ\n\n   ![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-3d9a56a0d655fe1e95d8857095fb46aa71a.png)\n\n   ʱ 5 ṹе£\n\n   | ṹ                            |              |\n      | ------------------------------- | ---------------- |\n   | `singletonObjects`              |                  |\n   | `earlySingletonObjects`         |                  |\n   | `singletonFactories`            | lambda(service1) |\n   | `singletonsCurrentlyInCreation` | service1         |\n   | `earlyProxyReferences`          |                  |\n\n   `lambda(service1)` ʵΪ `() -> getEarlyBeanReference(beanName, mbd, bean)` `lambda` ʽľǽ aop ݵеʱٷ\n\n4. ע룬ڵ 2 Уspring ҵ `service1` Ҫע `service2`ٵ `beanFactory.getBean(\"service2\")`  `service2` ڣֻص `AbstractBeanFactory#getBean(String)` \n\n    `populateBean(xxx)`  `getBean(xxx)` Ĳ൱࣬£\n\n   ```\n   |-AbstractBeanFactory#getBean(String)\n    |-AbstractBeanFactory#doGetBean\n     |-DefaultSingletonBeanRegistry#getSingleton(String, ObjectFactory<?>)\n      |-AbstractAutowireCapableBeanFactory#createBean(String, RootBeanDefinition, Object[])\n       |-AbstractAutowireCapableBeanFactory#doCreateBean\n        // ע\n        |-AbstractAutowireCapableBeanFactory#populateBean\n         |-AutowiredAnnotationBeanPostProcessor#postProcessProperties\n          |-InjectionMetadata#inject\n           |-AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject\n            |-DefaultListableBeanFactory#resolveDependency\n             |-DefaultListableBeanFactory#doResolveDependency\n              |-DependencyDescriptor#resolveCandidate\n               |-AbstractBeanFactory#getBean(String)\n   \n   ```\n\n   һ `DependencyDescriptor#resolveCandidate`õ `beanFactory.getBean(String)` \n\n   ```\n   public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory)\n           throws BeansException {\n       return beanFactory.getBean(beanName);\n   }\n   \n   ```\n\n`service1` Ļȡ̾ͣһ£Ϊ `service1` Ҫע `service2`Ϳʼȡ `service2` ̡\n\nСڽʱ5 ṹе£\n\n| ṹ                            |              |\n| ------------------------------- | ---------------- |\n| `singletonObjects`              |                  |\n| `earlySingletonObjects`         |                  |\n| `singletonFactories`            | lambda(service1) |\n| `singletonsCurrentlyInCreation` | service1         |\n| `earlyProxyReferences`          |                  |\n\n### 3\\. ڶε `AbstractBeanFactory#getBean(String)`ȡ `service2`\n\n#### 3.1 `AbstractBeanFactory#doGetBean`\n\nһȡ `service1` ̻һ£ͬ `beanName`  `service2`ٷ\n\n#### 3.2 `DefaultSingletonBeanRegistry#getSingleton(String, boolean)`\n\nһȡ `service1` ̻һ£ͬ `beanName`  `service2`ٷ\n\n#### 3.3 `DefaultSingletonBeanRegistry#getSingleton(String, ObjectFactory<?>)`\n\nһȡ `service1` ̻һ£ͬ `beanName`  `service2`ݲٷ\n\n`service2` һ  `singletonsCurrentlyInCreation` ṹУһ֮5 ṹе£\n\n| ṹ                            |                |\n| ------------------------------- | ------------------ |\n| `singletonObjects`              |                    |\n| `earlySingletonObjects`         |                    |\n| `singletonFactories`            | lambda(service1)   |\n| `singletonsCurrentlyInCreation` | service1, service2 |\n| `earlyProxyReferences`          |                    |\n\n#### 3.4 `AbstractAutowireCapableBeanFactory#doCreateBean`\n\nһȡ `service1` ̻һ£˵£\n\n1. 󴴽ᴴ `service2`ɺĶ£\n\n   ![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-393811afa91cc1ff696b02ce6d683c97640.png)\n\n   ͬģ`service2` е `service1` ҲΪ `null`\n\n2. ȡҪע뷽`service2`  `service1` ᱻҵΪԱע `@Autowired` ע⣻\n\n3.  bean ӵ `singletonFactories` Уһ֮5 ṹе£\n\n   | ṹ                            |                                |\n      | ------------------------------- | ---------------------------------- |\n   | `singletonObjects`              |                                    |\n   | `earlySingletonObjects`         |                                    |\n   | `singletonFactories`            | lambda(service1), lambda(service2) |\n   | `singletonsCurrentlyInCreation` | service1, service2                 |\n   | `earlyProxyReferences`          |                                    |\n\n   һĹؼڣ`service2` ӵˣ\n\n4. ע룬ڵ 2 Уspring ҵ `service2` Ҫע `service1`ٵ `getBean(\"service2\")` ֻص `AbstractBeanFactory#getBean(String)` ˡ\n\n`service2` ĻȡҲҪͣһˣΪ `service2` Ҫע `service1`Ҫʼȡ `service1` ̡\n\nСڽʱ5 ṹе£\n\n| ṹ                            |                                |\n| ------------------------------- | ---------------------------------- |\n| `singletonObjects`              |                                    |\n| `earlySingletonObjects`         |                                    |\n| `singletonFactories`            | lambda(service1), lambda(service2) |\n| `singletonsCurrentlyInCreation` | service1, service2                 |\n| `earlyProxyReferences`          |                                    |\n\n### 4\\. ε `AbstractBeanFactory#getBean(String)`ٴλȡ `service1`\n\n#### 4.1 `AbstractBeanFactory#doGetBean`\n\n`AbstractBeanFactory#doGetBean` £\n\n```\n    protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,\n            @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {\n\n        ...\n        // 1\\. ǷѳʼbeanNamesingletonsCurrentlyInCreation\n        Object sharedInstance = getSingleton(beanName);\n        ...\n}\n\n```\n\nе `Object sharedInstance = getSingleton(beanName)` ǰ`AbstractBeanFactory#doGetBean` ǰС `Object sharedInstance = getSingleton(beanName)` ˱仯 `getSingleton(beanName)` ִС\n\n#### 4.2 `DefaultSingletonBeanRegistry#getSingleton(String, boolean)`\n\n```\n/*\n * beanName: ֵΪ service1\n * allowEarlyReferenceֵΪ true\n */\nprotected Object getSingleton(String beanName, boolean allowEarlyReference) {\n    // 1\\. һлȡǻȡ\n    Object singletonObject = this.singletonObjects.get(beanName);\n    // 2\\. isSingletonCurrentlyInCreation(...) жservice1ǷڴУ true\n    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {\n            synchronized (this.singletonObjects) {\n                // 3\\. ӶлȡҲȡ\n                singletonObject = this.earlySingletonObjects.get(beanName);\n                // 4\\. allowEarlyReferencetrueִ\n                if (singletonObject == null && allowEarlyReference) {\n                    // 5\\. лȡܻȡ\n                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);\n                    if (singletonFactory != null) {\n                        // 6\\.  singletonFactory.getObject()\n                        // յõ AbstractAutowireCapableBeanFactory.getEarlyBeanReference \n                        singletonObject = singletonFactory.getObject();\n                        // 7\\. \n                        // ŵ\n                        this.earlySingletonObjects.put(beanName, singletonObject);\n                        // \n                        this.singletonFactories.remove(beanName);\n                    }\n                }\n            }\n    }\n    return singletonObject;\n}\n\n```\n\nڷǰʱ 5 ṹеݣ\n\n| ṹ                            |                                |\n| ------------------------------- | ---------------------------------- |\n| `singletonObjects`              |                                    |\n| `earlySingletonObjects`         |                                    |\n| `singletonFactories`            | lambda(service1), lambda(service2) |\n| `singletonsCurrentlyInCreation` | service1, service2                 |\n| `earlyProxyReferences`          |                                    |\n\nݣ£\n\n1. һлȡ 5 ṹеݣȡ `null`;\n\n2. `isSingletonCurrentlyInCreation(...)` ж `service1` ǷڴУ 5 ṹеݣ`service1`  `singletonsCurrentlyInCreation` У truẹ**һλȡ `service1` Ĳ֮ͬ**\n\n3. Ӷлȡ 5 ṹеݣ`service1`  `earlySingletonObjects`Ȼ `null`\n\n4. `allowEarlyReference` ǴĲΪ `true`ִ Ĵ룻\n\n5. Ӵлȡ 5 ṹеݣ`service1`  `singletonFactories` УܻȡصĽ£\n\n   ![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-57677e20855580e5bc2f5163d87ecfda7d2.png)\n\n6.  `singletonFactory.getObject()` Ǵ `singletonFactories` ĻǸ lambda ʽ`() -> getEarlyBeanReference(beanName, mbd, bean)` `singletonFactory.getObject()` յõľ `AbstractAutowireCapableBeanFactory#getEarlyBeanReference`\n\n   ```\n   protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {\n       Object exposedObject = bean;\n       if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {\n           for (BeanPostProcessor bp : getBeanPostProcessors()) {\n               if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {\n                   // úôaop\n                   SmartInstantiationAwareBeanPostProcessor ibp = \n                         (SmartInstantiationAwareBeanPostProcessor) bp;\n                   //  `AbstractAutoProxyCreator#getEarlyBeanReference`ǰ aop\n                   exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);\n               }\n           }\n       }\n       return exposedObject;\n   }\n   \n   ```\n\n   Ǹ `AbstractAutoProxyCreator#getEarlyBeanReference` \n\n   ```\n   public Object getEarlyBeanReference(Object bean, String beanName) {\n       Object cacheKey = getCacheKey(bean.getClass(), beanName);\n       this.earlyProxyReferences.put(cacheKey, bean);\n       // ɴ\n       return wrapIfNecessary(bean, beanName, cacheKey);\n   }\n   \n   ```\n\n    aop ̣ɲο [spring aop ֮ AnnotationAwareAspectJAutoProxyCreator £](https://my.oschina.net/funcy/blog/4687961)Ͳٷˡִ `AbstractAutoProxyCreator#getEarlyBeanReference` ֮5 ṹе£\n\n   | ṹ                            |                                |\n      | ------------------------------- | ---------------------------------- |\n   | `singletonObjects`              |                                    |\n   | `earlySingletonObjects`         |                                    |\n   | `singletonFactories`            | lambda(service1), lambda(service2) |\n   | `singletonsCurrentlyInCreation` | service1, service2                 |\n   | `earlyProxyReferences`          | service1                           |\n\n   ٻص `getSingleton` ִ `singletonFactory.getObject()` 󣬵õ `singletonObject` Ϊ\n\n   ![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-cf72a8ff3d91c3d2a4e300741ba7f0268e6.png)\n\n   һ󣬲 `service2` Ϊ `null`\n\n7. ǴˣһЩ map  put  remove Ͳ˵ˡ\n\n`DefaultSingletonBeanRegistry#getSingleton(String, boolean)` ִɺ5 ṹе£\n\n| ṹ                            |                |\n| ------------------------------- | ------------------ |\n| `singletonObjects`              |                    |\n| `earlySingletonObjects`         | service1           |\n| `singletonFactories`            | lambda(service2)   |\n| `singletonsCurrentlyInCreation` | service1, service2 |\n| `earlyProxyReferences`          | service1           |\n\n#### 4.3 ٴλص `AbstractBeanFactory#doGetBean`\n\nִ `DefaultSingletonBeanRegistry#getSingleton(String, boolean)` ٻص `AbstractBeanFactory#doGetBean`\n\n```\nprotected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,\n        @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {\n    ...\n\n    // Ƿѳʼ\n    Object sharedInstance = getSingleton(beanName);\n\n    // 1\\. ܻȡif Ĵִ\n    if (sharedInstance != null && args == null) {\n        // 2\\. ͨBean Ļֱӷأ FactoryBean ĻǸʵ\n        // beanservice1Ĵ\n        bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);\n    } else {\n        // Ĵ벻ִеͲ\n        ...\n    }\n\n    // beanתﷵfalseִе\n    if (requiredType != null && !requiredType.isInstance(bean)) {\n    ...\n    }\n\n    // 3\\. ش`getSingleton(beanName)`õĶ\n    return bean;\n\n}\n\n```\n\n1. ܻȡif ĴִУ\n\n2. `service1`  `FactoryBean` `getSingleton(beanName)` õĶͬһ£\n\n   ![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-0cd9c129bf2387d85a7bee4f4e135143f63.png)\n\n3. ش `getSingleton(beanName)` õĶ `getSingleton(beanName)` зصĶΪպ󣬾Ͳִ bean Ĵˣշصǵ 2 еõ bean ˡ\n\nڻȡ `service1` Ĵ󣬾ܴӦԭʼûע룬ȻԽк̣ȡ `service1` Ĵ`service2` ͿԽע룬ˡ\n\nִ֮5 ṹеݣ\n\n| ṹ                            |                             |\n| ------------------------------- | ------------------------------- |\n| `singletonObjects`              |                                 |\n| `earlySingletonObjects`         | service1$$EnhancerBySpringCGLIB |\n| `singletonFactories`            | lambda(service2)                |\n| `singletonsCurrentlyInCreation` | service1, service2              |\n| `earlyProxyReferences`          | service1                        |\n\nȡ `service1` 󣬽ͼ `service2` Ļȡˡ\n\n### 5  `service2` Ļȡ\n\nڵ 3 УΪ `service2` Ҫע `service1`˵ 4 ֵٴλȡ `service1` ̣Ҳɹػȡ `service1` Ĵ `service1$$EnhancerBySpringCGLIB` `service2` ע롣\n\n  3  ̣ص `AbstractAutowireCapableBeanFactory#doCreateBean` `service2` ̡\n\n#### 5.1 ص `AbstractAutowireCapableBeanFactory#doCreateBean`\n\n£\n\n```\nprotected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, \n            final @Nullable Object[] args) throws BeanCreationException {\n\n        // Ĵ 3.2 ѾˣͲٷ\n        ...\n\n        Object exposedObject = bean;\n        try {\n            // 1\\. װ, Ҳǳ˵ע\n            populateBean(beanName, mbd, instanceWrapper);\n            // 2\\. ʼᴦ aop \n            exposedObject = initializeBean(beanName, exposedObject, mbd);\n        }\n        catch (Throwable ex) {\n            ...\n        }\n\n        //ͬģѭ\n        if (earlySingletonExposure) {\n            // 3\\. ӻлȡ beanName Ӧbean\n            Object earlySingletonReference = getSingleton(beanName, false);\n            // 4\\. earlySingletonReference Ϊ nullif ݲִ\n            if (earlySingletonReference != null) {\n                ...\n            }\n        }\n\n        // ʡԲҪĴ\n        ...\n\n        return exposedObject;\n    }\n\n```\n\n˵£\n\n1. ע룬ﴥ˵ 4 ִֵ̣õ `service2` £\n\n   ![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-b294a02be5431c0aa66e5f126a6c6abbb92.png)\n\n   Կʱע뵽 `service2` е `service1` Ǵˣ\n\n2. ʼᴦ aop ִ aop ķΪ `AbstractAutoProxyCreator#postProcessAfterInitialization`£\n\n   ```\n   // beanΪservice2beanNameΪservice2\n   public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {\n       if (bean != null) {\n           Object cacheKey = getCacheKey(bean.getClass(), beanName);\n           // 1\\. earlyProxyReferences вû service2ifĴִе\n           if (this.earlyProxyReferences.remove(cacheKey) != bean) {\n               // 2\\. ﴦaop\n               return wrapIfNecessary(bean, beanName, cacheKey);\n           }\n       }\n       return bean;\n   }\n   \n   ```\n\n   ϴȴ `earlyProxyReferences` Ƴ `service2`Ȼ봫 bean Ƚϣ 5 ṹеݣǷ `service2`  `earlyProxyReferences` У\n\n   | ṹ                            |                             |\n      | ------------------------------- | ------------------------------- |\n   | `singletonObjects`              |                                 |\n   | `earlySingletonObjects`         | service1$$EnhancerBySpringCGLIB |\n   | `singletonFactories`            | lambda(service2)                |\n   | `singletonsCurrentlyInCreation` | service1, service2              |\n   | `earlyProxyReferences`          | service1                        |\n\n   ˣﷵ `null`Ȼڴ bean if еĴִУ`service2`  aopִһ󣬵õ `exposedObject` Ϊ\n\n   ![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-d62ebdbb71b76ada963a01c9b8c84b88b00.png)\n\n   `service2` Ҳ˴ע롣\n\n3. ӻлȡ beanName Ӧ beanִе `DefaultSingletonBeanRegistry#getSingleton(String, boolean)`£\n\n   ```\n   /**\n    * beanName: service2\n    * allowEarlyReferencefalse\n    */\n   protected Object getSingleton(String beanName, boolean allowEarlyReference) {\n       // 1\\. һлȡ service2ȡnull\n       Object singletonObject = this.singletonObjects.get(beanName);\n       // 2\\. ж service2 ǷڴУtrueִifеĴ\n       if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {\n           synchronized (this.singletonObjects) {\n               // 3\\. Ӷлȡ service2ȡnull\n               singletonObject = this.earlySingletonObjects.get(beanName);\n               // 4\\. ʱ allowEarlyReference ΪfalseifĴ벻ִ\n               if (singletonObject == null && allowEarlyReference) {\n                   ...\n               }\n           }\n       }\n       return singletonObject;\n   }\n   \n   ```\n\n   ǻ һ 5 ṹеݣִоһĿȻˣ\n\n   | ṹ                            |                             |\n      | ------------------------------- | ------------------------------- |\n   | `singletonObjects`              |                                 |\n   | `earlySingletonObjects`         | service1$$EnhancerBySpringCGLIB |\n   | `singletonFactories`            | lambda(service2)                |\n   | `singletonsCurrentlyInCreation` | service1, service2              |\n   | `earlyProxyReferences`          | service1                        |\n\n   ִУڴѾע͵úˣͶ˵ˣ\n\n4.  3 ص `earlySingletonReference` Ϊ nullif ݲִС\n\nõ `service2` ͷˡ\n\n#### 5.2 ص `DefaultSingletonBeanRegistry#getSingleton(String, ObjectFactory<?>)`\n\nõ `service2`  bean ǻص `DefaultSingletonBeanRegistry#getSingleton(String, ObjectFactory<?>)` `service2` ̣£\n\n```\npublic Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {\n    Assert.notNull(beanName, \"Bean name must not be null\");\n    synchronized (this.singletonObjects) {\n        ...\n            try {\n                // 1\\. beanĴ\n                singletonObject = singletonFactory.getObject();\n                newSingleton = true;\n            }\n            catch (...) {\n                ...\n            }\n            finally {\n                ...\n                // 2\\. ɺһЩ\n                //  service2  singletonsCurrentlyInCreation Ƴ\n                afterSingletonCreation(beanName);\n            }\n            if (newSingleton) {\n                // 3\\. öӵ beanFactory Уɾ\n                addSingleton(beanName, singletonObject);\n            }\n        ...\n        return singletonObject;\n    }\n}\n\n```\n\n˵£\n\n1. ǧգ`singletonFactory.getObject()` ִˣ֮ջǵõ `service2` Ĵ\n\n2. `service2` ɺ󣬾ͻὫ `singletonsCurrentlyInCreation` ƳȽϼ򵥣Ͳ˵ˣһõ 5 ṹ£\n\n   | ṹ                            |                             |\n      | ------------------------------- | ------------------------------- |\n   | `singletonObjects`              |                                 |\n   | `earlySingletonObjects`         | service1$$EnhancerBySpringCGLIB |\n   | `singletonFactories`            | lambda(service2)                |\n   | `singletonsCurrentlyInCreation` | service1                        |\n   | `earlyProxyReferences`          | service1                        |\n\n3. ţǻĴΪ `DefaultSingletonBeanRegistry#addSingleton`\n\n   ```\n   /*\n    * beanNameservice2\n    * singletonObjectservice2$$EnhancerBySpringCGLIB(service2Ĵ)\n    */ \n   protected void addSingleton(String beanName, Object singletonObject) {\n       synchronized (this.singletonObjects) {\n           // putremoveֻһиö\n           this.singletonObjects.put(beanName, singletonObject);\n           this.singletonFactories.remove(beanName);\n           this.earlySingletonObjects.remove(beanName);\n           this.registeredSingletons.add(beanName);\n       }\n   }\n   \n   ```\n\n   Ƚϼ򵥣Ͳ˵ˣ 5 ṹΪ\n\n   | ṹ                            |                             |\n      | ------------------------------- | ------------------------------- |\n   | `singletonObjects`              | service2$$EnhancerBySpringCGLIB |\n   | `earlySingletonObjects`         | service1$$EnhancerBySpringCGLIB |\n   | `singletonFactories`            |                                 |\n   | `singletonsCurrentlyInCreation` | service1                        |\n   | `earlyProxyReferences`          | service1                        |\n\nˣ`service2` ȡɣձ浽 `singletonObjects` е bean Ϊ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-763dad35376ed1d88086c45f01ee9c4cedd.png)\n\nڵҲ 5 ṹеݣ\n\n| ṹ                            |                             |\n| ------------------------------- | ------------------------------- |\n| `singletonObjects`              | service2$$EnhancerBySpringCGLIB |\n| `earlySingletonObjects`         | service1$$EnhancerBySpringCGLIB |\n| `singletonFactories`            |                                 |\n| `singletonsCurrentlyInCreation` | service1                        |\n| `earlyProxyReferences`          | service1                        |\n\nˣ`service2` ȡɣǼ `service1` Ļȡ̡\n\n### 6  `service1` Ļȡ\n\nڵ 2 УΪ `servic1` Ҫע `service2`˵ 3 ڻȡ `service2` ̣Ȼ־һϵеĲڵ 5 ڳɹػȡ `service2` Ĵ `service2$$EnhancerBySpringCGLIB` `service2` ע롣\n\n 2 ڣص `AbstractAutowireCapableBeanFactory#doCreateBean`  `service1` ̡\n\n#### 6.1 ص `AbstractAutowireCapableBeanFactory#doCreateBean`\n\n£\n\n```\nprotected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, \n            final @Nullable Object[] args) throws BeanCreationException {\n\n        // Ĵ 3.1 ѾˣͲٷ\n        ...\n\n        Object exposedObject = bean;\n        try {\n            // 1\\. װ, Ҳǳ˵ע\n            populateBean(beanName, mbd, instanceWrapper);\n            // 2\\. ʼᴦ aop \n            exposedObject = initializeBean(beanName, exposedObject, mbd);\n        }\n        catch (Throwable ex) {\n            ...\n        }\n\n        //ͬģѭ\n        if (earlySingletonExposure) {\n            // 3\\. ӻлȡ beanName Ӧbean\n            Object earlySingletonReference = getSingleton(beanName, false);\n            // 4\\. earlySingletonReference Ϊ nullif ݻִ\n            if (earlySingletonReference != null) {\n                if (exposedObject == bean) {\n                    // 5\\. صĶֵ exposedObjectضΪ\n                    exposedObject = earlySingletonReference;\n                }\n            }\n        }\n\n        // ʡԲҪĴ\n        ...\n\n        return exposedObject;\n    }\n\n```\n\n˵£\n\n1. ע룬ﴥ˵ 3 ڵִ̣õ `service1` £\n\n   ![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-3883285be5904e056bc517d35bfd44c47a0.png)\n\n   Կ`service1` Ѿע룬Ҵʱע뵽 `service1` е `service2` ѾǴˣ `service1` ʱԭʼǼ¿\n\n2. ʼᴦ aop ִ aop ķΪ `AbstractAutoProxyCreator#postProcessAfterInitialization`£\n\n   ```\n   // beanΪservice1beanNameΪservice1\n   public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {\n       if (bean != null) {\n           Object cacheKey = getCacheKey(bean.getClass(), beanName);\n           // 1\\. earlyProxyReferences  service1ifĴ벻ִе\n           if (this.earlyProxyReferences.remove(cacheKey) != bean) {\n               // 2\\. ﴦaopservice1 Ѿִйaop\n               return wrapIfNecessary(bean, beanName, cacheKey);\n           }\n       }\n       return bean;\n   }\n   \n   ```\n\n   ϴȴ `earlyProxyReferences` Ƴ `service1`Ȼ봫 bean Ƚϣ 5 ṹеݣǷ `service1` ʱ `earlyProxyReferences` У\n\n   | ṹ                            |                             |\n      | ------------------------------- | ------------------------------- |\n   | `singletonObjects`              | service2$$EnhancerBySpringCGLIB |\n   | `earlySingletonObjects`         | service1$$EnhancerBySpringCGLIB |\n   | `singletonFactories`            |                                 |\n   | `singletonsCurrentlyInCreation` | service1                        |\n   | `earlyProxyReferences`          | service1                        |\n\n   ˣ᷵ `service1`if еĴ벻ִСִһ5 ṹеΪ\n\n   | ṹ                            |                             |\n      | ------------------------------- | ------------------------------- |\n   | `singletonObjects`              | service2$$EnhancerBySpringCGLIB |\n   | `earlySingletonObjects`         | service1$$EnhancerBySpringCGLIB |\n   | `singletonFactories`            |                                 |\n   | `singletonsCurrentlyInCreation` | service1                        |\n   | `earlyProxyReferences`          |                                 |\n\n3. ӻлȡ beanName Ӧ beanִе `DefaultSingletonBeanRegistry#getSingleton(String, boolean)`£\n\n   ```\n   /**\n    * beanName: service1\n    * allowEarlyReferencefalse\n    */\n   protected Object getSingleton(String beanName, boolean allowEarlyReference) {\n       // 1\\. һлȡ service1ȡnull\n       Object singletonObject = this.singletonObjects.get(beanName);\n       // 2\\. ж service1 ǷڴУtrueִifеĴ\n       if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {\n           synchronized (this.singletonObjects) {\n               // 3\\. Ӷлȡ service1ܻȡ\n               singletonObject = this.earlySingletonObjects.get(beanName);\n               // 4\\. singletonObject ΪnullifĴ벻ִ\n               if (singletonObject == null && allowEarlyReference) {\n                   ...\n               }\n           }\n       }\n       return singletonObject;\n   }\n   \n   ```\n\n   ǿ һ 5 ṹеݣִоһĿȻˣ\n\n   | ṹ                            |                             |\n      | ------------------------------- | ------------------------------- |\n   | `singletonObjects`              | service2$$EnhancerBySpringCGLIB |\n   | `earlySingletonObjects`         | service1$$EnhancerBySpringCGLIB |\n   | `singletonFactories`            |                                 |\n   | `singletonsCurrentlyInCreation` | service1                        |\n   | `earlyProxyReferences`          |                                 |\n\n   ִУڴѾע͵úˣͶ˵ˣһ `service1` Ĵ󷵻أ\n\n4. һõ `earlySingletonReference` Ϊ\n\n   ![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-425636328820897c75890f01614d94fe9dd.png)\n\n   ѾˣԼִ if Ĵˣ\n\n5. һǸֵصĶֵ `exposedObject`Ȼ󷵻 `exposedObject`շصĶΪ `service1` Ĵ\n\nһ`service1` ĴҲȡˡ\n\n#### 6.2 ص `DefaultSingletonBeanRegistry#getSingleton(String, ObjectFactory<?>)`\n\nһĲһЩ棬Ͳٷˣ 5 ṹе£\n\n| ṹ                            |                                                          |\n| ------------------------------- | ------------------------------------------------------------ |\n| `singletonObjects`              | service2<nobr aria-hidden=\"true\">EnhancerBySpringCGLIB,service1</nobr><math xmlns=\"http://www.w3.org/1998/Math/MathML\" display=\"block\"><mi>?</mi><mi>?</mi><mi>?</mi><mi>?</mi><mi>?</mi><mi>?</mi><mi>?</mi><mi>?</mi><mi>?</mi><mi>?</mi><mi>?</mi><mi>?</mi><mi>?</mi><mi>?</mi><mi>?</mi><mi>?</mi><mi>?</mi><mi>?</mi><mi>?</mi><mi>?</mi><mi>?</mi><mo>,</mo><mi>?</mi><mi>?</mi><mi>?</mi><mi>?</mi><mi>?</mi><mi>?</mi><mi>?</mi><mn>1</mn></math><mi>E</mi><mi>n</mi><mi>h</mi><mi>a</mi><mi>n</mi><mi>c</mi><mi>e</mi><mi>r</mi><mi>B</mi><mi>y</mi><mi>S</mi><mi>p</mi><mi>r</mi><mi>i</mi><mi>n</mi><mi>g</mi><mi>C</mi><mi>G</mi><mi>L</mi><mi>I</mi><mi>B</mi><mo>,</mo><mi>s</mi><mi>e</mi><mi>r</mi><mi>v</mi><mi>i</mi><mi>c</mi><mi>e</mi><mn>1</mn></math>\" role=\"presentation\">EnhancerBySpringCGLIB |\n| `earlySingletonObjects`         |                                                              |\n| `singletonFactories`            |                                                              |\n| `singletonsCurrentlyInCreation` |                                                              |\n| `earlyProxyReferences`          |                                                              |\n\nͨԣ鿴õ `singletonObjects` ж£\n\n1. service1\n\n   ![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-b6ba35a7a701056ae55a8f9b2ef9345cac5.png)\n\n2. service2\n\n   ![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-563d6e323ef8fc5e9fd02f7a938ddbac655.png)\n\nԿ`singletonObjects` ߶Ǵ󣬱˴עģҲǴѭõ˽\n\n### 4\\. ܽ\n\nspring ѭķͽˣѭнĺ**ǰ aop**spring һ㣬 5 ṹ洢ҪϢһѭ⡣\n\n* * *\n\n_ԭӣ[https://my.oschina.net/funcy/blog/4815992](https://my.oschina.net/funcy/blog/4815992) ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע_"
  },
  {
    "path": "docs/Spring全家桶/Spring源码分析/Spring重要机制探秘/Spring探秘之监听器注解EventListener.md",
    "content": " `BeanFactoryPostProcessor` ʱ˴¼һִʽʹ `@EventListener` ע⣬ `EventListenerMethodProcessor`ȴһʾ𲽷 `@EventListener` Ĵ̡\n\n### 1. `@EventListener` ʹʾ\n\nȶһ¼\n\n```\npublic class MyApplicationEvent extends ApplicationEvent {\n\n    private static final long serialVersionUID = -1L;\n\n    public MyApplicationEvent(Object source) {\n        super(source);\n    }\n}\n\n```\n\n׼һ¼ʹ `@EventListener` ָ\n\n```\n@Configuration\npublic class Demo08Config {\n\n    /**\n     * Ǹ¼\n     */\n    @EventListener(MyApplicationEvent.class)\n    public void listener(MyApplicationEvent event) {\n        System.out.println(\"@EventListener¼\"\n                + Thread.currentThread().getName() + \" | \" + event.getSource());\n    }\n}\n\n```\n\nȻ󷢲¼\n\n```\n@ComponentScan\npublic class Demo08Main {\n\n    public static void main(String[] args) {\n        ApplicationContext context = new AnnotationConfigApplicationContext(Demo08Config.class);\n        // ¼\n        context.publishEvent(new MyApplicationEvent(\n            Thread.currentThread().getName() + \" | Զ¼ ...\"));\n    }\n}\n\n```\n\nУ£\n\n```\n@EventListener¼main | main | Զ¼ ...\n\n```\n\nԿ `@EventListener` ǵķȷʵ¼ļ\n\n### 2. `@EventListener` \n\nʹ `@EventListener` ʵ `ApplicationListener` ʵ¼˴Ŀ `@EventListener` ΪЩʲô\n\n`@EventListener` Ĵ£\n\n```\n@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\npublic @interface EventListener {\n\n    /**\n     * classes ı\n     * ָ¼ͬʱ¼ \n     */\n    @AliasFor(\"classes\")\n    Class<?>[] value() default {};\n\n    /**\n     * value ı\n     * ָ¼ͬʱ¼ \n     */\n    @AliasFor(\"value\")\n    Class<?>[] classes() default {};\n\n    /**\n     * ָһʱŻִ\n     * ֧spring el ʽ\n     */\n    String condition() default \"\";\n\n}\n\n```\n\nӴ`@EventListener` ṩܣ\n\n*   ָ¼ָ¼\n*   ָһʱŻִУ֧ spring EL ʽ\n\n˽ `@EventListener` ṩĹܺ󣬽 spring δעġ\n\n### 3. `@EventListener` Ĵ`EventListenerMethodProcessor`\n\n¿ƪ˵ `@EventListener` Ĺܣ `BeanFactoryPostProcessor` ʱֵģʱ `BeanFactoryPostProcessor` ʵ `EventListenerMethodProcessor` ᴦ `@EventListener` ע⣬ʹӴǶ `EventListenerMethodProcessor`  `@EventListener` ̡\n\nʶ `EventListenerMethodProcessor`\n\n```\npublic class EventListenerMethodProcessor\n        implements SmartInitializingSingleton, ApplicationContextAware, BeanFactoryPostProcessor {\n    ...\n}\n\n```\n\nҪʵӿڣ\n\n*   `BeanFactoryPostProcessor` `BeanFactoryPostProcessor` Զƻ `BeanFactory` һЩΪ\n*   `SmartInitializingSingleton` bean ĳʼִʱ `bean` ʼ֮\n\n `BeanFactoryPostProcessor#postProcessBeanFactory` ʵ֣\n\n```\n    @Nullable\n    private List<EventListenerFactory> eventListenerFactories;\n\n    /**\n     *  BeanFactoryPostProcessor  postProcessBeanFactory(...) .\n     * ǻȡ EventListenerFactoryȻ󱣴 eventListenerFactories.\n     * spring Ĭṩ EventListenerFactory \n     *     1\\. DefaultEventListenerFactoryspring Ĭϵ\n     *     2\\. TransactionalEventListenerFactory\n     * ⲿֲûʲô\n     */\n    @Override\n    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {\n        this.beanFactory = beanFactory;\n\n        Map<String, EventListenerFactory> beans = beanFactory\n                .getBeansOfType(EventListenerFactory.class, false, false);\n        List<EventListenerFactory> factories = new ArrayList<>(beans.values());\n        // \n        AnnotationAwareOrderComparator.sort(factories);\n        this.eventListenerFactories = factories;\n    }\n\n```\n\nǱȽϼ򵥵ģֻǴлȡ `EventListenerFactory` ֵ `eventListenerFactories`\n\n`EventListenerFactory` Ĺ `ApplicationListener`ǻ spring ΰ `@EventListener` ǵķת `ApplicationListener` ġӴspring ṩ `EventListenerFactory` \n\n*   `DefaultEventListenerFactory`spring Ĭϵ\n*   `TransactionalEventListenerFactory`\n\n `SmartInitializingSingleton#afterSingletonsInstantiated()` ʵ֣\n\n```\n    /**\n     *  SmartInitializingSingleton  afterSingletonsInstantiated() .\n     * beanʼɺá\n     * УҪǽ @EventListener ķת ApplicationListener 󣬲עᵽ\n     */\n    @Override\n    public void afterSingletonsInstantiated() {\n        ConfigurableListableBeanFactory beanFactory = this.beanFactory;\n        Assert.state(this.beanFactory != null, \"No ConfigurableListableBeanFactory set\");\n        String[] beanNames = beanFactory.getBeanNamesForType(Object.class);\n        for (String beanName : beanNames) {\n            if (!ScopedProxyUtils.isScopedTarget(beanName)) {\n                Class<?> type = null;\n                try {\n                    // ȡ aop ӦĿ࣬ beanName Ӧ BeanDefinition лȡ.\n                    // ȡʾǲǴʹ beanFactory.getType(beanName) ȡ\n                    type = AutoProxyUtils.determineTargetClass(beanFactory, beanName);\n                }\n                catch (Throwable ex) {\n                    ...\n                }\n                if (type != null) {\n                    if (ScopedObject.class.isAssignableFrom(type)) {\n                        try {\n                            Class<?> targetClass = AutoProxyUtils.determineTargetClass(\n                                    beanFactory, ScopedProxyUtils.getTargetBeanName(beanName));\n                            if (targetClass != null) {\n                                type = targetClass;\n                            }\n                        }\n                        catch (Throwable ex) {\n                            ...\n                        }\n                    }\n                    try {\n                        // Ĵ\n                        processBean(beanName, type);\n                    }\n                    catch (Throwable ex) {\n                        ...\n                    }\n                }\n            }\n        }\n    }\n\n```\n\nִ£\n\n1.  ȡǰ `beanFactory` е `bean`õ `beanFactory.getBeanNamesForType(Object.class)` ע⴫ `class`  `Object`óе `bean`\n2.  Щ `bean`ÿһ `bean`Ǵ `AutoProxyUtils.determineTargetClass(beanFactory, beanName)` ȡĿ֪࣬עǲܼ̳еģҪȡ `@EventListener` ǵķҪĿȥȡǴĿ `bean` Ӧࣻ\n3.   `processBean(beanName, type)` һ.\n\nؼ `EventListenerMethodProcessor#processBean` ˣ\n\n```\n/**\n *  bean Ὣ @EventListener עǵķתΪ ApplicationListener 󣬲עᵽ.\n * @param beanName\n * @param targetType\n */\nprivate void processBean(final String beanName, final Class<?> targetType) {\n    if (!this.nonAnnotatedClasses.contains(targetType) &&\n            AnnotationUtils.isCandidateClass(targetType, EventListener.class) &&\n            !isSpringContainerClass(targetType)) {\n        Map<Method, EventListener> annotatedMethods = null;\n\n        try {\n            // 1\\. ҵ @EventListener ķ\n            annotatedMethods = MethodIntrospector.selectMethods(targetType,\n                (MethodIntrospector.MetadataLookup<EventListener>) method ->\n                    AnnotatedElementUtils.findMergedAnnotation(method, EventListener.class));\n        }\n        catch (Throwable ex) {\n            ...\n        }\n\n        if (CollectionUtils.isEmpty(annotatedMethods)) {\n            this.nonAnnotatedClasses.add(targetType);\n        }\n        else {\n            ConfigurableApplicationContext context = this.applicationContext;\n            Assert.state(context != null, \"No ApplicationContext set\");\n            List<EventListenerFactory> factories = this.eventListenerFactories;\n            Assert.state(factories != null, \"EventListenerFactory List not initialized\");\n            // 2\\. ʹ EventListenerFactory  ApplicationListener \n            for (Method method : annotatedMethods.keySet()) {\n                for (EventListenerFactory factory : factories) {\n                    // жϵǰ EventListenerFactory Ƿֵ֧ǰ\n                    if (factory.supportsMethod(method)) {\n                        // Ǵõķ\n                        Method methodToUse = AopUtils.selectInvocableMethod(\n                                method, context.getType(beanName));\n                        //  ApplicationListenerǴķ\n                        ApplicationListener<?> applicationListener = factory\n                                .createApplicationListener(beanName, targetType, methodToUse);\n                        // ʼһҪǶжֵthis.evaluator\n                        if (applicationListener instanceof ApplicationListenerMethodAdapter) {\n                            ((ApplicationListenerMethodAdapter) applicationListener)\n                                    .init(context, this.evaluator);\n                        }\n                        // ӵУ Listener SetԶȥ\n                        context.addApplicationListener(applicationListener);\n                        // һ EventListenerFactory Ͳִˣ\n                        // ʱͻᷢ factories ˳Ҫ\n                        //  EventListenerFactory ָ˳\n                        break;\n                    }\n                }\n            }\n        }\n    }\n}\n\n```\n\n `processBean(...)` Ĺؼעͣܽ´̣\n\n1.  ҵ `@EventListener` ķʹõ `MethodIntrospector#selectMethods(...)` вң鵽ǰķ丸ķԼӿڵĬϷһֱ Object ΪֹЩ `@EventListener`ᱻҵջҵзŵһ Map У\n2.  õ mapÿתΪ `ApplicationListener` ӵ `applicationContext`  `ApplicationListener` У£\n    1.  ǰõ `EventListenerFactory`\n    2.  ǰ `EventListenerFactory` Ƿָ֧÷֧һ֧򲻴\n    3.  ڴҵǰĴִеҲǴ\n    4.   `ApplicationListener` 󣬹췽Ĳᴫ `method`ڴ `method` Ǵ `method`\n    5.   `ApplicationListenerMethodAdapter` ʵгʼҪǸֵthis.evaluator\n    6.  õ `ApplicationListener` ӵУ `Listener` һ `Set`Զȥء\n\n `@EventListener` ǵķ֮ܶ¼мΪ spring ÷װһ `ApplicationListener` ӵˣ֮͸ʵ `ApplicationListener` ӿڵļһܶ¼мˡ\n\n### 5. `ApplicationListener` \n\nǰ `@EventListener` Ĵ̣ڽ `ApplicationListener` ɣӦĴΪ\n\n```\n// EventListenerMethodProcessor#processBean\nApplicationListener<?> applicationListener = factory\n        .createApplicationListener(beanName, targetType, methodToUse);\n\n```\n\nǰǽܹ spring ṩ `EventListenerFactory` \n\n*   `DefaultEventListenerFactory`spring Ĭϵ\n*   `TransactionalEventListenerFactory`\n\nǾ `EventListenerFactory`\n\n#### 5.1 `DefaultEventListenerFactory`\n\n```\npublic class DefaultEventListenerFactory implements EventListenerFactory, Ordered {\n    ...\n\n    /**\n     * Ĭϵʵ֣֧еķ\n     */\n    @Override\n    public boolean supportsMethod(Method method) {\n        return true;\n    }\n\n    /**\n     * \n     */ \n    @Override\n    public ApplicationListener<?> createApplicationListener(String beanName, \n            Class<?> type, Method method) {\n        return new ApplicationListenerMethodAdapter(beanName, type, method);\n    }\n}\n\n```\n\nϴ˵£\n\n1.  `DefaultEventListenerFactory`  spring ṩĬʵ֣֧б `@EventListener` ķ\n2.   `ApplicationListener` ķ `createApplicationListener`õ `ApplicationListenerMethodAdapter` Ĺ췽\n\nǼ `ApplicationListenerMethodAdapter`\n\n```\npublic class ApplicationListenerMethodAdapter implements GenericApplicationListener {\n\n    ...\n\n    public ApplicationListenerMethodAdapter(String beanName, Class<?> targetClass, Method method) {\n        this.beanName = beanName;\n        // \n        this.method = BridgeMethodResolver.findBridgedMethod(method);\n        this.targetMethod = (!Proxy.isProxyClass(targetClass) ?\n                AopUtils.getMostSpecificMethod(method, targetClass) : this.method);\n        this.methodKey = new AnnotatedElementKey(this.targetMethod, targetClass);\n\n        EventListener ann = AnnotatedElementUtils\n                .findMergedAnnotation(this.targetMethod, EventListener.class);\n        // ֵ֧¼\n        this.declaredEventTypes = resolveDeclaredEventTypes(method, ann);\n        // עָ\n        this.condition = (ann != null ? ann.condition() : null);\n        this.order = resolveOrder(this.targetMethod);\n    }\n\n    ...\n}\n\n```\n\n`ApplicationListenerMethodAdapter` Ĺ췽һѵĸֵصע¼Ĵ\n\n```\npublic class ApplicationListenerMethodAdapter implements GenericApplicationListener {\n\n    ...\n\n    /**\n     * ܼ¼\n     */\n    private static List<ResolvableType> resolveDeclaredEventTypes(Method method, \n            @Nullable EventListener ann) {\n        // ֻһ¼ @EventListener ָ\n        int count = method.getParameterCount();\n        if (count > 1) {\n            throw new IllegalStateException(\n                    \"Maximum one parameter is allowed for event listener method: \" + method);\n        }\n\n        if (ann != null) {\n            // ¼ָȡ classes ֵ\n            Class<?>[] classes = ann.classes();\n            if (classes.length > 0) {\n                List<ResolvableType> types = new ArrayList<>(classes.length);\n                for (Class<?> eventType : classes) {\n                    types.add(ResolvableType.forClass(eventType));\n                }\n                return types;\n            }\n        }\n\n        if (count == 0) {\n            throw new IllegalStateException(\n                    \"Event parameter is mandatory for event listener method: \" + method);\n        }\n        return Collections.singletonList(ResolvableType.forMethodParameter(method, 0));\n    }\n}\n\n```\n\nӴԵó½ۣ\n\n1.   `@EventListener` ǵķֻһ\n2.  ܼ¼ж `@EventListener` ָ\n\n#### 5.2 `TransactionalEventListenerFactory`\n\n `TransactionalEventListenerFactory`\n\n```\npublic class TransactionalEventListenerFactory implements EventListenerFactory, Ordered {\n\n    ...\n\n    /**\n     * ֱֻ֧ @TransactionalEventListener עķ\n     */\n    @Override\n    public boolean supportsMethod(Method method) {\n        return AnnotatedElementUtils.hasAnnotation(method, TransactionalEventListener.class);\n    }\n\n    /**\n     * Ķ ApplicationListenerMethodTransactionalAdapter\n     */\n    @Override\n    public ApplicationListener<?> createApplicationListener(String beanName, \n            Class<?> type, Method method) {\n        return new ApplicationListenerMethodTransactionalAdapter(beanName, type, method);\n    }\n}\n\n```\n\nӴ`TransactionalEventListenerFactory`  `DefaultEventListenerFactory` £\n\n*   `ApplicationListenerMethodTransactionalAdapter` ֱֻ֧ `@TransactionalEventListener` ķ\n*    `ApplicationListener` ʵΪ `ApplicationListenerMethodTransactionalAdapter`\n\n `@TransactionalEventListener` Ǹɶ\n\n```\n@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\n/**\n *  @EventListener ע\n */\n@EventListener\npublic @interface TransactionalEventListener {\n    /**\n     * \n     */\n    TransactionPhase phase() default TransactionPhase.AFTER_COMMIT;\n\n    /**\n     * \n     */\n    boolean fallbackExecution() default false;\n\n    /**\n     * ָ¼\n     */\n    @AliasFor(annotation = EventListener.class, attribute = \"classes\")\n    Class<?>[] value() default {};\n\n    /**\n     * ָ¼\n     */\n    @AliasFor(annotation = EventListener.class, attribute = \"classes\")\n    Class<?>[] classes() default {};\n\n    /**\n     * ָ\n     */\n    String condition() default \"\";\n}\n\n```\n\nԿע `@EventListener`˾ `@EventListener` ͬĹܡ⣬ `@EventListener` ȣԣ`phase()`  `fallbackExecution()`ġ\n\n˽ `TransactionalEventListener` ֮ `ApplicationListenerMethodTransactionalAdapter`\n\n```\n/**\n * ̳ ApplicationListenerMethodAdapter\n */\nclass ApplicationListenerMethodTransactionalAdapter extends ApplicationListenerMethodAdapter {\n    ...\n\n    private final TransactionalEventListener annotation;\n\n    public ApplicationListenerMethodTransactionalAdapter(String beanName, \n            Class<?> targetClass, Method method) {\n        // øķ\n        super(beanName, targetClass, method);\n        // ע @TransactionalEventListener\n        TransactionalEventListener ann = AnnotatedElementUtils\n                .findMergedAnnotation(method, TransactionalEventListener.class);\n        if (ann == null) {\n            throw new IllegalStateException(...);\n        }\n        this.annotation = ann;\n    }\n\n    ...\n}\n\n```\n\nӴ`ApplicationListenerMethodTransactionalAdapter` ̳ `ApplicationListenerMethodAdapter`乹췽Ҳȵ `ApplicationListenerMethodAdapter` Ĺ췽Ȼٸ `annotation` ֵ\n\n### 6\\. ¼\n\n¼ļ\n\n#### 6.1 `ApplicationListenerMethodAdapter` ¼\n\nǰУ֪ `ApplicationListener` Ҫһ¼\n\n1.  жϵǰ `ApplicationListener` Ƿֵ֧ǰ¼\n2.  ֧֣¼\n\n `ApplicationListenerMethodAdapter` \n\n```\npublic class ApplicationListenerMethodAdapter implements GenericApplicationListener {\n\n    ...\n\n    /**\n     * ǰ listener Ƿִ֧¼.\n     * ʹõķ ResolvableType#isAssignableFrom(ResolvableType)Խ÷\n     * ΪǶ Class#isAssignableFrom ܵչ\n     */\n    @Override\n    public boolean supportsEventType(ResolvableType eventType) {\n        for (ResolvableType declaredEventType : this.declaredEventTypes) {\n            if (declaredEventType.isAssignableFrom(eventType)) {\n                return true;\n            }\n            if (PayloadApplicationEvent.class.isAssignableFrom(eventType.toClass())) {\n                ResolvableType payloadType = eventType\n                        .as(PayloadApplicationEvent.class).getGeneric();\n                if (declaredEventType.isAssignableFrom(payloadType)) {\n                    return true;\n                }\n            }\n        }\n        return eventType.hasUnresolvableGenerics();\n    }\n\n    /**\n     * ¼\n     */\n    @Override\n    public void onApplicationEvent(ApplicationEvent event) {\n        processEvent(event);\n    }\n\n    public void processEvent(ApplicationEvent event) {\n        // \n        Object[] args = resolveArguments(event);\n        // shouldHandleж EventListener.condition() ṩ\n        if (shouldHandle(event, args)) {\n            // ÷\n            Object result = doInvoke(args);\n            if (result != null) {\n                // ִнصĽ¼ȥ\n                handleResult(result);\n            }\n            else {\n                logger.trace(\"No result object given - no result to handle\");\n            }\n        }\n    }\n\n}\n\n```\n\nԿжϵǰǰ `ApplicationListener` Ƿֵ֧ǰ¼ʱʹõ `ResolvableType#isAssignableFrom(ResolvableType)`ĲԼ򵥵ؽ÷ΪǶ `Class#isAssignableFrom` ܵչ\n\n`ApplicationListenerMethodAdapter` ¼£\n\n1.   `event` תΪоֵ\n2.  ͨñ `@EventListener` ǵķ\n3.   `@EventListener` ķؽ `handleResult(...)` ԽصĽ¼ٷȥ\n\n `ApplicationListenerMethodAdapter` ¼ͷˡ\n\n#### 6.2 `ApplicationListenerMethodTransactionalAdapter` ¼\n\n`ApplicationListenerMethodTransactionalAdapter` ¼ `ApplicationListenerMethodAdapter` ҪһЩ\n\n```\n/**\n * ̳ ApplicationListenerMethodAdapter\n */\nclass ApplicationListenerMethodTransactionalAdapter extends ApplicationListenerMethodAdapter {\n\n    ...\n\n    // supportsEventType(...) ҲǼ̳ķʵ\n\n    /**\n     * ¼\n     */\n    @Override\n    public void onApplicationEvent(ApplicationEvent event) {\n        // ڴĲло\n        if (TransactionSynchronizationManager.isSynchronizationActive()\n                && TransactionSynchronizationManager.isActualTransactionActive()) {\n            TransactionSynchronization transactionSynchronization \n                    = createTransactionSynchronization(event);\n            TransactionSynchronizationManager.registerSynchronization(transactionSynchronization);\n        }\n        else if (this.annotation.fallbackExecution()) {\n            // õǸķ\n            processEvent(event);\n        }\n        else {\n            // ֻlog ӡʡ\n            ...\n        }\n    }\n\n    ...\n}\n\n```\n\nϴ˵£\n\n1.  `ApplicationListenerMethodTransactionalAdapter`  `ApplicationListenerMethodAdapter` ̳࣬ `ApplicationListenerMethodAdapter` ķ\n2.  `ApplicationListenerMethodTransactionalAdapter` ûд `supportsEventType(...)` Ҳʹ `ApplicationListenerMethodAdapter`  `supportsEventType(...)` ж¼֧\n3.  ڴ¼ʱ`ApplicationListenerMethodTransactionalAdapter` صĴĴ߼ľͲ\n\n### 7\\. ܽ\n\nҪ `@EventListener` Ĵ̣ܽ£\n\n1.  `@EventListener` ָ¼¼\n2.   `@EventListener`  `EventListenerMethodProcessor`ѱ `@EventListener` ǵķתһ `ApplicationListener` Ȼӵ `ApplicationContext` ļ\n3.  ת `ApplicationListener` Ĳ `EventListenerFactory` ṩspring ṩ `EventListenerFactory`\n    *   `DefaultEventListenerFactory`spring Ĭϵģתɵ `ApplicationListener` Ϊ `ApplicationListenerMethodAdapter`\n    *   `TransactionalEventListenerFactory`ģתɵ `ApplicationListener` Ϊ `ApplicationListenerMethodTransactionalAdapter`\n\n* * *\n\n_ԭӣ[https://my.oschina.net/funcy/blog/4926344](https://my.oschina.net/funcy/blog/4926344) ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע_"
  },
  {
    "path": "docs/Spring全家桶/Spring源码分析/Spring重要机制探秘/Spring探秘之组合注解的处理.md",
    "content": "### 1\\. ʲôע⣿\n\n spring Уһرע⣺ע⡣˵springmvc У`@Controller` ע÷·ȣ`@ResponseBody` עͼȾֱչʾнһת json أ `@RestController` ߵĹܣ÷·ͬʱҲֱչʾн£\n\n```\n@Controller\n@ResponseBody\npublic @interface RestController {\n    /**\n     * ע\n     */\n    @AliasFor(annotation = Controller.class)\n    String value() default \"\";\n\n}\n\n```\n\nԿ`@RestController` ϱע⣺`@Controller`  `@ResponseBody`ͬʱӵߵĹܡ\n\nһӣspring УڱʶһΪ spring bean ʱ򣬿õЩע⣺`@Component``@Repository``@Service` ȣٽһ룬 `@Repository``@Service` ж `@Component`\n\n```\n@Component\npublic @interface Repository {\n    @AliasFor(annotation = Component.class)\n    String value() default \"\";\n}\n\n@Component\npublic @interface Service {\n    @AliasFor(annotation = Component.class)\n    String value() default \"\";\n}\n\n```\n\nҲ˵`@Repository``@Service`  `@Component` Ĺܣ\n\nʵϣԼдһע⣬\n\n```\n@Target({ElementType.TYPE})\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\n@Component\npublic @interface MyComponent {\n\n    @AliasFor(annotation = Component.class)\n    String value() default \"\";\n\n}\n\n```\n\nȻʹã\n\n```\n@MyComponent(\"beanObj3\")\npublic class BeanObj3 {\n    ...\n}\n\n```\n\nspring Ȼ `BeanObj3` ʼΪ spring bean\n\nô spring һأʵϣspring ڴ `@MyComponent` ʱжϸעǷ `@Component` ע⣬ͻȡעãȻ `@Component` Ĵ߼д\n\nͬأspring ڴ `@RestController` ʱǰǴ `@Controller` ߼ʹ `@RestController` лȡ `@Controller` ȻдǰǴ `@ResponseBody` ߼ʹ `@RestController` лȡ `@ResponseBody` Ȼд\n\n### 2\\. ݹȡָע\n\nˣעеעҪôȡأ\n\n jdk ṩķ\n\n```\nRestController annotation = BeanObj3.class.getAnnotation(MyComponent.class);\n\n```\n\nõ `annotation` ضΪ `null`ԭ `Class#getAnnotation` ֻܻȡֱӳֵע⣬`BeanObj3` ûֱӳ `@Component` ģ˵õĽΪ null취ҲҲ뵽ˣǼ¶ȡ \"עע\"ôʾ£\n\n```\npublic class AnnotationHandler {\n\n    /**\n     * jdkṩԪע\n     */\n    private static Set<Class<?>> metaAnnotations = new HashSet<>();\n    static {\n        metaAnnotations.add(Target.class);\n        metaAnnotations.add(Documented.class);\n        metaAnnotations.add(Retention.class);\n    }\n\n    public static void main(String[] args) {\n        List<Class<?>> list = getAnnotations(BeanObj3.class);\n        System.out.println(list);\n    }\n\n    /**\n     * ȡݹ\n     */\n    public static List<Class<?>> getAnnotations(Class<?> cls) {\n        // Ÿϵע⣬עע\n        List<Class<?>> list = new ArrayList<>();\n        //  doGetAnnotations(...) ȡ\n        doGetAnnotations(list, cls);\n        return list;\n    }\n\n    /**\n     * ȡעľ\n     */\n    private static void doGetAnnotations(List<Class<?>> list, Class<?> cls) {\n        // ȡеע\n        Annotation[] annotations = cls.getAnnotations();\n        if(annotations != null && annotations.length > 0) {\n            for(Annotation annotation : annotations) {\n                // ȡע\n                Class<?> annotationType = annotation.annotationType();\n                // jdkṩԪע\n                if(metaAnnotations.contains(annotationType)) {\n                    continue;\n                }\n                // ݹ\n                doGetAnnotations(list, annotationType);\n            }\n        }\n        // ע⣬ӵ list \n        if(cls.isAnnotation()) {\n            list.add(cls);\n        }\n    }\n}\n\n```\n\nҪȡ `BeanObj3` ע⣬Ϳˣ\n\n```\n// õ BeanObj3 ϵע⣬עע⡱\nList<Class<?>> list = AnnotationHandler.getAnnotations(BeanObj3.class);\n// ж BeanObj3 עǷ @Component\nlist.contains(Component.class);\n\n```\n\n demo ǱȽϴֲڣ jdk Ԫע⣬ֻų `@Component` гֵģ `@Component` ֮ϵעȡѾ㹻ˣҲҪģûлȡעݡ spring УעⲢֻһǣԶһϵ\n\n```\n//  spring bean Ϊ beanObj3\n@MyComponent(\"beanObj3\")\npublic class BeanObj3 {\n    ...\n}\n\n```\n\n `AnnotationHandler` ܻȡעݣ\n\n spring ôעݵĶȡġ\n\n### 3\\. spring ȡעϢ\n\nspring 5.2 УעϢĶȡṩࣺ\n\n*   `AnnotationMetadataReadingVisitor`עݵĶȡ࣬ asm ʵ֣ spring5.2 Ѿ `@Deprecated`ʹ `SimpleAnnotationMetadataReadingVisitor`˱Ĳ\n*   `SimpleAnnotationMetadataReadingVisitor`עݵĶȡ࣬ asm ʵ֣spring 5.2 ࣬ `AnnotationMetadataReadingVisitor`Ҫעǣ`SimpleAnnotationMetadataReadingVisitor` ķʼĬϵģ޷ڰ֮ʣͬʱҲ `final` ģܱ̳У޷ֱӲ spring ṩһࣺ`SimpleMetadataReaderFactory`ͨͿʹ `SimpleAnnotationMetadataReadingVisitor` \n*   `StandardAnnotationMetadata`עݵĶȡ࣬ڷʵ\n\n#### 3.1 `SimpleAnnotationMetadataReadingVisitor`\n\nspring ûṩֱӲ `SimpleAnnotationMetadataReadingVisitor` ĻᣬǷװ `SimpleMetadataReaderFactory` ˣࣺ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-f2926fbc022517409bfb6f6641c87a193c2.png)\n\nԿ`SimpleMetadataReaderFactory` ҪΪ֣\n\n1.  췽\n2.  ԴĻȡ\n\nֱӿȡĻȡҲ `getMetadataReader(...)` \n\n`getMetadataReader(Resource resource)`:  `Resource` ȡ `getMetadataReader(String className)`: ȡݣȫ޶ ӴҲתΪ `Resource`Ȼ `getMetadataReader(Resource)` жȡ\n\nķֵ `MetadataReader`ǸɶأǼ¿.\n\n##### `MetadataReader`\n\n`MetadataReader` Ĳַ£\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-4a1184918664e8a4bd363d6399ed4092692.png)\n\nԿǸӿڣﷵصľ; `SimpleMetadataReader` ˣ 3 \n\n*   `getResource()`: ȡԴ\n*   `getClassMetadata()`: ȡԪ\n*   `getAnnotationMetadata()`: ȡעԪ\n\nǻȡעϢֻע `getAnnotationMetadata()` \n\n```\nAnnotationMetadata getAnnotationMetadata();\n\n```\n\nص `AnnotationMetadata`Ǹɶ\n\n##### `AnnotationMetadata`\n\nķ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-fc19d57bbba3d70da04fd32e6eef511ad4f.png)\n\nЩΪࣺ\n\n*   `getXxx(...)`עȡӦϢ\n*   `hasXxx(...)`жǷĳע\n\nһ⼸Ĭʵֶ֣ `getAnnotations()` \n\n```\npublic interface AnnotationMetadata extends ClassMetadata, AnnotatedTypeMetadata {\n\n    default Set<String> getAnnotationTypes() {\n        //  getAnnotations()\n    return getAnnotations().stream()\n        .filter(MergedAnnotation::isDirectlyPresent)\n        .map(annotation -> annotation.getType().getName())\n        .collect(Collectors.toCollection(LinkedHashSet::new));\n    }\n\n    default Set<String> getMetaAnnotationTypes(String annotationName) {\n        //  getAnnotations()\n    MergedAnnotation<?> annotation = getAnnotations().get(annotationName, \n        MergedAnnotation::isDirectlyPresent);\n    if (!annotation.isPresent()) {\n        return Collections.emptySet();\n    }\n    return MergedAnnotations.from(annotation.getType(), SearchStrategy.INHERITED_ANNOTATIONS)\n                .stream()\n        .map(mergedAnnotation -> mergedAnnotation.getType().getName())\n        .collect(Collectors.toCollection(LinkedHashSet::new));\n    }\n\n    default boolean hasAnnotation(String annotationName) {\n        //  getAnnotations()\n    return getAnnotations().isDirectlyPresent(annotationName);\n    }\n\n    ...\n}\n\n```\n\nٽһ鿴 `getAnnotations()`  `AnnotatedTypeMetadata`\n\n```\npublic interface AnnotatedTypeMetadata {\n\n    /**\n     * ȡע\n     */\n    MergedAnnotations getAnnotations();\n\n    ...\n\n}\n\n```\n\nһƺעõռ `MergedAnnotations` ˣǼ̽\n\n##### `MergedAnnotations`\n\n`MergedAnnotations` Ĳע£\n\n> Provides access to a collection of merged annotations, usually obtained from a source such as a {@link Class} or {@link Method}.\n>\n> ṩעļϵķʣЩעͨǴ Class  Method ֮Դõġ\n\n`MergedAnnotations` յעļˣļ\n\n```\n// жעǷڣеעж\n<A extends Annotation> boolean isPresent(Class<A> annotationType);\n\n// жעǷڣеעжϣķͬǣﴫַ\nboolean isPresent(String annotationType);\n\n// жֱעǷڣҲֻжϵǰûиע⣬жעע\n<A extends Annotation> boolean isDirectlyPresent(Class<A> annotationType);\n\n// ͬϣﴫַʽΪ\".\"\nboolean isDirectlyPresent(String annotationType);\n\n// ȡע\n<A extends Annotation> MergedAnnotation<A> get(Class<A> annotationType);\n\n// ȡע⣬ﴫַʽΪ\".\"\n<A extends Annotation> MergedAnnotation<A> get(String annotationType);\n\n```\n\nӷϴ¿Կ`MergedAnnotations` עļϣṩעжĳעǷڣҲԻȡеĳע⡣\n\n##### `MergedAnnotation`\n\n`MergedAnnotations` עļϣзŵɶأ `get(...)` ŵ `MergedAnnotation` `MergedAnnotation` ֵ֧ķ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-a3a749f4546d63b49bfd25d2fc2c73dcb6f.png)\n\nϵķԿ`MergedAnnotation` עݳṩ˷ḻ api ȡעݡ\n\n##### ʹʾ\n\nʾ\n\n```\n// õ SimpleMetadataReaderFactory ʵյõ SimpleAnnotationMetadataReadingVisitor ȡ\nSimpleMetadataReaderFactory readerFactory = new SimpleMetadataReaderFactory();\nMetadataReader metadataReader = readerFactory.getMetadataReader(BeanObj3.class.getName());\nAnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();\n\n// AnnotationMetadata ṩĲصעעص\nSet<String> annotationTypes = annotationMetadata.getAnnotationTypes();\nSystem.out.println(\"-------------\");\nannotationTypes.forEach(type -> System.out.println(type));\nSystem.out.println(\"-------------\");\n\n// ֱӻȡBeanObj3 ֱӱ @MyComponentģصtrue\nboolean exist1 = annotationMetadata.hasAnnotation(MyComponent.class.getName());\nSystem.out.println(\"hasAnnotation @MyComponent:\" + exist1);\n\n// ֱӻȡBeanObj3 ûֱӱ @Componentģصfalse\nboolean exist2 = annotationMetadata.hasAnnotation(Component.class.getName());\nSystem.out.println(\"hasAnnotation @Component:\" + exist2);\n\n// ȡ MergedAnnotations\nMergedAnnotations annotations = annotationMetadata.getAnnotations();\nSystem.out.println(\"-------------\");\nannotations.forEach(annotationMergedAnnotation -> System.out.println(annotationMergedAnnotation));\nSystem.out.println(\"-------------\");\n\n// ֱӻȡBeanObj3 ûֱӱ @Componentģصfalse\nboolean directlyPresent = annotations.isDirectlyPresent(Component.class);\nSystem.out.println(\"directlyPresent Component:\" + directlyPresent);\n\n// жûע⣬BeanObj3 ϵ@MyComponentУ @Component ģصtrue\nboolean present = annotations.isPresent(Component.class);\nSystem.out.println(\"present Component:\" + present);\n\n// ȡ @Component ע\nMergedAnnotation<Component> mergedAnnotation = annotations.get(Component.class);\n//  @MyComponent  value()  @AliasFor(annotation = Component.class)\n// õ value  beanObj3 BeanObj3ôָģ@MyComponent(\"beanObj3\")\nString value = mergedAnnotation.getString(\"value\");\nSystem.out.println(\"Component value:\" + value);\n\n//  @Component עתΪ AnnotationAttributes\nAnnotationAttributes annotationAttributes = mergedAnnotation.asAnnotationAttributes();\nSystem.out.println(annotationAttributes);\n\n```\n\nУ£\n\n```\n-------------\norg.springframework.learn.explore.demo01.MyComponent\n-------------\nhasAnnotation @MyComponent:true\nhasAnnotation @Component:false\n-------------\n@org.springframework.learn.explore.demo01.MyComponent(value=beanObj3)\n@org.springframework.stereotype.Component(value=beanObj3)\n@org.springframework.stereotype.Indexed()\n-------------\ndirectlyPresent Component:false\npresent Component:true\nComponent value:beanObj3\n{value=beanObj3}\n\n```\n\n##### 䣺`AnnotationAttributes`\n\n˵ `AnnotationAttributes`\n\n```\npublic class AnnotationAttributes extends LinkedHashMap<String, Object> {\n    ...\n}\n\n```\n\nʵ `LinkedHashMap`ṩĲַ£\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-a8c8dc975790719c37a8e41a4b0067c517c.png)\n\nﲻѿ`AnnotationAttributes` ǰעֵ mapkey Ϊvalue Ϊֵ\n\n#### 3.2 `StandardAnnotationMetadata`\n\nǽ `StandardAnnotationMetadata`:\n\n```\npublic class StandardAnnotationMetadata extends StandardClassMetadata \n        implements AnnotationMetadata {\n\n    /**\n     * Create a new {@code StandardAnnotationMetadata} wrapper for the given Class.\n     * @param introspectedClass the Class to introspect\n     * @see #StandardAnnotationMetadata(Class, boolean)\n     * @deprecated since 5.2 in favor of the factory method \n     *   {@link AnnotationMetadata#introspect(Class)}\n     */\n    @Deprecated\n    public StandardAnnotationMetadata(Class<?> introspectedClass) {\n    this(introspectedClass, false);\n    }\n\n    ...\n}\n\n```\n\n`StandardAnnotationMetadata` ʵ `AnnotationMetadata` ӿڣעĲܵ `AnnotationMetadata` ̫Ͳ׸ˡ\n\n `StandardAnnotationMetadata` Ĺ췽Ѿˣʹ `AnnotationMetadata#introspect(Class)` ȡ `StandardAnnotationMetadata` ʵǣǿ\n\n```\n// ȡ annotationMetadata ʵ StandardAnnotationMetadata\nAnnotationMetadata annotationMetadata = AnnotationMetadata.introspect(BeanObj3.class);\n\n//----------- SimpleAnnotationMetadataReadingVisitor һģһ\n\n// AnnotationMetadata ṩĲصעעص\nSet<String> annotationTypes = annotationMetadata.getAnnotationTypes();\nSystem.out.println(\"-------------\");\nannotationTypes.forEach(type -> System.out.println(type));\nSystem.out.println(\"-------------\");\n\n// ֱӻȡBeanObj3 ֱӱ @MyComponentģصtrue\nboolean exist1 = annotationMetadata.hasAnnotation(MyComponent.class.getName());\nSystem.out.println(\"hasAnnotation @MyComponent:\" + exist1);\n\n// ֱӻȡBeanObj3 ûֱӱ @Componentģصfalse\nboolean exist2 = annotationMetadata.hasAnnotation(Component.class.getName());\nSystem.out.println(\"hasAnnotation @Component:\" + exist2);\n\n// ȡ MergedAnnotations\nMergedAnnotations annotations = annotationMetadata.getAnnotations();\nSystem.out.println(\"-------------\");\nannotations.forEach(annotationMergedAnnotation -> System.out.println(annotationMergedAnnotation));\nSystem.out.println(\"-------------\");\n\n// ֱӻȡBeanObj3 ûֱӱ @Componentģصfalse\nboolean directlyPresent = annotations.isDirectlyPresent(Component.class);\nSystem.out.println(\"directlyPresent Component:\" + directlyPresent);\n\n// жûע⣬BeanObj3 ϵ@MyComponentУ @Component ģصtrue\nboolean present = annotations.isPresent(Component.class);\nSystem.out.println(\"present Component:\" + present);\n\n// ȡ @Component ע\nMergedAnnotation<Component> mergedAnnotation = annotations.get(Component.class);\n//  @MyComponent  value()  @AliasFor(annotation = Component.class)\n// õ value  beanObj3 BeanObj3ôָģ@MyComponent(\"beanObj3\")\nString value = mergedAnnotation.getString(\"value\");\nSystem.out.println(\"Component value:\" + value);\n\n//  @Component עתΪ AnnotationAttributes\nAnnotationAttributes annotationAttributes = mergedAnnotation.asAnnotationAttributes();\nSystem.out.println(annotationAttributes);\n\n```\n\nн£\n\n```\n-------------\norg.springframework.learn.explore.demo01.MyComponent\n-------------\nhasAnnotation @MyComponent:true\nhasAnnotation @Component:false\n-------------\n@org.springframework.learn.explore.demo01.MyComponent(value=beanObj3)\n@org.springframework.stereotype.Component(value=beanObj3)\n@org.springframework.stereotype.Indexed()\n-------------\ndirectlyPresent Component:false\npresent Component:true\nComponent value:beanObj3\n{value=beanObj3}\n\n```\n\nʾǧ࣬յõ `MergedAnnotations`ȻͨжעǷڡȡעֵ\n\n#### 3.3 ߵʹó\n\n`SimpleAnnotationMetadataReadingVisitor`  `StandardAnnotationMetadata` Ҫڣ`SimpleAnnotationMetadataReadingVisitor` ǻ asm ʵ֣`StandardAnnotationMetadata` ǻڷʵ֣ʹʱӦҪôѡأ\n\nڻڷҪȼص jvm еģҵжǣ**ǰûмص jvm Уʹ `SimpleAnnotationMetadataReadingVisitor`Ѿص jvm ˣ߽Կʹ**\n\nʵϣ spring ɨ׶ΣȡϵעʱʹõĶ `SimpleAnnotationMetadataReadingVisitor`Ϊʱಢûмص jvmʹ `StandardAnnotationMetadata` ȡͻᵼǰءǰʲôأjava ǰصģе jvm ڶûõȫˣͰװ˷ڴˡ\n\n### 4\\. spring ṩע⹤\n\nǰʾУȡעģ\n\n```\n// ȡ annotationMetadataҲʹ SimpleMetadataReaderFactory ȡ\nAnnotationMetadata annotationMetadata = AnnotationMetadata.introspect(BeanObj3.class);\nMergedAnnotations annotations = annotationMetadata.getAnnotations();\n\n// жעǷ\nboolean present = annotations.isPresent(Component.class);\n// ȡע\nMergedAnnotation<Component> mergedAnnotation = annotations.get(Component.class);\nAnnotationAttributes annotationAttributes = mergedAnnotation.asAnnotationAttributes();\n\n```\n\n˵ȡעԲȽ϶࣬㣬뵽ԽЩװһндspring Ҳôģ͵ý spring עصࣺ`AnnotationUtils`  `AnnotatedElementUtils``AnnotationUtils` ֱӻȡעֵᴦԸǣ `AnnotatedElementUtils` ᴦԸǡ\n\nʲôԸأ\n\n˵`@MyComponent` \n\n```\n@Target({ElementType.TYPE})\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\n// עComponentֵָ123\n@Component(\"123\")\npublic @interface MyComponent {\n\n    @AliasFor(annotation = Component.class)\n    String value() default \"\";\n\n}\n\n```\n\n `@MyComponent` עУָ `@Component`  `value` ֵΪ 123Ȼôָ `@MyComponent`  `value` ֵ\n\n```\n@MyComponent(\"beanObj3\")\npublic class BeanObj3 {\n\n    ...\n}    \n\n```\n\n spring ʼõ `BeanObj3`  `123`  `beanObj3` أ `@MyComponent`  `value` Ϊ `beanObj3` ˵Ȼϣ bean Ϊ `beanObj3` spring ҲôģԸˣ`@MyComponent`  `value`  `@Component`  `value` ֵ\n\n`AnnotationUtils`/`AnnotatedElementUtils` ܵ `SimpleAnnotationMetadataReadingVisitor`/`StandardAnnotationMetadata` Ǻιϵأ\n\nʹ `SimpleAnnotationMetadataReadingVisitor`/`StandardAnnotationMetadata` ʱҪõ `MergedAnnotations` ٽһϵвжעǷڡȡעֵȣ `AnnotationUtils`/`AnnotatedElementUtils` Դ룬ͻᷢǵطҲǲ `MergedAnnotations` ࣬ȡע⣺\n\n`AnnotationUtils#getAnnotation(AnnotatedElement, Class<A>)` \n\n```\npublic static <A extends Annotation> A getAnnotation(AnnotatedElement annotatedElement, \n        Class<A> annotationType) {\n    if (AnnotationFilter.PLAIN.matches(annotationType) ||\n            AnnotationsScanner.hasPlainJavaAnnotationsOnly(annotatedElement)) {\n        return annotatedElement.getAnnotation(annotationType);\n    }\n    // ͨ MergedAnnotations лȡ\n    return MergedAnnotations.from(annotatedElement, \n            SearchStrategy.INHERITED_ANNOTATIONS, RepeatableContainers.none())\n            .get(annotationType).withNonMergedAttributes()\n            .synthesize(AnnotationUtils::isSingleLevelPresent).orElse(null);\n}\n\n```\n\n`AnnotatedElementUtils#getAllMergedAnnotations(AnnotatedElement, Class<A>)` \n\n```\npublic static <A extends Annotation> Set<A> getAllMergedAnnotations(\n        AnnotatedElement element, Class<A> annotationType) {\n    return getAnnotations(element).stream(annotationType)\n            .collect(MergedAnnotationCollectors.toAnnotationSet());\n}\n\n// AnnotatedElementUtils#getAnnotations Ҳǲ MergedAnnotations ķ\nprivate static MergedAnnotations getAnnotations(AnnotatedElement element) {\n    return MergedAnnotations.from(element, SearchStrategy.INHERITED_ANNOTATIONS, \n            RepeatableContainers.none());\n}\n\n```\n\nˣ`AnnotationUtils`/`AnnotatedElementUtils`  `SimpleAnnotationMetadataReadingVisitor`/`StandardAnnotationMetadata` ײ㶼ǲ `MergedAnnotations` ġ\n\n#### 4.1 `AnnotationUtils`\n\n`AnnotationUtils` ֵ֧Ĳַ£\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-51b0f496180a16d947b8f83285370cabd8f.png)\n\nʵʹЩ\n\n```\n//  BeanObj3 ȡ @Component\nAnnotation annotation = AnnotationUtils.getAnnotation(BeanObj3.class, Component.class);\nif(null == annotation) {\n    System.out.println(\"עⲻڣ\");\n    return;\n}\nSystem.out.println(\"annotation: \" + annotation);\n\n// ȡ AnnotationAttributes\nAnnotationAttributes annotationAttributes\n        = AnnotationUtils.getAnnotationAttributes(BeanObj3.class, annotation);\nSystem.out.println(\"AnnotationAttributes: \" + annotationAttributes);\n\n// ȡ annotationAttributeMap\nMap<String, Object> annotationAttributeMap = AnnotationUtils.getAnnotationAttributes(annotation);\nSystem.out.println(\"annotationAttributeMap: \" + annotationAttributeMap);\n\n// ȡvalueֵ\nObject value = AnnotationUtils.getValue(annotation, \"value\");\nSystem.out.println(\"value: \" + value);\n\n```\n\n£\n\n```\nannotation: @org.springframework.stereotype.Component(value=123)\nAnnotationAttributes: {value=123}\nannotationAttributeMap: {value=123}\nvalue: 123\n\n```\n\nӽֱͨ `AnnotationUtils.getAnnotation(...)` Ҳܻȡ `@Component` עģ `BeanObj3` ûֱӱ `@Component`. Ҫעǣȡ `@Component`  `value` ֵ \"123\" `@MyComponent` õ `beanObj3`Ҳ֤ `AnnotationUtils` ȡֵʱԸǲ\n\n#### 4.2 `AnnotatedElementUtils`\n\n`AnnotatedElementUtils` ֵ֧Ĳַ£\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/up-9815fe697f5ccf14d3aa215952bfcbcc3ab.png)\n\nʾɣ\n\n```\n// 1\\. жǷ Component ע\nboolean result = AnnotatedElementUtils.hasAnnotation(BeanObj3.class, Component.class);\nSystem.out.println(\"hasAnnotation: \" + result);\n\n// 2\\. ȡ attributeMapԿǣȡ @Component  @MyComponent õĽһ\n// Component attributeMap: {value=[123]}\nMultiValueMap<String, Object> attributeMap1 = AnnotatedElementUtils\n        .getAllAnnotationAttributes(BeanObj3.class, Component.class.getName());\nSystem.out.println(\"Component attributeMap: \" + attributeMap1);\n// MyComponent attributeMap: {value=[beanObj3]}\nMultiValueMap<String, Object> attributeMap2 = AnnotatedElementUtils\n        .getAllAnnotationAttributes(BeanObj3.class, MyComponent.class.getName());\nSystem.out.println(\"MyComponent attributeMap: \" + attributeMap2);\n\n// 3\\. ȡе @Component ע⣬value=beanObj3\nSet<Component> mergedAnnotations = AnnotatedElementUtils\n        .getAllMergedAnnotations(BeanObj3.class, Component.class);\nSystem.out.println(\"mergedAnnotations: \" + mergedAnnotations);\n\n// 4\\. ȡֵ{value=beanObj3}\nAnnotationAttributes attributes = AnnotatedElementUtils\n        .getMergedAnnotationAttributes(BeanObj3.class, Component.class);\nSystem.out.println(\"attributes: \" + attributes);\n\n// 5\\. ȡ MyComponent ϵע\nSet<String> types = AnnotatedElementUtils\n        .getMetaAnnotationTypes(BeanObj3.class, MyComponent.class);\nSystem.out.println(\"types: \" + types);\n\n```\n\n£\n\n```\nhasAnnotation: true\nComponent attributeMap: {value=[123]}\nMyComponent attributeMap: {value=[beanObj3]}\nmergedAnnotations: [@org.springframework.stereotype.Component(value=beanObj3)]\nattributes: {value=beanObj3}\ntypes: [org.springframework.stereotype.Component, org.springframework.stereotype.Indexed]\n\n```\n\nӴڵõ `Set<Component>`  `AnnotationAttributes` УֵѾϲ.\n\nѡʹ `AnnotationUtils`  `AnnotatedElementUtils` ʱԸҪҪԸѡҪԸǣʹ `AnnotatedElementUtils`Ҫʹ `AnnotationUtils` ɣ\n\n### 5\\. ܽ\n\nĽ spring עĲҪ `SimpleAnnotationMetadataReadingVisitor`  `StandardAnnotationMetadata` ʹ÷ʹȽ϶ֽ࣬ spring ṩࣺ`AnnotationUtils`  `AnnotatedElementUtils`ҪԸǣҪʹ `AnnotatedElementUtils`Ҫʹ `AnnotationUtils`\n\n* * *\n\n_ԭӣ[https://my.oschina.net/funcy/blog/4633161](https://my.oschina.net/funcy/blog/4633161) ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע_"
  },
  {
    "path": "docs/backend/Hadoop生态总结.md",
    "content": "# 目录\n\n* [Hadoop生态](#hadoop生态)\n  * [hdfs](#hdfs)\n    * [架构](#架构)\n    * [读写](#读写)\n    * [高可用](#高可用)\n  * [MapReduce](#mapreduce)\n    * [架构和流程](#架构和流程)\n    * [wordcount](#wordcount)\n  * [hive](#hive)\n  * [hbase](#hbase)\n    * [简介](#简介)\n    * [存储](#存储)\n  * [zk](#zk)\n  * [sqoop](#sqoop)\n  * [yarn](#yarn)\n  * [kafka](#kafka)\n  * [flume](#flume)\n  * [spark](#spark)\n  * [storm](#storm)\n\n\n[toc]\n\n这篇总结主要是基于我之前Hadoop生态基础系列文章而形成的的。主要是把重要的知识点用自己的话说了一遍，可能会有一些错误，还望见谅和指点。谢谢\n\n<!-- more -->  \n\n# Hadoop生态\n\n## hdfs\n\n### 架构\n\nhdfs是一个分布式文件系统。底层的存储采用廉价的磁盘阵列RAID，由于可以并发读写所以效率很高。\n\n基本架构是一个namenode和多个dataNode。node的意思是节点，一般指主机，也可以是虚拟机。\n\n每个文件都会有两个副本存放在datanode中。\n\n### 读写\n\n客户端写入文件时，先把请求发送到namenode，namenode会返回datanode的服务地址，接着客户端去访问datanode，进行文件写入，然后通知namenode，namenode接收到写入完成的消息以后，会另外选两个datanode存放冗余副本。\n\n读取文件时，从namenode获取一个datanode的地址，然后自己去读取即可。\n\n当一个文件的副本不足两份时，namenode自动会完成副本复制。并且，由于datanode一般会放在各个机架。namenode一般会把副本一个放在同一机架，一个放在其他机架，防止某个机架出问题导致整个文件读写不可用。\n\n### 高可用\n\nnamenode节点是单点，所以宕机了就没救了，所以我们可以使用zookeeper来保证namenode的高可用。可以使用zookeeper选主来实现故障切换，namenode先注册一个节点在zk上，表示自己是主，宕机时zk会通知备份节点进行切换。\n\nHadoop2.0中加入了hdfs namenode高可用的方案，也叫HDFS HA。namenode和一个备份节点绑定在一起，并且通过一个共享数据区域进行数据同步。同时支持故障切换。\n\n## MapReduce\n\n### 架构和流程\n\nMapReduce是基于Hadoop集群的分布式计算方案。一般先编写map函数进行数据分片，然后通过shuffle进行相同分片的整合，最后通过reduce把所有的数据结果进行整理。\n\n具体来说，用户提交一个MapReduce程序给namenode节点，namenode节点启动一个jobtracker进行子任务的调度和监控，然后派发每个子任务tasktracker到datanode进行任务执行，由于数据分布在各个节点，每个tasktracker只需要执行自己的那一部分即可。最后再将结果汇总给tasktracker。\n\n### wordcount\n\n首先是一个文本文件:hi hello good hello hi hi。  \n三个节点，则进行三次map。hi hello，good hello，hi hi分别由三个节点处理。结果分别是hi 1 hello 1,good 1 hello 1,hi 1,hi 1。  \nshuffle时进行combine操作，得到hi 1,hello 1,good 1 hello 1,hi 2。最终reduce的结果是hi 3 hello 2 good 1.\n\n## hive\n\nhive是一个基于hdfs文件系统的数据仓库。可以通过hive sql语句执行对hdfs上文件的数据查询。原理是hive把hdfs上的数据文件看成一张张数据表，把表结构信息存在关系数据库如mysql中。然后执行sql时通过对表结构的解析再去hdfs上查询真正的数据，最后也会以结构化的形式返回。\n\n## hbase\n\n### 简介\n\nhbase是基于列的数据库。\n\n他与传统关系数据库有很大不同。\n\n首先在表结构上，hbase使用rowkey行键作为唯一主键，通过行键唯一确定一行数据。\n\n同时，hbase使用列族的概念，每个表都有固定的列族，每一行的数据的列族都一样，但是每一行所在列族的实际列都可以不一样。  \n比如列族是info，列可以是name age，也可以是sex address等。也就是说具体列可以在插入数据时再进行确认。\n\n并且，hbase的每一行数据还可以有多个版本，通过时间戳来表示不同的数据版本。\n\n### 存储\n\n一般情况下hbase使用hdfs作为底层存储，所以hdfs提供了数据的可靠性以及并发读写的高效率。\n\nhbase一个表的 每n行数据会存在一个region中，并且，对于列来说，每一个列族都会用一个region来存储，假设有m个列族，那么就会有n * m个region需要存储在hdfs上。\n\n同时hbase使用regionserver来管理这些region，他们可能存在不同的datanode里，所以通过regionserver可以找出每一个region的位置。\n\nhbase使用zookeeper来保证regionserver的高可用，会自动进行故障切换。\n\n## zk\n\nzk在Hadoop的作用有几个，通过选主等机制保证主节点高可用。\n\n使用zk进行配置资源的统一管理，保证服务器节点无状态，所有服务信息直接从zk获取即可。\n\n使用zookeeper进行节点间的通信等，也可以使用zk的目录顺序节点实现分布式锁，以及服务器选主。不仅在Hadoop中，zk在分布式系统中总能有用武之地。\n\nzookeeper本身的部署方式就是一个集群,一个master和多个slave。\n\n使用zab协议保证一致性和高可用。\n\nzab协议实现原理：\n\n1 使用两段式提交的方式确保一个协议需要半数以上节点同意以后再进行广播执行。\n\n2 使用基于机器编号加时间戳的id来表示每个事务，通过这个方式当初始选举或者主节点宕机时进行一轮选主，每个节点优先选择自己当主节点，在选举过程中节点优先采纳比较新的事务，将自己的选票更新，然后反馈个其他机器，最后当一个机器获得超过半数选票时当选为master。\n\n3选主结束以后，主节点与slave进行主从同步，保证数据一致性，然后对外提供服务，并且写入只能通过master而读取可以通过任意一台机器。\n\n## sqoop\n\n将hive表中的内容导入到MySQL数据库，也可以将MySQL中的数据导入hive中。\n\n## yarn\n\n没有yarn之前，hdfs使用jobtracker和tasktracker来执行和跟踪任务，jobtracker的任务太重，又要执行又要监控还要获取结果。  \n并且不同机器的资源情况没有被考虑在内。\n\nyarn是一个资源调度系统。提供applicationmaster对一个调度任务进行封装，然后有一个resourcemanager专门负责各节点资源的管理和监控。同时nodemanager则运行每个节点中用于监控节点状态和向rm汇报。还有一个container则是对节点资源的一个抽象，applicationmaster任务将由节点上的一个container进行执行。rm会将他调度到最合适的机器上。\n\n## kafka\n\n架构\n\n> kafka是一个分布式的消息队列。\n>\n> 它组成一般包括kafka broker，每个broker中有多个的partition作为存储消息的队列。\n>\n> 并且向上提供服务时抽象为一个topic，我们访问topic时实际上执行的是对partition的写入和读取操作。\n\n\n读写和高可用\n\n> partition支持顺序写入，效率比较高，并且支持零拷贝机制，通过内存映射磁盘mmap的方式，写入partition的数据顺序写入到映射的磁盘中，比传统的IO要快。\n>\n> 由于partition可能会宕机，所以一般也要支持partition的备份，1个broker ，master通常会有多个  \n> broker slave，是主从关系，通过zookeeper进行选主和故障切换。\n>\n> 当数据写入队列时，一般也会通过日志文件的方式进行数据备份，会把broker中的partition被分在各个slave中以便于均匀分布和恢复。\n\n生产者和消费者\n\n> 生产者消费者需要访问kafka的队列时，如果是写入，直接向zk发送请求，一般是向一个topic写入消息，broker会自动分配partition进行写入。然后zk会告诉生产者写入的partition所在的broker地址，然后进行写入。\n>\n> 如果是读取的话，也是通过zk获取partition所在位置，然后通过给定的offset进行读取，读取完后更新offset。\n>\n> 由于kafka的partition支持顺序读写。所以保证一个partition中的读取和写入时是顺序的，但是如果是多个partition则不保证顺序。\n>\n> 正常情况下kafka使用topic来实现消息点对点发送，并且每个consumer都要在一个consumer group中，而且comsumer group中每次只能有一个消费者能接受对应topic的消息。因为为了实现订阅也就是一对多发送，我们让每个consumer在一个单独的group，于是每个consumer都可以接受到该消息。\n\n\n## flume\n\nflume用于数据的收集和分发，flume可以监听端口的数据流入，监视文件的变动以及各种数据形式的数据流入，然后再把数据重新转发到其他需要数据的节点或存储中。\n\n1、Flume的概念   \nflume是分布式的日志收集系统，它将各个服务器中的数据收集起来并送到指定的地方去，比如说送到图中的HDFS，简单来说flume就是收集日志的。   \n2、Event的概念 在这里有必要先介绍一下flume中event的相关概念：flume的核心是把数据从数据源(source)收集过来，在将收集到的数据送到指定的目的地(sink)。为了保证输送的过程一定成功，在送到目的地(sink)之前，会先缓存数据(channel),待数据真正到达目的地(sink)后，flume在删除自己缓存的数据。   \n在整个数据的传输的过程中，流动的是event，即事务保证是在event级别进行的。那么什么是event呢？—–event将传输的数据进行封装，是flume传输数据的基本单位，如果是文本文件，通常是一行记录，event也是事务的基本单位。event从source，流向channel，再到sink，本身为一个字节数组，并可携带headers(头信息)信息。event代表着一个数据的最小完整单元，从外部数据源来，向外部的目的地去。         \nflume使用   ## ambari  \nambari就是一个Hadoop的Web应用。\n\n## spark\n\nspark和MapReduce不同的地方就是，把计算过程放在内存中运行。\n\nspark提出了抽象的RDD分布式内存模型，把每一步的计算操作转换成一个RDD结构，然后形成一个RDD连接而成的有向图。\n\n比如data.map().filter().reduce();  \n程序提交到master以后，会解析成多个RDD，并且形成一个有向图，然后spark再根据这些RD结构在内存中执行对应的操作。当然这个拓扑结构会被拆分为各个子任务分发到各个spark节点上，然后计算完以后再形成下一个rdd。最后汇总结果即可。\n\n由于是在内存中对数据进行操作，省去了不必要的IO操作，，不需要像Mapreduce一样还得先去hdfs读取文件再完成计算。\n\n\n\n\n## storm\n\n在运行一个Storm任务之前，需要了解一些概念：\n\n1.  Topologies\n2.  Streams\n3.  Spouts\n4.  Bolts\n5.  Stream groupings\n6.  Reliability\n7.  Tasks\n8.  Workers\n9.  Configuration\n\nStorm集群和Hadoop集群表面上看很类似。但是Hadoop上运行的是MapReduce jobs，而在Storm上运行的是拓扑（topology），这两者之间是非常不一样的。一个关键的区别是： 一个MapReduce job最终会结束， 而一个topology永远会运行（除非你手动kill掉）。\n\n在Storm的集群里面有两种节点： 控制节点（master node）和工作节点（worker node）。控制节点上面运行一个叫Nimbus后台程序，它的作用类似Hadoop里面的JobTracker。Nimbus负责在集群里面分发代码，分配计算任务给机器， 并且监控状态。\n\n每一个工作节点上面运行一个叫做Supervisor的节点。Supervisor会监听分配给它那台机器的工作，根据需要启动/关闭工作进程。每一个工作进程执行一个topology的一个子集；一个运行的topology由运行在很多机器上的很多工作进程组成。   \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230408112703.png)\n\nNimbus和Supervisor之间的所有协调工作都是通过Zookeeper集群完成。另外，Nimbus进程和Supervisor进程都是快速失败（fail-fast)和无状态的。所有的状态要么在zookeeper里面， 要么在本地磁盘上。这也就意味着你可以用kill -9来杀死Nimbus和Supervisor进程， 然后再重启它们，就好像什么都没有发生过。这个设计使得Storm异常的稳定。\n\nstorm比起spark它的实时性能更高更强，storm可以做到亚秒级别的数据输入分析。而spark的方式是通过秒级的数据切分，来形成spark rdd数据集，然后再按照DAG有向图进行执行的。\n\nstorm则不然。\n\n一：介绍Storm设计模型\n\n1.Topology\n\nStorm对任务的抽象，其实 就是将实时数据分析任务 分解为 不同的阶段　　　　  \n　　点： 计算组件   Spout   Bolt\n\n边： 数据流向    数据从上一个组件流向下一个组件  带方向\n\n2.tuple\n\nStorm每条记录 封装成一个tuple\n\n其实就是一些keyvalue对按顺序排列\n\n方便组件获取数据\n\n3.Spout\n\n数据采集器  \n　　源源不断的日志记录  如何被topology接收进行处理？\n\nSpout负责从数据源上获取数据，简单处理 封装成tuple向后面的bolt发射\n\n\n4.Bolt\n\n数据处理器　　二：开发wordcount案例\n\n1.书写整个大纲的点线图\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230408112730.png) \n　　topology就是一个拓扑图，类似于spark中的dag有向图，只不过storm执行的流式的数据，比dag执行更加具有实时性。\n\ntopology包含了spout和bolt。  \nspout负责获取数据，并且将数据发送给bolt，这个过程就是把任务派发到多个节点，bolt则负责对数据进行处理，比如splitbolt负责把每个单词提取出来，countbolt负责单词数量的统计，最后的printbolt将每个结果集tuple打印出来。\n\n这就形成了一个完整的流程。\n"
  },
  {
    "path": "docs/backend/后端技术杂谈开篇：云计算，大数据与AI的故事.md",
    "content": "# 目录\n\n* [一、云计算最初是实现资源管理的灵活性](#一、云计算最初是实现资源管理的灵活性)\n  * [1.1 管数据中心就像配电脑](#11-管数据中心就像配电脑)\n  * [1.2 灵活就是想啥时要都有，想要多少都行](#12-灵活就是想啥时要都有，想要多少都行)\n  * [1.3 物理设备不灵活](#13-物理设备不灵活)\n  * [1.4 虚拟化灵活多了](#14-虚拟化灵活多了)\n  * [1.5 虚拟世界的赚钱与情怀](#15-虚拟世界的赚钱与情怀)\n  * [1.6 虚拟化的半自动和云计算的全自动](#16-虚拟化的半自动和云计算的全自动)\n  * [1.7 云计算的私有与公有](#17-云计算的私有与公有)\n  * [1.8 云计算的赚钱与情怀](#18-云计算的赚钱与情怀)\n  * [1.9 IaaS,资源层面的灵活性](#19-iaas资源层面的灵活性)\n* [二、云计算不光管资源，也要管应用](#二、云计算不光管资源，也要管应用)\n* [三、大数据拥抱云计算](#三、大数据拥抱云计算)\n  * [3.1 数据不大也包含智慧](#31-数据不大也包含智慧)\n  * [3.2 数据如何升华为智慧](#32-数据如何升华为智慧)\n  * [3.3 大数据时代，众人拾柴火焰高](#33-大数据时代，众人拾柴火焰高)\n  * [3.4 大数据需要云计算，云计算需要大数据](#34-大数据需要云计算，云计算需要大数据)\n  * [4.1 机器什么时候才能懂人心](#41-机器什么时候才能懂人心)\n  * [4.2 让机器学会推理](#42-让机器学会推理)\n  * [4.3 教给机器知识](#43-教给机器知识)\n  * [4.4 算了，教不会你自己学吧](#44-算了，教不会你自己学吧)\n  * [4.5 模拟大脑的工作方式](#45-模拟大脑的工作方式)\n  * [4.6 没道理但做得到](#46-没道理但做得到)\n  * [4.7 人工智能的经济学解释](#47-人工智能的经济学解释)\n  * [4.8 人工智能需要大数据](#48-人工智能需要大数据)\n* [五、云计算，大数据，人工智能过上了美好的生活](#五、云计算，大数据，人工智能过上了美好的生活)\n\n\n[toc]\n我今天要讲这三个话题，一个是云计算，一个大数据，一个人工智能，我为什么要讲这三个东西呢？因为这三个东西现在非常非常的火，它们之间好像互相有关系，一般谈云计算的时候也会提到大数据，谈人工智能的时候也会提大数据，谈人工智能的时候也会提云计算。所以说感觉他们又相辅相成不可分割，如果是非技术的人员来讲可能比较难理解说这三个之间的相互关系，所以有必要解释一下。\n\n#  一、云计算最初是实现资源管理的灵活性\n\n我们首先来说云计算，云计算最初的目标是对资源的管理，管理的主要是计算资源，网络资源，存储资源三个方面。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/635909-20180304165200175-1916661625.png)\n\n\n\n## 1.1 管数据中心就像配电脑\n\n\n\n什么叫计算，网络，存储资源呢？就说你要买台笔记本电脑吧，你是不是要关心这台电脑什么样的CPU啊？多大的内存啊？这两个我们称为计算资源。\n\n\n\n这台电脑要能上网吧，需要有个网口可以插网线，或者有无线网卡可以连接我们家的路由器，您家也需要到运营商比如联通，移动，电信开通一个网络，比如100M的带宽，然后会有师傅弄一根网线到您家来，师傅可能会帮您将您的路由器和他们公司的网络连接配置好，这样您家的所有的电脑，手机，平板就都可以通过您的路由器上网了。这就是网络。\n\n\n\n您可能还会问硬盘多大啊？原来硬盘都很小，10G之类的，后来500G，1T，2T的硬盘也不新鲜了。(1T是1000G)，这就是存储。\n\n\n\n对于一台电脑是这个样子的，对于一个数据中心也是同样的。想象你有一个非常非常大的机房，里面堆了很多的服务器，这些服务器也是有CPU，内存，硬盘的，也是通过类似路由器的设备上网的。这个时候的一个问题就是，运营数据中心的人是怎么把这些设备统一的管理起来的呢？\n\n## 1.2 灵活就是想啥时要都有，想要多少都行\n\n管理的目标就是要达到两个方面的灵活性。哪两个方面呢？比如有个人需要一台很小很小的电脑，只有一个CPU，1G内存，10G的硬盘，一兆的带宽，你能给他吗？像这种这么小规格的电脑，现在随便一个笔记本电脑都比这个配置强了，家里随便拉一个宽带都要100M。然而如果去一个云计算的平台上，他要想要这个资源的时候，只要一点就有了。\n\n所以说它就能达到两个方面灵活性。\n\n*   第一个方面就是想什么时候要就什么时候要，比如需要的时候一点就出来了，这个叫做时间灵活性。\n\n*   第二个方面就是想要多少呢就有多少，比如需要一个很小很小的电脑，可以满足，比如需要一个特别大的空间，以云盘为例，似乎云盘给每个人分配的空间动不动就就很大很大，随时上传随时有空间，永远用不完，这个叫做空间灵活性。\n\n空间灵活性和时间灵活性，也即我们常说的云计算的弹性。\n\n为了解决这个弹性的问题，经历了漫长时间的发展。\n\n## 1.3 物理设备不灵活\n\n首先第一个阶段就是物理机，或者说物理设备时期。这个时期相当于客户需要一台电脑，我们就买一台放在数据中心里。物理设备当然是越来越牛，例如服务器，内存动不动就是百G内存，例如网络设备，一个端口的带宽就能有几十G甚至上百G，例如存储，在数据中心至少是PB级别的(一个P是1000个T，一个T是1000个G)。\n\n然而物理设备不能做到很好的灵活性。首先它不能够达到想什么时候要就什么时候要、比如买台服务器，哪怕买个电脑，都有采购的时间。突然用户告诉某个云厂商，说想要开台电脑，如果使用物理服务器，当时去采购啊就很难，如果说供应商啊关系一般，可能采购一个月，供应商关系好的话也需要一个星期。用户等了一个星期后，这时候电脑才到位，用户还要登录上去开始慢慢部署自己的应用，时间灵活性非常差。第二是空间灵活性也不行，例如上述的用户，要一个很小很小的电脑，现在哪还有这么小型号的电脑啊。不能为了满足用户只要一个G的内存是80G硬盘的，就去买一个这么小的机器。但是如果买一个大的呢，因为电脑大，就向用户多收钱，用户说他只用这么小的一点，如果让用户多付钱就很冤。\n\n## 1.4 虚拟化灵活多了\n\n有人就想办法了。第一个办法就是虚拟化。用户不是只要一个很小的电脑么？数据中心的物理设备都很强大，我可以从物理的CPU，内存，硬盘中虚拟出一小块来给客户，同时也可以虚拟出一小块来给其他客户，每个客户都只能看到自己虚的那一小块，其实每个客户用的是整个大的设备上其中的一小块。虚拟化的技术能使得不同的客户的电脑看起来是隔离的，我看着好像这块盘就是我的，你看这呢这块盘就是你的，实际情况可能我这个10G和您这个10G是落在同样一个很大很大的这个存储上的。\n\n而且如果事先物理设备都准备好，虚拟化软件虚拟出一个电脑是非常快的，基本上几分钟就能解决。所以在任何一个云上要创建一台电脑，一点几分钟就出来了，就是这个道理。\n\n这个空间灵活性和时间灵活性就基本解决了。\n\n## 1.5 虚拟世界的赚钱与情怀\n\n在虚拟化阶段，最牛的公司是Vmware，是实现虚拟化技术比较早的一家公司，可以实现计算，网络，存储的虚拟化，这家公司很牛，性能也做得非常好，然后虚拟化软件卖的也非常好，赚了好多的钱，后来让EMC(世界五百强，存储厂商第一品牌)给收购了。\n\n但是这个世界上还是有很多有情怀的人的，尤其是程序员里面，有情怀的人喜欢做一件什么事情呢？开源。这个世界上很多软件都是有闭源就有开源，源就是源代码。就是说某个软件做的好，所有人都爱用，这个软件的代码呢，我封闭起来只有我公司知道，其他人不知道，如果其他人想用这个软件，就要付我钱，这就叫闭源。但是世界上总有一些大牛看不惯钱都让一家赚了去。大牛们觉得，这个技术你会我也会，你能开发出来，我也能，我开发出来就是不收钱，把代码拿出来分享给大家，全世界谁用都可以，所有的人都可以享受到好处，这个叫做开源。\n\n比如最近蒂姆·伯纳斯·李就是个非常有情怀的人，2017年，他因“发明万维网、第一个浏览器和使万维网得以扩展的基本协议和算法”而获得2016年度的图灵奖。图灵奖就是计算机界的诺贝尔奖。然而他最令人敬佩的是，他将万维网，也就是我们常见的www的技术无偿贡献给全世界免费使用。我们现在在网上的所有行为都应该感谢他的功劳，如果他将这个技术拿来收钱，应该和比尔盖茨差不多有钱。\n\n例如在闭源的世界里有windows，大家用windows都得给微软付钱，开源的世界里面就出现了Linux。比尔盖茨靠windows，Office这些闭源的软件赚了很多钱，称为世界首富，就有大牛开发了另外一种操作系统Linux。很多人可能没有听说过Linux，很多后台的服务器上跑的程序都是Linux上的，比如大家享受双十一，支撑双十一抢购的系统，无论是淘宝，京东，考拉，都是跑在Linux上的。\n\n再如有apple就有安卓。apple市值很高，但是苹果系统的代码我们是看不到的。于是就有大牛写了安卓手机操作系统。所以大家可以看到几乎所有的其他手机厂商，里面都装安卓系统，因为苹果系统不开源，而安卓系统大家都可以用。\n\n在虚拟化软件也一样，有了Vmware，这个软件非常非常的贵。那就有大牛写了两个开源的虚拟化软件，一个叫做Xen，一个叫做KVM，如果不做技术的，可以不用管这两个名字，但是后面还是会提到。\n\n## 1.6 虚拟化的半自动和云计算的全自动\n\n虚拟化软件似乎解决了灵活性问题，其实不全对。因为虚拟化软件一般创建一台虚拟的电脑，是需要人工指定这台虚拟电脑放在哪台物理机上的，可能还需要比较复杂的人工配置，所以使用Vmware的虚拟化软件，需要考一个很牛的证书，能拿到这个证书的人，薪资是相当的高，也可见复杂程度。所以仅仅凭虚拟化软件所能管理的物理机的集群规模都不是特别的大，一般在十几台，几十台，最多百台这么一个规模。这一方面会影响时间灵活性，虽然虚拟出一台电脑的时间很短，但是随着集群规模的扩大，人工配置的过程越来越复杂，越来越耗时。另一方面也影响空间灵活性，当用户数量多的时候，这点集群规模，还远达不到想要多少要多少的程度，很可能这点资源很快就用完了，还得去采购。所以随着集群的规模越来越大，基本都是千台起步，动辄上万台，甚至几十上百万台，如果去查一下BAT，包括网易，包括谷歌，亚马逊，服务器数目都大的吓人。这么多机器要靠人去选一个位置放这台虚拟化的电脑并做相应的配置，几乎是不可能的事情，还是需要机器去做这个事情。\n\n人们发明了各种各样的算法来做这个事情，算法的名字叫做调度(Scheduler)。通俗一点的说，就是有一个调度中心，几千台机器都在一个池子里面，无论用户需要多少CPU，内存，硬盘的虚拟电脑，调度中心会自动在大池子里面找一个能够满足用户需求的地方，把虚拟电脑启动起来做好配置，用户就直接能用了。这个阶段，我们称为池化，或者云化，到了这个阶段，才可以称为云计算，在这之前都只能叫虚拟化。\n\n## 1.7 云计算的私有与公有\n\n云计算大致分两种，一个是私有云，一个是公有云，还有人把私有云和公有云连接起来称为混合云，我们暂且不说这个。私有云就是把虚拟化和云化的这套软件部署在别人的数据中心里面，使用私有云的用户往往很有钱，自己买地建机房，自己买服务器，然后让云厂商部署在自己这里，Vmware后来除了虚拟化，也推出了云计算的产品，并且在私有云市场赚的盆满钵满。所谓公有云就是虚拟化和云化软件部署在云厂商自己数据中心里面的，用户不需要很大的投入，只要注册一个账号，就能在一个网页上点一下创建一台虚拟电脑，例如AWS也即亚马逊的公有云，例如国内的阿里云，腾讯云，网易云等。\n\n亚马逊呢为什么要做公有云呢？我们知道亚马逊原来是国外比较大的一个电商，它做电商的时候也肯定会遇到类似双11的场景，在某一个时刻大家都冲上来买东西。当大家都冲上买东西的时候，就特别需要云的时间灵活性和空间灵活性。因为它不能时刻准备好所有的资源，那样太浪费了。但也不能什么都不准备，看着双十一这么多用户想买东西登不上去。所以需要双十一的时候，创建一大批虚拟电脑来支撑电商应用，过了双十一再把这些资源都释放掉去干别的。所以亚马逊是需要一个云平台的。\n\n然而商用的虚拟化软件实在是太贵了，亚马逊总不能把自己在电商赚的钱全部给了虚拟化厂商吧。于是亚马逊基于开源的虚拟化技术，如上所述的Xen或者KVM，开发了一套自己的云化软件。没想到亚马逊后来电商越做越牛，云平台也越做越牛。而且由于他的云平台需要支撑自己的电商应用，而传统的云计算厂商多为IT厂商出身，几乎没有自己的应用，因而亚马逊的云平台对应用更加的友好，迅速发展成为云计算的第一品牌，赚了很多钱。在亚马逊公布其云计算平台财报之前，人们都猜测，亚马逊电商赚钱，云也赚钱吗？后来一公布财报，发现不是一般的赚钱，仅仅去年，亚马逊AWS年营收达122亿美元，运营利润31亿美元。\n\n## 1.8 云计算的赚钱与情怀\n\n公有云的第一名亚马逊过得很爽，第二名Rackspace过的就一般了。没办法，这就是互联网行业的残酷性，多是赢者通吃的模式。所以第二名如果不是云计算行业的，很多人可能都没听过了。第二名就想，我干不过老大怎么办呢？开源吧。如上所述，亚马逊虽然使用了开源的虚拟化技术，但是云化的代码是闭源的，很多想做又做不了云化平台的公司，只能眼巴巴的看着亚马逊挣大钱。Rackspace把源代码一公开，整个行业就可以一起把这个平台越做越好，兄弟们大家一起上，和老大拼了。\n\n于是Rackspace和美国航空航天局合作创办了开源软件OpenStack，如图所示OpenStack的架构图，不是云计算行业的不用弄懂这个图，但是能够看到三个关键字，Compute计算，Networking网络，Storage存储。还是一个计算，网络，存储的云化管理平台。\n\n当然第二名的技术也是非常棒的，有了OpenStack之后，果真像Rackspace想象的一样，所有想做云的大企业都疯了，你能想象到的所有如雷贯耳的大型IT企业，IBM，惠普，戴尔，华为，联想等等，都疯了。原来云平台大家都想做，看着亚马逊和Vmware赚了这么多钱，眼巴巴看着没办法，想自己做一个好像难度还挺大。现在好了，有了这样一个开源的云平台OpenStack，所有的IT厂商都加入到这个社区中来，对这个云平台进行贡献，包装成自己的产品，连同自己的硬件设备一起卖。有的做了私有云，有的做了公有云，OpenStack已经成为开源云平台的事实标准。\n\n## 1.9 IaaS,资源层面的灵活性\n\n随着OpenStack的技术越来越成熟，可以管理的规模也越来越大，并且可以有多个OpenStack集群部署多套，比如北京部署一套，杭州部署两套，广州部署一套，然后进行统一的管理。这样整个规模就更大了。在这个规模下，对于普通用户的感知来讲，基本能够做到想什么时候要就什么什么药，想要多少就要多少。还是拿云盘举例子，每个用户云盘都分配了5T甚至更大的空间，如果有1亿人，那加起来空间多大啊。其实背后的机制是这样的，分配你的空间，你可能只用了其中很少一点，比如说它分配给你了5个T，这么大的空间仅仅是你看到的，而不是真的就给你了，你其实只用了50个G，则真实给你的就是50个G，随着你文件的不断上传，分给你的空间会越来越多。当大家都上传，云平台发现快满了的时候(例如用了70%)，会采购更多的服务器，扩充背后的资源，这个对用户是透明的，看不到的，从感觉上来讲，就实现了云计算的弹性。其实有点像银行，给储户的感觉是什么时候取钱都有，只要不同时挤兑，银行就不会垮。\n\n这里做一个简单的总结，到了这个阶段，云计算基本上实现了时间灵活性和空间灵活性，实现了计算，网络，存储资源的弹性。计算，网络，存储我们常称为基础设施Infranstracture, 因而这个阶段的弹性称为资源层面的弹性，管理资源的云平台，我们称为基础设施服务，就是我们常听到的IaaS，Infranstracture As A Service。\n\n# 二、云计算不光管资源，也要管应用\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/635909-20180304165352365-1674249597.png)\n\n有了IaaS，实现了资源层面的弹性就够了吗？显然不是。还有应用层面的弹性。这里举个例子，比如说实现一个电商的应用，平时十台机器就够了，双十一需要一百台。你可能觉得很好办啊，有了IaaS，新创建九十台机器就可以了啊。但是90台机器创建出来是空的啊，电商应用并没有放上去啊，只能你公司的运维人员一台一台的弄，还是需要很长时间才能安装好的。虽然资源层面实现了弹性，但是没有应用层的弹性，依然灵活性是不够的。\n\n有没有方法解决这个问题呢？于是人们在IaaS平台之上又加了一层，用于管理资源以上的应用弹性的问题，这一层通常称为PaaS（Platform As A Service）。这一层往往比较难理解，其实大致分两部分，一部分我称为你自己的应用自动安装，一部分我称为通用的应用不用安装。\n\n我们先来说第一部分，自己的应用自动安装。比如电商应用是你自己开发的，除了你自己，其他人是不知道怎么安装的，比如电商应用，安装的时候需要配置支付宝或者微信的账号，才能别人在你的电商上买东西的时候，付的钱是打到你的账户里面的，除了你，谁也不知道，所以安装的过程平台帮不了忙，但是能够帮你做的自动化，你需要做一些工作，将自己的配置信息融入到自动化的安装过程中方可。比如上面的例子，双十一新创建出来的90台机器是空的，如果能够提供一个工具，能够自动在这新的90台机器上将电商应用安装好，就能够实现应用层面的真正弹性。例如Puppet, Chef, Ansible, Cloud Foundary都可以干这件事情，最新的容器技术Docker能更好的干这件事情，不做技术的可以不用管这些词。\n\n第二部分，通用的应用不用安装。所谓通用的应用，一般指一些复杂性比较高，但是大家都在用的，例如数据库。几乎所有的应用都会用数据库，但是数据库软件是标准的，虽然安装和维护比较复杂，但是无论谁安装都是一样。这样的应用可以变成标准的PaaS层的应用放在云平台的界面上。当用户需要一个数据库的时候，一点就出来了，用户就可以直接用了。有人问，既然谁安装都一个样，那我自己来好了，不需要花钱在云平台上买。当然不是，数据库是一个非常难的东西，光Oracle这家公司，靠数据库就能赚这么多钱。买Oracle也是要花很多很多钱的。然而大多数云平台会提供Mysql这样的开源数据库，又是开源，钱不需要花这么多了，但是维护这个数据库，却需要专门招一个很大的团队，如果这个数据库能够优化到能够支撑双十一，也不是一年两年能够搞定的。比如您是一个做单车的，当然没必要招一个非常大的数据库团队来干这件事情，成本太高了，应该交给云平台来做这件事情，专业的事情专业的人来自，云平台专门养了几百人维护这套系统，您只要专注于您的单车应用就可以了。\n\n要么是自动部署，要么是不用部署，总的来说就是应用层你也要少操心，这就是PaaS层的重要作用。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/635909-20180304171116823-2125176837.png)\n\n虽说脚本的方式能够解决自己的应用的部署问题，然而不同的环境千差万别，一个脚本往往在一个环境上运行正确，到另一个环境就不正确了。\n\n而容器是能更好的解决这个问题的。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/635909-20180304171549380-884329721.png)\n\n容器是 Container，Container另一个意思是集装箱，其实容器的思想就是要变成软件交付的集装箱。集装箱的特点，一是封装，二是标准。\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/635909-20180304174029819-460006657.png)\n\n\n\n在没有集装箱的时代，假设将货物从 A运到 B，中间要经过三个码头、换三次船。每次都要将货物卸下船来，摆的七零八落，然后搬上船重新整齐摆好。因此在没有集装箱的时候，每次换船，船员们都要在岸上待几天才能走。\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/635909-20180304174049081-1911645088.png)\n\n有了集装箱以后，所有的货物都打包在一起了，并且集装箱的尺寸全部一致，所以每次换船的时候，一个箱子整体搬过去就行了，小时级别就能完成，船员再也不用上岸长时间耽搁了。\n\n\n\n这是集装箱“封装”、“标准”两大特点在生活中的应用。\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/635909-20180304174111204-554853341.png)\n\n\n\n\n\n那么容器如何对应用打包呢？还是要学习集装箱，首先要有个封闭的环境，将货物封装起来，让货物之间互不干扰，互相隔离，这样装货卸货才方便。好在 Ubuntu中的LXC技术早就能做到这一点。\n\n\n\n封闭的环境主要使用了两种技术，一种是看起来是隔离的技术，称为 Namespace，也即每个 Namespace中的应用看到的是不同的 IP地址、用户空间、程号等。另一种是用起来是隔离的技术，称为 Cgroups，也即明明整台机器有很多的 CPU、内存，而一个应用只能用其中的一部分。\n\n所谓的镜像，就是将你焊好集装箱的那一刻，将集装箱的状态保存下来，就像孙悟空说：“定”，集装箱里面就定在了那一刻，然后将这一刻的状态保存成一系列文件。这些文件的格式是标准的，谁看到这些文件都能还原当时定住的那个时刻。将镜像还原成运行时的过程（就是读取镜像文件，还原那个时刻的过程）就是容器运行的过程。\n\n有了容器，使得 PaaS层对于用户自身应用的自动部署变得快速而优雅。\n\n# 三、大数据拥抱云计算\n\n在PaaS层中一个复杂的通用应用就是大数据平台。大数据是如何一步一步融入云计算的呢？\n\n## 3.1 数据不大也包含智慧\n\n一开始这个大数据并不大，你想象原来才有多少数据？现在大家都去看电子书，上网看新闻了，在我们80后小时候，信息量没有那么大，也就看看书，看看报，一个星期的报纸加起来才有多少字啊，如果你不在一个大城市，一个普通的学校的图书馆加起来也没几个书架，是后来随着信息化的到来，信息才会越来越多。\n\n首先我们来看一下大数据里面的数据，就分三种类型，一种叫结构化的数据，一种叫非结构化的数据，还有一种叫半结构化的数据。什么叫结构化的数据呢？叫有固定格式和有限长度的数据。例如填的表格就是结构化的数据，国籍：中华人民共和国，民族：汉，性别：男，这都叫结构化数据。现在越来越多的就是非结构化的数据，就是不定长，无固定格式的数据，例如网页，有时候非常长，有时候几句话就没了，例如语音，视频都是非结构化的数据。半结构化数据是一些xml或者html的格式的，不从事技术的可能不了解，但也没有关系。\n\n数据怎么样才能对人有用呢？其实数据本身不是有用的，必须要经过一定的处理。例如你每天跑步带个手环收集的也是数据，网上这么多网页也是数据，我们称为Data，数据本身没有什么用处，但是数据里面包含一个很重要的东西，叫做信息Information，数据十分杂乱，经过梳理和清洗，才能够称为信息。信息会包含很多规律，我们需要从信息中将规律总结出来，称为知识knowledge，知识改变命运。信息是很多的，但是有人看到了信息相当于白看，但是有人就从信息中看到了电商的未来，有人看到了直播的未来，所以人家就牛了，你如果没有从信息中提取出知识，天天看朋友圈，也只能在互联网滚滚大潮中做个看客。有了知识，然后利用这些知识去应用于实战，有的人会做得非常好，这个东西叫做智慧intelligence。有知识并不一定有智慧，例如好多学者很有知识，已经发生的事情可以从各个角度分析的头头是道，但一到实干就歇菜，并不能转化成为智慧。而很多的创业家之所以伟大，就是通过获得的知识应用于实践，最后做了很大的生意。\n\n所以数据的应用分这四个步骤：数据，信息，知识，智慧。这是很多商家都想要的，你看我收集了这么多的数据，能不能基于这些数据来帮我做下一步的决策，改善我的产品，例如让用户看视频的时候旁边弹出广告，正好是他想买的东西，再如让用户听音乐的时候，另外推荐一些他非常想听的其他音乐。用户在我的应用或者网站上随便点点鼠标，输入文字对我来说都是数据，我就是要将其中某些东西提取出来，指导实践，形成智慧，让用户陷入到我的应用里面不可自拔，上了我的网就不想离开，手不停的点，不停的买，很多人说双十一我都想断网了，我老婆在上面不断的买买买，买了A又推荐B，老婆大人说，“哎呀，B也是我喜欢的啊，老公我要买”。你说这个程序怎么这么牛，这么有智慧，比我还了解我老婆，这件事情是怎么做到的呢？\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/635909-20180304172910727-1535815123.png)\n\n## 3.2 数据如何升华为智慧\n\n数据的处理分几个步骤，完成了才最后会有智慧。\n\n第一个步骤叫数据的收集。首先得有数据，数据的收集有两个方式，第一个方式是拿，专业点的说法叫抓取或者爬取，例如搜索引擎就是这么做的，它把网上的所有的信息都下载到它的数据中心，然后你一搜才能搜出来。比如你去搜索的时候，结果会是一个列表，这个列表为什么会在搜索引擎的公司里面呢，就是因为他把这个数据啊都拿下来了，但是你一点链接，点出来这个网站就不在搜索引擎它们公司了。比如说新浪有个新闻，你拿百度搜出来，你不点的时候，那一页在百度数据中心，一点出来的网页就是在新浪的数据中心了。另外一个方式就是推送，有很多终端可以帮我收集数据，比如说小米手环，可以将你每天跑步的数据，心跳的数据，睡眠的数据都上传到数据中心里面。\n\n第二个步骤是数据的传输。一般会通过队列方式进行，因为数据量实在是太大了，数据必须经过处理才会有用，可是系统处理不过来，只好排好队，慢慢的处理。\n\n第三个步骤是数据的存储。现在数据就是金钱，掌握了数据就相当于掌握了钱。要不然网站怎么知道你想买什么呢？就是因为它有你历史的交易的数据，这个信息可不能给别人，十分宝贵，所以需要存储下来。\n\n第四个步骤是数据的处理和分析。上面存储的数据是原始数据，原始数据多是杂乱无章的，有很多垃圾数据在里面，因而需要清洗和过滤，得到一些高质量的数据。对于高质量的数据，就可以进行分析，从而对数据进行分类，或者发现数据之间的相互关系，得到知识。比如盛传的沃尔玛超市的啤酒和尿布的故事，就是通过对人们的购买数据进行分析，发现了男人一般买尿布的时候，会同时购买啤酒，这样就发现了啤酒和尿布之间的相互关系，获得知识，然后应用到实践中，将啤酒和尿布的柜台弄的很近，就获得了智慧。\n\n第五个步骤就是对于数据的检索和挖掘。检索就是搜索，所谓外事不决问google，内事不决问百度。内外两大搜索引擎都是讲分析后的数据放入搜索引擎，从而人们想寻找信息的时候，一搜就有了。另外就是挖掘，仅仅搜索出来已经不能满足人们的要求了，还需要从信息中挖掘出相互的关系。比如财经搜索，当搜索某个公司股票的时候，该公司的高管是不是也应该被挖掘出来呢？如果仅仅搜索出这个公司的股票发现涨的特别好，于是你就去买了，其实其高管发了一个声明，对股票十分不利，第二天就跌了，这不坑害广大股民么？所以通过各种算法挖掘数据中的关系，形成知识库，十分重要。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/635909-20180304173120622-1040592225.png)\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/635909-20180304173217309-1598751514.png)\n\n## 3.3 大数据时代，众人拾柴火焰高\n\n当数据量很小的时候，很少的几台机器就能解决。慢慢的当数据量越来越大，最牛的服务器都解决不了问题的时候，就想怎么办呢？要聚合多台机器的力量，大家齐心协力一起把这个事搞定，众人拾柴火焰高。\n\n对于数据的收集，对于IoT来讲，外面部署这成千上万的检测设备，将大量的温度，适度，监控，电力等等数据统统收集上来，对于互联网网页的搜索引擎来讲，需要将整个互联网所有的网页都下载下来，这显然一台机器做不到，需要多台机器组成网络爬虫系统，每台机器下载一部分，同时工作，才能在有限的时间内，将海量的网页下载完毕。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/635909-20180304173243017-953751892.png)\n\n\n\n对于数据的传输，一个内存里面的队列肯定会被大量的数据挤爆掉，于是就产生了基于硬盘的分布式队列，这样队列可以多台机器同时传输，随你数据量多大，只要我的队列足够多，管道足够粗，就能够撑得住。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/635909-20180304173256198-1194238978.png)\n\n对于数据的存储，一台机器的文件系统肯定是放不下了，所以需要一个很大的分布式文件系统来做这件事情，把多台机器的硬盘打成一块大的文件系统。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/635909-20180304173309193-29645192.png)\n\n\n\n再如数据的分析，可能需要对大量的数据做分解，统计，汇总，一台机器肯定搞不定，处理到猴年马月也分析不完，于是就有分布式计算的方法，将大量的数据分成小份，每台机器处理一小份，多台机器并行处理，很快就能算完。例如著名的Terasort对1个TB的数据排序，相当于1000G，如果单机处理，怎么也要几个小时，但是并行处理209秒就完成了。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/635909-20180304173340325-454046253.png)\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/635909-20180304173407555-1514738312.png)\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/635909-20180304173430982-1156642033.png)\n\n所以说大数据平台，什么叫做大数据，说白了就是一台机器干不完，大家一起干。随着数据量越来越大，很多不大的公司都需要处理相当多的数据，这些小公司没有这么多机器可怎么办呢？\n\n## 3.4 大数据需要云计算，云计算需要大数据\n\n说到这里，大家想起云计算了吧。当想要干这些活的时候，需要好多好多的机器一块做，真的是想什么时候要，想要多少就要多少。例如大数据分析公司的财务情况，可能一周分析一次，如果要把这一百台机器或者一千台机器都在那放着，一周用一次对吧，非常浪费。那能不能需要计算的时候，把这一千台机器拿出来，然后不算的时候，这一千台机器可以去干别的事情。谁能做这个事儿呢？只有云计算，可以为大数据的运算提供资源层的灵活性。而云计算也会部署大数据放到它的PaaS平台上，作为一个非常非常重要的通用应用。因为大数据平台能够使得多台机器一起干一个事儿，这个东西不是一般人能开发出来的，也不是一般人玩得转的，怎么也得雇个几十上百号人才能把这个玩起来，所以说就像数据库一样，其实还是需要有一帮专业的人来玩这个东西。现在公有云上基本上都会有大数据的解决方案了，一个小公司我需要大数据平台的时候，不需要采购一千台机器，只要到公有云上一点，这一千台机器都出来了，并且上面已经部署好了的大数据平台，只要把数据放进去算就可以了。\n\n云计算需要大数据，大数据需要云计算，两个人就这样结合了。\n\n四、人工智能拥抱大数据\n\n## 4.1 机器什么时候才能懂人心\n\n虽说有了大数据，人的欲望总是这个不能够满足。虽说在大数据平台里面有搜索引擎这个东西，想要什么东西我一搜就出来了。但是也存在这样的情况，我想要的东西不会搜，表达不出来，搜索出来的又不是我想要的。例如音乐软件里面推荐一首歌，这首歌我没听过，当然不知道名字，也没法搜，但是软件推荐给我，我的确喜欢，这就是搜索做不到的事情。当人们使用这种应用的时候，会发现机器知道我想要什么，而不是说当我想要的时候，去机器里面搜索。这个机器真像我的朋友一样懂我，这就有点人工智能的意思了。\n\n人们很早就在想这个事情了。最早的时候，人们想象，如果要是有一堵墙，墙后面是个机器，我给它说话，它就给我回应，我如果感觉不出它那边是人还是机器，那它就真的是一个人工智能的东西了。\n\n## 4.2 让机器学会推理\n\n怎么才能做到这一点呢？人们就想：我首先要告诉计算机人类的推理的能力。你看人重要的是什么呀，人和动物的区别在什么呀，就是能推理。我要是把我这个推理的能力啊告诉机器，机器就能根据你的提问，推理出相应的回答，真能这样多好。推理其实人们慢慢的让机器能够做到一些了，例如证明数学公式。这是一个非常让人惊喜的一个过程，机器竟然能够证明数学公式。但是慢慢发现其实这个结果，也没有那么令人惊喜，因为大家发现了一个问题，数学公式非常严谨，推理过程也非常严谨，而且数学公式很容易拿机器来进行表达，程序也相对容易表达。然而人类的语言就没这么简单了，比如今天晚上，你和你女朋友约会，你女朋友说：如果你早来，我没来，你等着，如果我早来，你没来，你等着。这个机器就比比较难理解了，但是人都懂，所以你和女朋友约会，你是不敢迟到的。\n\n## 4.3 教给机器知识\n\n所以仅仅告诉机器严格的推理是不够的，还要告诉机器一些知识。但是知识这个事儿，一般人可能就做不来了，可能专家可以，比如语言领域的专家，或者财经领域的专家。语言领域和财经领域知识能不能表示成像数学公式一样稍微严格点呢？例如语言专家可能会总结出主谓宾定状补这些语法规则，主语后面一定是谓语，谓语后面一定是宾语，将这些总结出来，并严格表达出来不久行了吗？后来发现这个不行，太难总结了，语言表达千变万化。就拿主谓宾的例子，很多时候在口语里面就省略了谓语，别人问：你谁啊？我回答：我刘超。但是你不能规定在语音语义识别的时候，要求对着机器说标准的书面语，这样还是不够智能，就像罗永浩在一次演讲中说的那样，每次对着手机，用书面语说：请帮我呼叫某某某，这是一件很尴尬的事情。\n\n人工智能这个阶段叫做专家系统。专家系统不易成功，一方面是知识比较难总结，另一方面总结出来的知识难以教给计算机。因为你自己还迷迷糊糊，似乎觉得有规律，就是说不出来，就怎么能够通过编程教给计算机呢？\n\n## 4.4 算了，教不会你自己学吧\n\n于是人们想到，看来机器是和人完全不一样的物种，干脆让机器自己学习好了。机器怎么学习呢？既然机器的统计能力这么强，基于统计学习，一定能从大量的数字中发现一定的规律。\n\n其实在娱乐圈有很好的一个例子，可见一斑\n\n有一位网友统计了知名歌手在大陆发行的 9 张专辑中 117 首歌曲的歌词，同一词语在一首歌出现只算一次，形容词、名词和动词的前十名如下表所示（词语后面的数字是出现的次数）：\n\n\n\n\n````\n| a | 形容词 | b | 名词 | c | 动词 |  \n| 0 | 孤独:34 | 0 | 生命:50 | 0 | 爱:54 |  \n| 1 | 自由:17 | 1 | 路:37 | 1 | 碎:37 |  \n| 2 | 迷惘:16 | 2 | 夜:29 | 2 | 哭:35 |  \n| 3 | 坚强:13 | 3 | 天空:24 | 3 | 死:27 |  \n| 4 | 绝望:8 | 4 | 孩子:23 | 4 | 飞:26 |  \n| 5 | 青春:7 | 5 | 雨:21 | 5 | 梦想:14 |  \n| 6 | 迷茫:6 | 6 | 石头:9 | 6 | 祈祷:10 |  \n| 7 | 光明:6 | 7 | 鸟:9 | 7 | 离去:10 |  \n````\n\n\n\n\n如果我们随便写一串数字，然后按照数位依次在形容词、名词和动词中取出一个词，连在一起会怎么样呢？\n\n例如取圆周率 3.1415926，对应的词语是：坚强，路，飞，自由，雨，埋，迷惘。稍微连接和润色一下：\n\n坚强的孩子，\n\n依然前行在路上，\n\n张开翅膀飞向自由，\n\n让雨水埋葬他的迷惘。\n\n\n\n是不是有点感觉了？当然真正基于统计的学习算法比这个简单的统计复杂的多。\n\n然而统计学习比较容易理解简单的相关性，例如一个词和另一个词总是一起出现，两个词应该有关系，而无法表达复杂的相关性，并且统计方法的公式往往非常复杂，为了简化计算，常常做出各种独立性的假设，来降低公式的计算难度，然而现实生活中，具有独立性的事件是相对较少的。\n\n## 4.5 模拟大脑的工作方式\n\n\n\n于是人类开始从机器的世界，反思人类的世界是怎么工作的。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/635909-20180304173542876-248664847.png)\n\n\n\n人类的脑子里面不是存储着大量的规则，也不是记录着大量的统计数据，而是通过神经元的触发实现的，每个神经元有从其他神经元的输入，当接收到输入的时候，会产生一个输出来刺激其他的神经元，于是大量的神经元相互反应，最终形成各种输出的结果。例如当人们看到美女瞳孔放大，绝不是大脑根据身材比例进行规则判断，也不是将人生中看过的所有的美女都统计一遍，而是神经元从视网膜触发到大脑再回到瞳孔。在这个过程中，其实很难总结出每个神经元对最终的结果起到了哪些作用，反正就是起作用了。\n\n\n\n于是人们开始用一个数学单元模拟神经元\n\n这个神经元有输入，有输出，输入和输出之间通过一个公式来表示，输入根据重要程度不同(权重)，影响着输出。\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/635909-20180304173608115-399984901.png)\n\n\n\n于是将n个神经元通过像一张神经网络一样连接在一起，n这个数字可以很大很大，所有的神经元可以分成很多列，每一列很多个排列起来，每个神经元的对于输入的权重可以都不相同，从而每个神经元的公式也不相同。当人们从这张网络中输入一个东西的时候，希望输出一个对人类来讲正确的结果。例如上面的例子，输入一个写着2的图片，输出的列表里面第二个数字最大，其实从机器来讲，它既不知道输入的这个图片写的是2，也不知道输出的这一系列数字的意义，没关系，人知道意义就可以了。正如对于神经元来说，他们既不知道视网膜看到的是美女，也不知道瞳孔放大是为了看的清楚，反正看到美女，瞳孔放大了，就可以了。\n\n对于任何一张神经网络，谁也不敢保证输入是2，输出一定是第二个数字最大，要保证这个结果，需要训练和学习。毕竟看到美女而瞳孔放大也是人类很多年进化的结果。学习的过程就是，输入大量的图片，如果结果不是想要的结果，则进行调整。如何调整呢，就是每个神经元的每个权重都向目标进行微调，由于神经元和权重实在是太多了，所以整张网络产生的结果很难表现出非此即彼的结果，而是向着结果微微的进步，最终能够达到目标结果。当然这些调整的策略还是非常有技巧的，需要算法的高手来仔细的调整。正如人类见到美女，瞳孔一开始没有放大到能看清楚，于是美女跟别人跑了，下次学习的结果是瞳孔放大一点点，而不是放大鼻孔。\n\n## 4.6 没道理但做得到\n\n\n\n听起来也没有那么有道理，但是的确能做到，就是这么任性。\n\n神经网络的普遍性定理是这样说的，假设某个人给你某种复杂奇特的函数，f(x)：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/635909-20180304173627213-1028735538.png)\n\n不管这个函数是什么样的，总会确保有个神经网络能够对任何可能的输入x，其值f(x)（或者某个能够准确的近似）是神经网络的输出。\n\n如果在函数代表着规律，也意味着这个规律无论多么奇妙，多么不能理解，都是能通过大量的神经元，通过大量权重的调整，表示出来的。\n\n## 4.7 人工智能的经济学解释\n\n\n\n这让我想到了经济学，于是比较容易理解了。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/635909-20180304173647419-351927988.png)\n\n我们把每个神经元当成社会中从事经济活动的个体。于是神经网络相当于整个经济社会，每个神经元对于社会的输入，都有权重的调整，做出相应的输出，比如工资涨了，菜价也涨了，股票跌了，我应该怎么办，怎么花自己的钱。这里面没有规律么？肯定有，但是具体什么规律呢？却很难说清楚。\n\n基于专家系统的经济属于计划经济，整个经济规律的表示不希望通过每个经济个体的独立决策表现出来，而是希望通过专家的高屋建瓴和远见卓识总结出来。专家永远不可能知道哪个城市的哪个街道缺少一个卖甜豆腐脑的。于是专家说应该产多少钢铁，产多少馒头，往往距离人民生活的真正需求有较大的差距，就算整个计划书写个几百页，也无法表达隐藏在人民生活中的小规律。\n\n基于统计的宏观调控就靠谱的多了，每年统计局都会统计整个社会的就业率，通胀率，GDP等等指标，这些指标往往代表着很多的内在规律，虽然不能够精确表达，但是相对靠谱。然而基于统计的规律总结表达相对比较粗糙，比如经济学家看到这些统计数据可以总结出长期来看房价是涨还是跌，股票长期来看是涨还是跌，如果经济总体上扬，房价和股票应该都是涨的。但是基于统计数据，无法总结出股票，物价的微小波动规律。\n\n基于神经网络的微观经济学才是对整个经济规律最最准确的表达，每个人对于从社会中的输入，进行各自的调整，并且调整同样会作为输入反馈到社会中。想象一下股市行情细微的波动曲线，正是每个独立的个体各自不断交易的结果，没有统一的规律可循。而每个人根据整个社会的输入进行独立决策，当某些因素经过多次训练，也会形成宏观上的统计性的规律，这也就是宏观经济学所能看到的。例如每次货币大量发行，最后房价都会上涨，多次训练后，人们也就都学会了。\n\n## 4.8 人工智能需要大数据\n\n\n\n然而神经网络包含这么多的节点，每个节点包含非常多的参数，整个参数量实在是太大了，需要的计算量实在太大，但是没有关系啊，我们有大数据平台，可以汇聚多台机器的力量一起来计算，才能在有限的时间内得到想要的结果。\n\n人工智能可以做的事情非常多，例如可以鉴别垃圾邮件，鉴别黄色暴力文字和图片等。这也是经历了三个阶段的。第一个阶段依赖于关键词黑白名单和过滤技术，包含哪些词就是黄色或者暴力的文字。随着这个网络语言越来越多，词也不断的变化，不断的更新这个词库就有点顾不过来。第二个阶段时，基于一些新的算法，比如说贝叶斯过滤等，你不用管贝叶斯算法是什么，但是这个名字你应该听过，这个一个基于概率的算法。第三个阶段就是基于大数据和人工智能，进行更加精准的用户画像和文本理解和图像理解。\n\n由于人工智能算法多是依赖于大量的数据的，这些数据往往需要面向某个特定的领域(例如电商，邮箱)进行长期的积累，如果没有数据，就算有人工智能算法也白搭，所以人工智能程序很少像前面的IaaS和PaaS一样，将人工智能程序给某个客户安装一套让客户去用，因为给某个客户单独安装一套，客户没有相关的数据做训练，结果往往是很差的。但是云计算厂商往往是积累了大量数据的，于是就在云计算厂商里面安装一套，暴露一个服务接口，比如您想鉴别一个文本是不是涉及黄色和暴力，直接用这个在线服务就可以了。这种形势的服务，在云计算里面称为软件即服务，SaaS (Software AS A Service)\n\n于是工智能程序作为SaaS平台进入了云计算。\n\n# 五、云计算，大数据，人工智能过上了美好的生活\n\n终于云计算的三兄弟凑齐了，分别是IaaS，PaaS和SaaS，所以一般在一个云计算平台上，云，大数据，人工智能都能找得到。对一个大数据公司，积累了大量的数据，也会使用一些人工智能的算法提供一些服务。对于一个人工智能公司，也不可能没有大数据平台支撑。所以云计算，大数据，人工智能就这样整合起来，完成了相遇，相识，相知。\n"
  },
  {
    "path": "docs/backend/后端技术杂谈：Docker 核心技术与实现原理.md",
    "content": "# 目录\n\n  * [Namespaces](#namespaces)\n  * [进程](#进程)\n  * [网络](#网络)\n    * [libnetwork](#libnetwork)\n  * [挂载点](#挂载点)\n  * [chroot](#chroot)\n  * [CGroups](#cgroups)\n  * [UnionFS](#unionfs)\n  * [存储驱动](#存储驱动)\n  * [AUFS](#aufs)\n  * [其他存储驱动](#其他存储驱动)\n  * [总结](#总结)\n\n\n[toc]\n\n本文转自互联网，侵删  \n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本系列文章将整理到我的个人博客\n> www.how2playlife.com\n\n更多Java技术文章会更新在我的微信公众号【Java技术江湖】上，欢迎关注  \n该系列博文会介绍常见的后端技术，这对后端工程师来说是一种综合能力，我们会逐步了解搜索技术，云计算相关技术、大数据研发等常见的技术喜提，以便让你更完整地了解后端技术栈的全貌，为后续参与分布式应用的开发和学习做好准备。\n\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系我，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->  \n\n\n\n\n**要搞懂docker的核心原理和技术，首先一定要对Linux内核有一定了解。**\n\n提到虚拟化技术，我们首先想到的一定是 Docker，经过四年的快速发展 Docker 已经成为了很多公司的标配，也不再是一个只能在开发阶段使用的玩具了。作为在生产环境中广泛应用的产品，Docker 有着非常成熟的社区以及大量的使用者，代码库中的内容也变得非常庞大。\n\n![docker-logo](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/2017-11-30-docker-logo.png)\n\n同样，由于项目的发展、功能的拆分以及各种奇怪的改名[PR](https://github.com/moby/moby/pull/32691)，让我们再次理解 Docker 的的整体架构变得更加困难。\n\n虽然 Docker 目前的组件较多，并且实现也非常复杂，但是本文不想过多的介绍 Docker 具体的实现细节，我们更想谈一谈 Docker 这种虚拟化技术的出现有哪些核心技术的支撑。\n\n![docker-core-techs](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/2017-11-30-docker-core-techs.png)\n\n首先，Docker 的出现一定是因为目前的后端在开发和运维阶段确实需要一种虚拟化技术解决开发环境和生产环境环境一致的问题，通过 Docker 我们可以将程序运行的环境也纳入到版本控制中，排除因为环境造成不同运行结果的可能。但是上述需求虽然推动了虚拟化技术的产生，但是如果没有合适的底层技术支撑，那么我们仍然得不到一个完美的产品。本文剩下的内容会介绍几种 Docker 使用的核心技术，如果我们了解它们的使用方法和原理，就能清楚 Docker 的实现原理。\n\n## Namespaces\n\n命名空间 (namespaces) 是 Linux 为我们提供的用于分离进程树、网络接口、挂载点以及进程间通信等资源的方法。在日常使用 Linux 或者 macOS 时，我们并没有运行多个完全分离的服务器的需要，但是如果我们在服务器上启动了多个服务，这些服务其实会相互影响的，每一个服务都能看到其他服务的进程，也可以访问宿主机器上的任意文件，这是很多时候我们都不愿意看到的，我们更希望运行在同一台机器上的不同服务能做到完全隔离，就像运行在多台不同的机器上一样。\n\n![multiple-servers-on-linux](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/2017-11-30-multiple-servers-on-linux.png)\n\n在这种情况下，一旦服务器上的某一个服务被入侵，那么入侵者就能够访问当前机器上的所有服务和文件，这也是我们不想看到的，而 Docker 其实就通过 Linux 的 Namespaces 对不同的容器实现了隔离。\n\nLinux 的命名空间机制提供了以下七种不同的命名空间，包括`CLONE_NEWCGROUP`、`CLONE_NEWIPC`、`CLONE_NEWNET`、`CLONE_NEWNS`、`CLONE_NEWPID`、`CLONE_NEWUSER`和`CLONE_NEWUTS`，通过这七个选项我们能在创建新的进程时设置新进程应该在哪些资源上与宿主机器进行隔离。\n\n## 进程\n\n进程是 Linux 以及现在操作系统中非常重要的概念，它表示一个正在执行的程序，也是在现代分时系统中的一个任务单元。在每一个 *nix 的操作系统上，我们都能够通过`ps`命令打印出当前操作系统中正在执行的进程，比如在 Ubuntu 上，使用该命令就能得到以下的结果：\n\n```  \n$ ps -efUID        PID  PPID  C STIME TTY          TIME CMDroot         1     0  0 Apr08 ?        00:00:09 /sbin/initroot         2     0  0 Apr08 ?        00:00:00 [kthreadd]root         3     2  0 Apr08 ?        00:00:05 [ksoftirqd/0]root         5     2  0 Apr08 ?        00:00:00 [kworker/0:0H]root         7     2  0 Apr08 ?        00:07:10 [rcu_sched]root        39     2  0 Apr08 ?        00:00:00 [migration/0]root        40     2  0 Apr08 ?        00:01:54 [watchdog/0]...  \n```  \n\n当前机器上有很多的进程正在执行，在上述进程中有两个非常特殊，一个是`pid`为 1 的`/sbin/init`进程，另一个是`pid`为 2 的`kthreadd`进程，这两个进程都是被 Linux 中的上帝进程`idle`创建出来的，其中前者负责执行内核的一部分初始化工作和系统配置，也会创建一些类似`getty`的注册进程，而后者负责管理和调度其他的内核进程。\n\n![linux-processes](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/2017-11-30-linux-processes.png)\n\n如果我们在当前的 Linux 操作系统下运行一个新的 Docker 容器，并通过`exec`进入其内部的`bash`并打印其中的全部进程，我们会得到以下的结果：\n\n```  \nroot@iZ255w13cy6Z:~# docker run -it -d ubuntub809a2eb3630e64c581561b08ac46154878ff1c61c6519848b4a29d412215e79root@iZ255w13cy6Z:~# docker exec -it b809a2eb3630 /bin/bashroot@b809a2eb3630:/# ps -efUID        PID  PPID  C STIME TTY          TIME CMDroot         1     0  0 15:42 pts/0    00:00:00 /bin/bashroot         9     0  0 15:42 pts/1    00:00:00 /bin/bashroot        17     9  0 15:43 pts/1    00:00:00 ps -ef  \n```  \n\n在新的容器内部执行`ps`命令打印出了非常干净的进程列表，只有包含当前`ps -ef`在内的三个进程，在宿主机器上的几十个进程都已经消失不见了。\n\n当前的 Docker 容器成功将容器内的进程与宿主机器中的进程隔离，如果我们在宿主机器上打印当前的全部进程时，会得到下面三条与 Docker 相关的结果：\n\n```  \nUID        PID  PPID  C STIME TTY          TIME CMDroot     29407     1  0 Nov16 ?        00:08:38 /usr/bin/dockerd --raw-logsroot      1554 29407  0 Nov19 ?        00:03:28 docker-containerd -l unix:///var/run/docker/libcontainerd/docker-containerd.sock --metrics-interval=0 --start-timeout 2m --state-dir /var/run/docker/libcontainerd/containerd --shim docker-containerd-shim --runtime docker-runcroot      5006  1554  0 08:38 ?        00:00:00 docker-containerd-shim b809a2eb3630e64c581561b08ac46154878ff1c61c6519848b4a29d412215e79 /var/run/docker/libcontainerd/b809a2eb3630e64c581561b08ac46154878ff1c61c6519848b4a29d412215e79 docker-runc  \n```  \n\n在当前的宿主机器上，可能就存在由上述的不同进程构成的进程树：\n\n![docker-process-group](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/2017-11-30-docker-process-group.png)\n\n这就是在使用`clone(2)`创建新进程时传入`CLONE_NEWPID`实现的，也就是使用 Linux 的命名空间实现进程的隔离，Docker 容器内部的任意进程都对宿主机器的进程一无所知。\n\n```  \ncontainerRouter.postContainersStart└── daemon.ContainerStart    └── daemon.createSpec        └── setNamespaces            └── setNamespace  \n```  \n\nDocker 的容器就是使用上述技术实现与宿主机器的进程隔离，当我们每次运行`docker run`或者`docker start`时，都会在下面的方法中创建一个用于设置进程间隔离的 Spec：\n\n```  \nfunc (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {    s := oci.DefaultSpec()     // ... if err := setNamespaces(daemon, &s, c); err != nil {      return nil, fmt.Errorf(\"linux spec namespaces: %v\", err)   }  return &s, nil}  \n```  \n\n在`setNamespaces`方法中不仅会设置进程相关的命名空间，还会设置与用户、网络、IPC 以及 UTS 相关的命名空间：\n\n```  \nfunc setNamespaces(daemon *Daemon, s *specs.Spec, c *container.Container) error {  // user    // network // ipc // uts     // pid if c.HostConfig.PidMode.IsContainer() {       ns := specs.LinuxNamespace{Type: \"pid\"}       pc, err := daemon.getPidContainer(c)      if err != nil {          return err    }     ns.Path = fmt.Sprintf(\"/proc/%d/ns/pid\", pc.State.GetPID())       setNamespace(s, ns)    } else if c.HostConfig.PidMode.IsHost() {     oci.RemoveNamespace(s, specs.LinuxNamespaceType(\"pid\"))    } else {      ns := specs.LinuxNamespace{Type: \"pid\"}       setNamespace(s, ns)    }  return nil}  \n```  \n\n所有命名空间相关的设置`Spec`最后都会作为`Create`函数的入参在创建新的容器时进行设置：\n\n```  \ndaemon.containerd.Create(context.Background(), container.ID, spec, createOptions)  \n  \n```  \n\n所有与命名空间的相关的设置都是在上述的两个函数中完成的，Docker 通过命名空间成功完成了与宿主机进程和网络的隔离。\n\n## 网络\n\n如果 Docker 的容器通过 Linux 的命名空间完成了与宿主机进程的网络隔离，但是却有没有办法通过宿主机的网络与整个互联网相连，就会产生很多限制，所以 Docker 虽然可以通过命名空间创建一个隔离的网络环境，但是 Docker 中的服务仍然需要与外界相连才能发挥作用。\n\n每一个使用`docker run`启动的容器其实都具有单独的网络命名空间，Docker 为我们提供了四种不同的网络模式，Host、Container、None 和 Bridge 模式。\n\n![docker-network](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/2017-11-30-docker-network.png)\n\n在这一部分，我们将介绍 Docker 默认的网络设置模式：网桥模式。在这种模式下，除了分配隔离的网络命名空间之外，Docker 还会为所有的容器设置 IP 地址。当 Docker 服务器在主机上启动之后会创建新的虚拟网桥 docker0，随后在该主机上启动的全部服务在默认情况下都与该网桥相连。\n\n![docker-network-topology](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/2017-11-30-docker-network-topology.png)\n\n在默认情况下，每一个容器在创建时都会创建一对虚拟网卡，两个虚拟网卡组成了数据的通道，其中一个会放在创建的容器中，会加入到名为 docker0 网桥中。我们可以使用如下的命令来查看当前网桥的接口：\n\n```  \n$ brctl showbridge name    bridge id     STP enabled    interfacesdocker0     8000.0242a6654980  no    veth3e84d4f                                  veth9953b75  \n```  \n\ndocker0 会为每一个容器分配一个新的 IP 地址并将 docker0 的 IP 地址设置为默认的网关。网桥 docker0 通过 iptables 中的配置与宿主机器上的网卡相连，所有符合条件的请求都会通过 iptables 转发到 docker0 并由网桥分发给对应的机器。\n\n```  \n$ iptables -t nat -LChain PREROUTING (policy ACCEPT)target     prot opt source               destinationDOCKER     all  --  anywhere             anywhere             ADDRTYPE match dst-type LOCAL Chain DOCKER (2 references)target     prot opt source               destinationRETURN     all  --  anywhere             anywhere  \n```  \n\n我们在当前的机器上使用`docker run -d -p 6379:6379 redis`命令启动了一个新的 Redis 容器，在这之后我们再查看当前`iptables`的 NAT 配置就会看到在`DOCKER`的链中出现了一条新的规则：\n\n```  \nDNAT       tcp  --  anywhere             anywhere             tcp dpt:6379 to:192.168.0.4:6379  \n  \n```  \n\n上述规则会将从任意源发送到当前机器 6379 端口的 TCP 包转发到 192.168.0.4:6379 所在的地址上。\n\n这个地址其实也是 Docker 为 Redis 服务分配的 IP 地址，如果我们在当前机器上直接 ping 这个 IP 地址就会发现它是可以访问到的：\n\n```  \n$ ping 192.168.0.4PING 192.168.0.4 (192.168.0.4) 56(84) bytes of data.64 bytes from 192.168.0.4: icmp_seq=1 ttl=64 time=0.069 ms64 bytes from 192.168.0.4: icmp_seq=2 ttl=64 time=0.043 ms^C--- 192.168.0.4 ping statistics ---2 packets transmitted, 2 received, 0% packet loss, time 999msrtt min/avg/max/mdev = 0.043/0.056/0.069/0.013 ms  \n```  \n\n从上述的一系列现象，我们就可以推测出 Docker 是如何将容器的内部的端口暴露出来并对数据包进行转发的了；当有 Docker 的容器需要将服务暴露给宿主机器，就会为容器分配一个 IP 地址，同时向 iptables 中追加一条新的规则。\n\n![docker-network-forward](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/2017-11-30-docker-network-forward.png)\n\n当我们使用`redis-cli`在宿主机器的命令行中访问 127.0.0.1:6379 的地址时，经过 iptables 的 NAT PREROUTING 将 ip 地址定向到了 192.168.0.4，重定向过的数据包就可以通过 iptables 中的 FILTER 配置，最终在 NAT POSTROUTING 阶段将 ip 地址伪装成 127.0.0.1，到这里虽然从外面看起来我们请求的是 127.0.0.1:6379，但是实际上请求的已经是 Docker 容器暴露出的端口了。\n\n```  \n$ redis-cli -h 127.0.0.1 -p 6379 pingPONG  \n```  \n\nDocker 通过 Linux 的命名空间实现了网络的隔离，又通过 iptables 进行数据包转发，让 Docker 容器能够优雅地为宿主机器或者其他容器提供服务。\n\n### libnetwork\n\n整个网络部分的功能都是通过 Docker 拆分出来的 libnetwork 实现的，它提供了一个连接不同容器的实现，同时也能够为应用给出一个能够提供一致的编程接口和网络层抽象的容器网络模型。\n\n> The goal of libnetwork is to deliver a robust Container Network Model that provides a consistent programming interface and the required network abstractions for applications.\n\nlibnetwork 中最重要的概念，容器网络模型由以下的几个主要组件组成，分别是 Sandbox、Endpoint 和 Network：\n\n![container-network-model](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/2017-11-30-container-network-model.png)\n\n在容器网络模型中，每一个容器内部都包含一个 Sandbox，其中存储着当前容器的网络栈配置，包括容器的接口、路由表和 DNS 设置，Linux 使用网络命名空间实现这个 Sandbox，每一个 Sandbox 中都可能会有一个或多个 Endpoint，在 Linux 上就是一个虚拟的网卡 veth，Sandbox 通过 Endpoint 加入到对应的网络中，这里的网络可能就是我们在上面提到的 Linux 网桥或者 VLAN。\n\n> 想要获得更多与 libnetwork 或者容器网络模型相关的信息，可以阅读[Design · libnetwork](https://github.com/docker/libnetwork/blob/master/docs/design.md)了解更多信息，当然也可以阅读源代码了解不同 OS 对容器网络模型的不同实现。\n\n## 挂载点\n\n虽然我们已经通过 Linux 的命名空间解决了进程和网络隔离的问题，在 Docker 进程中我们已经没有办法访问宿主机器上的其他进程并且限制了网络的访问，但是 Docker 容器中的进程仍然能够访问或者修改宿主机器上的其他目录，这是我们不希望看到的。\n\n在新的进程中创建隔离的挂载点命名空间需要在`clone`函数中传入`CLONE_NEWNS`，这样子进程就能得到父进程挂载点的拷贝，如果不传入这个参数子进程对文件系统的读写都会同步回父进程以及整个主机的文件系统。\n\n如果一个容器需要启动，那么它一定需要提供一个根文件系统（rootfs），容器需要使用这个文件系统来创建一个新的进程，所有二进制的执行都必须在这个根文件系统中。\n\n![libcontainer-filesystem](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/2017-11-30-libcontainer-filesystem.png)\n\n想要正常启动一个容器就需要在 rootfs 中挂载以上的几个特定的目录，除了上述的几个目录需要挂载之外我们还需要建立一些符号链接保证系统 IO 不会出现问题。\n\n![libcontainer-symlinks-and-io](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/2017-11-30-libcontainer-symlinks-and-io.png)\n\n为了保证当前的容器进程没有办法访问宿主机器上其他目录，我们在这里还需要通过 libcontainer 提供的`pivot_root`或者`chroot`函数改变进程能够访问个文件目录的根节点。\n\n```  \n// pivor_rootput_old = mkdir(...);pivot_root(rootfs, put_old);chdir(\"/\");unmount(put_old, MS_DETACH);rmdir(put_old); // chrootmount(rootfs, \"/\", NULL, MS_MOVE, NULL);chroot(\".\");chdir(\"/\");  \n```  \n\n到这里我们就将容器需要的目录挂载到了容器中，同时也禁止当前的容器进程访问宿主机器上的其他目录，保证了不同文件系统的隔离。\n\n> 这一部分的内容是作者在 libcontainer 中的[SPEC.md](https://github.com/opencontainers/runc/blob/master/libcontainer/SPEC.md)文件中找到的，其中包含了 Docker 使用的文件系统的说明，对于 Docker 是否真的使用`chroot`来确保当前的进程无法访问宿主机器的目录，作者其实也没有确切的答案，一是 Docker 项目的代码太多庞大，不知道该从何入手，作者尝试通过 Google 查找相关的结果，但是既找到了无人回答的[问题](https://forums.docker.com/t/does-the-docker-engine-use-chroot/25429)，也得到了与 SPEC 中的描述有冲突的[答案](https://www.quora.com/Do-Docker-containers-use-a-chroot-environment)，如果各位读者有明确的答案可以在博客下面留言，非常感谢。\n\n## chroot  \n在这里不得不简单介绍一下`chroot`（change root），在 Linux 系统中，系统默认的目录就都是以`/`也就是根目录开头的，`chroot`的使用能够改变当前的系统根目录结构，通过改变当前系统的根目录，我们能够限制用户的权利，在新的根目录下并不能够访问旧系统根目录的结构个文件，也就建立了一个与原系统完全隔离的目录结构。\n\n> 与 chroot 的相关内容部分来自[理解 chroot](https://www.ibm.com/developerworks/cn/linux/l-cn-chroot/index.html)一文，各位读者可以阅读这篇文章获得更详细的信息。\n\n## CGroups\n\n我们通过 Linux 的命名空间为新创建的进程隔离了文件系统、网络并与宿主机器之间的进程相互隔离，但是命名空间并不能够为我们提供物理资源上的隔离，比如 CPU 或者内存，如果在同一台机器上运行了多个对彼此以及宿主机器一无所知的『容器』，这些容器却共同占用了宿主机器的物理资源。\n\n![docker-shared-resources](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/2017-11-30-docker-shared-resources.png)\n\n如果其中的某一个容器正在执行 CPU 密集型的任务，那么就会影响其他容器中任务的性能与执行效率，导致多个容器相互影响并且抢占资源。如何对多个容器的资源使用进行限制就成了解决进程虚拟资源隔离之后的主要问题，而 Control Groups（简称 CGroups）就是能够隔离宿主机器上的物理资源，例如 CPU、内存、磁盘 I/O 和网络带宽。\n\n每一个 CGroup 都是一组被相同的标准和参数限制的进程，不同的 CGroup 之间是有层级关系的，也就是说它们之间可以从父类继承一些用于限制资源使用的标准和参数。\n\n![cgroups-inheritance](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/2017-11-30-cgroups-inheritance.png)\n\nLinux 的 CGroup 能够为一组进程分配资源，也就是我们在上面提到的 CPU、内存、网络带宽等资源，通过对资源的分配，CGroup 能够提供以下的几种功能：\n\n![groups-features](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/2017-11-30-groups-features.png)\n\n> 在 CGroup 中，所有的任务就是一个系统的一个进程，而 CGroup 就是一组按照某种标准划分的进程，在 CGroup 这种机制中，所有的资源控制都是以 CGroup 作为单位实现的，每一个进程都可以随时加入一个 CGroup 也可以随时退出一个 CGroup。\n> > –[CGroup 介绍、应用实例及原理描述](https://www.ibm.com/developerworks/cn/linux/1506_cgroup/index.html)\n\nLinux 使用文件系统来实现 CGroup，我们可以直接使用下面的命令查看当前的 CGroup 中有哪些子系统：\n\n```  \n$ lssubsys -mcpuset /sys/fs/cgroup/cpusetcpu /sys/fs/cgroup/cpucpuacct /sys/fs/cgroup/cpuacctmemory /sys/fs/cgroup/memorydevices /sys/fs/cgroup/devicesfreezer /sys/fs/cgroup/freezerblkio /sys/fs/cgroup/blkioperf_event /sys/fs/cgroup/perf_eventhugetlb /sys/fs/cgroup/hugetlb  \n```  \n\n大多数 Linux 的发行版都有着非常相似的子系统，而之所以将上面的 cpuset、cpu 等东西称作子系统，是因为它们能够为对应的控制组分配资源并限制资源的使用。\n\n如果我们想要创建一个新的 cgroup 只需要在想要分配或者限制资源的子系统下面创建一个新的文件夹，然后这个文件夹下就会自动出现很多的内容，如果你在 Linux 上安装了 Docker，你就会发现所有子系统的目录下都有一个名为 docker 的文件夹：\n\n```  \n$ ls cpucgroup.clone_children  ...cpu.stat  docker  notify_on_release release_agent tasks $ ls cpu/docker/9c3057f1291b53fd54a3d12023d2644efe6a7db6ddf330436ae73ac92d401cf1 cgroup.clone_children  ...cpu.stat  notify_on_release release_agent tasks  \n```  \n\n`9c3057xxx`其实就是我们运行的一个 Docker 容器，启动这个容器时，Docker 会为这个容器创建一个与容器标识符相同的 CGroup，在当前的主机上 CGroup 就会有以下的层级关系：\n\n![linux-cgroups](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/2017-11-30-linux-cgroups.png)\n\n每一个 CGroup 下面都有一个`tasks`文件，其中存储着属于当前控制组的所有进程的 pid，作为负责 cpu 的子系统，`cpu.cfs_quota_us`文件中的内容能够对 CPU 的使用作出限制，如果当前文件的内容为 50000，那么当前控制组中的全部进程的 CPU 占用率不能超过 50%。\n\n如果系统管理员想要控制 Docker 某个容器的资源使用率就可以在`docker`这个父控制组下面找到对应的子控制组并且改变它们对应文件的内容，当然我们也可以直接在程序运行时就使用参数，让 Docker 进程去改变相应文件中的内容。\n\n```  \n$ docker run -it -d --cpu-quota=50000 busybox53861305258ecdd7f5d2a3240af694aec9adb91cd4c7e210b757f71153cdd274$ cd 53861305258ecdd7f5d2a3240af694aec9adb91cd4c7e210b757f71153cdd274/$ lscgroup.clone_children  cgroup.event_control  cgroup.procs  cpu.cfs_period_us  cpu.cfs_quota_us  cpu.shares  cpu.stat  notify_on_release  tasks$ cat cpu.cfs_quota_us50000  \n```  \n\n当我们使用 Docker 关闭掉正在运行的容器时，Docker 的子控制组对应的文件夹也会被 Docker 进程移除，Docker 在使用 CGroup 时其实也只是做了一些创建文件夹改变文件内容的文件操作，不过 CGroup 的使用也确实解决了我们限制子容器资源占用的问题，系统管理员能够为多个容器合理的分配资源并且不会出现多个容器互相抢占资源的问题。\n\n## UnionFS\n\nLinux 的命名空间和控制组分别解决了不同资源隔离的问题，前者解决了进程、网络以及文件系统的隔离，后者实现了 CPU、内存等资源的隔离，但是在 Docker 中还有另一个非常重要的问题需要解决 - 也就是镜像。\n\n镜像到底是什么，它又是如何组成和组织的是作者使用 Docker 以来的一段时间内一直比较让作者感到困惑的问题，我们可以使用`docker run`非常轻松地从远程下载 Docker 的镜像并在本地运行。\n\nDocker 镜像其实本质就是一个压缩包，我们可以使用下面的命令将一个 Docker 镜像中的文件导出：\n\n```  \n$ docker export $(docker create busybox) | tar -C rootfs -xvf -$ lsbin  dev  etc  home proc root sys  tmp  usr  var  \n```  \n\n你可以看到这个 busybox 镜像中的目录结构与 Linux 操作系统的根目录中的内容并没有太多的区别，可以说Docker 镜像就是一个文件。\n\n## 存储驱动\n\nDocker 使用了一系列不同的存储驱动管理镜像内的文件系统并运行容器，这些存储驱动与 Docker 卷（volume）有些不同，存储引擎管理着能够在多个容器之间共享的存储。\n\n想要理解 Docker 使用的存储驱动，我们首先需要理解 Docker 是如何构建并且存储镜像的，也需要明白 Docker 的镜像是如何被每一个容器所使用的；Docker 中的每一个镜像都是由一系列只读的层组成的，Dockerfile 中的每一个命令都会在已有的只读层上创建一个新的层：\n\n```  \nFROM ubuntu:15.04COPY . /appRUN make /appCMD python /app/app.py  \n```  \n\n容器中的每一层都只对当前容器进行了非常小的修改，上述的 Dockerfile 文件会构建一个拥有四层 layer 的镜像：\n\n![docker-container-laye](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/2017-11-30-docker-container-layer.png)\n\n当镜像被`docker run`命令创建时就会在镜像的最上层添加一个可写的层，也就是容器层，所有对于运行时容器的修改其实都是对这个容器读写层的修改。\n\n容器和镜像的区别就在于，所有的镜像都是只读的，而每一个容器其实等于镜像加上一个可读写的层，也就是同一个镜像可以对应多个容器。\n\n![docker-images-and-container](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/2017-12-06-docker-images-and-container.png)\n\n## AUFS\n\nUnionFS 其实是一种为 Linux 操作系统设计的用于把多个文件系统『联合』到同一个挂载点的文件系统服务。而 AUFS 即 Advanced UnionFS 其实就是 UnionFS 的升级版，它能够提供更优秀的性能和效率。\n\nAUFS 作为联合文件系统，它能够将不同文件夹中的层联合（Union）到了同一个文件夹中，这些文件夹在 AUFS 中称作分支，整个『联合』的过程被称为_联合挂载（Union Mount）_：\n\n![docker-aufs](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/2017-11-30-docker-aufs.png)\n\n每一个镜像层或者容器层都是`/var/lib/docker/`目录下的一个子文件夹；在 Docker 中，所有镜像层和容器层的内容都存储在`/var/lib/docker/aufs/diff/`目录中：\n\n```  \n$ ls /var/lib/docker/aufs/diff/00adcccc1a55a36a610a6ebb3e07cc35577f2f5a3b671be3dbc0e74db9ca691c       93604f232a831b22aeb372d5b11af8c8779feb96590a6dc36a80140e38e764d800adcccc1a55a36a610a6ebb3e07cc35577f2f5a3b671be3dbc0e74db9ca691c-init  93604f232a831b22aeb372d5b11af8c8779feb96590a6dc36a80140e38e764d8-init019a8283e2ff6fca8d0a07884c78b41662979f848190f0658813bb6a9a464a90       93b06191602b7934fafc984fbacae02911b579769d0debd89cf2a032e7f35cfa...  \n```  \n\n而`/var/lib/docker/aufs/layers/`中存储着镜像层的元数据，每一个文件都保存着镜像层的元数据，最后的`/var/lib/docker/aufs/mnt/`包含镜像或者容器层的挂载点，最终会被 Docker 通过联合的方式进行组装。\n\n![docker-filesystems](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/2017-11-30-docker-filesystems.png)\n\n上面的这张图片非常好的展示了组装的过程，每一个镜像层都是建立在另一个镜像层之上的，同时所有的镜像层都是只读的，只有每个容器最顶层的容器层才可以被用户直接读写，所有的容器都建立在一些底层服务（Kernel）上，包括命名空间、控制组、rootfs 等等，这种容器的组装方式提供了非常大的灵活性，只读的镜像层通过共享也能够减少磁盘的占用。\n\n## 其他存储驱动\n\nAUFS 只是 Docker 使用的存储驱动的一种，除了 AUFS 之外，Docker 还支持了不同的存储驱动，包括`aufs`、`devicemapper`、`overlay2`、`zfs`和`vfs`等等，在最新的 Docker 中，`overlay2`取代了`aufs`成为了推荐的存储驱动，但是在没有`overlay2`驱动的机器上仍然会使用`aufs`作为 Docker 的默认驱动。\n\n![docker-storage-driver](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/2017-11-30-docker-storage-driver.png)\n\n不同的存储驱动在存储镜像和容器文件时也有着完全不同的实现，有兴趣的读者可以在 Docker 的官方文档[Select a storage driver](https://docs.docker.com/engine/userguide/storagedriver/selectadriver/)中找到相应的内容。\n\n想要查看当前系统的 Docker 上使用了哪种存储驱动只需要使用以下的命令就能得到相对应的信息：\n\n```  \n$ docker info | grep StorageStorage Driver: aufs  \n```  \n\n作者的这台 Ubuntu 上由于没有`overlay2`存储驱动，所以使用`aufs`作为 Docker 的默认存储驱动。\n\n## 总结\n\nDocker 目前已经成为了非常主流的技术，已经在很多成熟公司的生产环境中使用，但是 Docker 的核心技术其实已经有很多年的历史了，Linux 命名空间、控制组和 UnionFS 三大技术支撑了目前 Docker 的实现，也是 Docker 能够出现的最重要原因。\n\n作者在学习 Docker 实现原理的过程中查阅了非常多的资料，从中也学习到了很多与 Linux 操作系统相关的知识，不过由于 Docker 目前的代码库实在是太过庞大，想要从源代码的角度完全理解 Docker 实现的细节已经是非常困难的了，但是如果各位读者真的对其实现细节感兴趣，可以从[Docker CE](https://github.com/docker/docker-ce)的源代码开始了解 Docker 的原理。\n"
  },
  {
    "path": "docs/backend/后端技术杂谈：Elasticsearch与solr入门实践.md",
    "content": "# 目录\n\n  * [一、安装](#一、安装)\n  * [二、基本概念](#二、基本概念)\n    * [2.1 Node 与 Cluster](#21-node-与-cluster)\n    * [2.2 Index](#22-index)\n    * [2.3 Document](#23-document)\n    * [2.4 Type](#24-type)\n  * [三、新建和删除 Index](#三、新建和删除-index)\n  * [四、中文分词设置](#四、中文分词设置)\n  * [五、数据操作](#五、数据操作)\n    * [5.1 新增记录](#51-新增记录)\n    * [5.2 查看记录](#52-查看记录)\n    * [5.3 删除记录](#53-删除记录)\n    * [5.4 更新记录](#54-更新记录)\n  * [六、数据查询](#六、数据查询)\n    * [6.1 返回所有记录](#61-返回所有记录)\n    * [6.2 全文搜索](#62-全文搜索)\n    * [6.3 逻辑运算](#63-逻辑运算)\n  * [七、参考链接](#七、参考链接)\n      * [一、前言](#一、前言)\n      * [二、安装](#二、安装)\n      * [三、创建索引](#三、创建索引)\n      * [四、搜索干预](#四、搜索干预)\n      * [五、中文分词](#五、中文分词)\n      * [六、总结](#六、总结)\n      * [七、附录](#七、附录)\n* [搜索引擎选型整理：Elasticsearch vs Solr](#搜索引擎选型整理：elasticsearch-vs-solr)\n  * [Elasticsearch简介](#elasticsearch简介)\n  * [Elasticsearch的优缺点](#elasticsearch的优缺点)\n    * [优点](#优点)\n    * [缺点](#缺点)\n  * [Solr简介](#solr简介)\n  * [Solr的优缺点](#solr的优缺点)\n    * [优点](#优点-1)\n    * [缺点](#缺点-1)\n  * [Elasticsearch与Solr的比较](#elasticsearch与solr的比较)\n  * [实际生产环境测试](#实际生产环境测试)\n  * [Elasticsearch 与 Solr 的比较总结](#elasticsearch-与-solr-的比较总结)\n  * [其他基于Lucene的开源搜索引擎解决方案](#其他基于lucene的开源搜索引擎解决方案)\n\n\n[toc]\n作者：阮一峰\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本系列文章将整理到我的个人博客\n\n> www.how2playlife.com\n\n更多Java技术文章会更新在我的微信公众号【Java技术江湖】上，欢迎关注\n该系列博文会介绍常见的后端技术，这对后端工程师来说是一种综合能力，我们会逐步了解搜索技术，云计算相关技术、大数据研发等常见的技术喜提，以便让你更完整地了解后端技术栈的全貌，为后续参与分布式应用的开发和学习做好准备。\n\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系我，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n原文链接：www.ruanyifeng.com\n\n\n它可以快速地储存、搜索和分析海量数据。维基百科、Stack Overflow、Github 都采用它。\n\nElastic 的底层是开源库Lucene\n但是，你没法直接用 Lucene，必须自己写代码去调用它的接口。\nElastic 是 Lucene 的封装，提供了 REST API 的操作接口，开箱即用。\n\n本文从零开始，讲解如何使用 Elastic 搭建自己的全文搜索引擎。每一步都有详细的说明，大家跟着做就能学会。\n\n## 一、安装\n\nElastic 需要 Java 8 环境。注意要保证环境变量`JAVA_HOME`正确设置。\n\n安装完 Java，就可以跟着官方文档安装 Elastic。直接下载压缩包比较简单。\n\n> ```\n> $ wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.5.1.zip$ unzip elasticsearch-5.5.1.zip$ cd elasticsearch-5.5.1/ \n> ```\n\n接着，进入解压后的目录，运行下面的命令，启动 Elastic。\n\n> ```\n> $ ./bin/elasticsearch\n> ```\n\n\n> ```\n> $ sudo sysctl -w vm.max_map_count=262144\n> ```\n\n如果一切正常，Elastic 就会在默认的9200端口运行。这时，打开另一个命令行窗口，请求该端口，会得到说明信息。\n\n```\n> $ curl localhost:9200\n{\n\"name\": \"atntrTf\",\n\"cluster_name\": \"elasticsearch\",\n\"cluster_uuid\": \"tf9250XhQ6ee4h7YI11anA\",\n\"version\": {\n\"number\": \"5.5.1\",\n\"build_hash\": \"19c13d0\",\n\"build_date\": \"2017-07-18T20:44:24.823Z\",\n\"build_snapshot\": false,\n\"lucene_version\": \"6.6.0\"\n},\n\"tagline\": \"You Know, for Search\"\n}\n```\n\n上面代码中，请求9200端口，Elastic 返回一个 JSON 对象，包含当前节点、集群、版本等信息。\n\n按下 Ctrl + C，Elastic 就会停止运行。\n\n默认情况下，Elastic 只允许本机访问，如果需要远程访问，可以修改 Elastic 安装目录的`config/elasticsearch.yml`文件，去掉`network.host`的注释，将它的值改成`0.0.0.0`，然后重新启动 Elastic。\n\n> ```\n> network.host: 0.0.0.0\n> ```\n\n上面代码中，设成`0.0.0.0`让任何人都可以访问。线上服务不要这样设置，要设成具体的 IP。\n\n## 二、基本概念\n\n### 2.1 Node 与 Cluster\n\nElastic 本质上是一个分布式数据库，允许多台服务器协同工作，每台服务器可以运行多个 Elastic 实例。\n\n单个 Elastic 实例称为一个节点（node）。一组节点构成一个集群（cluster）。\n\n### 2.2 Index\n\nElastic 会索引所有字段，经过处理后写入一个反向索引（Inverted Index）。查找数据的时候，直接查找该索引。\n\n所以，Elastic 数据管理的顶层单位就叫做 Index（索引）。它是单个数据库的同义词。每个 Index （即数据库）的名字必须是小写。\n\n下面的命令可以查看当前节点的所有 Index。\n\n> ```\n> $ curl -X GET 'http://localhost:9200/_cat/indices?v'\n> ```\n\n### 2.3 Document\n\nIndex 里面单条的记录称为 Document（文档）。许多条 Document 构成了一个 Index。\n\nDocument 使用 JSON 格式表示，下面是一个例子。\n\n> ```\n> {  \"user\": \"张三\",  \"title\": \"工程师\",  \"desc\": \"数据库管理\"}\n> ```\n\n同一个 Index 里面的 Document，不要求有相同的结构（scheme），但是最好保持相同，这样有利于提高搜索效率。\n\n### 2.4 Type\n\nDocument 可以分组，比如`weather`这个 Index 里面，可以按城市分组（北京和上海），也可以按气候分组（晴天和雨天）。这种分组就叫做 Type，它是虚拟的逻辑分组，用来过滤 Document。\n\n不同的 Type 应该有相似的结构（schema），举例来说，`id`字段不能在这个组是字符串，在另一个组是数值。\n这是与关系型数据库的表的一个区别。\n性质完全不同的数据（比如`products`和`logs`）应该存成两个 Index，而不是一个 Index 里面的两个 Type（虽然可以做到）。\n\n下面的命令可以列出每个 Index 所包含的 Type。\n\n> ```\n> $ curl 'localhost:9200/_mapping?pretty=true'\n> ```\n\n根据规划Elastic 6.x 版只允许每个 Index 包含一个 Type，7.x 版将会彻底移除 Type。\n\n## 三、新建和删除 Index\n\n新建 Index，可以直接向 Elastic 服务器发出 PUT 请求。下面的例子是新建一个名叫`weather`的 Index。\n\n> ```\n> $ curl -X PUT 'localhost:9200/weather'\n> ```\n\n服务器返回一个 JSON 对象，里面的`acknowledged`字段表示操作成功。\n\n> ```\n> {  \"acknowledged\":true,  \"shards_acknowledged\":true}\n> ```\n\n然后，我们发出 DELETE 请求，删除这个 Index。\n\n> ```\n> $ curl -X DELETE 'localhost:9200/weather'\n> ```\n\n## 四、中文分词设置\n\n首先，安装中文分词插件。这里使用的是ik，也可以考虑其他插件（比如smartcn）。\n\n> ```\n> $ ./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v5.5.1/elasticsearch-analysis-ik-5.5.1.zip\n> ```\n\n上面代码安装的是5.5.1版的插件，与 Elastic 5.5.1 配合使用。\n\n接着，重新启动 Elastic，就会自动加载这个新安装的插件。\n\n然后，新建一个 Index，指定需要分词的字段。这一步根据数据结构而异，下面的命令只针对本文。基本上，凡是需要搜索的中文字段，都要单独设置一下。\n\n```\n> $ curl -X PUT 'localhost:9200/accounts' -d \n{\n\t\"mappings\": {\n\t\t\"person\": {\n\t\t\t\"properties\": {\n\t\t\t\t\"user\": {\n\t\t\t\t\t\"type\": \"text\",\n\t\t\t\t\t\"analyzer\": \"ik_max_word\",\n\t\t\t\t\t\"search_analyzer\": \"ik_max_word\"\n\t\t\t\t},\n\t\t\t\t\"title\": {\n\t\t\t\t\t\"type\": \"text\",\n\t\t\t\t\t\"analyzer\": \"ik_max_word\",\n\t\t\t\t\t\"search_analyzer\": \"ik_max_word\"\n\t\t\t\t},\n\t\t\t\t\"desc\": {\n\t\t\t\t\t\"type\": \"text\",\n\t\t\t\t\t\"analyzer\": \"ik_max_word\",\n\t\t\t\t\t\"search_analyzer\": \"ik_max_word\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}```\n\n上面代码中，首先新建一个名称为`accounts`的 Index，里面有一个名称为`person`的 Type。`person`有三个字段。\n\n> *   user\n> *   title\n> *   desc\n\n这三个字段都是中文，而且类型都是文本（text），所以需要指定中文分词器，不能使用默认的英文分词器。\n\nElastic 的分词器称为analyzer。我们对每个字段指定分词器。\n\n> ```\n> \"user\": {  \"type\": \"text\",  \"analyzer\": \"ik_max_word\",  \"search_analyzer\": \"ik_max_word\"}\n> ```\n\n上面代码中，`analyzer`是字段文本的分词器，`search_analyzer`是搜索词的分词器。`ik_max_word`分词器是插件`ik`提供的，可以对文本进行最大数量的分词。\n\n## 五、数据操作\n\n### 5.1 新增记录\n\n向指定的 /Index/Type 发送 PUT 请求，就可以在 Index 里面新增一条记录。比如，向`/accounts/person`发送请求，就可以新增一条人员记录。\n\n> ```\n> $ curl -X PUT 'localhost:9200/accounts/person/1' -d '{  \"user\": \"张三\",  \"title\": \"工程师\",  \"desc\": \"数据库管理\"}' \n> ```\n\n服务器返回的 JSON 对象，会给出 Index、Type、Id、Version 等信息。\n\n> ```\n> {  \"_index\":\"accounts\",  \"_type\":\"person\",  \"_id\":\"1\",  \"_version\":1,  \"result\":\"created\",  \"_shards\":{\"total\":2,\"successful\":1,\"failed\":0},  \"created\":true}\n> ```\n\n如果你仔细看，会发现请求路径是`/accounts/person/1`，最后的`1`是该条记录的 Id。它不一定是数字，任意字符串（比如`abc`）都可以。\n\n新增记录的时候，也可以不指定 Id，这时要改成 POST 请求。\n\n> ```\n> $ curl -X POST 'localhost:9200/accounts/person' -d '{  \"user\": \"李四\",  \"title\": \"工程师\",  \"desc\": \"系统管理\"}'\n> ```\n\n上面代码中，向`/accounts/person`发出一个 POST 请求，添加一个记录。这时，服务器返回的 JSON 对象里面，`_id`字段就是一个随机字符串。\n\n```\n{\n\t\"_index\": \"accounts\",\n\t\"_type\": \"person\",\n\t\"_id\": \"AV3qGfrC6jMbsbXb6k1p\",\n\t\"_version\": 1,\n\t\"result\": \"created\",\n\t\"_shards\": {\n\t\t\"total\": 2,\n\t\t\"successful\": 1,\n\t\t\"failed\": 0\n\t},\n\t\"created\": true\n}\n```\n\n注意，如果没有先创建 Index（这个例子是`accounts`），直接执行上面的命令，Elastic 也不会报错，而是直接生成指定的 Index。所以，打字的时候要小心，不要写错 Index 的名称。\n\n### 5.2 查看记录\n\n向`/Index/Type/Id`发出 GET 请求，就可以查看这条记录。\n\n> ```\n> $ curl 'localhost:9200/accounts/person/1?pretty=true'\n> ```\n\n上面代码请求查看`/accounts/person/1`这条记录，URL 的参数`pretty=true`表示以易读的格式返回。\n\n返回的数据中，`found`字段表示查询成功，`_source`字段返回原始记录。\n\n```\n{\n\t\"_index\": \"accounts\",\n\t\"_type\": \"person\",\n\t\"_id\": \"1\",\n\t\"_version\": 1,\n\t\"found\": true,\n\t\"_source\": {\n\t\t\"user\": \"张三\",\n\t\t\"title\": \"工程师\",\n\t\t\"desc\": \"数据库管理\"\n\t}\n}\n```\n\n如果 Id 不正确，就查不到数据，`found`字段就是`false`。\n\n> ```\n> $ curl 'localhost:9200/weather/beijing/abc?pretty=true' {  \"_index\" : \"accounts\",  \"_type\" : \"person\",  \"_id\" : \"abc\",  \"found\" : false}\n> ```\n\n### 5.3 删除记录\n\n删除记录就是发出 DELETE 请求。\n\n> ```\n> $ curl -X DELETE 'localhost:9200/accounts/person/1'\n> ```\n\n这里先不要删除这条记录，后面还要用到。\n\n### 5.4 更新记录\n\n更新记录就是使用 PUT 请求，重新发送一次数据。\n\n>```\n> $ curl -X PUT 'localhost:9200/accounts/person/1' -d \n> '{    \"user\" : \"张三\",    \"title\" : \"工程师\",    \"desc\" : \"数据库管理，软件开发\"}'  \n>```\n\n上面代码中，我们将原始数据从\"数据库管理\"改成\"数据库管理，软件开发\"。 返回结果里面，有几个字段发生了变化。\n\n> ```\n> \"_version\" : 2,\"result\" : \"updated\",\"created\" : false\n> ```\n\n可以看到，记录的 Id 没变，但是版本（version）从`1`变成`2`，操作类型（result）从`created`变成`updated`，`created`字段变成`false`，因为这次不是新建记录。\n\n## 六、数据查询\n\n### 6.1 返回所有记录\n\n使用 GET 方法，直接请求`/Index/Type/_search`，就会返回所有记录。\n\n```\n> $ curl 'localhost:9200/accounts/person/_search' \n{\n\t\"took\": 2,\n\t\"timed_out\": false,\n\t\"_shards\": {\n\t\t\"total\": 5,\n\t\t\"successful\": 5,\n\t\t\"failed\": 0\n\t},\n\t\"hits\": {\n\t\t\"total\": 2,\n\t\t\"max_score\": 1.0,\n\t\t\"hits\": [{\n\t\t\t\"_index\": \"accounts\",\n\t\t\t\"_type\": \"person\",\n\t\t\t\"_id\": \"AV3qGfrC6jMbsbXb6k1p\",\n\t\t\t\"_score\": 1.0,\n\t\t\t\"_source\": {\n\t\t\t\t\"user\": \"李四\",\n\t\t\t\t\"title\": \"工程师\",\n\t\t\t\t\"desc\": \"系统管理\"\n\t\t\t}\n\t\t}, {\n\t\t\t\"_index\": \"accounts\",\n\t\t\t\"_type\": \"person\",\n\t\t\t\"_id\": \"1\",\n\t\t\t\"_score\": 1.0,\n\t\t\t\"_source\": {\n\t\t\t\t\"user\": \"张三\",\n\t\t\t\t\"title\": \"工程师\",\n\t\t\t\t\"desc\": \"数据库管理，软件开发\"\n\t\t\t}\n\t\t}]\n\t}\n}\n```\n\n上面代码中，返回结果的`took`字段表示该操作的耗时（单位为毫秒），`timed_out`字段表示是否超时，`hits`字段表示命中的记录，里面子字段的含义如下。\n\n> *   `total`：返回记录数，本例是2条。\n> *   `max_score`：最高的匹配程度，本例是`1.0`。\n> *   `hits`：返回的记录组成的数组。\n\n返回的记录中，每条记录都有一个`_score`字段，表示匹配的程序，默认是按照这个字段降序排列。\n\n### 6.2 全文搜索\n\nElastic 的查询非常特别，使用自己的查询语法，要求 GET 请求带有数据体。\n\n```\n> $ curl 'localhost:9200/accounts/person/_search'  -d \n'{  \"query\" : { \"match\" : { \"desc\" : \"软件\" }}}'\n```\n\n上面代码使用Match 查询，指定的匹配条件是`desc`字段里面包含\"软件\"这个词。返回结果如下。\n\n```\n{\n\t\"took\": 3,\n\t\"timed_out\": false,\n\t\"_shards\": {\n\t\t\"total\": 5,\n\t\t\"successful\": 5,\n\t\t\"failed\": 0\n\t},\n\t\"hits\": {\n\t\t\"total\": 1,\n\t\t\"max_score\": 0.28582606,\n\t\t\"hits\": [{\n\t\t\t\"_index\": \"accounts\",\n\t\t\t\"_type\": \"person\",\n\t\t\t\"_id\": \"1\",\n\t\t\t\"_score\": 0.28582606,\n\t\t\t\"_source\": {\n\t\t\t\t\"user\": \"张三\",\n\t\t\t\t\"title\": \"工程师\",\n\t\t\t\t\"desc\": \"数据库管理，软件开发\"\n\t\t\t}\n\t\t}]\n\t}\n}\n```\n\nElastic 默认一次返回10条结果，可以通过`size`字段改变这个设置。\n\n```\n> $ curl 'localhost:9200/accounts/person/_search'  -d \n'{  \"query\" : { \"match\" : { \"desc\" : \"管理\" }},  \"size\": 1}'\n```\n\n上面代码指定，每次只返回一条结果。\n\n还可以通过`from`字段，指定位移。\n\n ```\n> $ curl 'localhost:9200/accounts/person/_search'  -d \n'{  \"query\" : { \"match\" : { \"desc\" : \"管理\" }},  \"from\": 1,  \"size\": 1}'\n ```\n\n上面代码指定，从位置1开始（默认是从位置0开始），只返回一条结果。\n\n### 6.3 逻辑运算\n\n如果有多个搜索关键字， Elastic 认为它们是`or`关系。\n\n```\n> $ curl 'localhost:9200/accounts/person/_search'  -d \n'{  \"query\" : { \"match\" : { \"desc\" : \"软件 系统\" }}}'\n```\n\n上面代码搜索的是`软件 or 系统`。\n\n如果要执行多个关键词的`and`搜索，必须使用[布尔查询](https://link.juejin.im/?target=https%3A%2F%2Fwww.elastic.co%2Fguide%2Fen%2Felasticsearch%2Freference%2F5.5%2Fquery-dsl-bool-query.html)。\n\n```\n> $ curl 'localhost:9200/accounts/person/_search'  -d \n{\n\t\"query\": {\n\t\t\"bool\": {\n\t\t\t\"must\": [{\n\t\t\t\t\"match\": {\n\t\t\t\t\t\"desc\": \"软件\"\n\t\t\t\t}\n\t\t\t}, {\n\t\t\t\t\"match\": {\n\t\t\t\t\t\"desc\": \"系统\"\n\t\t\t\t}\n\t\t\t}]\n\t\t}\n\t}\n}\n```\n\n## 七、参考链接\n\n*   [ElasticSearch 官方手册](https://link.juejin.im/?target=https%3A%2F%2Fwww.elastic.co%2Fguide%2Fen%2Felasticsearch%2Freference%2Fcurrent%2Fgetting-started.html)\n*   [A Practical Introduction to Elasticsearch](https://link.juejin.im/?target=https%3A%2F%2Fwww.elastic.co%2Fblog%2Fa-practical-introduction-to-elasticsearch)\n\n（完）\n\n\n\n\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/19687-3a3865f474573947.png)\n\n\n\n\n\n#### 一、前言\n\n在开发网站/App项目的时候，通常需要搭建搜索服务。比如，新闻类应用需要检索标题/内容，社区类应用需要检索用户/帖子。\n\n对于简单的需求，可以使用数据库的 LIKE 模糊搜索，示例：\n\n> SELECT * FROM news WHERE title LIKE '%法拉利跑车%'\n\n可以查询到所有标题含有 \"法拉利跑车\" 关键词的新闻，但是这种方式有明显的弊端：\n\n> 1、模糊查询性能极低，当数据量庞大的时候，往往会使数据库服务中断；\n>\n> 2、无法查询相关的数据，只能严格在标题中匹配关键词。\n\n因此，需要搭建专门提供搜索功能的服务，具备分词、全文检索等高级功能。 Solr 就是这样一款搜索引擎，可以让你快速搭建适用于自己业务的搜索服务。\n\n#### 二、安装\n\n到官网[http://lucene.apache.org/solr/](https://link.jianshu.com/?t=http://lucene.apache.org/solr/)下载安装包，解压并进入 Solr 目录：\n\n> wget 'http://apache.website-solution.net/lucene/solr/6.2.0/solr-6.2.0.tgz'\n>\n> tar xvf solr-6.2.0.tgz\n>\n> cd solr-6.2.0\n\n目录结构如下：\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/19687-ddbb880dd1a7bcb0.png)\n\n\n\nSolr 6.2 目录结构\n\n\n\n启动 Solr 服务之前，确认已经安装 Java 1.8 ：\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/19687-049501dade838caf.png)\n\n\n\n查看 Java 版本\n\n\n\n启动 Solr 服务：\n\n> ./bin/solr start -m 1g\n\nSolr 将默认监听 8983 端口，其中 -m 1g 指定分配给 JVM 的内存为 1 G。\n\n在浏览器中访问 Solr 管理后台：\n\n> http://127.0.0.1:8983/solr/#/\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/19687-19bdf6ec1077db99.png)\n\n\n\nSolr 管理后台\n\n\n\n创建 Solr 应用：\n\n> ./bin/solr create -c my_news\n\n可以在 solr-6.2.0/server/solr 目录下生成 my_news 文件夹，结构如下：\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/19687-9911b7416917ca06.png)\n\n\n\nmy_news 目录结构\n\n\n\n同时，可以在管理后台看到 my_news：\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/19687-81af0fb0b5d89edd.png)\n\n\n\n管理后台\n\n\n\n#### 三、创建索引\n\n我们将从 MySQL 数据库中导入数据到 Solr 并建立索引。\n\n首先，需要了解 Solr 中的两个概念： 字段(field) 和 字段类型(fieldType)，配置示例如下：\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/19687-cbc2ba3d84087319.png)\n\n\n\nschema.xml 示例\n\n\n\nfield 指定一个字段的名称、是否索引/存储和字段类型。\n\nfieldType 指定一个字段类型的名称以及在查询/索引的时候可能用到的分词插件。\n\n将 solr-6.2.0\\server\\solr\\my_news\\conf 目录下默认的配置文件 managed-schema 重命名为 schema.xml 并加入新的 fieldType：\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/19687-2657cfb3507d1bae.png)\n\n\n\n分词类型\n\n\n\n在 my_news 目录下创建 lib 目录，将用到的分词插件 ik-analyzer-solr5-5.x.jar 加到 lib 目录，结构如下：\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/19687-3a3e436e33fa9311.png)\n\n\n\nmy_news 目录结构\n\n\n\n在 Solr 安装目录下重启服务：\n\n> ./bin/solr restart\n\n可以在管理后台看到新加的类型：\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/19687-5609a84930ed96f0.png)\n\n\n\ntext_ik 类型\n\n\n\n接下来创建和我们数据库字段对应的 field：title 和 content，类型选为 text_ik：\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/19687-a46bba01779c0701.png)\n\n\n\n新建字段 title\n\n\n\n将要导入数据的 MySQL 数据库表结构：\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/19687-ab4dec5179c0f5c3.png)\n\n\n\n\n\n编辑 conf/solrconfig.xml 文件，加入类库和数据库配置：\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/19687-e3dc609b92f395a1.png)\n\n\n\n类库\n\n\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/19687-7a145baf9aa36599.png)\n\n\n\ndataimport config\n\n\n\n同时新建数据库连接配置文件 conf/db-mysql-config.xml ，内容如下：\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/19687-edc3bb352c36e8c2.png)\n\n\n\n数据库配置文件\n\n\n\n将数据库连接组件 mysql-connector-java-5.1.39-bin.jar 放到 lib 目录下，重启 Solr，访问管理后台，执行全量导入数据：\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/19687-a4462a20df0716a2.png)\n\n\n\n全量导入数据\n\n\n\n创建定时更新脚本：\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/19687-f437d561069eedd2.png)\n\n\n\n定时更新脚本\n\n\n\n加入到定时任务，每5分钟增量更新一次索引：\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/19687-73d93e996f0a132c.png)\n\n\n\n定时任务\n\n\n\n在 Solr 管理后台测试搜索结果：\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/19687-9f003409af70ae7a.png)\n\n\n\n分词搜索结果\n\n\n\n至此，基本的搜索引擎搭建完毕，外部应用只需通过 http 协议提供查询参数，就可以获取搜索结果。\n\n#### 四、搜索干预\n\n通常需要对搜索结果进行人工干预，比如编辑推荐、竞价排名或者屏蔽搜索结果。Solr 已经内置了 QueryElevationComponent 插件，可以从配置文件中获取搜索关键词对应的干预列表，并将干预结果排在搜索结果的前面。\n\n在 solrconfig.xml 文件中，可以看到：\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/19687-09494ec9437338cd.png)\n\n\n\n干预其请求配置\n\n\n\n定义了搜索组件 elevator，应用在 /elevate 的搜索请求中，干预结果的配置文件在 solrconfig.xml 同目录下的 elevate.xml 中，干预配置示例：\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/19687-3f2587b4bb0dcee3.png)\n\n\n\n\n\n重启 Solr ，当搜索 \"关键词\" 的时候，id 为 1和 4 的文档将出现在前面，同时 id = 3 的文档被排除在结果之外，可以看到，没有干预的时候，搜索结果为：\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/19687-b12a6ec2234beaef.png)\n\n\n\n无干预结果\n\n\n\n当有搜索干预的时候：\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/19687-f57a54656abc2f62.png)\n\n\n\n干预结果\n\n\n\n通过配置文件干预搜索结果，虽然简单，但是每次更新都要重启 Solr 才能生效，稍显麻烦，我们可以仿照 QueryElevationComponent 类，开发自己的干预组件，例如:从 Redis 中读取干预配置。\n\n#### 五、中文分词\n\n中文的搜索质量，和分词的效果息息相关，可以在 Solr 管理后台测试分词：\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/19687-bc4dfa9a4801846f.png)\n\n\n\n分词结果测试\n\n\n\n上例可以看到，使用[IKAnalyzer](https://blog.csdn.net/a724888/article/details/80993677)分词插件，对 “北京科技大学” 分词的测试结果。当用户搜索 “北京”、“科技大学”、“科技大”、“科技”、“大学” 这些关键词的时候，都会搜索到文本内容含 “北京科技大学” 的文档。\n\n常用的中文分词插件有 IKAnalyzer、mmseg4j和 Solr 自带的 smartcn 等，分词效果各有优劣，具体选择哪个，可以根据自己的业务场景，分别测试效果再选择。\n\n分词插件一般都有自己的默认词库和扩展词库，默认词库包含了绝大多数常用的中文词语。如果默认词库无法满足你的需求，比如某些专业领域的词汇，可以在扩展词库中手动添加，这样分词插件就能识别新词语了。\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/19687-ac9e935a3b98661c.png)\n\n\n\n分词插件扩展词库配置示例\n\n\n\n分词插件还可以指定停止词库，将某些无意义的词汇剔出分词结果，比如：“的”、“哼” 等，例如：\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/19687-34e025db9e4db451.png)\n\n\n\n去除无意义的词\n\n\n\n#### 六、总结\n\n以上介绍了 Solr 最常用的一些功能，Solr 本身还有很多其他丰富的功能，比如分布式部署。\n\n希望对你有所帮助。\n\n#### 七、附录\n\n1、参考资料：\n\n[https://wiki.apache.org/solr/](https://link.jianshu.com/?t=https://wiki.apache.org/solr/)\n\n[http://lucene.apache.org/solr/quickstart.html](https://link.jianshu.com/?t=http://lucene.apache.org/solr/quickstart.html)\n\n[https://cwiki.apache.org/confluence/display/solr/Apache+Solr+Reference+Guide](https://link.jianshu.com/?t=https://cwiki.apache.org/confluence/display/solr/Apache+Solr+Reference+Guide)\n\n2、上述 Demo 中用到的所有配置文件、Jar 包：\n\n[https://github.com/Ceelog/OpenSchool/blob/master/my_news.zip](https://link.jianshu.com/?t=https://github.com/Ceelog/OpenSchool/blob/master/my_news.zip)\n\n3、还有疑问？联系作者微博/微信[@Ceelog](https://link.jianshu.com/?t=http://weibo.com/ceelog/)\n\n\n\n# 搜索引擎选型整理：Elasticsearch vs Solr\n\n\n\n> 本文首发于[我的博客](https://link.juejin.im/?target=https%3A%2F%2Fblog.kittypanic.com%2F)\n> 原文链接：[Elasticsearch 与 Solr 的比较](https://link.juejin.im/?target=https%3A%2F%2Fblog.kittypanic.com%2Felastic_vs_solr%2F)\n\n## Elasticsearch简介\n\nElasticsearch是一个实时的分布式搜索和分析引擎。它可以帮助你用前所未有的速度去处理大规模数据。\n\n它可以用于全文搜索，结构化搜索以及分析，当然你也可以将这三者进行组合。\n\nElasticsearch是一个建立在全文搜索引擎 Apache Lucene(TM) 基础上的搜索引擎，可以说Lucene是当今最先进，最高效的全功能开源搜索引擎框架。\n\n但是Lucene只是一个框架，要充分利用它的功能，需要使用JAVA，并且在程序中集成Lucene。需要很多的学习了解，才能明白它是如何运行的，Lucene确实非常复杂。\n\nElasticsearch使用Lucene作为内部引擎，但是在使用它做全文搜索时，只需要使用统一开发好的API即可，而不需要了解其背后复杂的Lucene的运行原理。\n\n当然Elasticsearch并不仅仅是Lucene这么简单，它不但包括了全文搜索功能，还可以进行以下工作:\n\n*   分布式实时文件存储，并将每一个字段都编入索引，使其可以被搜索。\n\n*   实时分析的分布式搜索引擎。\n\n*   可以扩展到上百台服务器，处理PB级别的结构化或非结构化数据。\n\n这么多的功能被集成到一台服务器上，你可以轻松地通过客户端或者任何你喜欢的程序语言与ES的RESTful API进行交流。\n\nElasticsearch的上手是非常简单的。它附带了很多非常合理的默认值，这让初学者很好地避免一上手就要面对复杂的理论，\n\n它安装好了就可以使用了，用很小的学习成本就可以变得很有生产力。\n\n随着越学越深入，还可以利用Elasticsearch更多高级的功能，整个引擎可以很灵活地进行配置。可以根据自身需求来定制属于自己的Elasticsearch。\n\n使用案例：\n\n*   维基百科使用Elasticsearch来进行全文搜做并高亮显示关键词，以及提供search-as-you-type、did-you-mean等搜索建议功能。\n\n*   英国卫报使用Elasticsearch来处理访客日志，以便能将公众对不同文章的反应实时地反馈给各位编辑。\n\n*   StackOverflow将全文搜索与地理位置和相关信息进行结合，以提供more-like-this相关问题的展现。\n\n*   GitHub使用Elasticsearch来检索超过1300亿行代码。\n\n*   每天，Goldman Sachs使用它来处理5TB数据的索引，还有很多投行使用它来分析股票市场的变动。\n\n但是Elasticsearch并不只是面向大型企业的，它还帮助了很多类似DataDog以及Klout的创业公司进行了功能的扩展。\n\n## Elasticsearch的优缺点\n\n### 优点\n\n1.  Elasticsearch是分布式的。不需要其他组件，分发是实时的，被叫做\"Push replication\"。\n\n*   Elasticsearch 完全支持 Apache Lucene 的接近实时的搜索。\n*   处理多租户（[multitenancy](https://link.juejin.im/?target=http%3A%2F%2Fen.wikipedia.org%2Fwiki%2FMultitenancy)）不需要特殊配置，而Solr则需要更多的高级设置。\n*   Elasticsearch 采用 Gateway 的概念，使得完备份更加简单。\n*   各节点组成对等的网络结构，某些节点出现故障时会自动分配其他节点代替其进行工作。\n\n### 缺点\n\n1.  只有一名开发者（当前Elasticsearch GitHub组织已经不只如此，已经有了相当活跃的维护者）\n\n*   还不够自动（不适合当前新的Index Warmup API）\n\n## Solr简介\n\nSolr（读作“solar”）是Apache Lucene项目的开源企业搜索平台。其主要功能包括全文检索、命中标示、分面搜索、动态聚类、数据库集成，以及富文本（如Word、PDF）的处理。Solr是高度可扩展的，并提供了分布式搜索和索引复制。Solr是最流行的企业级搜索引擎，Solr4 还增加了NoSQL支持。\n\nSolr是用Java编写、运行在Servlet容器（如 Apache Tomcat 或Jetty）的一个独立的全文搜索服务器。 Solr采用了 Lucene Java 搜索库为核心的全文索引和搜索，并具有类似REST的HTTP/XML和JSON的API。Solr强大的外部配置功能使得无需进行Java编码，便可对其进行调整以适应多种类型的应用程序。Solr有一个插件架构，以支持更多的高级定制。\n\n因为2010年 Apache Lucene 和 Apache Solr 项目合并，两个项目是由同一个Apache软件基金会开发团队制作实现的。提到技术或产品时，Lucene/Solr或Solr/Lucene是一样的。\n\n## Solr的优缺点\n\n### 优点\n\n1.  Solr有一个更大、更成熟的用户、开发和贡献者社区。\n\n*   支持添加多种格式的索引，如：HTML、PDF、微软 Office 系列软件格式以及 JSON、XML、CSV 等纯文本格式。\n*   Solr比较成熟、稳定。\n*   不考虑建索引的同时进行搜索，速度更快。\n\n### 缺点\n\n1.  建立索引时，搜索效率下降，实时索引搜索效率不高。\n\n## Elasticsearch与Solr的比较\n\n当单纯的对已有数据进行搜索时，Solr更快。\n\n![](https://user-gold-cdn.xitu.io/2016/12/30/d5944021d5ad35ab6c62e4e56ae21e22.png?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)Search Fesh Index While Idle\n\n当实时建立索引时, Solr会产生io阻塞，查询性能较差, Elasticsearch具有明显的优势。\n\n![](https://user-gold-cdn.xitu.io/2016/12/30/8c908279adf11197d4631c95915fc167.png?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)search_fresh_index_while_indexing\n\n随着数据量的增加，Solr的搜索效率会变得更低，而Elasticsearch却没有明显的变化。\n\n![](https://user-gold-cdn.xitu.io/2016/12/30/931c2f218ae2c0c145279507b4eb0e7b.png?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)search_fresh_index_while_indexing\n\n综上所述，Solr的架构不适合实时搜索的应用。\n\n## 实际生产环境测试\n\n下图为将搜索引擎从Solr转到Elasticsearch以后的平均查询速度有了50倍的提升。\n\n![](https://user-gold-cdn.xitu.io/2016/12/30/76c108b2590ef4835b114dec4a018b8a.jpg?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)average_execution_time\n\n## Elasticsearch 与 Solr 的比较总结\n\n*   二者安装都很简单；\n\n*   Solr 利用 Zookeeper 进行分布式管理，而 Elasticsearch 自身带有分布式协调管理功能;\n*   Solr 支持更多格式的数据，而 Elasticsearch 仅支持json文件格式；\n*   Solr 官方提供的功能更多，而 Elasticsearch 本身更注重于核心功能，高级功能多有第三方插件提供；\n*   Solr 在传统的搜索应用中表现好于 Elasticsearch，但在处理实时搜索应用时效率明显低于 Elasticsearch。\n\nSolr 是传统搜索应用的有力解决方案，但 Elasticsearch 更适用于新兴的实时搜索应用。\n\n## 其他基于Lucene的开源搜索引擎解决方案\n\n1. 直接使用[Lucene](https://link.juejin.im/?target=http%3A%2F%2Flucene.apache.org)\n\n   说明：Lucene 是一个 JAVA 搜索类库，它本身并不是一个完整的解决方案，需要额外的开发工作。\n\n   优点：成熟的解决方案，有很多的成功案例。apache 顶级项目，正在持续快速的进步。庞大而活跃的开发社区，大量的开发人员。它只是一个类库，有足够的定制和优化空间：经过简单定制，就可以满足绝大部分常见的需求；经过优化，可以支持 10亿+ 量级的搜索。\n\n   缺点：需要额外的开发工作。所有的扩展，分布式，可靠性等都需要自己实现；非实时，从建索引到可以搜索中间有一个时间延迟，而当前的“近实时”(Lucene Near Real Time search)搜索方案的可扩展性有待进一步完善\n\n* [Katta](https://link.juejin.im/?target=http%3A%2F%2Fkatta.sourceforge.net)\n\n  说明：基于 Lucene 的，支持分布式，可扩展，具有容错功能，准实时的搜索方案。\n\n  优点：开箱即用，可以与 Hadoop 配合实现分布式。具备扩展和容错机制。\n\n  缺点：只是搜索方案，建索引部分还是需要自己实现。在搜索功能上，只实现了最基本的需求。成功案例较少，项目的成熟度稍微差一些。因为需要支持分布式，对于一些复杂的查询需求，定制的难度会比较大。\n\n* [Hadoop contrib/index](https://link.juejin.im/?target=http%3A%2F%2Fsvn.apache.org%2Frepos%2Fasf%2Fhadoop%2Fmapreduce%2Ftrunk%2Fsrc%2Fcontrib%2Findex%2FREADME)\n\n  说明：Map/Reduce 模式的，分布式建索引方案，可以跟 Katta 配合使用。\n\n  优点：分布式建索引，具备可扩展性。\n\n  缺点：只是建索引方案，不包括搜索实现。工作在批处理模式，对实时搜索的支持不佳。\n\n* [LinkedIn 的开源方案](https://link.juejin.im/?target=http%3A%2F%2Fsna-projects.com)\n\n  说明：基于 Lucene 的一系列解决方案，包括 准实时搜索 zoie ，facet 搜索实现 bobo ，机器学习算法 decomposer ，摘要存储库 krati ，数据库模式包装 sensei 等等\n\n  优点：经过验证的解决方案，支持分布式，可扩展，丰富的功能实现\n\n  缺点：与 linkedin 公司的联系太紧密，可定制性比较差\n\n* [Lucandra](https://link.juejin.im/?target=https%3A%2F%2Fgithub.com%2Ftjake%2FLucandra)\n\n  说明：基于 Lucene，索引存在 cassandra 数据库中\n\n  优点：参考 cassandra 的优点\n\n  缺点：参考 cassandra 的缺点。另外，这只是一个 demo，没有经过大量验证\n\n* [HBasene](https://link.juejin.im/?target=https%3A%2F%2Fgithub.com%2Fakkumar%2Fhbasene)\n\n  说明：基于 Lucene，索引存在 HBase 数据库中\n\n  优点：参考 HBase 的优点\n\n  缺点：参考 HBase 的缺点。另外，在实现中，lucene terms 是存成行，但每个 term 对应的 posting lists 是以列的方式存储的。随着单个 term 的 posting lists 的增大，查询时的速度受到的影响会非常大\n\n\n\n\n\n"
  },
  {
    "path": "docs/backend/后端技术杂谈：Lucene基础原理与实践.md",
    "content": "# 目录\n\n  * [二、索引里面究竟存些什么](#二、索引里面究竟存些什么)\n  * [三、如何创建索引](#三、如何创建索引)\n    * [第一步：一些要索引的原文档(Document)。](#第一步：一些要索引的原文档document。)\n    * [第二步：将原文档传给分次组件(Tokenizer)。](#第二步：将原文档传给分次组件tokenizer。)\n    * [第三步：将得到的词元(Token)传给语言处理组件(Linguistic Processor)。](#第三步：将得到的词元token传给语言处理组件linguistic-processor。)\n    * [第四步：将得到的词(Term)传给索引组件(Indexer)。](#第四步：将得到的词term传给索引组件indexer。)\n  * [三、如何对索引进行搜索？](#三、如何对索引进行搜索？)\n    * [第一步：用户输入查询语句。](#第一步：用户输入查询语句。)\n    * [第二步：对查询语句进行词法分析，语法分析，及语言处理。](#第二步：对查询语句进行词法分析，语法分析，及语言处理。)\n    * [第三步：搜索索引，得到符合语法树的文档。](#第三步：搜索索引，得到符合语法树的文档。)\n    * [第四步：根据得到的文档和查询语句的相关性，对结果进行排序。](#第四步：根据得到的文档和查询语句的相关性，对结果进行排序。)\n    * [1. 计算权重(Term weight)的过程。](#1-计算权重term-weight的过程。)\n    * [2\\. 判断Term之间的关系从而得到文档相关性的过程，也即向量空间模型的算法(VSM)。](#2-判断term之间的关系从而得到文档相关性的过程，也即向量空间模型的算法vsm。)\n  * [Spring Boot 中使用 Java API 调用 lucene](#spring-boot-中使用-java-api-调用-lucene)\n  * [Github 代码](#github-代码)\n  * [添加依赖](#添加依赖)\n  * [配置 lucene](#配置-lucene)\n  * [创建索引](#创建索引)\n  * [删除文档](#删除文档)\n  * [更新文档](#更新文档)\n  * [按词条搜索](#按词条搜索)\n  * [多条件查询](#多条件查询)\n  * [匹配前缀](#匹配前缀)\n  * [短语搜索](#短语搜索)\n  * [相近词语搜索](#相近词语搜索)\n  * [通配符搜索](#通配符搜索)\n  * [分词查询](#分词查询)\n  * [多个 Field 分词查询](#多个-field-分词查询)\n  * [中文分词器](#中文分词器)\n  * [高亮处理](#高亮处理)\n\n\n[toc]\nLucene是一个高效的，基于Java的全文检索库。\n\n所以在了解Lucene之前要费一番工夫了解一下全文检索。\n\n那么什么叫做全文检索呢？这要从我们生活中的数据说起。\n\n我们生活中的数据总体分为两种：结构化数据和非结构化数据。\n\n*   结构化数据：指具有固定格式或有限长度的数据，如数据库，元数据等。\n*   非结构化数据：指不定长或无固定格式的数据，如邮件，word文档等。\n\n当然有的地方还会提到第三种，半结构化数据，如XML，HTML等，当根据需要可按结构化数据来处理，也可抽取出纯文本按非结构化数据来处理。\n\n非结构化数据又一种叫法叫全文数据。\n\n按照数据的分类，搜索也分为两种：\n\n*   对结构化数据的搜索：如对数据库的搜索，用SQL语句。再如对元数据的搜索，如利用windows搜索对文件名，类型，修改时间进行搜索等。\n*   对非结构化数据的搜索：如利用windows的搜索也可以搜索文件内容，Linux下的grep命令，再如用Google和百度可以搜索大量内容数据。\n\n对非结构化数据也即对全文数据的搜索主要有两种方法：\n\n一种是顺序扫描法(Serial Scanning)：所谓顺序扫描，比如要找内容包含某一个字符串的文件，就是一个文档一个文档的看，对于每一个文档，从头看到尾，如果此文档包含此字符串，则此文档为我们要找的文件，接着看下一个文件，直到扫描完所有的文件。如利用windows的搜索也可以搜索文件内容，只是相当的慢。如果你有一个80G硬盘，如果想在上面找到一个内容包含某字符串的文件，不花他几个小时，怕是做不到。Linux下的grep命令也是这一种方式。大家可能觉得这种方法比较原始，但对于小数据量的文件，这种方法还是最直接，最方便的。但是对于大量的文件，这种方法就很慢了。\n\n有人可能会说，对非结构化数据顺序扫描很慢，对结构化数据的搜索却相对较快（由于结构化数据有一定的结构可以采取一定的搜索算法加快速度），那么把我们的非结构化数据想办法弄得有一定结构不就行了吗？\n\n这种想法很天然，却构成了全文检索的基本思路，也即将非结构化数据中的一部分信息提取出来，重新组织，使其变得有一定结构，然后对此有一定结构的数据进行搜索，从而达到搜索相对较快的目的。\n\n这部分从非结构化数据中提取出的然后重新组织的信息，我们称之索引。\n\n这种说法比较抽象，举几个例子就很容易明白，比如字典，字典的拼音表和部首检字表就相当于字典的索引，对每一个字的解释是非结构化的，如果字典没有音节表和部首检字表，在茫茫辞海中找一个字只能顺序扫描。然而字的某些信息可以提取出来进行结构化处理，比如读音，就比较结构化，分声母和韵母，分别只有几种可以一一列举，于是将读音拿出来按一定的顺序排列，每一项读音都指向此字的详细解释的页数。我们搜索时按结构化的拼音搜到读音，然后按其指向的页数，便可找到我们的非结构化数据——也即对字的解释。\n\n这种先建立索引，再对索引进行搜索的过程就叫全文检索(Full-text Search)。\n\n下面这幅图来自《Lucene in action》，但却不仅仅描述了Lucene的检索过程，而是描述了全文检索的一般过程。\n\n全文检索大体分两个过程，索引创建(Indexing)和搜索索引(Search)。\n\n*   索引创建：将现实世界中所有的结构化和非结构化数据提取信息，创建索引的过程。\n*   搜索索引：就是得到用户的查询请求，搜索创建的索引，然后返回结果的过程。\n\n于是全文检索就存在三个重要问题：\n\n1\\. 索引里面究竟存些什么？(Index)\n\n2\\. 如何创建索引？(Indexing)\n\n3\\. 如何对索引进行搜索？(Search)\n\n下面我们顺序对每个个问题进行研究。\n\n## 二、索引里面究竟存些什么\n\n索引里面究竟需要存些什么呢？\n\n首先我们来看为什么顺序扫描的速度慢：\n\n其实是由于我们想要搜索的信息和非结构化数据中所存储的信息不一致造成的。\n\n非结构化数据中所存储的信息是每个文件包含哪些字符串，也即已知文件，欲求字符串相对容易，也即是从文件到字符串的映射。而我们想搜索的信息是哪些文件包含此字符串，也即已知字符串，欲求文件，也即从字符串到文件的映射。两者恰恰相反。于是如果索引总能够保存从字符串到文件的映射，则会大大提高搜索速度。\n\n由于从字符串到文件的映射是文件到字符串映射的反向过程，于是保存这种信息的索引称为反向索引。\n\n反向索引的所保存的信息一般如下：\n\n假设我的文档集合里面有100篇文档，为了方便表示，我们为文档编号从1到100，得到下面的结构\n\n左边保存的是一系列字符串，称为词典。\n\n每个字符串都指向包含此字符串的文档(Document)链表，此文档链表称为倒排表(Posting List)。\n\n有了索引，便使保存的信息和要搜索的信息一致，可以大大加快搜索的速度。\n\n比如说，我们要寻找既包含字符串“lucene”又包含字符串“solr”的文档，我们只需要以下几步：\n\n1\\. 取出包含字符串“lucene”的文档链表。\n\n2\\. 取出包含字符串“solr”的文档链表。\n\n3\\. 通过合并链表，找出既包含“lucene”又包含“solr”的文件。\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-c1064a6ada64169f76ec63e44e82f0f5_720w.webp)\n\n</figure>\n\n看到这个地方，有人可能会说，全文检索的确加快了搜索的速度，但是多了索引的过程，两者加起来不一定比顺序扫描快多少。的确，加上索引的过程，全文检索不一定比顺序扫描快，尤其是在数据量小的时候更是如此。而对一个很大量的数据创建索引也是一个很慢的过程。\n\n然而两者还是有区别的，顺序扫描是每次都要扫描，而创建索引的过程仅仅需要一次，以后便是一劳永逸的了，每次搜索，创建索引的过程不必经过，仅仅搜索创建好的索引就可以了。\n\n这也是全文搜索相对于顺序扫描的优势之一：一次索引，多次使用。\n\n## 三、如何创建索引\n\n全文检索的索引创建过程一般有以下几步：\n\n### 第一步：一些要索引的原文档(Document)。\n\n为了方便说明索引创建过程，这里特意用两个文件为例：\n\n文件一：Students should be allowed to go out with their friends, but not allowed to drink beer.\n\n文件二：My friend Jerry went to school to see his students but found them drunk which is not allowed.\n\n### 第二步：将原文档传给分次组件(Tokenizer)。\n\n分词组件(Tokenizer)会做以下几件事情(此过程称为Tokenize)：\n\n1\\. 将文档分成一个一个单独的单词。\n\n2\\. 去除标点符号。\n\n3\\. 去除停词(Stop word)。\n\n所谓停词(Stop word)就是一种语言中最普通的一些单词，由于没有特别的意义，因而大多数情况下不能成为搜索的关键词，因而创建索引时，这种词会被去掉而减少索引的大小。\n\n英语中挺词(Stop word)如：“the”,“a”，“this”等。\n\n对于每一种语言的分词组件(Tokenizer)，都有一个停词(stop word)集合。\n\n经过分词(Tokenizer)后得到的结果称为词元(Token)。\n\n在我们的例子中，便得到以下词元(Token)：\n\n\n\n```\n“Students”，“allowed”，“go”，“their”，“friends”，“allowed”，“drink”，“beer”，“My”，“friend”，“Jerry”，“went”，“school”，“see”，“his”，“students”，“found”，“them”，“drunk”，“allowed”。\n```\n\n\n\n### 第三步：将得到的词元(Token)传给语言处理组件(Linguistic Processor)。\n\n语言处理组件(linguistic processor)主要是对得到的词元(Token)做一些同语言相关的处理。\n\n对于英语，语言处理组件(Linguistic Processor)一般做以下几点：\n\n1\\. 变为小写(Lowercase)。\n\n2\\. 将单词缩减为词根形式，如“cars”到“car”等。这种操作称为：stemming。\n\n3\\. 将单词转变为词根形式，如“drove”到“drive”等。这种操作称为：lemmatization。\n\nStemming 和 lemmatization的异同：\n\n*   相同之处：Stemming和lemmatization都要使词汇成为词根形式。\n*   两者的方式不同：\n\n*   Stemming采用的是“缩减”的方式：“cars”到“car”，“driving”到“drive”。\n*   Lemmatization采用的是“转变”的方式：“drove”到“drove”，“driving”到“drive”。\n\n*   两者的算法不同：\n\n*   Stemming主要是采取某种固定的算法来做这种缩减，如去除“s”，去除“ing”加“e”，将“ational”变为“ate”，将“tional”变为“tion”。\n*   Lemmatization主要是采用保存某种字典的方式做这种转变。比如字典中有“driving”到“drive”，“drove”到“drive”，“am, is, are”到“be”的映射，做转变时，只要查字典就可以了。\n\n*   Stemming和lemmatization不是互斥关系，是有交集的，有的词利用这两种方式都能达到相同的转换。\n\n语言处理组件(linguistic processor)的结果称为词(Term)。\n\n在我们的例子中，经过语言处理，得到的词(Term)如下：\n\n\n\n```\n“student”，“allow”，“go”，“their”，“friend”，“allow”，“drink”，“beer”，“my”，“friend”，“jerry”，“go”，“school”，“see”，“his”，“student”，“find”，“them”，“drink”，“allow”。\n```\n\n\n\n也正是因为有语言处理的步骤，才能使搜索drove，而drive也能被搜索出来。\n\n### 第四步：将得到的词(Term)传给索引组件(Indexer)。\n\n索引组件(Indexer)主要做以下几件事情：\n\n1\\. 利用得到的词(Term)创建一个字典。\n\n在我们的例子中字典如下：\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-b8477ac42cb442585aa090f33c0b34a6_720w.webp)\n\n</figure>\n\n2\\. 对字典按字母顺序进行排序。\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230408113241.png)\n\n</figure>\n\n3\\. 合并相同的词(Term)成为文档倒排(Posting List)链表。\n\n在此表中，有几个定义：\n\n*   Document Frequency 即文档频次，表示总共有多少文件包含此词(Term)。\n*   Frequency 即词频率，表示此文件中包含了几个此词(Term)。\n\n所以对词(Term) “allow”来讲，总共有两篇文档包含此词(Term)，从而词(Term)后面的文档链表总共有两项，第一项表示包含“allow”的第一篇文档，即1号文档，此文档中，“allow”出现了2次，第二项表示包含“allow”的第二个文档，是2号文档，此文档中，“allow”出现了1次。\n\n到此为止，索引已经创建好了，我们可以通过它很快的找到我们想要的文档。\n\n而且在此过程中，我们惊喜地发现，搜索“drive”，“driving”，“drove”，“driven”也能够被搜到。因为在我们的索引中，“driving”，“drove”，“driven”都会经过语言处理而变成“drive”，在搜索时，如果您输入“driving”，输入的查询语句同样经过我们这里的一到三步，从而变为查询“drive”，从而可以搜索到想要的文档。\n\n## 三、如何对索引进行搜索？\n\n到这里似乎我们可以宣布“我们找到想要的文档了”。\n\n然而事情并没有结束，找到了仅仅是全文检索的一个方面。不是吗？如果仅仅只有一个或十个文档包含我们查询的字符串，我们的确找到了。然而如果结果有一千个，甚至成千上万个呢？那个又是您最想要的文件呢？\n\n打开Google吧，比如说您想在微软找份工作，于是您输入“Microsoft job”，您却发现总共有22600000个结果返回。好大的数字呀，突然发现找不到是一个问题，找到的太多也是一个问题。在如此多的结果中，如何将最相关的放在最前面呢？\n\n当然Google做的很不错，您一下就找到了jobs at Microsoft。想象一下，如果前几个全部是“Microsoft does a good job at software industry…”将是多么可怕的事情呀。\n\n如何像Google一样，在成千上万的搜索结果中，找到和查询语句最相关的呢？\n\n如何判断搜索出的文档和查询语句的相关性呢？\n\n这要回到我们第三个问题：如何对索引进行搜索？\n\n搜索主要分为以下几步：\n\n### 第一步：用户输入查询语句。\n\n查询语句同我们普通的语言一样，也是有一定语法的。\n\n不同的查询语句有不同的语法，如SQL语句就有一定的语法。\n\n查询语句的语法根据全文检索系统的实现而不同。最基本的有比如：AND, OR, NOT等。\n\n举个例子，用户输入语句：lucene AND learned NOT hadoop。\n\n说明用户想找一个包含lucene和learned然而不包括hadoop的文档。\n\n### 第二步：对查询语句进行词法分析，语法分析，及语言处理。\n\n由于查询语句有语法，因而也要进行语法分析，语法分析及语言处理。\n\n1\\. 词法分析主要用来识别单词和关键字。\n\n如上述例子中，经过词法分析，得到单词有lucene，learned，hadoop, 关键字有AND, NOT。\n\n如果在词法分析中发现不合法的关键字，则会出现错误。如lucene AMD learned，其中由于AND拼错，导致AMD作为一个普通的单词参与查询。\n\n2\\. 语法分析主要是根据查询语句的语法规则来形成一棵语法树。\n\n如果发现查询语句不满足语法规则，则会报错。如lucene NOT AND learned，则会出错。\n\n如上述例子，lucene AND learned NOT hadoop形成的语法树如下：\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-5acb4fdbd41e04fe83396ea82679bee9_720w.webp)\n\n</figure>\n\n3\\. 语言处理同索引过程中的语言处理几乎相同。\n\n如learned变成learn等。\n\n经过第二步，我们得到一棵经过语言处理的语法树。\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230408113252.png)\n\n</figure>\n\n### 第三步：搜索索引，得到符合语法树的文档。\n\n此步骤有分几小步：\n\n1.  首先，在反向索引表中，分别找出包含lucene，learn，hadoop的文档链表。\n2.  其次，对包含lucene，learn的链表进行合并操作，得到既包含lucene又包含learn的文档链表。\n3.  然后，将此链表与hadoop的文档链表进行差操作，去除包含hadoop的文档，从而得到既包含lucene又包含learn而且不包含hadoop的文档链表。\n4.  此文档链表就是我们要找的文档。\n\n### 第四步：根据得到的文档和查询语句的相关性，对结果进行排序。\n\n虽然在上一步，我们得到了想要的文档，然而对于查询结果应该按照与查询语句的相关性进行排序，越相关者越靠前。\n\n如何计算文档和查询语句的相关性呢？\n\n不如我们把查询语句看作一片短小的文档，对文档与文档之间的相关性(relevance)进行打分(scoring)，分数高的相关性好，就应该排在前面。\n\n那么又怎么对文档之间的关系进行打分呢？\n\n这可不是一件容易的事情，首先我们看一看判断人之间的关系吧。\n\n首先看一个人，往往有很多要素，如性格，信仰，爱好，衣着，高矮，胖瘦等等。\n\n其次对于人与人之间的关系，不同的要素重要性不同，性格，信仰，爱好可能重要些，衣着，高矮，胖瘦可能就不那么重要了，所以具有相同或相似性格，信仰，爱好的人比较容易成为好的朋友，然而衣着，高矮，胖瘦不同的人，也可以成为好的朋友。\n\n因而判断人与人之间的关系，首先要找出哪些要素对人与人之间的关系最重要，比如性格，信仰，爱好。其次要判断两个人的这些要素之间的关系，比如一个人性格开朗，另一个人性格外向，一个人信仰佛教，另一个信仰上帝，一个人爱好打篮球，另一个爱好踢足球。我们发现，两个人在性格方面都很积极，信仰方面都很善良，爱好方面都爱运动，因而两个人关系应该会很好。\n\n我们再来看看公司之间的关系吧。\n\n首先看一个公司，有很多人组成，如总经理，经理，首席技术官，普通员工，保安，门卫等。\n\n其次对于公司与公司之间的关系，不同的人重要性不同，总经理，经理，首席技术官可能更重要一些，普通员工，保安，门卫可能较不重要一点。所以如果两个公司总经理，经理，首席技术官之间关系比较好，两个公司容易有比较好的关系。然而一位普通员工就算与另一家公司的一位普通员工有血海深仇，怕也难影响两个公司之间的关系。\n\n因而判断公司与公司之间的关系，首先要找出哪些人对公司与公司之间的关系最重要，比如总经理，经理，首席技术官。其次要判断这些人之间的关系，不如两家公司的总经理曾经是同学，经理是老乡，首席技术官曾是创业伙伴。我们发现，两家公司无论总经理，经理，首席技术官，关系都很好，因而两家公司关系应该会很好。\n\n分析了两种关系，下面看一下如何判断文档之间的关系了。\n\n首先，一个文档有很多词(Term)组成，如search, lucene, full-text, this, a, what等。\n\n其次对于文档之间的关系，不同的Term重要性不同，比如对于本篇文档，search, Lucene, full-text就相对重要一些，this, a , what可能相对不重要一些。所以如果两篇文档都包含search, Lucene，fulltext，这两篇文档的相关性好一些，然而就算一篇文档包含this, a, what，另一篇文档不包含this, a, what，也不能影响两篇文档的相关性。\n\n因而判断文档之间的关系，首先找出哪些词(Term)对文档之间的关系最重要，如search, Lucene, fulltext。然后判断这些词(Term)之间的关系。\n\n找出词(Term)对文档的重要性的过程称为计算词的权重(Term weight)的过程。\n\n计算词的权重(term weight)有两个参数，第一个是词(Term)，第二个是文档(Document)。\n\n词的权重(Term weight)表示此词(Term)在此文档中的重要程度，越重要的词(Term)有越大的权重(Term weight)，因而在计算文档之间的相关性中将发挥更大的作用。\n\n判断词(Term)之间的关系从而得到文档相关性的过程应用一种叫做向量空间模型的算法(Vector Space Model)。\n\n下面仔细分析一下这两个过程：\n\n### 1. 计算权重(Term weight)的过程。\n\n影响一个词(Term)在一篇文档中的重要性主要有两个因素：\n\n*   Term Frequency (tf)：即此Term在此文档中出现了多少次。tf 越大说明越重要。\n*   Document Frequency (df)：即有多少文档包含次Term。df 越大说明越不重要。\n\n容易理解吗？词(Term)在文档中出现的次数越多，说明此词(Term)对该文档越重要，如“搜索”这个词，在本文档中出现的次数很多，说明本文档主要就是讲这方面的事的。然而在一篇英语文档中，this出现的次数更多，就说明越重要吗？不是的，这是由第二个因素进行调整，第二个因素说明，有越多的文档包含此词(Term), 说明此词(Term)太普通，不足以区分这些文档，因而重要性越低。\n\n这也如我们程序员所学的技术，对于程序员本身来说，这项技术掌握越深越好（掌握越深说明花时间看的越多，tf越大），找工作时越有竞争力。然而对于所有程序员来说，这项技术懂得的人越少越好（懂得的人少df小），找工作越有竞争力。人的价值在于不可替代性就是这个道理。\n\n道理明白了，我们来看看公式：\n\n这仅仅只term weight计算公式的简单典型实现。实现全文检索系统的人会有自己的实现，Lucene就与此稍有不同。\n\n### 2\\. 判断Term之间的关系从而得到文档相关性的过程，也即向量空间模型的算法(VSM)。\n\n我们把文档看作一系列词(Term)，每一个词(Term)都有一个权重(Term weight)，不同的词(Term)根据自己在文档中的权重来影响文档相关性的打分计算。\n\n于是我们把所有此文档中词(term)的权重(term weight) 看作一个向量。\n\nDocument = {term1, term2, …… ,term N}\n\nDocument Vector = {weight1, weight2, …… ,weight N}\n\n同样我们把查询语句看作一个简单的文档，也用向量来表示。\n\nQuery = {term1, term 2, …… , term N}\n\nQuery Vector = {weight1, weight2, …… , weight N}\n\n我们把所有搜索出的文档向量及查询向量放到一个N维空间中，每个词(term)是一维。\n\n我们认为两个向量之间的夹角越小，相关性越大。\n\n所以我们计算夹角的余弦值作为相关性的打分，夹角越小，余弦值越大，打分越高，相关性越大。\n\n有人可能会问，查询语句一般是很短的，包含的词(Term)是很少的，因而查询向量的维数很小，而文档很长，包含词(Term)很多，文档向量维数很大。你的图中两者维数怎么都是N呢？\n\n在这里，既然要放到相同的向量空间，自然维数是相同的，不同时，取二者的并集，如果不含某个词(Term)时，则权重(Term Weight)为0。\n\n相关性打分公式如下：\n\n举个例子，查询语句有11个Term，共有三篇文档搜索出来。其中各自的权重(Term weight)，如下表格。\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230408113310.png)\n\n</figure>\n\n于是计算，三篇文档同查询语句的相关性打分分别为：\n\n于是文档二相关性最高，先返回，其次是文档一，最后是文档三。\n\n到此为止，我们可以找到我们最想要的文档了。\n\n说了这么多，其实还没有进入到Lucene，而仅仅是信息检索技术(Information retrieval)中的基本理论，然而当我们看过Lucene后我们会发现，Lucene是对这种基本理论的一种基本的的实践。所以在以后分析Lucene的文章中，会常常看到以上理论在Lucene中的应用。\n\n在进入Lucene之前，对上述索引创建和搜索过程所一个总结，如图：\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230408113319.png)\n\n</figure>\n\n1\\. 索引过程：\n\n1) 有一系列被索引文件\n\n2) 被索引文件经过语法分析和语言处理形成一系列词(Term)。\n\n3) 经过索引创建形成词典和反向索引表。\n\n4) 通过索引存储将索引写入硬盘。\n\n2\\. 搜索过程：\n\na) 用户输入查询语句。\n\nb) 对查询语句经过语法分析和语言分析得到一系列词(Term)。\n\nc) 通过语法分析得到一个查询树。\n\nd) 通过索引存储将索引读入到内存。\n\ne) 利用查询树搜索索引，从而得到每个词(Term)的文档链表，对文档链表进行交，差，并得到结果文档。\n\nf) 将搜索到的结果文档对查询的相关性进行排序。\n\ng) 返回查询结果给用户。\n\n下面我们可以进入Lucene的世界了。\n\n## Spring Boot 中使用 Java API 调用 lucene\n\n## Github 代码\n\n代码我已放到 Github ，导入`spring-boot-lucene-demo` 项目\n\ngithub [spring-boot-lucene-demo](https://link.juejin.im/?target=https%3A%2F%2Fgithub.com%2Fsouyunku%2Fspring-boot-examples%2Ftree%2Fmaster%2Fspring-boot-lucene-demo)\n\n## 添加依赖\n\n\n\n```\n      21  <!--对分词索引查询解析-->\n        <dependency>\n            <groupId>org.apache.lucene</groupId>\n            <artifactId>lucene-queryparser</artifactId>\n            <version>7.1.0</version>\n        </dependency>\n        <!--高亮 -->\n        <dependency>\n            <groupId>org.apache.lucene</groupId>\n            <artifactId>lucene-highlighter</artifactId>\n            <version>7.1.0</version>\n        </dependency>\n        <!--smartcn 中文分词器 SmartChineseAnalyzer  smartcn分词器 需要lucene依赖 且和lucene版本同步-->\n        <dependency>\n            <groupId>org.apache.lucene</groupId>\n            <artifactId>lucene-analyzers-smartcn</artifactId>\n            <version>7.1.0</version>\n        </dependency>\n        <!--ik-analyzer 中文分词器-->\n        <dependency>\n            <groupId>cn.bestwu</groupId>\n            <artifactId>ik-analyzers</artifactId>\n            <version>5.1.0</version>\n        </dependency> <!--MMSeg4j 分词器-->\n        <dependency>\n            <groupId>com.chenlb.mmseg4j</groupId>\n            <artifactId>mmseg4j-solr</artifactId>\n            <version>2.4.0</version>\n            <exclusions>\n                <exclusion>\n                    <groupId>org.apache.solr</groupId>\n                    <artifactId>solr-core</artifactId>\n                </exclusion>\n            </exclusions>\n        </dependency>\n```\n\n\n\n## 配置 lucene\n\n\n\n```\nprivate Directory directory; private IndexReader indexReader; \nprivate IndexSearcher indexSearcher; \n@Beforepublic void setUp() throws IOException {\n//索引存放的位置，设置在当前目录中    \ndirectory = FSDirectory.open(Paths.get(\"indexDir/\"));     \n//创建索引的读取器    \nindexReader = DirectoryReader.open(directory);     \n//创建一个索引的查找器，来检索索引库    \nindexSearcher = new IndexSearcher(indexReader);\n} \n@Afterpublic void tearDown() throws Exception {    \nindexReader.close();\n} \n** * 执行查询，并打印查询到的记录数 * * \n@param query * @throws IOException */\npublic void executeQuery(Query query) throws IOException {     \nTopDocs topDocs = indexSearcher.search(query, 100);     \n//打印查询到的记录数    \nSystem.out.println(\"总共查询到\" + topDocs.totalHits + \"个文档\");    \nfor (ScoreDoc scoreDoc : topDocs.scoreDocs) {         \n//取得对应的文档对象        Document document = indexSearcher.doc(scoreDoc.doc);\nSystem.out.println(\"id：\" + document.get(\"id\"));        \nSystem.out.println(\"title：\" + document.get(\"title\"));        \nSystem.out.println(\"content：\" + document.get(\"content\"));    \n}} \n/** * 分词打印 * * @param analyzer * @param text * @throws IOException */\npublic void printAnalyzerDoc(Analyzer analyzer, String text) throws IOException {     \nTokenStream tokenStream = analyzer.tokenStream(\"content\", new StringReader(text));    \nCharTermAttribute charTermAttribute = tokenStream.addAttribute(CharTermAttribute.class);    \ntry {        \ntokenStream.reset();        \nwhile (tokenStream.incrementToken()) {            \nSystem.out.println(charTermAttribute.toString());        \n}        \ntokenStream.end();    \n} finally {        \ntokenStream.close();        \nanalyzer.close();    \n}}\n```\n\n\n\n## 创建索引\n\n\n\n```\n@Test\npublic void indexWriterTest() throws IOException {    \nlong start = System.currentTimeMillis();     \n//索引存放的位置，设置在当前目录中   \nDirectory directory = FSDirectory.open(Paths.get(\"indexDir/\"));    \n//在 6.6 以上版本中 version 不再是必要的，并且，存在无参构造方法，可以直接使用默认的 StandardAnalyzer 分词器。    Version version = Version.LUCENE_7_1_0;     \n//Analyzer analyzer = new StandardAnalyzer(); \n// 标准分词器，适用于英文    \n//Analyzer analyzer = new SmartChineseAnalyzer();\n//中文分词    \n//Analyzer analyzer = new ComplexAnalyzer();//中文分词    \n//Analyzer analyzer = new IKAnalyzer();//中文分词     \nAnalyzer analyzer = new IKAnalyzer();\n//中文分词     \n//创建索引写入配置    \nIndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer);     \n//创建索引写入对象    \nIndexWriter indexWriter = new IndexWriter(directory, indexWriterConfig);     \n//创建Document对象，存储索引     \nDocument doc = new Document();     int id = 1;     \n//将字段加入到doc中    \ndoc.add(new IntPoint(\"id\", id));    \ndoc.add(new StringField(\"title\", \"Spark\", Field.Store.YES));    \ndoc.add(new TextField(\"content\", \"Apache Spark 是专为大规模数据处理而设计的快速通用的计算引擎\", Field.Store.YES));    \ndoc.add(new StoredField(\"id\", id));     \n//将doc对象保存到索引库中    \nindexWriter.addDocument(doc);     \nindexWriter.commit();    \n//关闭流    \nindexWriter.close();     \nlong end = System.currentTimeMillis();    \nSystem.out.println(\"索引花费了\" + (end - start) + \" 毫秒\");}\n```\n\n\n\n响应\n\n17:58:14.655 [main] DEBUG org.wltea.analyzer.dic.Dictionary - 加载扩展词典：ext.dic17:58:14.660 [main] DEBUG org.wltea.analyzer.dic.Dictionary - 加载扩展停止词典：stopword.dic索引花费了879 毫秒\n\n## 删除文档\n\n\n\n```\n@Testpublic void deleteDocumentsTest() throws IOException \n// 标准分词器，适用于英文   \n//Analyzer analyzer = new SmartChineseAnalyzer();\n//中文分词    \n//Analyzer analyzer = new ComplexAnalyzer();\n//中文分词    \n//Analyzer analyzer = new IKAnalyzer();\n//中文分词     \nAnalyzer analyzer = new IKAnalyzer();\n//中文分词     \n//创建索引写入配置    \nIndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer);     \n//创建索引写入对象    \nIndexWriter indexWriter = new IndexWriter(directory, indexWriterConfig);     \n// 删除title中含有关键词“Spark”的文档    \nlong count = indexWriter.deleteDocuments(new Term(\"title\", \"Spark\"));     \n//  除此之外IndexWriter还提供了以下方法：   \n// DeleteDocuments(Query query):根据Query条件来删除单个或多个Document    \n// DeleteDocuments(Query[] queries):根据Query条件来删除单个或多个Document    \n// DeleteDocuments(Term term):根据Term来删除单个或多个Document    \n// DeleteDocuments(Term[] terms):根据Term来删除单个或多个Document    \n// DeleteAll():删除所有的Document     \n//使用IndexWriter进行Document删除操作时，文档并不会立即被删除，而是把这个删除动作缓存起来，当IndexWriter.Commit()或IndexWriter.Close()时，删除操作才会被真正执行。     \nindexWriter.commit();    \nindexWriter.close();     \nSystem.out.println(\"删除完成:\" + count);\n}\n```\n\n\n\n响应\n\n删除完成:1\n\n## 更新文档\n\n\n\n```\n/** * 测试更新 * 实际上就是删除后新增一条 * * @throws IOException */\n@Test\npublic void updateDocumentTest() throws IOException {    \n//Analyzer analyzer = new StandardAnalyzer(); \n// 标准分词器，适用于英文   \n//Analyzer analyzer = new SmartChineseAnalyzer();//中文分词    \n//Analyzer analyzer = new ComplexAnalyzer();\n//中文分词    \n//Analyzer analyzer = new IKAnalyzer();\n//中文分词     \nAnalyzer analyzer = new IKAnalyzer();\n//中文分词     \n//创建索引写入配置    \nIndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer);     \n//创建索引写入对象    \nIndexWriter indexWriter = new IndexWriter(directory, indexWriterConfig);    \nDocument doc = new Document();     \nint id = 1;     \ndoc.add(new IntPoint(\"id\", id));    \ndoc.add(new StringField(\"title\", \"Spark\", Field.Store.YES));    \ndoc.add(new TextField(\"content\", \"Apache Spark 是专为大规模数据处理而设计的快速通用的计算引擎\", Field.Store.YES));    \ndoc.add(new StoredField(\"id\", id));     \nlong count = indexWriter.updateDocument(new Term(\"id\", \"1\"), doc);    \nSystem.out.println(\"更新文档:\" + count);    \nindexWriter.close();\n}\n```\n\n\n\n响应\n\n更新文档:1\n\n## 按词条搜索\n\n\n\n```\n/** * 按词条搜索 * <p> * TermQuery是最简单、也是最常用的Query。TermQuery可以理解成为“词条搜索”， * 在搜索引擎中最基本的搜索就是在索引中搜索某一词条，而TermQuery就是用来完成这项工作的。 * 在Lucene中词条是最基本的搜索单位，从本质上来讲一个词条其实就是一个名/值对。 * 只不过这个“名”是字段名，而“值”则表示字段中所包含的某个关键字。 * * @throws IOException */\n@Test\npublic void termQueryTest() throws IOException {\n     String searchField = \"title\";    \n     //这是一个条件查询的api，用于添加条件    \n     TermQuery query = new TermQuery(new Term(searchField, \"Spark\"));     \n     //执行查询，并打印查询到的记录数    \n     executeQuery(query);\n     }\n```\n\n\n\n响应\n\n总共查询到1个文档id：1title：Sparkcontent：Apache Spark 是专为大规模数据处理而设计的快速通用的计算引擎!\n\n## 多条件查询\n\n\n\n```\n/** * 多条件查询 * * BooleanQuery也是实际开发过程中经常使用的一种Query。 * 它其实是一个组合的Query，在使用时可以把各种Query对象添加进去并标明它们之间的逻辑关系。 * BooleanQuery本身来讲是一个布尔子句的容器，它提供了专门的API方法往其中添加子句， * 并标明它们之间的关系，以下代码为BooleanQuery提供的用于添加子句的API接口： * * @throws IOException */\n@Testpublic void BooleanQueryTest() throws IOException { \n    String searchField1 = \"title\";    \n    String searchField2 = \"content\";    \n    Query query1 = new TermQuery(new Term(searchField1, \"Spark\"));    \n    Query query2 = new TermQuery(new Term(searchField2, \"Apache\"));    \n    BooleanQuery.Builder builder = new BooleanQuery.Builder();     \n    // BooleanClause用于表示布尔查询子句关系的类，    \n    // 包 括：    \n    // BooleanClause.Occur.MUST，    \n    // BooleanClause.Occur.MUST_NOT，    \n    // BooleanClause.Occur.SHOULD。    \n    // 必须包含,不能包含,可以包含三种.有以下6种组合：    \n    //    \n    // 1．MUST和MUST：取得连个查询子句的交集。    \n    // 2．MUST和MUST_NOT：表示查询结果中不能包含MUST_NOT所对应得查询子句的检索结果。    \n    // 3．SHOULD与MUST_NOT：连用时，功能同MUST和MUST_NOT。    \n    // 4．SHOULD与MUST连用时，结果为MUST子句的检索结果,但是SHOULD可影响排序。    \n    // 5．SHOULD与SHOULD：表示“或”关系，最终检索结果为所有检索子句的并集。    \n    // 6．MUST_NOT和MUST_NOT：无意义，检索无结果。     \n    builder.add(query1, BooleanClause.Occur.SHOULD);   \n    builder.add(query2, BooleanClause.Occur.SHOULD);     \n    BooleanQuery query = builder.build();     \n    //执行查询，并打印查询到的记录数    \n    executeQuery(query);\n    }\n```\n\n\n\n响应\n\n总共查询到1个文档id：1title：Sparkcontent：Apache Spark 是专为大规模数据处理而设计的快速通用的计算引擎!\n\n## 匹配前缀\n\n\n\n```\n/** * 匹配前缀 * <p> * PrefixQuery用于匹配其索引开始以指定的字符串的文档。就是文档中存在xxx% * <p> * * @throws IOException */\n@Testpublic void prefixQueryTest() throws IOException {    \nString searchField = \"title\";    \nTerm term = new Term(searchField, \"Spar\");    \nQuery query = new PrefixQuery(term);     \n//执行查询，并打印查询到的记录数    \nexecuteQuery(query);\n}\n```\n\n\n\n响应\n\n总共查询到1个文档id：1title：Sparkcontent：Apache Spark 是专为大规模数据处理而设计的快速通用的计算引擎!\n\n## 短语搜索\n\n\n\n```\n/** * 短语搜索 * <p> * 所谓PhraseQuery，就是通过短语来检索，比如我想查“big car”这个短语， * 那么如果待匹配的document的指定项里包含了\"big car\"这个短语， * 这个document就算匹配成功。可如果待匹配的句子里包含的是“big black car”， * 那么就无法匹配成功了，如果也想让这个匹配，就需要设定slop， * 先给出slop的概念：slop是指两个项的位置之间允许的最大间隔距离 * * @throws IOException */\n@Testpublic void phraseQueryTest() throws IOException {     \nString searchField = \"content\";    \nString query1 = \"apache\";    \nString query2 = \"spark\";     \nPhraseQuery.Builder builder = new PhraseQuery.Builder();    \nbuilder.add(new Term(searchField, query1));    \nbuilder.add(new Term(searchField, query2));    \nbuilder.setSlop(0);    \nPhraseQuery phraseQuery = builder.build();     \n//执行查询，并打印查询到的记录数    \nexecuteQuery(phraseQuery);\n}\n```\n\n\n\n响应\n\n总共查询到1个文档id：1title：Sparkcontent：Apache Spark 是专为大规模数据处理而设计的快速通用的计算引擎!\n\n## 相近词语搜索\n\n/** * 相近词语搜索 * <p> * FuzzyQuery是一种模糊查询，它可以简单地识别两个相近的词语。 * * @throws IOException */@Testpublic void fuzzyQueryTest() throws IOException { String searchField = \"content\"; Term t = new Term(searchField, \"大规模\"); Query query = new FuzzyQuery(t); //执行查询，并打印查询到的记录数 executeQuery(query);}\n\n响应\n\n总共查询到1个文档id：1title：Sparkcontent：Apache Spark 是专为大规模数据处理而设计的快速通用的计算引擎!\n\n## 通配符搜索\n\n/** * 通配符搜索 * <p> * Lucene也提供了通配符的查询，这就是WildcardQuery。 * 通配符“?”代表1个字符，而“*”则代表0至多个字符。 * * @throws IOException */@Testpublic void wildcardQueryTest() throws IOException { String searchField = \"content\"; Term term = new Term(searchField, \"大*规模\"); Query query = new WildcardQuery(term); //执行查询，并打印查询到的记录数 executeQuery(query);}\n\n响应\n\n总共查询到1个文档id：1title：Sparkcontent：Apache Spark 是专为大规模数据处理而设计的快速通用的计算引擎!\n\n## 分词查询\n\n\n\n```\n/** * 分词查询 * * @throws IOException * @throws ParseException */\n@Test\npublic void queryParserTest() throws IOException, ParseException {    \n//Analyzer analyzer = new StandardAnalyzer(); \n// 标准分词器，适用于英文    \n//Analyzer analyzer = new SmartChineseAnalyzer();\n//中文分词    \n//Analyzer analyzer = new ComplexAnalyzer();\n//中文分词    //Analyzer analyzer = new IKAnalyzer();\n//中文分词     Analyzer analyzer = new IKAnalyzer();\n//中文分词     String searchField = \"content\";     \n//指定搜索字段和分析器    \nQueryParser parser = new QueryParser(searchField, analyzer);     \n//用户输入内容    Query query = parser.parse(\"计算引擎\");     \n//执行查询，并打印查询到的记录数    \nexecuteQuery(query);\n}\n```\n\n\n\n响应\n\n总共查询到1个文档id：1title：Sparkcontent：Apache Spark 是专为大规模数据处理而设计的快速通用的计算引擎!\n\n## 多个 Field 分词查询\n\n\n\n```\n/** * 多个 Field 分词查询 * * @throws IOException * @throws ParseException */\n@Testpublic void multiFieldQueryParserTest() throws IOException, ParseException {    \n//Analyzer analyzer = new StandardAnalyzer(); \n// 标准分词器，适用于英文    \n//Analyzer analyzer = new SmartChineseAnalyzer();//中文分词    \n//Analyzer analyzer = new ComplexAnalyzer();\n//中文分词    \n//Analyzer analyzer = new IKAnalyzer();\n//中文分词     Analyzer analyzer = new IKAnalyzer();\n//中文分词     String[] filedStr = new String[]{\"title\", \"content\"};     \n//指定搜索字段和分析器    \nQueryParser queryParser = new MultiFieldQueryParser(filedStr, analyzer);     \n//用户输入内容    \nQuery query = queryParser.parse(\"Spark\");     \n//执行查询，并打印查询到的记录数    executeQuery(query);}\n```\n\n\n\n响应\n\n总共查询到1个文档id：1title：Sparkcontent：Apache Spark 是专为大规模数据处理而设计的快速通用的计算引擎!\n\n## 中文分词器\n\n\n\n```\n/** * IKAnalyzer  中文分词器 * SmartChineseAnalyzer  smartcn分词器 需要lucene依赖 且和lucene版本同步 * * @throws IOException */\n@Test\npublic void AnalyzerTest() throws IOException { \n//Analyzer analyzer = new StandardAnalyzer(); \n// 标准分词器，适用于英文    \n//Analyzer analyzer = new SmartChineseAnalyzer();\n//中文分词    //Analyzer analyzer = new ComplexAnalyzer();\n//中文分词    //Analyzer analyzer = new IKAnalyzer();\n//中文分词     \nAnalyzer analyzer = null;    \nString text = \"Apache Spark 是专为大规模数据处理而设计的快速通用的计算引擎\";\nanalyzer = new IKAnalyzer();//IKAnalyzer 中文分词    \nprintAnalyzerDoc(analyzer, text);    \nSystem.out.println();     \nanalyzer = new ComplexAnalyzer();\n//MMSeg4j 中文分词    \nprintAnalyzerDoc(analyzer, text);    \nSystem.out.println();     \nanalyzer = new SmartChineseAnalyzer();\n//Lucene 中文分词器    \nprintAnalyzerDoc(analyzer, text);}\n```\n\n\n\n三种分词响应\n\napachespark专为大规模规模模数数据处理数据处理而设设计快速通用计算引擎\n\napachespark是专为大规模数据处理而设计的快速通用的计算引擎\n\napachspark是专为大规模数据处理而设计的快速通用的计算引擎\n\n## 高亮处理\n\n\n\n```\n/** * 高亮处理 * * @throws IOException */\n@Test\npublic void HighlighterTest() throws IOException, ParseException, InvalidTokenOffsetsException {\n//Analyzer analyzer = new StandardAnalyzer(); \n// 标准分词器，适用于英文   \n//Analyzer analyzer = new SmartChineseAnalyzer();\n//中文分词   \n//Analyzer analyzer = new ComplexAnalyzer();\n//中文分词    \n//Analyzer analyzer = new IKAnalyzer();\n//中文分词     Analyzer analyzer = new IKAnalyzer();\n//中文分词     String searchField = \"content\";    \nString text = \"Apache Spark 大规模数据处理\";     \n//指定搜索字段和分析器    \nQueryParser parser = new QueryParser(searchField, analyzer);     \n//用户输入内容    \nQuery query = parser.parse(text);     \nTopDocs topDocs = indexSearcher.search(query, 100);     \n// 关键字高亮显示的html标签，需要导入lucene-highlighter-xxx.jar    \nSimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter(\"\", \"\");    \nHighlighter highlighter = new Highlighter(simpleHTMLFormatter, new QueryScorer(query));     \nfor (ScoreDoc scoreDoc : topDocs.scoreDocs) {         \n//取得对应的文档对象        \nDocument document = indexSearcher.doc(scoreDoc.doc);         \n// 内容增加高亮显示        \nTokenStream tokenStream = analyzer.tokenStream(\"content\", new StringReader(document.get(\"content\")));        \nString content = highlighter.getBestFragment(tokenStream, document.get(\"content\"));         \nSystem.out.println(content);    \n} \n}\n```\n\n\n\n响应\n\nApache Spark 是专为大规模数据处理而设计的快速通用的计算引擎!\n\n代码我已放到 Github ，导入`spring-boot-lucene-demo` 项目\n\n[github spring-boot-lucene-demo](https://docs.qq.com/doc/DWVNPQXRvcWhMTktC)\n"
  },
  {
    "path": "docs/backend/后端技术杂谈：OpenStack架构设计.md",
    "content": "# 目录\n\n  * [openstack 逻辑架构图](#openstack-逻辑架构图)\n  * [OpenStack 项目和组件](#openstack-项目和组件)\n  * [生产部署架构](#生产部署架构)\n    * [控制器](#控制器)\n    * [计算](#计算)\n    * [块设备存储](#块设备存储)\n    * [对象存储](#对象存储)\n    * [网络](#网络)\n  * [涉及的 Linux 网络技术](#涉及的-linux-网络技术)\n  * [总结](#总结)\n\n\n[toc]\n\n本文转自互联网，侵删\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本系列文章将整理到我的个人博客\n\n> www.how2playlife.com\n\n更多Java技术文章会更新在我的微信公众号【Java技术江湖】上，欢迎关注\n该系列博文会介绍常见的后端技术，这对后端工程师来说是一种综合能力，我们会逐步了解搜索技术，云计算相关技术、大数据研发等常见的技术喜提，以便让你更完整地了解后端技术栈的全貌，为后续参与分布式应用的开发和学习做好准备。\n\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系我，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\nOpenStack 是开源云计算平台，支持多种虚拟化环境，并且其服务组件都提供了API接口便于二次开发。\n\nOpenStack通过各种补充服务提供基础设施即服务 Infrastructure-as-a-Service (IaaS)`的解决方案。每个服务都提供便于集成的应用程序接口`Application Programming Interface (API)。\n\n## openstack 逻辑架构图\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230408115819.png)\n\nOpenStack 本身是一个分布式系统，不但各个服务可以分布部署，服务中的组件也可以分布部署。 这种分布式特性让 OpenStack 具备极大的灵活性、伸缩性和高可用性。 当然从另一个角度讲，这也使得 OpenStack 比一般系统复杂，学习难度也更大。\n\n后面章节我们会深入学习 Keystone、Glance、Nova、Neutron 和 Cinder 这几个 OpenStack 最重要最核心的服务。\n\nopenstack的核心和扩展的主要项目如下：\n\n*   OpenStack Compute (code-name Nova) 计算服务\n\n*   OpenStack Networking (code-name Neutron) 网络服务\n\n*   OpenStack Object Storage (code-name Swift) 对象存储服务\n\n*   OpenStack Block Storage (code-name Cinder) 块设备存储服务\n\n*   OpenStack Identity (code-name Keystone) 认证服务\n\n*   OpenStack Image Service (code-name Glance) 镜像文件服务\n\n*   OpenStack Dashboard (code-name Horizon) 仪表盘服务\n\n*   OpenStack Telemetry (code-name Ceilometer) 告警服务\n\n*   OpenStack Orchestration (code-name Heat) 流程服务\n\n*   OpenStack Database (code-name Trove) 数据库服务\n\nOpenStack的各个服务之间通过统一的REST风格的API调用，实现系统的松耦合。上图是OpenStack各个服务之间API调用的概览，其中实线代表client 的API调用，虚线代表各个组件之间通过rpc调用进行通信。松耦合架构的好处是，各个组件的开发人员可以只关注各自的领域，对各自领域的修改不会影响到其他开发人员。不过从另一方面来讲，这种松耦合的架构也给整个系统的维护带来了一定的困难，运维人员要掌握更多的系统相关的知识去调试出了问题的组件。所以无论对于开发还是维护人员，搞清楚各个组件之间的相互调用关系是怎样的都是非常必要的。\n\n对Linux经验丰富的OpenStack新用户，使用openstack是非常容易的，在后续`openstack系列`文章中会逐步展开介绍。\n\n## OpenStack 项目和组件\n\nOpenStack services\n\n*   Dashboard   【Horizon】   提供了一个基于web的自服务门户，与OpenStack底层服务交互，诸如启动一个实例，分配IP地址以及配置访问控制。\n\n*   Compute    【Nova】    在OpenStack环境中计算实例的生命周期管理。按需响应包括生成、调度、回收虚拟机等操作。\n\n*   Networking  【Neutron】   确保为其它OpenStack服务提供网络连接即服务，比如OpenStack计算。为用户提供API定义网络和使用。基于插件的架构其支持众多的网络提供商和技术。\n\n*   Object Storage  【Swift】  通过一个 RESTful,基于HTTP的应用程序接口存储和任意检索的非结构化数据对象。它拥有高容错机制，基于数据复制和可扩展架构。它的实现并像是一个文件服务器需要挂载目录。在此种方式下，它写入对象和文件到多个硬盘中，以确保数据是在集群内跨服务器的多份复制。\n\n*   Block Storage    【Cinder】  为运行实例而提供的持久性块存储。它的可插拔驱动架构的功能有助于创建和管理块存储设备。\n\n*   Identity service 【Keystone】  为其他OpenStack服务提供认证和授权服务，为所有的OpenStack服务提供一个端点目录。\n\n*   Image service   【Glance】 存储和检索虚拟机磁盘镜像，OpenStack计算会在实例部署时使用此服务。\n\n*   Telemetry服务   【Ceilometer】 为OpenStack云的计费、基准、扩展性以及统计等目的提供监测和计量。\n\n*   Orchestration服务  【Heat服务】  Orchestration服务支持多样化的综合的云应用，通过调用OpenStack-native REST API和CloudFormation-compatible Query API，支持`HOT <Heat Orchestration Template (HOT)>`格式模板或者AWS CloudFormation格式模板\n\n通过对这些组件的介绍，可以帮助我们在后续的内容中，了解各个组件的作用，便于排查问题，而在你对基础安装，配置，操作和故障诊断熟悉之后，你应该考虑按照生产架构来进行部署。\n\n## 生产部署架构\n\n建议使用自动化部署工具，例如Ansible, Chef, Puppet, or Salt来自动化部署，管理生产环境。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230408115834.png)\n\n这个示例架构需要至少2个（主机）节点来启动基础服务`virtual machine <virtual machine (VM)>`或者实例。像块存储服务，对象存储服务这一类服务还需要额外的节点。\n\n*   网络代理驻留在控制节点上而不是在一个或者多个专用的网络节点上。\n\n*   私有网络的覆盖流量通过管理网络而不是专用网络\n\n### 控制器\n\n控制节点上运行身份认证服务，镜像服务，计算服务的管理部分，网络服务的管理部分，多种网络代理以及仪表板。也需要包含一些支持服务，例如：SQL数据库，term:消息队列, and NTP。\n\n可选的，可以在计算节点上运行部分块存储，对象存储，Orchestration 和 Telemetry 服务。\n\n计算节点上需要至少两块网卡。\n\n### 计算\n\n计算节点上运行计算服务中管理实例的管理程序部分。默认情况下，计算服务使用 KVM。\n\n你可以部署超过一个计算节点。每个结算节点至少需要两块网卡。\n\n### 块设备存储\n\n可选的块存储节点上包含了磁盘，块存储服务和共享文件系统会向实例提供这些磁盘。\n\n为了简单起见，计算节点和本节点之间的服务流量使用管理网络。生产环境中应该部署一个单独的存储网络以增强性能和安全。\n\n你可以部署超过一个块存储节点。每个块存储节点要求至少一块网卡。\n\n### 对象存储\n\n可选的对象存储节点包含了磁盘。对象存储服务用这些磁盘来存储账号，容器和对象。\n\n为了简单起见，计算节点和本节点之间的服务流量使用管理网络。生产环境中应该部署一个单独的存储网络以增强性能和安全。\n\n这个服务要求两个节点。每个节点要求最少一块网卡。你可以部署超过两个对象存储节点。\n\n### 网络\n\nopenstack网络是非常复杂的，并且也支持多种模式其中支持GRE，VLAN,VXLAN等，在openstack中网络是通过一个组件`Neutron`提供服务，Neutron 管理的网络资源包括如下。\n\n*   network 是一个隔离的二层广播域。Neutron 支持多种类型的 network，包括 local, flat, VLAN, VxLAN 和 GRE。\n\n*   local 网络与其他网络和节点隔离。local 网络中的 instance 只能与位于同一节点上同一网络的 instance 通信，local 网络主要用于单机测试。\n\n*   flat 网络是无 vlan tagging 的网络。flat 网络中的 instance 能与位于同一网络的 instance 通信，并且可以跨多个节点。\n\n*   vlan 网络是具有 802.1q tagging 的网络。vlan 是一个二层的广播域，同一 vlan 中的 instance 可以通信，不同 vlan 只能通过 router 通信。vlan 网络可以跨节点，是应用最广泛的网络类型。\n\n*   vxlan 是基于隧道技术的 overlay 网络。vxlan 网络通过唯一的 segmentation ID（也叫 VNI）与其他 vxlan 网络区分。vxlan 中数据包会通过 VNI 封装成 UPD 包进行传输。因为二层的包通过封装在三层传输，能够克服 vlan 和物理网络基础设施的限制。\n\n*   gre 是与 vxlan 类似的一种 overlay 网络。主要区别在于使用 IP 包而非 UDP 进行封装。 不同 network 之间在二层上是隔离的。以 vlan 网络为例，network A 和 network B 会分配不同的 VLAN ID，这样就保证了 network A 中的广播包不会跑到 network B 中。当然，这里的隔离是指二层上的隔离，借助路由器不同 network 是可能在三层上通信的。network 必须属于某个 Project（ Tenant 租户），Project 中可以创建多个 network。 network 与 Project 之间是 1对多关系。\n\n*   subnet 是一个 IPv4 或者 IPv6 地址段。instance 的 IP 从 subnet 中分配。每个 subnet 需要定义 IP 地址的范围和掩码。\n\n*   port 可以看做虚拟交换机上的一个端口。port 上定义了 MAC 地址和 IP 地址，当 instance 的虚拟网卡 VIF（Virtual Interface） 绑定到 port 时，port 会将 MAC 和 IP 分配给 VIF。port 与 subnet 是 1对多 关系。一个 port 必须属于某个 subnet；一个 subnet 可以有多个 port。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230408115845.png)\n\n如上图所示，为VLAN模式下，网络节点的通信方式。\n\n在我们后续实施安装的时候，选择使用VXLAN网络模式，下面我们来重点介绍一下VXLAN模式。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230408115856.png)\n\nVXLAN网络模式，可以隔离广播风暴，不需要交换机配置chunk口，解决了vlan id个数限制，解决了gre点对点隧道个数过多问题，实现了大2层网络，可以让vm在机房之间无缝迁移，便于跨机房部署。缺点是，vxlan增加了ip头部大小，需要降低vm的mtu值，传输效率上会略有下降。\n\n## 涉及的 Linux 网络技术\n\nNeutron 的设计目标是实现“网络即服务”，为了达到这一目标，在设计上遵循了基于“软件定义网络”实现网络虚拟化的原则，在实现上充分利用了 Linux 系统上的各种网络相关的技术。理解了 Linux 系统上的这些概念将有利于快速理解 Neutron 的原理和实现。\n\n*   bridge：网桥，Linux中用于表示一个能连接不同网络设备的虚拟设备，linux中传统实现的网桥类似一个hub设备，而ovs管理的网桥一般类似交换机。\n\n*   br-int：bridge-integration，综合网桥，常用于表示实现主要内部网络功能的网桥。\n\n*   br-ex：bridge-external，外部网桥，通常表示负责跟外部网络通信的网桥。\n\n*   GRE：General Routing Encapsulation，一种通过封装来实现隧道的方式。在openstack中一般是基于L3的gre，即original pkt/GRE/IP/Ethernet\n\n*   VETH：虚拟ethernet接口，通常以pair的方式出现，一端发出的网包，会被另一端接收，可以形成两个网桥之间的通道。\n\n*   qvb：neutron veth, Linux Bridge-side\n\n*   qvo：neutron veth, OVS-side\n\n*   TAP设备：模拟一个二层的网络设备，可以接受和发送二层网包。\n\n*   TUN设备：模拟一个三层的网络设备，可以接受和发送三层网包。\n\n*   iptables：Linux 上常见的实现安全策略的防火墙软件。\n\n*   Vlan：虚拟 Lan，同一个物理 Lan 下用标签实现隔离，可用标号为1-4094。\n\n*   VXLAN：一套利用 UDP 协议作为底层传输协议的 Overlay 实现。一般认为作为 VLan 技术的延伸或替代者。\n\n*   namespace：用来实现隔离的一套机制，不同 namespace 中的资源之间彼此不可见。\n\n## 总结\n\nopenstack是一个非法复杂的分布式软件，涉及到很多底层技术，我自己对一些网络的理解也是非常有限，主要还是应用层面的知识，所以本章内容写的比较浅显一些，有问题请留言？在下一章节我们会进入生产环境如何实施规划openstack集群，至于openstack底层的技术，我也没有很深入研究，如果有任何不恰当的地方可以进行留言，非常感谢！\n"
  },
  {
    "path": "docs/backend/后端技术杂谈：OpenStack的基石KVM.md",
    "content": "# 目录\n\n  * [Kvm虚拟化技术实践](#kvm虚拟化技术实践)\n    * [VMware虚拟机支持Kvm虚拟化技术？](#vmware虚拟机支持kvm虚拟化技术？)\n    * [安装Kvm虚拟化软件](#安装kvm虚拟化软件)\n    * [kvm创建虚拟机](#kvm创建虚拟机)\n    * [虚拟机远程管理软件](#虚拟机远程管理软件)\n    * [KVM虚拟机管理](#kvm虚拟机管理)\n    * [libvirt虚拟机配置文件](#libvirt虚拟机配置文件)\n    * [监控kvm虚拟机](#监控kvm虚拟机)\n    * [KVM修改NAT模式为桥接](#kvm修改nat模式为桥接)\n    * [总结](#总结)\n\n\n\n\n[toc]\n\n本文转载自[Itweet](https://link.juejin.im/?target=http%3A%2F%2Fwww.itweet.cn)的博客\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本系列文章将整理到我的个人博客\n\n> www.how2playlife.com\n\n更多Java技术文章会更新在我的微信公众号【Java技术江湖】上，欢迎关注\n该系列博文会介绍常见的后端技术，这对后端工程师来说是一种综合能力，我们会逐步了解搜索技术，云计算相关技术、大数据研发等常见的技术喜提，以便让你更完整地了解后端技术栈的全貌，为后续参与分布式应用的开发和学习做好准备。\n\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系我，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n\n\n当你安装了一台Linux，想启动一个KVM虚拟机的时候，你会发现需要安装不同的软件，启动虚拟机的时候，有多种方法：\n\n*   virsh start\n\n*   kvm命令\n\n*   qemu命令\n\n*   qemu-kvm命令\n\n*   qemu-system-x86_64命令\n\n这些之间是什么关系呢？请先阅读上一篇《[白话虚拟化技术](https://blog.csdn.net/a724888/article/details/80996570)》\n\n有了上一篇的基础，我们就能说清楚来龙去脉。\n\nKVM（Kernel-based Virtual Machine的英文缩写）是内核内建的虚拟机。有点类似于 Xen ，但更追求更简便的运作，比如运行此虚拟机，仅需要加载相应的 kvm 模块即可后台待命。和 Xen 的完整模拟不同的是，KVM 需要芯片支持虚拟化技术（英特尔的 VT 扩展或者 AMD 的 AMD-V 扩展）。\n\n首先看qemu，其中关键字emu，全称emulator，模拟器，所以单纯使用qemu是采用的完全虚拟化的模式。\n\nQemu向Guest OS模拟CPU，也模拟其他的硬件，GuestOS认为自己和硬件直接打交道，其实是同Qemu模拟出来的硬件打交道，Qemu将这些指令转译给真正的硬件。由于所有的指令都要从Qemu里面过一手，因而性能比较差\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/635909-20180307150620008-108720261.jpg)\n\n按照上一次的理论，完全虚拟化是非常慢的，所以要使用硬件辅助虚拟化技术Intel-VT，AMD-V，所以需要CPU硬件开启这个标志位，一般在BIOS里面设置。查看是否开启\n\n对于Intel CPU 可用命令 grep \"vmx\" /proc/cpuinfo 判断\n\n对于AMD CPU 可用命令 grep \"svm\" /proc/cpuinfo 判断\n\n当确认开始了标志位之后，通过KVM，GuestOS的CPU指令不用经过Qemu转译，直接运行，大大提高了速度。\n\n所以KVM在内核里面需要有一个模块，来设置当前CPU是Guest OS在用，还是Host OS在用。\n\n查看内核模块中是否含有kvm, ubuntu默认加载这些模块\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/635909-20180307150634298-628102674.png)\n\nKVM内核模块通过/dev/kvm暴露接口，用户态程序可以通过ioctl来访问这个接口，例如书写下面的程序\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/635909-20180307150654328-1662633336.png)\n\nQemu将KVM整合进来，通过ioctl调用/dev/kvm接口，将有关CPU指令的部分交由内核模块来做，就是qemu-kvm (qemu-system-XXX)\n\nQemu-kvm对kvm的整合从release_0_5_1开始有branch，在1.3.0正式merge到master\n\n![](https://images2018.cnblogs.com/blog/635909/201803/635909-20180307150710177-1591777831.png)\n\nqemu和kvm整合之后，CPU的性能问题解决了，另外Qemu还会模拟其他的硬件，如Network, Disk，同样全虚拟化的方式也会影响这些设备的性能。\n\n于是qemu采取半虚拟化或者类虚拟化的方式，让Guest OS加载特殊的驱动来做这件事情。\n\n例如网络需要加载virtio_net，存储需要加载virtio_blk，Guest需要安装这些半虚拟化驱动，GuestOS知道自己是虚拟机，所以数据直接发送给半虚拟化设备，经过特殊处理，例如排队，缓存，批量处理等性能优化方式，最终发送给真正的硬件，一定程度上提高了性能。\n\n至此整个关系如下：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/635909-20180307150733042-369996016.jpg)\n\nqemu-kvm会创建Guest OS，当需要执行CPU指令的时候，通过/dev/kvm调用kvm内核模块，通过硬件辅助虚拟化方式加速。如果需要进行网络和存储访问，则通过类虚拟化或者直通Pass through的方式，通过加载特殊的驱动，加速访问网络和存储资源。\n\n然而直接用qemu或者qemu-kvm或者qemu-system-xxx的少，大多数还是通过virsh启动，virsh属于libvirt工具，libvirt是目前使用最为广泛的对KVM虚拟机进行管理的工具和API，可不止管理KVM。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/635909-20180307150801681-1586679180.jpg)\n\nLibvirt分服务端和客户端，Libvirtd是一个daemon进程，是服务端，可以被本地的virsh调用，也可以被远程的virsh调用，virsh相当于客户端。\n\nLibvirtd调用qemu-kvm操作虚拟机，有关CPU虚拟化的部分，qemu-kvm调用kvm的内核模块来实现\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/635909-20180307150815111-1223973253.jpg)\n\n这下子，整个相互关系才搞清楚了。\n\n虽然使用virsh创建虚拟机相对简单，但是为了探究虚拟机的究竟如何使用，下一次，我们来解析一下如何裸使用qemu-kvm来创建一台虚拟机，并且能上网。\n\n如果搭建使用过vmware桌面版或者virtualbox桌面版，创建一个能上网的虚拟机非常简单，但是其实背后做了很多事情，下一次我们裸用qemu-kvm，全部使用手工配置，看创建虚拟机都做了哪些事情。\n\n本章节我们主要介绍通过VMware技术虚拟出相关的Linux软件环境，在Linux系统中，安装KVM虚拟化软件，实实在在的去实践一下KVM到底是一个什么样的技术？\n\n## Kvm虚拟化技术实践\n\n### VMware虚拟机支持Kvm虚拟化技术？\n\n在VMware创建的虚拟机中，默认不支持Kvm虚拟化技术，需要芯片级的扩展支持，幸好VMware提供完整的解决方案，可以通过修改虚拟化引擎。\n\nVMware软件版本信息，`VMware® Workstation 11.0.0 build-2305329`\n\n首先，你需要启动VMware软件，新建一个`CentOS 6.x`类型的虚拟机，正常安装完成，这个虚拟机默认的`虚拟化引擎`，`首选模式`为”自动”。\n\n如果想让我们的VMware虚拟化出来的CentOS虚拟机支持KVM虚拟化，我们需要修改它支持的`虚拟化引擎`,打开新建的虚拟机，虚拟机状态必须处于`关闭`状态，通过双击`编辑虚拟机设置`##>##`硬件`##，选择`处理器`菜单，右边会出现`虚拟化引擎`区域，选择`首选模式`为##_Intel Tv-x/EPT或AMD-V/RVI_,接下来勾选`虚拟化Intel Tv-x/EPT或AMD-V/RVI(v)`，点击`确定`。\n\nKVM需要虚拟机宿主（host）的处理器带有虚拟化支持（对于Intel处理器来说是VT-x，对于AMD处理器来说是AMD-V）。你可以通过以下命令来检查你的处理器是否支持虚拟化：\n\n```\n grep --color -E '(vmx|svm)' /proc/cpuinfo\n\n```\n\n如果运行后没有显示，那么你的处理器不支持硬件虚拟化，你不能使用KVM。\n\n*   注意: 如果是硬件服务器，您可能需要在BIOS中启用虚拟化支持，参考[Private Cloud personal workstation](https://link.juejin.im/?target=http%3A%2F%2Fwww.itweet.cn%2Fblog%2F2016%2F06%2F14%2FPrivate%2520Cloud%2520personal%2520workstation)\n\n### 安装Kvm虚拟化软件\n安装kvm虚拟化软件，我们需要一个Linux操作系统环境，这里我们选择的Linux版本为`CentOS release 6.8 (Final)`，在这个VMware虚拟化出来的虚拟机中安装kvm虚拟化软件，具体步骤如下：\n\n* 首选安装epel源\n\n  ```\n  sudo rpm -ivh http://mirrors.ustc.edu.cn/fedora/epel/6/x86_64/epel-release-6-8.noarch.rpm\n  \n  ```\n\n* 安装kvm虚拟化软件\n\n  ```\n  sudo yum install qemu-kvm qeum-kvm-tools virt-manager libvirt\n  \n  ```\n\n* 启动kvm虚拟化软件\n\n  ```\n  sudo /etc/init.d/libvirtd start\n  \n  ```\n\n启动成功之后你可以通过`/etc/init.d/libvirtd status`查看启动状态，这个时候，kvm会自动生成一个本地网桥##`virbr0`，可以通过命令查看他的详细信息\n\n```\n ifconfig virbr0virbr0    Link encap:Ethernet  HWaddr 52:54:00:D7:23:AD            inet addr:192.168.122.1  Bcast:192.168.122.255  Mask:255.255.255.0          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1          RX packets:0 errors:0 dropped:0 overruns:0 frame:0          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0          collisions:0 txqueuelen:0           RX bytes:0 (0.0 b)  TX bytes:0 (0.0 b)\n```\n\nKVM默认使用NAT网络模式。虚拟机获取一个私有 IP（例如 192.168.122.0/24 网段的），并通过本地主机的NAT访问外网。\n\n```\n brctl showbridge name     bridge id               STP enabled     interfacesvirbr0          8000.525400d723ad       yes             virbr0-nic\n```\n\n创建一个本地网桥virbr0，包括两个端口：virbr0-nic 为网桥内部端口，vnet0 为虚拟机网关端口（192.168.122.1）。\n\n虚拟机启动后，配置 192.168.122.1（vnet0）为网关。所有网络操作均由本地主机系统负责。\n\nDNS/DHCP的实现，本地主机系统启动一个 dnsmasq 来负责管理。\n\n```\nps aux|grep dnsmasq\n\n```\n\n`注意：`##启动libvirtd之后自动启动iptables，并且写上一些默认规则。\n\n```\n iptables -nvL -t natChain PREROUTING (policy ACCEPT 304 packets, 38526 bytes) pkts bytes target     prot opt in     out     source               destination          Chain POSTROUTING (policy ACCEPT 7 packets, 483 bytes) pkts bytes target     prot opt in     out     source               destination             0     0 MASQUERADE  tcp  --  *      *       192.168.122.0/24    !192.168.122.0/24    masq ports: 1024-65535     0     0 MASQUERADE  udp  --  *      *       192.168.122.0/24    !192.168.122.0/24    masq ports: 1024-65535     0     0 MASQUERADE  all  --  *      *       192.168.122.0/24    !192.168.122.0/24     Chain OUTPUT (policy ACCEPT 7 packets, 483 bytes) pkts bytes target     prot opt in     out     source               destination\n```\n\n### kvm创建虚拟机\n上传一个镜像文件：`CentOS-6.6-x86_64-bin-DVD1.iso`\n\n通过`qemu`创建一个raw格式的文件(注：QEMU使用的镜像文件：qcow2与raw，它们都是QEMU(KVM)虚拟机使用的磁盘文件格式)，大小为5G。\n\n```\nqemu-img create -f raw /data/Centos-6.6-x68_64.raw 5G\n\n```\n\n查看创建的raw磁盘格式文件信息\n\n```\nqemu-img info /data/Centos-6.6-x68_64.raw  image: /data/Centos-6.6-x68_64.rawfile format: rawvirtual size: 5.0G (5368709120 bytes)disk size: 0\n```\n\n启动，kvm虚拟机，进行操作系统安装\n\n```\nvirt-install  --virt-type kvm --name CentOS-6.6-x86_64 --ram 512 --cdrom /data/CentOS-6.6-x86_64-bin-DVD1.iso --disk path=/data/Centos-6.6-x68_64.raw --network network=default --graphics vnc,listen=0.0.0.0 --noautoconsole\n\n```\n\n启动之后，通过命令查看启动状态，默认会在操作系统开一个`5900`的端口，可以通过虚拟机远程管理软件`vnc`客户端连接，然后可视化的方式安装操作系统。\n\n```\n netstat -ntlp|grep 5900tcp        0      0 0.0.0.0:5900                0.0.0.0:*                   LISTEN      2504/qemu-kvm\n```\n\n`注意`：kvm安装的虚拟机，不确定是那一台，在后台就是一个进程，每增加一台端口号+1，第一次创建的为5900！\n\n### 虚拟机远程管理软件\n我们可以使用虚拟机远程管理软件VNC进行操作系统的安装，我使用过的两款不错的虚拟机远程管理终端软件，一个是Windows上使用，一个在Mac上为了方便安装一个Google Chrome插件后即可开始使用，软件信息##`Tightvnc`##或者##`VNC[@Viewer](https://link.juejin.im/?target=https%3A%2F%2Fgithub.com%2FViewer \"@Viewer\")##for Google Chrome`\n\n如果你和我一样使用的是`Google Chrome`提供的VNC插件，使用方式，在`Address`输入框中输入，宿主机IP:59000,`Picture Quality`选择框使用默认选项，点击`Connect`进入到安装操作系统的界面，你可以安装常规的方式进行安装，等待系统安装完成重启，然后就可以正常使用kvm虚拟化出来的操作系统了。\n\n`Tightvnc`软件的使用，请参考官方手册。\n\n*   Tightvnc下载地址：[www.tightvnc.com/download.ph…](https://link.juejin.im/?target=http%3A%2F%2Fwww.tightvnc.com%2Fdownload.php)\n*   Tightvnc下载地址：[www.tightvnc.com/download/2.…](https://link.juejin.im/?target=http%3A%2F%2Fwww.tightvnc.com%2Fdownload%2F2.7.10%2Ftightvnc-2.7.10-setup-64bit.msi)\n*   Tightvnc下载地址：[www.tightvnc.com/download/2.…](https://link.juejin.im/?target=http%3A%2F%2Fwww.tightvnc.com%2Fdownload%2F2.7.10%2Ftightvnc-2.7.10-setup-32bit.msi)\n\n### KVM虚拟机管理\nkvm虚拟机是通过virsh命令进行管理的，libvirt是Linux上的虚拟化库，是长期稳定的C语言API，支持KVM/QEMU、Xen、LXC等主流虚拟化方案。链接：[libvirt.org/](https://link.juejin.im/?target=http%3A%2F%2Flibvirt.org%2F)\nvirsh是Libvirt对应的shell命令。\n\n查看所有虚拟机状态\n\n```\nvirsh list --all\n\n```\n\n启动虚拟机\n\n```\nvirsh start [NAME]\n\n```\n\n列表启动状态的虚拟机\n\n```\nvirsh list\n\n```\n\n* 常用命令查看\n\n  ```\n  virsh --help|more less\n  \n  ```\n\n### libvirt虚拟机配置文件\n虚拟机libvirt配置文件在`/etc/libvirt/qemu`路径下，生产中我们需要去修改它的网络信息。\n\n```\n lltotal 8-rw-------. 1 root root 3047 Oct 19  2016 Centos-6.6-x68_64.xmldrwx------. 3 root root 4096 Oct 17  2016 networks\n```\n\n`注意`：不能直接修改xml文件，需要通过提供的命令！\n\n```\n virsh edit Centos-6.6-x68_64\n\n```\n\nkvm三种网络类型,桥接、NAT、仅主机模式，默认NAT模式,其他机器无法登陆，生产中一般选择桥接。\n\n### 监控kvm虚拟机\n*   安装软件监控虚拟机\n\n```\nyum install virt-top -y\n\n```\n\n*   查看虚拟机资源使用情况\n\n```\nvirt-top virt-top 23:46:39 - x86_64 1/1CPU 3392MHz 3816MB1 domains, 1 active, 1 running, 0 sleeping, 0 paused, 0 inactive D:0 O:0 X:0CPU: 5.6%  Mem: 2024 MB (2024 MB by guests)    ID S RDRQ WRRQ RXBY TXBY %CPU %MEM    TIME   NAME                                                                                                     1 R    0    1   52    0  5.6 53.0   5:16.15 centos-6.8\n```\n\n### KVM修改NAT模式为桥接\n在开始案例之前，需要知道的必要信息，宿主机IP是`192.168.2.200`，操作系统版本`Centos-6.6-x68_64`。\n\n启动虚拟网卡\n\n```\nifup eth0\n\n```\n\n这里网卡是NAT模式，可以上网，ping通其他机器，但是其他机器无法登陆！\n\n宿主机查看网卡信息\n\n```\nbrctl show ifconfig virbr0 ifconfig vnet0\n```\n\n实现网桥，在kvm宿主机完成\n\n*   步骤1，创建一个网桥，新建网桥连接到eth0,删除eth0,让新的网桥拥有eth0的ip\n\n```\nbrctl addbr br0  #创建一个网桥 brctl show       #显示网桥信息 brctl addif br0 eth0 && ip addr del dev eth0 192.168.2.200/24 && ifconfig br0 192.168.2.200/24 up brctl show      #查看结果ifconfig br0    #验证br0是否成功取代了eth0的IP\n```\n\n`注意`: 这里的IP地址为##_宿主机ip_\n\n*   修改虚拟机桥接到br0网卡，在宿主机修改\n\n```\nvirsh list --all ps aux |grep kvm virsh stop Centos-6.6-x68_64 virsh list --all\n```\n\n修改虚拟机桥接到宿主机，修改52行type为`bridge`，第54行bridge为`br0`\n\n```\n virsh edit Centos-6.6-x68_64  # 命令 52     <interface type='network'>     53       <mac address='52:54:00:2a:2d:60'/>     54       <source network='default'/>     55            56     </interface> 修改为：52     <interface type='bridge'>     53       <mac address='52:54:00:2a:2d:60'/>     54       <source bridge='br0'/>     55            56     </interface>\n```\n\n启动虚拟机，看到启动前后，桥接变化，vnet0被桥接到了br0\n\n启动前：\n\n```\n brctl showbridge name     bridge id               STP enabled     interfacesbr0             8000.000c29f824c9       no              eth0virbr0          8000.525400353d8e       yes             virbr0-nic\n```\n\n启动后：\n\n```\n virsh start CentOS-6.6-x86_64Domain CentOS-6.6-x86_64 started # brctl show                   bridge name     bridge id               STP enabled     interfacesbr0             8000.000c29f824c9       no              eth0                                                        vnet0virbr0          8000.525400353d8e       yes             virbr0-nic\n```\n\nVnc登陆后，修改ip地址，看到dhcp可以使用，被桥接到现有的ip段，ip是自动获取,而且是和宿主机在同一个IP段.\n\n```\n ifup eth0\n\n```\n\n从宿主机登陆此服务器，可以成功。\n\n```\n ssh 192.168.2.108root@192.168.2.108's password: Last login: Sat Jan 30 12:40:28 2016\n```\n\n从同一网段其他服务器登陆此虚拟机,也可以成功,至此让kvm管理的服务器能够桥接上网就完成了，在生产环境中，桥接上网是非常必要的。\n\n### 总结\n通过kvm相关的命令来创建虚拟机，安装和调试是非常必要的，因为现有的很多私有云，公有云产品都使用到了kvm这样的技术，学习基本的kvm使用对维护`openstack`集群有非常要的作用，其次所有的`openstack image`制作也得通过kvm这样的底层技术来完成，最后上传到`openstack`的镜像管理模块，才能开始通过`openstack image`生成云主机。\n\n到此，各位应该能够体会到，其实kvm是一个非常底层和核心的虚拟化技术，而openstack就是对`kvm`这样的技术进行了一个上层封装，可以非常方便，可视化的操作和维护`kvm`虚拟机，这就是现在`牛`上天的`云计算`技术最底层技术栈\n\n没有`openstack`我们依然可以通过，`libvirt`来对虚拟机进行操作，只不过比较繁琐和难以维护。通过openstack就可以非常方便的进行底层虚拟化技术的管理、维护、使用。\n\n"
  },
  {
    "path": "docs/backend/后端技术杂谈：云计算的前世今生.md",
    "content": "# 目录\n\n* [云计算概述](#云计算概述)\n* [云计算发展脉络](#云计算发展脉络)\n  * [第一阶段：合，即物理设备](#第一阶段：合，即物理设备)\n    * [物理设备简介](#物理设备简介)\n    * [物理设备的缺点](#物理设备的缺点)\n  * [第二阶段：分，即虚拟化](#第二阶段：分，即虚拟化)\n    * [虚拟化简介](#虚拟化简介)\n    * [虚拟化解决的问题](#虚拟化解决的问题)\n    * [虚拟化时代的生态](#虚拟化时代的生态)\n    * [虚拟化的缺点](#虚拟化的缺点)\n  * [第三阶段：合，即云计算](#第三阶段：合，即云计算)\n    * [云计算解决的问题](#云计算解决的问题)\n    * [私有云、公有云的两极分化](#私有云、公有云的两极分化)\n    * [私有云厂商与公有云厂商的联系与区别](#私有云厂商与公有云厂商的联系与区别)\n    * [公有云生态及老二的逆袭](#公有云生态及老二的逆袭)\n    * [OpenStack的组件](#openstack的组件)\n    * [OpenStack带来私有云市场的红海](#openstack带来私有云市场的红海)\n    * [公有or私有？网易云的选择](#公有or私有？网易云的选择)\n  * [第四阶段：分，即容器](#第四阶段：分，即容器)\n    * [现在来谈谈，应用层面，即PaaS层。](#现在来谈谈，应用层面，即paas层。)\n      * [1\\. PaaS的定义与作用](#1-paas的定义与作用)\n      * [2\\. PaaS的优点](#2-paas的优点)\n      * [3\\. PaaS部署的问题](#3-paas部署的问题)\n    * [容器的诞生](#容器的诞生)\n      * [1\\. 容器的定义](#1-容器的定义)\n      * [2.容器在开发中的应用](#2容器在开发中的应用)\n    * [容器的诞生](#容器的诞生-1)\n    * [容器管理平台](#容器管理平台)\n    * [容器初体验](#容器初体验)\n    * [容器管理平台初体验](#容器管理平台初体验)\n\n\n[toc]\n\n作者简介：刘超，网易云解决方案首席架构师。\n\n10年云计算领域研发及架构经验，Open DC/OS贡献者。\n\n长期专注于kubernetes, OpenStack、Hadoop、Docker、Lucene、Mesos等开源软件的企业级应用及产品化。曾出版《Lucene应用开发揭秘》。\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本系列文章将整理到我的个人博客\n\n> www.how2playlife.com\n\n更多Java技术文章会更新在我的微信公众号【Java技术江湖】上，欢迎关注\n该系列博文会介绍常见的后端技术，这对后端工程师来说是一种综合能力，我们会逐步了解搜索技术，云计算相关技术、大数据研发等常见的技术喜提，以便让你更完整地了解后端技术栈的全貌，为后续参与分布式应用的开发和学习做好准备。\n\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系我，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n**以下为正文：**\n\n# 云计算概述\n\n云计算主要解决了四个方面的内容：计算，网络，存储，应用。\n\n计算就是CPU和内存，例如“1+1”这个最简单的算法就是把“1”放在内存里面，然后CPU做加法，返回的结果“2”又保存在内存里面。网络就是你插根网线能上网。存储就是你下个电影有地方放。本次讨论就是围绕这四个部分来讲的。其中，计算、网络、存储三个是IaaS层面，应用是PaaS层面。\n\n# 云计算发展脉络\n\n云计算整个发展过程，用一句话来形容，就是“分久必合，合久必分”。\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230408115426.png)\n\n## 第一阶段：合，即物理设备\n\n### 物理设备简介\n\n在互联网发展初期，大家都爱用物理设备：\n\n1.  服务器用物理机，像戴尔、惠普、IBM、联想等物理服务器，随着硬件设备的进步，物理服务器越来越强大了，64核128G内存都算是普通配置；\n2.  网络用的是硬件交换机和路由器，例如思科的，华为的，从1GE到10GE，现在有40GE和100GE，带宽越来越牛；\n3.  存储方面有的用普通的磁盘，也有更快的SSD盘。容量从M，到G，连笔记本电脑都能配置到T，更何况磁盘阵列；\n\n### 物理设备的缺点\n\n部署应用直接使用物理机，看起来很爽，有种土豪的感觉，却有大大的缺点：\n\n1.  人工运维。如果你在一台服务器上安装软件，把系统安装坏了，怎么办？只有重装。当你想配置一下交换机的参数，需要串口连上去进行配置；当你想增加一块磁盘，要买一块插进服务器，这些都需要人工来，而且很大可能要求机房。你们公司在北五环，机房在南六环，这酸爽。\n2.  浪费资源。其实你只想部署一个小小的网站，却要用128G的内存。混着部署吧，就有隔离性的问题。\n3.  隔离性差。你把好多的应用部署在同一台物理机上，他们之间抢内存、抢cpu，一个写满了硬盘，另一个就没法用了，一个弄挂了内核，另一个也跟著挂了，如果部署两个相同的应用，端口还会冲突，动不动就会出错。\n\n## 第二阶段：分，即虚拟化\n\n### 虚拟化简介\n\n因为物理设备的以上缺点，就有了第一次“合久必分”的过程，叫做虚拟化。所谓虚拟化，就是把实的变成虚的：\n\n1.  物理机变为虚拟机。cpu是虚拟的，内存是虚拟的，内核是虚拟的，硬盘是虚拟的；\n2.  物理交换机变为虚拟交换机。网卡是虚拟的，交换机是虚拟的，带宽也是虚拟的；\n3.  物理存储变成虚拟存储。多块硬盘虚拟成一大块；\n\n### 虚拟化解决的问题\n\n虚拟化很好地解决了在物理设备阶段存在的三个问题：\n\n1.  人工运维。虚拟机的创建和删除都可以远程操作，虚拟机被玩坏了，删了再建一个分钟级别的。虚拟网络的配置也可以远程操作，创建网卡、分配带宽都是调用接口就能搞定的；\n2.  资源浪费。虚拟化了以后，资源可以分配地很小很小，比如1个cpu，1G内存，1M带宽，1G硬盘，都可以被虚拟出来；\n3.  隔离性差。每个虚拟机都有独立的cpu、 内存、硬盘、网卡，不同虚拟机之间的应用互不干扰；\n\n### 虚拟化时代的生态\n\n在虚拟化阶段，领跑者是Vmware，可以实现基本的计算、网络、存储的虚拟化。\n如同这个世界有闭源就有开源、有windows就有linux、有Apple就有Android一样，有Vmware，就有Xen和KVM。\n\n在开源虚拟化方面，Xen 的Citrix做的不错，后来Redhat在KVM发力不少；对于网络虚拟化，有Openvswitch，可以通过命令创建网桥、网卡、设置VLAN、设置带宽；对于存储虚拟化，本地盘有LVM，可以将多个硬盘变成一大块盘，然后在里面切出一小块给用户。\n\n### 虚拟化的缺点\n\n但是虚拟化也有缺点。通过虚拟化软件创建虚拟机，需要人工指定放在哪台机器上、硬盘放在哪个存储设备上，网络的VLAN ID、带宽的具体配置等，都需要人工指定。所以仅使用虚拟化的运维工程师往往有一个Excel表格，记录有多少台物理机，每台机器部署了哪些虚拟机。受此限制，一般虚拟化的集群数目都不是特别大。\n\n## 第三阶段：合，即云计算\n\n### 云计算解决的问题\n\n为了解决虚拟化阶段遗留的问题，于是有了分久必合的过程。这个过程我们可以形象地称为池化。\n虚拟化将资源分得很细，但是如此细分的资源靠Excel去管理，成本太高。池化就是将资源打成一个大的池，当需要资源的时候，帮助用户自动地选择，而非用户指定。这个阶段的关键点：调度器Scheduler。\n\n### 私有云、公有云的两极分化\n\n这样，Vmware有了自己的Vcloud；也有了基于Xen和KVM的私有云平台CloudStack（后来Citrix将其收购后开源）。\n\n当这些私有云平台在用户的数据中心里卖得奇贵无比、赚得盆满钵盈的时候，有其他的公司开始了另外的选择。这就是AWS和Google，他们开始了公有云领域的探索。\n\nAWS最初就是基于Xen技术进行虚拟化的，并且最终形成了公有云平台。也许AWS最初只是不想让自己的电商领域的利润全部交给私有云厂商吧，所以自己的云平台首先支撑起了自己的业务。在这个过程中，AWS严肃地使用了自己的云计算平台，使得公有云平台并不是对资源的配置更加友好，而是对应用的部署更加友好，最终大放异彩。\n\n### 私有云厂商与公有云厂商的联系与区别\n\n如果仔细观察就会发现，私有云和公有云虽然使用的是类似的技术，但在产品设计上却是完全不同的两种生物。\n\n私有云厂商和公有云厂商也拥有类似的技术，但在产品运营上呈现出完全不同的基因。\n\n私有云厂商是卖资源的，所以往往在卖私有云平台的时候伴随着卖计算、网络、存储设备。在产品设计上，私有云厂商往往会对客户强调其几乎不会使用的计算、网络、存储的技术参数，因为这些参数可以在和友商对标的过程中占尽优势。私有云的厂商几乎没有自己的大规模应用，所以私有云厂商的平台做出来是给别人用的，自己不会大规模使用，所以产品往往围绕资源展开，而不会对应用的部署友好。\n\n公有云的厂商往往都是有自己大规模的应用需要部署，所以其产品的设计可以将常见的应用部署需要的模块作为组件提供出来，用户可以像拼积木一样，拼接一个适用于自己应用的架构。公有云厂商不必关心各种技术参数的PK，不必关心是否开源，是否兼容各种虚拟化平台，是否兼容各种服务器设备、网络设备、存储设备。你管我用什么，客户部署应用方便就好。\n\n### 公有云生态及老二的逆袭\n\n公有云的第一名AWS活的自然很爽，作为第二名Rackspace就不那么舒坦了。\n\n没错，互联网行业基本上就是一家独大，那第二名如何逆袭呢？开源是很好的办法，让整个行业一起为这个云平台出力。于是Rackspace与美国航空航天局（NASA）合作创始了开源云平台OpenStack。\n\nOpenStack现在发展的和AWS有点像了，所以从OpenStack的模块组成可以看到云计算池化的方法。\n\n### OpenStack的组件\n\n1.  计算池化模块Nova：OpenStack的计算虚拟化主要使用KVM，然而到底在哪个物理机上开虚拟机呢，这要靠nova-scheduler；\n2.  网络池化模块Neutron：OpenStack的网络虚拟化主要使用Openvswitch，然而对于每一个Openvswitch的虚拟网络、虚拟网卡、VLAN、带宽的配置，不需要登录到集群上配置，Neutron可以通过SDN的方式进行配置；\n3.  存储池化模块Cinder: OpenStack的存储虚拟化，如果使用本地盘，则基于LVM，使用哪个LVM上分配的盘，也是通过scheduler来的。后来就有了将多台机器的硬盘打成一个池的方式Ceph，而调度的过程，则在Ceph层完成。\n\n### OpenStack带来私有云市场的红海\n\n有了OpenStack，所有的私有云厂商都疯了，原来VMware在私有云市场赚的实在太多了，眼巴巴的看着，没有对应的平台可以和他抗衡。现在有了现成的框架，再加上自己的硬件设备，几乎所有的IT厂商巨头，全部都加入到社区里，将OpenStack开发为自己的产品，连同硬件设备一起，杀入私有云市场。\n\n### 公有or私有？网易云的选择\n\n网易云当然也没有错过这次风口，上线了自己的OpenStack集群，网易云基于OpenStack自主研发了IaaS服务，在计算虚拟化方面，通过裁剪KVM镜像，优化虚拟机启动流程等改进，实现了虚拟机的秒级别启动。在网络虚拟化方面，通过SDN和Openvswitch技术，实现了虚拟机之间的高性能互访。在存储虚拟化方面，通过优化Ceph存储，实现高性能云盘。\n\n但是网易云并没有杀进私有云市场，而是使用OpenStack支撑起了自己的应用，这是互联网的思维。而仅仅是资源层面弹性是不够的，还需要开发出对应用部署友好的组件。例如数据库，负载均衡，缓存等，这些都是应用部署必不可少的，也是网易云在大规模应用实践中，千锤百炼过的。这些组件称为PaaS。\n\n## 第四阶段：分，即容器\n\n### 现在来谈谈，应用层面，即PaaS层。\n\n前面一直在讲IaaS层的故事，也即基础设施即服务，基本上在谈计算、网络、存储的事情。现在应该说说应用层，即PaaS层的事情了。\n\n#### 1. PaaS的定义与作用\n\nIaaS的定义比较清楚，PaaS的定义就没那么清楚了。有人把数据库、负载均衡、缓存作为PaaS服务；有人把大数据Hadoop,、Spark平台作为PaaS服务；还有人将应用的安装与管理，例如Puppet、 Chef,、Ansible作为PaaS服务。\n\n其实PaaS主要用于管理应用层。我总结为两部分：一部分是你自己的应用应当自动部署，比如Puppet、Chef、Ansible、 Cloud Foundry等，可以通过脚本帮你部署；另一部分是你觉得复杂的通用应用不用部署，比如数据库、缓存、大数据平台，可以在云平台上一点即得。\n\n要么就是自动部署，要么就是不用部署，总的来说就是应用层你也少操心，就是PaaS的作用。当然最好还是都不用去部署，一键可得，所以公有云平台将通用的服务都做成了PaaS平台。另一些你自己开发的应用，除了你自己其他人不会知道，所以你可以用工具变成自动部署。\n\n#### 2. PaaS的优点\n\nPaaS最大的优点，就是可以实现应用层的弹性伸缩。比如在双十一期间，10个节点要变成100个节点，如果使用物理设备，再买90台机器肯定来不及，仅仅有IaaS实现资源的弹性是不够的，再创建90台虚拟机，也是空的，还是需要运维人员一台一台地部署。所以有了PaaS就好了，一台虚拟机启动后，马上运行自动部署脚本，进行应用的安装，90台机器自动安装好了应用，才是真正的弹性伸缩。\n\n#### 3. PaaS部署的问题\n\n当然这种部署方式也有一个问题，就是无论Puppet、 Chef、Ansible把安装脚本抽象的再好，说到底也是基于脚本的，然而应用所在的环境千差万别。文件路径的差别，文件权限的差别，依赖包的差别，应用环境的差别，Tomcat、 PHP、 Apache等软件版本的差别，JDK、Python等版本的差别，是否安装了一些系统软件，是否占用了哪些端口，都可能造成脚本执行的不成功。所以看起来是一旦脚本写好，就能够快速复制了，但是环境稍有改变，就需要把脚本进行新一轮的修改、测试、联调。例如在数据中心写好的脚本移到AWS上就不一定直接能用，在AWS上联调好了，迁移到Google Cloud上也可能会再出问题。\n\n### 容器的诞生\n\n#### 1. 容器的定义\n\n于是容器便应运而生。容器是Container，Container另一个意思是集装箱，其实容器的思想就是要变成软件交付的集装箱。集装箱的特点，一是打包，二是标准。设想没有集装箱的时代，如果将货物从A运到B，中间要经过三个码头，换三次船的话，货物每次都要卸下船来，摆的七零八落，然后换船的时候，需要重新摆放整齐，在没有集装箱的时候，船员们都需要在岸上待几天再走。而在有了集装箱后，所有的货物都打包在一起了，并且集装箱的尺寸全部一致，所以每次换船的时候，整体一个箱子搬过去就可以了，小时级别就能完成，船员再也不用长时间上岸等待了。\n\n#### 2.容器在开发中的应用\n\n设想A就是程序员，B就是用户，货物就是代码及运行环境，中间的三个码头分别是开发，测试，上线。\n假设代码的运行环境如下：\n\n1.  Ubuntu操作系统\n2.  创建用户hadoop\n3.  下载解压JDK 1.7在某个目录下\n4.  将这个目录加入JAVA_HOME和PATH的环境变量里面\n5.  将环境变量的export放在hadoop用户的home目录下的.bashrc文件中\n6.  下载并解压tomcat 7\n7.  将war放到tomcat的webapp路径下面\n8.  修改tomcat的启动参数，将Java的Heap Size设为1024M\n\n看，一个简单的Java网站，就需要考虑这么多零零散散的东西，如果不打包，就需要在开发，测试，生产的每个环境上查看，保证环境的一致，甚至要将这些环境重新搭建一遍，就像每次将货物打散了重装一样麻烦。中间稍有差池，比如开发环境用了JDK 1.8，而线上是JDK 1.7；比如开发环境用了root用户，线上需要使用hadoop用户，都可能导致程序的运行失败。\n\n\n\n\n\n### 容器的诞生\n\n[云计算的前世今生（上）](https://link.jianshu.com/?t=https://segmentfault.com/a/1190000008091499)中提到：云计算解决了基础资源层的弹性伸缩，却没有解决PaaS层应用随基础资源层弹性伸缩而带来的批量、快速部署问题。于是容器应运而生。\n\n容器是Container，Container另一个意思是集装箱，其实容器的思想就是要变成软件交付的集装箱。集装箱的特点，一是打包，二是标准。\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/3947555-e2fa7af659850ef5.PNG)\n\n\n\n\n\n在没有集装箱的时代，假设将货物从A运到B，中间要经过三个码头、换三次船。每次都要将货物卸下船来，摆的七零八落，然后搬上船重新整齐摆好。因此在没有集装箱的时候，每次换船，船员们都要在岸上待几天才能走。\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/3947555-74ef589e8d826755.png)\n\n\n\n\n\n有了集装箱以后，所有的货物都打包在一起了，并且集装箱的尺寸全部一致，所以每次换船的时候，一个箱子整体搬过去就行了，小时级别就能完成，船员再也不能上岸长时间耽搁了。这是集装箱“打包”、“标准”两大特点在生活中的应用。下面用一个简单的案例来看看容器在开发部署中的实际应用。\n\n假设有一个简单的Java网站需要上线，代码的运行环境如下：\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/3947555-f77373dcf00bafe5.png)\n\n\n\n\n\n看，一个简单的Java网站，就有这么多零零散散的东西！这就像很多零碎地货物，如果不打包，就需要在开发、测试、生产的每个环境上重新查看以保证环境的一致，有时甚至要将这些环境重新搭建一遍，就像每次将货物卸载、重装一样麻烦。中间稍有差池，比如开发环境用了JDK 1.8，而线上是JDK 1.7；比如开发环境用了root用户，线上需要使用hadoop用户，都可能导致程序的运行失败。\n\n那么容器如何对应用打包呢？还是要学习集装箱，首先要有个封闭的环境，将货物封装起来，让货物之间互不干扰，互相隔离，这样装货卸货才方便。好在ubuntu中的lxc技术早就能做到这一点。\n\n封闭的环境主要使用了两种技术，一种是看起来是隔离的技术，称为namespace，也即每个namespace中的应用看到的是不同的IP地址、用户空间、程号等。另一种是用起来是隔离的技术，称为cgroup，也即明明整台机器有很多的CPU、内存，而一个应用只能用其中的一部分。有了这两项技术，集装箱的铁盒子我们是焊好了，接下来是决定往里面放什么。\n\n最简单粗暴的方法，就是将上面列表中所有的都放到集装箱里面。但是这样太大了！因为即使你安装一个干干静静的ubuntu操作系统，什么都不装，就很大了。把操作系统装进容器相当于把船也放到了集装箱里面！传统的虚拟机镜像就是这样的，动辄几十G。答案当然是NO！所以第一项操作系统不能装进容器。\n\n撇下第一项操作系统，剩下的所有的加起来，也就几百M，就轻便多了。因此一台服务器上的容器是共享操作系统内核的，容器在不同机器之间的迁移不带内核，这也是很多人声称容器是轻量级的虚拟机的原因。轻不白轻，自然隔离性就差了，一个容器让操作系统崩溃了，其他容器也就跟着崩溃了，这相当于一个集装箱把船压漏水了，所有的集装箱一起沉。\n\n另一个需要撇下的就是随着应用的运行而产生并保存在本地的数据。这些数据多以文件的形式存在，例如数据库文件、文本文件。这些文件会随着应用的运行，越来越大，如果这些数据也放在容器里面，会让容器变得很大，影响容器在不同环境的迁移。而且这些数据在开发、测试、线上环境之间的迁移是没有意义的，生产环境不可能用测试环境的文件，所以往往这些数据也是保存在容器外面的存储设备上。也是为什么人们称容器是无状态的。\n\n至此集装箱焊好了，货物也装进去了，接下来就是如何将这个集装箱标准化，从而在哪艘船上都能运输。这里的标准一个是镜像，一个是容器的运行环境。\n\n所谓的镜像，就是将你焊好集装箱的那个时刻，将集装箱的状态保存下来，就像孙悟空说定，集装箱里面就定在了那一刻，然后将这一刻的状态保存成一系列文件。这些文件的格式是标准的，谁看到这些文件，都能还原当时定住的那个时刻。将镜像还原成运行时的过程（就是读取镜像文件，还原那个时刻的过程）就是容器的运行的过程。除了大名鼎鼎的Docker，还有其他的容器，例如AppC、Mesos Container，都能运行容器镜像。所以说容器不等于Docker。\n\n总而言之，容器是轻量级的、隔离差的、适用于无状态的，可以基于镜像标准实现跨主机、跨环境的随意迁移。\n\n有了容器，使得PaaS层对于用户自身应用的自动部署变得快速而优雅。容器快，快在了两方面，第一是虚拟机启动的时候要先启动操作系统，容器不用启动操作系统，因为是共享内核的。第二是虚拟机启动后使用脚本安装应用，容器不用安装应用，因为已经打包在镜像里面了。所以最终虚拟机的启动是分钟级别，而容器的启动是秒级。容器咋这么神奇。其实一点都不神奇，第一是偷懒少干活了，第二是提前把活干好了。\n\n因为容器的启动快，人们往往不会创建一个个小的虚拟机来部署应用，因为这样太费时间了，而是创建一个大的虚拟机，然后在大的虚拟机里面再划分容器，而不同的用户不共享大的虚拟机，可以实现操作系统内核的隔离。这又是一次合久必分的过程。由IaaS层的虚拟机池，划分为更细粒度的容器池。\n\n### 容器管理平台\n\n有了容器的管理平台，又是一次分久必合的过程。\n\n容器的粒度更加细，管理起来更难管，甚至是手动操作难以应对的。假设你有100台物理机，其实规模不是太大，用Excel人工管理是没问题的，但是一台上面开10台虚拟机，虚拟机的个数就是1000台，人工管理已经很困难了，但是一台虚拟机里面开10个容器，就是10000个容器，你是不是已经彻底放弃人工运维的想法了。\n\n所以容器层面的管理平台是一个新的挑战，关键字就是自动化：\n\n自发现：容器与容器之间的相互配置还能像虚拟机一样，记住IP地址，然后互相配置吗？这么多容器，你怎么记得住一旦一台虚拟机挂了重启，IP改变，应该改哪些配置，列表长度至少万行级别的啊。所以容器之间的配置通过名称来的，无论容器跑到哪台机器上，名称不变，就能访问到。\n\n自修复：容器挂了，或是进程宕机了，能像虚拟机那样，登陆上去查看一下进程状态，如果不正常重启一下么？你要登陆万台docker了。所以容器的进程挂了，容器就自动挂掉了，然后自动重启。\n\n弹性自伸缩 Auto Scaling：当容器的性能不足的时候，需要手动伸缩，手动部署么？当然也要自动来。\n\n当前火热的容器管理平台有三大流派：\n\n一个是Kubernetes，我们称为段誉型。段誉(Kubernetes)的父亲(Borg)武功高强，出身皇族(Google)，管理过偌大的一个大理国(Borg是Google数据中心的容器管理平台)。作为大理段式后裔，段誉的武功基因良好(Kubernetes的理念设计比较完善)，周围的高手云集，习武环境也好(Kubernetes生态活跃，热度高)，虽然刚刚出道的段誉武功不及其父亲，但是只要跟着周围的高手不断切磋，武功既可以飞速提升。\n\n一个是Mesos，我们称为乔峰型。乔峰(Mesos)的主要功夫降龙十八掌(Mesos的调度功能)独步武林，为其他帮派所无。而且乔峰也管理过人数众多的丐帮(Mesos管理过Tweeter的容器集群)。后来乔峰从丐帮出来，在江湖中特例独行(Mesos的创始人成立了公司Mesosphere)。乔峰的优势在于，乔峰的降龙十八掌(Mesos)就是在丐帮中使用的降龙十八掌，相比与段誉初学其父的武功来说，要成熟很多。但是缺点是，降龙十八掌只掌握在少数的几个丐帮帮主手中(Mesos社区还是以Mesosphere为主导)，其他丐帮兄弟只能远远崇拜乔峰，而无法相互切磋(社区热度不足)。\n\n一个是Swarm，我们称为慕容型。慕容家族(Swarm是Docker家族的集群管理软件)的个人功夫是非常棒的(Docker可以说称为容器的事实标准)，但是看到段誉和乔峰能够管理的组织规模越来越大，有一统江湖的趋势，着实眼红了，于是开始想创建自己的慕容鲜卑帝国(推出Swarm容器集群管理软件)。但是个人功夫好，并不代表着组织能力强(Swarm的集群管理能力)，好在慕容家族可以借鉴段誉和乔峰的组织管理经验，学习各家公司，以彼之道，还施彼身，使得慕容公子的组织能力(Swarm借鉴了很多前面的集群管理思想)也在逐渐的成熟中。\n\n三大容器门派，到底鹿死谁手，谁能一统江湖，尚未可知。\n\n网易之所以选型Kubernetes作为自己的容器管理平台，是因为基于 Borg 成熟的经验打造的 Kubernetes，为容器编排管理提供了完整的开源方案，并且社区活跃，生态完善，积累了大量分布式、服务化系统架构的最佳实践。\n\n### 容器初体验\n\n想不想尝试一下最先进的容器管理平台呢？我们先了解一下Docker的生命周期。如图所示。\n\n图中最中间就是最核心的两个部分，一个是镜像Images，一个是容器Containers。镜像运行起来就是容器。容器运行的过程中，基于原始镜像做了改变，比如安装了程序，添加了文件，也可以提交回去(commit)成为镜像。如果大家安装过系统，镜像有点像GHOST镜像，从GHOST镜像安装一个系统，运行起来，就相当于容器；容器里面自带应用，就像GHOST镜像安装的系统里面不是裸的操作系统，里面可能安装了微信，QQ，视频播放软件等。安装好的系统使用的过程中又安装了其他的软件，或者下载了文件，还可以将这个系统重新GHOST成一个镜像，当其他人通过这个镜像再安装系统的时候，则其他的软件也就自带了。\n\n普通的GHOST镜像就是一个文件，但是管理不方便，比如如果有十个GHOST镜像的话，你可能已经记不清楚哪个镜像里面安装了哪个版本的软件了。所以容器镜像有tag的概念，就是一个标签，比如dev-1.0，dev-1.1，production-1.1等，凡是能够帮助你区分不同镜像的，都可以。为了镜像的统一管理，有一个镜像库的东西，可以通过push将本地的镜像放到统一的镜像库中保存，可以通过pull将镜像库中的镜像拉到本地来。\n\n从镜像运行一个容器可使用下面的命令，如果初步使用Docker，记下下面这一个命令就可以了。\n\n这行命令会启动一个里面安装了mysql的容器。其中docker run就是运行一个容器；--name就是给这个容器起个名字；-v 就是挂数据盘，将外面的一个目录/my/own/datadir挂载到容器里面的一个目录/var/lib/mysql作为数据盘，外面的目录是在容器所运行的主机上的，也可以是远程的一个云盘；-e 是设置容器运行环境的环境变量，环境变量是最常使用的设置参数的方式，例如这里设置mysql的密码。mysql:tag就是镜像的名字和标签。\n\ndocker stop可以停止这个容器，start可以再启动这个容器，restart可以重启这个容器。在容器内部做了改变，例如安装了新的软件，产生了新的文件，则调用docker commit变成新的镜像。\n\n镜像生产过程，除了可以通过启动一个docker，手动修改，然后调用docker commit形成新镜像之外，还可以通过书写Dockerfile，通过docker build来编译这个Dockerfile来形成新镜像。为什么要这样做呢？前面的方式太不自动化了，需要手工干预，而且还经常会忘了手工都做了什么。用Dockerfile可以很好的解决这个问题。\n\nDockerfile的一个简单的例子如下：\n\n这其实是一个镜像的生产说明书，Docker build的过程就是根据这个生产说明书来生产镜像：\n\nFROM基础镜像，先下载这个基础镜像，然后从这个镜像启动一个容器，并且登陆到容器里面；\n\nRUN运行一个命令，在容器里面运行这个命令；\n\nCOPY/ADD将一些文件添加到容器里面；\n\n最终给容器设置启动命令 ENTRYPOINT，这个命令不在镜像生成过程中执行，而是在容器运行的时候作为主程序执行；\n\n将所有的修改commit成镜像。\n\n这里需要说明一下的就是主程序，是Docker里面一个重要的概念，虽然镜像里面可以安装很多的程序，但是必须有一个主程序，主程序和容器的生命周期完全一致，主程序在则容器在，主程序亡则容器亡。\n\n就像图中展示的一样，容器是一个资源限制的框，但是这个框没有底，全靠主进程撑着，主进程挂了，衣服架子倒了，衣服也就垮了。\n\n了解了如何运行一个独立的容器，接下来介绍如何使用容器管理平台。\n\n### 容器管理平台初体验\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/3947555-7d07dda243e0a863.png)\n\n\n\n\n\n容器管理平台会对容器做更高的抽象，容器不再是单打独斗，而且组成集团军共同战斗。多个容器组成一个Pod，这几个容器亲如兄弟，干的也是相关性很强的活，能够通过localhost访问彼此，真是兄弟齐心，力可断金。有的任务一帮兄弟还刚不住，就需要多个Pod合力完成，这个由ReplicationController进行控制，可以将一个Pod复制N个副本，同时承载任务，众人拾柴火焰高。\n\nN个Pod如果对外散兵作战，一是无法合力，二是给人很乱的感觉，因而需要有一个老大，作为代言人，将大家团结起来，一致对外，这就是Service。老大对外提供统一的虚拟IP和端口，并将这个IP和服务名关联起来，访问服务名，则自动映射为虚拟IP。老大的意思就是，如果外面要访问我这个团队，喊一声名字就可以，例如”雷锋班，帮敬老院打扫卫生！”，你不用管雷锋班的那个人去打扫卫生，每个人打扫哪一部分，班长会统一分配。\n\n最上层通过namespace分隔完全隔离的环境，例如生产环境，测试环境，开发环境等。就像军队分华北野战军，东北野战军一样。野战军立正，出发，部署一个Tomcat的Java应用。\n\n\n\n作者：网易云基础服务\n链接：https://www.jianshu.com/p/52312b1eb633\n來源：简书\n简书著作权归作者所有，任何形式的转载都请联系作者获得授权并注明出处。\n"
  },
  {
    "path": "docs/backend/后端技术杂谈：先搞懂Docker核心概念吧.md",
    "content": "# 目录\n\n  * [1、了解Docker的前生LXC](#1、了解docker的前生lxc)\n  * [2、LXC与docker什么关系？](#2、lxc与docker什么关系？)\n  * [3、什么是docker](#3、什么是docker)\n  * [4、docker官方文档](#4、docker官方文档)\n  * [5、为什么docker越来越受欢迎](#5、为什么docker越来越受欢迎)\n  * [6、docker版本](#6、docker版本)\n  * [7、docker和openstack的几项对比](#7、docker和openstack的几项对比)\n  * [8、容器在内核中支持2种重要技术](#8、容器在内核中支持2种重要技术)\n  * [9、了解docker三个重要概念](#9、了解docker三个重要概念)\n  * [10、docker的主要用途](#10、docker的主要用途)\n  * [11、docker改变了什么](#11、docker改变了什么)\n  * [1、总体架构](#1、总体架构)\n  * [2、docker架构2](#2、docker架构2)\n  * [3、docker架构3](#3、docker架构3)\n  * [1、docker client](#1、docker-client)\n  * [2、docker daemon](#2、docker-daemon)\n  * [3、docker server](#3、docker-server)\n  * [4、engine](#4、engine)\n  * [5、job](#5、job)\n  * [6、docker registry](#6、docker-registry)\n  * [7、Graph](#7、graph)\n  * [8、driver](#8、driver)\n  * [9、libcontainer](#9、libcontainer)\n  * [10、docker container](#10、docker-container)\n\n\n[toc]\n**一、简介**\n\n\n## 1、了解Docker的前生LXC\n\n\n\n\n\nLXC为Linux Container的简写。可以提供轻量级的虚拟化，以便隔离进程和资源，而且不需要提供指令解释机制以及全虚拟化的其他复杂性。相当于C++中的NameSpace。容器有效地将由单个操作系统管理的资源划分到孤立的组中，以更好地在孤立的组之间平衡有冲突的资源使用需求。\n\n\n\n\n\n与传统虚拟化技术相比，它的优势在于：  （1）与宿主机使用同一个内核，性能损耗小；  （2）不需要指令级模拟；  （3）不需要即时(Just-in-time)编译；  （4）容器可以在CPU核心的本地运行指令，不需要任何专门的解释机制；（5）避免了准虚拟化和系统调用替换中的复杂性；  （6）轻量级隔离，在隔离的同时还提供共享机制，以实现容器与宿主机的资源共享。\n\n\n\n\n\n总结：Linux Container是一种轻量级的虚拟化的手段。  Linux Container提供了在单一可控主机节点上支持多个相互隔离的server container同时执行的机制。Linux Container有点像chroot，提供了一个拥有自己进程和网络空间的虚拟环境，但又有别于虚拟机，因为lxc是一种操作系统层次上的资源的虚拟化。\n\n\n\n\n\n## 2、LXC与docker什么关系？\n\n\n\n\n\ndocker并不是LXC替代品，docker底层使用了LXC来实现，LXC将linux进程沙盒化，使得进程之间相互隔离，并且能对各进程资源合理分配。  在LXC的基础之上，docker提供了一系列更强大的功能。\n\n\n\n\n\n## 3、什么是docker\n\n\n\n\n\ndocker是一个开源的应用容器引擎，基于go语言开发并遵循了apache2.0协议开源。  docker可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中，然后发布到任何流行的linux服务器，也可以实现虚拟化。  容器是完全使用沙箱机制，相互之间不会有任何接口（类iphone的app），并且容器开销极其低。\n\n\n\n\n\n## 4、docker官方文档\n\n\n\n\n\n## 5、为什么docker越来越受欢迎\n\n\n\n\n\n官方话语：\n\n\n\n\n\n*   容器化越来越受欢迎，因为容器是：\n\n*   灵活：即使是最复杂的应用也可以集装箱化。\n\n*   轻量级：容器利用并共享主机内核。\n\n*   可互换：您可以即时部署更新和升级。\n\n*   便携式：您可以在本地构建，部署到云，并在任何地方运行。\n\n*   可扩展：您可以增加并自动分发容器副本。\n\n*   可堆叠：您可以垂直和即时堆叠服务。\n\n*   镜像和容器（contalners）\n\n\n\n\n\n通过镜像启动一个容器，一个镜像是一个可执行的包，其中包括运行应用程序所需要的所有内容包含代码，运行时间，库、环境变量、和配置文件。  容器是镜像的运行实例，当被运行时有镜像状态和用户进程，可以使用docker ps 查看。\n\n\n\n\n\n*   容器和虚拟机\n\n\n\n\n\n容器是在linux上本机运行，并与其他容器共享主机的内核，它运行的一个独立的进程，不占用其他任何可执行文件的内存，非常轻量。  虚拟机运行的是一个完成的操作系统，通过虚拟机管理程序对主机资源进行虚拟访问，相比之下需要的资源更多。\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230408120435.png)\n\n\n\n\n\n## 6、docker版本\n\n\n\n\n\nDocker Community Edition（CE）社区版  Enterprise Edition(EE) 商业版\n\n\n\n\n\n## 7、docker和openstack的几项对比\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230408120450.png)\n\n\n\n\n\n\n\n## 8、容器在内核中支持2种重要技术\n\n\n\n\n\ndocker本质就是宿主机的一个进程，docker是通过namespace实现资源隔离，通过cgroup实现资源限制，通过写时复制技术（copy-on-write）实现了高效的文件操作（类似虚拟机的磁盘比如分配500g并不是实际占用物理磁盘500g）\n\n\n\n\n\n1）namespaces 名称空间\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230408120519.png)\n\n\n\n\n\n\n\n\n\n\n\n2）control Group 控制组cgroup的特点是：\n\n\n\n\n\n*   cgroup的api以一个伪文件系统的实现方式，用户的程序可以通过文件系统实现cgroup的组件管理\n\n*   cgroup的组件管理操作单元可以细粒度到线程级别，另外用户可以创建和销毁cgroup，从而实现资源的分配和再利用\n\n*   所有资源管理的功能都以子系统的方式实现，接口统一子任务创建之初与其父任务处于同一个cgroup的控制组\n\n\n\n\n\n四大功能：\n\n\n\n\n\n*   资源限制：可以对任务使用的资源总额进行限制\n\n*   优先级分配：通过分配的cpu时间片数量以及磁盘IO带宽大小，实际上相当于控制了任务运行优先级\n\n*   资源统计：可以统计系统的资源使用量，如cpu时长，内存用量等\n\n*   任务控制：cgroup可以对任务执行挂起、恢复等操作\n\n\n\n\n\n## 9、了解docker三个重要概念\n\n\n\n\n\n1）image镜像  docker镜像就是一个只读模板，比如，一个镜像可以包含一个完整的centos，里面仅安装apache或用户的其他应用，镜像可以用来创建docker容器，另外docker提供了一个很简单的机制来创建镜像或者更新现有的镜像，用户甚至可以直接从其他人那里下一个已经做好的镜像来直接使用\n\n\n\n\n\n2）container容器  docker利用容器来运行应用，容器是从镜像创建的运行实例，它可以被启动，开始、停止、删除、每个容器都是互相隔离的，保证安全的平台，可以把容器看做是要给简易版的linux环境（包括root用户权限、镜像空间、用户空间和网络空间等）和运行在其中的应用程序\n\n\n\n\n\n3）repostory仓库  仓库是集中存储镜像文件的沧桑，registry是仓库主从服务器，实际上参考注册服务器上存放着多个仓库，每个仓库中又包含了多个镜像，每个镜像有不同的标签（tag）  仓库分为两种，公有参考，和私有仓库，最大的公开仓库是docker Hub，存放了数量庞大的镜像供用户下载，国内的docker pool，这里仓库的概念与Git类似，registry可以理解为github这样的托管服务。\n\n\n\n\n\n## 10、docker的主要用途\n\n\n\n\n\n官方就是Bulid 、ship、run any app/any where，编译、装载、运行、任何app/在任意地方都能运行。就是实现了应用的封装、部署、运行的生命周期管理只要在glibc的环境下，都可以运行。运维生成环境中：docker化。\n\n\n\n\n\n*   发布服务不用担心服务器的运行环境，所有的服务器都是自动分配docker，自动部署，自动安装，自动运行\n\n*   再不用担心其他服务引擎的磁盘问题，cpu问题，系统问题了\n\n*   资源利用更出色\n\n*   自动迁移，可以制作镜像，迁移使用自定义的镜像即可迁移，不会出现什么问题\n\n*   管理更加方便了\n\n\n\n\n\n## 11、docker改变了什么\n\n\n\n\n\n*   面向产品：产品交付\n\n*   面向开发：简化环境配置\n\n*   面向测试：多版本测试\n\n*   面向运维：环境一致性\n\n*   面向架构：自动化扩容（微服务）\n\n\n\n\n\n**二、docker架构**\n\n\n\n\n\n## 1、总体架构\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230408120534.png)\n\n\n\n\n\n*   distribution 负责与docker registry交互，上传洗澡镜像以及v2 registry 有关的源数据\n\n*   registry负责docker registry有关的身份认证、镜像查找、镜像验证以及管理registry mirror等交互操作\n\n*   image 负责与镜像源数据有关的存储、查找，镜像层的索引、查找以及镜像tar包有关的导入、导出操作\n\n*   reference负责存储本地所有镜像的repository和tag名，并维护与镜像id之间的映射关系\n\n*   layer模块负责与镜像层和容器层源数据有关的增删改查，并负责将镜像层的增删改查映射到实际存储镜像层文件的graphdriver模块\n\n*   graghdriver是所有与容器镜像相关操作的执行者\n\n\n\n\n\n## 2、docker架构2\n\n\n\n\n\n如果觉得上面架构图比较乱可以看这个架构：\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230408120545.png)\n\n\n\n\n\n从上图不难看出，用户是使用Docker Client与Docker Daemon建立通信，并发送请求给后者。\n\n\n\n\n\n而Docker Daemon作为Docker架构中的主体部分，首先提供Server的功能使其可以接受Docker Client的请求；而后Engine执行Docker内部的一系列工作，每一项工作都是以一个Job的形式的存在。\n\n\n\n\n\nJob的运行过程中，当需要容器镜像时，则从Docker Registry中下载镜像，并通过镜像管理驱动graphdriver将下载镜像以Graph的形式存储；当需要为Docker创建网络环境时，通过网络管理驱动networkdriver创建并配置Docker容器网络环境；当需要限制Docker容器运行资源或执行用户指令等操作时，则通过execdriver来完成。\n\n\n\n\n\n而libcontainer是一项独立的容器管理包，networkdriver以及execdriver都是通过libcontainer来实现具体对容器进行的操作。当执行完运行容器的命令后，一个实际的Docker容器就处于运行状态，该容器拥有独立的文件系统，独立并且安全的运行环境等。\n\n\n\n\n\n## 3、docker架构3\n\n\n\n\n\n再来看看另外一个架构，这个个架构就简单清晰指明了server/client交互，容器和镜像、数据之间的一些联系。\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230408120558.png)\n\n\n\n\n\n这个架构图更加清晰了架构\n\n\n\n\n\ndocker daemon就是docker的守护进程即server端，可以是远程的，也可以是本地的，这个不是C/S架构吗，客户端Docker client 是通过rest api进行通信。\n\n\n\n\n\ndocker cli 用来管理容器和镜像，客户端提供一个只读镜像，然后通过镜像可以创建多个容器，这些容器可以只是一个RFS（Root file system根文件系统），也可以ishi一个包含了用户应用的RFS，容器在docker client中只是要给进程，两个进程之间互不可见。\n\n\n\n\n\n用户不能与server直接交互，但可以通过与容器这个桥梁来交互，由于是操作系统级别的虚拟技术，中间的损耗几乎可以不计。\n\n\n\n\n\n**三、docker架构2各个模块的功能（待完善）**\n\n\n\n\n\n主要的模块有：Docker Client、Docker Daemon、Docker Registry、Graph、Driver、libcontainer以及Docker container。\n\n\n\n\n\n## 1、docker client\n\n\n\n\n\ndocker client 是docker架构中用户用来和docker daemon建立通信的客户端，用户使用的可执行文件为docker，通过docker命令行工具可以发起众多管理container的请求。\n\n\n\n\n\ndocker client可以通过一下三宗方式和docker daemon建立通信：docker client可以通过设置命令行flag参数的形式设置安全传输层协议(TLS)的有关参数，保证传输的安全性。\n\n\n\n\n\ndocker client发送容器管理请求后，由docker daemon接受并处理请求，当docker client 接收到返回的请求相应并简单处理后，docker client 一次完整的生命周期就结束了，当需要继续发送容器管理请求时，用户必须再次通过docker可以执行文件创建docker client。\n\n\n\n\n\n## 2、docker daemon\n\n\n\n\n\ndocker daemon 是docker架构中一个常驻在后台的系统进程，功能是：接收处理docker client发送的请求。该守护进程在后台启动一个server，server负载接受docker client发送的请求；接受请求后，server通过路由与分发调度，找到相应的handler来执行请求。\n\n\n\n\n\ndocker daemon启动所使用的可执行文件也为docker，与docker client启动所使用的可执行文件docker相同，在docker命令执行时，通过传入的参数来判别docker daemon与docker client。\n\n\n\n\n\ndocker daemon的架构可以分为：docker server、engine、job。daemon\n\n\n\n\n\n## 3、docker server\n\n\n\n\n\ndocker server在docker架构中时专门服务于docker client的server，该server的功能是：接受并调度分发docker client发送的请求，架构图如下：\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230408120607.png)\n\n\n\n\n\n在Docker的启动过程中，通过包gorilla/mux（golang的类库解析），创建了一个mux.Router，提供请求的路由功能。在Golang中，gorilla/mux是一个强大的URL路由器以及调度分发器。该mux.Router中添加了众多的路由项，每一个路由项由HTTP请求方法（PUT、POST、GET或DELETE）、URL、Handler三部分组成。\n\n\n\n\n\n若Docker Client通过HTTP的形式访问Docker Daemon，创建完mux.Router之后，Docker将Server的监听地址以及mux.Router作为参数，创建一个httpSrv=http.Server{}，最终执行httpSrv.Serve()为请求服务。\n\n\n\n\n\n在Server的服务过程中，Server在listener上接受Docker Client的访问请求，并创建一个全新的goroutine来服务该请求。在goroutine中，首先读取请求内容，然后做解析工作，接着找到相应的路由项，随后调用相应的Handler来处理该请求，最后Handler处理完请求之后回复该请求。\n\n\n\n\n\n需要注意的是：Docker Server的运行在Docker的启动过程中，是靠一个名为”serveapi”的job的运行来完成的。原则上，Docker Server的运行是众多job中的一个，但是为了强调Docker Server的重要性以及为后续job服务的重要特性，将该”serveapi”的job单独抽离出来分析，理解为Docker Server。\n\n\n\n\n\n## 4、engine\n\n\n\n\n\nEngine是Docker架构中的运行引擎，同时也Docker运行的核心模块。它扮演Docker container存储仓库的角色，并且通过执行job的方式来操纵管理这些容器。\n\n\n\n\n\n在Engine数据结构的设计与实现过程中，有一个handler对象。该handler对象存储的都是关于众多特定job的handler处理访问。举例说明，Engine的handler对象中有一项为：{“create”: daemon.ContainerCreate,}，则说明当名为”create”的job在运行时，执行的是daemon.ContainerCreate的handler。\n\n\n\n\n\n## 5、job\n\n\n\n\n\n一个Job可以认为是Docker架构中Engine内部最基本的工作执行单元。Docker可以做的每一项工作，都可以抽象为一个job。例如：在容器内部运行一个进程，这是一个job；创建一个新的容器，这是一个job，从Internet上下载一个文档，这是一个job；包括之前在Docker Server部分说过的，创建Server服务于HTTP的API，这也是一个job，等等。\n\n\n\n\n\nJob的设计者，把Job设计得与Unix进程相仿。比如说：Job有一个名称，有参数，有环境变量，有标准的输入输出，有错误处理，有返回状态等。\n\n\n\n\n\n## 6、docker registry\n\n\n\n\n\nDocker Registry是一个存储容器镜像的仓库。而容器镜像是在容器被创建时，被加载用来初始化容器的文件架构与目录。\n\n\n\n\n\n在Docker的运行过程中，Docker Daemon会与Docker Registry通信，并实现搜索镜像、下载镜像、上传镜像三个功能，这三个功能对应的job名称分别为”search”，”pull” 与 “push”。\n\n\n\n\n\n其中，在Docker架构中，Docker可以使用公有的Docker Registry，即大家熟知的Docker Hub，如此一来，Docker获取容器镜像文件时，必须通过互联网访问Docker Hub；同时Docker也允许用户构建本地私有的Docker Registry，这样可以保证容器镜像的获取在内网完成。\n\n\n\n\n\n## 7、Graph\n\n\n\n\n\nGraph在Docker架构中扮演已下载容器镜像的保管者，以及已下载容器镜像之间关系的记录者。一方面，Graph存储着本地具有版本信息的文件系统镜像，另一方面也通过GraphDB记录着所有文件系统镜像彼此之间的关系。Graph的架构如下：\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230408120623.png)\n\n\n\n\n\n其中，GraphDB是一个构建在SQLite之上的小型图数据库，实现了节点的命名以及节点之间关联关系的记录。它仅仅实现了大多数图数据库所拥有的一个小的子集，但是提供了简单的接口表示节点之间的关系。\n\n\n\n\n\n同时在Graph的本地目录中，关于每一个的容器镜像，具体存储的信息有：该容器镜像的元数据，容器镜像的大小信息，以及该容器镜像所代表的具体rootfs。\n\n\n\n\n\n## 8、driver\n\n\n\n\n\nDriver是Docker架构中的驱动模块。通过Driver驱动，Docker可以实现对Docker容器执行环境的定制。由于Docker运行的生命周期中，并非用户所有的操作都是针对Docker容器的管理，另外还有关于Docker运行信息的获取，Graph的存储与记录等。因此，为了将Docker容器的管理从Docker Daemon内部业务逻辑中区分开来，设计了Driver层驱动来接管所有这部分请求。\n\n\n\n\n\n在Docker Driver的实现中，可以分为以下三类驱动：graphdriver、networkdriver和execdriver。\n\n\n\n\n\ngraphdriver主要用于完成容器镜像的管理，包括存储与获取。即当用户需要下载指定的容器镜像时，graphdriver将容器镜像存储在本地的指定目录；同时当用户需要使用指定的容器镜像来创建容器的rootfs时，graphdriver从本地镜像存储目录中获取指定的容器镜像。\n\n\n\n\n\n在graphdriver的初始化过程之前，有4种文件系统或类文件系统在其内部注册，它们分别是aufs、btrfs、vfs和devmapper。而Docker在初始化之时，通过获取系统环境变量”DOCKER_DRIVER”来提取所使用driver的指定类型。而之后所有的graph操作，都使用该driver来执行。\n\n\n\n\n\ngraphdriver的架构如下：\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230408120634.png)\n\n\n\n\n\nnetworkdriver的用途是完成Docker容器网络环境的配置，其中包括Docker启动时为Docker环境创建网桥；Docker容器创建时为其创建专属虚拟网卡设备；以及为Docker容器分配IP、端口并与宿主机做端口映射，设置容器防火墙策略等。networkdriver的架构如下：\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230408120642.png)\n\n\n\n\n\nexecdriver作为Docker容器的执行驱动，负责创建容器运行命名空间，负责容器资源使用的统计与限制，负责容器内部进程的真正运行等。在execdriver的实现过程中，原先可以使用LXC驱动调用LXC的接口，来操纵容器的配置以及生命周期，而现在execdriver默认使用native驱动，不依赖于LXC。具体体现在Daemon启动过程中加载的ExecDriverflag参数，该参数在配置文件已经被设为”native”。这可以认为是Docker在1.2版本上一个很大的改变，或者说Docker实现跨平台的一个先兆。execdriver架构如下：\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230408120652.png)\n\n\n\n\n\n## 9、libcontainer\n\n\n\n\n\nlibcontainer是Docker架构中一个使用Go语言设计实现的库，设计初衷是希望该库可以不依靠任何依赖，直接访问内核中与容器相关的API。\n\n\n\n\n\n正是由于libcontainer的存在，Docker可以直接调用libcontainer，而最终操纵容器的namespace、cgroups、apparmor、网络设备以及防火墙规则等。这一系列操作的完成都不需要依赖LXC或者其他包。libcontainer架构如下：\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230408120659.png)\n\n\n\n\n\n另外，libcontainer提供了一整套标准的接口来满足上层对容器管理的需求。或者说，libcontainer屏蔽了Docker上层对容器的直接管理。又由于libcontainer使用Go这种跨平台的语言开发实现，且本身又可以被上层多种不同的编程语言访问，因此很难说，未来的Docker就一定会紧紧地和Linux捆绑在一起。而于此同时，Microsoft在其著名云计算平台Azure中，也添加了对Docker的支持，可见Docker的开放程度与业界的火热度。\n\n\n\n\n\n暂不谈Docker，由于libcontainer的功能以及其本身与系统的松耦合特性，很有可能会在其他以容器为原型的平台出现，同时也很有可能催生出云计算领域全新的项目。\n\n\n\n\n\n## 10、docker container\n\n\n\n\n\nDocker container（Docker容器）是Docker架构中服务交付的最终体现形式。Docker按照用户的需求与指令，订制相应的Docker容器：\n\n\n\n\n\n*   用户通过指定容器镜像，使得Docker容器可以自定义rootfs等文件系统；\n\n*   用户通过指定计算资源的配额，使得Docker容器使用指定的计算资源；\n\n*   用户通过配置网络及其安全策略，使得Docker容器拥有独立且安全的网络环境；\n\n*   用户通过指定运行的命令，使得Docker容器执行指定的工作。\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230408120710.png)\n"
  },
  {
    "path": "docs/backend/后端技术杂谈：十分钟理解Kubernetes核心概念.md",
    "content": "# 目录\n\n* [十分钟带你理解Kubernetes核心概念](#十分钟带你理解kubernetes核心概念)\n  * [什么是Kubernetes？](#什么是kubernetes？)\n  * [集群](#集群)\n  * [Pod](#pod)\n  * [Label](#label)\n  * [Replication Controller](#replication-controller)\n  * [Service](#service)\n  * [Node](#node)\n  * [Kubernetes Master](#kubernetes-master)\n  * [下一步](#下一步)\n\n\n[toc]\n本文转自互联网，侵删\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本系列文章将整理到我的个人博客\n> www.how2playlife.com\n\n更多Java技术文章会更新在我的微信公众号【Java技术江湖】上，欢迎关注  \n该系列博文会介绍常见的后端技术，这对后端工程师来说是一种综合能力，我们会逐步了解搜索技术，云计算相关技术、大数据研发等常见的技术喜提，以便让你更完整地了解后端技术栈的全貌，为后续参与分布式应用的开发和学习做好准备。\n\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系我，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->  \n\n# 十分钟带你理解Kubernetes核心概念\n\n本文将会简单介绍[Kubernetes](http://kubernetes.io/v1.1/docs/whatisk8s.html)的核心概念。因为这些定义可以在Kubernetes的文档中找到，所以文章也会避免用大段的枯燥的文字介绍。相反，我们会使用一些图表（其中一些是动画）和示例来解释这些概念。我们发现一些概念（比如Service）如果没有图表的辅助就很难全面地理解。在合适的地方我们也会提供Kubernetes文档的链接以便读者深入学习。\n\n> 容器特性、镜像、网络；Kubernetes架构、核心组件、基本功能；Kubernetes设计理念、架构设计、基本功能、常用对象、设计原则；Kubernetes的数据库、运行时、网络、插件已经落地经验；微服务架构、组件、监控方案等。\n\n这就开始吧。\n\n## 什么是Kubernetes？\n\nKubernetes（k8s）是自动化容器操作的开源平台，这些操作包括部署，调度和节点集群间扩展。如果你曾经用过Docker容器技术部署容器，那么可以将Docker看成Kubernetes内部使用的低级别组件。Kubernetes不仅仅支持Docker，还支持Rocket，这是另一种容器技术。  \n使用Kubernetes可以：\n\n*   自动化容器的部署和复制\n*   随时扩展或收缩容器规模\n*   将容器组织成组，并且提供容器间的负载均衡\n*   很容易地升级应用程序容器的新版本\n*   提供容器弹性，如果容器失效就替换它，等等...\n\n实际上，使用Kubernetes只需一个[部署文件](https://github.com/kubernetes/kubernetes/blob/master/examples/guestbook/all-in-one/guestbook-all-in-one.yaml)，使用一条命令就可以部署多层容器（前端，后台等）的完整集群：\n\n```  \n$ kubectl create -f single-config-file.yaml  \n  \n```  \n\nkubectl是和Kubernetes API交互的命令行程序。现在介绍一些核心概念。\n\n## 集群\n\n集群是一组节点，这些节点可以是物理服务器或者虚拟机，之上安装了Kubernetes平台。下图展示这样的集群。注意该图为了强调核心概念有所简化。[这里](http://kubernetes.io/v1.1/docs/design/architecture.html)可以看到一个典型的Kubernetes架构图。\n\n[](http://dockone.io/uploads/article/20151230/d56441427680948fb56a00af57bda690.png)\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1233356-838c13e9241040b4.png)\n\n\n\n[1.png](http://dockone.io/uploads/article/20151230/d56441427680948fb56a00af57bda690.png)\n\n上图可以看到如下组件，使用特别的图标表示Service和Label：\n\n*   Pod\n*   Container（容器）\n*   Label（标签）\n*   Replication Controller（复制控制器）\n*   Service（服务）\n*   Node（节点）\n*   Kubernetes Master（Kubernetes主节点）\n\n## Pod\n\n[Pod](http://kubernetes.io/v1.1/docs/user-guide/pods.html)（上图绿色方框）安排在节点上，包含一组容器和卷。同一个Pod里的容器共享同一个网络命名空间，可以使用localhost互相通信。Pod是短暂的，不是持续性实体。你可能会有这些问题：\n\n*   如果Pod是短暂的，那么我怎么才能持久化容器数据使其能够跨重启而存在呢？ 是的，Kubernetes支持[卷](http://kubernetes.io/v1.1/docs/user-guide/volumes.html)的概念，因此可以使用持久化的卷类型。\n*   是否手动创建Pod，如果想要创建同一个容器的多份拷贝，需要一个个分别创建出来么？可以手动创建单个Pod，但是也可以使用Replication Controller使用Pod模板创建出多份拷贝，下文会详细介绍。\n*   如果Pod是短暂的，那么重启时IP地址可能会改变，那么怎么才能从前端容器正确可靠地指向后台容器呢？这时可以使用Service，下文会详细介绍。\n\n## Label\n\n正如图所示，一些Pod有Label。一个Label是attach到Pod的一对键/值对，用来传递用户定义的属性。比如，你可能创建了一个\"tier\"和“app”标签，通过Label（**tier=frontend, app=myapp**）来标记前端Pod容器，使用Label（**tier=backend, app=myapp**）标记后台Pod。然后可以使用[Selectors](http://kubernetes.io/v1.1/docs/user-guide/labels.html#label-selectors)选择带有特定Label的Pod，并且将Service或者Replication Controller应用到上面。\n\n## Replication Controller\n\n_是否手动创建Pod，如果想要创建同一个容器的多份拷贝，需要一个个分别创建出来么，能否将Pods划到逻辑组里？_\n\nReplication Controller确保任意时间都有指定数量的Pod“副本”在运行。如果为某个Pod创建了Replication Controller并且指定3个副本，它会创建3个Pod，并且持续监控它们。如果某个Pod不响应，那么Replication Controller会替换它，保持总数为3.如下面的动画所示：\n\n[](http://dockone.io/uploads/article/20151230/5e2bad1a25e33e2d155da81da1d3a54b.gif)\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1233356-5f5b425bb2705525.gif)\n\n\n\n\n如果之前不响应的Pod恢复了，现在就有4个Pod了，那么Replication Controller会将其中一个终止保持总数为3。如果在运行中将副本总数改为5，Replication Controller会立刻启动2个新Pod，保证总数为5。还可以按照这样的方式缩小Pod，这个特性在执行滚动[升级](https://cloud.google.com/container-engine/docs/replicationcontrollers/#rolling_updates)时很有用。\n\n当创建Replication Controller时，需要指定两个东西：\n\n1.  [Pod模板](http://kubernetes.io/v1.1/docs/user-guide/replication-controller.html#pod-template)：用来创建Pod副本的模板\n2.  [Label](http://kubernetes.io/v1.1/docs/user-guide/replication-controller.html#labels)：Replication Controller需要监控的Pod的标签。\n\n现在已经创建了Pod的一些副本，那么在这些副本上如何均衡负载呢？我们需要的是Service。\n\n## Service\n\n_如果Pods是短暂的，那么重启时IP地址可能会改变，怎么才能从前端容器正确可靠地指向后台容器呢？_\n\n[Service](http://kubernetes.io/v1.1/docs/user-guide/services.html)是定义一系列Pod以及访问这些Pod的策略的一层**抽象**。Service通过Label找到Pod组。因为Service是抽象的，所以在图表里通常看不到它们的存在，这也就让这一概念更难以理解。\n\n现在，假定有2个后台Pod，并且定义后台Service的名称为‘backend-service’，lable选择器为（**tier=backend, app=myapp**）。_backend-service_##的Service会完成如下两件重要的事情：\n\n*   会为Service创建一个本地集群的DNS入口，因此前端Pod只需要DNS查找主机名为 ‘backend-service’，就能够解析出前端应用程序可用的IP地址。\n*   现在前端已经得到了后台服务的IP地址，但是它应该访问2个后台Pod的哪一个呢？Service在这2个后台Pod之间提供透明的负载均衡，会将请求分发给其中的任意一个（如下面的动画所示）。通过每个Node上运行的代理（kube-proxy）完成。[这里](http://kubernetes.io/v1.1/docs/user-guide/services.html#virtual-ips-and-service-proxies)有更多技术细节。\n\n下述动画展示了Service的功能。注意该图作了很多简化。如果不进入网络配置，那么达到透明的负载均衡目标所涉及的底层网络和路由相对先进。如果有兴趣，[这里](http://www.dasblinkenlichten.com/kubernetes-101-networking/)有更深入的介绍。\n\n[](http://dockone.io/uploads/article/20151230/125bbccce0b3bbf42abab0e520d9250b.gif)\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1233356-d12e172a195f74cd.gif)\n\n\n\n[3.gif](http://dockone.io/uploads/article/20151230/125bbccce0b3bbf42abab0e520d9250b.gif)\n\n有一个特别类型的Kubernetes Service，称为'[LoadBalancer](http://kubernetes.io/v1.1/docs/user-guide/services.html#type-loadbalancer)'，作为外部负载均衡器使用，在一定数量的Pod之间均衡流量。比如，对于负载均衡Web流量很有用。\n\n## Node\n\n节点（上图橘色方框）是物理或者虚拟机器，作为Kubernetes worker，通常称为Minion。每个节点都运行如下Kubernetes关键组件：\n\n*   Kubelet：是主节点代理。\n*   Kube-proxy：Service使用其将链接路由到Pod，如上文所述。\n*   Docker或Rocket：Kubernetes使用的容器技术来创建容器。\n\n## Kubernetes Master\n\n集群拥有一个Kubernetes Master（紫色方框）。Kubernetes Master提供集群的独特视角，并且拥有一系列组件，比如Kubernetes API Server。API Server提供可以用来和集群交互的REST端点。master节点包括用来创建和复制Pod的Replication Controller。\n\n## 下一步\n\n现在我们已经了解了Kubernetes核心概念的基本知识，你可以进一步阅读Kubernetes##[用户手册](http://kubernetes.io/v1.1/docs/user-guide/README.html)。用户手册提供了快速并且完备的学习文档。  \n如果迫不及待想要试试Kubernetes，可以使用[Google Container Engine](https://cloud.google.com/container-engine/docs/)。Google Container Engine是托管的Kubernetes容器环境。简单注册/登录之后就可以在上面尝试示例了。\n\n**原文链接：[Learn the Kubernetes Key Concepts in 10 Minutes](http://omerio.com/2015/12/18/learn-the-kubernetes-key-concepts-in-10-minutes/)（翻译：崔婧雯）**  \n＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝  \n译者介绍  \n崔婧雯，现就职于IBM，高级软件工程师，负责IBM WebSphere业务流程管理软件的系统测试工作。曾就职于VMware从事桌面虚拟化产品的质量保证工作。对虚拟化，中间件技术，业务流程管理有浓厚的兴趣。\n"
  },
  {
    "path": "docs/backend/后端技术杂谈：捋一捋大数据研发的基本概念.md",
    "content": "# 目录\n\n  * [0x00 前言](#0x00-前言)\n  * [0x01 数据？数据！](#0x01-数据？数据！)\n  * [0x02 概览](#0x02-概览)\n  * [0x03 关于内容](#0x03-关于内容)\n  * [0xFF 总结](#0xff-总结)\n\n\n[toc]\n\n本文作者：[**木东居士**]\n\n转自：[http://www.mdjs.info](https://link.jianshu.com/?t=http%3A%2F%2Fwww.mdjs.info)\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本系列文章将整理到我的个人博客\n> www.how2playlife.com\n\n更多Java技术文章会更新在我的微信公众号【Java技术江湖】上，欢迎关注\n该系列博文会介绍常见的后端技术，这对后端工程师来说是一种综合能力，我们会逐步了解搜索技术，云计算相关技术、大数据研发等常见的技术喜提，以便让你更完整地了解后端技术栈的全貌，为后续参与分布式应用的开发和学习做好准备。\n\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系我，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n## 0x00 前言\n\n> 你了解你的数据吗？\n\n前几天突然来了点灵感，想梳理一下自己对数据的理解，因此便有了这篇博客或者说这系列博客来聊聊数据。\n\n数据从业者有很多，比如说数据开发工程师、数据仓库工程师、数据分析师、数据挖掘工程师、数据产品经理等等，不同岗位的童鞋对数据的理解有很大的不一样，而且侧重点也不同。那么，是否有一些数据相关的基础知识是所有数据从业者都值得了解的？不同的岗位对数据的理解又有多大的不同？数据开发工程师是否有必要去了解数据分析师是如何看待数据的？\n\n本系列博客会尝试去学习、挖掘和总结这些内容，在数据的海洋中一起装x一起飞。\n\n## 0x01 数据？数据！\n\n开篇先上几个问题：\n\n1.  你知道自己的系统数据接入量是多少吗？\n2.  你知道数据的分布情况吗？\n3.  你知道自己常用的数据有什么隐藏的坑吗？\n\n如果你对前面说的问题有不太了解的，那么我们就可以在以后的内容中一起愉快地交流和探讨。如果前面说的问题你的回答都是 “Yes”，那么我还是会尝试用新的问题来留住你。比如说：\n\n1.  既然你知道系统的数据接入量，那你知道每天的数据量波动吗？波动量在多大范围内是正常情况？\n2.  你知道的数据分布情况是什么样子的？除了性别、年龄和城市的分布，还有什么分布？\n3.  在偌大的数据仓库中，哪些数据被使用最多，哪些数据又无人问津，这些你了解吗？\n4.  在最常用的那批数据中，有哪些核心的维度？有相同维度的两个表之间的数据口径是否也一样？\n\n假设你对上面的问题有稍许困惑或者感兴趣，我们正式开始对数据的认知之旅。\n\n## 0x02 概览\n\n现在，我们粗略地将数据从业者分为数据集群运维、数据开发工程师、数据仓库工程师、数据分析师、数据挖掘工程师和数据产品经理，这一小节先起一个引子来大致说明不同岗位对数据的了解是不同的，后文会详细地说明细节内容。\n\n首先要说明的是，在工作中数据相关的职位都是有很多重合的，很难一刀切区分不同岗位的职责，比如说数据开发工程师本身就是一个很大的概念，他可以做数据接入、数据清洗、数据仓库开发、数据挖掘算法开发等等，再比如说数据分析师，很多数据分析师既要做数据分析，又要做一些提数的需求，有时候还要自己做各种处理。\n\n公司的数据团队越大，相应的岗位职责就会越细分，反之亦然。在这里我们姑且用数据开发工程师和数据仓库工程师做对比来说明不同职责的同学对数据理解的侧重点有什么不同。我们假设**数据开发工程师侧重于数据的接入、存储和基本的数据处理**，**数据仓库工程师侧重于数据模型的设计和开发（比如维度建模）**。\n\n1.  数据开发工程师对数据最基本的了解是需要知道数据的接入状态，比如说每天总共接入多少数据，整体数据量是多大，接入的业务有多少，每个业务的接入量多大，多大波动范围是正常？然后还要对数据的存储周期有一个把握，比如说有多少表的存储周期是30天，有多少是90天？集群每日新增的存储量是多大，多久后集群存储会撑爆？\n\n2.  数据仓库工程师对上面的内容也要有一定的感知力，但是会有所区别，比如说，数据仓库工程师会更关注自己仓库建模中用到业务的数据状态。然后还需要知道终点业务的数据分布，比如说用户表中的年龄分布、性别分布、地域分布等。除此之外还应关注数据口径问题，比如说有很多份用户资料表，每张表的性别取值是否都是：男、女、未知，还是说会有用数值类型：1男、2女、0未知。\n\n3.  然后数据开发工程师对数据异常的侧重点可能会在今天的数据是否延迟落地，总量是否波动很大，数据可用率是否正常。\n\n4.  数据仓库工程师对数据异常的侧重点则可能是，今天落地的数据中性别为 0 的数据量是否激增（这可能会造成数据倾斜），某一个关键维度取值是否都为空。\n\n_上面的例子可能都会在一个数据质量监控系统中一起解决，但是我们在这里不讨论系统的设计，而是先有整体的意识和思路。_\n\n## 0x03 关于内容\n\n那么，后续博客的内容会是什么样子的呢？目前来看，我认为会有两个角度：\n\n1.  **抛开岗位的区分度，从基本到高级来阐释对数据的了解**。比如说数据分布，最基本的程度只需要知道每天的接入量；深一点地话需要了解到其中重点维度的分布，比如说男女各多少；再深一点可能要需要知道重点维度的数据值分布，比如说年龄分布，怎样来合理划分年龄段，不同年龄段的比例。\n2.  **每个岗位会关注的一些侧重点**。这点笔者认为不太好区分，因为很多岗位重合度比较高，但是笔者会尝试去总结，同时希望大家一起来探讨这个问题。\n\n## 0xFF 总结\n\n本篇主要是抛出一些问题，后续会逐步展开地细说数据从业者对数据理解。其实最开始我想用“数据敏感度”、“数据感知力”这类标题，但是感觉这种概念比较难定义，因此用了比较口语化的标题。\n\n笔者认为，在数据从业者的职业生涯中，不应只有编程、算法和系统，还应有一套数据相关的方法论，这套方法论会来解决某一领域的问题，即使你们的系统从Hadoop换到了Spark，数据模型从基本的策略匹配换到了深度学习，这些方法论也依旧会伴你整个职业生涯。因此这系列博客会尝试去学习、挖掘和总结一套这样的方法论，与君共勉。\n\n* * *\n\n"
  },
  {
    "path": "docs/backend/后端技术杂谈：搜索引擎基础倒排索引.md",
    "content": "# 目录\n\n  * [什么是倒排索引？](#什么是倒排索引？)\n    * [1.单词——文档矩阵](#1单词文档矩阵)\n    * [2.倒排索引基本概念](#2倒排索引基本概念)\n    * [3.倒排索引简单实例](#3倒排索引简单实例)\n    * [4. 单词词典](#4-单词词典)\n\n\n[toc]\n本文转载自 https://www.cnblogs.com/zlslch/p/6440114.html\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本系列文章将整理到我的个人博客\n> www.how2playlife.com\n\n更多Java技术文章会更新在我的微信公众号【Java技术江湖】上，欢迎关注  \n该系列博文会介绍常见的后端技术，这对后端工程师来说是一种综合能力，我们会逐步了解搜索技术，云计算相关技术、大数据研发等常见的技术喜提，以便让你更完整地了解后端技术栈的全貌，为后续参与分布式应用的开发和学习做好准备。\n\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系我，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->  \n\n## 什么是倒排索引？\n\n见其名知其意，有倒排索引，对应肯定，有正向索引。  \n  正向索引（forward index），反向索引（inverted index）更熟悉的名字是倒排索引。\n\n在搜索引擎中每个文件都对应一个文件ID，文件内容被表示为一系列关键词的集合（实际上在搜索引擎索引库中，关键词也已经转换为关键词ID）。例如“文档1”经过分词，提取了20个关键词，每个关键词都会记录它在文档中的出现次数和出现位置。\n\n得到正向索引的结构如下：\n\n“文档1”的ID >单词1：出现次数，出现位置列表；单词2：出现次数，出现位置列表；…………。\n\n“文档2”的ID >此文档出现的关键词列表。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/855959-20170706154309815-1724421988.png)\n\n一般是通过key，去找value。\n\n当用户在主页上搜索关键词“华为手机”时，假设只存在正向索引（forward index），那么就需要扫描索引库中的所有文档，找出所有包含关键词“华为手机”的文档，再根据打分模型进行打分，排出名次后呈现给用户。因为互联网上收录在搜索引擎中的文档的数目是个天文数字，这样的索引结构根本无法满足实时返回排名结果的要求。\n\n所以，搜索引擎会将正向索引重新构建为倒排索引，即把文件ID对应到关键词的映射转换为关键词到文件ID的映射，每个关键词都对应着一系列的文件，这些文件中都出现这个关键词。\n\n得到倒排索引的结构如下：\n\n“关键词1”：“文档1”的ID，“文档2”的ID，…………。\n\n“关键词2”：带有此关键词的文档ID列表。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/855959-20170706154505378-610589524.png)\n\n从词的关键字，去找文档。\n### 1.单词——文档矩阵\n\n单词-文档矩阵是表达两者之间所具有的一种包含关系的概念模型，图1展示了其含义。图3-1的每列代表一个文档，每行代表一个单词，打对勾的位置代表包含关系。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/855959-20170224195649726-238255448.png)\n\n图1 单词-文档矩阵\n\n从纵向即文档这个维度来看，每列代表文档包含了哪些单词，比如文档1包含了词汇1和词汇4，而不包含其它单词。从横向即单词这个维度来看，每行代表了哪些文档包含了某个单词。比如对于词汇1来说，文档1和文档4中出现过单词1，而其它文档不包含词汇1。矩阵中其它的行列也可作此种解读。\n\n搜索引擎的索引其实就是实现“单词-文档矩阵”的具体[数据结构](http://lib.csdn.net/base/datastructure \"算法与数据结构知识库\")。可以有不同的方式来实现上述概念模型，比如“倒排索引”、“签名文件”、“后缀树”等方式。但是各项实验数据表明，“倒排索引”是实现单词到文档映射关系的最佳实现方式，所以本博文主要介绍“倒排索引”的技术细节。\n\n### 2.倒排索引基本概念\n\n文档(Document)：一般搜索引擎的处理对象是互联网网页，而文档这个概念要更宽泛些，代表以文本形式存在的存储对象，相比网页来说，涵盖更多种形式，比如Word，PDF，html，XML等不同格式的文件都可以称之为文档。再比如一封邮件，一条短信，一条微博也可以称之为文档。在本书后续内容，很多情况下会使用文档来表征文本信息。\n\n文档集合(Document Collection)：由若干文档构成的集合称之为文档集合。比如海量的互联网网页或者说大量的电子邮件都是文档集合的具体例子。\n\n文档编号(Document ID)：在搜索引擎内部，会将文档集合内每个文档赋予一个唯一的内部编号，以此编号来作为这个文档的唯一标识，这样方便内部处理，每个文档的内部编号即称之为“文档编号”，后文有时会用DocID来便捷地代表文档编号。\n\n单词编号(Word ID)：与文档编号类似，搜索引擎内部以唯一的编号来表征某个单词，单词编号可以作为某个单词的唯一表征。\n\n倒排索引(Inverted Index)：倒排索引是实现“单词-文档矩阵”的一种具体存储形式，通过倒排索引，可以根据单词快速获取包含这个单词的文档列表。倒排索引主要由两个部分组成：“单词词典”和“倒排文件”。\n\n单词词典(Lexicon)：搜索引擎的通常索引单位是单词，单词词典是由文档集合中出现过的所有单词构成的字符串集合，单词词典内每条索引项记载单词本身的一些信息以及指向“倒排列表”的指针。\n\n倒排列表(PostingList)：倒排列表记载了出现过某个单词的所有文档的文档列表及单词在该文档中出现的位置信息，每条记录称为一个倒排项(Posting)。根据倒排列表，即可获知哪些文档包含某个单词。\n\n倒排文件(Inverted File)：所有单词的倒排列表往往顺序地存储在磁盘的某个文件里，这个文件即被称之为倒排文件，倒排文件是存储倒排索引的物理文件。\n\n关于这些概念之间的关系，通过图2可以比较清晰的看出来。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/855959-20170224200237991-592489046.png)\n\n### 3.倒排索引简单实例\n\n倒排索引从逻辑结构和基本思路上来讲非常简单。下面我们通过具体实例来进行说明，使得读者能够对倒排索引有一个宏观而直接的感受。\n\n假设文档集合包含五个文档，每个文档内容如图3所示，在图中最左端一栏是每个文档对应的文档编号。我们的任务就是对这个文档集合建立倒排索引。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/855959-20170224200300601-1967186316.png)\n\n图3  文档集合\n\n中文和英文等语言不同，单词之间没有明确分隔符号，所以首先要用分词系统将文档自动切分成单词序列。这样每个文档就转换为由单词序列构成的数据流，为了系统后续处理方便，需要对每个不同的单词赋予唯一的单词编号，同时记录下哪些文档包含这个单词，在如此处理结束后，我们可以得到最简单的倒排索引（参考图3-4）。在图4中，“单词ID”一栏记录了每个单词的单词编号，第二栏是对应的单词，第三栏即每个单词对应的倒排列表。比如单词“谷歌”，其单词编号为1，倒排列表为{1,2,3,4,5}，说明文档集合中每个文档都包含了这个单词。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/855959-20170224200334195-2052728227.png)\n\n图4  简单的倒排索引\n\n之所以说图4所示倒排索引是最简单的，是因为这个索引系统只记载了哪些文档包含某个单词，而事实上，索引系统还可以记录除此之外的更多信息。图5是一个相对复杂些的倒排索引，与图4的基本索引系统比，在单词对应的倒排列表中不仅记录了文档编号，还记载了单词频率信息（TF），即这个单词在某个文档中的出现次数，之所以要记录这个信息，是因为词频信息在搜索结果排序时，计算查询和文档相似度是很重要的一个计算因子，所以将其记录在倒排列表中，以方便后续排序时进行分值计算。在图5的例子里，单词“创始人”的单词编号为7，对应的倒排列表内容为：（3:1），其中的3代表文档编号为3的文档包含这个单词，数字1代表词频信息，即这个单词在3号文档中只出现过1次，其它单词对应的倒排列表所代表含义与此相同。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/855959-20170224200417070-899412746.png)\n\n图 5 带有单词频率信息的倒排索引\n\n实用的倒排索引还可以记载更多的信息，图6所示索引系统除了记录文档编号和单词频率信息外，额外记载了两类信息，即每个单词对应的“文档频率信息”（对应图6的第三栏）以及在倒排列表中记录单词在某个文档出现的位置信息。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/855959-20170224200448148-924219280.png)\n\n图6  带有单词频率、文档频率和出现位置信息的倒排索引\n\n“文档频率信息”代表了在文档集合中有多少个文档包含某个单词，之所以要记录这个信息，其原因与单词频率信息一样，这个信息在搜索结果排序计算中是非常重要的一个因子。而单词在某个文档中出现的位置信息并非索引系统一定要记录的，在实际的索引系统里可以包含，也可以选择不包含这个信息，之所以如此，因为这个信息对于搜索系统来说并非必需的，位置信息只有在支持“短语查询”的时候才能够派上用场。\n\n以单词“拉斯”为例，其单词编号为8，文档频率为2，代表整个文档集合中有两个文档包含这个单词，对应的倒排列表为：{(3;1;<4>)，(5;1;<4>)},其含义为在文档3和文档5出现过这个单词，单词频率都为1，单词“拉斯”在两个文档中的出现位置都是4，即文档中第四个单词是“拉斯”。\n\n图6所示倒排索引已经是一个非常完备的索引系统，实际搜索系统的索引结构基本如此，区别无非是采取哪些具体的数据结构来实现上述逻辑结构。\n\n有了这个索引系统，搜索引擎可以很方便地响应用户的查询，比如用户输入查询词“Facebook”，搜索系统查找倒排索引，从中可以读出包含这个单词的文档，这些文档就是提供给用户的搜索结果，而利用单词频率信息、文档频率信息即可以对这些候选搜索结果进行排序，计算文档和查询的相似性，按照相似性得分由高到低排序输出，此即为搜索系统的部分内部流程，具体实现方案本书第五章会做详细描述。\n\n### 4. 单词词典\n\n单词词典是倒排索引中非常重要的组成部分，它用来维护文档集合中出现过的所有单词的相关信息，同时用来记载某个单词对应的倒排列表在倒排文件中的位置信息。在支持搜索时，根据用户的查询词，去单词词典里查询，就能够获得相应的倒排列表，并以此作为后续排序的基础。 对于一个规模很大的文档集合来说，可能包含几十万甚至上百万的不同单词，能否快速定位某个单词，这直接影响搜索时的响应速度，所以需要高效的数据结构来对单词词典进行构建和查找，常用的数据结构包括哈希加链表结构和树形词典结构。  \n4.1 哈希加链表  \n 图7是这种词典结构的示意图。这种词典结构主要由两个部分构成：\n\n主体部分是哈希表，每个哈希表项保存一个指针，指针指向冲突链表，在冲突链表里，相同哈希值的单词形成链表结构。之所以会有冲突链表，是因为两个不同单词获得相同的哈希值，如果是这样，在哈希方法里被称做是一次冲突，可以将相同哈希值的单词存储在链表里，以供后续查找。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/855959-20170224200556085-1266490265.png)\n\n在建立索引的过程中，词典结构也会相应地被构建出来。比如在解析一个新文档的时候，对于某个在文档中出现的单词T，首先利用哈希函数获得其哈希值，之后根据哈希值对应的哈希表项读取其中保存的指针，就找到了对应的冲突链表。如果冲突链表里已经存在这个单词，说明单词在之前解析的文档里已经出现过。如果在冲突链表里没有发现这个单词，说明该单词是首次碰到，则将其加入冲突链表里。通过这种方式，当文档集合内所有文档解析完毕时，相应的词典结构也就建立起来了。\n\n在响应用户查询请求时，其过程与建立词典类似，不同点在于即使词典里没出现过某个单词，也不会添加到词典内。以图7为例，假设用户输入的查询请求为单词3，对这个单词进行哈希，定位到哈希表内的2号槽，从其保留的指针可以获得冲突链表，依次将单词3和冲突链表内的单词比较，发现单词3在冲突链表内，于是找到这个单词，之后可以读出这个单词对应的倒排列表来进行后续的工作，如果没有找到这个单词，说明文档集合内没有任何文档包含单词，则搜索结果为空。\n\n4.2 树形结构  \n B树（或者B+树）是另外一种高效查找结构，图8是一个 B树结构示意图。B树与哈希方式查找不同，需要字典项能够按照大小排序（数字或者字符序），而哈希方式则无须数据满足此项要求。  \n B树形成了层级查找结构，中间节点用于指出一定顺序范围的词典项目存储在哪个子树中，起到根据词典项比较大小进行导航的作用，最底层的叶子节点存储单词的地址信息，根据这个地址就可以提取出单词字符串。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/855959-20170224200636976-342112863.png)\n\n图8  B树查找结构  \n  \n总结\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/855959-20170224200716070-76147588.png)\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/855959-20170224200724179-596243521.png)\n\n单词ID：记录每个单词的单词编号；  \n单词：对应的单词；  \n文档频率：代表文档集合中有多少个文档包含某个单词  \n倒排列表：包含单词ID及其他必要信息  \nDocId：单词出现的文档id  \nTF：单词在某个文档中出现的次数  \nPOS：单词在文档中出现的位置  \n  以单词“加盟”为例，其单词编号为6，文档频率为3，代表整个文档集合中有三个文档包含这个单词，对应的倒排列表为{(2;1;<4>),(3;1;<7>),(5;1;<5>)}，含义是在文档2，3，5出现过这个单词，在每个文档的出现过1次，单词“加盟”在第一个文档的POS是4，即文档的第四个单词是“加盟”，其他的类似。  \n这个倒排索引已经是一个非常完备的索引系统，实际搜索系统的索引结构基本如此。\n"
  },
  {
    "path": "docs/backend/后端技术杂谈：搜索引擎工作原理.md",
    "content": "# 目录\n\n  * [写在前面](#写在前面)\n  * [搜索引擎发展过程](#搜索引擎发展过程)\n  * [搜索引擎分类](#搜索引擎分类)\n  * [相关实现技术](#相关实现技术)\n  * [自己实现搜索引擎](#自己实现搜索引擎)\n  * [搜索引擎解决方案](#搜索引擎解决方案)\n\n\n[toc]\n\n本文作者：顿炖  \n链接：https://www.zhihu.com/question/19937854/answer/98791215  \n来源：知乎  \n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本系列文章将整理到我的个人博客\n> www.how2playlife.com\n\n更多Java技术文章会更新在我的微信公众号【Java技术江湖】上，欢迎关注  \n该系列博文会介绍常见的后端技术，这对后端工程师来说是一种综合能力，我们会逐步了解搜索技术，云计算相关技术、大数据研发等常见的技术喜提，以便让你更完整地了解后端技术栈的全貌，为后续参与分布式应用的开发和学习做好准备。\n\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系我，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->  \n## 写在前面\n\nMax Grigorev最近写了一篇文章，题目是[《What every software engineer should know about search》](https://link.juejin.im/?target=https%3A%2F%2Fmedium.com%2Fstartup-grind%2Fwhat-every-software-engineer-should-know-about-search-27d1df99f80d)，这篇文章里指出了现在一些软件工程师的问题，他们认为开发一个搜索引擎功能就是搭建一个ElasticSearch集群，而没有深究背后的技术，以及技术发展趋势。Max认为，除了搜索引擎自身的搜索问题解决、人类使用方式等之外，也需要解决索引、分词、权限控制、国际化等等的技术点，看了他的文章，勾起了我多年前的想法。\n\n很多年前，我曾经想过自己实现一个搜索引擎，作为自己的研究生论文课题，后来琢磨半天没有想出新的技术突破点（相较于已发表的文章），所以切换到了大数据相关的技术点。当时没有写出来，心中有点小遗憾，毕竟凭借搜索引擎崛起的谷歌是我内心渴望的公司。今天我就想结合自己的一些积累，聊聊作为一名软件工程师，您需要了解的搜索引擎知识。\n\n## 搜索引擎发展过程\n\n现代意义上的搜索引擎的祖先，是1990年由蒙特利尔大学学生Alan Emtage发明的Archie。即便没有英特网，网络中文件传输还是相当频繁的，而且由于大量的文件散布在各个分散的FTP主机中，查询起来非常不便，因此Alan Emtage想到了开发一个可以以文件名查找文件的系统，于是便有了Archie。Archie工作原理与现在的搜索引擎已经很接近，它依靠脚本程序自动搜索网上的文件，然后对有关信息进行索引，供使用者以一定的表达式查询。\n\n互联网兴起后，需要能够监控的工具。世界上第一个用于监测互联网发展规模的“机器人”程序是Matthew Gray开发的World wide Web Wanderer，刚开始它只用来统计互联网上的服务器数量，后来则发展为能够检索网站域名。\n\n随着互联网的迅速发展，每天都会新增大量的网站、网页，检索所有新出现的网页变得越来越困难，因此，在Matthew Gray的Wanderer基础上，一些编程者将传统的“蜘蛛”程序工作原理作了些改进。现代搜索引擎都是以此为基础发展的。\n\n## 搜索引擎分类\n\n*   全文搜索引擎\n\n当前主流的是全文搜索引擎，较为典型的代表是Google、百度。全文搜索引擎是指通过从互联网上提取的各个网站的信息（以网页文字为主），保存在自己建立的数据库中。用户发起检索请求后，系统检索与用户查询条件匹配的相关记录，然后按一定的排列顺序将结果返回给用户。从搜索结果来源的角度，全文搜索引擎又可细分为两种，一种是拥有自己的检索程序（Indexer），俗称“蜘蛛”（Spider）程序或“机器人”（Robot）程序，并自建网页数据库，搜索结果直接从自身的数据存储层中调用；另一种则是租用其他引擎的数据库，并按自定的格式排列搜索结果，如Lycos引擎。\n\n*   目录索引类搜索引擎\n\n虽然有搜索功能，但严格意义上不能称为真正的搜索引擎，只是按目录分类的网站链接列表而已。用户完全可以按照分类目录找到所需要的信息，不依靠关键词（Keywords）进行查询。目录索引中最具代表性的莫过于大名鼎鼎的Yahoo、新浪分类目录搜索。\n\n*   元搜索引擎\n\n    元搜索引擎在接受用户查询请求时，同时在其他多个引擎上进行搜索，并将结果返回给用户。著名的元搜索引擎有InfoSpace、Dogpile、Vivisimo等，中文元搜索引擎中具代表性的有搜星搜索引擎。在搜索结果排列方面，有的直接按来源引擎排列搜索结果，如Dogpile，有的则按自定的规则将结果重新排列组合，如Vivisimo。\n\n## 相关实现技术\n\n搜索引擎产品虽然一般都只有一个输入框，但是对于所提供的服务，背后有很多不同业务引擎支撑，每个业务引擎又有很多不同的策略，每个策略又有很多模块协同处理，及其复杂。\n\n搜索引擎本身包含网页抓取、网页评价、反作弊、建库、倒排索引、索引压缩、在线检索、ranking排序策略等等知识。\n\n*   网络爬虫技术\n\n网络爬虫技术指的是针对网络数据的抓取。因为在网络中抓取数据是具有关联性的抓取，它就像是一只蜘蛛一样在互联网中爬来爬去，所以我们很形象地将其称为是网络爬虫技术。网络爬虫也被称为是网络机器人或者是网络追逐者。\n\n网络爬虫获取网页信息的方式和我们平时使用浏览器访问网页的工作原理是完全一样的，都是根据HTTP协议来获取，其流程主要包括如下步骤：\n\n1）连接DNS域名服务器，将待抓取的URL进行域名解析（URL------>IP）；\n\n2）根据HTTP协议，发送HTTP请求来获取网页内容。\n\n一个完整的网络爬虫基础框架如下图所示：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/006tKfTcly1fkrzrfk7trj30pl0grjt9.jpg)\n\n整个架构共有如下几个过程：\n\n1）需求方提供需要抓取的种子URL列表，根据提供的URL列表和相应的优先级，建立待抓取URL队列（先来先抓）；\n\n2）根据待抓取URL队列的排序进行网页抓取；\n\n3）将获取的网页内容和信息下载到本地的网页库，并建立已抓取URL列表（用于去重和判断抓取的进程）；\n\n4）将已抓取的网页放入到待抓取的URL队列中，进行循环抓取操作；\n- 索引\n\n从用户的角度来看，搜索的过程是通过关键字在某种资源中寻找特定的内容的过程。而从计算机的角度来看，实现这个过程可以有两种办法。一是对所有资源逐个与关键字匹配，返回所有满足匹配的内容；二是如同字典一样事先建立一个对应表，把关键字与资源的内容对应起来，搜索时直接查找这个表即可。显而易见，第二个办法效率要高得多。建立这个对应表事实上就是建立逆向索引（inverted index）的过程。\n\n*   Lucene\n\nLucene是一个高性能的java全文检索工具包，它使用的是倒排文件索引结构。\n\n全文检索大体分两个过程，索引创建 (Indexing) 和搜索索引 (Search) 。\n\n索引创建：将现实世界中所有的结构化和非结构化数据提取信息，创建索引的过程。  \n搜索索引：就是得到用户的查询请求，搜索创建的索引，然后返回结果的过程。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/006tKfTcly1fkrzrpvzm1j30f40db755.jpg)\n\n非结构化数据中所存储的信息是每个文件包含哪些字符串，也即已知文件，欲求字符串相对容易，也即是从文件到字符串的映射。而我们想搜索的信息是哪些文件包含此字符串，也即已知字符串，欲求文件，也即从字符串到文件的映射。两者恰恰相反。于是如果索引总能够保存从字符串到文件的映射，则会大大提高搜索速度。\n\n由于从字符串到文件的映射是文件到字符串映射的反向过程，于是保存这种信息的索引称为反向索引 。\n\n反向索引的所保存的信息一般如下：\n\n假设我的文档集合里面有100篇文档，为了方便表示，我们为文档编号从1到100，得到下面的结构\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/006tKfTcly1fkrzrz05rqj30f4049gmb.jpg)\n\n每个字符串都指向包含此字符串的文档(Document)链表，此文档链表称为倒排表 (Posting List)。\n\n*   ElasticSearch\n\nElasticsearch是一个实时的分布式搜索和分析引擎，可以用于全文搜索，结构化搜索以及分析，当然你也可以将这三者进行组合。Elasticsearch是一个建立在全文搜索引擎 Apache Lucene™ 基础上的搜索引擎，但是Lucene只是一个框架，要充分利用它的功能，需要使用JAVA，并且在程序中集成Lucene。Elasticsearch使用Lucene作为内部引擎，但是在使用它做全文搜索时，只需要使用统一开发好的API即可，而不需要了解其背后复杂的Lucene的运行原理。\n\n*   Solr\n\nSolr是一个基于Lucene的搜索引擎服务器。Solr 提供了层面搜索、命中醒目显示并且支持多种输出格式（包括 XML/XSLT 和 JSON 格式）。它易于安装和配置，而且附带了一个基于 HTTP 的管理界面。Solr已经在众多大型的网站中使用，较为成熟和稳定。Solr 包装并扩展了 Lucene，所以Solr的基本上沿用了Lucene的相关术语。更重要的是，Solr 创建的索引与 Lucene 搜索引擎库完全兼容。通过对Solr 进行适当的配置，某些情况下可能需要进行编码，Solr 可以阅读和使用构建到其他 Lucene 应用程序中的索引。此外，很多 Lucene 工具（如Nutch、 Luke）也可以使用Solr 创建的索引。\n\n*   Hadoop\n\n谷歌公司发布的一系列技术白皮书导致了Hadoop的诞生。Hadoop是一系列大数据处理工具，可以被用在大规模集群里。Hadoop目前已经发展为一个生态体系，包括了很多组件，如图所示。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/006tKfTcly1fkrzs6lbs1j31hm10x466.jpg)\n\nCloudera是一家将Hadoop技术用于搜索引擎的公司，用户可以采用全文搜索方式检索存储在HDFS（Hadoop分布式文件系统）和Apache HBase里面的数据，再加上开源的搜索引擎Apache Solr，Cloudera提供了搜索功能，并结合Apache ZooKeeper进行分布式处理的管理、索引切分以及高性能检索。\n\n*   PageRank\n\n谷歌Pagerank算法基于随机冲浪模型，基本思想是基于网站之间的相互投票，即我们常说的网站之间互相指向。如果判断一个网站是高质量站点时，那么该网站应该是被很多高质量的网站引用又或者是该网站引用了大量的高质量权威的站点。\n- 国际化\n\n坦白说，Google虽然做得非常好，无论是技术还是产品设计，都很好。但是国际化确实是非常难做的，很多时候在细分领域还是会有其他搜索引擎的生存余地。例如在韩国，Naver是用户的首选，它本身基于Yahoo的Overture系统，广告系统则是自己开发的。在捷克，我们则更多会使用Seznam。在瑞典，用户更多选择Eniro，它最初是瑞典的黄页开发公司。\n\n国际化、个性化搜索、匿名搜索，这些都是Google这样的产品所不能完全覆盖到的，事实上，也没有任何一款产品可以适用于所有需求。\n\n## 自己实现搜索引擎\n\n如果我们想要实现搜索引擎，最重要的是索引模块和搜索模块。索引模块在不同的机器上各自进行对资源的索引，并把索引文件统一传输到同一个地方（可以是在远程服务器上，也可以是在本地）。搜索模块则利用这些从多个索引模块收集到的数据完成用户的搜索请求。因此，我们可以理解两个模块之间相对是独立的，它们之间的关联不是通过代码，而是通过索引和元数据，如下图所示。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/006tKfTcly1fkrzshq4cbj30ga05874m.jpg)\n\n对于索引的建立，我们需要注意性能问题。当需要进行索引的资源数目不多时，隔一定的时间进行一次完全索引，不会占用很长时间。但在大型应用中，资源的容量是巨大的，如果每次都进行完整的索引，耗费的时间会很惊人。我们可以通过跳过已经索引的资源内容，删除已不存在的资源内容的索引，并进行增量索引来解决这个问题。这可能会涉及文件校验和索引删除等。另一方面，框架可以提供查询缓存功能，提高查询效率。框架可以在内存中建立一级缓存，并使用如 OSCache或 EHCache缓存框架，实现磁盘上的二级缓存。当索引的内容变化不频繁时，使用查询缓存更会明显地提高查询速度、降低资源消耗。\n\n## 搜索引擎解决方案\n\n*   Sphinx\n\n俄罗斯一家公司开源的全文搜索引擎软件Sphinx，单一索引最大可包含1亿条记录，在1千万条记录情况下的查询速度为0.x秒（毫秒级）。Sphinx创建索引的速度很快，根据网上的资料，Sphinx创建100万条记录的索引只需3～4分钟，创建1000万条记录的索引可以在50分钟内完成，而只包含最新10万条记录的增量索引，重建一次只需几十秒。\n\n*   OmniFind\n\nOmniFind 是 IBM 公司推出的企业级搜索解决方案。基于 UIMA (Unstructured Information Management Architecture) 技术，它提供了强大的索引和获取信息功能，支持巨大数量、多种类型的文档资源（无论是结构化还是非结构化），并为 Lotus®Domino®和 WebSphere®Portal 专门进行了优化。  \n下一代搜索引擎\n\n从技术和产品层面来看，接下来的几年，甚至于更长时间，应该没有哪一家搜索引擎可以撼动谷歌的技术领先优势和产品地位。但是我们也可以发现一些现象，例如搜索假期租房的时候，人们更喜欢使用Airbub，而不是Google，这就是针对匿名/个性化搜索需求，这些需求是谷歌所不能完全覆盖到的，毕竟原始数据并不在谷歌。我们可以看一个例子：DuckDuckGo。这是一款有别于大众理解的搜索引擎，DuckDuckGo强调的是最佳答案，而不是更多的结果，所以每个人搜索相同关键词时，返回的结果是不一样的。\n\n另一个方面技术趋势是引入人工智能技术。在搜索体验上，通过大量算法的引入，对用户搜索的内容和访问偏好进行分析，将标题摘要进行一定程度的优化，以更容易理解的方式呈现给用户。谷歌在搜索引擎AI化的步骤领先于其他厂商，2016年，随着Amit Singhal被退休，John Giannandrea上位的交接班过程后，正式开启了自身的革命。Giannandrea是深度神经网络、近似人脑中的神经元网络研究方面的顶级专家，通过分析海量级的数字数据，这些神经网络可以学习排列方式，例如对图片进行分类、识别智能手机的语音控制等等，对应也可以应用在搜索引擎。因此，Singhal向Giannandrea的过渡，也意味着传统人为干预的规则设置的搜索引擎向AI技术的过渡。引入深度学习技术之后的搜索引擎，通过不断的模型训练，它会深层次地理解内容，并为客户提供更贴近实际需求的服务，这才是它的有用，或者可怕之处。\n\n**Google搜索引擎的工作流程**\n\n贴个图，自己感受下。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/5b981a1b36b15a5a82c8f4e65a718afa_b.jpg)![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/5b981a1b36b15a5a82c8f4e65a718afa_hd.jpg)\n\n**详细点的 ：**  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/6e7fd609f7de41c35f5587a1ceb42523_b.jpg)![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/6e7fd609f7de41c35f5587a1ceb42523_hd.jpg)\n"
  },
  {
    "path": "docs/backend/后端技术杂谈：白话虚拟化技术.md",
    "content": "\n本文转自互联网，侵删\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本系列文章将整理到我的个人博客\n\n> www.how2playlife.com\n\n更多Java技术文章会更新在我的微信公众号【Java技术江湖】上，欢迎关注\n该系列博文会介绍常见的后端技术，这对后端工程师来说是一种综合能力，我们会逐步了解搜索技术，云计算相关技术、大数据研发等常见的技术喜提，以便让你更完整地了解后端技术栈的全貌，为后续参与分布式应用的开发和学习做好准备。\n\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系我，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n内核，是指的操作系统内核。\n\n所有的操作系统都有内核，无论是Windows还是Linux，都管理着三个重要的资源：计算，网络，存储。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230408115530.png)\n\n计算指CPU和内存，网络即网络设备，存储即硬盘之类的。\n\n内核是个大管家，想象你的机器上跑着很多的程序，有word，有excel，看着视频，听着音乐，每个程序都要使用CPU和内存，都要上网，都要存硬盘，如果没有一个大管家管着，大家随便用，就乱了。所以需要管家来协调调度整个资源，谁先用，谁后用，谁用多少，谁放在这里，谁放在那里，都需要管家操心。\n\n所以在这个计算机大家庭里面，管家有着比普通的程序更高的权限，运行在内核态，而其他的普通程序运行在用户态，用户态的程序一旦要申请公共的资源，就需要向管家申请，管家帮它分配好，它才能用。\n\n为了区分内核态和用户态，CPU专门设置四个特权等级0,1,2,3 来做这个事情。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230408115543.png)\n\n当时写Linux内核的时候，估计大牛们还不知道将来虚拟机会大放异彩，大牛们想，一共两级特权，一个内核态，一个用户态，却有四个等级，好奢侈，好富裕，就敞开了用，内核态运行在第0等级，用户态运行在第3等级，占了两头，太不会过日子了。\n\n大牛们在写Linux内核的时候，如果用户态程序做事情，就将扳手掰到第3等级，一旦要申请使用更多的资源，就需要申请将扳手掰到第0等级，内核才能在高权限访问这些资源，申请完资源，返回到用户态，扳手再掰回去。\n\n这个程序一直非常顺利的运行着，直到虚拟机的出现。\n\n如果大家用过Vmware桌面版，或者Virtualbox桌面版，你可以用这个虚拟化软件创建虚拟机，在虚拟机里面安装一个Linux或者windows，外面的操作系统也可以是Linux或者Windows。\n\n当你使用虚拟机软件的时候，和你的excel一样，都是在你的任务栏里面并排的放着，是一个普通的应用。\n\n当你进入虚拟机的时候，虚拟机里面的excel也是一个普通的应用。\n\n但是当你设身处地的站在虚拟机里面的内核的角度思考一下人生，你就困惑了，我到底个啥？\n\n在硬件上的操作系统来看，我是一个普通的应用，只能运行在用户态。可是大牛们生我的时候，我的每一行代码，都告诉我，我是个内核啊，应该运行在内核态，当虚拟机里面的excel要访问网络的时候，向我请求，我的代码就要努力的去操作网络资源，我努力，但是我做不到，我没有权限！\n\n我分裂了。\n\n虚拟化层，也就是Vmware或者Virtualbox需要帮我解决这个问题。\n\n第一种方式，完全虚拟化，其实就是骗我。虚拟化软件模拟假的CPU，内存，网络，硬盘给我，让我自我感觉良好，终于又像个内核了。\n\n真正的工作模式是这样的。\n\n虚拟机内核：我要在CPU上跑一个指令！\n\n虚拟化软件：没问题，你是内核嘛，可以跑\n\n虚拟化软件转过头去找物理机内核：报告管家，我管理的虚拟机里面的一个要执行一个CPU指令，帮忙来一小段时间空闲的CPU时间，让我代他跑个指令。\n\n物理机内核：你等着，另一个跑着呢。好嘞，他终于跑完了，该你了。\n\n虚拟化软件：我代他跑，终于跑完了，出来结果了\n\n虚拟化软件转头给虚拟机内核：哥们，跑完了，结果是这个，我说你是内核吧，绝对有权限，没问题，下次跑指令找我啊。\n\n虚拟机内核：看来我真的是内核呢。可是哥，好像这点指令跑的有点慢啊。\n\n虚拟化软件：这就不错啦，好几个排着队跑呢。\n\n内存的申请模式如下。\n\n虚拟机内核：我启动需要4G内存，我好分给我上面的应用。\n\n虚拟化软件：没问题，才4G，你是内核嘛，马上申请好。\n\n虚拟化软件转头给物理机内核：报告，管家，我启动了一个虚拟机，需要4G内存，给我4个房间呗。\n\n物理机内核：怎么又一个虚拟机啊，好吧，给你90,91,92,93四个房间。\n\n虚拟化软件转头给虚拟机内核：哥们，内存有了，0,1,2,3这个四个房间都是你的，你看，你是内核嘛，独占资源，从0编号的就是你的。\n\n虚拟机内核：看来我真的是内核啊，能从头开始用。那好，我就在房间2的第三个柜子里面放个东西吧。\n\n虚拟化软件：要放东西啊，没问题。心里想：我查查看，这个虚拟机是90号房间开头的，他要在房间2放东西，那就相当于在房间92放东西。\n\n虚拟化软件转头给物理机内核：报告，管家，我上面的虚拟机要在92号房间的第三个柜子里面放个东西。\n\n好了，说完了CPU和内存的例子，不细说网络和硬盘了，也是类似，都是虚拟化软件模拟一个给虚拟机内核看的，其实啥事儿都需要虚拟化软件转一遍。\n\n这种方式一个坏处，就是慢，往往慢到不能忍受。\n\n于是虚拟化软件想，我能不能不当传话筒，还是要让虚拟机内核正视自己的身份，别说你是内核，你还真喘上了，你不是物理机，你是虚拟机。\n\n但是怎么解决权限等级的问题呢？于是Intel的VT-x和AMD的AMD-V从硬件层面帮上了忙。当初谁让你们这些写内核的大牛用等级这么奢侈，用完了0，就是3，也不省着点用，没办法，只好另起炉灶弄一个新的标志位，表示当前是在虚拟机状态下，还是真正的物理机内核下。\n\n对于虚拟机内核来讲，只要将标志位设为虚拟机状态，则可以直接在CPU上执行大部分的指令，不需要虚拟化软件在中间转述，除非遇到特别敏感的指令，才需要将标志位设为物理机内核态运行，这样大大提高了效率。\n\n所以安装虚拟机的时候，务必要将物理CPU的这个标志位打开，是否打开对于Intel可以查看grep\"vmx\" /proc/cpuinfo，对于AMD可以查看grep \"svm\" /proc/cpuinfo\n\n这叫做硬件辅助虚拟化。\n\n另外就是访问网络或者硬盘的时候，为了取得更高的性能，也需要让虚拟机内核加载特殊的驱动，也是让虚拟机内核从代码层面就重新定位自己的身份，不能像访问物理机一样访问网络或者硬盘，而是用一种特殊的方式：我知道我不是物理机内核，我知道我是虚拟机，我没那么高的权限，我很可能和很多虚拟机共享物理资源，所以我要学会排队，我写硬盘其实写的是一个物理机上的文件，那我的写文件的缓存方式是不是可以变一下，我发送网络包，根本就不是发给真正的网络设备，而是给虚拟的设备，我可不可以直接在内存里面拷贝给他，等等等等。\n\n一旦我知道我不是物理机内核，痛定思痛，只好重新认识自己，反而能找出很多方式来优化我的资源访问。\n\n这叫做类虚拟化或者半虚拟化。\n\n如果您想更技术的了解本文背后的原理，请看书《系统虚拟化——原理与实现》\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230408115552.png)\n"
  },
  {
    "path": "docs/cache/Redis原理与实践总结.md",
    "content": "# 目录\n  * [使用和基础数据结构（外观）](#使用和基础数据结构（外观）)\n  * [底层数据结构](#底层数据结构)\n  * [redis server结构和数据库redisDb](#redis-server结构和数据库redisdb)\n  * [redis的事件模型](#redis的事件模型)\n  * [备份方式](#备份方式)\n  * [redis主从复制](#redis主从复制)\n  * [分布式锁实现](#分布式锁实现)\n    * [使用setnx加expire实现加锁和时限](#使用setnx加expire实现加锁和时限)\n    * [使用getset加锁和获取过期时间](#使用getset加锁和获取过期时间)\n    * [2.0的setnx可以配置过期时间。](#20的setnx可以配置过期时间。)\n    * [使用sentx将值设为时间戳，通过lua脚本进行cas比较和删除操作](#使用sentx将值设为时间戳，通过lua脚本进行cas比较和删除操作)\n    * [分布式Redis锁：Redlock](#分布式redis锁：redlock)\n    * [总结](#总结)\n  * [分布式方案](#分布式方案)\n  * [redis事务](#redis事务)\n    * [redis脚本事务](#redis脚本事务)\n  * [微信公众号](#微信公众号)\n    * [Java技术江湖](#java技术江湖)\n    * [个人公众号：黄小斜](#个人公众号：黄小斜)\n\n\n[toc]\n\n\n本文主要对Redis的设计和实现原理做了一个介绍很总结，有些东西我也介绍的不是很详细准确，尽量在自己的理解范围内把一些知识点和关键性技术做一个描述。如有错误，还望见谅，欢迎指出。\n这篇文章主要还是参考我之前的技术专栏总结而来的。欢迎查看：\n\n重新学习Redis\n\nhttps://blog.csdn.net/column/details/21877.html\n<!-- more -->\n\n## 使用和基础数据结构（外观）\n\nredis的基本使用方式是建立在redis提供的数据结构上的。\n\n字符串\nREDIS_STRING (字符串)是 Redis 使用得最为广泛的数据类型,它除了是 SET 、GET 等命令 的操作对象之外,数据库中的所有键,以及执行命令时提供给 Redis 的参数,都是用这种类型 保存的。\n\n字符串类型分别使用 REDIS_ENCODING_INT 和 REDIS_ENCODING_RAW 两种编码\n\n只有能表示为 long 类型的值,才会以整数的形式保存,其他类型 的整数、小数和字符串,都是用 sdshdr 结构来保存\n\n哈希表\nREDIS_HASH (哈希表)是HSET 、HLEN 等命令的操作对象\n\n它使用 REDIS_ENCODING_ZIPLIST和REDIS_ENCODING_HT 两种编码方式\n\nRedis 中每个hash可以存储232-1键值对（40多亿）\n\n列表\nREDIS_LIST(列表)是LPUSH 、LRANGE等命令的操作对象\n\n它使用 REDIS_ENCODING_ZIPLIST和REDIS_ENCODING_LINKEDLIST 这两种方式编码\n\n一个列表最多可以包含232-1 个元素(4294967295, 每个列表超过40亿个元素)。\n\n集合\nREDIS_SET (集合) 是 SADD 、 SRANDMEMBER 等命令的操作对象\n\n它使用 REDIS_ENCODING_INTSET 和 REDIS_ENCODING_HT 两种方式编码\n\nRedis 中集合是通过哈希表实现的，所以添加，删除，查找的复杂度都是O(1)。\n\n集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)\n\n有序集\nREDIS_ZSET (有序集)是ZADD 、ZCOUNT 等命令的操作对象\n\n它使用 REDIS_ENCODING_ZIPLIST和REDIS_ENCODING_SKIPLIST 两种方式编码\n\n不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。\n\n有序集合的成员是唯一的,但分数(score)却可以重复。\n\n集合是通过哈希表实现的，所以添加，删除，查找的复杂度都是O(1)。 集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)\n\n## 底层数据结构\n\n下面讨论redis底层数据结构\n\n\n1 SDS动态字符串\n\nsds字符串是字符串的实现\n\n动态字符串是一个结构体，内部有一个buf数组，以及字符串长度，剩余长度等字段，优点是通过长度限制写入，避免缓冲区溢出，另外剩余长度不足时会自动扩容，扩展性较好，不需要频繁分配内存。\n\n并且sds支持写入二进制数据，而不一定是字符。\n\n2 dict字典\n\ndict字典是哈希表的实现。\n\ndict字典与Java中的哈希表实现简直如出一辙，首先都是数组+链表组成的结构，通过dictentry保存节点。\n\n其中dict同时保存两个entry数组，当需要扩容时，把节点转移到第二个数组即可，平时只使用一个数组。\n\n![image](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/redis_dict_structure.png)\n\n3 压缩链表ziplist\n\n3.1 ziplist是一个经过特殊编码的双向链表，它的设计目标就是为了提高存储效率。ziplist可以用于存储字符串或整数，其中整数是按真正的二进制表示进行编码的，而不是编码成字符串序列。它能以O(1)的时间复杂度在表的两端提供push和pop操作。\n\n3.2 实际上，ziplist充分体现了Redis对于存储效率的追求。一个普通的双向链表，链表中每一项都占用独立的一块内存，各项之间用地址指针（或引用）连接起来。这种方式会带来大量的内存碎片，而且地址指针也会占用额外的内存。\n\n3.3 而ziplist却是将表中每一项存放在前后连续的地址空间内，一个ziplist整体占用一大块内存。它是一个表（list），但其实不是一个链表（linked list）。\n\n3.4 另外，ziplist为了在细节上节省内存，对于值的存储采用了变长的编码方式，大概意思是说，对于大的整数，就多用一些字节来存储，而对于小的整数，就少用一些字节来存储。\n\n实际上。redis的字典一开始的数据比较少时，会使用ziplist的方式来存储，也就是key1，value1，key2，value2这样的顺序存储，对于小数据量来说，这样存储既省空间，查询的效率也不低。\n\n当数据量超过阈值时，哈希表自动膨胀为之前我们讨论的dict。\n\n4 quicklist\n\nquicklist是结合ziplist存储优势和链表灵活性与一身的双端链表。\n\nquicklist的结构为什么这样设计呢？总结起来，大概又是一个空间和时间的折中：\n\n4.1 双向链表便于在表的两端进行push和pop操作，但是它的内存开销比较大。\n\n首先，它在每个节点上除了要保存数据之外，还要额外保存两个指针；其次，双向链表的各个节点是单独的内存块，地址不连续，节点多了容易产生内存碎片。\n\n4.2 ziplist由于是一整块连续内存，所以存储效率很高。\n\n但是，它不利于修改操作，每次数据变动都会引发一次内存的realloc。特别是当ziplist长度很长的时候，一次realloc可能会导致大批量的数据拷贝，进一步降低性能。\n\n![image](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/redis_quicklist_structure.png)\n\n5 zset\nzset其实是两种结构的合并。也就是dict和skiplist结合而成的。dict负责保存数据对分数的映射，而skiplist用于根据分数进行数据的查询（相辅相成）\n\n6 skiplist\n\nsortset数据结构使用了ziplist+zset两种数据结构。\n\nRedis里面使用skiplist是为了实现sorted set这种对外的数据结构。sorted set提供的操作非常丰富，可以满足非常多的应用场景。这也意味着，sorted set相对来说实现比较复杂。\n\nsortedset是由skiplist，dict和ziplist组成的。\n\n当数据较少时，sorted set是由一个ziplist来实现的。\n当数据多的时候，sorted\n\nset是由一个叫zset的数据结构来实现的，这个zset包含一个dict + 一个skiplist。dict用来查询数据到分数(score)的对应关系，而skiplist用来根据分数查询数据（可能是范围查找）。\n\n在本系列前面关于ziplist的文章里，我们介绍过，ziplist就是由很多数据项组成的一大块连续内存。由于sorted set的每一项元素都由数据和score组成，因此，当使用zadd命令插入一个(数据, score)对的时候，底层在相应的ziplist上就插入两个数据项：数据在前，score在后。\n\n![image](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/redis_skiplist_example.png)\n\nskiplist的节点中存着节点值和分数。并且跳表是根据节点的分数进行排序的，所以可以根据节点分数进行范围查找。\n\n7inset\n\ninset是一个数字结合，他使用灵活的数据类型来保持数字。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230406202310.png)\n\n新创建的intset只有一个header，总共8个字节。其中encoding = 2, length = 0。\n添加13, 5两个元素之后，因为它们是比较小的整数，都能使用2个字节表示，所以encoding不变，值还是2。\n当添加32768的时候，它不再能用2个字节来表示了（2个字节能表达的数据范围是-215~215-1，而32768等于215，超出范围了），因此encoding必须升级到INTSET_ENC_INT32（值为4），即用4个字节表示一个元素。\n\n8总结\n\nsds是一个灵活的字符串数组，并且支持直接存储二进制数据，同时提供长度和剩余空间的字段来保证伸缩性和防止溢出。\n\ndict是一个字典结构，实现方式就是Java中的hashmap实现，同时持有两个节点数组，但只使用其中一个，扩容时换成另外一个。\n\nziplist是一个压缩链表，他放弃内存不连续的连接方式，而是直接分配连续内存进行存储，减少内存碎片。提高利用率，并且也支持存储二进制数据。\n\nquicklist是ziplist和传统链表的中和形成的链表结果，每个链表节点都是一个ziplist。\n\nskiplist一般有ziplist和zset两种实现方法，根据数据量来决定。zset本身是由skiplist和dict实现的。\n\ninset是一个数字集合，他根据插入元素的数据类型来决定数组元素的长度。并自动进行扩容。\n\n9 他们实现了哪些结构\n\n字符串由sds实现\n\nlist由ziplist和quicklist实现\n\nsortset由ziplist和zset实现\n\nhash表由dict实现\n\n集合由inset实现。\n\n\n## redis server结构和数据库redisDb\n\n1 redis服务器中维护着一个数据库名为redisdb，实际上他是一个dict结构。\n\nRedis的数据库使用字典作为底层实现，数据库的增、删、查、改都是构建在字典的操作之上的。\n\n2 redis服务器将所有数据库都保存在服务器状态结构redisServer(redis.h/redisServer)的db数组（应该是一个链表）里：\n\n同理也有一个redis client结构，通过指针可以选择redis client访问的server是哪一个。\n\n3 redisdb的键空间\n\n    typedef struct redisDb {\n        // 数据库键空间，保存着数据库中的所有键值对\n        dict *dict;                 /* The keyspace for this DB */\n        // 键的过期时间，字典的键为键，字典的值为过期事件 UNIX 时间戳\n        dict *expires;              /* Timeout of keys with a timeout set */\n        // 数据库号码\n        int id;                     /* Database ID */\n        // 数据库的键的平均 TTL ，统计信息\n        long long avg_ttl;          /* Average TTL, just for stats */\n        //..\n    } redisDb\n\n这部分的代码说明了，redisdb除了维护一个dict组以外，还需要对应地维护一个expire的字典数组。\n\n大的dict数组中有多个小的dict字典，他们共同负责存储redisdb的所有键值对。\n\n同时，对应的expire字典则负责存储这些键的过期时间\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230406201529.png)\n\n4 过期键的删除策略\n\n2、过期键删除策略\n通过前面的介绍，大家应该都知道数据库键的过期时间都保存在过期字典里，那假如一个键过期了，那么这个过期键是什么时候被删除的呢？现在来看看redis的过期键的删除策略：\n\na、定时删除：在设置键的过期时间的同时，创建一个定时器，在定时结束的时候，将该键删除；\n\nb、惰性删除：放任键过期不管，在访问该键的时候，判断该键的过期时间是否已经到了，如果过期时间已经到了，就执行删除操作；\n\nc、定期删除：每隔一段时间，对数据库中的键进行一次遍历，删除过期的键。\n\n## redis的事件模型\n\nredis处理请求的方式基于reactor线程模型，即一个线程处理连接，并且注册事件到IO多路复用器，复用器触发事件以后根据不同的处理器去执行不同的操作。总结以下客户端到服务端的请求过程\n\n总结\n\n    远程客户端连接到 redis 后，redis服务端会为远程客户端创建一个 redisClient 作为代理。\n    \n    redis 会读取嵌套字中的数据，写入 querybuf 中。\n    \n    解析 querybuf 中的命令，记录到 argc 和 argv 中。\n    \n    根据 argv[0] 查找对应的 recommand。\n    \n    执行 recommend 对应的执行函数。\n    \n    执行以后将结果存入 buf & bufpos & reply 中。\n    \n    返回给调用方。返回数据的时候，会控制写入数据量的大小，如果过大会分成若干次。保证 redis 的相应时间。\n    \n    Redis 作为单线程应用，一直贯彻的思想就是，每个步骤的执行都有一个上限（包括执行时间的上限或者文件尺寸的上限）一旦达到上限，就会记录下当前的执行进度，下次再执行。保证了 Redis 能够及时响应不发生阻塞。\n\n## 备份方式\n\n快照（RDB）：就是我们俗称的备份，他可以在定期内对数据进行备份，将Redis服务器中的数据持久化到硬盘中；\n\n只追加文件（AOF）：他会在执行写命令的时候，将执行的写命令复制到硬盘里面，后期恢复的时候，只需要重新执行一下这个写命令就可以了。类似于我们的MySQL数据库在进行主从复制的时候，使用的是binlog二进制文件，同样的是执行一遍写命令；\n\nappendfsync同步频率的区别如下图：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230406201549.png)\n\n## redis主从复制\n\nRedis复制工作过程：\n\nslave向master发送sync命令。\n\nmaster开启子进程来讲dataset写入rdb文件，同时将子进程完成之前接收到的写命令缓存起来。\n\n子进程写完，父进程得知，开始将RDB文件发送给slave。\n\nmaster发送完RDB文件，将缓存的命令也发给slave。\n\nmaster增量的把写命令发给slave。\n\n    注意有两步操作，一个是写入rdb的时候要缓存写命令，防止数据不一致。发完rdb后还要发写命令给salve，以后增量发命令就可以了\n\n## 分布式锁实现\n\n### 使用setnx加expire实现加锁和时限\n\n加锁时使用setnx设置key为1并设置超时时间，解锁时删除键\n\n    tryLock(){  \n        SETNX Key 1\n        EXPIRE Key Seconds\n    }\n    release(){  \n      DELETE Key\n    }\n\n这个方案的一个问题在于每次提交一个Redis请求，如果执行完第一条命令后应用异常或者重启，锁将无法过期，一种改善方案就是使用Lua脚本（包含SETNX和EXPIRE两条命令），但是如果Redis仅执行了一条命令后crash或者发生主从切换，依然会出现锁没有过期时间，最终导致无法释放。\n\n### 使用getset加锁和获取过期时间\n\n针对锁无法释放问题的一个解决方案基于GETSET命令来实现\n\n\n    思路：\n    \n    SETNX(Key,ExpireTime)获取锁\n    \n    如果获取锁失败，通过GET(Key)返回的时间戳检查锁是否已经过期\n    \n    GETSET(Key,ExpireTime)修改Value为NewExpireTime\n    \n    检查GETSET返回的旧值，如果等于GET返回的值，则认为获取锁成功\n    \n    注意：这个版本去掉了EXPIRE命令，改为通过Value时间戳值来判断过期\n\n\n\n\n### 2.0的setnx可以配置过期时间。\n\n    V2.0 基于SETNX\n    \n    tryLock(){  \n        SETNX Key 1 Seconds\n    }\n    release(){  \n      DELETE Key\n    }\n\nRedis 2.6.12版本后SETNX增加过期时间参数，这样就解决了两条命令无法保证原子性的问题。但是设想下面一个场景：\n\n1. C1成功获取到了锁，之后C1因为GC进入等待或者未知原因导致任务执行过长，最后在锁失效前C1没有主动释放锁 2. C2在C1的锁超时后获取到锁，并且开始执行，这个时候C1和C2都同时在执行，会因重复执行造成数据不一致等未知情况 3. C1如果先执行完毕，则会释放C2的锁，此时可能导致另外一个C3进程获取到了锁\n\n### 使用sentx将值设为时间戳，通过lua脚本进行cas比较和删除操作\n\n    V3.0\n    tryLock(){  \n        SETNX Key UnixTimestamp Seconds\n    }\n    release(){  \n        EVAL(\n          //LuaScript\n          if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n              return redis.call(\"del\",KEYS[1])\n          else\n              return 0\n          end\n        )\n    }\n\n这个方案通过指定Value为时间戳，并在释放锁的时候检查锁的Value是否为获取锁的Value，避免了V2.0版本中提到的C1释放了C2持有的锁的问题；另外在释放锁的时候因为涉及到多个Redis操作，并且考虑到Check And Set 模型的并发问题，所以使用Lua脚本来避免并发问题。\n\n如果在并发极高的场景下，比如抢红包场景，可能存在UnixTimestamp重复问题，另外由于不能保证分布式环境下的物理时钟一致性，也可能存在UnixTimestamp重复问题，只不过极少情况下会遇到。\n\n### 分布式Redis锁：Redlock\n\nredlock的思想就是要求一个节点获取集群中N/2 + 1个节点\n上的锁才算加锁成功。\n\n\n### 总结\n\n不论是基于SETNX版本的Redis单实例分布式锁，还是Redlock分布式锁，都是为了保证下特性\n\n    1. 安全性：在同一时间不允许多个Client同时持有锁\n    2. 活性\n\n    死锁：锁最终应该能够被释放，即使Client端crash或者出现网络分区（通常基于超时机制）\n    容错性：只要超过半数Redis节点可用，锁都能被正确获取和释放\n\n## 分布式方案\n\n1 主从复制，优点是备份简易使用。缺点是不能故障切换，并且不易扩展。\n\n2 使用sentinel哨兵工具监控和实现自动切换。\n\n3 codis集群方案\n\n首先codis使用代理的方式隐藏底层redis，这样可以完美融合以前的代码，不需要更改redis访问操作。\n\n然后codis使用了zookeeper进行监控和自动切换。同时使用了redis-group的概念，保证一个group里是一主多从的主从模型，基于此来进行切换。\n\n4 redis cluster集群\n\n该集群是一个p2p方式部署的集群\n\nRedis cluster是一个去中心化、多实例Redis间进行数据共享的集群。\n\n每个节点上都保存着其他节点的信息，通过任一节点可以访问正常工作的节点数据，因为每台机器上的保留着完整的分片信息，某些机器不正常工作不影响整体集群的工作。并且每一台redis主机都会配备slave，通过sentinel自动切换。\n\n## redis事务\n\n事务\nMULTI 、 EXEC 、 DISCARD 和 WATCH 是 Redis 事务相关的命令。事务可以一次执行多个命令， 并且带有以下两个重要的保证：\n\n事务是一个单独的隔离操作：事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中，不会被其他客户端发送来的命令请求所打断。\n\n事务是一个原子操作：事务中的命令要么全部被执行，要么全部都不执行。\n\nredis事务有一个特点，那就是在2.6以前，事务的一系列操作，如果有的成功有的失败，仍然会提交成功的那部分，后来改为全部不提交了。\n\n但是Redis事务不支持回滚，提交以后不能执行回滚操作。\n\n    为什么 Redis 不支持回滚（roll back）\n    如果你有使用关系式数据库的经验， 那么 “Redis 在事务失败时不进行回滚，而是继续执行余下的命令”这种做法可能会让你觉得有点奇怪。\n    \n    以下是这种做法的优点：\n    \n    Redis 命令只会因为错误的语法而失败（并且这些问题不能在入队时发现），或是命令用在了错误类型的键上面：这也就是说，从实用性的角度来说，失败的命令是由编程错误造成的，而这些错误应该在开发的过程中被发现，而不应该出现在生产环境中。\n    因为不需要对回滚进行支持，所以 Redis 的内部可以保持简单且快速。\n\n\n\n\n### redis脚本事务\n\nRedis 脚本和事务\n从定义上来说， Redis 中的脚本本身就是一种事务， 所以任何在事务里可以完成的事， 在脚本里面也能完成。 并且一般来说， 使用脚本要来得更简单，并且速度更快。\n\n因为脚本功能是 Redis 2.6 才引入的， 而事务功能则更早之前就存在了， 所以 Redis 才会同时存在两种处理事务的方法。\n\nredis事务的ACID特性\n在传统的关系型数据库中,尝尝用ACID特质来检测事务功能的可靠性和安全性。\n在redis中事务总是具有原子性(Atomicity),一致性(Consistency)和隔离性(Isolation),并且当redis运行在某种特定的持久化\n模式下,事务也具有耐久性(Durability).\n\n①原子性\n\n事务具有原子性指的是,数据库将事务中的多个操作当作一个整体来执行,服务器要么就执行事务中的所有操作,要么就一个操作也不执行。\n但是对于redis的事务功能来说,事务队列中的命令要么就全部执行,要么就一个都不执行,因此redis的事务是具有原子性的。\n\n②一致性\n\n    事务具有一致性指的是,如果数据库在执行事务之前是一致的,那么在事务执行之后,无论事务是否执行成功,数据库也应该仍然一致的。\n    ”一致“指的是数据符合数据库本身的定义和要求,没有包含非法或者无效的错误数据。redis通过谨慎的错误检测和简单的设计来保证事务一致性。\n\n③隔离性\n\n    事务的隔离性指的是,即使数据库中有多个事务并发在执行,各个事务之间也不会互相影响,并且在并发状态下执行的事务和串行执行的事务产生的结果完全\n    相同。\n    因为redis使用单线程的方式来执行事务(以及事务队列中的命令),并且服务器保证,在执行事务期间不会对事物进行中断,因此,redis的事务总是以串行\n    的方式运行的,并且事务也总是具有隔离性的\n\n④持久性\n\n    事务的耐久性指的是,当一个事务执行完毕时,执行这个事务所得的结果已经被保持到永久存储介质里面。\n    因为redis事务不过是简单的用队列包裹起来一组redis命令,redis并没有为事务提供任何额外的持久化功能,所以redis事务的耐久性由redis使用的模式\n    决定\n"
  },
  {
    "path": "docs/cache/探索Redis设计与实现开篇：什么是Redis.md",
    "content": "# 目录\n\n  * [redis 学习笔记](#redis-学习笔记)\n    * [redis 是什么?](#redis-是什么)\n    * [Redis 数据结构](#redis-数据结构)\n    * [Redis 数据类型](#redis-数据类型)\n    * [过期时间](#过期时间)\n    * [应用场景](#应用场景)\n    * [内存优化](#内存优化)\n  * [天下无难试之Redis面试刁难大全](#天下无难试之redis面试刁难大全)\n\n\n[toc]\n\n本文转自互联网\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章首发于我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《探索Redis设计与实现》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，Redis基本的使用方法，Redis的基本数据结构，以及一些进阶的使用方法，同时也需要进一步了解Redis的底层数据结构，再接着，还会带来Redis主从复制、集群、分布式锁等方面的相关内容，以及作为缓存的一些使用方法和注意事项，以便让你更完整地了解整个Redis相关的技术体系，形成自己的知识框架。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n## redis 学习笔记\n\n> 这篇 redis 学习笔记主要介绍 redis 的数据结构和数据类型，并讨论数据结构的选择以及应用场景的优化。\n\n### redis 是什么?\n\n> Redis是一种面向“键/值”对类型数据的分布式NoSQL数据库系统，特点是高性能，持久存储，适应高并发的应用场景。\n\n### Redis 数据结构\n\n*   动态字符串 (Sds)\n*   双端列表 (LINKEDLIST)\n*   字典\n*   跳跃表 (SKIPLIST)\n*   整数集合 (INTSET)\n*   压缩列表 (ZIPLIST)\n\nHUGOMORE42\n\n[动态字符串](https://link.juejin.im/?target=http%3A%2F%2Forigin.redisbook.com%2Finternal-datastruct%2Fsds.html)\n\nSds (Simple Dynamic String,简单动态字符串)是 Redis 底层所使用的字符串表示,它被用 在几乎所有的 Redis 模块中\n\nRedis 是一个键值对数据库(key-value DB),数据库的值可以是字符串、集合、列表等多种类 型的对象,而数据库的键则总是字符串对象\n\n在 Redis 中, 一个字符串对象除了可以保存字符串值之外,还可以保存 long 类型的值当字符串对象保存的是字符串时,它包含的才是 sds 值,否则的话,它就 是一个 long 类型的值\n\n动态字符串主要有两个作用:\n\n1.  实现字符串对象(StringObject)\n2.  在 Redis 程序内部用作 char * 类型的替代品\n\n[双端列表](https://link.juejin.im/?target=http%3A%2F%2Forigin.redisbook.com%2Finternal-datastruct%2Fadlist.html)\n\n双端链表还是 Redis 列表类型的底层实现之一，当对列表类型的键进行操作——比如执行 RPUSH 、LPOP 或 LLEN 等命令时,程序在底层操作的可能就是双端链表\n\n双端链表主要有两个作用:\n\n*   作为 Redis 列表类型的底层实现之一;\n*   作为通用数据结构,被其他功能模块所使用;\n\n[字典](https://link.juejin.im/?target=http%3A%2F%2Forigin.redisbook.com%2Finternal-datastruct%2Fdict.html)\n\n字典(dictionary),又名映射(map)或关联数组(associative array), 它是一种抽象数据结 构,由一集键值对(key-value pairs)组成,各个键值对的键各不相同,程序可以将新的键值对 添加到字典中,或者基于键进行查找、更新或删除等操作\n\n字典的应用\n\n1.  实现数据库键空间(key space);\n2.  用作 Hash 类型键的其中一种底层实现;\n\n> Redis 是一个键值对数据库,数据库中的键值对就由字典保存:每个数据库都有一个与之相对应的字典,这个字典被称之为键空间(key space)。\n\nRedis 的 Hash 类型键使用**字典和压缩列表**两种数据结构作为底层实现\n\n[跳跃表](https://link.juejin.im/?target=http%3A%2F%2Forigin.redisbook.com%2Finternal-datastruct%2Fskiplist.html)\n\n跳跃表(skiplist)是一种随机化的数据,由 William Pugh 在论文《Skip lists: a probabilistic alternative to balanced trees》中提出,这种数据结构以有序的方式在层次化的链表中保存元素,它的效率可以和平衡树媲美——查找、删除、添加等操作都可以在对数期望时间下完成, 并且比起平衡树来说,跳跃表的实现要简单直观得多\n\n和字典、链表或者字符串这几种在 Redis 中大量使用的数据结构不同,跳跃表在 Redis 的唯一作用,就是实现有序集数据类型\n跳跃表将指向有序集的 score 值和 member 域的指针作为元素,并以 score 值为索引,对有序集元素进行排序。\n\n[整数集合](https://link.juejin.im/?target=http%3A%2F%2Forigin.redisbook.com%2Fcompress-datastruct%2Fintset.html)\n\n整数集合(intset)用于有序、无重复地保存多个整数值,它会根据元素的值,自动选择该用什么长度的整数类型来保存元素\n\nIntset 是集合键的底层实现之一,如果一个集合:\n\n1.  只保存着整数元素;\n2.  元素的数量不多;\n    那么 Redis 就会使用 intset 来保存集合元素。\n\n[压缩列表](https://link.juejin.im/?target=http%3A%2F%2Forigin.redisbook.com%2Fcompress-datastruct%2Fziplist.html)\n\nZiplist 是由一系列特殊编码的内存块构成的列表,一个 ziplist 可以包含多个节点(entry),每个节点可以保存一个长度受限的字符数组(不以 \\0 结尾的 char 数组)或者整数\n\n### Redis 数据类型\n\n[RedisObject](https://link.juejin.im/?target=http%3A%2F%2Forigin.redisbook.com%2Fdatatype%2Fobject.html%23redisobject-redis)\n\nredisObject 是 Redis 类型系统的核心,数据库中的每个键、值,以及 Redis 本身处理的参数,都表示为这种数据类型\n\nredisObject 的定义位于 redis.h :\n\n```\n/** Redis 对象*/typedef struct redisObject {    // 类型    unsigned type:4;    // 对齐位    unsigned notused:2;    // 编码方式    unsigned encoding:4;    // LRU 时间(相对于 server.lruclock)    unsigned lru:22;    // 引用计数    int refcount;    // 指向对象的值    void *ptr;} robj;\n```\n\ntype 、encoding 和 ptr 是最重要的三个属性。\n\ntype 记录了对象所保存的值的类型,它的值可能是以下常量的其中一个\n\n```\n/** 对象类型*/#define REDIS_STRING 0 // 字符串#define REDIS_LIST 1   // 列表#define REDIS_SET 2    // 集合#define REDIS_ZSET 3   // 有序集#define REDIS_HASH 4   // 哈希表\n```\n\nencoding 记录了对象所保存的值的编码,它的值可能是以下常量的其中一个\n\n```\n/** 对象编码*/#define REDIS_ENCODING_RAW 0    // 编码为字符串#define REDIS_ENCODING_INT 1    // 编码为整数#define REDIS_ENCODING_HT 2     // 编码为哈希表#define REDIS_ENCODING_ZIPMAP 3 // 编码为 zipmap(2.6 后不再使用)#define REDIS_ENCODING_LINKEDLIST 4 // 编码为双端链表#define REDIS_ENCODING_ZIPLIST 5    // 编码为压缩列表#define REDIS_ENCODING_INTSET 6     // 编码为整数集合#define REDIS_ENCODING_SKIPLIST 7    // 编码为跳跃表\n```\n\nptr 是一个指针,指向实际保存值的数据结构,这个数据结构由 type 属性和 encoding 属性决定。\n\n当执行一个处理数据类型的命令时,Redis 执行以下步骤:\n\n1.  根据给定key,在数据库字典中查找和它像对应的redisObject,如果没找到,就返回 NULL 。\n2.  检查redisObject的type属性和执行命令所需的类型是否相符,如果不相符,返回类 型错误。\n3.  根据redisObject的encoding属性所指定的编码,选择合适的操作函数来处理底层的 数据结构。\n4.  返回数据结构的操作结果作为命令的返回值。\n\n[字符串](https://link.juejin.im/?target=http%3A%2F%2Fredisdoc.com%2Fstring%2Findex.html)\n\nREDIS_STRING (字符串)是 Redis 使用得最为广泛的数据类型,它除了是 SET 、GET 等命令 的操作对象之外,数据库中的所有键,以及执行命令时提供给 Redis 的参数,都是用这种类型 保存的。\n\n字符串类型分别使用 REDIS_ENCODING_INT 和 REDIS_ENCODING_RAW 两种编码\n\n> 只有能表示为 long 类型的值,才会以整数的形式保存,其他类型 的整数、小数和字符串,都是用 sdshdr 结构来保存\n\n[哈希表](https://link.juejin.im/?target=http%3A%2F%2Fredisdoc.com%2Fhash%2Findex.html)\n\nREDIS_HASH (哈希表)是HSET 、HLEN 等命令的操作对象\n\n它使用 REDIS_ENCODING_ZIPLIST和REDIS_ENCODING_HT 两种编码方式\n\nRedis 中每个hash可以存储232-1键值对（40多亿）\n\n[列表](https://link.juejin.im/?target=http%3A%2F%2Fredisdoc.com%2Flist%2Findex.html)\n\nREDIS_LIST(列表)是LPUSH 、LRANGE等命令的操作对象\n\n它使用 REDIS_ENCODING_ZIPLIST和REDIS_ENCODING_LINKEDLIST 这两种方式编码\n\n一个列表最多可以包含232-1 个元素(4294967295, 每个列表超过40亿个元素)。\n\n[集合](https://link.juejin.im/?target=http%3A%2F%2Fredisdoc.com%2Fset%2Findex.html)\n\nREDIS_SET (集合) 是 SADD 、 SRANDMEMBER 等命令的操作对象\n\n它使用 REDIS_ENCODING_INTSET 和 REDIS_ENCODING_HT 两种方式编码\n\nRedis 中集合是通过哈希表实现的，所以添加，删除，查找的复杂度都是O(1)。\n\n集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)\n\n[有序集](https://link.juejin.im/?target=http%3A%2F%2Fredisdoc.com%2Fsorted_set%2Findex.html)\n\nREDIS_ZSET (有序集)是ZADD 、ZCOUNT 等命令的操作对象\n\n它使用 REDIS_ENCODING_ZIPLIST和REDIS_ENCODING_SKIPLIST 两种方式编码\n\n不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。\n\n有序集合的成员是唯一的,但分数(score)却可以重复。\n\n集合是通过哈希表实现的，所以添加，删除，查找的复杂度都是O(1)。 集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)\n\nRedis各种数据类型_以及它们的编码方式\n\n![Redis各种数据类型_以及它们的编码方式](https://user-gold-cdn.xitu.io/2017/9/17/2c71cff03efc96d2280d12602cc2aa92?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)Redis各种数据类型_以及它们的编码方式\n\n### 过期时间\n\n在数据库中,所有键的过期时间都被保存在 redisDb 结构的 expires 字典里:\n\n```\ntypedef struct redisDb {    // ...    dict *expires;    // ...} redisDb;\n```\n\nexpires 字典的键是一个指向 dict 字典(键空间)里某个键的指针,而字典的值则是键所指 向的数据库键的到期时间,这个值以 long long 类型表示\n\n过期时间设置\n\nRedis 有四个命令可以设置键的生存时间(可以存活多久)和过期时间(什么时候到期):\n\n*   EXPIRE 以秒为单位设置键的生存时间;\n*   PEXPIRE 以毫秒为单位设置键的生存时间;\n*   EXPIREAT 以秒为单位,设置键的过期 UNIX 时间戳;\n*   PEXPIREAT 以毫秒为单位,设置键的过期 UNIX 时间戳。\n\n> 虽然有那么多种不同单位和不同形式的设置方式,但是 expires 字典的值只保存“以毫秒为单位的过期 UNIX 时间戳” ,这就是说,通过进行转换,所有命令的效果最后都和 PEXPIREAT 命令的效果一样。\n\n**如果一个键是过期的,那它什么时候会被删除?**\n\n下边是参考答案\n\n1.  定时删除:在设置键的过期时间时,创建一个定时事件,当过期时间到达时,由事件处理 器自动执行键的删除操作。\n2.  惰性删除:放任键过期不管,但是在每次从 dict 字典中取出键值时,要检查键是否过 期,如果过期的话,就删除它,并返回空;如果没过期,就返回键值。\n3.  定期删除:每隔一段时间,对expires字典进行检查,删除里面的过期键\n\nRedis 使用的过期键删除策略是惰性删除加上定期删除\n\n### 应用场景\n\n*   缓存\n*   队列\n*   需要精准设定过期时间的应用\n\n> 比如你可以把上面说到的sorted set的score值设置成过期时间的时间戳，那么就可以简单地通过过期时间排序，定时清除过期数据了，不仅是清除Redis中的过期数据，你完全可以把Redis里这个过期时间当成是对数据库中数据的索引，用Redis来找出哪些数据需要过期删除，然后再精准地从数据库中删除相应的记录\n\n*   排行榜应用，取TOP N操作\n\n    > 这个需求与上面需求的不同之处在于，前面操作以时间为权重，这个是以某个条件为权重，比如按顶的次数排序，这时候就需要我们的sorted set出马了，将你要排序的值设置成sorted set的score，将具体的数据设置成相应的value，每次只需要执行一条ZADD命令即可\n\n*   统计页面访问次数\n\n> 使用 incr 命令 定时使用 getset 命令 读取数据 并设置新的值 0\n\n*   使用set 设置标签\n\n例如假设我们的话题D 1000被加了三个标签tag 1,2,5和77，就可以设置下面两个集合：\n\n```\n$ redis-cli sadd topics:1000:tags 1(integer) 1$ redis-cli sadd topics:1000:tags 2(integer) 1$ redis-cli sadd topics:1000:tags 5(integer) 1$ redis-cli sadd topics:1000:tags 77(integer) 1$ redis-cli sadd tag:1:objects 1000(integer) 1$ redis-cli sadd tag:2:objects 1000(integer) 1$ redis-cli sadd tag:5:objects 1000(integer) 1$ redis-cli sadd tag:77:objects 1000(integer) 1\n```\n\n要获取一个对象的所有标签：\n\n```\n$ redis-cli smembers topics:1000:tags1. 52. 13. 774. 2\n```\n\n获得一份同时拥有标签1, 2,10和27的对象列表。\n这可以用SINTER命令来做，他可以在不同集合之间取出交集\n\n### 内存优化\n\n`问题`: Instagram的照片数量已经达到3亿，而在Instagram里，我们需要知道每一张照片的作者是谁，下面就是Instagram团队如何使用Redis来解决这个问题并进行内存优化的。\n\n具体方法，参考下边这篇文章：[节约内存：Instagram的Redis实践](https://link.juejin.im/?target=http%3A%2F%2Fblog.nosqlfan.com%2Fhtml%2F3379.html)。\n\n## 天下无难试之Redis面试刁难大全\n\nRedis在互联网技术存储方面使用如此广泛，几乎所有的后端技术面试官都要在Redis的使用和原理方面对小伙伴们进行各种刁难。作为一名在互联网技术行业打击过成百上千名【请允许我夸张一下】的资深技术面试官，看过了无数落寞的身影失望的离开，略感愧疚，故献上此文，希望各位读者以后面试势如破竹，永无失败！    \n\nRedis有哪些数据结构？\n\n字符串String、字典Hash、列表List、集合Set、有序集合SortedSet。\n\n如果你是Redis中高级用户，还需要加上下面几种数据结构HyperLogLog、Geo、Pub/Sub。\n\n如果你说还玩过Redis Module，像BloomFilter，RedisSearch，Redis-ML，面试官得眼睛就开始发亮了。\n\n使用过Redis分布式锁么，它是什么回事？\n\n先拿setnx来争抢锁，抢到之后，再用expire给锁加一个过期时间防止锁忘记了释放。\n\n这时候对方会告诉你说你回答得不错，然后接着问如果在setnx之后执行expire之前进程意外crash或者要重启维护了，那会怎么样？\n\n这时候你要给予惊讶的反馈：唉，是喔，这个锁就永远得不到释放了。紧接着你需要抓一抓自己得脑袋，故作思考片刻，好像接下来的结果是你主动思考出来的，然后回答：我记得set指令有非常复杂的参数，这个应该是可以同时把setnx和expire合成一条指令来用的！对方这时会显露笑容，心里开始默念：摁，这小子还不错。\n\n假如Redis里面有1亿个key，其中有10w个key是以某个固定的已知的前缀开头的，如果将它们全部找出来？\n\n使用keys指令可以扫出指定模式的key列表。\n\n对方接着追问：如果这个redis正在给线上的业务提供服务，那使用keys指令会有什么问题？\n\n这个时候你要回答redis关键的一个特性：redis的单线程的。keys指令会导致线程阻塞一段时间，线上服务会停顿，直到指令执行完毕，服务才能恢复。这个时候可以使用scan指令，scan指令可以无阻塞的提取出指定模式的key列表，但是会有一定的重复概率，在客户端做一次去重就可以了，但是整体所花费的时间会比直接用keys指令长。\n\n使用过Redis做异步队列么，你是怎么用的？\n\n一般使用list结构作为队列，rpush生产消息，lpop消费消息。当lpop没有消息的时候，要适当sleep一会再重试。\n\n如果对方追问可不可以不用sleep呢？list还有个指令叫blpop，在没有消息的时候，它会阻塞住直到消息到来。\n\n如果对方追问能不能生产一次消费多次呢？使用pub/sub主题订阅者模式，可以实现1:N的消息队列。\n\n如果对方追问pub/sub有什么缺点？在消费者下线的情况下，生产的消息会丢失，得使用专业的消息队列如rabbitmq等。\n\n如果对方追问redis如何实现延时队列？我估计现在你很想把面试官一棒打死如果你手上有一根棒球棍的话，怎么问的这么详细。但是你很克制，然后神态自若的回答道：使用sortedset，拿时间戳作为score，消息内容作为key调用zadd来生产消息，消费者用zrangebyscore指令获取N秒之前的数据轮询进行处理。\n\n到这里，面试官暗地里已经对你竖起了大拇指。但是他不知道的是此刻你却竖起了中指，在椅子背后。\n\n如果有大量的key需要设置同一时间过期，一般需要注意什么？\n\n如果大量的key过期时间设置的过于集中，到过期的那个时间点，redis可能会出现短暂的卡顿现象。一般需要在时间上加一个随机值，使得过期时间分散一些。\n\nRedis如何做持久化的？\n\nbgsave做镜像全量持久化，aof做增量持久化。因为bgsave会耗费较长时间，不够实时，在停机的时候会导致大量丢失数据，所以需要aof来配合使用。在redis实例重启时，会使用bgsave持久化文件重新构建内存，再使用aof重放近期的操作指令来实现完整恢复重启之前的状态。\n\n对方追问那如果突然机器掉电会怎样？取决于aof日志sync属性的配置，如果不要求性能，在每条写指令时都sync一下磁盘，就不会丢失数据。但是在高性能的要求下每次都sync是不现实的，一般都使用定时sync，比如1s1次，这个时候最多就会丢失1s的数据。\n\n对方追问bgsave的原理是什么？你给出两个词汇就可以了，fork和cow。fork是指redis通过创建子进程来进行bgsave操作，cow指的是copy on write，子进程创建后，父子进程共享数据段，父进程继续提供读写服务，写脏的页面数据会逐渐和子进程分离开来。\n\nPipeline有什么好处，为什么要用pipeline？\n\n可以将多次IO往返的时间缩减为一次，前提是pipeline执行的指令之间没有因果相关性。使用redis-benchmark进行压测的时候可以发现影响redis的QPS峰值的一个重要因素是pipeline批次指令的数目。\n\nRedis的同步机制了解么？\n\nRedis可以使用主从同步，从从同步。第一次同步时，主节点做一次bgsave，并同时将后续修改操作记录到内存buffer，待完成后将rdb文件全量同步到复制节点，复制节点接受完成后将rdb镜像加载到内存。加载完成后，再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程。\n\n是否使用过Redis集群，集群的原理是什么？\n\nRedis Sentinal着眼于高可用，在master宕机时会自动将slave提升为master，继续提供服务。\n\nRedis Cluster着眼于扩展性，在单个redis内存不足时，使用Cluster进行分片存储。\n"
  },
  {
    "path": "docs/cache/探索Redis设计与实现：Redis 的基础数据结构概览.md",
    "content": "# 目录\n\n  * [SDS](#sds)\n  * [链表](#链表)\n  * [字典](#字典)\n\n\n[toc]\n\n本文转自https://www.xilidou.com/2018/03/22/redis-event/  \n\n作者：犀利豆\n\n本文将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，Redis基本的使用方法，Redis的基本数据结构，以及一些进阶的使用方法，同时也需要进一步了解Redis的底层数据结构，再接着，还会带来Redis主从复制、集群、分布式锁等方面的相关内容，以及作为缓存的一些使用方法和注意事项，以便让你更完整地了解整个Redis相关的技术体系，形成自己的知识框架。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->  \n\n\n这周开始学习 Redis，看看Redis是怎么实现的。所以会写一系列关于 Redis的文章。这篇文章关于 Redis 的基础数据。阅读这篇文章你可以了解：\n\n*   动态字符串（SDS）\n*   链表\n*   字典\n\n三个数据结构 Redis 是怎么实现的。\n\n\n\n## SDS\n\nSDS （Simple Dynamic String）是 Redis 最基础的数据结构。直译过来就是”简单的动态字符串“。Redis 自己实现了一个动态的字符串，而不是直接使用了 C 语言中的字符串。\n\nsds 的数据结构：\n\n````\n    struct sdshdr {// buf 中已占用空间的长度 int len;// buf 中剩余可用空间的长度 int free;// 数据空间   \n    char buf[];  \n            }  \n\n````\n\n所以一个 SDS 的就如下图：\n\n![sds](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/2019-04-25-022158.jpg)\n\n所以我们看到，sds 包含3个参数。buf 的长度 len，buf 的剩余长度，以及buf。\n\n为什么这么设计呢？\n\n*   可以直接获取字符串长度。  \n    C 语言中，获取字符串的长度需要用指针遍历字符串，时间复杂度为 O(n)，而 SDS 的长度，直接从len 获取复杂度为 O(1)。\n\n*   杜绝缓冲区溢出。  \n    由于C 语言不记录字符串长度，如果增加一个字符传的长度，如果没有注意就可能溢出，覆盖了紧挨着这个字符的数据。对于SDS 而言增加字符串长度需要验证 free的长度，如果free 不够就会扩容整个 buf，防止溢出。\n\n*   减少修改字符串长度时造成的内存再次分配。  \n    redis 作为高性能的内存数据库，需要较高的相应速度。字符串也很大概率的频繁修改。 SDS 通过未使用空间这个参数，将字符串的长度和底层buf的长度之间的额关系解除了。buf的长度也不是字符串的长度。基于这个分设计 SDS 实现了空间的预分配和惰性释放。\n\n    1.  预分配  \n        如果对 SDS 修改后，如果 len 小于 1MB 那 len = 2 * len + 1byte。 这个 1 是用于保存空字节。  \n        如果 SDS 修改后 len 大于 1MB 那么 len = 1MB + len + 1byte。\n    2.  惰性释放  \n        如果缩短 SDS 的字符串长度，redis并不是马上减少 SDS 所占内存。只是增加 free 的长度。同时向外提供 API 。真正需要释放的时候，才去重新缩小 SDS 所占的内存\n*   二进制安全。  \n    C 语言中的字符串是以 ”\\0“ 作为字符串的结束标记。而 SDS 是使用 len 的长度来标记字符串的结束。所以SDS 可以存储字符串之外的任意二进制流。因为有可能有的二进制流在流中就包含了”\\0“造成字符串提前结束。也就是说 SDS 不依赖 “\\0” 作为结束的依据。\n\n*   兼容C语言  \n    SDS 按照惯例使用 ”\\0“ 作为结尾的管理。部分普通C 语言的字符串 API 也可以使用。\n\n## 链表\n\nC语言中并没有链表这个数据结构所以 Redis 自己实现了一个。Redis 中的链表是：\n````\n    typedef struct listNode {// 前置节点 struct listNode *prev;// 后置节点 struct listNode *next;// 节点的值 void *value;} listNode;  \n````\n非常典型的双向链表的数据结构。\n\n同时为双向链表提供了如下操作的函数：\n````\n\n    /* * 双端链表迭代器 */typedef struct listIter {// 当前迭代到的节点 listNode *next;// 迭代的方向 int direction;} listIter;    /* * 双端链表结构   \n      \n    */typedef struct list {   \n// 表头节点 listNode *head;// 表尾节点 listNode *tail;// 节点值复制函数 void *(*dup)(void *ptr);// 节点值释放函数 void (*free)(void *ptr);// 节点值对比函数 int (*match)(void *ptr, void *key);// 链表所包含的节点数量 unsigned long len;\n} list;\n````\n链表的结构比较简单，数据结构如下：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/2019-04-25-22159.jpg)\n\n总结一下性质：\n\n*   双向链表，某个节点寻找上一个或者下一个节点时间复杂度 O(1)。\n*   list 记录了 head 和 tail，寻找 head 和 tail 的时间复杂度为 O(1)。\n*   获取链表的长度 len 时间复杂度 O(1)。\n\n## 字典\n\n字典数据结构极其类似 java 中的 Hashmap。\n\nRedis的字典由三个基础的数据结构组成。最底层的单位是哈希表节点。结构如下：\n````\n    typedef struct dictEntry {        // 键  \n        void *key;    // 值  \n        union {            void *val;            uint64_t u64;            int64_t s64;        } v;    // 指向下个哈希表节点，形成链表  \n        struct dictEntry *next;        } dictEntry;  \n````\n实际上哈希表节点就是一个单项列表的节点。保存了一下下一个节点的指针。 key 就是节点的键，v是这个节点的值。这个 v 既可以是一个指针，也可以是一个`uint64_t`或者`int64_t`整数。*next 指向下一个节点。\n\n通过一个哈希表的数组把各个节点链接起来：  \n````\ntypedef struct dictht {\n\n        // 哈希表数组  \n        dictEntry **table;    // 哈希表大小  \n        unsigned long size;        // 哈希表大小掩码，用于计算索引值  \n        // 总是等于 size - 1        unsigned long sizemask;    // 该哈希表已有节点的数量  \n        unsigned long used;        \n}\n````\n通过图示我们观察：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/2019-04-25-022159.jpg)\n\n实际上，如果对java 的基本数据结构了解的同学就会发现，这个数据结构和 java 中的 HashMap 是很类似的，就是数组加链表的结构。\n\n字典的数据结构：\n````\n    typedef struct dict {    // 类型特定函数  \n        dictType *type;    // 私有数据  \n        void *privdata;    // 哈希表  \n        dictht ht[2];    // rehash 索引  \n        // 当 rehash 不在进行时，值为 -1        int rehashidx; /* rehashing not in progress if rehashidx == -1 */    // 目前正在运行的安全迭代器的数量  \n        int iterators; /* number of iterators currently running */        } dict;  \n````\n其中的dictType 是一组方法，代码如下：\n\n````\n\n/*     \n * 字典类型特定函数  \n */    \ntypedef struct dictType {    // 计算哈希值的函数  \n    unsigned int (*hashFunction)(const void *key);    // 复制键的函数  \n    void *(*keyDup)(void *privdata, const void *key);    // 复制值的函数  \n    void *(*valDup)(void *privdata, const void *obj);    // 对比键的函数  \n    int (*keyCompare)(void *privdata, const void *key1, const void *key2);    // 销毁键的函数  \n    void (*keyDestructor)(void *privdata, void *key);        // 销毁值的函数  \n    void (*valDestructor)(void *privdata, void *obj);       \n   \n} \n````\n字典的数据结构如下图：\n\n![dict](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/2019-04-25-022200.jpg)\n\n这里我们可以看到一个dict 拥有两个 dictht。一般来说只使用 ht[0],当扩容的时候发生了rehash的时候，ht[1]才会被使用。\n\n当我们观察或者研究一个hash结构的时候偶我们首先要考虑的这个 dict 如何插入一个数据？\n\n我们梳理一下插入数据的逻辑。\n\n*   计算Key 的 hash 值。找到 hash 映射到 table 数组的位置。\n\n*   如果数据已经有一个 key 存在了。那就意味着发生了 hash 碰撞。新加入的节点，就会作为链表的一个节点接到之前节点的 next 指针上。\n\n*   如果 key 发生了多次碰撞，造成链表的长度越来越长。会使得字典的查询速度下降。为了维持正常的负载。Redis 会对 字典进行 rehash 操作。来增加 table 数组的长度。所以我们要着重了解一下 Redis 的 rehash。步骤如下：\n\n    1.  根据ht[0] 的数据和操作的类型（扩大或缩小），分配 ht[1] 的大小。\n    2.  将 ht[0] 的数据 rehash 到 ht[1] 上。\n    3.  rehash 完成以后，将ht[1] 设置为 ht[0]，生成一个新的ht[1]备用。\n*   渐进式的 rehash 。  \n    其实如果字典的 key 数量很大，达到千万级以上，rehash 就会是一个相对较长的时间。所以为了字典能够在 rehash 的时候能够继续提供服务。Redis 提供了一个渐进式的 rehash 实现，rehash的步骤如下：\n\n    1.  分配 ht[1] 的空间，让字典同时持有 ht[1] 和 ht[0]。\n    2.  在字典中维护一个 rehashidx，设置为 0 ，表示字典正在 rehash。\n    3.  在rehash期间，每次对字典的操作除了进行指定的操作以外，都会根据 ht[0] 在 rehashidx 上对应的键值对 rehash 到 ht[1]上。\n    4.  随着操作进行， ht[0] 的数据就会全部 rehash 到 ht[1] 。设置ht[0] 的 rehashidx 为 -1，渐进的 rehash 结束。\n\n这样保证数据能够平滑的进行 rehash。防止 rehash 时间过久阻塞线程。\n\n*   在进行 rehash 的过程中，如果进行了 delete 和 update 等操作，会在两个哈希表上进行。如果是 find 的话优先在ht[0] 上进行，如果没有找到，再去 ht[1] 中查找。如果是 insert 的话那就只会在 ht[1]中插入数据。这样就会保证了 ht[1] 的数据只增不减，ht[0]的数据只减不增。\n"
  },
  {
    "path": "docs/cache/探索Redis设计与实现：Redis事务浅析与ACID特性介绍.md",
    "content": "# 目录\n\n* [事务](#事务)\n  * [用法](#用法)\n  * [事务中的错误](#事务中的错误)\n  * [为什么 Redis 不支持回滚（roll back）](#为什么-redis-不支持回滚（roll-back）)\n  * [放弃事务](#放弃事务)\n  * [使用 check-and-set 操作实现乐观锁](#使用-check-and-set-操作实现乐观锁)\n  * [了解`WATCH`](#了解`watch`)\n    * [使用 WATCH 实现 ZPOP](#使用-watch-实现-zpop)\n  * [Redis 脚本和事务](#redis-脚本和事务)\n* [redis事务的ACID特性](#redis事务的acid特性)\n\n\n[toc]\n\n本文转自互联网\n本文将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，Redis基本的使用方法，Redis的基本数据结构，以及一些进阶的使用方法，同时也需要进一步了解Redis的底层数据结构，再接着，还会带来Redis主从复制、集群、分布式锁等方面的相关内容，以及作为缓存的一些使用方法和注意事项，以便让你更完整地了解整个Redis相关的技术体系，形成自己的知识框架。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n\n# 事务\n\n[MULTI](http://www.redis.cn/commands/multi.html)、[EXEC](http://www.redis.cn/commands/exec.html)、[DISCARD](http://www.redis.cn/commands/discard.html)和[WATCH](http://www.redis.cn/commands/watch.html)是 Redis 事务相关的命令。事务可以一次执行多个命令， 并且带有以下两个重要的保证：\n\n*   事务是一个单独的隔离操作：事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中，不会被其他客户端发送来的命令请求所打断。\n\n*   事务是一个原子操作：事务中的命令要么全部被执行，要么全部都不执行。\n\n[EXEC](http://www.redis.cn/commands/exec.html)命令负责触发并执行事务中的所有命令：\n\n*   如果客户端在使用[MULTI](http://www.redis.cn/commands/multi.html)开启了一个事务之后，却因为断线而没有成功执行[EXEC](http://www.redis.cn/commands/exec.html)，那么事务中的所有命令都不会被执行。\n*   另一方面，如果客户端成功在开启事务之后执行[EXEC](http://www.redis.cn/commands/exec.html)，那么事务中的所有命令都会被执行。\n\n当使用 AOF 方式做持久化的时候， Redis 会使用单个 write(2) 命令将事务写入到磁盘中。\n\n然而，如果 Redis 服务器因为某些原因被管理员杀死，或者遇上某种硬件故障，那么可能只有部分事务命令会被成功写入到磁盘中。\n\n如果 Redis 在重新启动时发现 AOF 文件出了这样的问题，那么它会退出，并汇报一个错误。\n\n使用`redis-check-aof`程序可以修复这一问题：它会移除 AOF 文件中不完整事务的信息，确保服务器可以顺利启动。\n\n从 2.2 版本开始，Redis 还可以通过乐观锁（optimistic lock）实现 CAS （check-and-set）操作，具体信息请参考文档的后半部分。\n\n## 用法\n\n[MULTI](http://www.redis.cn/commands/multi.html)命令用于开启一个事务，它总是返回`OK`。[MULTI](http://www.redis.cn/commands/multi.html)执行之后， 客户端可以继续向服务器发送任意多条命令， 这些命令不会立即被执行， 而是被放到一个队列中， 当[EXEC](http://www.redis.cn/commands/exec.html)命令被调用时， 所有队列中的命令才会被执行。\n\n另一方面， 通过调用[DISCARD](http://www.redis.cn/commands/discard.html)， 客户端可以清空事务队列， 并放弃执行事务。\n\n以下是一个事务例子， 它原子地增加了`foo`和`bar`两个键的值：\n\n\n\n```\n> MULTIOK> INCR fooQUEUED> INCR barQUEUED> EXEC1) (integer) 12) (integer) 1\n```\n\n\n\n[EXEC](http://www.redis.cn/commands/exec.html)命令的回复是一个数组， 数组中的每个元素都是执行事务中的命令所产生的回复。 其中， 回复元素的先后顺序和命令发送的先后顺序一致。\n\n当客户端处于事务状态时， 所有传入的命令都会返回一个内容为`QUEUED`的状态回复（status reply）， 这些被入队的命令将在 EXEC 命令被调用时执行。\n\n## 事务中的错误\n\n使用事务时可能会遇上以下两种错误：\n\n*   事务在执行[EXEC](http://www.redis.cn/commands/exec.html)之前，入队的命令可能会出错。比如说，命令可能会产生语法错误（参数数量错误，参数名错误，等等），或者其他更严重的错误，比如内存不足（如果服务器使用`maxmemory`设置了最大内存限制的话）。\n*   命令可能在[EXEC](http://www.redis.cn/commands/exec.html)调用之后失败。举个例子，事务中的命令可能处理了错误类型的键，比如将列表命令用在了字符串键上面，诸如此类。\n\n对于发生在[EXEC](http://www.redis.cn/commands/exec.html)执行之前的错误，客户端以前的做法是检查命令入队所得的返回值：如果命令入队时返回`QUEUED`，那么入队成功；否则，就是入队失败。如果有命令在入队时失败，那么大部分客户端都会停止并取消这个事务。\n\n不过，从 Redis 2.6.5 开始，服务器会对命令入队失败的情况进行记录，并在客户端调用[EXEC](http://www.redis.cn/commands/exec.html)命令时，拒绝执行并自动放弃这个事务。\n\n在 Redis 2.6.5 以前， Redis 只执行事务中那些入队成功的命令，而忽略那些入队失败的命令。 而新的处理方式则使得在流水线（pipeline）中包含事务变得简单，因为发送事务和读取事务的回复都只需要和服务器进行一次通讯。\n\n至于那些在[EXEC](http://www.redis.cn/commands/exec.html)命令执行之后所产生的错误， 并没有对它们进行特别处理： 即使事务中有某个/某些命令在执行时产生了错误， 事务中的其他命令仍然会继续执行。\n\n从协议的角度来看这个问题，会更容易理解一些。 以下例子中，[LPOP](http://www.redis.cn/commands/lpop.html)命令的执行将出错， 尽管调用它的语法是正确的：\n\n\n\n```\nTrying 127.0.0.1...Connected to localhost.Escape character is '^]'.MULTI+OKSET a 3abc+QUEUEDLPOP a+QUEUEDEXEC*2+OK-ERR Operation against a key holding the wrong kind of value\n```\n\n\n\n[EXEC](http://www.redis.cn/commands/exec.html)返回两条[bulk-string-reply](http://www.redis.cn/topics/protocol.html#bulk-string-reply)： 第一条是`OK`，而第二条是`-ERR`。 至于怎样用合适的方法来表示事务中的错误， 则是由客户端自己决定的。\n\n最重要的是记住这样一条， 即使事务中有某条/某些命令执行失败了， 事务队列中的其他命令仍然会继续执行 —— Redis 不会停止执行事务中的命令。\n\n以下例子展示的是另一种情况， 当命令在入队时产生错误， 错误会立即被返回给客户端：\n\n\n\n```\nMULTI+OKINCR a b c-ERR wrong number of arguments for 'incr' command\n```\n\n\n\n因为调用[INCR](http://www.redis.cn/commands/incr.html)命令的参数格式不正确， 所以这个[INCR](http://www.redis.cn/commands/incr.html)命令入队失败。\n\n## 为什么 Redis 不支持回滚（roll back）\n\n如果你有使用关系式数据库的经验， 那么 “Redis 在事务失败时不进行回滚，而是继续执行余下的命令”这种做法可能会让你觉得有点奇怪。\n\n以下是这种做法的优点：\n\n*   Redis 命令只会因为错误的语法而失败（并且这些问题不能在入队时发现），或是命令用在了错误类型的键上面：这也就是说，从实用性的角度来说，失败的命令是由编程错误造成的，而这些错误应该在开发的过程中被发现，而不应该出现在生产环境中。\n*   因为不需要对回滚进行支持，所以 Redis 的内部可以保持简单且快速。\n\n有种观点认为 Redis 处理事务的做法会产生 bug ， 然而需要注意的是， 在通常情况下， 回滚并不能解决编程错误带来的问题。 举个例子， 如果你本来想通过[INCR](http://www.redis.cn/commands/incr.html)命令将键的值加上 1 ， 却不小心加上了 2 ， 又或者对错误类型的键执行了[INCR](http://www.redis.cn/commands/incr.html)， 回滚是没有办法处理这些情况的。\n\n## 放弃事务\n\n当执行[DISCARD](http://www.redis.cn/commands/discard.html)命令时， 事务会被放弃， 事务队列会被清空， 并且客户端会从事务状态中退出：\n\n\n\n```\n> SET foo 1OK> MULTIOK> INCR fooQUEUED> DISCARDOK> GET foo\"1\"\n```\n\n\n\n## 使用 check-and-set 操作实现乐观锁\n\n[WATCH](http://www.redis.cn/commands/watch.html)命令可以为 Redis 事务提供 check-and-set （CAS）行为。\n\n被[WATCH](http://www.redis.cn/commands/watch.html)的键会被监视，并会发觉这些键是否被改动过了。 如果有至少一个被监视的键在[EXEC](http://www.redis.cn/commands/exec.html)执行之前被修改了， 那么整个事务都会被取消，[EXEC](http://www.redis.cn/commands/exec.html)返回[nil-reply](http://www.redis.cn/topics/protocol.html#nil-reply)来表示事务已经失败。\n\n举个例子， 假设我们需要原子性地为某个值进行增 1 操作（假设[INCR](http://www.redis.cn/commands/incr.html)不存在）。\n\n首先我们可能会这样做：\n\n\n\n```\nval = GET mykeyval = val + 1SET mykey $val\n```\n\n\n\n上面的这个实现在只有一个客户端的时候可以执行得很好。 但是， 当多个客户端同时对同一个键进行这样的操作时， 就会产生竞争条件。举个例子， 如果客户端 A 和 B 都读取了键原来的值， 比如 10 ， 那么两个客户端都会将键的值设为 11 ， 但正确的结果应该是 12 才对。\n\n有了[WATCH](http://www.redis.cn/commands/watch.html)， 我们就可以轻松地解决这类问题了：\n\n\n\n```\nWATCH mykeyval = GET mykeyval = val + 1MULTISET mykey $valEXEC\n```\n\n\n\n使用上面的代码， 如果在[WATCH](http://www.redis.cn/commands/watch.html)执行之后，[EXEC](http://www.redis.cn/commands/exec.html)执行之前， 有其他客户端修改了`mykey`的值， 那么当前客户端的事务就会失败。 程序需要做的， 就是不断重试这个操作， 直到没有发生碰撞为止。\n\n这种形式的锁被称作乐观锁， 它是一种非常强大的锁机制。 并且因为大多数情况下， 不同的客户端会访问不同的键， 碰撞的情况一般都很少， 所以通常并不需要进行重试。\n\n## 了解`WATCH`\n\n[WATCH](http://www.redis.cn/commands/watch.html)使得[EXEC](http://www.redis.cn/commands/exec.html)命令需要有条件地执行： 事务只能在所有被监视键都没有被修改的前提下执行， 如果这个前提不能满足的话，事务就不会被执行。[了解更多->](http://code.google.com/p/redis/issues/detail?id=270)\n\n[WATCH](http://www.redis.cn/commands/watch.html)命令可以被调用多次。 对键的监视从[WATCH](http://www.redis.cn/commands/watch.html)执行之后开始生效， 直到调用[EXEC](http://www.redis.cn/commands/exec.html)为止。\n\n用户还可以在单个[WATCH](http://www.redis.cn/commands/watch.html)命令中监视任意多个键， 就像这样：\n\n\n\n```\nredis> WATCH key1 key2 key3OK\n```\n\n\n\n当[EXEC](http://www.redis.cn/commands/exec.html)被调用时， 不管事务是否成功执行， 对所有键的监视都会被取消。\n\n另外， 当客户端断开连接时， 该客户端对键的监视也会被取消。\n\n使用无参数的[UNWATCH](http://www.redis.cn/commands/unwatch.html)命令可以手动取消对所有键的监视。 对于一些需要改动多个键的事务， 有时候程序需要同时对多个键进行加锁， 然后检查这些键的当前值是否符合程序的要求。 当值达不到要求时， 就可以使用[UNWATCH](http://www.redis.cn/commands/unwatch.html)命令来取消目前对键的监视， 中途放弃这个事务， 并等待事务的下次尝试。\n\n### 使用 WATCH 实现 ZPOP\n\n[WATCH](http://www.redis.cn/commands/watch.html)可以用于创建 Redis 没有内置的原子操作。举个例子， 以下代码实现了原创的[ZPOP](http://www.redis.cn/commands/zpop.html)命令， 它可以原子地弹出有序集合中分值（score）最小的元素：\n\n\n\n```\nWATCH zsetelement = ZRANGE zset 0 0MULTIZREM zset elementEXEC\n```\n\n\n\n程序只要重复执行这段代码， 直到[EXEC](http://www.redis.cn/commands/exec.html)的返回值不是[nil-reply](http://www.redis.cn/topics/protocol.html#nil-reply)回复即可。\n\n## Redis 脚本和事务\n\n从定义上来说， Redis 中的脚本本身就是一种事务， 所以任何在事务里可以完成的事， 在脚本里面也能完成。 并且一般来说， 使用脚本要来得更简单，并且速度更快。\n\n因为脚本功能是 Redis 2.6 才引入的， 而事务功能则更早之前就存在了， 所以 Redis 才会同时存在两种处理事务的方法。\n\n不过我们并不打算在短时间内就移除事务功能， 因为事务提供了一种即使不使用脚本， 也可以避免竞争条件的方法， 而且事务本身的实现并不复杂。\n\n不过在不远的将来， 可能所有用户都会只使用脚本来实现事务也说不定。 如果真的发生这种情况的话， 那么我们将废弃并最终移除事务功能。\n\n\n\n\n\n\n\n\n\n\n\n# redis事务的ACID特性\n\n```\n在传统的关系型数据库中,尝尝用ACID特质来检测事务功能的可靠性和安全性。\n```\n\n在redis中事务总是具有原子性(Atomicity),一致性(Consistency)和隔离性(Isolation),并且当redis运行在某种特定的持久化\n模式下,事务也具有耐久性(Durability).\n\n> ①原子性\n> \n> ```\n> 事务具有原子性指的是,数据库将事务中的多个操作当作一个整体来执行,服务器要么就执行事务中的所有操作,要么就一个操作也不执行。\n> ```\n> \n> 但是对于redis的事务功能来说,事务队列中的命令要么就全部执行,要么就一个都不执行,因此redis的事务是具有原子性的。我们通常会知道\n\n两种关于redis事务原子性的说法,一种是要么事务都执行,要么都不执行。另外一种说法是redis事务当事务中的命令执行失败后面的命令还\n会执行,错误之前的命令不会回滚。其实这个两个说法都是正确的。但是缺一不可。我们接下来具体分析下\n\n```\n 我们先看一个可以正确执行的事务例子\n```\n\n```\nredis > MULTIOK redis > SET username \"bugall\"QUEUED redis > EXEC1) OK2) \"bugall\"\n```\n\n```\n与之相反,我们再来看一个事务执行失败的例子。这个事务因为命令在放入事务队列的时候被服务器拒绝,所以事务中的所有命令都不会执行,因为前面我们有介绍到,redis的事务命令是统一先放到事务队列里,在用户输入EXEC命令的时候再统一执行。但是我们错误的使用\"GET\"命令,在命令放入事务队列的时候被检测到事务,这时候还没有接收到EXEC命令,所以这个时候不牵扯到回滚的问题,在EXEC的时候发现事务队列里有命令存在错误,所以事务里的命令就全都不执行,这样就达到里事务的原子性,我们看下例子。\n```\n\n```\nredis > MULTIOK redis > GET(error) ERR wrong number of arguments for 'get' command redis > GET usernameQUEUED redis > EXEC(error) EXECABORT Transaction discarded because of previous errors\n```\n\n```\nredis的事务和传统的关系型数据库事务的最大区别在于,redis不支持事务的回滚机制,即使事务队列中的某个命令在执行期间出现错误,整个事务也会继续执行下去,直到将事务队列中的所有命令都执行完毕为止,我们看下面的例子\n```\n\n```\nredis > SET username \"bugall\"OK redis > MULTIOK redis > SADD member \"bugall\" \"litengfe\" \"yangyifang\"QUEUED redis > RPUSH　username \"b\" \"l\" \"y\" //错误对键username使用列表键命令QUEUED redis > SADD password \"123456\" \"123456\" \"123456\"QUEUED redis > EXEC1) (integer) 32) (error) WRONGTYPE Operation against a key holding the wrong kind of value3) (integer) 3\n```\n\n```\nredis的作者在十五功能的文档中解释说,不支持事务回滚是因为这种复杂的功能和redis追求的简单高效的设计主旨不符合,并且他认为,redis事务的执行时错误通常都是编程错误造成的,这种错误通常只会出现在开发环境中,而很少会在实际的生产环境中出现,所以他认为没有必要为redis开发事务回滚功能。所以我们在讨论redis事务回滚的时候,一定要区分命令发生错误的时候。\n```\n\n> ②一致性\n> \n> ```\n>     事务具有一致性指的是,如果数据库在执行事务之前是一致的,那么在事务执行之后,无论事务是否执行成功,数据库也应该仍然一致的。    ”一致“指的是数据符合数据库本身的定义和要求,没有包含非法或者无效的错误数据。redis通过谨慎的错误检测和简单的设计来保证事务一致性。\n> ```\n> \n> ③隔离性\n> \n> ```\n>     事务的隔离性指的是,即使数据库中有多个事务并发在执行,各个事务之间也不会互相影响,并且在并发状态下执行的事务和串行执行的事务产生的结果完全    相同。    因为redis使用单线程的方式来执行事务(以及事务队列中的命令),并且服务器保证,在执行事务期间不会对事物进行中断,因此,redis的事务总是以串行    的方式运行的,并且事务也总是具有隔离性的\n> ```\n> \n> ④持久性\n> \n> ```\n>     事务的耐久性指的是,当一个事务执行完毕时,执行这个事务所得的结果已经被保持到永久存储介质里面。    因为redis事务不过是简单的用队列包裹起来一组redis命令,redis并没有为事务提供任何额外的持久化功能,所以redis事务的耐久性由redis使用的模式    决定    - 当服务器在无持久化的内存模式下运行时,事务不具有耐久性,一旦服务器停机,包括事务数据在内的所有服务器数据都将丢失    - 当服务器在RDB持久化模式下运作的时候,服务器只会在特定的保存条件满足的时候才会执行BGSAVE命令,对数据库进行保存操作,并且异步执行的BGSAVE不    能保证事务数据被第一时间保存到硬盘里面,因此RDB持久化模式下的事务也不具有耐久性    - 当服务器运行在AOF持久化模式下,并且appedfsync的选项的值为always时,程序总会在执行命令之后调用同步函数,将命令数据真正的保存到硬盘里面,因此    这种配置下的事务是具有耐久性的。    - 当服务器运行在AOF持久化模式下,并且appedfsync的选项的值为everysec时,程序会每秒同步一次\n> ```\n\n\n\n\n\n\n\n"
  },
  {
    "path": "docs/cache/探索Redis设计与实现：Redis内部数据结构详解——dict.md",
    "content": "# 目录\n\n  * [dict的数据结构定义](#dict的数据结构定义)\n  * [dict的创建（dictCreate）](#dict的创建（dictcreate）)\n  * [dict的查找（dictFind）](#dict的查找（dictfind）)\n  * [dict的插入（dictAdd和dictReplace）](#dict的插入（dictadd和dictreplace）)\n  * [dict的删除（dictDelete）](#dict的删除（dictdelete）)\n\n\n[toc]\n\n本文转自互联网\n\n本文将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，Redis基本的使用方法，Redis的基本数据结构，以及一些进阶的使用方法，同时也需要进一步了解Redis的底层数据结构，再接着，还会带来Redis主从复制、集群、分布式锁等方面的相关内容，以及作为缓存的一些使用方法和注意事项，以便让你更完整地了解整个Redis相关的技术体系，形成自己的知识框架。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n如果你使用过Redis，一定会像我一样对它的内部实现产生兴趣。《Redis内部数据结构详解》是我准备写的一个系列，也是我个人对于之前研究Redis的一个阶段性总结，着重讲解Redis在内存中的数据结构实现（暂不涉及持久化的话题）。Redis本质上是一个数据结构服务器（data structures server），以高效的方式实现了多种现成的数据结构，研究它的数据结构和基于其上的算法，对于我们自己提升局部算法的编程水平有很重要的参考意义。\n\n当我们在本文中提到Redis的“数据结构”，可能是在两个不同的层面来讨论它。\n\n第一个层面，是从使用者的角度。比如：\n\n*   string\n*   list\n*   hash\n*   set\n*   sorted set\n\n这一层面也是Redis暴露给外部的调用接口。\n\n第二个层面，是从内部实现的角度，属于更底层的实现。比如：\n\n*   dict\n*   sds\n*   ziplist\n*   quicklist\n*   skiplist\n\n第一个层面的“数据结构”，Redis的官方文档([http://redis.io/topics/data-types-intro](http://redis.io/topics/data-types-intro))有详细的介绍。本文的重点在于讨论第二个层面，Redis数据结构的内部实现，以及这两个层面的数据结构之间的关系：Redis如何通过组合第二个层面的各种基础数据结构来实现第一个层面的更高层的数据结构。\n\n在讨论任何一个系统的内部实现的时候，我们都要先明确它的设计原则，这样我们才能更深刻地理解它为什么会进行如此设计的真正意图。在本文接下来的讨论中，我们主要关注以下几点：\n\n*   存储效率（memory efficiency）。Redis是专用于存储数据的，它对于计算机资源的主要消耗就在于内存，因此节省内存是它非常非常重要的一个方面。这意味着Redis一定是非常精细地考虑了压缩数据、减少内存碎片等问题。\n*   快速响应时间（fast response time）。与快速响应时间相对的，是高吞吐量（high throughput）。Redis是用于提供在线访问的，对于单个请求的响应时间要求很高，因此，快速响应时间是比高吞吐量更重要的目标。有时候，这两个目标是矛盾的。\n*   单线程（single-threaded）。Redis的性能瓶颈不在于CPU资源，而在于内存访问和网络IO。而采用单线程的设计带来的好处是，极大简化了数据结构和算法的实现。相反，Redis通过异步IO和pipelining等机制来实现高速的并发访问。显然，单线程的设计，对于单个请求的快速响应时间也提出了更高的要求。\n\n本文是《Redis内部数据结构详解》系列的第一篇，讲述Redis一个重要的基础数据结构：dict。\n\ndict是一个用于维护key和value映射关系的数据结构，与很多语言中的Map或dictionary类似。Redis的一个database中所有key到value的映射，就是使用一个dict来维护的。不过，这只是它在Redis中的一个用途而已，它在Redis中被使用的地方还有很多。比如，一个Redis hash结构，当它的field较多时，便会采用dict来存储。再比如，Redis配合使用dict和skiplist来共同维护一个sorted set。这些细节我们后面再讨论，在本文中，我们集中精力讨论dict本身的实现。\n\ndict本质上是为了解决算法中的查找问题（Searching），一般查找问题的解法分为两个大类：一个是基于各种平衡树，一个是基于哈希表。我们平常使用的各种Map或dictionary，大都是基于哈希表实现的。在不要求数据有序存储，且能保持较低的哈希值冲突概率的前提下，基于哈希表的查找性能能做到非常高效，接近O(1)，而且实现简单。\n\n在Redis中，dict也是一个基于哈希表的算法。和传统的哈希算法类似，它采用某个哈希函数从key计算得到在哈希表中的位置，采用拉链法解决冲突，并在装载因子（load factor）超过预定值时自动扩展内存，引发重哈希（rehashing）。Redis的dict实现最显著的一个特点，就在于它的重哈希。它采用了一种称为增量式重哈希（incremental rehashing）的方法，在需要扩展内存时避免一次性对所有key进行重哈希，而是将重哈希操作分散到对于dict的各个增删改查的操作中去。这种方法能做到每次只对一小部分key进行重哈希，而每次重哈希之间不影响dict的操作。dict之所以这样设计，是为了避免重哈希期间单个请求的响应时间剧烈增加，这与前面提到的“快速响应时间”的设计原则是相符的。\n\n下面进行详细介绍。\n\n## dict的数据结构定义\n\n为了实现增量式重哈希（incremental rehashing），dict的数据结构里包含两个哈希表。在重哈希期间，数据从第一个哈希表向第二个哈希表迁移。\n\ndict的C代码定义如下（出自Redis源码dict.h）：\n\n````\n    typedef struct dictEntry {\n        void *key;\n        union {\n            void *val;\n            uint64_t u64;\n            int64_t s64;\n            double d;\n        } v;\n        struct dictEntry *next;\n    } dictEntry;\n    \n    typedef struct dictType {\n        unsigned int (*hashFunction)(const void *key);\n        void *(*keyDup)(void *privdata, const void *key);\n        void *(*valDup)(void *privdata, const void *obj);\n        int (*keyCompare)(void *privdata, const void *key1, const void *key2);\n        void (*keyDestructor)(void *privdata, void *key);\n        void (*valDestructor)(void *privdata, void *obj);\n    } dictType;\n    \n    /* This is our hash table structure. Every dictionary has two of this as we\n     * implement incremental rehashing, for the old to the new table. */\n    typedef struct dictht {\n        dictEntry **table;\n        unsigned long size;\n        unsigned long sizemask;\n        unsigned long used;\n    } dictht;\n    \n    typedef struct dict {\n        dictType *type;\n        void *privdata;\n        dictht ht[2];\n        long rehashidx; /* rehashing not in progress if rehashidx == -1 */\n        int iterators; /* number of iterators currently running */\n    } dict;\n\n````\n为了能更清楚地展示dict的数据结构定义，我们用一张结构图来表示它。如下。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230406203512.png)\n\n结合上面的代码和结构图，可以很清楚地看出dict的结构。一个dict由如下若干项组成：\n\n*   一个指向dictType结构的指针（type）。它通过自定义的方式使得dict的key和value能够存储任何类型的数据。\n*   一个私有数据指针（privdata）。由调用者在创建dict的时候传进来。\n*   两个哈希表（ht[2]）。只有在重哈希的过程中，ht[0]和ht[1]才都有效。而在平常情况下，只有ht[0]有效，ht[1]里面没有任何数据。上图表示的就是重哈希进行到中间某一步时的情况。\n*   当前重哈希索引（rehashidx）。如果rehashidx = -1，表示当前没有在重哈希过程中；否则，表示当前正在进行重哈希，且它的值记录了当前重哈希进行到哪一步了。\n*   当前正在进行遍历的iterator的个数。这不是我们现在讨论的重点，暂时忽略。\n\ndictType结构包含若干函数指针，用于dict的调用者对涉及key和value的各种操作进行自定义。这些操作包含：\n\n*   hashFunction，对key进行哈希值计算的哈希算法。\n*   keyDup和valDup，分别定义key和value的拷贝函数，用于在需要的时候对key和value进行深拷贝，而不仅仅是传递对象指针。\n*   keyCompare，定义两个key的比较操作，在根据key进行查找时会用到。\n*   keyDestructor和valDestructor，分别定义对key和value的析构函数。\n\n私有数据指针（privdata）就是在dictType的某些操作被调用时会传回给调用者。\n\n需要详细察看的是dictht结构。它定义一个哈希表的结构，由如下若干项组成：\n\n*   一个dictEntry指针数组（table）。key的哈希值最终映射到这个数组的某个位置上（对应一个bucket）。如果多个key映射到同一个位置，就发生了冲突，那么就拉出一个dictEntry链表。\n*   size：标识dictEntry指针数组的长度。它总是2的指数。\n*   sizemask：用于将哈希值映射到table的位置索引。它的值等于(size-1)，比如7, 15, 31, 63，等等，也就是用二进制表示的各个bit全1的数字。每个key先经过hashFunction计算得到一个哈希值，然后计算(哈希值 & sizemask)得到在table上的位置。相当于计算取余(哈希值 % size)。\n*   used：记录dict中现有的数据个数。它与size的比值就是装载因子（load factor）。这个比值越大，哈希值冲突概率越高。\n\ndictEntry结构中包含k, v和指向链表下一项的next指针。k是void指针，这意味着它可以指向任何类型。v是个union，当它的值是uint64_t、int64_t或double类型时，就不再需要额外的存储，这有利于减少内存碎片。当然，v也可以是void指针，以便能存储任何类型的数据。\n\n## dict的创建（dictCreate）\n````\n    dict *dictCreate(dictType *type,\n            void *privDataPtr)\n    {\n        dict *d = zmalloc(sizeof(*d));\n    \n        _dictInit(d,type,privDataPtr);\n        return d;\n    }\n    \n    int _dictInit(dict *d, dictType *type,\n            void *privDataPtr)\n    {\n        _dictReset(&d->ht[0]);\n        _dictReset(&d->ht[1]);\n        d->type = type;\n        d->privdata = privDataPtr;\n        d->rehashidx = -1;\n        d->iterators = 0;\n        return DICT_OK;\n    }\n    \n    static void _dictReset(dictht *ht)\n    {\n        ht->table = NULL;\n        ht->size = 0;\n        ht->sizemask = 0;\n        ht->used = 0;\n    }\n\n````\ndictCreate为dict的数据结构分配空间并为各个变量赋初值。其中两个哈希表ht[0]和ht[1]起始都没有分配空间，table指针都赋为NULL。这意味着要等第一个数据插入时才会真正分配空间。\n\n## dict的查找（dictFind）\n````\n    //define dictIsRehashing(d) ((d)->rehashidx != -1)\n    \n    dictEntry *dictFind(dict *d, const void *key)\n    {\n        dictEntry *he;\n        unsigned int h, idx, table;\n    \n        if (d->ht[0].used + d->ht[1].used == 0) return NULL; /* dict is empty */\n        if (dictIsRehashing(d)) _dictRehashStep(d);\n        h = dictHashKey(d, key);\n        for (table = 0; table <= 1; table++) {\n            idx = h & d->ht[table].sizemask;\n            he = d->ht[table].table[idx];\n            while(he) {\n                if (key==he->key || dictCompareKeys(d, key, he->key))\n                    return he;\n                he = he->next;\n            }\n            if (!dictIsRehashing(d)) return NULL;\n        }\n        return NULL;\n    }\n````\n\n上述dictFind的源码，根据dict当前是否正在重哈希，依次做了这么几件事：\n\n*   如果当前正在进行重哈希，那么将重哈希过程向前推进一步（即调用_dictRehashStep）。实际上，除了查找，插入和删除也都会触发这一动作。这就将重哈希过程分散到各个查找、插入和删除操作中去了，而不是集中在某一个操作中一次性做完。\n*   计算key的哈希值（调用dictHashKey，里面的实现会调用前面提到的hashFunction）。\n*   先在第一个哈希表ht[0]上进行查找。在table数组上定位到哈希值对应的位置（如前所述，通过哈希值与sizemask进行按位与），然后在对应的dictEntry链表上进行查找。查找的时候需要对key进行比较，这时候调用dictCompareKeys，它里面的实现会调用到前面提到的keyCompare。如果找到就返回该项。否则，进行下一步。\n*   判断当前是否在重哈希，如果没有，那么在ht[0]上的查找结果就是最终结果（没找到，返回NULL）。否则，在ht[1]上进行查找（过程与上一步相同）。\n\n下面我们有必要看一下增量式重哈希的_dictRehashStep的实现。\n\n````\n    static void _dictRehashStep(dict *d) {\n        if (d->iterators == 0) dictRehash(d,1);\n    }\n    \n    int dictRehash(dict *d, int n) {\n        int empty_visits = n*10; /* Max number of empty buckets to visit. */\n        if (!dictIsRehashing(d)) return 0;\n    \n        while(n-- && d->ht[0].used != 0) {\n            dictEntry *de, *nextde;\n    \n            /* Note that rehashidx can't overflow as we are sure there are more\n             * elements because ht[0].used != 0 */\n            assert(d->ht[0].size > (unsigned long)d->rehashidx);\n            while(d->ht[0].table[d->rehashidx] == NULL) {\n                d->rehashidx++;\n                if (--empty_visits == 0) return 1;\n            }\n            de = d->ht[0].table[d->rehashidx];\n            /* Move all the keys in this bucket from the old to the new hash HT */\n            while(de) {\n                unsigned int h;\n    \n                nextde = de->next;\n                /* Get the index in the new hash table */\n                h = dictHashKey(d, de->key) & d->ht[1].sizemask;\n                de->next = d->ht[1].table[h];\n                d->ht[1].table[h] = de;\n                d->ht[0].used--;\n                d->ht[1].used++;\n                de = nextde;\n            }\n            d->ht[0].table[d->rehashidx] = NULL;\n            d->rehashidx++;\n        }\n    \n        /* Check if we already rehashed the whole table... */\n        if (d->ht[0].used == 0) {\n            zfree(d->ht[0].table);\n            d->ht[0] = d->ht[1];\n            _dictReset(&d->ht[1]);\n            d->rehashidx = -1;\n            return 0;\n        }\n    \n        /* More to rehash... */\n        return 1;\n    }\n````\ndictRehash每次将重哈希至少向前推进n步（除非不到n步整个重哈希就结束了），每一步都将ht[0]上某一个bucket（即一个dictEntry链表）上的每一个dictEntry移动到ht[1]上，它在ht[1]上的新位置根据ht[1]的sizemask进行重新计算。rehashidx记录了当前尚未迁移（有待迁移）的ht[0]的bucket位置。\n\n如果dictRehash被调用的时候，rehashidx指向的bucket里一个dictEntry也没有，那么它就没有可迁移的数据。这时它尝试在ht[0].table数组中不断向后遍历，直到找到下一个存有数据的bucket位置。如果一直找不到，则最多走n*10步，本次重哈希暂告结束。\n\n最后，如果ht[0]上的数据都迁移到ht[1]上了（即d->ht[0].used == 0），那么整个重哈希结束，ht[0]变成ht[1]的内容，而ht[1]重置为空。\n\n根据以上对于重哈希过程的分析，我们容易看出，本文前面的dict结构图中所展示的正是rehashidx=2时的情况，前面两个bucket（ht[0].table[0]和ht[0].table[1]）都已经迁移到ht[1]上去了。\n\n## dict的插入（dictAdd和dictReplace）\n\ndictAdd插入新的一对key和value，如果key已经存在，则插入失败。\n\ndictReplace也是插入一对key和value，不过在key存在的时候，它会更新value。\n\n````\n    int dictAdd(dict *d, void *key, void *val)\n    {\n        dictEntry *entry = dictAddRaw(d,key);\n    \n        if (!entry) return DICT_ERR;\n        dictSetVal(d, entry, val);\n        return DICT_OK;\n    }\n    \n    dictEntry *dictAddRaw(dict *d, void *key)\n    {\n        int index;\n        dictEntry *entry;\n        dictht *ht;\n    \n        if (dictIsRehashing(d)) _dictRehashStep(d);\n    \n        /* Get the index of the new element, or -1 if\n         * the element already exists. */\n        if ((index = _dictKeyIndex(d, key)) == -1)\n            return NULL;\n    \n        /* Allocate the memory and store the new entry.\n         * Insert the element in top, with the assumption that in a database\n         * system it is more likely that recently added entries are accessed\n         * more frequently. */\n        ht = dictIsRehashing(d) ? &d->ht[1] : &d->ht[0];\n        entry = zmalloc(sizeof(*entry));\n        entry->next = ht->table[index];\n        ht->table[index] = entry;\n        ht->used++;\n    \n        /* Set the hash entry fields. */\n        dictSetKey(d, entry, key);\n        return entry;\n    }\n    \n    static int _dictKeyIndex(dict *d, const void *key)\n    {\n        unsigned int h, idx, table;\n        dictEntry *he;\n    \n        /* Expand the hash table if needed */\n        if (_dictExpandIfNeeded(d) == DICT_ERR)\n            return -1;\n        /* Compute the key hash value */\n        h = dictHashKey(d, key);\n        for (table = 0; table <= 1; table++) {\n            idx = h & d->ht[table].sizemask;\n            /* Search if this slot does not already contain the given key */\n            he = d->ht[table].table[idx];\n            while(he) {\n                if (key==he->key || dictCompareKeys(d, key, he->key))\n                    return -1;\n                he = he->next;\n            }\n            if (!dictIsRehashing(d)) break;\n        }\n        return idx;\n    }\n````\n以上是dictAdd的关键实现代码。我们主要需要注意以下几点：\n\n*   它也会触发推进一步重哈希（_dictRehashStep）。\n*   如果正在重哈希中，它会把数据插入到ht[1]；否则插入到ht[0]。\n*   在对应的bucket中插入数据的时候，总是插入到dictEntry的头部。因为新数据接下来被访问的概率可能比较高，这样再次查找它时就比较次数较少。\n*   _dictKeyIndex在dict中寻找插入位置。如果不在重哈希过程中，它只查找ht[0]；否则查找ht[0]和ht[1]。\n*   _dictKeyIndex可能触发dict内存扩展（_dictExpandIfNeeded，它将哈希表长度扩展为原来两倍，具体请参考dict.c中源码）。\n\ndictReplace在dictAdd基础上实现，如下：\n\n````\n    int dictReplace(dict *d, void *key, void *val)\n    {\n        dictEntry *entry, auxentry;\n    \n        /* Try to add the element. If the key\n         * does not exists dictAdd will suceed. */\n        if (dictAdd(d, key, val) == DICT_OK)\n            return 1;\n        /* It already exists, get the entry */\n        entry = dictFind(d, key);\n        /* Set the new value and free the old one. Note that it is important\n         * to do that in this order, as the value may just be exactly the same\n         * as the previous one. In this context, think to reference counting,\n         * you want to increment (set), and then decrement (free), and not the\n         * reverse. */\n        auxentry = *entry;\n        dictSetVal(d, entry, val);\n        dictFreeVal(d, &auxentry);\n        return 0;\n    }\n````\n在key已经存在的情况下，dictReplace会同时调用dictAdd和dictFind，这其实相当于两次查找过程。这里Redis的代码不够优化。\n\n## dict的删除（dictDelete）\n\ndictDelete的源码这里忽略，具体请参考dict.c。需要稍加注意的是：\n\n*   dictDelete也会触发推进一步重哈希（_dictRehashStep）\n*   如果当前不在重哈希过程中，它只在ht[0]中查找要删除的key；否则ht[0]和ht[1]它都要查找。\n*   删除成功后会调用key和value的析构函数（keyDestructor和valDestructor）。\n\n* * *\n\ndict的实现相对来说比较简单，本文就介绍到这。在下一篇中我们将会介绍Redis中动态字符串的实现——sds，敬请期待。\n\n"
  },
  {
    "path": "docs/cache/探索Redis设计与实现：Redis内部数据结构详解——intset.md",
    "content": "# 目录\n\n  * [intset数据结构简介](#intset数据结构简介)\n  * [intset的查找和添加操作](#intset的查找和添加操作)\n  * [Redis的set](#redis的set)\n  * [Redis set的并、交、差算法](#redis-set的并、交、差算法)\n    * [交集](#交集)\n    * [并集](#并集)\n    * [差集](#差集)\n\n\n[toc]\n\n本文转自互联网\n\n本文将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，Redis基本的使用方法，Redis的基本数据结构，以及一些进阶的使用方法，同时也需要进一步了解Redis的底层数据结构，再接着，还会带来Redis主从复制、集群、分布式锁等方面的相关内容，以及作为缓存的一些使用方法和注意事项，以便让你更完整地了解整个Redis相关的技术体系，形成自己的知识框架。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n本文是《[Redis内部数据结构详解](http://zhangtielei.com/posts/blog-redis-dict.html)》系列的第七篇。在本文中，我们围绕一个Redis的内部数据结构——intset展开讨论。\n\nRedis里面使用intset是为了实现集合(set)这种对外的数据结构。set结构类似于数学上的集合的概念，它包含的元素无序，且不能重复。Redis里的set结构还实现了基础的集合并、交、差的操作。与Redis对外暴露的其它数据结构类似，set的底层实现，随着元素类型是否是整型以及添加的元素的数目多少，而有所变化。概括来讲，当set中添加的元素都是整型且元素数目较少时，set使用intset作为底层数据结构，否则，set使用[dict](http://zhangtielei.com/posts/blog-redis-dict.html)作为底层数据结构。\n\n在本文中我们将大体分成三个部分进行介绍：\n\n1.  集中介绍intset数据结构。\n2.  讨论set是如何在intset和[dict](http://zhangtielei.com/posts/blog-redis-dict.html)基础上构建起来的。\n3.  集中讨论set的并、交、差的算法实现以及时间复杂度。注意，其中差集的计算在Redis中实现了两种算法。\n\n我们在讨论中还会涉及到一个Redis配置（在redis.conf中的ADVANCED CONFIG部分）：\n\n\n\n\n\n```\nset-max-intset-entries 512\n\n```\n\n\n\n\n\n注：本文讨论的代码实现基于Redis源码的3.2分支。\n\n## intset数据结构简介\n\nintset顾名思义，是由整数组成的集合。实际上，intset是一个由整数组成的有序集合，从而便于在上面进行二分查找，用于快速地判断一个元素是否属于这个集合。它在内存分配上与[ziplist](http://zhangtielei.com/posts/blog-redis-ziplist.html)有些类似，是连续的一整块内存空间，而且对于大整数和小整数（按绝对值）采取了不同的编码，尽量对内存的使用进行了优化。\n\nintset的数据结构定义如下（出自intset.h和intset.c）：\n\n\n\n\n\n```\ntypedef struct intset {\n    uint32_t encoding;\n    uint32_t length;\n    int8_t contents[];\n} intset;\n\n#define INTSET_ENC_INT16 (sizeof(int16_t))\n#define INTSET_ENC_INT32 (sizeof(int32_t))\n#define INTSET_ENC_INT64 (sizeof(int64_t))\n\n```\n\n\n\n\n\n各个字段含义如下：\n\n*   `encoding`: 数据编码，表示intset中的每个数据元素用几个字节来存储。它有三种可能的取值：INTSET_ENC_INT16表示每个元素用2个字节存储，INTSET_ENC_INT32表示每个元素用4个字节存储，INTSET_ENC_INT64表示每个元素用8个字节存储。因此，intset中存储的整数最多只能占用64bit。\n*   `length`: 表示intset中的元素个数。`encoding`和`length`两个字段构成了intset的头部（header）。\n*   `contents`: 是一个柔性数组（[flexible array member](https://en.wikipedia.org/wiki/Flexible_array_member)），表示intset的header后面紧跟着数据元素。这个数组的总长度（即总字节数）等于`encoding * length`。柔性数组在Redis的很多数据结构的定义中都出现过（例如[sds](http://zhangtielei.com/posts/blog-redis-sds.html),[quicklist](http://zhangtielei.com/posts/blog-redis-quicklist.html),[skiplist](http://zhangtielei.com/posts/blog-redis-skiplist.html)），用于表达一个偏移量。`contents`需要单独为其分配空间，这部分内存不包含在intset结构当中。\n\n其中需要注意的是，intset可能会随着数据的添加而改变它的数据编码：\n\n*   最开始，新创建的intset使用占内存最小的INTSET_ENC_INT16（值为2）作为数据编码。\n*   每添加一个新元素，则根据元素大小决定是否对数据编码进行升级。\n\n下图给出了一个添加数据的具体例子（点击看大图）。\n\n[![intset添加数据举例](http://zhangtielei.com/assets/photos_redis/intset/redis_intset_add_example.png)](http://zhangtielei.com/assets/photos_redis/intset/redis_intset_add_example.png)\n\n在上图中：\n\n*   新创建的intset只有一个header，总共8个字节。其中`encoding`= 2,`length`= 0。\n*   添加13, 5两个元素之后，因为它们是比较小的整数，都能使用2个字节表示，所以`encoding`不变，值还是2。\n*   当添加32768的时候，它不再能用2个字节来表示了（2个字节能表达的数据范围是-2<sup>15</sup>~2<sup>15</sup>-1，而32768等于2<sup>15</sup>，超出范围了），因此`encoding`必须升级到INTSET_ENC_INT32（值为4），即用4个字节表示一个元素。\n*   在添加每个元素的过程中，intset始终保持从小到大有序。\n*   与[ziplist](http://zhangtielei.com/posts/blog-redis-ziplist.html)类似，intset也是按小端（little endian）模式存储的（参见维基百科词条[Endianness](https://en.wikipedia.org/wiki/Endianness)）。比如，在上图中intset添加完所有数据之后，表示`encoding`字段的4个字节应该解释成0x00000004，而第5个数据应该解释成0x000186A0 = 100000。\n\nintset与[ziplist](http://zhangtielei.com/posts/blog-redis-ziplist.html)相比：\n\n*   ziplist可以存储任意二进制串，而intset只能存储整数。\n*   ziplist是无序的，而intset是从小到大有序的。因此，在ziplist上查找只能遍历，而在intset上可以进行二分查找，性能更高。\n*   ziplist可以对每个数据项进行不同的变长编码（每个数据项前面都有数据长度字段`len`），而intset只能整体使用一个统一的编码（`encoding`）。\n\n## intset的查找和添加操作\n\n要理解intset的一些实现细节，只需要关注intset的两个关键操作基本就可以了：查找（`intsetFind`）和添加（`intsetAdd`）元素。\n\n`intsetFind`的关键代码如下所示（出自intset.c）：\n\n\n\n\n\n```\nuint8_t intsetFind(intset *is, int64_t value) {\n    uint8_t valenc = _intsetValueEncoding(value);\n    return valenc <= intrev32ifbe(is->encoding) && intsetSearch(is,value,NULL);\n}\n\nstatic uint8_t intsetSearch(intset *is, int64_t value, uint32_t *pos) {\n    int min = 0, max = intrev32ifbe(is->length)-1, mid = -1;\n    int64_t cur = -1;\n\n    /* The value can never be found when the set is empty */\n    if (intrev32ifbe(is->length) == 0) {\n        if (pos) *pos = 0;\n        return 0;\n    } else {\n        /* Check for the case where we know we cannot find the value,\n         * but do know the insert position. */\n        if (value > _intsetGet(is,intrev32ifbe(is->length)-1)) {\n            if (pos) *pos = intrev32ifbe(is->length);\n            return 0;\n        } else if (value < _intsetGet(is,0)) {\n            if (pos) *pos = 0;\n            return 0;\n        }\n    }\n\n    while(max >= min) {\n        mid = ((unsigned int)min + (unsigned int)max) >> 1;\n        cur = _intsetGet(is,mid);\n        if (value > cur) {\n            min = mid+1;\n        } else if (value < cur) {\n            max = mid-1;\n        } else {\n            break;\n        }\n    }\n\n    if (value == cur) {\n        if (pos) *pos = mid;\n        return 1;\n    } else {\n        if (pos) *pos = min;\n        return 0;\n    }\n}\n\n```\n\n\n\n\n\n关于以上代码，我们需要注意的地方包括：\n\n*   `intsetFind`在指定的intset中查找指定的元素`value`，找到返回1，没找到返回0。\n*   `_intsetValueEncoding`函数会根据要查找的`value`落在哪个范围而计算出相应的数据编码（即它应该用几个字节来存储）。\n*   如果`value`所需的数据编码比当前intset的编码要大，则它肯定在当前intset所能存储的数据范围之外（特别大或特别小），所以这时会直接返回0；否则调用`intsetSearch`执行一个二分查找算法。\n*   `intsetSearch`在指定的intset中查找指定的元素`value`，如果找到，则返回1并且将参数`pos`指向找到的元素位置；如果没找到，则返回0并且将参数`pos`指向能插入该元素的位置。\n*   `intsetSearch`是对于二分查找算法的一个实现，它大致分为三个部分：\n    *   特殊处理intset为空的情况。\n    *   特殊处理两个边界情况：当要查找的`value`比最后一个元素还要大或者比第一个元素还要小的时候。实际上，这两部分的特殊处理，在二分查找中并不是必须的，但它们在这里提供了特殊情况下快速失败的可能。\n    *   真正执行二分查找过程。注意：如果最后没找到，插入位置在`min`指定的位置。\n*   代码中出现的`intrev32ifbe`是为了在需要的时候做大小端转换的。前面我们提到过，intset里的数据是按小端（little endian）模式存储的，因此在大端（big endian）机器上运行时，这里的`intrev32ifbe`会做相应的转换。\n*   这个查找算法的总的时间复杂度为O(log n)。\n\n而`intsetAdd`的关键代码如下所示（出自intset.c）：\n\n\n\n\n\n```\nintset *intsetAdd(intset *is, int64_t value, uint8_t *success) {\n    uint8_t valenc = _intsetValueEncoding(value);\n    uint32_t pos;\n    if (success) *success = 1;\n\n    /* Upgrade encoding if necessary. If we need to upgrade, we know that\n     * this value should be either appended (if > 0) or prepended (if < 0),\n     * because it lies outside the range of existing values. */\n    if (valenc > intrev32ifbe(is->encoding)) {\n        /* This always succeeds, so we don't need to curry *success. */\n        return intsetUpgradeAndAdd(is,value);\n    } else {\n        /* Abort if the value is already present in the set.\n         * This call will populate \"pos\" with the right position to insert\n         * the value when it cannot be found. */\n        if (intsetSearch(is,value,&pos)) {\n            if (success) *success = 0;\n            return is;\n        }\n\n        is = intsetResize(is,intrev32ifbe(is->length)+1);\n        if (pos < intrev32ifbe(is->length)) intsetMoveTail(is,pos,pos+1);\n    }\n\n    _intsetSet(is,pos,value);\n    is->length = intrev32ifbe(intrev32ifbe(is->length)+1);\n    return is;\n}\n\n```\n\n\n\n\n\n关于以上代码，我们需要注意的地方包括：\n\n*   `intsetAdd`在intset中添加新元素`value`。如果`value`在添加前已经存在，则不会重复添加，这时参数`success`被置为0；如果`value`在原来intset中不存在，则将`value`插入到适当位置，这时参数`success`被置为0。\n*   如果要添加的元素`value`所需的数据编码比当前intset的编码要大，那么则调用`intsetUpgradeAndAdd`将intset的编码进行升级后再插入`value`。\n*   调用`intsetSearch`，如果能查到，则不会重复添加。\n*   如果没查到，则调用`intsetResize`对intset进行内存扩充，使得它能够容纳新添加的元素。因为intset是一块连续空间，因此这个操作会引发内存的`realloc`（参见[http://man.cx/realloc](http://man.cx/realloc)）。这有可能带来一次数据拷贝。同时调用`intsetMoveTail`将待插入位置后面的元素统一向后移动1个位置，这也涉及到一次数据拷贝。值得注意的是，在`intsetMoveTail`中是调用`memmove`完成这次数据拷贝的。`memmove`保证了在拷贝过程中不会造成数据重叠或覆盖，具体参见[http://man.cx/memmove](http://man.cx/memmove)。\n*   `intsetUpgradeAndAdd`的实现中也会调用`intsetResize`来完成内存扩充。在进行编码升级时，`intsetUpgradeAndAdd`的实现会把原来intset中的每个元素取出来，再用新的编码重新写入新的位置。\n*   注意一下`intsetAdd`的返回值，它返回一个新的intset指针。它可能与传入的intset指针`is`相同，也可能不同。调用方必须用这里返回的新的intset，替换之前传进来的旧的intset变量。类似这种接口使用模式，在Redis的实现代码中是很常见的，比如我们之前在介绍[sds](http://zhangtielei.com/posts/blog-redis-sds.html)和[ziplist](http://zhangtielei.com/posts/blog-redis-ziplist.html)的时候都碰到过类似的情况。\n*   显然，这个`intsetAdd`算法总的时间复杂度为O(n)。\n\n## Redis的set\n\n为了更好地理解Redis对外暴露的set数据结构，我们先看一下set的一些关键的命令。下面是一些命令举例：\n\n[![set命令举例](http://zhangtielei.com/assets/photos_redis/intset/redis_set_cmd_example.png)](http://zhangtielei.com/assets/photos_redis/intset/redis_set_cmd_example.png)\n\n上面这些命令的含义：\n\n*   `sadd`用于分别向集合`s1`和`s2`中添加元素。添加的元素既有数字，也有非数字（”a”和”b”）。\n*   `sismember`用于判断指定的元素是否在集合内存在。\n*   `sinter`,`sunion`和`sdiff`分别用于计算集合的交集、并集和差集。\n\n我们前面提到过，set的底层实现，随着元素类型是否是整型以及添加的元素的数目多少，而有所变化。例如，具体到上述命令的执行过程中，集合`s1`的底层数据结构会发生如下变化：\n\n*   在开始执行完`sadd s1 13 5`之后，由于添加的都是比较小的整数，所以`s1`底层是一个intset，其数据编码`encoding`= 2。\n*   在执行完`sadd s1 32768 10 100000`之后，`s1`底层仍然是一个intset，但其数据编码`encoding`从2升级到了4。\n*   在执行完`sadd s1 a b`之后，由于添加的元素不再是数字，`s1`底层的实现会转成一个dict。\n\n我们知道，dict是一个用于维护key和value映射关系的数据结构，那么当set底层用dict表示的时候，它的key和value分别是什么呢？实际上，key就是要添加的集合元素，而value是NULL。\n\n除了前面提到的由于添加非数字元素造成集合底层由intset转成dict之外，还有两种情况可能造成这种转换：\n\n*   添加了一个数字，但它无法用64bit的有符号数来表达。intset能够表达的最大的整数范围为-2<sup>64</sup>~2<sup>64</sup>-1，因此，如果添加的数字超出了这个范围，这也会导致intset转成dict。\n*   添加的集合元素个数超过了`set-max-intset-entries`配置的值的时候，也会导致intset转成dict（具体的触发条件参见t_set.c中的`setTypeAdd`相关代码）。\n\n对于小集合使用intset来存储，主要的原因是节省内存。特别是当存储的元素个数较少的时候，dict所带来的内存开销要大得多（包含两个哈希表、链表指针以及大量的其它元数据）。所以，当存储大量的小集合而且集合元素都是数字的时候，用intset能节省下一笔可观的内存空间。\n\n实际上，从时间复杂度上比较，intset的平均情况是没有dict性能高的。以查找为例，intset是O(log n)的，而dict可以认为是O(1)的。但是，由于使用intset的时候集合元素个数比较少，所以这个影响不大。\n\n## Redis set的并、交、差算法\n\nRedis set的并、交、差算法的实现代码，在t_set.c中。其中计算交集调用的是`sinterGenericCommand`，计算并集和差集调用的是`sunionDiffGenericCommand`。它们都能同时对多个（可以多于2个）集合进行运算。当对多个集合进行差集运算时，它表达的含义是：用第一个集合与第二个集合做差集，所得结果再与第三个集合做差集，依次向后类推。\n\n我们在这里简要介绍一下三个算法的实现思路。\n\n### 交集\n\n计算交集的过程大概可以分为三部分：\n\n1.  检查各个集合，对于不存在的集合当做空集来处理。一旦出现空集，则不用继续计算了，最终的交集就是空集。\n2.  对各个集合按照元素个数由少到多进行排序。这个排序有利于后面计算的时候从最小的集合开始，需要处理的元素个数较少。\n3.  对排序后第一个集合（也就是最小集合）进行遍历，对于它的每一个元素，依次在后面的所有集合中进行查找。只有在所有集合中都能找到的元素，才加入到最后的结果集合中。\n\n需要注意的是，上述第3步在集合中进行查找，对于intset和dict的存储来说时间复杂度分别是O(log n)和O(1)。但由于只有小集合才使用intset，所以可以粗略地认为intset的查找也是常数时间复杂度的。因此，如Redis官方文档上所说（[http://redis.io/commands/sinter](http://redis.io/commands/sinter)），`sinter`命令的时间复杂度为：\n\n> O(N*M) worst case where N is the cardinality of the smallest set and M is the number of sets.\n\n### 并集\n\n计算并集最简单，只需要遍历所有集合，将每一个元素都添加到最后的结果集合中。向集合中添加元素会自动去重。\n\n由于要遍历所有集合的每个元素，所以Redis官方文档给出的`sunion`命令的时间复杂度为（[http://redis.io/commands/sunion](http://redis.io/commands/sunion)）：\n\n> O(N) where N is the total number of elements in all given sets.\n\n注意，这里同前面讨论交集计算一样，将元素插入到结果集合的过程，忽略intset的情况，认为时间复杂度为O(1)。\n\n### 差集\n\n计算差集有两种可能的算法，它们的时间复杂度有所区别。\n\n第一种算法：\n\n*   对第一个集合进行遍历，对于它的每一个元素，依次在后面的所有集合中进行查找。只有在所有集合中都找不到的元素，才加入到最后的结果集合中。\n\n这种算法的时间复杂度为O(N*M)，其中N是第一个集合的元素个数，M是集合数目。\n\n第二种算法：\n\n*   将第一个集合的所有元素都加入到一个中间集合中。\n*   遍历后面所有的集合，对于碰到的每一个元素，从中间集合中删掉它。\n*   最后中间集合剩下的元素就构成了差集。\n\n这种算法的时间复杂度为O(N)，其中N是所有集合的元素个数总和。\n\n在计算差集的开始部分，会先分别估算一下两种算法预期的时间复杂度，然后选择复杂度低的算法来进行运算。还有两点需要注意：\n\n*   在一定程度上优先选择第一种算法，因为它涉及到的操作比较少，只用添加，而第二种算法要先添加再删除。\n*   如果选择了第一种算法，那么在执行该算法之前，Redis的实现中对于第二个集合之后的所有集合，按照元素个数由多到少进行了排序。这个排序有利于以更大的概率查找到元素，从而更快地结束查找。\n\n对于`sdiff`的时间复杂度，Redis官方文档（[http://redis.io/commands/sdiff](http://redis.io/commands/sdiff)）只给出了第二种算法的结果，是不准确的。\n\n* * *\n\n系列下一篇待续，敬请期待。\n\n\n\n**原创文章，转载请注明出处，并包含下面的二维码！否则拒绝转载！**\n**本文链接：**[http://zhangtielei.com/posts/blog-redis-intset.html](http://zhangtielei.com/posts/blog-redis-intset.html)\n\n![我的微信公众号: tielei-blog (张铁蕾)](http://zhangtielei.com/assets/my_weixin_sign_sf_840.jpg)\n"
  },
  {
    "path": "docs/cache/探索Redis设计与实现：Redis内部数据结构详解——quicklist.md",
    "content": "# 目录\n\n  * [quicklist概述](#quicklist概述)\n  * [quicklist的数据结构定义](#quicklist的数据结构定义)\n  * [quicklist的创建](#quicklist的创建)\n  * [quicklist的push操作](#quicklist的push操作)\n  * [quicklist的其它操作](#quicklist的其它操作)\n\n\n[toc]\n\n本文转自互联网\n\n本文将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，Redis基本的使用方法，Redis的基本数据结构，以及一些进阶的使用方法，同时也需要进一步了解Redis的底层数据结构，再接着，还会带来Redis主从复制、集群、分布式锁等方面的相关内容，以及作为缓存的一些使用方法和注意事项，以便让你更完整地了解整个Redis相关的技术体系，形成自己的知识框架。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->  \n\n本文是《[Redis内部数据结构详解](http://zhangtielei.com/posts/blog-redis-dict.html)》系列的第五篇。在本文中，我们介绍一个Redis内部数据结构——quicklist。Redis对外暴露的list数据类型，它底层实现所依赖的内部数据结构就是quicklist。\n\n我们在讨论中还会涉及到两个Redis配置（在redis.conf中的ADVANCED CONFIG部分）：\n\n\n\n\n\n```  \nlist-max-ziplist-size -2  \nlist-compress-depth 0  \n  \n```\n\n\n\n\n\n我们在讨论中会详细解释这两个配置的含义。\n\n注：本文讨论的quicklist实现基于Redis源码的3.2分支。\n\n## quicklist概述\n\nRedis对外暴露的上层list数据类型，经常被用作队列使用。比如它支持的如下一些操作：\n\n*   `lpush`: 在左侧（即列表头部）插入数据。\n*   `rpop`: 在右侧（即列表尾部）删除数据。\n*   `rpush`: 在右侧（即列表尾部）插入数据。\n*   `lpop`: 在左侧（即列表头部）删除数据。\n\n这些操作都是O(1)时间复杂度的。\n\n当然，list也支持在任意中间位置的存取操作，比如`lindex`和`linsert`，但它们都需要对list进行遍历，所以时间复杂度较高，为O(N)。\n\n概况起来，list具有这样的一些特点：它是一个能维持数据项先后顺序的列表（各个数据项的先后顺序由插入位置决定），便于在表的两端追加和删除数据，而对于中间位置的存取具有O(N)的时间复杂度。这不正是一个双向链表所具有的特点吗？\n\nlist的内部实现quicklist正是一个双向链表。在quicklist.c的文件头部注释中，是这样描述quicklist的：\n\n> A doubly linked list of ziplists\n\n它确实是一个双向链表，而且是一个ziplist的双向链表。\n\n这是什么意思呢？\n\n我们知道，双向链表是由多个节点（Node）组成的。这个描述的意思是：quicklist的每个节点都是一个ziplist。ziplist我们已经在[上一篇](http://zhangtielei.com/posts/blog-redis-ziplist.html)介绍过。\n\nziplist本身也是一个能维持数据项先后顺序的列表（按插入位置），而且是一个内存紧缩的列表（各个数据项在内存上前后相邻）。比如，一个包含3个节点的quicklist，如果每个节点的ziplist又包含4个数据项，那么对外表现上，这个list就总共包含12个数据项。\n\nquicklist的结构为什么这样设计呢？总结起来，大概又是一个空间和时间的折中：\n\n*   双向链表便于在表的两端进行push和pop操作，但是它的内存开销比较大。首先，它在每个节点上除了要保存数据之外，还要额外保存两个指针；其次，双向链表的各个节点是单独的内存块，地址不连续，节点多了容易产生内存碎片。\n*   ziplist由于是一整块连续内存，所以存储效率很高。但是，它不利于修改操作，每次数据变动都会引发一次内存的realloc。特别是当ziplist长度很长的时候，一次realloc可能会导致大批量的数据拷贝，进一步降低性能。\n\n于是，结合了双向链表和ziplist的优点，quicklist就应运而生了。\n\n不过，这也带来了一个新问题：到底一个quicklist节点包含多长的ziplist合适呢？比如，同样是存储12个数据项，既可以是一个quicklist包含3个节点，而每个节点的ziplist又包含4个数据项，也可以是一个quicklist包含6个节点，而每个节点的ziplist又包含2个数据项。\n\n这又是一个需要找平衡点的难题。我们只从存储效率上分析一下：\n\n*   每个quicklist节点上的ziplist越短，则内存碎片越多。内存碎片多了，有可能在内存中产生很多无法被利用的小碎片，从而降低存储效率。这种情况的极端是每个quicklist节点上的ziplist只包含一个数据项，这就蜕化成一个普通的双向链表了。\n*   每个quicklist节点上的ziplist越长，则为ziplist分配大块连续内存空间的难度就越大。有可能出现内存里有很多小块的空闲空间（它们加起来很多），但却找不到一块足够大的空闲空间分配给ziplist的情况。这同样会降低存储效率。这种情况的极端是整个quicklist只有一个节点，所有的数据项都分配在这仅有的一个节点的ziplist里面。这其实蜕化成一个ziplist了。\n\n可见，一个quicklist节点上的ziplist要保持一个合理的长度。那到底多长合理呢？这可能取决于具体应用场景。实际上，Redis提供了一个配置参数`list-max-ziplist-size`，就是为了让使用者可以来根据自己的情况进行调整。\n\n\n\n\n\n```  \nlist-max-ziplist-size -2  \n  \n```\n\n\n\n\n\n我们来详细解释一下这个参数的含义。它可以取正值，也可以取负值。\n\n当取正值的时候，表示按照数据项个数来限定每个quicklist节点上的ziplist长度。比如，当这个参数配置成5的时候，表示每个quicklist节点的ziplist最多包含5个数据项。\n\n当取负值的时候，表示按照占用字节数来限定每个quicklist节点上的ziplist长度。这时，它只能取-1到-5这五个值，每个值含义如下：\n\n*   -5: 每个quicklist节点上的ziplist大小不能超过64 Kb。（注：1kb => 1024 bytes）\n*   -4: 每个quicklist节点上的ziplist大小不能超过32 Kb。\n*   -3: 每个quicklist节点上的ziplist大小不能超过16 Kb。\n*   -2: 每个quicklist节点上的ziplist大小不能超过8 Kb。（-2是Redis给出的默认值）\n*   -1: 每个quicklist节点上的ziplist大小不能超过4 Kb。\n\n另外，list的设计目标是能够用来存储很长的数据列表的。比如，Redis官网给出的这个教程：[Writing a simple Twitter clone with PHP and Redis](http://redis.io/topics/twitter-clone)，就是使用list来存储类似Twitter的timeline数据。\n\n当列表很长的时候，最容易被访问的很可能是两端的数据，中间的数据被访问的频率比较低（访问起来性能也很低）。如果应用场景符合这个特点，那么list还提供了一个选项，能够把中间的数据节点进行压缩，从而进一步节省内存空间。Redis的配置参数`list-compress-depth`就是用来完成这个设置的。\n\n\n\n\n\n```  \nlist-compress-depth 0  \n  \n```\n\n\n\n\n\n这个参数表示一个quicklist两端不被压缩的节点个数。注：这里的节点个数是指quicklist双向链表的节点个数，而不是指ziplist里面的数据项个数。实际上，一个quicklist节点上的ziplist，如果被压缩，就是整体被压缩的。\n\n参数`list-compress-depth`的取值含义如下：\n\n*   0: 是个特殊值，表示都不压缩。这是Redis的默认值。\n*   1: 表示quicklist两端各有1个节点不压缩，中间的节点压缩。\n*   2: 表示quicklist两端各有2个节点不压缩，中间的节点压缩。\n*   3: 表示quicklist两端各有3个节点不压缩，中间的节点压缩。\n*   依此类推…\n\n由于0是个特殊值，很容易看出quicklist的头节点和尾节点总是不被压缩的，以便于在表的两端进行快速存取。\n\nRedis对于quicklist内部节点的压缩算法，采用的[LZF](http://oldhome.schmorp.de/marc/liblzf.html)——一种无损压缩算法。\n\n## quicklist的数据结构定义\n\nquicklist相关的数据结构定义可以在quicklist.h中找到：\n\n\n\n\n\n```  \ntypedef struct quicklistNode {  \n    struct quicklistNode *prev;    struct quicklistNode *next;    unsigned char *zl;    unsigned int sz;             /* ziplist size in bytes */    unsigned int count : 16;     /* count of items in ziplist */    unsigned int encoding : 2;   /* RAW==1 or LZF==2 */    unsigned int container : 2;  /* NONE==1 or ZIPLIST==2 */    unsigned int recompress : 1; /* was this node previous compressed? */    unsigned int attempted_compress : 1; /* node can't compress; too small */    unsigned int extra : 10; /* more bits to steal for future usage */} quicklistNode;  \n  \ntypedef struct quicklistLZF {  \n    unsigned int sz; /* LZF size in bytes*/    char compressed[];} quicklistLZF;  \n  \ntypedef struct quicklist {  \n    quicklistNode *head;    quicklistNode *tail;    unsigned long count;        /* total count of all entries in all ziplists */    unsigned int len;           /* number of quicklistNodes */    int fill : 16;              /* fill factor for individual nodes */    unsigned int compress : 16; /* depth of end nodes not to compress;0=off */} quicklist;  \n  \n```\n\n\n\n\n\nquicklistNode结构代表quicklist的一个节点，其中各个字段的含义如下：\n\n*   prev: 指向链表前一个节点的指针。\n*   next: 指向链表后一个节点的指针。\n*   zl: 数据指针。如果当前节点的数据没有压缩，那么它指向一个ziplist结构；否则，它指向一个quicklistLZF结构。\n*   sz: 表示zl指向的ziplist的总大小（包括`zlbytes`, `zltail`, `zllen`, `zlend`和各个数据项）。需要注意的是：如果ziplist被压缩了，那么这个sz的值仍然是压缩前的ziplist大小。\n*   count: 表示ziplist里面包含的数据项个数。这个字段只有16bit。稍后我们会一起计算一下这16bit是否够用。\n*   encoding: 表示ziplist是否压缩了（以及用了哪个压缩算法）。目前只有两种取值：2表示被压缩了（而且用的是[LZF](http://oldhome.schmorp.de/marc/liblzf.html)压缩算法），1表示没有压缩。\n*   container: 是一个预留字段。本来设计是用来表明一个quicklist节点下面是直接存数据，还是使用ziplist存数据，或者用其它的结构来存数据（用作一个数据容器，所以叫container）。但是，在目前的实现中，这个值是一个固定的值2，表示使用ziplist作为数据容器。\n*   recompress: 当我们使用类似lindex这样的命令查看了某一项本来压缩的数据时，需要把数据暂时解压，这时就设置recompress=1做一个标记，等有机会再把数据重新压缩。\n*   attempted_compress: 这个值只对Redis的自动化测试程序有用。我们不用管它。\n*   extra: 其它扩展字段。目前Redis的实现里也没用上。\n\nquicklistLZF结构表示一个被压缩过的ziplist。其中：\n\n*   sz: 表示压缩后的ziplist大小。\n*   compressed: 是个柔性数组（[flexible array member](https://en.wikipedia.org/wiki/Flexible_array_member)），存放压缩后的ziplist字节数组。\n\n真正表示quicklist的数据结构是同名的quicklist这个struct：\n\n*   head: 指向头节点（左侧第一个节点）的指针。\n*   tail: 指向尾节点（右侧第一个节点）的指针。\n*   count: 所有ziplist数据项的个数总和。\n*   len: quicklist节点的个数。\n*   fill: 16bit，ziplist大小设置，存放`list-max-ziplist-size`参数的值。\n*   compress: 16bit，节点压缩深度设置，存放`list-compress-depth`参数的值。\n\n[![Redis quicklist 结构图](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/redis_quicklist_structure.png)](http://zhangtielei.com/assets/photos_redis/redis_quicklist_structure.png)\n\n上图是一个quicklist的结构图举例。图中例子对应的ziplist大小配置和节点压缩深度配置，如下：\n\n\n\n\n\n```  \nlist-max-ziplist-size 3  \nlist-compress-depth 2  \n  \n```\n\n\n\n\n\n这个例子中我们需要注意的几点是：\n\n*   两端各有2个橙黄色的节点，是没有被压缩的。它们的数据指针zl指向真正的ziplist。中间的其它节点是被压缩过的，它们的数据指针zl指向被压缩后的ziplist结构，即一个quicklistLZF结构。\n*   左侧头节点上的ziplist里有2项数据，右侧尾节点上的ziplist里有1项数据，中间其它节点上的ziplist里都有3项数据（包括压缩的节点内部）。这表示在表的两端执行过多次`push`和`pop`操作后的一个状态。\n\n现在我们来大概计算一下quicklistNode结构中的count字段这16bit是否够用。\n\n我们已经知道，ziplist大小受到`list-max-ziplist-size`参数的限制。按照正值和负值有两种情况：\n\n*   当这个参数取正值的时候，就是恰好表示一个quicklistNode结构中zl所指向的ziplist所包含的数据项的最大值。`list-max-ziplist-size`参数是由quicklist结构的fill字段来存储的，而fill字段是16bit，所以它所能表达的值能够用16bit来表示。\n*   当这个参数取负值的时候，能够表示的ziplist最大长度是64 Kb。而ziplist中每一个数据项，最少需要2个字节来表示：1个字节的`prevrawlen`，1个字节的`data`（`len`字段和`data`合二为一；详见[上一篇](http://zhangtielei.com/posts/blog-redis-ziplist.html)）。所以，ziplist中数据项的个数不会超过32 K，用16bit来表达足够了。\n\n实际上，在目前的quicklist的实现中，ziplist的大小还会受到另外的限制，根本不会达到这里所分析的最大值。\n\n下面进入代码分析阶段。\n\n## quicklist的创建\n\n当我们使用`lpush`或`rpush`命令第一次向一个不存在的list里面插入数据的时候，Redis会首先调用`quicklistCreate`接口创建一个空的quicklist。\n\n\n\n\n\n```  \nquicklist *quicklistCreate(void) {  \n    struct quicklist *quicklist;  \n    quicklist = zmalloc(sizeof(*quicklist));    quicklist->head = quicklist->tail = NULL;    quicklist->len = 0;    quicklist->count = 0;    quicklist->compress = 0;    quicklist->fill = -2;    return quicklist;}  \n  \n```\n\n\n\n\n\n在很多介绍数据结构的书上，实现双向链表的时候经常会多增加一个空余的头节点，主要是为了插入和删除操作的方便。从上面`quicklistCreate`的代码可以看出，quicklist是一个不包含空余头节点的双向链表（`head`和`tail`都初始化为NULL）。\n\n## quicklist的push操作\n\nquicklist的push操作是调用`quicklistPush`来实现的。\n\n\n\n\n\n```  \nvoid quicklistPush(quicklist *quicklist, void *value, const size_t sz,  \n                   int where) {    if (where == QUICKLIST_HEAD) {        quicklistPushHead(quicklist, value, sz);    } else if (where == QUICKLIST_TAIL) {        quicklistPushTail(quicklist, value, sz);    }}  \n  \n/* Add new entry to head node of quicklist.  \n * * Returns 0 if used existing head. * Returns 1 if new head created. */int quicklistPushHead(quicklist *quicklist, void *value, size_t sz) {  \n    quicklistNode *orig_head = quicklist->head;    if (likely(            _quicklistNodeAllowInsert(quicklist->head, quicklist->fill, sz))) {        quicklist->head->zl =            ziplistPush(quicklist->head->zl, value, sz, ZIPLIST_HEAD);        quicklistNodeUpdateSz(quicklist->head);    } else {        quicklistNode *node = quicklistCreateNode();        node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_HEAD);  \n        quicklistNodeUpdateSz(node);        _quicklistInsertNodeBefore(quicklist, quicklist->head, node);    }    quicklist->count++;    quicklist->head->count++;    return (orig_head != quicklist->head);}  \n  \n/* Add new entry to tail node of quicklist.  \n * * Returns 0 if used existing tail. * Returns 1 if new tail created. */int quicklistPushTail(quicklist *quicklist, void *value, size_t sz) {  \n    quicklistNode *orig_tail = quicklist->tail;    if (likely(            _quicklistNodeAllowInsert(quicklist->tail, quicklist->fill, sz))) {        quicklist->tail->zl =            ziplistPush(quicklist->tail->zl, value, sz, ZIPLIST_TAIL);        quicklistNodeUpdateSz(quicklist->tail);    } else {        quicklistNode *node = quicklistCreateNode();        node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_TAIL);  \n        quicklistNodeUpdateSz(node);        _quicklistInsertNodeAfter(quicklist, quicklist->tail, node);    }    quicklist->count++;    quicklist->tail->count++;    return (orig_tail != quicklist->tail);}  \n  \n```\n\n\n\n\n\n不管是在头部还是尾部插入数据，都包含两种情况：\n\n*   如果头节点（或尾节点）上ziplist大小没有超过限制（即`_quicklistNodeAllowInsert`返回1），那么新数据被直接插入到ziplist中（调用`ziplistPush`）。\n*   如果头节点（或尾节点）上ziplist太大了，那么新创建一个quicklistNode节点（对应地也会新创建一个ziplist），然后把这个新创建的节点插入到quicklist双向链表中（调用`_quicklistInsertNodeAfter`）。\n\n在`_quicklistInsertNodeAfter`的实现中，还会根据`list-compress-depth`的配置将里面的节点进行压缩。它的实现比较繁琐，我们这里就不展开讨论了。\n\n## quicklist的其它操作\n\nquicklist的操作较多，且实现细节都比较繁杂，这里就不一一分析源码了，我们简单介绍一些比较重要的操作。\n\nquicklist的pop操作是调用`quicklistPopCustom`来实现的。`quicklistPopCustom`的实现过程基本上跟quicklistPush相反，先从头部或尾部节点的ziplist中把对应的数据项删除，如果在删除后ziplist为空了，那么对应的头部或尾部节点也要删除。删除后还可能涉及到里面节点的解压缩问题。\n\nquicklist不仅实现了从头部或尾部插入，也实现了从任意指定的位置插入。`quicklistInsertAfter`和`quicklistInsertBefore`就是分别在指定位置后面和前面插入数据项。这种在任意指定位置插入数据的操作，情况比较复杂，有众多的逻辑分支。\n\n*   当插入位置所在的ziplist大小没有超过限制时，直接插入到ziplist中就好了；\n*   当插入位置所在的ziplist大小超过了限制，但插入的位置位于ziplist两端，并且相邻的quicklist链表节点的ziplist大小没有超过限制，那么就转而插入到相邻的那个quicklist链表节点的ziplist中；\n*   当插入位置所在的ziplist大小超过了限制，但插入的位置位于ziplist两端，并且相邻的quicklist链表节点的ziplist大小也超过限制，这时需要新创建一个quicklist链表节点插入。\n*   对于插入位置所在的ziplist大小超过了限制的其它情况（主要对应于在ziplist中间插入数据的情况），则需要把当前ziplist分裂为两个节点，然后再其中一个节点上插入数据。\n\n`quicklistSetOptions`用于设置ziplist大小配置参数（`list-max-ziplist-size`）和节点压缩深度配置参数（`list-compress-depth`）。代码比较简单，就是将相应的值分别设置给quicklist结构的fill字段和compress字段。\n\n* * *\n\n下一篇我们将介绍skiplist和它所支撑的Redis数据类型sorted set，敬请期待。\n\n**原创文章，转载请注明出处，并包含下面的二维码！否则拒绝转载！**  \n**本文链接：**[http://zhangtielei.com/posts/blog-redis-quicklist.html](http://zhangtielei.com/posts/blog-redis-quicklist.html)\n\n![我的微信公众号: tielei-blog (张铁蕾)](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/my_weixin_sign_sf_840.jpg)\n"
  },
  {
    "path": "docs/cache/探索Redis设计与实现：Redis内部数据结构详解——sds.md",
    "content": "# 目录\n\n  * [前言](#前言)\n  * [sds的数据结构定义](#sds的数据结构定义)\n  * [Redis dict结构举例](#redis-dict结构举例)\n  * [sds的创建和销毁](#sds的创建和销毁)\n  * [sds的连接（追加）操作](#sds的连接（追加）操作)\n  * [浅谈sds与string的关系](#浅谈sds与string的关系)\n\n\n[toc]\n\n本文转自互联网\n\n本文将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，Redis基本的使用方法，Redis的基本数据结构，以及一些进阶的使用方法，同时也需要进一步了解Redis的底层数据结构，再接着，还会带来Redis主从复制、集群、分布式锁等方面的相关内容，以及作为缓存的一些使用方法和注意事项，以便让你更完整地了解整个Redis相关的技术体系，形成自己的知识框架。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->  \n## 前言\n\n本文是《Redis内部数据结构详解》系列的第二篇，讲述Redis中使用最多的一个基础数据结构：sds。\n\n不管在哪门编程语言当中，字符串都几乎是使用最多的数据结构。sds正是在Redis中被广泛使用的字符串结构，它的全称是Simple Dynamic String。与其它语言环境中出现的字符串相比，它具有如下显著的特点：\n\n可动态扩展内存。sds表示的字符串其内容可以修改，也可以追加。在很多语言中字符串会分为mutable和immutable两种，显然sds属于mutable类型的。  \n二进制安全（Binary Safe）。sds能存储任意二进制数据，而不仅仅是可打印字符。  \n与传统的C语言字符串类型兼容。这个的含义接下来马上会讨论。  \n看到这里，很多对Redis有所了解的同学可能已经产生了一个疑问：Redis已经对外暴露了一个字符串结构，叫做string，那这里所说的sds到底和string是什么关系呢？可能有人会猜：string是基于sds实现的。这个猜想已经非常接近事实，但在描述上还不太准确。有关string和sds之间关系的详细分析，我们放在后面再讲。现在为了方便讨论，让我们先暂时简单地认为，string的底层实现就是sds。\n\n在讨论sds的具体实现之前，我们先站在Redis使用者的角度，来观察一下string所支持的一些主要操作。下面是一个操作示例：\n\n![image](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/redis_string_op_examples.png)  \nRedis string操作示例\n\n\n\n以上这些操作都比较简单，我们简单解释一下：\n\n初始的字符串的值设为”tielei”。  \n第3步通过append命令对字符串进行了追加，变成了”tielei zhang”。  \n然后通过setbit命令将第53个bit设置成了1。bit的偏移量从左边开始算，从0开始。其中第48～55bit是中间的空格那个字符，它的ASCII码是0x20。将第53个bit设置成1之后，它的ASCII码变成了0x24，打印出来就是’$’。因此，现在字符串的值变成了”tielei$zhang”。  \n最后通过getrange取从倒数第5个字节到倒数第1个字节的内容，得到”zhang”。  \n这些命令的实现，有一部分是和sds的实现有关的。下面我们开始详细讨论。\n\n## sds的数据结构定义\n我们知道，在C语言中，字符串是以’\\0’字符结尾（NULL结束符）的字符数组来存储的，通常表达为字符指针的形式（char *）。它不允许字节0出现在字符串中间，因此，它不能用来存储任意的二进制数据。\n\n我们可以在sds.h中找到sds的类型定义：\n\ntypedef char *sds;  \n肯定有人感到困惑了，竟然sds就等同于char *？我们前面提到过，sds和传统的C语言字符串保持类型兼容，因此它们的类型定义是一样的，都是char *。在有些情况下，需要传入一个C语言字符串的地方，也确实可以传入一个sds。但是，sds和char *并不等同。sds是Binary Safe的，它可以存储任意二进制数据，不能像C语言字符串那样以字符’\\0’来标识字符串的结束，因此它必然有个长度字段。但这个长度字段在哪里呢？实际上sds还包含一个header结构：\n````\n    struct __attribute__ ((__packed__)) sdshdr5 {        unsigned char flags; /* 3 lsb of type, and 5 msb of string length */        char buf[];    };    struct __attribute__ ((__packed__)) sdshdr8 {        uint8_t len; /* used */        uint8_t alloc; /* excluding the header and null terminator */        unsigned char flags; /* 3 lsb of type, 5 unused bits */        char buf[];    };    struct __attribute__ ((__packed__)) sdshdr16 {        uint16_t len; /* used */        uint16_t alloc; /* excluding the header and null terminator */        unsigned char flags; /* 3 lsb of type, 5 unused bits */        char buf[];    };    struct __attribute__ ((__packed__)) sdshdr32 {        uint32_t len; /* used */        uint32_t alloc; /* excluding the header and null terminator */        unsigned char flags; /* 3 lsb of type, 5 unused bits */        char buf[];    };    struct __attribute__ ((__packed__)) sdshdr64 {        uint64_t len; /* used */        uint64_t alloc; /* excluding the header and null terminator */        unsigned char flags; /* 3 lsb of type, 5 unused bits */        char buf[];    };sds一共有5种类型的header。之所以有5种，是为了能让不同长度的字符串可以使用不同大小的header。这样，短字符串就能使用较小的header，从而节省内存。  \n````\n一个sds字符串的完整结构，由在内存地址上前后相邻的两部分组成：\n\n一个header。通常包含字符串的长度(len)、最大容量(alloc)和flags。sdshdr5有所不同。  \n一个字符数组。这个字符数组的长度等于最大容量+1。真正有效的字符串数据，其长度通常小于最大容量。在真正的字符串数据之后，是空余未用的字节（一般以字节0填充），允许在不重新分配内存的前提下让字符串数据向后做有限的扩展。在真正的字符串数据之后，还有一个NULL结束符，即ASCII码为0的’\\0’字符。这是为了和传统C字符串兼容。之所以字符数组的长度比最大容量多1个字节，就是为了在字符串长度达到最大容量时仍然有1个字节存放NULL结束符。  \n除了sdshdr5之外，其它4个header的结构都包含3个字段：\n\n    len: 表示字符串的真正长度（不包含NULL结束符在内）。  \n    alloc: 表示字符串的最大容量（不包含最后多余的那个字节）。  \n    flags: 总是占用一个字节。其中的最低3个bit用来表示header的类型。header的类型共有5种，在sds.h中有常量定义。  \n    #define SDS_TYPE_5  0    #define SDS_TYPE_8  1    #define SDS_TYPE_16 2    #define SDS_TYPE_32 3    #define SDS_TYPE_64 4sds的数据结构，我们有必要非常仔细地去解析它。  \n\n## Redis dict结构举例\n\n上图是sds的一个内部结构的例子。图中展示了两个sds字符串s1和s2的内存结构，一个使用sdshdr8类型的header，另一个使用sdshdr16类型的header。但它们都表达了同样的一个长度为6的字符串的值：”tielei”。下面我们结合代码，来解释每一部分的组成。\n\nsds的字符指针（s1和s2）就是指向真正的数据（字符数组）开始的位置，而header位于内存地址较低的方向。在sds.h中有一些跟解析header有关的宏定义：\n````\n    #define SDS_TYPE_MASK 7    #define SDS_TYPE_BITS 3    #define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T)));    #define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))    #define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS)其中SDS_HDR用来从sds字符串获得header起始位置的指针，比如SDS_HDR(8, s1)表示s1的header指针，SDS_HDR(16, s2)表示s2的header指针。  \n````\n当然，使用SDS_HDR之前我们必须先知道到底是哪一种header，这样我们才知道SDS_HDR第1个参数应该传什么。由sds字符指针获得header类型的方法是，先向低地址方向偏移1个字节的位置，得到flags字段。比如，s1[-1]和s2[-1]分别获得了s1和s2的flags的值。然后取flags的最低3个bit得到header的类型。\n\n由于s1[-1] == 0x01 == SDS_TYPE_8，因此s1的header类型是sdshdr8。  \n由于s2[-1] == 0x02 == SDS_TYPE_16，因此s2的header类型是sdshdr16。  \n有了header指针，就能很快定位到它的len和alloc字段：\n\ns1的header中，len的值为0x06，表示字符串数据长度为6；alloc的值为0x80，表示字符数组最大容量为128。  \ns2的header中，len的值为0x0006，表示字符串数据长度为6；alloc的值为0x03E8，表示字符数组最大容量为1000。（注意：图中是按小端地址构成）  \n在各个header的类型定义中，还有几个需要我们注意的地方：\n\n在各个header的定义中使用了__attribute__ ((packed))，是为了让编译器以紧凑模式来分配内存。如果没有这个属性，编译器可能会为struct的字段做优化对齐，在其中填充空字节。那样的话，就不能保证header和sds的数据部分紧紧前后相邻，也不能按照固定向低地址方向偏移1个字节的方式来获取flags字段了。\n\n在各个header的定义中最后有一个char buf[]。我们注意到这是一个没有指明长度的字符数组，这是C语言中定义字符数组的一种特殊写法，称为柔性数组（flexible array member），只能定义在一个结构体的最后一个字段上。\n\n它在这里只是起到一个标记的作用，表示在flags字段后面就是一个字符数组，或者说，它指明了紧跟在flags字段后面的这个字符数组在结构体中的偏移位置。而程序在为header分配的内存的时候，它并不占用内存空间。\n\n如果计算sizeof(struct sdshdr16)的值，那么结果是5个字节，其中没有buf字段。  \nsdshdr5与其它几个header结构不同，它不包含alloc字段，而长度使用flags的高5位来存储。\n\n因此，它不能为字符串分配空余空间。如果字符串需要动态增长，那么它就必然要重新分配内存才行。所以说，这种类型的sds字符串更适合存储静态的短字符串（长度小于32）。\n\n至此，我们非常清楚地看到了：sds字符串的header，其实隐藏在真正的字符串数据的前面（低地址方向）。这样的一个定义，有如下几个好处：\n\nheader和数据相邻，而不用分成两块内存空间来单独分配。这有利于减少内存碎片，提高存储效率（memory efficiency）。\n\n虽然header有多个类型，但sds可以用统一的char *来表达。且它与传统的C语言字符串保持类型兼容。\n\n如果一个sds里面存储的是可打印字符串，那么我们可以直接把它传给C函数，比如使用strcmp比较字符串大小，或者使用printf进行打印。  \n弄清了sds的数据结构，它的具体操作函数就比较好理解了。\n\n    sds的一些基础函数  \n    sdslen(const sds s): 获取sds字符串长度。  \n    sdssetlen(sds s, size_t newlen): 设置sds字符串长度。  \n    sdsinclen(sds s, size_t inc): 增加sds字符串长度。  \n    sdsalloc(const sds s): 获取sds字符串容量。  \n    sdssetalloc(sds s, size_t newlen): 设置sds字符串容量。  \n    sdsavail(const sds s): 获取sds字符串空余空间（即alloc - len）。  \n    sdsHdrSize(char type): 根据header类型得到header大小。  \n    sdsReqType(size_t string_size):  \n根据字符串数据长度计算所需要的header类型。  \n这里我们挑选sdslen和sdsReqType的代码，察看一下。\n````\n    static inline size_t sdslen(const sds s) {        unsigned char flags = s[-1];        switch(flags&SDS_TYPE_MASK) {            case SDS_TYPE_5:                return SDS_TYPE_5_LEN(flags);            case SDS_TYPE_8:                return SDS_HDR(8,s)->len;            case SDS_TYPE_16:                return SDS_HDR(16,s)->len;            case SDS_TYPE_32:                return SDS_HDR(32,s)->len;            case SDS_TYPE_64:                return SDS_HDR(64,s)->len;        }        return 0;    }     static inline char sdsReqType(size_t string_size) {  \n        if (string_size < 1<<5)            return SDS_TYPE_5;        if (string_size < 1<<8)            return SDS_TYPE_8;        if (string_size < 1<<16)            return SDS_TYPE_16;        if (string_size < 1ll<<32)            return SDS_TYPE_32;        return SDS_TYPE_64;    }    跟前面的分析类似，sdslen先用s[-1]向低地址方向偏移1个字节，得到flags；然后与SDS_TYPE_MASK进行按位与，得到header类型；然后根据不同的header类型，调用SDS_HDR得到header起始指针，进而获得len字段。  \n````\n通过sdsReqType的代码，很容易看到：\n\n长度在0和2^5-1之间，选用SDS_TYPE_5类型的header。  \n长度在2^5和2^8-1之间，选用SDS_TYPE_8类型的header。  \n长度在2^8和2^16-1之间，选用SDS_TYPE_16类型的header。  \n长度在2^16和2^32-1之间，选用SDS_TYPE_32类型的header。  \n长度大于2^32的，选用SDS_TYPE_64类型的header。能表示的最大长度为2^64-1。  \n注：sdsReqType的实现代码，直到3.2.0，它在长度边界值上都一直存在问题，直到最近3.2 branch上的commit 6032340才修复。\n\n## sds的创建和销毁\n````\n    sds sdsnewlen(const void *init, size_t initlen) {        void *sh;        sds s;        char type = sdsReqType(initlen);        /* Empty strings are usually created in order to append. Use type 8         * since type 5 is not good at this. */        if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;        int hdrlen = sdsHdrSize(type);        unsigned char *fp; /* flags pointer. */             sh = s_malloc(hdrlen+initlen+1);  \n        if (!init)            memset(sh, 0, hdrlen+initlen+1);        if (sh == NULL) return NULL;        s = (char*)sh+hdrlen;        fp = ((unsigned char*)s)-1;        switch(type) {            case SDS_TYPE_5: {                *fp = type | (initlen << SDS_TYPE_BITS);                break;            }            case SDS_TYPE_8: {                SDS_HDR_VAR(8,s);                sh->len = initlen;                sh->alloc = initlen;                *fp = type;                break;            }            case SDS_TYPE_16: {                SDS_HDR_VAR(16,s);                sh->len = initlen;                sh->alloc = initlen;                *fp = type;                break;            }            case SDS_TYPE_32: {                SDS_HDR_VAR(32,s);                sh->len = initlen;                sh->alloc = initlen;                *fp = type;                break;            }            case SDS_TYPE_64: {                SDS_HDR_VAR(64,s);                sh->len = initlen;                sh->alloc = initlen;                *fp = type;                break;            }        }        if (initlen && init)            memcpy(s, init, initlen);        s[initlen] = '\\0';        return s;    }         sds sdsempty(void) {  \n        return sdsnewlen(\"\",0);    }         sds sdsnew(const char *init) {  \n        size_t initlen = (init == NULL) ? 0 : strlen(init);        return sdsnewlen(init, initlen);    }         void sdsfree(sds s) {  \n        if (s == NULL) return;        s_free((char*)s-sdsHdrSize(s[-1]));    }          \n````\nsdsnewlen创建一个长度为initlen的sds字符串，并使用init指向的字符数组（任意二进制数据）来初始化数据。如果init为NULL，那么使用全0来初始化数据。它的实现中，我们需要注意的是：\n\n如果要创建一个长度为0的空字符串，那么不使用SDS_TYPE_5类型的header，而是转而使用SDS_TYPE_8类型的header。这是因为创建的空字符串一般接下来的操作很可能是追加数据，但SDS_TYPE_5类型的sds字符串不适合追加数据（会引发内存重新分配）。  \n需要的内存空间一次性进行分配，其中包含三部分：header、数据、最后的多余字节（hdrlen+initlen+1）。  \n初始化的sds字符串数据最后会追加一个NULL结束符（s[initlen] = ‘\\0’）。  \n关于sdsfree，需要注意的是：内存要整体释放，所以要先计算出header起始指针，把它传给s_free函数。这个指针也正是在sdsnewlen中调用s_malloc返回的那个地址。\n\n## sds的连接（追加）操作\n````\n    sds sdscatlen(sds s, const void *t, size_t len) {        size_t curlen = sdslen(s);             s = sdsMakeRoomFor(s,len);  \n        if (s == NULL) return NULL;        memcpy(s+curlen, t, len);        sdssetlen(s, curlen+len);        s[curlen+len] = '\\0';        return s;    }         sds sdscat(sds s, const char *t) {  \n        return sdscatlen(s, t, strlen(t));    }         sds sdscatsds(sds s, const sds t) {  \n        return sdscatlen(s, t, sdslen(t));    }         sds sdsMakeRoomFor(sds s, size_t addlen) {  \n        void *sh, *newsh;        size_t avail = sdsavail(s);        size_t len, newlen;        char type, oldtype = s[-1] & SDS_TYPE_MASK;        int hdrlen;             /* Return ASAP if there is enough space left. */  \n        if (avail >= addlen) return s;             len = sdslen(s);  \n        sh = (char*)s-sdsHdrSize(oldtype);        newlen = (len+addlen);        if (newlen < SDS_MAX_PREALLOC)            newlen *= 2;        else            newlen += SDS_MAX_PREALLOC;             type = sdsReqType(newlen);  \n             /* Don't use type 5: the user is appending to the string and type 5 is  \n         * not able to remember empty space, so sdsMakeRoomFor() must be called         * at every appending operation. */        if (type == SDS_TYPE_5) type = SDS_TYPE_8;             hdrlen = sdsHdrSize(type);  \n        if (oldtype==type) {            newsh = s_realloc(sh, hdrlen+newlen+1);            if (newsh == NULL) return NULL;            s = (char*)newsh+hdrlen;        } else {            /* Since the header size changes, need to move the string forward,             * and can't use realloc */            newsh = s_malloc(hdrlen+newlen+1);            if (newsh == NULL) return NULL;            memcpy((char*)newsh+hdrlen, s, len+1);            s_free(sh);            s = (char*)newsh+hdrlen;            s[-1] = type;            sdssetlen(s, len);        }        sdssetalloc(s, newlen);        return s;    }    sdscatlen将t指向的长度为len的任意二进制数据追加到sds字符串s的后面。本文开头演示的string的append命令，内部就是调用sdscatlen来实现的。  \n````\n在sdscatlen的实现中，先调用sdsMakeRoomFor来保证字符串s有足够的空间来追加长度为len的数据。sdsMakeRoomFor可能会分配新的内存，也可能不会。\n\nsdsMakeRoomFor是sds实现中很重要的一个函数。关于它的实现代码，我们需要注意的是：\n\n如果原来字符串中的空余空间够用（avail >= addlen），那么它什么也不做，直接返回。\n\n如果需要分配空间，它会比实际请求的要多分配一些，以防备接下来继续追加。它在字符串已经比较长的情况下要至少多分配SDS_MAX_PREALLOC个字节，这个常量在sds.h中定义为(1024*1024)=1MB。\n\n按分配后的空间大小，可能需要更换header类型（原来header的alloc字段太短，表达不了增加后的容量）。\n\n如果需要更换header，那么整个字符串空间（包括header）都需要重新分配（s_malloc），并拷贝原来的数据到新的位置。\n\n如果不需要更换header（原来的header够用），那么调用一个比较特殊的s_realloc，试图在原来的地址上重新分配空间。s_realloc的具体实现得看Redis编译的时候选用了哪个allocator（在Linux上默认使用jemalloc）。\n\n但不管是哪个realloc的实现，它所表达的含义基本是相同的：它尽量在原来分配好的地址位置重新分配，如果原来的地址位置有足够的空余空间完成重新分配，那么它返回的新地址与传入的旧地址相同；否则，它分配新的地址块，并进行数据搬迁。参见http://man.cx/realloc。\n\n从sdscatlen的函数接口，我们可以看到一种使用模式：调用它的时候，传入一个旧的sds变量，然后它返回一个新的sds变量。由于它的内部实现可能会造成地址变化，因此调用者在调用完之后，原来旧的变量就失效了，而都应该用新返回的变量来替换。不仅仅是sdscatlen函数，sds中的其它函数（比如sdscpy、sdstrim、sdsjoin等），还有Redis中其它一些能自动扩展内存的数据结构（如ziplist），也都是同样的使用模式。\n\n## 浅谈sds与string的关系\n\n现在我们回过头来看看本文开头给出的string操作的例子。\n\nappend操作使用sds的sdscatlen来实现。前面已经提到。\n\nsetbit和getrange都是先根据key取到整个sds字符串，然后再从字符串选取或修改指定的部分。由于sds就是一个字符数组，所以对它的某一部分进行操作似乎都比较简单。\n\n但是，string除了支持这些操作之外，当它存储的值是个数字的时候，它还支持incr、decr等操作。那么，当string存储数字值的时候，它的内部存储还是sds吗？\n\n实际上，不是了。而且，这种情况下，setbit和getrange的实现也会有所不同。这些细节，我们放在下一篇介绍robj的时候再进行系统地讨论。\n"
  },
  {
    "path": "docs/cache/探索Redis设计与实现：Redis内部数据结构详解——skiplist.md",
    "content": "# 目录\n\n  * [skiplist数据结构简介](#skiplist数据结构简介)\n  * [skiplist的算法性能分析](#skiplist的算法性能分析)\n  * [skiplist与平衡树、哈希表的比较](#skiplist与平衡树、哈希表的比较)\n  * [Redis中的skiplist实现](#redis中的skiplist实现)\n    * [sorted set的命令举例](#sorted-set的命令举例)\n    * [Redis中skiplist实现的特殊性](#redis中skiplist实现的特殊性)\n    * [skiplist的数据结构定义](#skiplist的数据结构定义)\n  * [Redis中的sorted set](#redis中的sorted-set)\n  * [Redis为什么用skiplist而不用平衡树？](#redis为什么用skiplist而不用平衡树？)\n\n\n[toc]\n\n\n本文转自互联网\n\n本文将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，Redis基本的使用方法，Redis的基本数据结构，以及一些进阶的使用方法，同时也需要进一步了解Redis的底层数据结构，再接着，还会带来Redis主从复制、集群、分布式锁等方面的相关内容，以及作为缓存的一些使用方法和注意事项，以便让你更完整地了解整个Redis相关的技术体系，形成自己的知识框架。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n本文是《[Redis内部数据结构详解](http://zhangtielei.com/posts/blog-redis-dict.html)》系列的第六篇。在本文中，我们围绕一个Redis的内部数据结构——skiplist展开讨论。\n\nRedis里面使用skiplist是为了实现sorted set这种对外的数据结构。sorted set提供的操作非常丰富，可以满足非常多的应用场景。这也意味着，sorted set相对来说实现比较复杂。同时，skiplist这种数据结构对于很多人来说都比较陌生，因为大部分学校里的算法课都没有对这种数据结构进行过详细的介绍。因此，为了介绍得足够清楚，本文会比这个系列的其它几篇花费更多的篇幅。\n\n我们将大体分成三个部分进行介绍：\n\n1.  介绍经典的skiplist数据结构，并进行简单的算法分析。这一部分的介绍，与Redis没有直接关系。我会尝试尽量使用通俗易懂的语言进行描述。\n2.  讨论Redis里的skiplist的具体实现。为了支持sorted set本身的一些要求，在经典的skiplist基础上，Redis里的相应实现做了若干改动。\n3.  讨论sorted set是如何在skiplist, dict和ziplist基础上构建起来的。\n\n我们在讨论中还会涉及到两个Redis配置（在redis.conf中的ADVANCED CONFIG部分）：\n\n\n\n\n\n```\nzset-max-ziplist-entries 128\nzset-max-ziplist-value 64\n\n```\n\n\n\n\n\n我们在讨论中会详细解释这两个配置的含义。\n\n注：本文讨论的代码实现基于Redis源码的3.2分支。\n\n## skiplist数据结构简介\n\nskiplist本质上也是一种查找结构，用于解决算法中的查找问题（Searching），即根据给定的key，快速查到它所在的位置（或者对应的value）。\n\n我们在《Redis内部数据结构详解》系列的[第一篇](http://zhangtielei.com/posts/blog-redis-dict.html)中介绍dict的时候，曾经讨论过：一般查找问题的解法分为两个大类：一个是基于各种平衡树，一个是基于哈希表。但skiplist却比较特殊，它没法归属到这两大类里面。\n\n这种数据结构是由[William Pugh](https://en.wikipedia.org/wiki/William_Pugh)发明的，最早出现于他在1990年发表的论文《[Skip Lists: A Probabilistic Alternative to Balanced Trees](ftp://ftp.cs.umd.edu/pub/skipLists/skiplists.pdf)》。对细节感兴趣的同学可以下载论文原文来阅读。\n\nskiplist，顾名思义，首先它是一个list。实际上，它是在有序链表的基础上发展起来的。\n\n我们先来看一个有序链表，如下图（最左侧的灰色节点表示一个空的头结点）：\n\n![[有序链表结构图](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/sorted_linked_list.png)](http://zhangtielei.com/assets/photos_redis/skiplist/sorted_linked_list.png)\n\n在这样一个链表中，如果我们要查找某个数据，那么需要从头开始逐个进行比较，直到找到包含数据的那个节点，或者找到第一个比给定数据大的节点为止（没找到）。也就是说，时间复杂度为O(n)。同样，当我们要插入新数据的时候，也要经历同样的查找过程，从而确定插入位置。\n\n假如我们每相邻两个节点增加一个指针，让指针指向下下个节点，如下图：\n\n[![每两个节点增加一个跳跃指针的有序链表](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/skip2node_linked_list.png)](http://zhangtielei.com/assets/photos_redis/skiplist/skip2node_linked_list.png)\n\n这样所有新增加的指针连成了一个新的链表，但它包含的节点个数只有原来的一半（上图中是7, 19, 26）。现在当我们想查找数据的时候，可以先沿着这个新链表进行查找。当碰到比待查数据大的节点时，再回到原来的链表中进行查找。比如，我们想查找23，查找的路径是沿着下图中标红的指针所指向的方向进行的：\n\n[![一个搜索路径的例子](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/search_path_on_skip2node_list.png)](http://zhangtielei.com/assets/photos_redis/skiplist/search_path_on_skip2node_list.png)\n\n*   23首先和7比较，再和19比较，比它们都大，继续向后比较。\n*   但23和26比较的时候，比26要小，因此回到下面的链表（原链表），与22比较。\n*   23比22要大，沿下面的指针继续向后和26比较。23比26小，说明待查数据23在原链表中不存在，而且它的插入位置应该在22和26之间。\n\n在这个查找过程中，由于新增加的指针，我们不再需要与链表中每个节点逐个进行比较了。需要比较的节点数大概只有原来的一半。\n\n利用同样的方式，我们可以在上层新产生的链表上，继续为每相邻的两个节点增加一个指针，从而产生第三层链表。如下图：\n\n[![两层跳跃指针](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/skip2node_level3_linked_list.png)](http://zhangtielei.com/assets/photos_redis/skiplist/skip2node_level3_linked_list.png)\n\n在这个新的三层链表结构上，如果我们还是查找23，那么沿着最上层链表首先要比较的是19，发现23比19大，接下来我们就知道只需要到19的后面去继续查找，从而一下子跳过了19前面的所有节点。可以想象，当链表足够长的时候，这种多层链表的查找方式能让我们跳过很多下层节点，大大加快查找的速度。\n\nskiplist正是受这种多层链表的想法的启发而设计出来的。实际上，按照上面生成链表的方式，上面每一层链表的节点个数，是下面一层的节点个数的一半，这样查找过程就非常类似于一个二分查找，使得查找的时间复杂度可以降低到O(log n)。但是，这种方法在插入数据的时候有很大的问题。新插入一个节点之后，就会打乱上下相邻两层链表上节点个数严格的2:1的对应关系。如果要维持这种对应关系，就必须把新插入的节点后面的所有节点（也包括新插入的节点）重新进行调整，这会让时间复杂度重新蜕化成O(n)。删除数据也有同样的问题。\n\nskiplist为了避免这一问题，它不要求上下相邻两层链表之间的节点个数有严格的对应关系，而是为每个节点随机出一个层数(level)。比如，一个节点随机出的层数是3，那么就把它链入到第1层到第3层这三层链表中。为了表达清楚，下图展示了如何通过一步步的插入操作从而形成一个skiplist的过程：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230406205225.png)\n\n从上面skiplist的创建和插入过程可以看出，每一个节点的层数（level）是随机出来的，而且新插入一个节点不会影响其它节点的层数。因此，插入操作只需要修改插入节点前后的指针，而不需要对很多节点都进行调整。这就降低了插入操作的复杂度。实际上，这是skiplist的一个很重要的特性，这让它在插入性能上明显优于平衡树的方案。这在后面我们还会提到。\n\n根据上图中的skiplist结构，我们很容易理解这种数据结构的名字的由来。skiplist，翻译成中文，可以翻译成“跳表”或“跳跃表”，指的就是除了最下面第1层链表之外，它会产生若干层稀疏的链表，这些链表里面的指针故意跳过了一些节点（而且越高层的链表跳过的节点越多）。这就使得我们在查找数据的时候能够先在高层的链表中进行查找，然后逐层降低，最终降到第1层链表来精确地确定数据位置。在这个过程中，我们跳过了一些节点，从而也就加快了查找速度。\n\n刚刚创建的这个skiplist总共包含4层链表，现在假设我们在它里面依然查找23，下图给出了查找路径：\n\n[![skiplist上的查找路径展示](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/search_path_on_skiplist.png)](http://zhangtielei.com/assets/photos_redis/skiplist/search_path_on_skiplist.png)\n\n需要注意的是，前面演示的各个节点的插入过程，实际上在插入之前也要先经历一个类似的查找过程，在确定插入位置后，再完成插入操作。\n\n至此，skiplist的查找和插入操作，我们已经很清楚了。而删除操作与插入操作类似，我们也很容易想象出来。这些操作我们也应该能很容易地用代码实现出来。\n\n当然，实际应用中的skiplist每个节点应该包含key和value两部分。前面的描述中我们没有具体区分key和value，但实际上列表中是按照key进行排序的，查找过程也是根据key在比较。\n\n但是，如果你是第一次接触skiplist，那么一定会产生一个疑问：节点插入时随机出一个层数，仅仅依靠这样一个简单的随机数操作而构建出来的多层链表结构，能保证它有一个良好的查找性能吗？为了回答这个疑问，我们需要分析skiplist的统计性能。\n\n在分析之前，我们还需要着重指出的是，执行插入操作时计算随机数的过程，是一个很关键的过程，它对skiplist的统计特性有着很重要的影响。这并不是一个普通的服从均匀分布的随机数，它的计算过程如下：\n\n*   首先，每个节点肯定都有第1层指针（每个节点都在第1层链表里）。\n*   如果一个节点有第i层(i>=1)指针（即节点已经在第1层到第i层链表中），那么它有第(i+1)层指针的概率为p。\n*   节点最大的层数不允许超过一个最大值，记为MaxLevel。\n\n这个计算随机层数的伪码如下所示：\n\n\n\n\n\n```\nrandomLevel()\n    level := 1\n    // random()返回一个[0...1)的随机数\n    while random() < p and level < MaxLevel do\n        level := level + 1\n    return level\n\n```\n\n\n\n\n\nrandomLevel()的伪码中包含两个参数，一个是p，一个是MaxLevel。在Redis的skiplist实现中，这两个参数的取值为：\n\n\n\n\n\n```\np = 1/4\nMaxLevel = 32\n\n```\n\n\n\n\n\n## skiplist的算法性能分析\n\n在这一部分，我们来简单分析一下skiplist的时间复杂度和空间复杂度，以便对于skiplist的性能有一个直观的了解。如果你不是特别偏执于算法的性能分析，那么可以暂时跳过这一小节的内容。\n\n我们先来计算一下每个节点所包含的平均指针数目（概率期望）。节点包含的指针数目，相当于这个算法在空间上的额外开销(overhead)，可以用来度量空间复杂度。\n\n根据前面randomLevel()的伪码，我们很容易看出，产生越高的节点层数，概率越低。定量的分析如下：\n\n*   节点层数至少为1。而大于1的节点层数，满足一个概率分布。\n*   节点层数恰好等于1的概率为1-p。\n*   节点层数大于等于2的概率为p，而节点层数恰好等于2的概率为p(1-p)。\n*   节点层数大于等于3的概率为p<sup>2</sup>，而节点层数恰好等于3的概率为p<sup>2</sup>(1-p)。\n*   节点层数大于等于4的概率为p<sup>3</sup>，而节点层数恰好等于4的概率为p<sup>3</sup>(1-p)。\n*   ……\n\n因此，一个节点的平均层数（也即包含的平均指针数目），计算如下：\n\n[![skiplist平均层数计算](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/skiplist_avg_level.png)](http://zhangtielei.com/assets/photos_redis/skiplist/skiplist_avg_level.png)\n\n现在很容易计算出：\n\n*   当p=1/2时，每个节点所包含的平均指针数目为2；\n*   当p=1/4时，每个节点所包含的平均指针数目为1.33。这也是Redis里的skiplist实现在空间上的开销。\n\n接下来，为了分析时间复杂度，我们计算一下skiplist的平均查找长度。查找长度指的是查找路径上跨越的跳数，而查找过程中的比较次数就等于查找长度加1。以前面图中标出的查找23的查找路径为例，从左上角的头结点开始，一直到结点22，查找长度为6。\n\n为了计算查找长度，这里我们需要利用一点小技巧。我们注意到，每个节点插入的时候，它的层数是由随机函数randomLevel()计算出来的，而且随机的计算不依赖于其它节点，每次插入过程都是完全独立的。所以，从统计上来说，一个skiplist结构的形成与节点的插入顺序无关。\n\n这样的话，为了计算查找长度，我们可以将查找过程倒过来看，从右下方第1层上最后到达的那个节点开始，沿着查找路径向左向上回溯，类似于爬楼梯的过程。我们假设当回溯到某个节点的时候，它才被插入，这虽然相当于改变了节点的插入顺序，但从统计上不影响整个skiplist的形成结构。\n\n现在假设我们从一个层数为i的节点x出发，需要向左向上攀爬k层。这时我们有两种可能：\n\n*   如果节点x有第(i+1)层指针，那么我们需要向上走。这种情况概率为p。\n*   如果节点x没有第(i+1)层指针，那么我们需要向左走。这种情况概率为(1-p)。\n\n这两种情形如下图所示：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230406205314.png)\n\n用C(k)表示向上攀爬k个层级所需要走过的平均查找路径长度（概率期望），那么：\n\n\n\n\n\n```\nC(0)=0\nC(k)=(1-p)×(上图中情况b的查找长度) + p×(上图中情况c的查找长度)\n\n```\n\n\n\n\n\n代入，得到一个差分方程并化简：\n\n\n\n\n\n```\nC(k)=(1-p)(C(k)+1) + p(C(k-1)+1)\nC(k)=1/p+C(k-1)\nC(k)=k/p\n\n```\n\n\n\n\n\n这个结果的意思是，我们每爬升1个层级，需要在查找路径上走1/p步。而我们总共需要攀爬的层级数等于整个skiplist的总层数-1。\n\n那么接下来我们需要分析一下当skiplist中有n个节点的时候，它的总层数的概率均值是多少。这个问题直观上比较好理解。根据节点的层数随机算法，容易得出：\n\n*   第1层链表固定有n个节点；\n*   第2层链表平均有n*p个节点；\n*   第3层链表平均有n*p<sup>2</sup>个节点；\n*   …\n\n所以，从第1层到最高层，各层链表的平均节点数是一个指数递减的等比数列。容易推算出，总层数的均值为log<sub>1/p</sub>n，而最高层的平均节点数为1/p。\n\n综上，粗略来计算的话，平均查找长度约等于：\n\n*   C(log<sub>1/p</sub>n-1)=(log<sub>1/p</sub>n-1)/p\n\n即，平均时间复杂度为O(log n)。\n\n当然，这里的时间复杂度分析还是比较粗略的。比如，沿着查找路径向左向上回溯的时候，可能先到达左侧头结点，然后沿头结点一路向上；还可能先到达最高层的节点，然后沿着最高层链表一路向左。但这些细节不影响平均时间复杂度的最后结果。另外，这里给出的时间复杂度只是一个概率平均值，但实际上计算一个精细的概率分布也是有可能的。详情还请参见[William Pugh](https://en.wikipedia.org/wiki/William_Pugh)的论文《[Skip Lists: A Probabilistic Alternative to Balanced Trees](ftp://ftp.cs.umd.edu/pub/skipLists/skiplists.pdf)》。\n\n## skiplist与平衡树、哈希表的比较\n\n*   skiplist和各种平衡树（如AVL、红黑树等）的元素是有序排列的，而哈希表不是有序的。因此，在哈希表上只能做单个key的查找，不适宜做范围查找。所谓范围查找，指的是查找那些大小在指定的两个值之间的所有节点。\n*   在做范围查找的时候，平衡树比skiplist操作要复杂。在平衡树上，我们找到指定范围的小值之后，还需要以中序遍历的顺序继续寻找其它不超过大值的节点。如果不对平衡树进行一定的改造，这里的中序遍历并不容易实现。而在skiplist上进行范围查找就非常简单，只需要在找到小值之后，对第1层链表进行若干步的遍历就可以实现。\n*   平衡树的插入和删除操作可能引发子树的调整，逻辑复杂，而skiplist的插入和删除只需要修改相邻节点的指针，操作简单又快速。\n*   从内存占用上来说，skiplist比平衡树更灵活一些。一般来说，平衡树每个节点包含2个指针（分别指向左右子树），而skiplist每个节点包含的指针数目平均为1/(1-p)，具体取决于参数p的大小。如果像Redis里的实现一样，取p=1/4，那么平均每个节点包含1.33个指针，比平衡树更有优势。\n*   查找单个key，skiplist和平衡树的时间复杂度都为O(log n)，大体相当；而哈希表在保持较低的哈希值冲突概率的前提下，查找时间复杂度接近O(1)，性能更高一些。所以我们平常使用的各种Map或dictionary结构，大都是基于哈希表实现的。\n*   从算法实现难度上来比较，skiplist比平衡树要简单得多。\n\n## Redis中的skiplist实现\n\n在这一部分，我们讨论Redis中的skiplist实现。\n\n在Redis中，skiplist被用于实现暴露给外部的一个数据结构：sorted set。准确地说，sorted set底层不仅仅使用了skiplist，还使用了ziplist和dict。这几个数据结构的关系，我们下一章再讨论。现在，我们先花点时间把sorted set的关键命令看一下。这些命令对于Redis里skiplist的实现，有重要的影响。\n\n### sorted set的命令举例\n\nsorted set是一个有序的数据集合，对于像类似排行榜这样的应用场景特别适合。\n\n现在我们来看一个例子，用sorted set来存储代数课（algebra）的成绩表。原始数据如下：\n\n*   Alice 87.5\n*   Bob 89.0\n*   Charles 65.5\n*   David 78.0\n*   Emily 93.5\n*   Fred 87.5\n\n这份数据给出了每位同学的名字和分数。下面我们将这份数据存储到sorted set里面去：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230406205334.png)\n\n对于上面的这些命令，我们需要的注意的地方包括：\n\n*   前面的6个zadd命令，将6位同学的名字和分数(score)都输入到一个key值为algebra的sorted set里面了。注意Alice和Fred的分数相同，都是87.5分。\n*   zrevrank命令查询Alice的排名（命令中的rev表示按照倒序排列，也就是从大到小），返回3。排在Alice前面的分别是Emily、Bob、Fred，而排名(rank)从0开始计数，所以Alice的排名是3。注意，其实Alice和Fred的分数相同，这种情况下sorted set会把分数相同的元素，按照字典顺序来排列。按照倒序，Fred排在了Alice的前面。\n*   zscore命令查询了Charles对应的分数。\n*   zrevrange命令查询了从大到小排名为0~3的4位同学。\n*   zrevrangebyscore命令查询了分数在80.0和90.0之间的所有同学，并按分数从大到小排列。\n\n总结一下，sorted set中的每个元素主要表现出3个属性：\n\n*   数据本身（在前面的例子中我们把名字存成了数据）。\n*   每个数据对应一个分数(score)。\n*   根据分数大小和数据本身的字典排序，每个数据会产生一个排名(rank)。可以按正序或倒序。\n\n### Redis中skiplist实现的特殊性\n\n我们简单分析一下前面出现的几个查询命令：\n\n*   zrevrank由数据查询它对应的排名，这在前面介绍的skiplist中并不支持。\n*   zscore由数据查询它对应的分数，这也不是skiplist所支持的。\n*   zrevrange根据一个排名范围，查询排名在这个范围内的数据。这在前面介绍的skiplist中也不支持。\n*   zrevrangebyscore根据分数区间查询数据集合，是一个skiplist所支持的典型的范围查找（score相当于key）。\n\n实际上，Redis中sorted set的实现是这样的：\n\n*   当数据较少时，sorted set是由一个ziplist来实现的。\n*   当数据多的时候，sorted set是由一个dict + 一个skiplist来实现的。简单来讲，dict用来查询数据到分数的对应关系，而skiplist用来根据分数查询数据（可能是范围查找）。\n\n这里sorted set的构成我们在下一章还会再详细地讨论。现在我们集中精力来看一下sorted set与skiplist的关系，：\n\n*   zscore的查询，不是由skiplist来提供的，而是由那个dict来提供的。\n*   为了支持排名(rank)，Redis里对skiplist做了扩展，使得根据排名能够快速查到数据，或者根据分数查到数据之后，也同时很容易获得排名。而且，根据排名的查找，时间复杂度也为O(log n)。\n*   zrevrange的查询，是根据排名查数据，由扩展后的skiplist来提供。\n*   zrevrank是先在dict中由数据查到分数，再拿分数到skiplist中去查找，查到后也同时获得了排名。\n\n前述的查询过程，也暗示了各个操作的时间复杂度：\n\n*   zscore只用查询一个dict，所以时间复杂度为O(1)\n*   zrevrank, zrevrange, zrevrangebyscore由于要查询skiplist，所以zrevrank的时间复杂度为O(log n)，而zrevrange, zrevrangebyscore的时间复杂度为O(log(n)+M)，其中M是当前查询返回的元素个数。\n\n总结起来，Redis中的skiplist跟前面介绍的经典的skiplist相比，有如下不同：\n\n*   分数(score)允许重复，即skiplist的key允许重复。这在最开始介绍的经典skiplist中是不允许的。\n*   在比较时，不仅比较分数（相当于skiplist的key），还比较数据本身。在Redis的skiplist实现中，数据本身的内容唯一标识这份数据，而不是由key来唯一标识。另外，当多个元素分数相同的时候，还需要根据数据内容来进字典排序。\n*   第1层链表不是一个单向链表，而是一个双向链表。这是为了方便以倒序方式获取一个范围内的元素。\n*   在skiplist中可以很方便地计算出每个元素的排名(rank)。\n\n### skiplist的数据结构定义\n\n\n\n\n\n```\n#define ZSKIPLIST_MAXLEVEL 32\n#define ZSKIPLIST_P 0.25\n\ntypedef struct zskiplistNode {\n    robj *obj;\n    double score;\n    struct zskiplistNode *backward;\n    struct zskiplistLevel {\n        struct zskiplistNode *forward;\n        unsigned int span;\n    } level[];\n} zskiplistNode;\n\ntypedef struct zskiplist {\n    struct zskiplistNode *header, *tail;\n    unsigned long length;\n    int level;\n} zskiplist;\n\n```\n\n\n\n\n\n这段代码出自server.h，我们来简要分析一下：\n\n*   开头定义了两个常量，ZSKIPLIST_MAXLEVEL和ZSKIPLIST_P，分别对应我们前面讲到的skiplist的两个参数：一个是MaxLevel，一个是p。\n*   zskiplistNode定义了skiplist的节点结构。\n    *   obj字段存放的是节点数据，它的类型是一个string robj。本来一个string robj可能存放的不是sds，而是long型，但zadd命令在将数据插入到skiplist里面之前先进行了解码，所以这里的obj字段里存储的一定是一个sds。有关robj的详情可以参见系列文章的第三篇：《[Redis内部数据结构详解(3)——robj](http://zhangtielei.com/posts/blog-redis-robj.html)》。这样做的目的应该是为了方便在查找的时候对数据进行字典序的比较，而且，skiplist里的数据部分是数字的可能性也比较小。\n    *   score字段是数据对应的分数。\n    *   backward字段是指向链表前一个节点的指针（前向指针）。节点只有1个前向指针，所以只有第1层链表是一个双向链表。\n    *   level[]存放指向各层链表后一个节点的指针（后向指针）。每层对应1个后向指针，用forward字段表示。另外，每个后向指针还对应了一个span值，它表示当前的指针跨越了多少个节点。span用于计算元素排名(rank)，这正是前面我们提到的Redis对于skiplist所做的一个扩展。需要注意的是，level[]是一个柔性数组（[flexible array member](https://en.wikipedia.org/wiki/Flexible_array_member)），因此它占用的内存不在zskiplistNode结构里面，而需要插入节点的时候单独为它分配。也正因为如此，skiplist的每个节点所包含的指针数目才是不固定的，我们前面分析过的结论——skiplist每个节点包含的指针数目平均为1/(1-p)——才能有意义。\n*   zskiplist定义了真正的skiplist结构，它包含：\n    *   头指针header和尾指针tail。\n    *   链表长度length，即链表包含的节点总数。注意，新创建的skiplist包含一个空的头指针，这个头指针不包含在length计数中。\n    *   level表示skiplist的总层数，即所有节点层数的最大值。\n\n下图以前面插入的代数课成绩表为例，展示了Redis中一个skiplist的可能结构：\n\n[![Redis skiplist结构举例](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/redis_skiplist_example.png)](http://zhangtielei.com/assets/photos_redis/skiplist/redis_skiplist_example.png)\n\n注意：图中前向指针上面括号中的数字，表示对应的span的值。即当前指针跨越了多少个节点，这个计数不包括指针的起点节点，但包括指针的终点节点。\n\n假设我们在这个skiplist中查找score=89.0的元素（即Bob的成绩数据），在查找路径中，我们会跨域图中标红的指针，这些指针上面的span值累加起来，就得到了Bob的排名(2+2+1)-1=4（减1是因为rank值以0起始）。需要注意这里算的是从小到大的排名，而如果要算从大到小的排名，只需要用skiplist长度减去查找路径上的span累加值，即6-(2+2+1)=1。\n\n可见，在查找skiplist的过程中，通过累加span值的方式，我们就能很容易算出排名。相反，如果指定排名来查找数据（类似zrange和zrevrange那样），也可以不断累加span并时刻保持累加值不超过指定的排名，通过这种方式就能得到一条O(log n)的查找路径。\n\n## Redis中的sorted set\n\n我们前面提到过，Redis中的sorted set，是在skiplist, dict和ziplist基础上构建起来的:\n\n*   当数据较少时，sorted set是由一个ziplist来实现的。\n*   当数据多的时候，sorted set是由一个叫zset的数据结构来实现的，这个zset包含一个dict + 一个skiplist。dict用来查询数据到分数(score)的对应关系，而skiplist用来根据分数查询数据（可能是范围查找）。\n\n在这里我们先来讨论一下前一种情况——基于ziplist实现的sorted set。在本系列前面[关于ziplist的文章](http://zhangtielei.com/posts/blog-redis-ziplist.html)里，我们介绍过，ziplist就是由很多数据项组成的一大块连续内存。由于sorted set的每一项元素都由数据和score组成，因此，当使用zadd命令插入一个(数据, score)对的时候，底层在相应的ziplist上就插入两个数据项：数据在前，score在后。\n\nziplist的主要优点是节省内存，但它上面的查找操作只能按顺序查找（可以正序也可以倒序）。因此，sorted set的各个查询操作，就是在ziplist上从前向后（或从后向前）一步步查找，每一步前进两个数据项，跨域一个(数据, score)对。\n\n随着数据的插入，sorted set底层的这个ziplist就可能会转成zset的实现（转换过程详见t_zset.c的zsetConvert）。那么到底插入多少才会转呢？\n\n还记得本文开头提到的两个Redis配置吗？\n\n\n\n\n\n```\nzset-max-ziplist-entries 128\nzset-max-ziplist-value 64\n\n```\n\n\n\n\n\n这个配置的意思是说，在如下两个条件之一满足的时候，ziplist会转成zset（具体的触发条件参见t_zset.c中的zaddGenericCommand相关代码）：\n\n*   当sorted set中的元素个数，即(数据, score)对的数目超过128的时候，也就是ziplist数据项超过256的时候。\n*   当sorted set中插入的任意一个数据的长度超过了64的时候。\n\n最后，zset结构的代码定义如下：\n\n\n\n\n\n```\ntypedef struct zset {\n    dict *dict;\n    zskiplist *zsl;\n} zset;\n\n```\n\n\n\n\n\n## Redis为什么用skiplist而不用平衡树？\n\n在前面我们对于skiplist和平衡树、哈希表的比较中，其实已经不难看出Redis里使用skiplist而不用平衡树的原因了。现在我们看看，对于这个问题，Redis的作者 @antirez 是怎么说的：\n\n> There are a few reasons:\n>\n> 1) They are not very memory intensive. It’s up to you basically. Changing parameters about the probability of a node to have a given number of levels will make then less memory intensive than btrees.\n>\n> 2) A sorted set is often target of many ZRANGE or ZREVRANGE operations, that is, traversing the skip list as a linked list. With this operation the cache locality of skip lists is at least as good as with other kind of balanced trees.\n>\n> 3) They are simpler to implement, debug, and so forth. For instance thanks to the skip list simplicity I received a patch (already in Redis master) with augmented skip lists implementing ZRANK in O(log(N)). It required little changes to the code.\n\n这段话原文出处：\n\n> [https://news.ycombinator.com/item?id=1171423](https://news.ycombinator.com/item?id=1171423)\n\n这里从内存占用、对范围查找的支持和实现难易程度这三方面总结的原因，我们在前面其实也都涉及到了。\n\n* * *\n\n系列下一篇我们将介绍intset，以及它与Redis对外暴露的数据类型set的关系，敬请期待。\n\n（完）\n\n**原创文章，转载请注明出处，并包含下面的二维码！否则拒绝转载！**\n**本文链接：**[http://zhangtielei.com/posts/blog-redis-skiplist.html](http://zhangtielei.com/posts/blog-redis-skiplist.html)\n\n![我的微信公众号: tielei-blog (张铁蕾)](http://zhangtielei.com/assets/my_weixin_sign_sf_840.jpg)\n"
  },
  {
    "path": "docs/cache/探索Redis设计与实现：Redis内部数据结构详解——ziplist.md",
    "content": "# 目录\n\n  * [什么是ziplist](#什么是ziplist)\n  * [ziplist的数据结构定义](#ziplist的数据结构定义)\n  * [ziplist的接口](#ziplist的接口)\n  * [ziplist的插入逻辑解析](#ziplist的插入逻辑解析)\n  * [hash与ziplist](#hash与ziplist)\n\n\n[toc]\n\n本文转自互联网\n\n本文将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，Redis基本的使用方法，Redis的基本数据结构，以及一些进阶的使用方法，同时也需要进一步了解Redis的底层数据结构，再接着，还会带来Redis主从复制、集群、分布式锁等方面的相关内容，以及作为缓存的一些使用方法和注意事项，以便让你更完整地了解整个Redis相关的技术体系，形成自己的知识框架。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->  \n\n\n本文是《[Redis内部数据结构详解](http://zhangtielei.com/posts/blog-redis-dict.html)》系列的第四篇。在本文中，我们首先介绍一个新的Redis内部数据结构——ziplist，然后在文章后半部分我们会讨论一下在robj, dict和ziplist的基础上，Redis对外暴露的hash结构是怎样构建起来的。\n\n我们在讨论中还会涉及到两个Redis配置（在redis.conf中的ADVANCED CONFIG部分）：\n\n\n```  \nhash-max-ziplist-entries 512  \nhash-max-ziplist-value 64  \n  \n```\n\n\n本文的后半部分会对这两个配置做详细的解释。\n\n## 什么是ziplist\n\nRedis官方对于ziplist的定义是（出自ziplist.c的文件头部注释）：\n\n> The ziplist is a specially encoded dually linked list that is designed to be very memory efficient. It stores both strings and integer values, where integers are encoded as actual integers instead of a series of characters. It allows push and pop operations on either side of the list in O(1) time.\n\n翻译一下就是说：ziplist是一个经过特殊编码的双向链表，它的设计目标就是为了提高存储效率。ziplist可以用于存储字符串或整数，其中整数是按真正的二进制表示进行编码的，而不是编码成字符串序列。它能以O(1)的时间复杂度在表的两端提供`push`和`pop`操作。\n\n实际上，ziplist充分体现了Redis对于存储效率的追求。一个普通的双向链表，链表中每一项都占用独立的一块内存，各项之间用地址指针（或引用）连接起来。这种方式会带来大量的内存碎片，而且地址指针也会占用额外的内存。而ziplist却是将表中每一项存放在前后连续的地址空间内，一个ziplist整体占用一大块内存。它是一个表（list），但其实不是一个链表（linked list）。\n\n另外，ziplist为了在细节上节省内存，对于值的存储采用了变长的编码方式，大概意思是说，对于大的整数，就多用一些字节来存储，而对于小的整数，就少用一些字节来存储。我们接下来很快就会讨论到这些实现细节。\n\n## ziplist的数据结构定义\n\nziplist的数据结构组成是本文要讨论的重点。实际上，ziplist还是稍微有点复杂的，它复杂的地方就在于它的数据结构定义。一旦理解了数据结构，它的一些操作也就比较容易理解了。\n\n我们接下来先从总体上介绍一下ziplist的数据结构定义，然后举一个实际的例子，通过例子来解释ziplist的构成。如果你看懂了这一部分，本文的任务就算完成了一大半了。\n\n从宏观上看，ziplist的内存结构如下：\n\n`<zlbytes><zltail><zllen><entry>...<entry><zlend>`\n\n各个部分在内存上是前后相邻的，它们分别的含义如下：\n\n*   `<zlbytes>`: 32bit，表示ziplist占用的字节总数（也包括`<zlbytes>`本身占用的4个字节）。\n*   `<zltail>`: 32bit，表示ziplist表中最后一项（entry）在ziplist中的偏移字节数。`<zltail>`的存在，使得我们可以很方便地找到最后一项（不用遍历整个ziplist），从而可以在ziplist尾端快速地执行push或pop操作。\n*   `<zllen>`: 16bit， 表示ziplist中数据项（entry）的个数。zllen字段因为只有16bit，所以可以表达的最大值为2^16-1。这里需要特别注意的是，如果ziplist中数据项个数超过了16bit能表达的最大值，ziplist仍然可以来表示。那怎么表示呢？这里做了这样的规定：如果`<zllen>`小于等于2^16-2（也就是不等于2^16-1），那么`<zllen>`就表示ziplist中数据项的个数；否则，也就是`<zllen>`等于16bit全为1的情况，那么`<zllen>`就不表示数据项个数了，这时候要想知道ziplist中数据项总数，那么必须对ziplist从头到尾遍历各个数据项，才能计数出来。\n*   `<entry>`: 表示真正存放数据的数据项，长度不定。一个数据项（entry）也有它自己的内部结构，这个稍后再解释。\n*   `<zlend>`: ziplist最后1个字节，是一个结束标记，值固定等于255。\n\n上面的定义中还值得注意的一点是：`<zlbytes>`,`<zltail>`,`<zllen>`既然占据多个字节，那么在存储的时候就有大端（big endian）和小端（little endian）的区别。ziplist采取的是小端模式来存储，这在下面我们介绍具体例子的时候还会再详细解释。\n\n我们再来看一下每一个数据项`<entry>`的构成：\n\n`<prevrawlen><len><data>`\n\n我们看到在真正的数据（`<data>`）前面，还有两个字段：\n\n*   `<prevrawlen>`: 表示前一个数据项占用的总字节数。这个字段的用处是为了让ziplist能够从后向前遍历（从后一项的位置，只需向前偏移prevrawlen个字节，就找到了前一项）。这个字段采用变长编码。\n*   `<len>`: 表示当前数据项的数据长度（即`<data>`部分的长度）。也采用变长编码。\n\n那么`<prevrawlen>`和`<len>`是怎么进行变长编码的呢？各位读者打起精神了，我们终于讲到了ziplist的定义中最繁琐的地方了。\n\n先说`<prevrawlen>`。它有两种可能，或者是1个字节，或者是5个字节：\n\n1.  如果前一个数据项占用字节数小于254，那么`<prevrawlen>`就只用一个字节来表示，这个字节的值就是前一个数据项的占用字节数。\n2.  如果前一个数据项占用字节数大于等于254，那么`<prevrawlen>`就用5个字节来表示，其中第1个字节的值是254（作为这种情况的一个标记），而后面4个字节组成一个整型值，来真正存储前一个数据项的占用字节数。\n\n有人会问了，为什么没有255的情况呢？\n\n这是因为：255已经定义为ziplist结束标记`<zlend>`的值了。在ziplist的很多操作的实现中，都会根据数据项的第1个字节是不是255来判断当前是不是到达ziplist的结尾了，因此一个正常的数据的第1个字节（也就是`<prevrawlen>`的第1个字节）是不能够取255这个值的，否则就冲突了。\n\n而`<len>`字段就更加复杂了，它根据第1个字节的不同，总共分为9种情况（下面的表示法是按二进制表示）：\n\n1.  |00pppppp| - 1 byte。第1个字节最高两个bit是00，那么`<len>`字段只有1个字节，剩余的6个bit用来表示长度值，最高可以表示63 (2^6-1)。\n2.  |01pppppp|qqqqqqqq| - 2 bytes。第1个字节最高两个bit是01，那么`<len>`字段占2个字节，总共有14个bit用来表示长度值，最高可以表示16383 (2^14-1)。\n3.  |10**__**|qqqqqqqq|rrrrrrrr|ssssssss|tttttttt| - 5 bytes。第1个字节最高两个bit是10，那么len字段占5个字节，总共使用32个bit来表示长度值（6个bit舍弃不用），最高可以表示2^32-1。需要注意的是：在前三种情况下，`<data>`都是按字符串来存储的；从下面第4种情况开始，`<data>`开始变为按整数来存储了。\n4.  |11000000| - 1 byte。`<len>`字段占用1个字节，值为0xC0，后面的数据`<data>`存储为2个字节的int16_t类型。\n5.  |11010000| - 1 byte。`<len>`字段占用1个字节，值为0xD0，后面的数据`<data>`存储为4个字节的int32_t类型。\n6.  |11100000| - 1 byte。`<len>`字段占用1个字节，值为0xE0，后面的数据`<data>`存储为8个字节的int64_t类型。\n7.  |11110000| - 1 byte。`<len>`字段占用1个字节，值为0xF0，后面的数据`<data>`存储为3个字节长的整数。\n8.  |11111110| - 1 byte。`<len>`字段占用1个字节，值为0xFE，后面的数据`<data>`存储为1个字节的整数。\n9.  |1111xxxx| - - (xxxx的值在0001和1101之间)。这是一种特殊情况，xxxx从1到13一共13个值，这时就用这13个值来表示真正的数据。注意，这里是表示真正的数据，而不是数据长度了。也就是说，在这种情况下，后面不再需要一个单独的`<data>`字段来表示真正的数据了，而是`<len>`和`<data>`合二为一了。另外，由于xxxx只能取0001和1101这13个值了（其它可能的值和其它情况冲突了，比如0000和1110分别同前面第7种第8种情况冲突，1111跟结束标记冲突），而小数值应该从0开始，因此这13个值分别表示0到12，即xxxx的值减去1才是它所要表示的那个整数数据的值。\n\n好了，ziplist的数据结构定义，我们介绍完了，现在我们看一个具体的例子。\n\n[![Redis Ziplist Sample](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/redis_ziplist_sample.png)\n\n上图是一份真实的ziplist数据。我们逐项解读一下：\n\n*   这个ziplist一共包含33个字节。字节编号从byte[0]到byte[32]。图中每个字节的值使用16进制表示。\n*   头4个字节（0x21000000）是按小端（little endian）模式存储的`<zlbytes>`字段。什么是小端呢？就是指数据的低字节保存在内存的低地址中（参见维基百科词条[Endianness](https://en.wikipedia.org/wiki/Endianness)）。因此，这里`<zlbytes>`的值应该解析成0x00000021，用十进制表示正好就是33。\n*   接下来4个字节（byte[4..7]）是`<zltail>`，用小端存储模式来解释，它的值是0x0000001D（值为29），表示最后一个数据项在byte[29]的位置（那个数据项为0x05FE14）。\n*   再接下来2个字节（byte[8..9]），值为0x0004，表示这个ziplist里一共存有4项数据。\n*   接下来6个字节（byte[10..15]）是第1个数据项。其中，prevrawlen=0，因为它前面没有数据项；len=4，相当于前面定义的9种情况中的第1种，表示后面4个字节按字符串存储数据，数据的值为”name”。\n*   接下来8个字节（byte[16..23]）是第2个数据项，与前面数据项存储格式类似，存储1个字符串”tielei”。\n*   接下来5个字节（byte[24..28]）是第3个数据项，与前面数据项存储格式类似，存储1个字符串”age”。\n*   接下来3个字节（byte[29..31]）是最后一个数据项，它的格式与前面的数据项存储格式不太一样。其中，第1个字节prevrawlen=5，表示前一个数据项占用5个字节；第2个字节=FE，相当于前面定义的9种情况中的第8种，所以后面还有1个字节用来表示真正的数据，并且以整数表示。它的值是20（0x14）。\n*   最后1个字节（byte[32]）表示`<zlend>`，是固定的值255（0xFF）。\n\n总结一下，这个ziplist里存了4个数据项，分别为：\n\n*   字符串: “name”\n*   字符串: “tielei”\n*   字符串: “age”\n*   整数: 20\n\n（好吧，被你发现了~~tielei实际上当然不是20岁，他哪有那么年轻啊……）\n\n实际上，这个ziplist是通过两个`hset`命令创建出来的。这个我们后半部分会再提到。\n\n好了，既然你已经阅读到这里了，说明你还是很有耐心的（其实我写到这里也已经累得不行了）。可以先把本文收藏，休息一下，回头再看后半部分。\n\n接下来我要贴一些代码了。\n\n## ziplist的接口\n\n我们先不着急看实现，先来挑几个ziplist的重要的接口，看看它们长什么样子：\n\n\n\n\n\n```  \nunsigned char *ziplistNew(void);  \nunsigned char *ziplistMerge(unsigned char **first, unsigned char **second);  \nunsigned char *ziplistPush(unsigned char *zl, unsigned char *s, unsigned int slen, int where);  \nunsigned char *ziplistIndex(unsigned char *zl, int index);  \nunsigned char *ziplistNext(unsigned char *zl, unsigned char *p);  \nunsigned char *ziplistPrev(unsigned char *zl, unsigned char *p);  \nunsigned char *ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen);  \nunsigned char *ziplistDelete(unsigned char *zl, unsigned char **p);  \nunsigned char *ziplistFind(unsigned char *p, unsigned char *vstr, unsigned int vlen, unsigned int skip);  \nunsigned int ziplistLen(unsigned char *zl);  \n  \n```  \n\n\n\n\n\n我们从这些接口的名字就可以粗略猜出它们的功能，下面简单解释一下：\n\n*   ziplist的数据类型，没有用自定义的struct之类的来表达，而就是简单的unsigned char *。这是因为ziplist本质上就是一块连续内存，内部组成结构又是一个高度动态的设计（变长编码），也没法用一个固定的数据结构来表达。\n*   ziplistNew: 创建一个空的ziplist（只包含`<zlbytes><zltail><zllen><zlend>`）。\n*   ziplistMerge: 将两个ziplist合并成一个新的ziplist。\n*   ziplistPush: 在ziplist的头部或尾端插入一段数据（产生一个新的数据项）。注意一下这个接口的返回值，是一个新的ziplist。调用方必须用这里返回的新的ziplist，替换之前传进来的旧的ziplist变量，而经过这个函数处理之后，原来旧的ziplist变量就失效了。为什么一个简单的插入操作会导致产生一个新的ziplist呢？这是因为ziplist是一块连续空间，对它的追加操作，会引发内存的realloc，因此ziplist的内存位置可能会发生变化。实际上，我们在之前介绍sds的文章中提到过类似这种接口使用模式（参见sdscatlen函数的说明）。\n*   ziplistIndex: 返回index参数指定的数据项的内存位置。index可以是负数，表示从尾端向前进行索引。\n*   ziplistNext和ziplistPrev分别返回一个ziplist中指定数据项p的后一项和前一项。\n*   ziplistInsert: 在ziplist的任意数据项前面插入一个新的数据项。\n*   ziplistDelete: 删除指定的数据项。\n*   ziplistFind: 查找给定的数据（由vstr和vlen指定）。注意它有一个skip参数，表示查找的时候每次比较之间要跳过几个数据项。为什么会有这么一个参数呢？其实这个参数的主要用途是当用ziplist表示hash结构的时候，是按照一个field，一个value来依次存入ziplist的。也就是说，偶数索引的数据项存field，奇数索引的数据项存value。当按照field的值进行查找的时候，就需要把奇数项跳过去。\n*   ziplistLen: 计算ziplist的长度（即包含数据项的个数）。\n\n## ziplist的插入逻辑解析\n\nziplist的相关接口的具体实现，还是有些复杂的，限于篇幅的原因，我们这里只结合代码来讲解插入的逻辑。插入是很有代表性的操作，通过这部分来一窥ziplist内部的实现，其它部分的实现我们也就会很容易理解了。\n\nziplistPush和ziplistInsert都是插入，只是对于插入位置的限定不同。它们在内部实现都依赖一个名为__ziplistInsert的内部函数，其代码如下（出自ziplist.c）:\n\n\n\n\n\n```  \nstatic unsigned char *__ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen) {  \n    size_t curlen = intrev32ifbe(ZIPLIST_BYTES(zl)), reqlen;    unsigned int prevlensize, prevlen = 0;    size_t offset;    int nextdiff = 0;    unsigned char encoding = 0;    long long value = 123456789; /* initialized to avoid warning. Using a value                                    that is easy to see if for some reason                                    we use it uninitialized. */    zlentry tail;  \n    /* Find out prevlen for the entry that is inserted. */    if (p[0] != ZIP_END) {        ZIP_DECODE_PREVLEN(p, prevlensize, prevlen);    } else {        unsigned char *ptail = ZIPLIST_ENTRY_TAIL(zl);        if (ptail[0] != ZIP_END) {            prevlen = zipRawEntryLength(ptail);        }    }  \n    /* See if the entry can be encoded */    if (zipTryEncoding(s,slen,&value,&encoding)) {        /* 'encoding' is set to the appropriate integer encoding */        reqlen = zipIntSize(encoding);    } else {        /* 'encoding' is untouched, however zipEncodeLength will use the         * string length to figure out how to encode it. */        reqlen = slen;    }    /* We need space for both the length of the previous entry and     * the length of the payload. */    reqlen += zipPrevEncodeLength(NULL,prevlen);    reqlen += zipEncodeLength(NULL,encoding,slen);  \n    /* When the insert position is not equal to the tail, we need to     * make sure that the next entry can hold this entry's length in     * its prevlen field. */    nextdiff = (p[0] != ZIP_END) ? zipPrevLenByteDiff(p,reqlen) : 0;  \n    /* Store offset because a realloc may change the address of zl. */    offset = p-zl;    zl = ziplistResize(zl,curlen+reqlen+nextdiff);    p = zl+offset;  \n    /* Apply memory move when necessary and update tail offset. */    if (p[0] != ZIP_END) {        /* Subtract one because of the ZIP_END bytes */        memmove(p+reqlen,p-nextdiff,curlen-offset-1+nextdiff);  \n        /* Encode this entry's raw length in the next entry. */        zipPrevEncodeLength(p+reqlen,reqlen);  \n        /* Update offset for tail */        ZIPLIST_TAIL_OFFSET(zl) =            intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+reqlen);  \n        /* When the tail contains more than one entry, we need to take         * \"nextdiff\" in account as well. Otherwise, a change in the         * size of prevlen doesn't have an effect on the *tail* offset. */        zipEntry(p+reqlen, &tail);        if (p[reqlen+tail.headersize+tail.len] != ZIP_END) {            ZIPLIST_TAIL_OFFSET(zl) =                intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+nextdiff);        }    } else {        /* This element will be the new tail. */        ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(p-zl);    }  \n    /* When nextdiff != 0, the raw length of the next entry has changed, so     * we need to cascade the update throughout the ziplist */    if (nextdiff != 0) {        offset = p-zl;        zl = __ziplistCascadeUpdate(zl,p+reqlen);        p = zl+offset;    }  \n    /* Write the entry */    p += zipPrevEncodeLength(p,prevlen);    p += zipEncodeLength(p,encoding,slen);    if (ZIP_IS_STR(encoding)) {        memcpy(p,s,slen);    } else {        zipSaveInteger(p,value,encoding);    }    ZIPLIST_INCR_LENGTH(zl,1);    return zl;}  \n  \n```  \n\n\n\n\n\n我们来简单解析一下这段代码：\n\n*   这个函数是在指定的位置p插入一段新的数据，待插入数据的地址指针是s，长度为slen。插入后形成一个新的数据项，占据原来p的配置，原来位于p位置的数据项以及后面的所有数据项，需要统一向后移动，给新插入的数据项留出空间。参数p指向的是ziplist中某一个数据项的起始位置，或者在向尾端插入的时候，它指向ziplist的结束标记`<zlend>`。\n*   函数开始先计算出待插入位置前一个数据项的长度`prevlen`。这个长度要存入新插入的数据项的`<prevrawlen>`字段。\n*   然后计算当前数据项占用的总字节数`reqlen`，它包含三部分：`<prevrawlen>`,`<len>`和真正的数据。其中的数据部分会通过调用`zipTryEncoding`先来尝试转成整数。\n*   由于插入导致的ziplist对于内存的新增需求，除了待插入数据项占用的`reqlen`之外，还要考虑原来p位置的数据项（现在要排在待插入数据项之后）的`<prevrawlen>`字段的变化。本来它保存的是前一项的总长度，现在变成了保存当前插入的数据项的总长度。这样它的`<prevrawlen>`字段本身需要的存储空间也可能发生变化，这个变化可能是变大也可能是变小。这个变化了多少的值`nextdiff`，是调用`zipPrevLenByteDiff`计算出来的。如果变大了，`nextdiff`是正值，否则是负值。\n*   现在很容易算出来插入后新的ziplist需要多少字节了，然后调用`ziplistResize`来重新调整大小。ziplistResize的实现里会调用allocator的`zrealloc`，它有可能会造成数据拷贝。\n*   现在额外的空间有了，接下来就是将原来p位置的数据项以及后面的所有数据都向后挪动，并为它设置新的`<prevrawlen>`字段。此外，还可能需要调整ziplist的`<zltail>`字段。\n*   最后，组装新的待插入数据项，放在位置p。\n\n## hash与ziplist\n\nhash是Redis中可以用来存储一个对象结构的比较理想的数据类型。一个对象的各个属性，正好对应一个hash结构的各个field。\n\n我们在网上很容易找到这样一些技术文章，它们会说存储一个对象，使用hash比string要节省内存。实际上这么说是有前提的，具体取决于对象怎么来存储。如果你把对象的多个属性存储到多个key上（各个属性值存成string），当然占的内存要多。但如果你采用一些序列化方法，比如[Protocol Buffers](https://github.com/google/protobuf)，或者[Apache Thrift](https://thrift.apache.org/)，先把对象序列化为字节数组，然后再存入到Redis的string中，那么跟hash相比，哪一种更省内存，就不一定了。\n\n当然，hash比序列化后再存入string的方式，在支持的操作命令上，还是有优势的：它既支持多个field同时存取（`hmset`/`hmget`），也支持按照某个特定的field单独存取（`hset`/`hget`）。\n\n实际上，hash随着数据的增大，其底层数据结构的实现是会发生变化的，当然存储效率也就不同。在field比较少，各个value值也比较小的时候，hash采用ziplist来实现；而随着field增多和value值增大，hash可能会变成dict来实现。当hash底层变成dict来实现的时候，它的存储效率就没法跟那些序列化方式相比了。\n\n当我们为某个key第一次执行`hset key field value`命令的时候，Redis会创建一个hash结构，这个新创建的hash底层就是一个ziplist。\n\n\n\n\n\n```  \nrobj *createHashObject(void) {  \n    unsigned char *zl = ziplistNew();    robj *o = createObject(OBJ_HASH, zl);    o->encoding = OBJ_ENCODING_ZIPLIST;    return o;}  \n  \n```  \n\n\n\n\n\n上面的`createHashObject`函数，出自object.c，它负责的任务就是创建一个新的hash结构。可以看出，它创建了一个`type = OBJ_HASH`但`encoding = OBJ_ENCODING_ZIPLIST`的robj对象。\n\n实际上，本文前面给出的那个ziplist实例，就是由如下两个命令构建出来的。\n\n\n\n\n\n```  \nhset user:100 name tielei  \nhset user:100 age 20  \n  \n```  \n\n\n\n\n\n每执行一次`hset`命令，插入的field和value分别作为一个新的数据项插入到ziplist中（即每次`hset`产生两个数据项）。\n\n当随着数据的插入，hash底层的这个ziplist就可能会转成dict。那么到底插入多少才会转呢？\n\n还记得本文开头提到的两个Redis配置吗？\n\n\n\n\n\n```  \nhash-max-ziplist-entries 512  \nhash-max-ziplist-value 64  \n  \n```  \n\n\n\n\n\n这个配置的意思是说，在如下两个条件之一满足的时候，ziplist会转成dict：\n\n*   当hash中的数据项（即field-value对）的数目超过512的时候，也就是ziplist数据项超过1024的时候（请参考t_hash.c中的`hashTypeSet`函数）。\n*   当hash中插入的任意一个value的长度超过了64的时候（请参考t_hash.c中的`hashTypeTryConversion`函数）。\n\nRedis的hash之所以这样设计，是因为当ziplist变得很大的时候，它有如下几个缺点：\n\n*   每次插入或修改引发的realloc操作会有更大的概率造成内存拷贝，从而降低性能。\n*   一旦发生内存拷贝，内存拷贝的成本也相应增加，因为要拷贝更大的一块数据。\n*   当ziplist数据项过多的时候，在它上面查找指定的数据项就会性能变得很低，因为ziplist上的查找需要进行遍历。\n\n总之，ziplist本来就设计为各个数据项挨在一起组成连续的内存空间，这种结构并不擅长做修改操作。一旦数据发生改动，就会引发内存realloc，可能导致内存拷贝。\n"
  },
  {
    "path": "docs/cache/探索Redis设计与实现：Redis分布式锁进化史.md",
    "content": "# 目录\n\n  * [Redis分布式锁进化史](#redis分布式锁进化史)\n    * [各个版本的Redis分布式锁](#各个版本的redis分布式锁)\n    * [分布式Redis锁：Redlock](#分布式redis锁：redlock)\n    * [总结](#总结)\n\n\n\n本文转自互联网\n本文将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，Redis基本的使用方法，Redis的基本数据结构，以及一些进阶的使用方法，同时也需要进一步了解Redis的底层数据结构，再接着，还会带来Redis主从复制、集群、分布式锁等方面的相关内容，以及作为缓存的一些使用方法和注意事项，以便让你更完整地了解整个Redis相关的技术体系，形成自己的知识框架。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n\n\n\n## Redis分布式锁进化史\n\n近两年来微服务变得越来越热门，越来越多的应用部署在分布式环境中，在分布式环境中，数据一致性是一直以来需要关注并且去解决的问题，分布式锁也就成为了一种广泛使用的技术，常用的分布式实现方式为Redis，Zookeeper，其中基于Redis的分布式锁的使用更加广泛。\n\n但是在工作和网络上看到过各个版本的Redis分布式锁实现，每种实现都有一些不严谨的地方，甚至有可能是错误的实现，包括在代码中，如果不能正确的使用分布式锁，可能造成严重的生产环境故障，本文主要对目前遇到的各种分布式锁以及其缺陷做了一个整理，并对如何选择合适的Redis分布式锁给出建议。\n\n### 各个版本的Redis分布式锁\n\n*   V1.0\n\n```\ntryLock(){  \n    SETNX Key 1\n    EXPIRE Key Seconds\n}\nrelease(){  \n  DELETE Key\n}\n```\n\n这个版本应该是最简单的版本，也是出现频率很高的一个版本，首先给锁加一个过期时间操作是为了避免应用在服务重启或者异常导致锁无法释放后，不会出现锁一直无法被释放的情况。\n\n这个方案的一个问题在于每次提交一个Redis请求，如果执行完第一条命令后应用异常或者重启，锁将无法过期，一种改善方案就是使用Lua脚本（包含SETNX和EXPIRE两条命令），但是如果Redis仅执行了一条命令后crash或者发生主从切换，依然会出现锁没有过期时间，最终导致无法释放。\n\n另外一个问题在于，很多同学在释放分布式锁的过程中，无论锁是否获取成功，都在finally中释放锁，这样是一个锁的错误使用，这个问题将在后续的V3.0版本中解决。\n\n针对锁无法释放问题的一个解决方案基于[GETSET](https://redis.io/commands/getset)命令来实现\n\n*   V1.1 基于[GETSET](https://redis.io/commands/getset)\n\n```\ntryLock(){  \n    NewExpireTime=CurrentTimestamp+ExpireSeconds\n    if(SETNX Key NewExpireTime Seconds){\n         oldExpireTime = GET(Key)\n          if( oldExpireTime < CurrentTimestamp){\n              NewExpireTime=CurrentTimestamp+ExpireSeconds\n              CurrentExpireTime=GETSET(Key,NewExpireTime)\n              if(CurrentExpireTime == oldExpireTime){\n                return 1;\n              }else{\n                return 0;\n              }\n          }\n    }\n}\nrelease(){  \n        DELETE key\n    }\n```\n\n思路：\n\n1.  SETNX(Key,ExpireTime)获取锁\n\n2.  如果获取锁失败，通过GET(Key)返回的时间戳检查锁是否已经过期\n\n3.  GETSET(Key,ExpireTime)修改Value为NewExpireTime\n\n4.  检查GETSET返回的旧值，如果等于GET返回的值，则认为获取锁成功\n\n    > 注意：这个版本去掉了EXPIRE命令，改为通过Value时间戳值来判断过期\n\n问题：\n\n```\n  1. 在锁竞争较高的情况下，会出现Value不断被覆盖，但是没有一个Client获取到锁  \n  \n  2. 在获取锁的过程中不断的修改原有锁的数据，设想一种场景C1，C2竞争锁，C1获取到了锁，C2锁执行了GETSET操作修改了C1锁的过期时间，如果C1没有正确释放锁，锁的过期时间被延长，其它Client需要等待更久的时间\n```\n\n*   V2.0 基于[SETNX](https://redis.io/commands/setnx)\n\n```\ntryLock(){      SETNX Key 1 Seconds}release(){    DELETE Key}\n```\n\nRedis 2.6.12版本后SETNX增加过期时间参数，这样就解决了两条命令无法保证原子性的问题。但是设想下面一个场景：\n\n1\\. C1成功获取到了锁，之后C1因为GC进入等待或者未知原因导致任务执行过长，最后在锁失效前C1没有主动释放锁 \n\n2\\.\nC2在C1的锁超时后获取到锁，并且开始执行，这个时候C1和C2都同时在执行，会因重复执行造成数据不一致等未知情况 \n\n3\\. C1如果先执行完毕，则会释放C2的锁，此时可能导致另外一个C3进程获取到了锁\n\n大致的流程图\n\n![](http://tech.dianwoda.com/content/images/2018/04/unsafe-lock.png)\n\n存在问题：\n\n```\n1\\. 由于C1的停顿导致C1 和C2同都获得了锁并且同时在执行，在业务实现间接要求必须保证幂等性\n\n2\\. C1释放了不属于C1的锁\n```\n\n*   V3.0\n\n```\ntryLock(){  \n    SETNX Key UnixTimestamp Seconds\n}\nrelease(){  \n    EVAL(\n      //LuaScript\n      if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n          return redis.call(\"del\",KEYS[1])\n      else\n          return 0\n      end\n    )\n}\n```\n\n这个方案通过指定Value为时间戳，并在释放锁的时候检查锁的Value是否为获取锁的Value，避免了V2.0版本中提到的C1释放了C2持有的锁的问题；另外在释放锁的时候因为涉及到多个Redis操作，并且考虑到Check And Set 模型的并发问题，所以使用Lua脚本来避免并发问题。\n\n存在问题：\n\n\n如果在并发极高的场景下，比如抢红包场景，可能存在UnixTimestamp重复问题，另外由于不能保证分布式环境下的物理时钟一致性，也可能存在UnixTimestamp重复问题，只不过极少情况下会遇到。\n\n\n*   V3.1\n\n```\ntryLock(){  \n    SET Key UniqId Seconds\n}\nrelease(){  \n    EVAL(\n      //LuaScript\n      if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n          return redis.call(\"del\",KEYS[1])\n      else\n          return 0\n      end\n    )\n}\n\n```\n\nRedis 2.6.12后[SET](https://redis.io/commands/set)同样提供了一个NX参数，等同于SETNX命令，官方文档上提醒后面的版本有可能去掉[SETNX](https://redis.io/commands/setnx),[SETEX](https://redis.io/commands/setex),[PSETEX](https://redis.io/commands/psetex),并用SET命令代替，另外一个优化是使用一个自增的唯一UniqId代替时间戳来规避V3.0提到的时钟问题。\n\n这个方案是目前最优的分布式锁方案，但是如果在Redis集群环境下依然存在问题：\n\n由于Redis集群数据同步为异步，假设在Master节点获取到锁后未完成数据同步情况下Master节点crash，此时在新的Master节点依然可以获取锁，所以多个Client同时获取到了锁\n\n### 分布式Redis锁：Redlock\n\nV3.1的版本仅在单实例的场景下是安全的，针对如何实现分布式Redis的锁，国外的分布式专家有过激烈的讨论，antirez提出了分布式锁算法Redlock，在[distlock](https://redis.io/topics/distlock)话题下可以看到对Redlock的详细说明，下面是Redlock算法的一个中文说明（引用）\n\n假设有N个独立的Redis节点\n\n1.  获取当前时间（毫秒数）。\n\n2.  按顺序依次向N个Redis节点执行获取锁的操作。这个获取操作跟前面基于单Redis节点的获取锁的过程相同，包含随机字符串_my_random_value_，也包含过期时间(比如_PX 30000_，即锁的有效时间)。为了保证在某个Redis节点不可用的时候算法能够继续运行，这个获取锁的操作还有一个超时时间(time out)，它要远小于锁的有效时间（几十毫秒量级）。客户端在向某个Redis节点获取锁失败以后，应该立即尝试下一个Redis节点。这里的失败，应该包含任何类型的失败，比如该Redis节点不可用，或者该Redis节点上的锁已经被其它客户端持有（注：Redlock原文中这里只提到了Redis节点不可用的情况，但也应该包含其它的失败情况）。\n\n3.  计算整个获取锁的过程总共消耗了多长时间，计算方法是用当前时间减去第1步记录的时间。如果客户端从大多数Redis节点（>= N/2+1）成功获取到了锁，并且获取锁总共消耗的时间没有超过锁的有效时间(lock validity time)，那么这时客户端才认为最终获取锁成功；否则，认为最终获取锁失败。\n\n4.  如果最终获取锁成功了，那么这个锁的有效时间应该重新计算，它等于最初的锁的有效时间减去第3步计算出来的获取锁消耗的时间。\n\n5.  如果最终获取锁失败了（可能由于获取到锁的Redis节点个数少于N/2+1，或者整个获取锁的过程消耗的时间超过了锁的最初有效时间），那么客户端应该立即向所有Redis节点发起释放锁的操作（即前面介绍的Redis Lua脚本）。\n\n6.  释放锁：对所有的Redis节点发起释放锁操作\n\n然而Martin Kleppmann针对这个算法提出了[质疑](http://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html)，提出应该基于fencing token机制（每次对资源进行操作都需要进行token验证）\n\n```\n  1. Redlock在系统模型上尤其是在分布式时钟一致性问题上提出了假设，实际场景下存在时钟不一致和时钟跳跃问题，而Redlock恰恰是基于timing的分布式锁  2. 另外Redlock由于是基于自动过期机制，依然没有解决长时间的gc pause等问题带来的锁自动失效，从而带来的安全性问题。\n```\n\n接着antirez又[回复](http://antirez.com/news/101)了Martin Kleppmann的质疑，给出了过期机制的合理性，以及实际场景中如果出现停顿问题导致多个Client同时访问资源的情况下如何处理。\n\n针对Redlock的问题，[基于Redis的分布式锁到底安全吗](http://zhangtielei.com/posts/blog-Redlock-reasoning.html)给出了详细的中文说明，并对Redlock算法存在的问题提出了分析。\n\n### 总结\n\n不论是基于SETNX版本的Redis单实例分布式锁，还是Redlock分布式锁，都是为了保证下特性\n\n```\n  1\\. 安全性：在同一时间不允许多个Client同时持有锁  2\\. 活性    死锁：锁最终应该能够被释放，即使Client端crash或者出现网络分区（通常基于超时机制）    容错性：只要超过半数Redis节点可用，锁都能被正确获取和释放\n```\n\n所以在开发或者使用分布式锁的过程中要保证安全性和活性，避免出现不可预测的结果。\n\n另外每个版本的分布式锁都存在一些问题，在锁的使用上要针对锁的实用场景选择合适的锁，通常情况下锁的使用场景包括：\n\nEfficiency(效率)：只需要一个Client来完成操作，不需要重复执行，这是一个对宽松的分布式锁，只需要保证锁的活性即可；\n\nCorrectness(正确性)：多个Client保证严格的互斥性，不允许出现同时持有锁或者对同时操作同一资源，这种场景下需要在锁的选择和使用上更加严格，同时在业务代码上尽量做到幂等\n\n在Redis分布式锁的实现上还有很多问题等待解决，我们需要认识到这些问题并清楚如何正确实现一个Redis 分布式锁，然后在工作中合理的选择和正确的使用分布式锁。\n\n\n\n\n\n"
  },
  {
    "path": "docs/cache/探索Redis设计与实现：Redis的事件驱动模型与命令执行过程.md",
    "content": "[toc]\n\n本文转自https://www.xilidou.com/2018/03/12/redis-data/  \n作者：犀利豆  \n本文将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，Redis基本的使用方法，Redis的基本数据结构，以及一些进阶的使用方法，同时也需要进一步了解Redis的底层数据结构，再接着，还会带来Redis主从复制、集群、分布式锁等方面的相关内容，以及作为缓存的一些使用方法和注意事项，以便让你更完整地了解整个Redis相关的技术体系，形成自己的知识框架。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->  \n\n原文地址：https://www.xilidou.com/2018/03/22/redis-event/\n\nRedis 是一个事件驱动的内存数据库，服务器需要处理两种类型的事件。\n\n文件事件  \n时间事件  \n下面就会介绍这两种事件的实现原理。\n\n文件事件  \nRedis 服务器通过 socket 实现与客户端（或其他redis服务器）的交互,文件事件就是服务器对 socket 操作的抽象。 Redis 服务器，通过监听这些 socket 产生的文件事件并处理这些事件，实现对客户端调用的响应。\n\nReactor  \nRedis 基于 Reactor 模式开发了自己的事件处理器。\n\n这里就先展开讲一讲 Reactor 模式。看下图：  \n![image](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/Reactor.jpg)  \nreactor\n\n“I/O 多路复用模块”会监听多个 FD ，当这些FD产生，accept，read，write 或 close 的文件事件。会向“文件事件分发器（dispatcher）”传送事件。\n\n文件事件分发器（dispatcher）在收到事件之后，会根据事件的类型将事件分发给对应的 handler。\n\n我们顺着图，从上到下的逐一讲解 Redis 是怎么实现这个 Reactor 模型的。\n\nI/O 多路复用模块  \nRedis 的 I/O 多路复用模块，其实是封装了操作系统提供的 select，epoll，avport 和 kqueue 这些基础函数。向上层提供了一个统一的接口，屏蔽了底层实现的细节。\n\n一般而言 Redis 都是部署到 Linux 系统上，所以我们就看看使用 Redis 是怎么利用 linux 提供的 epoll 实现I/O 多路复用。\n\n首先看看 epoll 提供的三个方法：\n````\n    /*     * 创建一个epoll的句柄，size用来告诉内核这个监听的数目一共有多大  \n     */    int epoll_create(int size)；  \n        /*  \n     * 可以理解为，增删改 fd 需要监听的事件  \n     * epfd 是 epoll_create() 创建的句柄。  \n     * op 表示 增删改  \n     * epoll_event 表示需要监听的事件，Redis 只用到了可读，可写，错误，挂断 四个状态  \n     */    int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)；  \n        /*  \n     * 可以理解为查询符合条件的事件  \n     * epfd 是 epoll_create() 创建的句柄。  \n     * epoll_event 用来存放从内核得到事件的集合  \n     * maxevents 获取的最大事件数  \n     * timeout 等待超时时间  \n     */    int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);    再看 Redis 对文件事件，封装epoll向上提供的接口：  \n          \n    /*  \n     * 事件状态  \n     */    typedef struct aeApiState {    // epoll_event 实例描述符  \n        int epfd;    // 事件槽  \n        struct epoll_event *events;        } aeApiState;  \n        /*  \n     * 创建一个新的 epoll     */  \n    static int  aeApiCreate(aeEventLoop *eventLoop)    /*     * 调整事件槽的大小  \n     */    static int  aeApiResize(aeEventLoop *eventLoop, int setsize)    /*     * 释放 epoll 实例和事件槽  \n     */    static void aeApiFree(aeEventLoop *eventLoop)    /*     * 关联给定事件到 fd     */    static int  aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask)    /*     * 从 fd 中删除给定事件  \n     */    static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int mask)    /*     * 获取可执行事件  \n     */    static int  aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp)所以看看这个ae_peoll.c 如何对 epoll 进行封装的：  \n````\naeApiCreate() 是对 epoll.epoll_create() 的封装。  \naeApiAddEvent()和aeApiDelEvent() 是对 epoll.epoll_ctl()的封装。  \naeApiPoll() 是对 epoll_wait()的封装。  \n这样 Redis 的利用 epoll 实现的 I/O 复用器就比较清晰了。\n\n再往上一层次我们需要看看 ea.c 是怎么封装的？\n\n首先需要关注的是事件处理器的数据结构：\n\n````\n    typedef struct aeFileEvent {    // 监听事件类型掩码，  \n        // 值可以是 AE_READABLE 或 AE_WRITABLE ，  \n        // 或者 AE_READABLE | AE_WRITABLE        int mask; /* one of AE_(READABLE|WRITABLE) */    // 读事件处理器  \n        aeFileProc *rfileProc;    // 写事件处理器  \n        aeFileProc *wfileProc;    // 多路复用库的私有数据  \n        void *clientData;        } aeFileEvent;  \n````\nmask 就是可以理解为事件的类型。\n\n除了使用 ae_peoll.c 提供的方法外,ae.c 还增加 “增删查” 的几个 API。\n\n增:aeCreateFileEvent  \n删:aeDeleteFileEvent  \n查: 查包括两个维度 aeGetFileEvents 获取某个 fd 的监听类型和aeWait等待某个fd 直到超时或者达到某个状态。  \n事件分发器（dispatcher）  \nRedis 的事件分发器 ae.c/aeProcessEvents 不但处理文件事件还处理时间事件，所以这里只贴与文件分发相关的出部分代码，dispather 根据 mask 调用不同的事件处理器。\n````\n    //从 epoll 中获关注的事件  \n    numevents = aeApiPoll(eventLoop, tvp);    for (j = 0; j < numevents; j++) {        // 从已就绪数组中获取事件  \n        aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd];            int mask = eventLoop->fired[j].mask;  \n        int fd = eventLoop->fired[j].fd;        int rfired = 0;    // 读事件  \n        if (fe->mask & mask & AE_READABLE) {            // rfired 确保读/写事件只能执行其中一个  \n            rfired = 1;            fe->rfileProc(eventLoop,fd,fe->clientData,mask);        }        // 写事件  \n        if (fe->mask & mask & AE_WRITABLE) {            if (!rfired || fe->wfileProc != fe->rfileProc)                fe->wfileProc(eventLoop,fd,fe->clientData,mask);        }            processed++;  \n    }可以看到这个分发器，根据 mask 的不同将事件分别分发给了读事件和写事件。  \n````\n文件事件处理器的类型  \nRedis 有大量的事件处理器类型，我们就讲解处理一个简单命令涉及到的三个处理器：\n\nacceptTcpHandler 连接应答处理器，负责处理连接相关的事件，当有client 连接到Redis的时候们就会产生 AE_READABLE 事件。引发它执行。  \nreadQueryFromClinet 命令请求处理器，负责读取通过 sokect 发送来的命令。  \nsendReplyToClient 命令回复处理器，当Redis处理完命令，就会产生 AE_WRITEABLE 事件，将数据回复给 client。  \n文件事件实现总结  \n我们按照开始给出的 Reactor 模型，从上到下讲解了文件事件处理器的实现，下面将会介绍时间时间的实现。\n\n时间事件  \nReids 有很多操作需要在给定的时间点进行处理，时间事件就是对这类定时任务的抽象。\n\n先看时间事件的数据结构：\n````\n    /* Time event structure     *     * 时间事件结构  \n     */    typedef struct aeTimeEvent {    // 时间事件的唯一标识符  \n        long long id; /* time event identifier. */    // 事件的到达时间  \n        long when_sec; /* seconds */        long when_ms; /* milliseconds */    // 事件处理函数  \n        aeTimeProc *timeProc;    // 事件释放函数  \n        aeEventFinalizerProc *finalizerProc;    // 多路复用库的私有数据  \n        void *clientData;    // 指向下个时间事件结构，形成链表  \n        struct aeTimeEvent *next;        } aeTimeEvent;  \n````\n看见 next 我们就知道这个 aeTimeEvent 是一个链表结构。看图：  \n![image](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/timeEvent.jpg)\n\ntimeEvent\n\n注意这是一个按照id倒序排列的链表，并没有按照事件顺序排序。\n\nprocessTimeEvent  \nRedis 使用这个函数处理所有的时间事件，我们整理一下执行思路：\n\n记录最新一次执行这个函数的时间，用于处理系统时间被修改产生的问题。  \n遍历链表找出所有 when_sec 和 when_ms 小于现在时间的事件。  \n执行事件对应的处理函数。  \n检查事件类型，如果是周期事件则刷新该事件下一次的执行事件。  \n否则从列表中删除事件。  \n综合调度器（aeProcessEvents）  \n综合调度器是 Redis 统一处理所有事件的地方。我们梳理一下这个函数的简单逻辑：\n````\n    // 1. 获取离当前时间最近的时间事件  \n    shortest = aeSearchNearestTimer(eventLoop);    // 2. 获取间隔时间  \n    timeval = shortest - nowTime;    // 如果timeval 小于 0，说明已经有需要执行的时间事件了。  \n    if(timeval < 0){        timeval = 0    }    // 3. 在 timeval 时间内，取出文件事件。  \n    numevents = aeApiPoll(eventLoop, timeval);        // 4.根据文件事件的类型指定不同的文件处理器  \n    if (AE_READABLE) {        // 读事件  \n        rfileProc(eventLoop,fd,fe->clientData,mask);    }        // 写事件  \n    if (AE_WRITABLE) {        wfileProc(eventLoop,fd,fe->clientData,mask);    }以上的伪代码就是整个 Redis 事件处理器的逻辑。  \n````\n我们可以再看看谁执行了这个 aeProcessEvents:\n````\n    void aeMain(aeEventLoop *eventLoop) {            eventLoop->stop = 0;  \n            while (!eventLoop->stop) {  \n    // 如果有需要在事件处理前执行的函数，那么运行它  \n            if (eventLoop->beforesleep != NULL)                eventLoop->beforesleep(eventLoop);    // 开始处理事件  \n            aeProcessEvents(eventLoop, AE_ALL_EVENTS);        }    }然后我们再看看是谁调用了 eaMain:  \n    int main(int argc, char **argv) {        //一些配置和准备  \n        ...        aeMain(server.el);                //结束后的回收工作  \n        ...    }我们在 Redis 的 main 方法中找个了它。  \n````\n这个时候我们整理出的思路就是:\n\nRedis 的 main() 方法执行了一些配置和准备以后就调用 eaMain() 方法。\n\neaMain() while(true) 的调用 aeProcessEvents()。\n\n所以我们说 Redis 是一个事件驱动的程序，期间我们发现，Redis 没有 fork 过任何线程。所以也可以说 Redis 是一个基于事件驱动的单线程应用。\n\n总结  \n在后端的面试中 Redis 总是一个或多或少会问到的问题。\n\n读完这篇文章你也许就能回答这几个问题：\n\n为什么 Redis 是一个单线程应用？  \n为什么 Redis 是一个单线程应用，却有如此高的性能？  \n如果你用本文提供的知识点回答这两个问题，一定会在面试官心中留下一个高大的形象。\n\n大家还可以阅读我的 Redis 相关的文章：\n"
  },
  {
    "path": "docs/cache/探索Redis设计与实现：Redis集群机制及一个Redis架构演进实例.md",
    "content": "# 目录\n\n  * [Replication（主从复制）](#replication（主从复制）)\n    * [配置主服务器](#配置主服务器)\n    * [配置从服务器](#配置从服务器)\n    * [测试](#测试)\n  * [Sentinel（哨兵）](#sentinel（哨兵）)\n    * [配置Sentinel](#配置sentinel)\n    * [启动 Sentinel](#启动-sentinel)\n    * [测试](#测试-1)\n  * [Twemproxy](#twemproxy)\n  * [Codis](#codis)\n  * [Redis 3.0集群](#redis-30集群)\n    * [环境搭建](#环境搭建)\n* [根据实际情况修改](#根据实际情况修改)\n* [允许redis支持集群模式](#允许redis支持集群模式)\n* [节点配置文件，由redis自动维护](#节点配置文件，由redis自动维护)\n* [节点超时毫秒](#节点超时毫秒)\n* [开启AOF同步模式](#开启aof同步模式)\n    * [创建集群](#创建集群)\n\n\n## Replication（主从复制）\n\nRedis的replication机制允许slave从master那里通过网络传输拷贝到完整的数据备份，从而达到主从机制。为了实现主从复制，我们准备三个redis服务，依次命名为master，slave1，slave2。\n\n### 配置主服务器\n\n为了测试效果，我们先修改主服务器的配置文件redis.conf的端口信息\n\n\n\n1.  port 6300\n\n\n### 配置从服务器\n\nreplication相关的配置比较简单，只需要把下面一行加到slave的配置文件中。你只需要把ip地址和端口号改一下。\n\n1.  slaveof 192.168.1.1  6379\n\n\n\n我们先修改从服务器1的配置文件redis.conf的端口信息和从服务器配置。\n\n\n\n\n1.  port 6301\n2.  slaveof 127.0.0.1  6300\n\n\n\n我们再修改从服务器2的配置文件redis.conf的端口信息和从服务器配置。\n\n\n\n\n1.  port 6302\n2.  slaveof 127.0.0.1  6300\n\n\n\n值得注意的是，从redis2.6版本开始，slave支持只读模式，而且是默认的。可以通过配置项slave-read-only来进行配置。\n此外，如果master通过requirepass配置项设置了密码，slave每次同步操作都需要验证密码，可以通过在slave的配置文件中添加以下配置项\n\n\n\n\n1.  masterauth <password>\n\n\n\n### 测试\n\n分别启动主服务器，从服务器，我们来验证下主从复制。我们在主服务器写入一条消息，然后再其他从服务器查看是否成功复制了。\n\n## Sentinel（哨兵）\n\n主从机制，上面的方案中主服务器可能存在单点故障，万一主服务器宕机，这是个麻烦事情，所以Redis提供了Redis-Sentinel，以此来实现主从切换的功能，类似与zookeeper。\n\nRedis-Sentinel是Redis官方推荐的高可用性(HA)解决方案，当用Redis做master-slave的高可用方案时，假如master宕机了，Redis本身(包括它的很多客户端)都没有实现自动进行主备切换，而Redis-Sentinel本身也是一个独立运行的进程，它能监控多个master-slave集群，发现master宕机后能进行自动切换。\n\n它的主要功能有以下几点\n\n*   监控（Monitoring）：不断地检查redis的主服务器和从服务器是否运作正常。\n*   提醒（Notification）：如果发现某个redis服务器运行出现状况，可以通过 API 向管理员或者其他应用程序发送通知。\n*   自动故障迁移（Automatic failover）：能够进行自动切换。当一个主服务器不能正常工作时，会将失效主服务器的其中一个从服务器升级为新的主服务器，并让失效主服务器的其他从服务器改为复制新的主服务器； 当客户端试图连接失效的主服务器时， 集群也会向客户端返回新主服务器的地址， 使得集群可以使用新主服务器代替失效服务器。\n\nRedis Sentinel 兼容 Redis 2.4.16 或以上版本， 推荐使用 Redis 2.8.0 或以上的版本。\n\n### 配置Sentinel\n\n必须指定一个sentinel的配置文件sentinel.conf，如果不指定将无法启动sentinel。首先，我们先创建一个配置文件sentinel.conf\n\n\n````\n\nport 26379\nsentinel monitor mymaster 127.0.0.1  6300  2\n````\n\n\n官方典型的配置如下\n\n\n````\n\nsentinel monitor mymaster 127.0.0.1  6379  2\nsentinel down-after-milliseconds mymaster 60000\nsentinel failover-timeout mymaster 180000\nsentinel parallel-syncs mymaster 1\n\nsentinel monitor resque 192.168.1.3  6380  4\nsentinel down-after-milliseconds resque 10000\nsentinel failover-timeout resque 180000\nsentinel parallel-syncs resque 5\n````\n\n\n配置文件只需要配置master的信息就好啦，不用配置slave的信息，因为slave能够被自动检测到(master节点会有关于slave的消息)。\n\n需要注意的是，配置文件在sentinel运行期间是会被动态修改的，例如当发生主备切换时候，配置文件中的master会被修改为另外一个slave。这样，之后sentinel如果重启时，就可以根据这个配置来恢复其之前所监控的redis集群的状态。\n\n接下来我们将一行一行地解释上面的配置项：\n\n\n````\n\nsentinel monitor mymaster 127.0.0.1  6379  2\n````\n\n\n这行配置指示 Sentinel 去监视一个名为 mymaster 的主服务器， 这个主服务器的 IP 地址为 127.0.0.1 ， 端口号为 6300， 而将这个主服务器判断为失效至少需要 2 个 Sentinel 同意，只要同意 Sentinel 的数量不达标，自动故障迁移就不会执行。\n\n不过要注意， 无论你设置要多少个 Sentinel 同意才能判断一个服务器失效， 一个 Sentinel 都需要获得系统中多数（majority） Sentinel 的支持， 才能发起一次自动故障迁移， 并预留一个给定的配置纪元 （configuration Epoch ，一个配置纪元就是一个新主服务器配置的版本号）。换句话说， 在只有少数（minority） Sentinel 进程正常运作的情况下， Sentinel 是不能执行自动故障迁移的。sentinel集群中各个sentinel也有互相通信，通过gossip协议。\n\n除了第一行配置，我们发现剩下的配置都有一个统一的格式:\n\n\n\n````\nsentinel <option_name>  <master_name>  <option_value>\n````\n\n\n接下来我们根据上面格式中的option_name一个一个来解释这些配置项：\n\n*   down-after-milliseconds 选项指定了 Sentinel 认为服务器已经断线所需的毫秒数。\n*   parallel-syncs 选项指定了在执行故障转移时， 最多可以有多少个从服务器同时对新的主服务器进行同步， 这个数字越小， 完成故障转移所需的时间就越长。\n\n### 启动 Sentinel\n\n对于 redis-sentinel 程序， 你可以用以下命令来启动 Sentinel 系统\n\n\n````\n\nredis-sentinel sentinel.conf\n````\n\n\n对于 redis-server 程序， 你可以用以下命令来启动一个运行在 Sentinel 模式下的 Redis 服务器\n\n\n\n````\nredis-server sentinel.conf --sentinel\n````\n\n\n以上两种方式，都必须指定一个sentinel的配置文件sentinel.conf， 如果不指定将无法启动sentinel。sentinel默认监听26379端口，所以运行前必须确定该端口没有被别的进程占用。\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/sentinel01.png)\n\n### 测试\n\n此时，我们开启两个Sentinel，关闭主服务器，我们来验证下Sentinel。发现，服务器发生切换了。\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/sentinel02.png)\n当6300端口的这个服务重启的时候，他会变成6301端口服务的slave。\n\n## Twemproxy\n\nTwemproxy是由Twitter开源的Redis代理， Redis客户端把请求发送到Twemproxy，Twemproxy根据路由规则发送到正确的Redis实例，最后Twemproxy把结果汇集返回给客户端。\n\nTwemproxy通过引入一个代理层，将多个Redis实例进行统一管理，使Redis客户端只需要在Twemproxy上进行操作，而不需要关心后面有多少个Redis实例，从而实现了Redis集群。\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/twemproxy01.png)\nTwemproxy本身也是单点，需要用Keepalived做高可用方案。\n\n这么些年来，Twenproxy作为应用范围最广、稳定性最高、最久经考验的分布式中间件，在业界广泛使用。\n\n但是，Twemproxy存在诸多不方便之处，最主要的是，Twemproxy无法平滑地增加Redis实例，业务量突增，需增加Redis服务器；业务量萎缩，需要减少Redis服务器。但对Twemproxy而言，基本上都很难操作。其次，没有友好的监控管理后台界面，不利于运维监控。\n\n## Codis\n\nCodis解决了Twemproxy的这两大痛点，由豌豆荚于2014年11月开源，基于Go和C开发、现已广泛用于豌豆荚的各种Redis业务场景。\n\nCodis 3.x 由以下组件组成：\n\n*   Codis Server：基于 redis-2.8.21 分支开发。增加了额外的数据结构，以支持 slot 有关的操作以及数据迁移指令。具体的修改可以参考文档 redis 的修改。\n*   Codis Proxy：客户端连接的 Redis 代理服务, 实现了 Redis 协议。 除部分命令不支持以外(不支持的命令列表)，表现的和原生的 Redis 没有区别（就像 Twemproxy）。对于同一个业务集群而言，可以同时部署多个 codis-proxy 实例；不同 codis-proxy 之间由 codis-dashboard 保证状态同步。\n*   Codis Dashboard：集群管理工具，支持 codis-proxy、codis-server 的添加、删除，以及据迁移等操作。在集群状态发生改变时，codis-dashboard 维护集群下所有 codis-proxy 的状态的一致性。对于同一个业务集群而言，同一个时刻 codis-dashboard 只能有 0个或者1个；所有对集群的修改都必须通过 codis-dashboard 完成。\n*   Codis Admin：集群管理的命令行工具。可用于控制 codis-proxy、codis-dashboard 状态以及访问外部存储。\n*   Codis FE：集群管理界面。多个集群实例共享可以共享同一个前端展示页面；通过配置文件管理后端 codis-dashboard 列表，配置文件可自动更新。\n*   Codis HA：为集群提供高可用。依赖 codis-dashboard 实例，自动抓取集群各个组件的状态；会根据当前集群状态自动生成主从切换策略，并在需要时通过 codis-dashboard 完成主从切换。\n*   Storage：为集群状态提供外部存储。提供 Namespace 概念，不同集群的会按照不同 product name 进行组织；目前仅提供了 Zookeeper 和 Etcd 两种实现，但是提供了抽象的 interface 可自行扩展。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/codis02.png)\n\nCodis引入了Group的概念，每个Group包括1个Redis Master及一个或多个Redis Slave，这是和Twemproxy的区别之一，实现了Redis集群的高可用。当1个Redis Master挂掉时，Codis不会自动把一个Slave提升为Master，这涉及数据的一致性问题，Redis本身的数据同步是采用主从异步复制，当数据在Maste写入成功时，Slave是否已读入这个数据是没法保证的，需要管理员在管理界面上手动把Slave提升为Master。\n\nCodis使用，可以参考官方文档[https://github.com/CodisLabs/codis/blob/release3.0/doc/tutorial_zh.md](https://github.com/CodisLabs/codis/blob/release3.0/doc/tutorial_zh.md)\n\n## Redis 3.0集群\n\nRedis 3.0集群采用了P2P的模式，完全去中心化。支持多节点数据集自动分片，提供一定程度的分区可用性，部分节点挂掉或者无法连接其他节点后，服务可以正常运行。Redis 3.0集群采用Hash Slot方案，而不是一致性哈希。Redis把所有的Key分成了16384个slot，每个Redis实例负责其中一部分slot。集群中的所有信息（节点、端口、slot等），都通过节点之间定期的数据交换而更新。\n\nRedis客户端在任意一个Redis实例发出请求，如果所需数据不在该实例中，通过重定向命令引导客户端访问所需的实例。\n\nRedis 3.0集群，目前支持的cluster特性\n\n*   节点自动发现\n*   slave->master 选举,集群容错\n*   Hot resharding:在线分片\n*   集群管理:cluster xxx\n*   基于配置(nodes-port.conf)的集群管理\n*   ASK 转向/MOVED 转向机制\n\n* ![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/redis3-cluster01.png)\n\n如上图所示，所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽。节点的fail是通过集群中超过半数的节点检测失效时才生效。客户端与redis节点直连，不需要中间proxy层。客户端不需要连接集群所有节点，连接集群中任何一个可用节点即可。redis-cluster把所有的物理节点映射到[0-16383]slot上cluster负责维护node<->slot<->value。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/redis3-cluster02.png)\n\n选举过程是集群中所有master参与，如果半数以上master节点与master节点通信超时，认为当前master节点挂掉。\n\n当集群不可用时，所有对集群的操作做都不可用，收到((error) CLUSTERDOWN The cluster is down)错误。如果集群任意master挂掉，且当前master没有slave，集群进入fail状态，也可以理解成进群的slot映射[0-16383]不完成时进入fail状态。如果进群超过半数以上master挂掉，无论是否有slave集群进入fail状态。\n\n### 环境搭建\n\n现在，我们进行集群环境搭建。集群环境至少需要3个主服务器节点。本次测试，使用另外3个节点作为从服务器的节点，即3个主服务器，3个从服务器。\n\n修改配置文件，其它的保持默认即可。\n\n\n\n````\n# 根据实际情况修改\n\nport 7000  \n\n# 允许redis支持集群模式\n\ncluster-enabled yes \n\n# 节点配置文件，由redis自动维护\n\ncluster-config-file nodes.conf \n\n# 节点超时毫秒\n\ncluster-node-timeout 5000  \n\n# 开启AOF同步模式\n\nappendonly yes\n\n````\n\n### 创建集群\n\n目前这些实例虽然都开启了cluster模式，但是彼此还不认识对方，接下来可以通过Redis集群的命令行工具redis-trib.rb来完成集群创建。\n首先，下载 [https://raw.githubusercontent.com/antirez/redis/unstable/src/redis-trib.rb](https://raw.githubusercontent.com/antirez/redis/unstable/src/redis-trib.rb \"redis-trib.rb\")。\n\n然后，搭建Redis 的 Ruby 支持环境。这里，不进行扩展，参考相关文档。\n\n现在，接下来运行以下命令。这个命令在这里用于创建一个新的集群, 选项–replicas 1 表示我们希望为集群中的每个主节点创建一个从节点。\n\n1.  redis-trib.rb create --replicas 1  127.0.0.1:7001  127.0.0.1:7002  127.0.0.1:7003  127.0.0.1:7004  127.0.0.1:7005  127.0.0.1:7006\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/redis3-cluster03.png)\n\n5.3、测试\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/redis3-cluster04.png)\n\n*   版权声明：本文由 **梁桂钊** 发表于 [梁桂钊的博客](http://blog.720ui.com/)\n*   转载声明：转载请联系公众号【服务端思维】。\n*   文章标题：[Redis实战（四） 集群机制 | 梁桂钊的博客](http://blog.720ui.com/2016/redis_action_04_cluster/)\n*   文章链接：[http://blog.720ui.com/2016/redis_action_04_cluster/](http://blog.720ui.com/2016/redis_action_04_cluster/)\n"
  },
  {
    "path": "docs/cache/探索Redis设计与实现：使用快照和AOF将Redis数据持久化到硬盘中.md",
    "content": "# 目录\n\n* [前言](#前言)\n  * [快照持久化](#快照持久化)\n  * [AOF持久化](#aof持久化)\n  * [验证快照文件和AOF文件](#验证快照文件和aof文件)\n  * [总结](#总结)\n\n\n[toc]\n\n本文转自互联网\n本文将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，Redis基本的使用方法，Redis的基本数据结构，以及一些进阶的使用方法，同时也需要进一步了解Redis的底层数据结构，再接着，还会带来Redis主从复制、集群、分布式锁等方面的相关内容，以及作为缓存的一些使用方法和注意事项，以便让你更完整地了解整个Redis相关的技术体系，形成自己的知识框架。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n转自https://blog.csdn.net/xlgen157387/article/details/61925524\n\n# 前言\n\n我们知道Redis是一款内存服务器，就算我们对自己的服务器足够的信任，不会出现任何软件或者硬件的故障，但也会有可能出现突然断电等情况，造成Redis服务器中的数据失效。因此，我们需要向传统的关系型数据库一样对数据进行备份，将Redis在内存中的数据持久化到硬盘等非易失性介质中，来保证数据的可靠性。\n\n将Redis内存服务器中的数据持久化到硬盘等介质中的一个好处就是，使得我们的服务器在重启之后还可以重用以前的数据，或者是为了防止系统出现故障而将数据备份到一个远程的位置。\n\n还有一些场景，例如：\n\n```\n对于一些需要进行大量计算而得到的数据，放置在Redis服务器，我们就有必要对其进行数据的持久化，如果需要对数据进行恢复的时候，我们就不需进行重新的计算，只需要简单的将这台机器上的数据复制到另一台需要恢复的Redis服务器就可以了。\n```\n\n\nRedis给我们提供了两种不同方式的持久化方法：快照（Snapshotting）和只追加文件（append-only-file）。\n\n（1）名词简介\n\n快照（RDB）：就是我们俗称的备份，他可以在定期内对数据进行备份，将Redis服务器中的数据持久化到硬盘中；\n\n只追加文件（AOF）：他会在执行写命令的时候，将执行的写命令复制到硬盘里面，后期恢复的时候，只需要重新执行一下这个写命令就可以了。类似于我们的MySQL数据库在进行主从复制的时候，使用的是`binlog`二进制文件，同样的是执行一遍写命令；\n\n（2）快照持久化通用的配置：\n\n```\nsave 60 1000  #60秒时间内有1000次写入操作的时候执行快照的创建stop-writes-on-bgsave-error no  #创建快照失败的时候是否仍然继续执行写命令rdbcompression yes  #是否对快照文件进行压缩dbfilename dump.rdb   #如何命名硬盘上的快照文件dir ./  #快照所保存的位置\n```\n\n\n（3）AOP持久化配置：\n\n```\nappendonly no  #是否使用AOF持久化appendfsync everysec  #多久执行一次将写入内容同步到硬盘上no-appendfsync-on-rewrite no #对AOF进行压缩的时候能否执行同步操作auto-aof-rewrite-percentage 100  #多久执行一次AOF压缩auto-aof-rewrite-min-size 64mb  #多久执行一次AOF压缩dir ./ #AOF所保存的位置\n```\n\n\n需要注意的是：这两种持久化的方式既可以单独的使用，也可以同时使用，具体选择哪种方式需要根据具体的情况进行选择。\n\n## 快照持久化\n\n快照就是我们所说的备份。用户可以将Redis内存中的数据在某一个时间点进行备份，在创建快照之后，用户可以对快照进行备份。通常情况下，为了防止单台服务器出现故障造成所有数据的丢失，我们还可以将快照复制到其他服务器，创建具有相同数据的数据副本，这样的话，数据恢复的时候或者服务器重启的时候就可以使用这些快照信息进行数据的恢复，也可以防止单台服务器出现故障的时候造成数据的丢失。\n\n但是，没我们还需要注意的是，创建快照的方式，并不能完全保证我们的数据不丢失，这个大家可以很好的理解，因为快照的创建时定时的，并不是每一次更新操作都会创建一个快照的。系统发生崩溃的时候，用户将丢失最近一次生成快照之后更改的所有数据。因此，快照持久化的方式只适合于数据不经常修改或者丢失部分数据影响不大的场景。\n\n一、创建快照的方式：\n\n（1）客户端通过向Redis发送`BGSAVE`命令来创建快照。\n\n使用BGSAVE的时候，Redis会调用fork来创建一个子进程，然后子进程负责将快照写到硬盘中，而父进程则继续处理命令请求。\n\n使用场景：\n\n如果用户使用了save设置，例如：`save 60 1000`,那么从Redis最近一次创建快照之后开始计算，当“60秒之内有1000次写入操作”这个条件满足的时候，Redis就会自动触发BGSAVE命令。\n\n如果用户使用了多个save设置，那么当任意一个save配置满足条件的时候，Redis都会触发一次BGSAVE命令。\n\n（2）客户端通过向Redis发送`SAVE`命令来创建快照。\n\n接收到SAVE命令的Redis服务器在快照创建完毕之前将不再响应任何其他命令的请求。SAVE命令并不常用，我们通常只在没有足够的内存去执行BGSAVE命令的时候才会使用SAVE命令，或者即使等待持久化操作执行完毕也无所谓的情况下，才会使用这个命令；\n\n使用场景：\n\n当Redis通过SHUTDOWN命令接收到关闭服务器的请求时，或者接收到标准的TERM信号时，会执行一次SAVE命令，阻塞所有的客户端，不再执行客户端发送的任何命令，并且在执行完SAVE命令之后关闭服务器。\n\n二、使用快照持久化注意事项：\n\n我们在使用快照的方式来保存数据的时候，如果Redis服务器中的数据量比较小的话，例如只有几个GB的时候。Redis会创建子进程并将数据保存到硬盘里边，生成快照所需的时间比读取数据所需要的时间还要短。\n\n但是，随着数据的增大，Redis占用的内存越来越大的时候，BGSAVE在创建子进程的时候消耗的时间也会越来越多，如果Redis服务器所剩下的内存不多的时候，这行BGSAVE命令会使得系统长时间地停顿，还有可能导致服务器无法使用。\n\n各虚拟机类别，创建子线程所耗时间：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230406210020.png)\n\n因此，为了防止Redis因为创建子进程的时候出现停顿，我们可以考虑关闭自动保存，转而通过手动的方式发送BGSAVE或者SAVE来进行持久化，\n\n手动的方式发送BGSAVE也会出现停顿的现象，但是我们可以控制发送该命令的时间来控制出现停顿的时候不影响具体的业务请求。\n\n另外，值得注意的是，在使用SAVE命令的时候，虽然会一直阻塞Redis直到快照生成完毕，但是其不需要创建子进程，所以不会向BGSAVE一样，因为创建子进程而导致Redis停顿。也正因为如此，SAVE创建快照的速度要比BGSAVE创建快照的速度更快一些。\n\n创建快照的时候，我们可以在业务请求，比较少的时候，比如凌晨三、四点，通过手写脚本的方式，定时执行。\n\n## AOF持久化\n\nAOF持久化会将被执行的写命令写到AOF文件的末尾，以此来记录数据发生的变化。这样，我们在恢复数据的时候，只需要从头到尾的执行一下AOF文件即可恢复数据。\n\n一、打开AOF持久化选项\n\n我们可以通过使用如下命令打开AOF：\n\n```\nappendonly yes\n```\n\n\n我们，通过如下命令来配置AOF文件的同步频率：\n\n```\nappendfsync everysec/always/no\n```\n\n\n二、appendfsync同步频率的区别\n\nappendfsync同步频率的区别如下图：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230406210035.png)\n\n（1）always的方式固然可以对没一条数据进行很好的保存，但是这种同步策略需要对硬盘进行大量的写操作，所以Redis处理命令的速度会受到硬盘性能的限制。\n\n普通的硬盘每秒钟只能处理大约200个写命令，使用固态硬盘SSD每秒可以处理几万个写命令，但是每次只写一个命令，这种只能怪不断地写入很少量的数据的做法有可能引发严重的写入放大问题，这种情况下降严重影响固态硬盘的使用寿命。\n\n（2）everysec的方式，Redis以每秒一次的频率大队AOF文件进行同步。这样的话既可以兼顾数据安全也可以兼顾写入性能。\n\nRedis以每秒同步一次AOF文件的性能和不使用任何持久化特性时的性能相差无几，使用每秒更新一次 的方式，可以保证，即使出现故障，丢失的数据也在一秒之内产生的数据。\n\n（3）no的方式，Redis将不对AOF文件执行任何显示的同步操作，而是由操作系统来决定应该何时对AOF文件进行同步。\n\n这个命令一般不会对Redis的性能造成多大的影响，但是当系统出现故障的时候使用这种选项的Redis服务器丢失不定数量的数据。\n\n另外，当用户的硬盘处理写入操作的速度不够快的话，那么缓冲区被等待写入硬盘的数据填满时，Redis的写入操作将被阻塞，并导致Redis处理命令请求的速度变慢，因为这个原因，一般不推荐使用这个选项。\n\n三、重写/压缩AOF文件\n\n随着数据量的增大，AOF的文件可能会很大，这样在每次进行数据恢复的时候就会进行很长的时间，为了解决日益增大的AOF文件，用户可以向Redis发送`BGREWRITEAOF`命令，这个命令会通过移除AOF文件中的冗余命令来重写AOF文件，是AOF文件的体检变得尽可能的小。\n\nBGREWRITEAOF的工作原理和BGSAVE的原理很像：Redis会创建一个子进程，然后由子进程负责对AOF文件的重写操作。\n\n因为AOF文件重写的时候汇创建子进程，所以快照持久化因为创建子进程而导致的性能和内存占用问题同样会出现在AOF文件重写的 时候。\n\n四、触发重写/压缩AOF文件条件设定\n\nAOF通过设置`auto-aof-rewrite-percentage`和`auto-aof-rewrite-min-size`选项来自动执行BGREWRITEAOF。\n\n其具体含义，通过实例可以看出，如下配置：\n\n```\nauto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb\n```\n\n表示当前AOF的文件体积大于64MB，并且AOF文件的体积比上一次重写之后的体积变大了至少一倍（100%）的时候，Redis将执行重写BGREWRITEAOF命令。\n\n如果AOF重写执行的过于频繁的话，可以将`auto-aof-rewrite-percentage`选项的值设置为100以上，这种最偶发就可以让Redis在AOF文件的体积变得更大之后才执行重写操作，不过，这也使得在进行数据恢复的时候执行的时间变得更加长一些。\n\n## 验证快照文件和AOF文件\n\n无论使用哪种方式进行持久化，我们在进行恢复数据的时候，Redis提供了两个命令行程序：\n\n```\nredis-check-aofredis-check-dump\n```\n\n他们可以再系统发生故障的时候，检查快照和AOF文件的状态，并对有需要的情况对文件进行修复。\n\n如果用户在运行redis-check-aof命令的时候，指定了`--fix`参数，那么程序将对AOF文件进行修复。\n\n程序修复AOF文件的方法很简单：他会扫描给定的AOF文件，寻找不正确或者不完整的命令，当发现第一个出现错误命令的时候，程序会删除出错命令以及出错命令之后的所有命令，只保留那些位于出错命令之前的正确命令。大部分情况，被删除的都是AOF文件末尾的不完整的写命令。\n\n## 总结\n\n上述，一起学习了两种支持持久化的方式，一方面我们需要通过快照或者AOF的方式对数据进行持久化，另一方面，我们还需要将持久化所得到的文件进行备份，备份到不同的服务器上，这样才可以尽可能的减少数据丢失的损失。\n\n* * *\n\n参考文章：\n\n1、Redis in Action - [美] Josiah L.Carlsono\n\n\n\n"
  },
  {
    "path": "docs/cache/探索Redis设计与实现：数据库redisDb与键过期删除策略.md",
    "content": "# 目录\n\n  * [一. 数据库](#一-数据库)\n  * [二、切换数据库原理](#二、切换数据库原理)\n  * [三、数据库的键空间](#三、数据库的键空间)\n    * [1、数据库的结构（我们只分析键空间和键过期时间）](#1、数据库的结构（我们只分析键空间和键过期时间）)\n    * [2、键空间的初始化](#2、键空间的初始化)\n    * [3、键空间的操作](#3、键空间的操作)\n  * [四、数据库的过期键操作](#四、数据库的过期键操作)\n    * [1、过期键处理函数](#1、过期键处理函数)\n    * [2、过期键删除策略](#2、过期键删除策略)\n\n\n[toc]\n\n本文转自互联网\n本文将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，Redis基本的使用方法，Redis的基本数据结构，以及一些进阶的使用方法，同时也需要进一步了解Redis的底层数据结构，再接着，还会带来Redis主从复制、集群、分布式锁等方面的相关内容，以及作为缓存的一些使用方法和注意事项，以便让你更完整地了解整个Redis相关的技术体系，形成自己的知识框架。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n## 一. 数据库\nRedis的数据库使用字典作为底层实现，数据库的增、删、查、改都是构建在字典的操作之上的。\nredis服务器将所有数据库都保存在服务器状态结构redisServer(redis.h/redisServer)的db数组（应该是一个链表）里：\n````\n    struct redisServer {\n      //..\n      // 数据库数组，保存着服务器中所有的数据库\n        redisDb *db;\n      //..\n    }\n````\n在初始化服务器时，程序会根据服务器状态的dbnum属性来决定应该创建多少个数据库：\n````\n    struct redisServer {\n        // ..\n        //服务器中数据库的数量\n        int dbnum;\n        //..\n    }\n````\ndbnum属性的值是由服务器配置的database选项决定的，默认值为16；\n\n## 二、切换数据库原理\n每个Redis客户端都有自己的目标数据库，每当客户端执行数据库的读写命令时，目标数据库就会成为这些命令的操作对象。\n````\n    127.0.0.1:6379> set msg 'Hello world'\n    OK\n    127.0.0.1:6379> get msg\n    \"Hello world\"\n    127.0.0.1:6379> select 2\n    OK\n    127.0.0.1:6379[2]> get msg\n    (nil)\n    127.0.0.1:6379[2]>\n````\n在服务器内部，客户端状态redisClient结构(redis.h/redisClient)的db属性记录了客户端当前的目标数据库，这个属性是一个指向redisDb结构(redis.h/redisDb)的指针：\n````\n    typedef struct redisClient {\n        //..\n        // 客户端当前正在使用的数据库\n        redisDb *db;\n        //..\n    } redisClient;\n````\nredisClient.db指针指向redisServer.db数组中的一个元素，而被指向的元素就是当前客户端的目标数据库。\n我们就可以通过修改redisClient指针，让他指向服务器中的不同数据库，从而实现切换数据库的功能–这就是select命令的实现原理。\n实现代码：\n````\n    int selectDb(redisClient *c, int id) {\n        // 确保 id 在正确范围内\n        if (id < 0 || id >= server.dbnum)\n            return REDIS_ERR;\n        // 切换数据库（更新指针）\n        c->db = &server.db[id];\n        return REDIS_OK;\n    }\n````\n## 三、数据库的键空间\n### 1、数据库的结构（我们只分析键空间和键过期时间）\n````\n    typedef struct redisDb {\n        // 数据库键空间，保存着数据库中的所有键值对\n        dict *dict;                 /* The keyspace for this DB */\n        // 键的过期时间，字典的键为键，字典的值为过期事件 UNIX 时间戳\n        dict *expires;              /* Timeout of keys with a timeout set */\n        // 数据库号码\n        int id;                     /* Database ID */\n        // 数据库的键的平均 TTL ，统计信息\n        long long avg_ttl;          /* Average TTL, just for stats */\n        //..\n    } redisDb\n\n````\n上图是一个RedisDb的示例，该数据库存放有五个键值对，分别是sRedis，INums，hBooks，SortNum和sNums，它们各自都有自己的值对象，另外，其中有三个键设置了过期时间，当前数据库是服务器的第0号数据库。现在，我们就从源码角度分析这个数据库结构：\n我们知道，Redis是一个键值对数据库服务器，服务器中的每一个数据库都是一个redis.h/redisDb结构，其中，结构中的dict字典保存了数据库中所有的键值对，我们就将这个字典成为键空间。\nRedis数据库的数据都是以键值对的形式存在，其充分利用了字典高效索引的特点。\na、键空间的键就是数据库中的键，一般都是字符串对象；\nb、键空间的值就是数据库中的值，可以是5种类型对象（字符串、列表、哈希、集合和有序集合）之一。\n数据库的键空间结构分析完了，我们先看看数据库的初始化。\n\n### 2、键空间的初始化\n在redis.c中，我们可以找到键空间的初始化操作：\n````\n    //创建并初始化数据库结构\n     for (j = 0; j < server.dbnum; j++) {\n        // 创建每个数据库的键空间\n        server.db[j].dict = dictCreate(&dbDictType,NULL);\n        // ...\n        // 设定当前数据库的编号\n        server.db[j].id = j;\n    }\n````\n初始化之后就是对键空间的操作了。\n\n### 3、键空间的操作\n我先把一些常见的键空间操作函数列出来：\n````\n    // 从数据库中取出键key的值对象，若不存在就返回NULL\n    robj *lookupKey(redisDb *db, robj *key);\n    \n    /* 先删除过期键，以读操作的方式从数据库中取出指定键对应的值对象\n     * 并根据是否成功找到值，更新服务器的命中或不命中信息,\n     * 如不存在则返回NULL，底层调用lookupKey函数 */\n    robj *lookupKeyRead(redisDb *db, robj *key);\n\n    /* 先删除过期键，以写操作的方式从数据库中取出指定键对应的值对象\n     * 如不存在则返回NULL，底层调用lookupKey函数，\n     * 不会更新服务器的命中或不命中信息\n     */\n    robj *lookupKeyWrite(redisDb *db, robj *key);\n    \n    /* 先删除过期键，以读操作的方式从数据库中取出指定键对应的值对象\n     * 如不存在则返回NULL，底层调用lookupKeyRead函数\n     * 此操作需要向客户端回复\n     */\n    robj *lookupKeyReadOrReply(redisClient *c, robj *key, robj *reply);\n\n    /* 先删除过期键，以写操作的方式从数据库中取出指定键对应的值对象\n     * 如不存在则返回NULL，底层调用lookupKeyWrite函数\n     * 此操作需要向客户端回复\n     */\n    robj *lookupKeyWriteOrReply(redisClient *c, robj *key, robj *reply);\n    \n    /* 添加元素到指定数据库 */\n    void dbAdd(redisDb *db, robj *key, robj *val);\n    /* 重写指定键的值 */\n    void dbOverwrite(redisDb *db, robj *key, robj *val);\n    /* 设定指定键的值 */\n    void setKey(redisDb *db, robj *key, robj *val);\n    /* 判断指定键是否存在 */\n    int dbExists(redisDb *db, robj *key);\n    /* 随机返回数据库中的键 */\n    robj *dbRandomKey(redisDb *db);\n    /* 删除指定键 */\n    int dbDelete(redisDb *db, robj *key);\n    /* 清空所有数据库，返回键值对的个数 */\n    long long emptyDb(void(callback)(void*));\n````\n下面我选取几个比较典型的操作函数分析一下：\n\n查找键值对函数–lookupKey\n````\nrobj *lookupKey(redisDb *db, robj *key) {\n    // 查找键空间\n    dictEntry *de = dictFind(db->dict,key->ptr);\n    // 节点存在\n    if (de) {\n        // 取出该键对应的值\n        robj *val = dictGetVal(de);\n        // 更新时间信息\n        if (server.rdb_child_pid == -1 && server.aof_child_pid == -1)\n            val->lru = LRU_CLOCK();\n        // 返回值\n        return val;\n    } else {\n        // 节点不存在\n        return NULL;\n    }\n}\n````\n添加键值对–dbAdd\n添加键值对使我们经常使用到的函数，底层由dbAdd()函数实现，传入的参数是待添加的数据库，键对象和值对象，源码如下：\n````\nvoid dbAdd(redisDb *db, robj *key, robj *val) {\n    // 复制键名\n    sds copy = sdsdup(key->ptr);\n    // 尝试添加键值对\n    int retval = dictAdd(db->dict, copy, val);\n    // 如果键已经存在，那么停止\n    redisAssertWithInfo(NULL,key,retval == REDIS_OK);\n    // 如果开启了集群模式，那么将键保存到槽里面\n    if (server.cluster_enabled) slotToKeyAdd(key);\n }\n````\n好了，关于键空间操作函数就分析到这，其他函数(在文件db.c中)大家可以自己去分析，有问题的话可以回帖，我们可以一起讨论！\n\n## 四、数据库的过期键操作\n在前面我们说到，redisDb结构中有一个expires指针（概况图可以看上图），该指针指向一个字典结构，字典中保存了所有键的过期时间，该字典称为过期字典。\n过期字典的初始化：\n````\n// 创建并初始化数据库结构\n for (j = 0; j < server.dbnum; j++) {\n        // 创建每个数据库的过期时间字典\n        server.db[j].expires = dictCreate(&keyptrDictType,NULL);\n        // 设定当前数据库的编号\n        server.db[j].id = j;\n        // ..\n    }\n````\na、过期字典的键是一个指针，指向键空间中的某一个键对象（就是某一个数据库键）；\nb、过期字典的值是一个long long类型的整数，这个整数保存了键所指向的数据库键的时间戳–一个毫秒精度的unix时间戳。\n下面我们就来分析过期键的处理函数：\n\n### 1、过期键处理函数\n\n设置键的过期时间–setExpire()\n````\n/*\n * 将键 key 的过期时间设为 when\n */\nvoid setExpire(redisDb *db, robj *key, long long when) {\n    dictEntry *kde, *de;\n    // 从键空间中取出键key\n    kde = dictFind(db->dict,key->ptr);\n    // 如果键空间找不到该键，报错\n    redisAssertWithInfo(NULL,key,kde != NULL);\n    // 向过期字典中添加该键\n    de = dictReplaceRaw(db->expires,dictGetKey(kde));\n    // 设置键的过期时间\n    // 这里是直接使用整数值来保存过期时间，不是用 INT 编码的 String 对象\n    dictSetSignedIntegerVal(de,when);\n}\n````\n获取键的过期时间–getExpire()\n````\nlong long getExpire(redisDb *db, robj *key) {\n    dictEntry *de;\n    // 如果过期键不存在，那么直接返回\n    if (dictSize(db->expires) == 0 ||\n       (de = dictFind(db->expires,key->ptr)) == NULL) return -1;\n    redisAssertWithInfo(NULL,key,dictFind(db->dict,key->ptr) != NULL);\n    // 返回过期时间\n    return dictGetSignedIntegerVal(de);\n}\n````\n删除键的过期时间–removeExpire()\n````\n// 移除键 key 的过期时间\nint removeExpire(redisDb *db, robj *key) {\n    // 确保键带有过期时间\n    redisAssertWithInfo(NULL,key,dictFind(db->dict,key->ptr) != NULL);\n    // 删除过期时间\n    return dictDelete(db->expires,key->ptr) == DICT_OK;\n}\n````\n### 2、过期键删除策略\n通过前面的介绍，大家应该都知道数据库键的过期时间都保存在过期字典里，那假如一个键过期了，那么这个过期键是什么时候被删除的呢？现在来看看redis的过期键的删除策略：\na、定时删除：在设置键的过期时间的同时，创建一个定时器，在定时结束的时候，将该键删除；\nb、惰性删除：放任键过期不管，在访问该键的时候，判断该键的过期时间是否已经到了，如果过期时间已经到了，就执行删除操作；\nc、定期删除：每隔一段时间，对数据库中的键进行一次遍历，删除过期的键。\n其中定时删除可以及时删除数据库中的过期键，并释放过期键所占用的内存，但是它为每一个设置了过期时间的键都开了一个定时器，使的cpu的负载变高，会对服务器的响应时间和吞吐量造成影响。\n惰性删除有效的克服了定时删除对CPU的影响，但是，如果一个过期键很长时间没有被访问到，且若存在大量这种过期键时，势必会占用很大的内存空间，导致内存消耗过大。\n定时删除可以算是上述两种策略的折中。设定一个定时器，每隔一段时间遍历数据库，删除其中的过期键，有效的缓解了定时删除对CPU的占用以及惰性删除对内存的占用。\n在实际应用中，Redis采用了惰性删除和定时删除两种策略来对过期键进行处理，上面提到的lookupKeyWrite等函数中就利用到了惰性删除策略，定时删除策略则是在根据服务器的例行处理程序serverCron来执行删除操作，该程序每100ms调用一次。\n\n惰性删除函数–expireIfNeeded()\n源码如下：\n````\n/* 检查key是否已经过期，如果是的话，将它从数据库中删除 \n * 并将删除命令写入AOF文件以及附属节点(主从复制和AOF持久化相关)\n * 返回0代表该键还没有过期，或者没有设置过期时间\n * 返回1代表该键因为过期而被删除\n */\nint expireIfNeeded(redisDb *db, robj *key) {\n    // 获取该键的过期时间\n    mstime_t when = getExpire(db,key);\n    mstime_t now;\n    // 该键没有设定过期时间\n    if (when < 0) return 0;\n    // 服务器正在加载数据的时候，不要处理\n    if (server.loading) return 0;\n    // lua脚本相关\n    now = server.lua_caller ? server.lua_time_start : mstime();\n    // 主从复制相关，附属节点不主动删除key\n    if (server.masterhost != NULL) return now > when;\n    // 该键还没有过期\n    if (now <= when) return 0;\n    // 删除过期键\n    server.stat_expiredkeys++;\n    // 将删除命令传播到AOF文件和附属节点\n    propagateExpire(db,key);\n    // 发送键空间操作时间通知\n    notifyKeyspaceEvent(NOTIFY_EXPIRED,\n        \"expired\",key,db->id);\n    // 将该键从数据库中删除\n    return dbDelete(db,key);\n}\n````\n定期删除策略\n过期键的定期删除策略由redis.c/activeExpireCycle()函数实现，服务器周期性地操作redis.c/serverCron()（每隔100ms执行一次）时，会调用activeExpireCycle()函数，分多次遍历服务器中的各个数据库，从数据库中的expires字典中随机检查一部分键的过期时间，并删除其中的过期键。\n删除过期键的操作由activeExpireCycleTryExpire函数(activeExpireCycle()调用了该函数)执行，其源码如下：\n\n````\n/* 检查键的过期时间，如过期直接删除*/\nint activeExpireCycleTryExpire(redisDb *db, dictEntry *de, long long now) {\n    // 获取过期时间\n    long long t = dictGetSignedIntegerVal(de);\n    if (now > t) {\n        // 执行到此说明过期\n        // 创建该键的副本\n        sds key = dictGetKey(de);\n        robj *keyobj = createStringObject(key,sdslen(key));\n        // 将删除命令传播到AOF和附属节点\n        propagateExpire(db,keyobj);\n        // 在数据库中删除该键\n        dbDelete(db,keyobj);\n        // 发送事件通知\n        notifyKeyspaceEvent(NOTIFY_EXPIRED,\n            \"expired\",keyobj,db->id);\n        // 临时键对象的引用计数减1\n        decrRefCount(keyobj);\n        // 服务器的过期键计数加1\n        // 该参数影响每次处理的数据库个数\n        server.stat_expiredkeys++;\n        return 1;\n    } else {\n        return 0;\n    }\n}\n````\n删除过期键对AOF、RDB和主从复制都有影响，等到了介绍相关功能时再讨论。\n今天就先到这里~\n"
  },
  {
    "path": "docs/cache/探索Redis设计与实现：浅析Redis主从复制.md",
    "content": "# 目录\n\n* [Redis复制概论](#redis复制概论)\n  * [Redis复制方式](#redis复制方式)\n  * [复制优点](#复制优点)\n    * [1、高可用性](#1、高可用性)\n    * [2、高性能](#2、高性能)\n    * [3、水平扩展性](#3、水平扩展性)\n  * [复制缺点](#复制缺点)\n  * [复制实时性和数据一致性矛盾](#复制实时性和数据一致性矛盾)\n* [Redis复制原理及特性](#redis复制原理及特性)\n  * [slave指向master](#slave指向master)\n  * [复制过程](#复制过程)\n  * [增量复制](#增量复制)\n  * [免持久化复制](#免持久化复制)\n  * [slave只读模式](#slave只读模式)\n  * [半同步复制](#半同步复制)\n* [总结](#总结)\n\n\n[toc]\n\n\n本文转自互联网  \n本文将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，Redis基本的使用方法，Redis的基本数据结构，以及一些进阶的使用方法，同时也需要进一步了解Redis的底层数据结构，再接着，还会带来Redis主从复制、集群、分布式锁等方面的相关内容，以及作为缓存的一些使用方法和注意事项，以便让你更完整地了解整个Redis相关的技术体系，形成自己的知识框架。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->  \n\n转自网络，侵删\n\n早期的RDBMS被设计为运行在单个CPU之上，读写操作都由经单个数据库实例完成，复制技术使得数据库的读写操作可以分散在运行于不同CPU之上的独立服务器上，Redis作为一个开源的、优秀的key-value缓存及持久化存储解决方案，也提供了复制功能，本文主要介绍Redis的复制原理及特性。\n\n\n\n# Redis复制概论\n\n数据库复制指的是发生在不同数据库实例之间，单向的信息传播的行为，通常由被复制方和复制方组成，被复制方和复制方之间建立网络连接，复制方式通常为被复制方主动将数据发送到复制方，复制方接收到数据存储在当前实例，最终目的是为了保证双方的数据一致、同步。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/130324_yGmz_1759553.png)\n\n复制示意图\n\n\n\n## Redis复制方式\n\nRedis的复制方式有两种，一种是主（master）-从（slave）模式，一种是从（slave）-从（slave）模式，因此Redis的复制拓扑图会丰富一些，可以像星型拓扑，也可以像个有向无环：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/131412_Ak0S_1759553.png)\n\nRedis集群复制结构图\n\n通过配置多个Redis实例独立运行、定向复制，形成Redis集群，master负责写、slave负责读。\n\n\n\n## 复制优点\n\n通过配置多个Redis实例，数据备份在不同的实例上，主库专注写请求，从库负责读请求，这样的好处主要体现在下面几个方面：\n\n\n\n### 1、高可用性\n\n在一个Redis集群中，如果master宕机，slave可以介入并取代master的位置，因此对于整个Redis服务来说不至于提供不了服务，这样使得整个Redis服务足够安全。\n\n\n\n### 2、高性能\n\n在一个Redis集群中，master负责写请求，slave负责读请求，这么做一方面通过将读请求分散到其他机器从而大大减少了master服务器的压力，另一方面slave专注于提供读服务从而提高了响应和读取速度。\n\n\n\n### 3、水平扩展性\n\n通过增加slave机器可以横向（水平）扩展Redis服务的整个查询服务的能力。\n\n\n\n## 复制缺点\n\n复制提供了高可用性的解决方案，但同时引入了分布式计算的复杂度问题，认为有两个核心问题：\n\n1.  数据一致性问题，如何保证master服务器写入的数据能够及时同步到slave机器上。\n2.  编程复杂，如何在客户端提供读写分离的实现方案，通过客户端实现将读写请求分别路由到master和slave实例上。\n\n上面两个问题，尤其是第一个问题是Redis服务实现一直在演变，致力于解决的一个问题。\n\n\n\n## 复制实时性和数据一致性矛盾\n\nRedis提供了提高数据一致性的解决方案，本文后面会进行介绍，一致性程度的增加虽然使得我能够更信任数据，但是更好的一致性方案通常伴随着性能的损失，从而减少了吞吐量和服务能力。然而我们希望系统的性能达到最优，则必须要牺牲一致性的程度，因此Redis的复制实时性和数据一致性是存在矛盾的。\n\n\n\n# Redis复制原理及特性\n\n\n\n## slave指向master\n\n举个例子，我们有四台redis实例，M1，R1、R2、R3，其中M1为master，R1、R2、R3分别为三台slave redis实例。在M1启动如下：\n\n```  \n./redis-server ../redis8000.conf --port 8000  \n  \n```  \n\n下面分别为R1、R2、R3的启动命令：\n\n```  \n ./redis-server ../redis8001.conf --port 8001 --slaveof 127.0.0.1 8000 ./redis-server ../redis8002.conf --port 8002 --slaveof 127.0.0.1 8000 ./redis-server ../redis8003.conf --port 8003 --slaveof 127.0.0.1 8000```  \n  \n这样，我们就成功的启动了四台Redis实例，master实例的服务端口为8000，R1、R2、R3的服务端口分别为8001、8002、8003，集群图如下：  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/135124_AfX5_1759553.png)  \n  \nRedis集群复制拓扑  \n  \n上面的命令在slave启动的时候就指定了master机器，我们也可以在slave运行的时候通过slaveof命令来指定master机器。  \n  \n  \n  \n## 复制过程  \n  \nRedis复制主要由SYNC命令实现，复制过程如下图：  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/142921_LCDS_1759553.png)  \n  \nRedis复制过程  \n  \n上图为Redis复制工作过程：  \n  \n1.  slave向master发送sync命令。  \n2.  master开启子进程来讲dataset写入rdb文件，同时将子进程完成之前接收到的写命令缓存起来。  \n3.  子进程写完，父进程得知，开始将RDB文件发送给slave。  \n4.  master发送完RDB文件，将缓存的命令也发给slave。  \n5.  master增量的把写命令发给slave。  \n  \n值得注意的是，当slave跟master的连接断开时，slave可以自动的重新连接master，在redis2.8版本之前，每当slave进程挂掉重新连接master的时候都会开始新的一轮全量复制。如果master同时接收到多个slave的同步请求，则master只需要备份一次RDB文件。  \n  \n  \n  \n## 增量复制  \n  \n上面复制过程介绍的最后提到，slave和master断开了、当slave和master重新连接上之后需要全量复制，这个策略是很不友好的，从Redis2.8开始，Redis提供了增量复制的机制：  \n  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/150147_XvpH_1759553.png)  \n  \n增量复制机制  \n  \nmaster除了备份RDB文件之外还会维护者一个环形队列，以及环形队列的写索引和slave同步的全局offset，环形队列用于存储最新的操作数据，当slave和maste断开重连之后，会把slave维护的offset，也就是上一次同步到哪里的这个值告诉master，同时会告诉master上次和当前slave连接的master的runid，满足下面两个条件，Redis不会全量复制：  \n  \n1.  slave传递的run id和master的run id一致。  \n2.  master在环形队列上可以找到对呀offset的值。  \n  \n满足上面两个条件，Redis就不会全量复制，这样的好处是大大的提高的性能，不做无效的功。  \n  \n增量复制是由psync命令实现的，slave可以通过psync命令来让Redis进行增量复制，当然最终是否能够增量复制取决于环形队列的大小和slave的断线时间长短和重连的这个master是否是之前的master。  \n  \n环形队列大小配置参数：  \n  \n```  \nrepl-backlog-size 1mb\n```  \n  \nRedis同时也提供了当没有slave需要同步的时候，多久可以释放环形队列：  \n  \n```  \nrepl-backlog-ttl 3600\n```  \n  \n  \n  \n## 免持久化复制  \n  \n免持久化机制官方叫做Diskless Replication，前面基于RDB文件写磁盘的方式可以看出，Redis必须要先将RDB文件写入磁盘，才进行网络传输，那么为什么不能直接通过网络把RDB文件传送给slave呢？免持久化复制就是做这个事情的，而且在Redis2.8.18版本开始支持，当然目前还是实验阶段。  \n  \n值得注意的是，一旦基于Diskless Replication的复制传送开始，新的slave请求需要等待这次传输完毕才能够得到服务。  \n  \n是否开启Diskless Replication的开关配置为：  \n  \n```  \nrepo-diskless-sync no\n```  \n  \n为了让后续的slave能够尽量赶上本次复制，Redis提供了一个参数配置指定复制开始的时间延迟：  \n  \n```  \nrepl-diskless-sync-delay 5\n```  \n  \n  \n  \n## slave只读模式  \n  \n自从Redis2.6版本开始，支持对slave的只读模式的配置，默认对slave的配置也是只读。只读模式的slave将会拒绝客户端的写请求，从而避免因为从slave写入而导致的数据不一致问题。  \n  \n  \n  \n## 半同步复制  \n  \n和MySQL复制策略有点类似，Redis复制本身是异步的，但也提供了半同步的复制策略，半同步复制策略在Redis复制中的语义是这样的：  \n  \n```  \n允许用户给出这样的配置：在maste接受写操作的时候，只有当一定时间间隔内，至少有N台slave在线，否则写入无效。\n```  \n  \n上面功能的实现基于Redis下面特性：  \n  \n1.  Redis slaves每秒钟会ping一次master，告诉master当前slave复制到哪里了。  \n2.  Redis master会记住每个slave复制到哪里了。  \n  \n我们可以通过下面配置来指定时间间隔和N这个值：  \n  \n```  \nmin-slaves-to-write <number of slaves>min-slaves-max-lag <number of seconds>\n```  \n  \n当配置了上面两个参数之后，一旦对于一个写操作没有满足上面的两个条件，则master会报错，并且将本次写操作视为无效。这有点像CAP理论中的“C”，即一致性实现，虽然半同步策略不能够完全保证master和slave的数据一致性，但是相对减少了不一致性的窗口期。  \n  \n  \n  \n# 总结  \n  \n本文在理解Redis复制概念和复制的优缺点的基础之上介绍了当前Redis复制工作原理以及主要特性，希望能够帮助大家。\n"
  },
  {
    "path": "docs/cache/探索Redis设计与实现：连接底层与表面的数据结构robj.md",
    "content": "# 目录\n\n  * [robj的数据结构定义](#robj的数据结构定义)\n  * [string robj的编码过程](#string-robj的编码过程)\n  * [string robj的解码过程](#string-robj的解码过程)\n  * [再谈sds与string的关系](#再谈sds与string的关系)\n  * [robj的引用计数操作](#robj的引用计数操作)\n\n\n[toc]\n本文转自互联网\n本文将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，Redis基本的使用方法，Redis的基本数据结构，以及一些进阶的使用方法，同时也需要进一步了解Redis的底层数据结构，再接着，还会带来Redis主从复制、集群、分布式锁等方面的相关内容，以及作为缓存的一些使用方法和注意事项，以便让你更完整地了解整个Redis相关的技术体系，形成自己的知识框架。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n本文是《[Redis内部数据结构详解](http://zhangtielei.com/posts/blog-redis-dict.html)》系列的第三篇，讲述在Redis实现中的一个基础数据结构：robj。\n\n那到底什么是robj呢？它有什么用呢？\n\n从Redis的使用者的角度来看，一个Redis节点包含多个database（非cluster模式下默认是16个，cluster模式下只能是1个），而一个database维护了从key space到object space的映射关系。这个映射关系的key是string类型，而value可以是多种数据类型，比如：string, list, hash等。我们可以看到，key的类型固定是string，而value可能的类型是多个。\n\n而从Redis内部实现的角度来看，在前面第一篇文章中，我们已经提到过，一个database内的这个映射关系是用一个dict来维护的。dict的key固定用一种数据结构来表达就够了，这就是动态字符串sds。而value则比较复杂，为了在同一个dict内能够存储不同类型的value，这就需要一个通用的数据结构，这个通用的数据结构就是robj（全名是redisObject）。举个例子：如果value是一个list，那么它的内部存储结构是一个quicklist（quicklist的具体实现我们放在后面的文章讨论）；如果value是一个string，那么它的内部存储结构一般情况下是一个sds。当然实际情况更复杂一点，比如一个string类型的value，如果它的值是一个数字，那么Redis内部还会把它转成long型来存储，从而减小内存使用。而一个robj既能表示一个sds，也能表示一个quicklist，甚至还能表示一个long型。\n\n## robj的数据结构定义\n\n在server.h中我们找到跟robj定义相关的代码，如下（注意，本系列文章中的代码片段全部来源于Redis源码的3.2分支）：\n````\n    /* Object types */\n    #define OBJ_STRING 0\n    #define OBJ_LIST 1\n    #define OBJ_SET 2\n    #define OBJ_ZSET 3\n    #define OBJ_HASH 4\n    \n    /* Objects encoding. Some kind of objects like Strings and Hashes can be\n     * internally represented in multiple ways. The 'encoding' field of the object\n     * is set to one of this fields for this object. */\n    #define OBJ_ENCODING_RAW 0     /* Raw representation */\n    #define OBJ_ENCODING_INT 1     /* Encoded as integer */\n    #define OBJ_ENCODING_HT 2      /* Encoded as hash table */\n    #define OBJ_ENCODING_ZIPMAP 3  /* Encoded as zipmap */\n    #define OBJ_ENCODING_LINKEDLIST 4 /* Encoded as regular linked list */\n    #define OBJ_ENCODING_ZIPLIST 5 /* Encoded as ziplist */\n    #define OBJ_ENCODING_INTSET 6  /* Encoded as intset */\n    #define OBJ_ENCODING_SKIPLIST 7  /* Encoded as skiplist */\n    #define OBJ_ENCODING_EMBSTR 8  /* Embedded sds string encoding */\n    #define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */\n    \n    #define LRU_BITS 24\n    typedef struct redisObject {\n        unsigned type:4;\n        unsigned encoding:4;\n        unsigned lru:LRU_BITS; /* lru time (relative to server.lruclock) */\n        int refcount;\n        void *ptr;\n    } robj;\n````    \n\n一个robj包含如下5个字段：\n\n*   type: 对象的数据类型。占4个bit。可能的取值有5种：OBJ_STRING, OBJ_LIST, OBJ_SET, OBJ_ZSET, OBJ_HASH，分别对应Redis对外暴露的5种数据结构（即我们在第一篇文章中提到的第一个层面的5种数据结构）。\n*   encoding: 对象的内部表示方式（也可以称为编码）。占4个bit。可能的取值有10种，即前面代码中的10个OBJ_ENCODING_XXX常量。\n*   lru: 做LRU替换算法用，占24个bit。这个不是我们这里讨论的重点，暂时忽略。\n*   refcount: 引用计数。它允许robj对象在某些情况下被共享。\n*   ptr: 数据指针。指向真正的数据。比如，一个代表string的robj，它的ptr可能指向一个sds结构；一个代表list的robj，它的ptr可能指向一个quicklist。\n\n这里特别需要仔细察看的是encoding字段。对于同一个type，还可能对应不同的encoding，这说明同样的一个数据类型，可能存在不同的内部表示方式。而不同的内部表示，在内存占用和查找性能上会有所不同。\n\n比如，当type = OBJ_STRING的时候，表示这个robj存储的是一个string，这时encoding可以是下面3种中的一种：\n\n*   OBJ_ENCODING_RAW: string采用原生的表示方式，即用sds来表示。\n*   OBJ_ENCODING_INT: string采用数字的表示方式，实际上是一个long型。\n*   OBJ_ENCODING_EMBSTR: string采用一种特殊的嵌入式的sds来表示。接下来我们会讨论到这个细节。\n\n再举一个例子：当type = OBJ_HASH的时候，表示这个robj存储的是一个hash，这时encoding可以是下面2种中的一种：\n\n*   OBJ_ENCODING_HT: hash采用一个dict来表示。\n*   OBJ_ENCODING_ZIPLIST: hash采用一个ziplist来表示（ziplist的具体实现我们放在后面的文章讨论）。\n\n本文剩余主要部分将针对表示string的robj对象，围绕它的3种不同的encoding来深入讨论。前面代码段中出现的所有10种encoding，在这里我们先简单解释一下，在这个系列后面的文章中，我们应该还有机会碰到它们。\n\n*   OBJ_ENCODING_RAW: 最原生的表示方式。其实只有string类型才会用这个encoding值（表示成sds）。\n*   OBJ_ENCODING_INT: 表示成数字。实际用long表示。\n*   OBJ_ENCODING_HT: 表示成dict。\n*   OBJ_ENCODING_ZIPMAP: 是个旧的表示方式，已不再用。在小于Redis 2.6的版本中才有。\n*   OBJ_ENCODING_LINKEDLIST: 也是个旧的表示方式，已不再用。\n*   OBJ_ENCODING_ZIPLIST: 表示成ziplist。\n*   OBJ_ENCODING_INTSET: 表示成intset。用于set数据结构。\n*   OBJ_ENCODING_SKIPLIST: 表示成skiplist。用于sorted set数据结构。\n*   OBJ_ENCODING_EMBSTR: 表示成一种特殊的嵌入式的sds。\n*   OBJ_ENCODING_QUICKLIST: 表示成quicklist。用于list数据结构。\n\n我们来总结一下robj的作用：\n\n*   为多种数据类型提供一种统一的表示方式。\n*   允许同一类型的数据采用不同的内部表示，从而在某些情况下尽量节省内存。\n*   支持对象共享和引用计数。当对象被共享的时候，只占用一份内存拷贝，进一步节省内存。\n\n## string robj的编码过程\n\n当我们执行Redis的set命令的时候，Redis首先将接收到的value值（string类型）表示成一个type = OBJ_STRING并且encoding = OBJ_ENCODING_RAW的robj对象，然后在存入内部存储之前先执行一个编码过程，试图将它表示成另一种更节省内存的encoding方式。这一过程的核心代码，是object.c中的tryObjectEncoding函数。\n````\n    robj *tryObjectEncoding(robj *o) {\n        long value;\n        sds s = o->ptr;\n        size_t len;\n    \n        /* Make sure this is a string object, the only type we encode\n         * in this function. Other types use encoded memory efficient\n         * representations but are handled by the commands implementing\n         * the type. */\n        serverAssertWithInfo(NULL,o,o->type == OBJ_STRING);\n    \n        /* We try some specialized encoding only for objects that are\n         * RAW or EMBSTR encoded, in other words objects that are still\n         * in represented by an actually array of chars. */\n        if (!sdsEncodedObject(o)) return o;\n    \n        /* It's not safe to encode shared objects: shared objects can be shared\n         * everywhere in the \"object space\" of Redis and may end in places where\n         * they are not handled. We handle them only as values in the keyspace. */\n         if (o->refcount > 1) return o;\n    \n        /* Check if we can represent this string as a long integer.\n         * Note that we are sure that a string larger than 21 chars is not\n         * representable as a 32 nor 64 bit integer. */\n        len = sdslen(s);\n        if (len <= 21 && string2l(s,len,&value)) {\n            /* This object is encodable as a long. Try to use a shared object.\n             * Note that we avoid using shared integers when maxmemory is used\n             * because every object needs to have a private LRU field for the LRU\n             * algorithm to work well. */\n            if ((server.maxmemory == 0 ||\n                 (server.maxmemory_policy != MAXMEMORY_VOLATILE_LRU &&\n                  server.maxmemory_policy != MAXMEMORY_ALLKEYS_LRU)) &&\n                value >= 0 &&\n                value < OBJ_SHARED_INTEGERS)\n            {\n                decrRefCount(o);\n                incrRefCount(shared.integers[value]);\n                return shared.integers[value];\n            } else {\n                if (o->encoding == OBJ_ENCODING_RAW) sdsfree(o->ptr);\n                o->encoding = OBJ_ENCODING_INT;\n                o->ptr = (void*) value;\n                return o;\n            }\n        }\n    \n        /* If the string is small and is still RAW encoded,\n         * try the EMBSTR encoding which is more efficient.\n         * In this representation the object and the SDS string are allocated\n         * in the same chunk of memory to save space and cache misses. */\n        if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT) {\n            robj *emb;\n    \n            if (o->encoding == OBJ_ENCODING_EMBSTR) return o;\n            emb = createEmbeddedStringObject(s,sdslen(s));\n            decrRefCount(o);\n            return emb;\n        }\n    \n        /* We can't encode the object...\n         *\n         * Do the last try, and at least optimize the SDS string inside\n         * the string object to require little space, in case there\n         * is more than 10% of free space at the end of the SDS string.\n         *\n         * We do that only for relatively large strings as this branch\n         * is only entered if the length of the string is greater than\n         * OBJ_ENCODING_EMBSTR_SIZE_LIMIT. */\n        if (o->encoding == OBJ_ENCODING_RAW &&\n            sdsavail(s) > len/10)\n        {\n            o->ptr = sdsRemoveFreeSpace(o->ptr);\n        }\n    \n        /* Return the original object. */\n        return o;\n    }\n````\n\n这段代码执行的操作比较复杂，我们有必要仔细看一下每一步的操作：\n\n*   第1步检查，检查type。确保只对string类型的对象进行操作。\n*   第2步检查，检查encoding。sdsEncodedObject是定义在server.h中的一个宏，确保只对OBJ_ENCODING_RAW和OBJ_ENCODING_EMBSTR编码的string对象进行操作。这两种编码的string都采用sds来存储，可以尝试进一步编码处理。\n\n````\n    #define sdsEncodedObject(objptr) (objptr->encoding == OBJ_ENCODING_RAW || objptr->encoding == OBJ_ENCODING_EMBSTR)\n````\n*   第3步检查，检查refcount。引用计数大于1的共享对象，在多处被引用。由于编码过程结束后robj的对象指针可能会变化（我们在前一篇介绍sdscatlen函数的时候提到过类似这种接口使用模式），这样对于引用计数大于1的对象，就需要更新所有地方的引用，这不容易做到。因此，对于计数大于1的对象不做编码处理。\n*   试图将字符串转成64位的long。64位的long所能表达的数据范围是-2^63到2^63-1，用十进制表达出来最长是20位数（包括负号）。这里判断小于等于21，似乎是写多了，实际判断小于等于20就够了（如果我算错了请一定告诉我哦）。string2l如果将字符串转成long转成功了，那么会返回1并且将转好的long存到value变量里。\n*   在转成long成功时，又分为两种情况。\n    *   第一种情况：如果Redis的配置不要求运行LRU替换算法，且转成的long型数字的值又比较小（小于OBJ_SHARED_INTEGERS，在目前的实现中这个值是10000），那么会使用共享数字对象来表示。之所以这里的判断跟LRU有关，是因为LRU算法要求每个robj有不同的lru字段值，所以用了LRU就不能共享robj。shared.integers是一个长度为10000的数组，里面预存了10000个小的数字对象。这些小数字对象都是encoding = OBJ_ENCODING_INT的string robj对象。\n    *   第二种情况：如果前一步不能使用共享小对象来表示，那么将原来的robj编码成encoding = OBJ_ENCODING_INT，这时ptr字段直接存成这个long型的值。注意ptr字段本来是一个void *指针（即存储的是内存地址），因此在64位机器上有64位宽度，正好能存储一个64位的long型值。这样，除了robj本身之外，它就不再需要额外的内存空间来存储字符串值。\n*   接下来是对于那些不能转成64位long的字符串进行处理。最后再做两步处理：\n    *   如果字符串长度足够小（小于等于OBJ_ENCODING_EMBSTR_SIZE_LIMIT，定义为44），那么调用createEmbeddedStringObject编码成encoding = OBJ_ENCODING_EMBSTR；\n    *   如果前面所有的编码尝试都没有成功（仍然是OBJ_ENCODING_RAW），且sds里空余字节过多，那么做最后一次努力，调用sds的sdsRemoveFreeSpace接口来释放空余字节。\n\n其中调用的createEmbeddedStringObject，我们有必要看一下它的代码：\n````\n    robj *createEmbeddedStringObject(const char *ptr, size_t len) {\n        robj *o = zmalloc(sizeof(robj)+sizeof(struct sdshdr8)+len+1);\n        struct sdshdr8 *sh = (void*)(o+1);\n    \n        o->type = OBJ_STRING;\n        o->encoding = OBJ_ENCODING_EMBSTR;\n        o->ptr = sh+1;\n        o->refcount = 1;\n        o->lru = LRU_CLOCK();\n    \n        sh->len = len;\n        sh->alloc = len;\n        sh->flags = SDS_TYPE_8;\n        if (ptr) {\n            memcpy(sh->buf,ptr,len);\n            sh->buf[len] = '\\0';\n        } else {\n            memset(sh->buf,0,len+1);\n        }\n        return o;\n    }\n````\ncreateEmbeddedStringObject对sds重新分配内存，将robj和sds放在一个连续的内存块中分配，这样对于短字符串的存储有利于减少内存碎片。这个连续的内存块包含如下几部分：\n\n*   16个字节的robj结构。\n*   3个字节的sdshdr8头。\n*   最多44个字节的sds字符数组。\n*   1个NULL结束符。\n\n加起来一共不超过64字节（16+3+44+1），因此这样的一个短字符串可以完全分配在一个64字节长度的内存块中。\n\n## string robj的解码过程\n\n当我们需要获取字符串的值，比如执行get命令的时候，我们需要执行与前面讲的编码过程相反的操作——解码。\n\n这一解码过程的核心代码，是object.c中的getDecodedObject函数。\n````\n    robj *getDecodedObject(robj *o) {\n        robj *dec;\n    \n        if (sdsEncodedObject(o)) {\n            incrRefCount(o);\n            return o;\n        }\n        if (o->type == OBJ_STRING && o->encoding == OBJ_ENCODING_INT) {\n            char buf[32];\n    \n            ll2string(buf,32,(long)o->ptr);\n            dec = createStringObject(buf,strlen(buf));\n            return dec;\n        } else {\n            serverPanic(\"Unknown encoding type\");\n        }\n    }\n````\n\n这个过程比较简单，需要我们注意的点有：\n\n*   编码为OBJ_ENCODING_RAW和OBJ_ENCODING_EMBSTR的字符串robj对象，不做变化，原封不动返回。站在使用者的角度，这两种编码没有什么区别，内部都是封装的sds。\n*   编码为数字的字符串robj对象，将long重新转为十进制字符串的形式，然后调用createStringObject转为sds的表示。注意：这里由long转成的sds字符串长度肯定不超过20，而根据createStringObject的实现，它们肯定会被编码成OBJ_ENCODING_EMBSTR的对象。createStringObject的代码如下：\n\n    robj *createStringObject(const char *ptr, size_t len) {\n        if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)\n            return createEmbeddedStringObject(ptr,len);\n        else\n            return createRawStringObject(ptr,len);\n    }\n\n## 再谈sds与string的关系\n\n在上一篇文章中，我们简单地提到了sds与string的关系；在本文介绍了robj的概念之后，我们重新总结一下sds与string的关系。\n\n*   确切地说，string在Redis中是用一个robj来表示的。\n*   用来表示string的robj可能编码成3种内部表示：OBJ_ENCODING_RAW, OBJ_ENCODING_EMBSTR, OBJ_ENCODING_INT。其中前两种编码使用的是sds来存储，最后一种OBJ_ENCODING_INT编码直接把string存成了long型。\n*   在对string进行incr, decr等操作的时候，如果它内部是OBJ_ENCODING_INT编码，那么可以直接进行加减操作；如果它内部是OBJ_ENCODING_RAW或OBJ_ENCODING_EMBSTR编码，那么Redis会先试图把sds存储的字符串转成long型，如果能转成功，再进行加减操作。\n*   对一个内部表示成long型的string执行append, setbit, getrange这些命令，针对的仍然是string的值（即十进制表示的字符串），而不是针对内部表示的long型进行操作。比如字符串”32”，如果按照字符数组来解释，它包含两个字符，它们的ASCII码分别是0x33和0x32。当我们执行命令setbit key 7 0的时候，相当于把字符0x33变成了0x32，这样字符串的值就变成了”22”。而如果将字符串”32”按照内部的64位long型来解释，那么它是0x0000000000000020，在这个基础上执行setbit位操作，结果就完全不对了。因此，在这些命令的实现中，会把long型先转成字符串再进行相应的操作。由于篇幅原因，这三个命令的实现代码这里就不详细介绍了，有兴趣的读者可以参考Redis源码：\n    *   t_string.c中的appendCommand函数；\n    *   biops.c中的setbitCommand函数；\n    *   t_string.c中的getrangeCommand函数。\n\n值得一提的是，append和setbit命令的实现中，都会最终调用到db.c中的dbUnshareStringValue函数，将string对象的内部编码转成OBJ_ENCODING_RAW的（只有这种编码的robj对象，其内部的sds 才能在后面自由追加新的内容），并解除可能存在的对象共享状态。这里面调用了前面提到的getDecodedObject。\n````\n    \n    robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o) {\n        serverAssert(o->type == OBJ_STRING);\n        if (o->refcount != 1 || o->encoding != OBJ_ENCODING_RAW) {\n            robj *decoded = getDecodedObject(o);\n            o = createRawStringObject(decoded->ptr, sdslen(decoded->ptr));\n            decrRefCount(decoded);\n            dbOverwrite(db,key,o);\n        }\n        return o;\n    }\n````\n\n## robj的引用计数操作\n\n将robj的引用计数加1和减1的操作，定义在object.c中：\n\n````\n    void incrRefCount(robj *o) {\n        o->refcount++;\n    }\n    \n    void decrRefCount(robj *o) {\n        if (o->refcount <= 0) serverPanic(\"decrRefCount against refcount <= 0\");\n        if (o->refcount == 1) {\n            switch(o->type) {\n            case OBJ_STRING: freeStringObject(o); break;\n            case OBJ_LIST: freeListObject(o); break;\n            case OBJ_SET: freeSetObject(o); break;\n            case OBJ_ZSET: freeZsetObject(o); break;\n            case OBJ_HASH: freeHashObject(o); break;\n            default: serverPanic(\"Unknown object type\"); break;\n            }\n            zfree(o);\n        } else {\n            o->refcount--;\n        }\n    }\n````\n我们特别关注一下将引用计数减1的操作decrRefCount。如果只剩下最后一个引用了（refcount已经是1了），那么在decrRefCount被调用后，整个robj将被释放。\n\n注意：Redis的del命令就依赖decrRefCount操作将value释放掉。\n\n* * *\n\n经过了本文的讨论，我们很容易看出，robj所表示的就是Redis对外暴露的第一层面的数据结构：string, list, hash, set, sorted set，而每一种数据结构的底层实现所对应的是哪个（或哪些）第二层面的数据结构（dict, sds, ziplist, quicklist, skiplist, 等），则通过不同的encoding来区分。可以说，robj是联结两个层面的数据结构的桥梁。\n\n本文详细介绍了OBJ_STRING类型的字符串对象的底层实现，其编码和解码过程在Redis里非常重要，应用广泛，我们在后面的讨论中可能还会遇到。现在有了robj的概念基础，我们下一篇会讨论ziplist，以及它与hash的关系。\n\n* * *\n\n**后记**(追加于2016-07-09): 本文在解析“将string编码成long型”的代码时提到的判断21字节的问题，后来已经提交给@antirez并合并进了unstable分支，详见[commit f648c5a](https://github.com/antirez/redis/commit/f648c5a70c802aeb60ee9773dfdcf7cf08a06357)。\n"
  },
  {
    "path": "docs/cs/algorithms/剑指offer.md",
    "content": "# 目录\n\n* [数论和数字规律](#数论和数字规律)\n  * [从1到n整数中1出现的次数](#从1到n整数中1出现的次数)\n  * [排数组排成最小的数](#排数组排成最小的数)\n  * [丑数](#丑数)\n* [数组和矩阵](#数组和矩阵)\n  * [二维数组的查找](#二维数组的查找)\n  * [顺时针打印矩阵。](#顺时针打印矩阵。)\n  * [调整数组中数字的顺序，使正数在负数的前面](#调整数组中数字的顺序，使正数在负数的前面)\n  * [数组中出现次数超过一半的数字](#数组中出现次数超过一半的数字)\n  * [找出前k小的数](#找出前k小的数)\n  * [连续子数组的最大和](#连续子数组的最大和)\n  * [逆序对](#逆序对)\n  * [数字在排序数组中出现的次数](#数字在排序数组中出现的次数)\n  * [和为s的两个整数，和为s的连续正数序列](#和为s的两个整数，和为s的连续正数序列)\n  * [n个色子的点数](#n个色子的点数)\n  * [扑克牌的顺子](#扑克牌的顺子)\n  * [数组中重复的数字](#数组中重复的数字)\n  * [数组中重复的数字](#数组中重复的数字-1)\n  * [构建乘积数组](#构建乘积数组)\n  * [数据流的中位数](#数据流的中位数)\n  * [滑动窗口中的最大值](#滑动窗口中的最大值)\n* [字符串](#字符串)\n  * [字符串的排列](#字符串的排列)\n  * [替换空格](#替换空格)\n  * [第一次只出现一次的字符](#第一次只出现一次的字符)\n  * [翻转单词顺序和左旋转字符串](#翻转单词顺序和左旋转字符串)\n  * [把字符串转换为整数](#把字符串转换为整数)\n  * [表示数值的字符串](#表示数值的字符串)\n  * [字符流中第一个不重复的字符](#字符流中第一个不重复的字符)\n* [链表](#链表)\n  * [从尾到头打印链表](#从尾到头打印链表)\n  * [链表倒数第k个节点](#链表倒数第k个节点)\n  * [反转链表](#反转链表)\n  * [合并两个排序链表](#合并两个排序链表)\n  * [复杂链表的复制](#复杂链表的复制)\n  * [两个链表的第一个公共节点](#两个链表的第一个公共节点)\n  * [孩子们的游戏(圆圈中最后剩下的数)](#孩子们的游戏圆圈中最后剩下的数)\n  * [链表的环的入口结点](#链表的环的入口结点)\n  * [删除链表中重复的节点](#删除链表中重复的节点)\n      * [](#)\n  * [二叉搜索树转换为双向链表](#二叉搜索树转换为双向链表)\n  * [重建二叉树](#重建二叉树)\n  * [树的子结构](#树的子结构)\n  * [镜像二叉树](#镜像二叉树)\n  * [树的层次遍历](#树的层次遍历)\n  * [二叉树的深度](#二叉树的深度)\n  * [判断是否平衡二叉树](#判断是否平衡二叉树)\n  * [二叉搜索树的后序遍历](#二叉搜索树的后序遍历)\n  * [二叉树中和为某一值的路径](#二叉树中和为某一值的路径)\n  * [二叉树的下一个节点](#二叉树的下一个节点)\n  * [对称的二叉树](#对称的二叉树)\n  * [把二叉树打印成多行](#把二叉树打印成多行)\n  * [按之字形顺序打印二叉树](#按之字形顺序打印二叉树)\n  * [序列化和反序列化二叉树](#序列化和反序列化二叉树)\n  * [二叉搜索树的第k个结点](#二叉搜索树的第k个结点)\n* [栈和队列](#栈和队列)\n  * [用两个队列实现栈，用两个栈实现队列。](#用两个队列实现栈，用两个栈实现队列。)\n  * [包含min函数的栈](#包含min函数的栈)\n  * [栈的压入和弹出序列](#栈的压入和弹出序列)\n* [排序和查找](#排序和查找)\n  * [旋转数组的最小数字](#旋转数组的最小数字)\n* [递归](#递归)\n  * [斐波那契数列](#斐波那契数列)\n  * [青蛙跳台阶](#青蛙跳台阶)\n  * [变态跳台阶](#变态跳台阶)\n  * [矩形覆盖](#矩形覆盖)\n* [位运算](#位运算)\n  * [二进制中1的个数](#二进制中1的个数)\n  * [数组中只出现一次的数字](#数组中只出现一次的数字)\n  * [不用加减乘除做加法](#不用加减乘除做加法)\n* [回溯和DFS](#回溯和dfs)\n  * [矩阵中的路径](#矩阵中的路径)\n  * [机器人的运动范围](#机器人的运动范围)\n\n\n\n点击关注[公众号](#公众号)及时获取笔主最新更新文章，并可免费领取Java工程师必备学习资源。\n\n[TOC]\n\n节选剑指offer比较经典和巧妙的一些题目，以便复习使用。一部分题目给出了完整代码，一部分题目比较简单直接给出思路。但是不保证我说的思路都是正确的，个人对算法也不是特别在行，只不过这本书的算法多看了几遍多做了几遍多了点心得体会。于是想总结一下。如果有错误也希望能指出，谢谢。\n\n具体代码可以参考我的GitHub仓库：\n\nhttps://github.com/h2pl/SwordToOffer\n<!-- more -->\n\n\n# 数论和数字规律\n\n## 从1到n整数中1出现的次数\n\n题目描述\n求出1~13的整数中1出现的次数,并算出100~1300的整数中1出现的次数？为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数。\n\n1暴力办法，把整数转为字符串，依次枚举相加。复杂度是O（N * k）k为数字长度。\n\n2第二种办法看不懂，需要数学推导，太长不看\n\n## 排数组排成最小的数\n\n输入一个正整数数组，把数组里所有数字拼接起来排成一个数，打印能拼接出的所有数字中最小的一个。例如输入数组{3，32，321}，则打印出这三个数字能排成的最小数字为321323。\n\n解析：本题的关键是，两个数如何排成最小的，答案是，如果把数字看成字符串a,b那么如果a+b>b+a，则a应该放在b后面。\n例如 3和32 3 + 32 = 332,32 + 3 = 323,332>323,所以32要放在前面。\n\n根据这个规律，构造一个比较器，使用排序方法即可。\n\n## 丑数\n\n题目描述\n把只包含因子2、3和5的数称作丑数（Ugly Number）。例如6、8都是丑数，但14不是，因为它包含因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。\n\n解析\n\n1 暴力枚举每个丑数，找出第N个即可。\n\n2 这个思路比较巧妙，由于丑数一定是由2,3,5三个因子构成的，所以我们每次构造出一个比前面丑数大但是比后面小的丑数，构造N次即可。\n\n\t\t    public class Solution {\n\t\t        public static int GetUglyNumber_Solution(int index) {\n\t\t                if (index == 0) return 0;\n\t\t                int []res = new int[index];\n\t\t                res[0] = 1;\n\t\t                int i2,i3,i5;\n\t\t                i2 = i3 = i5 = 0;\n\t\t                for (int i = 1;i < index;i ++) {\n\t\t                    res[i] = Math.min(res[i2] * 2, Math.min(res[i3] * 3, res[i5] * 5));\n\t\t                    if (res[i] == res[i2] * 2) i2 ++;\n\t\t                    if (res[i] == res[i3] * 3) i3 ++;\n\t\t                    if (res[i] == res[i5] * 5) i5 ++;\n\t\t                }\n\t\t                return res[index - 1];\n\t\t            }\n\t\t        }\n\t\t    }\n\t\t    i2,i3,i5分别代表目前有几个2,3,5的因子，每次选一个最小的丑数，然后开始找下一个。当然i2，i3，i5也要跟着变。\n# 数组和矩阵\n\n\n## 二维数组的查找\n\n    /**\n     * Created by 周杰伦 on 2018/2/25.\n     * 题目描述\n     在一个二维数组中，每一行都按照从左到右递增的顺序排序，\n     每一列都按照从上到下递增的顺序排序。请完成一个函数，\n     输入这样的一个二维数组和一个整数，判断数组中是否含有该整数。\n     1 2 3\n     2 3 4\n     3 4 5\n     */\n     \n     解析：比较经典的一题，解法也比较巧妙，由于数组从左向右和从上到下的都是递增的，所以找一个数可以先从最右开始找。\n     假设最右值为a，待查数为x，那么如果x < a说明x在a的左边，往左找即可，如果x > a，说明x 在 a的下面一行，到下面一行继续按照该规则查找，就可以遍历所有数。\n     \n    算法的时间复杂度是O(M * N)\n     \n    public class 二维数组中的查找 {\n        public static boolean Find(int target, int[][] array) {\n    \n            if(array[0][0] > target) {\n                return false;\n            }\n    \n            int row = 0;\n            int col = 0;\n            while (row < array.length && col >0) {\n                if (target == array[row][col]) {\n                    return true;\n                }\n                else if (target <array[row][col]) {\n                    col --;\n                }\n                else if (target > array[row][col]) {\n                    col ++;\n                }\n                else row++;\n            }\n            return false;\n        }\n    }\n## 顺时针打印矩阵。\n\n输入一个矩阵，按照从外向里以顺时针的顺序依次打印出每一个数字，例如，如果输入如下矩阵： 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.\n\n这题还是有点麻烦的，因为要顺时针打印，所以实际上是由外向内打印，边界的处理和递归调用需要谨慎。\n\n这题我自己没写出标准答案。参考一个答案吧。关键在于四个循环中的分界点设置。\n\n    //主体循环部分才5行。其实是有规律可循的。将每一层的四个边角搞清楚就可以打印出来了\n            \n        import java.util.ArrayList;\n        public class Solution {\n            public ArrayList<Integer> printMatrix(int [][] array) {\n                ArrayList<Integer> result = new ArrayList<Integer> ();\n                if(array.length==0) return result;\n                int n = array.length,m = array[0].length;\n                if(m==0) return result;\n                int layers = (Math.min(n,m)-1)/2+1;//这个是层数\n                for(int i=0;i<layers;i++){\n                    for(int k = i;k<m-i;k++) result.add(array[i][k]);//左至右\n                    for(int j=i+1;j<n-i;j++) result.add(array[j][m-i-1]);//右上至右下\n                    for(int k=m-i-2;(k>=i)&&(n-i-1!=i);k--) result.add(array[n-i-1][k]);//右至左\n                    for(int j=n-i-2;(j>i)&&(m-i-1!=i);j--) result.add(array[j][i]);//左下至左上\n                }\n                return result;       \n            }\n        }\n\n## 调整数组中数字的顺序，使正数在负数的前面\n\n双指针即可以解决，变式有正负，奇偶等等。\n\n## 数组中出现次数超过一半的数字\n\n本题有很多种解法。\n\n1 最笨的解法，统计每个数的出现次数，O（n2)\n\n2 使用hashmap，空间换时间O（n)\n\n3 由于出现超过一半的数字一定也是中位数，所以可以先排序，再找到第n/2位置上的节点。\n\n4 使用快速排序的复杂度是O（nlogn)，基于快排的特性，每一轮的过程都会把一个数放到最终位置，所以我们可以判断一下这个数的位置是不是n/2，如果是的话，那么就直接返回即可。这样就优化了快排的步骤。\n\n4.5事实上，上述办法的复杂度仍然是O（nlogn）\n\n    快速排序的partition函数将一个数组分为左右两边，并且我们可以知道，如果flag值在k位置左边，那么往左找，如果在k位置右边，那么往左找。\n    \n    这里科普一下经典快排中的一个方法partition，剑指offer书中直接跳过了这部分，让我摸不着头脑。\n    \n    虽然快排用到了经典的分而治之的思想，但是快排实现的前提还是在于 partition 函数。正是有了 partition 的存在，才使得可以将整个大问题进行划分，进而分别进行处理。\n    \n    除了用来进行快速排序，partition 还可以用 O(N) 的平均时间复杂度从无序数组中寻找第K大的值。和快排一样，这里也用到了分而治之的思想。首先用 partition 将数组分为两部分，得到分界点下标 pos，然后分三种情况：\n    \n    pos == k-1，则找到第 K 大的值，arr[pos]；\n    pos > k-1，则第 K 大的值在左边部分的数组。\n    pos < k-1，则第 K 大的值在右边部分的数组。\n    下面给出基于迭代的实现（用来寻找第 K 小的数）：\n\n\n    int find_kth_number(vector<int> &arr, int k){\n        int begin = 0, end = arr.size();\n        assert(k>0 && k<=end);\n        int target_num = 0;\n        while (begin < end){\n            int pos = partition(arr, begin, end);\n            if(pos == k-1){\n                target_num = arr[pos];\n                break;\n            }\n            else if(pos > k-1){\n                end = pos;\n            }\n            else{\n                begin = pos + 1;\n            }\n        }\n        return target_num;\n    }\n\n   \n\n    该算法的时间复杂度是多少呢？考虑最坏情况下，每次 partition 将数组分为长度为 N-1 和 1 的两部分，然后在长的一边继续寻找第 K 大，此时时间复杂度为 O(N^2 )。不过如果在开始之前将数组进行随机打乱，那么可以尽量避免最坏情况的出现。而在最好情况下，每次将数组均分为长度相同的两半，运行时间 T(N) = N + T(N/2)，时间复杂度是 O(N)。\n\n所以也就是说，本题用这个方法解的话，复杂度只需要O（n),因为第一次交换需要N/2,j接下来的交换的次数越来越少，最后加起来就是O（N）了。\n\n5 由于数字出现次数超过长度的一半，也就是平均每两个数字就有一个该数字，但他们不一定连续，所以变量time保存一个数的出现次数，然后变量x代表目前选择的数字，遍历中，如果x与后一位不相等则time--，time=0时x改为后一位，time重新变为1。最终x指向的数字就是出现次数最多的。\n\n举两个例子，比如1,2,3,4,5,6,6,6,6,6,6。明显符合。1,6,2,6,3,6,4,6,5,6,6 遍历到最后得到x=6，以此类推，可以满足要求。\n\n## 找出前k小的数\n * 输入n个整数，找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字，则最小的4个数字是1,2,3,4,。\n */\n\n解析：\n\n1如果允许改变数组，那么则可以继承上一题的思想。，使用快速排序中的partition方法，只需要O（N）的复杂度\n\n2使用堆排序\n\n    解析：用前k个数构造一个大小为k的大顶堆，然后遍历余下数字，如果比堆顶大，则跳过，如果比堆顶小，则替换掉堆顶元素，然后执行一次堆排序（即根节点向下调整)。此时的堆顶元素已被替换，\n    \n    然后遍历完所有元素，堆中的元素就是最小的k个元素了。\n    \n    如果要求最大的k个元素，则构造小顶堆就可以了。\n    \n    构造堆的方法是，数组的第N/2号元素到0号元素依次向下调整，此时数组就构成了堆。\n    \n    实际上我们可以使用现成的集合类，红黑树是一棵搜索树，他是排序的，所以可以得到最大和最小值，那么我们每次和最小值比较，符合条件就进行替换即可。复杂度是O（nlogn)\n\n\n    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {\n    \n            ArrayList<Integer>arrayList=new ArrayList<>();\n            if(input==null || input.length==0 ||k==0 ||k>input.length)return arrayList;\n    \n            TreeSet<Integer> treeSet=new TreeSet<>();\n\n\n​    \n            for(int i=0;i<input.length;i++){\n                if(treeSet.size()<k){\n                    treeSet.add(input[i]);\n                }\n    \n                else {\n    \n                    if(input[i]<treeSet.last()){\n                        treeSet.pollLast();\n                        treeSet.add(input[i]);\n                    }\n                }\n            }\n    \n            for(Integer x:treeSet){\n            arrayList.add(x);\n    \n            }\n            return arrayList;\n    \n        }\n\n## 连续子数组的最大和\n\n**\n * Created by 周杰伦 on 2017/3/23.\n * 题目描述\n HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。\n 今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。\n 但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢？\n 例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。\n 你会不会被他忽悠住？(子向量的长度至少是1)\n\n\n解析：笨办法需要O（n2）的复杂度。\n\n1 但是实际上只需要\n一次遍历即可解决。通过sum保存当前和，然后如果当前和为正，那么继续往后加，如果当前和为负，则直接丢弃，令当前和等于一个新值。并且每一步都要比较当前和与最大值。\n\n     */\n    \n    public class 连续数字序列的最大和 {\n        public int FindGreatestSumOfSubArray(int[] array) {\n           if(array==null || array.length==0)return 0;\n            int sum=0;int max=array[0];\n    \n            for(int i=0;i<array.length;i++){\n               //如果当前和<0，那就不加，直接赋新值\n                if(sum<=0){\n                   sum=array[i];\n               }//如果当前和大于零，则继续加。\n               else {\n                   sum+=array[i];\n               }\n               if(max<sum){\n                max=sum;\n               }\n    \n            }\n            return max;\n        }\n\n2 本题也可以使用DP解法      \n\nDP数组代表以i为结尾元素的连续最大和\n\nDP[i] = arr[i] (DP[i-1] < 0) \n      = DP[i] + arr[i] (DP[i -1] > 0)\n      \n\n## 逆序对\n\n/**\n * Created by 周杰伦 on 2017/3/23.\n * 题目描述\n 在数组中的两个数字，如果前面一个数字大于后面的数字，则这两个数字组成一个逆序对。\n 输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。\n 即输出P%1000000007\n */\n\n    解析：本题采用归并排序的框架，只是在归并的时候做出逆序对查找，具体参见下面代码。\n    核心点是，在归并两个有序数组时，如果a数组的元素a1比b数组的元素b1大时，说明有mid - i + 1个数都比b1大。i为a1元素的位置。\n    这样子我们就可以统计逆序对的个数了。经典巧妙。！\n\n    public class 逆序对 {\n        public double Pairs = 0;\n        public int InversePairs(int [] array) {\n            if (array.length==0 ||array==null)\n                return 0;\n            mergesort(array,0,array.length-1);\n            Pairs = Pairs + 1000000007;\n            return (int) (Pairs % 1000000007);\n        }\n        public void merge(int []array,int left,int mid,int right){\n            //有一点很重要的是，归并分成两部分，其中一段是left到mid，第二段是mid+1到right。\n            //不能从0到mid-1，然后mid到right。因为这样左右不均分，会出错。千万注意。\n            //mid=(left+right)/2\n            if (array.length==0 ||array==null ||left>=right)\n                return ;\n            int p=left,q=mid+1,k=0;\n    \n            int []temp=new int[right-left+1];\n        \n            while (p<=mid && q<=right){\n                if(array[p]>array[q]){\n                    temp[k++]=array[q++];\n                    //当前半数组中有一个数p比后半个数组中的一个数q大时，由于两个数组\n                    //已经分别有序，所以说明p到中间数之间的所有数都比q大。\n                    Pairs+=mid-p+1;\n                }\n                else temp[k++]=array[p++];\n            }\n        \n            while (p<=mid){\n                temp[k++]=array[p++];}\n            while (q<=right){\n                temp[k++]=array[q++];}\n    \n    \n    \n            for (int m = 0; m < temp.length; m++)\n                array[left + m] = temp[m];\n        \n        }\n        \n        public void mergesort(int []arr,int left,int right){\n            if (arr.length==0 ||arr==null)\n                return ;\n            int mid=(right+left)/2;\n            if(left<right) {\n                mergesort(arr, left, mid);\n                mergesort(arr, mid + 1, right);\n                merge(arr, left,mid, right);\n            }\n        }\n## 数字在排序数组中出现的次数\n\n1 顺序扫描\n\n鲁迅说过：看到排序数组要想到二分法！\n\n2 通过二分查找找到数字k第一次出现的位置，即先比较k和中间值，再依次二分，如果中间值等于k并且中间值左边！=k，则是第一个k。\n反之可以找到最后一次出现k的位置。然后相减即可。复杂度是logn\n\n## 和为s的两个整数，和为s的连续正数序列\n\n1 和为s的两个整数，双指针遍历即可\n\n2 和为s的连续正数序列。维护一个范围，start到end表示目前的数字序列。大于S则start++,小于S则start--\n\n## n个色子的点数\n\n求n个色子点数之和等于s的概率\n\n1 递归实现\n\n2 循环实现，所有可能值存成一个数组，大小为6n，然后把每个出现数字次数存入数组，遍历一遍即可得到概率。\n\n## 扑克牌的顺子\n\n题目描述\nLL今天心情特别好,因为他去买了一副扑克牌,发现里面居然有2个大王,2个小王(一副牌原本是54张^_^)...他随机从中抽出了5张牌,想测测自己的手气,看看能不能抽到顺子,如果抽到的话,他决定去买体育彩票,嘿嘿！！“红心A,黑桃3,小王,大王,方片5”,“Oh My God!”不是顺子.....LL不高兴了,他想了想,决定大\\小 王可以看成任何数字,并且A看作1,J为11,Q为12,K为13。上面的5张牌就可以变成“1,2,3,4,5”(大小王分别看作2和4),“So Lucky!”。LL决定去买体育彩票啦。 现在,要求你使用这幅牌模拟上面的过程,然后告诉我们LL的运气如何。为了方便起见,你可以认为大小王是0。\n\n把扑克牌存到数组中，并且A看作1,J为11,Q为12,K为13，然后进行排序，如果有不连续的数字，不存在顺子，如果都连续，则是顺子\n\n     Arrays.sort(arr);\n            for (int i = 1 ;i < arr.length;i ++) {\n                if (arr[i] == arr[i - 1]) {\n                    return false;\n                }\n                if (arr[i] - arr[i - 1] == 1) {\n                    continue;\n                }else if (arr[i] - arr[i - 1]  - 1 <= cnt) {\n                    cnt -= arr[i] - arr[i - 1] - 1;\n                }else {\n                    return false;\n                }\n            }\n            return true;\n        }\n\n## 数组中重复的数字\n\n在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的，但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如，如果输入长度为7的数组{2,3,1,0,2,5,3}，那么对应的输出是第一个重复的数字2。\n\n## 数组中重复的数字\n\n在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的，但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如，如果输入长度为7的数组{2,3,1,0,2,5,3}，那么对应的输出是第一个重复的数字2。\n\n解析：一般使用hashmap即可达到O（n)\n\n但剑指offer的解法可以只用O(1)的空间做到。\n\n实际上当每个数字a和他们所在的位置n相同时，每个数字只出现一次，但如果n + 1的位置上的数也是a，那么a就是第一个重复出现的数字。\n\n根据这个思路。我们在循环中当第i个数a与arr[a]相等时，不变，如果不相等，则两者互换，然后开始下一轮遍历。接下来继续交换，如果出现相等的情况，则就是第一个重复出现的数。\n\n举例 2 3 1 0 2 5 3\n1 arr[0] = 2 != 0,所以arr[0]与arr[2]做交换，得1 3 2 0 2 5 3\n2 arr[0] = 1 != 0，所以arr[0]和arr[1]交换，的3 1 2 0 2 5 3\n3 arr[0] = 3 != 0,所以arr[0]和arr[3]交换，得0 1 2 3 2 5 3\n4 arr[0]到arr[3]都符合要求,arr[4] = 2 != 4，所以arr[4]和arr[2]交换，发现两者相等，所以他就是第一个重复的数。\n\n\n    public boolean duplicate(int array[],int length,int [] duplication) {\n        if ( array==null ) return false;\n    \n        // key step\n        for( int i=0; i<length; i++ ){\n            while( i!=array[i] ){\n                if ( array[i] == array[array[i]] ) {\n                    duplication[0] = array[i];\n                    return true;\n                }\n    \n                int temp = array[i];\n                array[i] = array[array[i]];\n                array[array[i]] = temp;\n            }\n        }\n    \n        return false;\n    }\n\n从上述例子可以看到，一个数最多被交换两次。所以复杂度为O（N)\n\n## 构建乘积数组\n\n题目描述\n给定一个数组A[0,1,...,n-1],请构建一个数组B[0,1,...,n-1],其中B中的元素B[i]=A[0]*A[1]*...*A[i-1]*A[i+1]*...*A[n-1]。不能使用除法。\n\n不能用除法，那么就两个循环，一个从0乘到i - 1，一个从i + 1乘到n-1\n\n## 数据流的中位数\n\n题目描述\n如何得到一个数据流中的中位数？如果从数据流中读出奇数个数值，那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值，那么中位数就是所有数值排序之后中间两个数的平均值。\n\n解析，与字符流第一个不重复的字符类似，每次添加数字都要输出一次结果。\n\n    public class Solution {\n    \n        static ArrayList<Integer> list = new ArrayList<>();\n        public static void Insert(Integer num) {\n            list.add(num);\n            Collections.sort(list);\n        }\n    \n        public static Double GetMedian() {\n            if (list.size() % 2 == 0) {\n                int l = list.get(list.size()/2);\n                int r = list.get(list.size()/2 - 1);\n                return (l + r)/2.0;\n            }\n            else {\n                return list.get(list.size()/2)/1.0;\n            }\n        }\n\n\n​    \n    }\n\n## 滑动窗口中的最大值\n\n给定一个数组和滑动窗口的大小，找出所有滑动窗口里数值的最大值。例如，如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3，那么一共存在6个滑动窗口，他们的最大值分别为{4,4,6,6,6,5}； 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个： {[2,3,4],2,6,2,5,1}， {2,[3,4,2],6,2,5,1}， {2,3,[4,2,6],2,5,1}， {2,3,4,[2,6,2],5,1}， {2,3,4,2,[6,2,5],1}， {2,3,4,2,6,[2,5,1]}。\n\n解析：\n1 保持窗口为3进行右移，每次计算出一个最大值即可。\n\n2 使用两个栈实现一个队列，复杂度O（N）,使用两个栈实现最大值栈，复杂度O（1）。两者结合可以完成本题。但是太麻烦了。\n\n3 使用双端队列解决该问题。\n\n    import java.util.*;\n    /**\n    用一个双端队列，队列第一个位置（队头）保存当前窗口的最大值，当窗口滑动一次\n    1.判断当前最大值是否过期（如果最大值所在的下标已经不在窗口范围内，则过期）\n    2.对于一个新加入的值，首先一定要先放入队列，即使他比队头元素小，因为队头元素可能过期。\n    3.新增加的值从队尾开始比较，把所有比他小的值丢掉(因为队列只存最大值，所以之前比他小的可以丢掉)\n    \n    */\n    public class Solution {\n       public ArrayList<Integer> maxInWindows(int [] num, int size)\n        {\n            ArrayList<Integer> res = new ArrayList<>();\n            if(size == 0) return res;\n            int begin; \n            ArrayDeque<Integer> q = new ArrayDeque<>();\n            for(int i = 0; i < num.length; i++){\n                begin = i - size + 1;\n                if(q.isEmpty())\n                    q.add(i);\n                else if(begin > q.peekFirst())\n                    q.pollFirst();\n             \n                while((!q.isEmpty()) && num[q.peekLast()] <= num[i])\n                    q.pollLast();\n                q.add(i);  \n                if(begin >= 0)\n                    res.add(num[q.peekFirst()]);\n            }\n            return res;\n        }\n    }\n\n# 字符串\n\n## 字符串的排列\n\n输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。\n\n    解析：这是一个全排列问题，也就是N个不同的数排成所有不同的序列，只不过把数换成了字符串。\n    全排列的过程就是，第一个元素与后续的某个元素交换，然后第二个元素也这么做，直到最后一个元素为之，过程是一个递归的过程，也是一个dfs的过程。\n    \n    注意元素也要和自己做一次交换，要不然会漏掉自己作为头部的情况。\n    \n    然后再进行一次字典序的排序即可。\n    \n    public static ArrayList<String> Permutation(String str) {\n            char []arr = str.toCharArray();\n            List<char []> list = new ArrayList<>();\n            all(arr, 0, arr.length - 1, list);\n            Collections.sort(list, (o1, o2) -> String.valueOf(o1).compareTo(String.valueOf(o2)));\n            ArrayList<String> res = new ArrayList<>();\n            for (char[] c : list) {\n                if (!res.contains(String.valueOf(c)))\n                res.add(String.valueOf(c));\n            }\n            return res;\n        }\n    \n        //注意要换完为之，因为每换一次可以去掉头部一个数字，这样可以避免重复\n        public static void all(char []arr, int cur, int end, List<char[]> list) {\n            if (cur == end) {\n    //            System.out.println(Arrays.toString(arr));\n                list.add(Arrays.copyOf(arr, arr.length));\n            }\n            for (int i = cur;i <= end;i ++) {\n                //这里的交换包括跟自己换，所以只有一轮换完才能确定一个结果\n                swap(arr, cur, i);\n                all(arr, cur + 1, end, list);\n                swap(arr, cur, i);\n            }\n        }\n        public static void swap(char []arr, int i, int j) {\n            if (i > arr.length || j > arr.length || i >= j)return;\n            char temp = arr[i];\n            arr[i] = arr[j];\n            arr[j] = temp;\n        }\n\n## 替换空格\n\n    /**\n     * Created by 周杰伦 on 2018/2/25.\n     * 请实现一个函数，将一个字符串中的空格替换成“%20”。例如，当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。\n     */\n     \n     解析：如果单纯地按顺序替换空格，每次替换完还要将数组扩容，再右移，这部操作的时间复杂度就是O（2*N）=O（N)，所以总的复杂度是O（n^2)，所以应该采取先扩容的办法，统计出空格数，然后扩容，接下来按顺序添加字符，遇到空格直接改成添加%20即可，这样避免了右移操作和多次扩容，复杂度是O（N)\n\n\n​     \n    public class 替换空格 {\n        public static String replaceSpace(StringBuffer str) {\n            int newlen = 0;\n            for(int i = 0; i < str.length(); i++) {\n                if(str.charAt(i) == ' ') {\n                    newlen = newlen + 3;\n                }\n                else {\n                    newlen ++;\n                }\n            }\n            char []newstr = new char[newlen];\n            int j = 0;\n            for(int i = 0 ; i < str.length(); i++) {\n                if (str.charAt(i) == ' ') {\n                    newstr[j++] = '%';\n                    newstr[j++] = '2';\n                    newstr[j++] = '0';\n                }else {\n                    newstr[j++] = str.charAt(i);\n                }\n            }\n            return String.valueOf(newstr);\n        }\n\n\n## 第一次只出现一次的字符\n\n哈希表可解\n\n## 翻转单词顺序和左旋转字符串\n\n1\n题目描述\n牛客最近来了一个新员工Fish，每天早晨总是会拿着一本英文杂志，写些句子在本子上。同事Cat对Fish写的内容颇感兴趣，有一天他向Fish借来翻看，但却读不懂它的意思。例如，“student. a am I”。后来才意识到，这家伙原来把句子单词的顺序翻转了，正确的句子应该是“I am a student.”。Cat对一一的翻转这些单词顺序可不在行，你能帮助他么？\n\n    这个解法很经典，先把每个单词逆序，再把整个字符串逆序，结果就是把每个单词都进行了翻转。\n\n2\n汇编语言中有一种移位指令叫做循环左移（ROL），现在有个简单的任务，就是用字符串模拟这个指令的运算结果。对于一个给定的字符序列S，请你把其循环左移K位后的序列输出。例如，字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果，即“XYZdefabc”。是不是很简单？OK，搞定它！\n\n    字符串循环左移N位的处理方法也很经典，先把前N位逆序，再把剩余字符串逆序，最后整体逆序。\n    \n    abcXYZdef -> cbafedZYX -> XYZdefabc\n\n## 把字符串转换为整数\n\n题目描述\n将一个字符串转换成一个整数，要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0\n\n解析：首先需要判断正负号，然后判断每一位是否是数字，然后判断是否溢出，判断溢出可以通过加完第n位的和与未加第n位的和进行比较。最后可以得出结果。所以需要3-4步判断。\n\n\n## 表示数值的字符串\n\n请实现一个函数用来判断字符串是否表示数值（包括整数和小数）。例如，字符串\"+100\",\"5e2\",\"-123\",\"3.1416\"和\"-1E-16\"都表示数值。 但是\"12e\",\"1a3.14\",\"1.2.3\",\"+-5\"和\"12e+4.3\"都不是。\n\n    不得不说这种题型太恶心了，就是需要一直判断边界条件\n    \n    参考一个答案。比较完整\n    \n        bool isNumeric(char* str) {\n            // 标记符号、小数点、e是否出现过\n            bool sign = false, decimal = false, hasE = false;\n            for (int i = 0; i < strlen(str); i++) {\n                if (str[i] == 'e' || str[i] == 'E') {\n                    if (i == strlen(str)-1) return false; // e后面一定要接数字\n                    if (hasE) return false;  // 不能同时存在两个e\n                    hasE = true;\n                } else if (str[i] == '+' || str[i] == '-') {\n                    // 第二次出现+-符号，则必须紧接在e之后\n                    if (sign && str[i-1] != 'e' && str[i-1] != 'E') return false;\n                    // 第一次出现+-符号，且不是在字符串开头，则也必须紧接在e之后\n                    if (!sign && i > 0 && str[i-1] != 'e' && str[i-1] != 'E') return false;\n                    sign = true;\n                } else if (str[i] == '.') {\n                  // e后面不能接小数点，小数点不能出现两次\n                    if (hasE || decimal) return false;\n                    decimal = true;\n                } else if (str[i] < '0' || str[i] > '9') // 不合法字符\n                    return false;\n            }\n            return true;\n        }\n\n## 字符流中第一个不重复的字符\n\n题目描述\n请实现一个函数用来找出字符流中第一个只出现一次的字符。例如，当从字符流中只读出前两个字符\"go\"时，第一个只出现一次的字符是\"g\"。当从该字符流中读出前六个字符“google\"时，第一个只出现一次的字符是\"l\"。\n\n本题主要要注意的是流。也就是说每次输入一个字符就要做一次判断。比如输入aaaabbbcd，输出就是a###b##cd\n\n    StringBuilder sb = new StringBuilder();\n        int []map = new int[256];\n        public  void Insert(char ch)\n        {\n            sb.append(ch);\n            if (map[ch] == 0) {\n                map[ch] = 1;\n            }else {\n                map[ch] ++;\n            }\n            System.out.println(FirstAppearingOnce());\n            \n        }\n        //return the first appearence once char in current stringstream\n        public char FirstAppearingOnce()\n        {\n            for (int i = 0;i < sb.length();i ++) {\n                if (map[sb.charAt(i)] == 1) {\n                    return sb.charAt(i);\n                }\n            }\n            return '#';\n        }\n\n# 链表\n\n## 从尾到头打印链表\n\n考查递归，递归可以使输出的顺序倒置\n\n    public static void printReverse(Node node) {\n            if (node.next != null) {\n                printReverse(node.next);\n            }\n            System.out.print(node.val + \" \");\n    }\n\n\n## 链表倒数第k个节点\n\n使用两个指针，一个先走k步。然后一起走即可。\n\n## 反转链表\n\n老生常谈，但是容易写错。\n\n    public ListNode ReverseList(ListNode head) {\n            if(head==null || head.next==null)return head;\n            ListNode pre,next;\n            pre=null;\n            next=null;\n            while(head!=null){\n                //保存下一个结点\n                next=head.next;\n                //连接下一个结点\n                head.next=pre;\n                pre=head;\n                head=next;\n            }\n            return pre;\n        }\n    }\n\n## 合并两个排序链表\n\n与归并排序的合并类似\n\n## 复杂链表的复制\n\n题目描述\n输入一个复杂链表（每个节点中有节点值，以及两个指针，一个指向下一个节点，另一个特殊指针指向任意一个节点），返回结果为复制后复杂链表的head。（注意，输出结果中请不要返回参数中的节点引用，否则判题程序会直接返回空）\n\n这题比较恶心。\n\n解析：\n\n1 直接复制链表，然后再去复制特殊指针，复杂度是O(n2)\n\n2 使用hash表保存特殊指针的映射关系，第二步简化操作，复杂度是O（n）\n\n3 复制每个节点并且连成一个大链表A-A'-B-B'，然后从头到尾判断特殊指针，如果有特殊指针，则让后续节点的特殊指针指向原节点特殊指针指向的节点的后置节点，晕了吧，其实就是原来是A指向B，现在是A’指向B‘。\n\n最后我们根据奇偶序号把链表拆开，复杂度是O（N)且不用额外空间。\n\n## 两个链表的第一个公共节点\n\n1 逆置链表，反向找第一个不同节点，前一个就是公共节点\n\n2 求长度并相减得n，短的链表先走n步，然后一起走即可。\n\n## 孩子们的游戏(圆圈中最后剩下的数)\n\n这是一个约瑟夫环问题。\n\n1 使用循环链表求解，每次走n步摘取一个节点，然后继续，直到最后一个节点就是剩下的数，空间复杂度为O（n)\n\n2 使用数组来做\npublic static int LastRemaining_Solution(int n, int m) {\n        int []arr = new int[n];\n        for (int i = 0;i < n;i ++) {\n            arr[i] = i;\n        }\n\n        int cnt = 0;\n        int sum = 0;\n        for (int i = 0;i < n;i = (i + 1) % n) {\n            if (arr[i] == -1) {\n                continue;\n            }\n            cnt ++;\n            if (cnt == m) {\n                arr[i] = -1;\n                cnt = 0;\n                sum ++;\n            }\n            if (sum == n) {\n                return i;\n            }\n        }\n        return n - 1;\n    }\n\n3 使用余数法求解\n\n\n    int LastRemaining_Solution(int n, int m) {\n            if (m == 0 || n == 0) {\n                return -1;\n            }\n            ArrayList<Integer> data = new ArrayList<Integer>();\n            for (int i = 0; i < n; i++) {\n                data.add(i);\n            }\n            int index = -1;\n            while (data.size() > 1) {\n    //          System.out.println(data);\n                index = (index + m) % data.size();\n    //          System.out.println(data.get(index));\n                data.remove(index);\n                index--;\n            }\n            return data.get(0);\n        }\n\n## 链表的环的入口结点\n\n一个链表中包含环，请找出该链表的环的入口结点。\n\n解析：\n\n1 指定两个指针，一个一次走两步，一个一次走一步，然后当两个节点相遇时，这个节点必定在环中。既然这个节点在环中，那么让这个节点走一圈直到与自己相等为之，可以得到环的长度n。\n\n2 得到了环的长度以后，根据数学推导的结果，我们可以指定两个指针，一个先走n步，然后两者同时走，这样的话，当慢节点到达入口节点时，快节点也转了一圈刚好又到达入口节点，所以也就是他们相等的时候就是入口节点了。\n\n## 删除链表中重复的节点\n\n题目描述\n在一个排序的链表中，存在重复的结点，请删除该链表中重复的结点，重复的结点不保留，返回链表头指针。 例如，链表1->2->3->3->4->4->5 处理后为 1->2->5\n\n保留头结点，然后找到下一个不重复的节点，与他相连，重复的节点直接跳过即可。\n\n#二叉树\n\n## 二叉搜索树转换为双向链表\n\n输入一棵二叉搜索树，将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点，只能调整树中结点指针的指向。\n\n二叉搜索树要转换成有序的双向链表，实际上就是使用中序遍历把节点连入链表中，并且题目要求在原来节点上进行操作，也就是使用左指针和右指针表示链表的前置节点和后置节点。\n\n使用栈实现中序遍历的非递归算法，便可以找出节点的先后关系，依次连接即可。\n\n    public TreeNode Convert(TreeNode root) {\n            if(root==null)\n                return null;\n            Stack<TreeNode> stack = new Stack<TreeNode>();\n            TreeNode p = root;\n            TreeNode pre = null;// 用于保存中序遍历序列的上一节点\n            boolean isFirst = true;\n            while(p!=null||!stack.isEmpty()){\n                while(p!=null){\n                    stack.push(p);\n                    p = p.left;\n                }\n                p = stack.pop();\n                if(isFirst){\n                    root = p;// 将中序遍历序列中的第一个节点记为root\n                    pre = root;\n                    isFirst = false;\n                }else{\n                    pre.right = p;\n                    p.left = pre;\n                    pre = p;\n                }      \n                p = p.right;\n            }\n            return root;\n        }\n    }\n\n## 重建二叉树\n\n         * 题目描述\n     输入某二叉树的前序遍历和中序遍历的结果，请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6}，则重建二叉树并返回。\n     */\n     \n     解析：首先，头结点一定是先序遍历的首位，并且该节点把中序分为左右子树，根据这个规则，左子树由左边数组来完成，右子树由右边数组来完成，根节点由中间节点来构建，于是便有了如下的递归代码。该题的难点就在于边界的判断。\n     \n    public TreeNode reConstructBinaryTree(int [] pre, int [] in) {\n        if(pre.length == 0||in.length == 0){\n            return null;\n        }\n        TreeNode node = new TreeNode(pre[0]);\n        for(int i = 0; i < in.length; i++){\n            if(pre[0] == in[i]){\n                node.left = reConstructBinaryTree(Arrays.copyOfRange(pre, 1, i+1), Arrays.copyOfRange(in, 0, i));//为什么不是i和i-1呢，因为要避免出错，中序找的元素需要再用一次。\n                node.right = reConstructBinaryTree(Arrays.copyOfRange(pre, i+1, pre.length), Arrays.copyOfRange(in, i+1,in.length));\n            }\n        }\n        return node;\n    }\n\n## 树的子结构\n\n    /**\n     * Created by 周杰伦 on 2018/3/27.\n     * 输入两棵二叉树A，B，判断B是不是A的子结构。（ps：我们约定空树不是任意一个树的子结构）\n     */\n    \n        解析：本题还是有点难度的，子结构要求节点完全相同，所以先判断节点是否相同，然后使用先序遍历进行递判断，判断的依据是如果子树为空，则说明节点都找到了，如果原树节点为空，说明找不到对应节点,接着递归地判断该节点的左右子树是否符合要求.\n    \n        public class 树的子结构 {\n    \n            public boolean HasSubtree(TreeNode root1, TreeNode root2) {\n                boolean res = false;\n                if (root1 != null && root2 != null) {\n                    if (root1.val == root2.val) {\n                        res = aHasb(root1, root2);\n                    }\n                    if (res == false) {\n                        res = HasSubtree(root1.left,root2);\n                    }\n                    if (res == false) {\n                        res = HasSubtree(root1.right,root2);\n                    }\n                    return res;\n                }\n                else return false;\n            }\n            public boolean aHasb(TreeNode t1, TreeNode t2){\n                if (t2 == null) return true;\n                if (t1 == null) return false;\n                if (t1.val != t2.val) return false;\n        \n                return aHasb(t1.left,t2.left) && aHasb(t1.right,t2.right);\n            }\n        }\n\n\n​    \n## 镜像二叉树\n\n    /**\n     * Created by 周杰伦 on 2017/3/19.操作给定的二叉树，将其变换为源二叉树的镜像。\n     输入描述:\n     二叉树的镜像定义：源二叉树\n      8\n     /  \\\n     6   10\n     / \\  / \\\n     5  7 9 11\n     镜像二叉树\n       8\n     /  \\\n     10   6\n     / \\  / \\\n     11 9 7  5\n     */\n\n\n    解析：其实镜像二叉树就是交换所有节点的左右子树，所以使用遍历并且进行交换即可。\n    \n    /**\n     public class TreeNode {\n     int val = 0;\n     TreeNode left = null;\n     TreeNode right = null;\n    \n     public TreeNode(int val) {\n     this.val = val;\n    \n     }\n    \n     }\n     */\n    public class 镜像二叉树 {\n        public void Mirror(TreeNode root) {\n            if(root == null)return;\n            if(root.left!=null || root.right!=null)\n            {\n                TreeNode temp=root.left;\n                root.left=root.right;\n                root.right=temp;\n            }\n            Mirror(root.left);\n            Mirror(root.right);\n    \n        }\n\n\n​    \n## 树的层次遍历\n\n也就是从上到下打印节点，使用队列即可完成。\n\n## 二叉树的深度\n\n经典遍历。\n\n## 判断是否平衡二叉树\n\n判断左右子树的高度差是否 <= 1即可。\n\n## 二叉搜索树的后序遍历\n\n题目描述\n输入一个整数数组，判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。\n\n解析：这题其实也非常巧妙。二叉搜索树的特点就是他的左子树都比根节点小，右子树都比跟节点大。而后序遍历的根节点在最后，所以后续遍历的第1到N-1个节点应该是左右子树的节点（不一定左右子树都存在）。\n\n后续遍历的序列是先左子树，再右子树，最后根节点，那么就要求，左半部分比根节点小，右半部分比根节点大，当然，左右部分不一定都存在。\n\n所以，找出根节点后，首先找出左半部分，要求小于根节点，然后找出右半部分，要求大于根节点，如果符合，则递归地判断左右子树到的根节点（本步骤已经将左右部分划分，割据中间节点进行递归），如果不符合，直接返回false。\n\n同理也可以判断前序遍历和中序遍历。\n\n    public class 二叉搜索树的后序遍历序列 {\n        public static void main(String[] args) {\n            int []a = {7,4,6,5};\n            System.out.println(VerifySquenceOfBST(a));\n        }\n        public static boolean VerifySquenceOfBST(int [] sequence) {\n            if (sequence == null || sequence.length == 0) {\n                return false;\n            }\n            return isBST(sequence, 0, sequence.length - 1);\n        }\n        public static boolean isBST(int []arr, int start, int end) {\n            if (start >= end) return true;\n            int root = arr[end];\n            int mid = start;\n            for (mid  = start;mid < end && arr[mid] < root;mid ++) {\n    \n            }\n            for (int i = mid;i < end; i ++) {\n                if (arr[i] < root)return false;\n            }\n            return isBST(arr, start, mid - 1) && isBST(arr, mid, end - 1);\n        }\n    }\n\n## 二叉树中和为某一值的路径\n\n/**\n * Created by 周杰伦 on 2018/3/29.\n * 题目描述\n 输入一颗二叉树和一个整数，打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。\n */\n\n    解析：由于要求从根节点到达叶子节点，并且要打印出所有路径，所以实际上用到了回溯的思想。\n    \n    通过target跟踪当前和，进行先序遍历，当和满足要求时，加入集合，由于有多种结果，所以需要回溯，将访问过的节点弹出访问序列，才能继续访问下一个节点。\n    \n    终止条件是和满足要求，并且节点是叶节点，或者已经访问到空节点也会返回。\n    \n\n    public class 二叉树中和为某一值的路径 {\n        private ArrayList<ArrayList<Integer>> listAll = new ArrayList<ArrayList<Integer>>();\n        private ArrayList<Integer> list = new ArrayList<Integer>();\n        public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {\n            if(root == null) return listAll;\n            list.add(root.val);\n            target -= root.val;\n            if(target == 0 && root.left == null && root.right == null)\n                listAll.add(new ArrayList<Integer>(list));\n            FindPath(root.left, target);\n            FindPath(root.right, target);\n            list.remove(list.size()-1);\n            return listAll;\n        }\n    \n        static int count = 0;\n        static Stack<Integer> path = new Stack<>();\n        static Stack<Integer> stack = new Stack<>();\n        static ArrayList<ArrayList<Integer>> lists = new ArrayList<>();\n    }\n\n## 二叉树的下一个节点\n\n给定一个二叉树和其中的一个结点，请找出中序遍历顺序的下一个结点并且返回。注意，树中的结点不仅包含左右子结点，同时包含指向父结点的指针。\n\n    解析：给出一个比较好懂的解法，中序遍历的结果存在集合中，找到根节点，进行中序遍历，然后找到该节点，下一个节点就是集合后一位\n    \n    public TreeLinkNode GetNext(TreeLinkNode TreeLinkNode)\n        {\n            return findNextNode(TreeLinkNode);\n        }\n        public TreeLinkNode findNextNode(TreeLinkNode anynode) {\n            if (anynode == null) return null;\n            TreeLinkNode p = anynode;\n            while (p.next != null) {\n                p = p.next;\n            }\n            ArrayList<TreeLinkNode> list = inOrderSeq(p);\n            for (int i = 0;i < list.size();i ++) {\n                if (list.get(i) == anynode) {\n                    if (i + 1 < list.size()) {\n                        return list.get(i + 1);\n                    }\n                    else return null;\n                }\n            }\n            return null;\n    \n        }\n        static ArrayList<TreeLinkNode> list = new ArrayList<>();\n        public static ArrayList<TreeLinkNode> inOrderSeq(TreeLinkNode TreeLinkNode) {\n            if (TreeLinkNode == null) return null;\n            inOrderSeq(TreeLinkNode.left);\n            list.add(TreeLinkNode);\n            inOrderSeq(TreeLinkNode.right);\n            return list;\n        }\n\n## 对称的二叉树\n\n请实现一个函数，用来判断一颗二叉树是不是对称的。注意，如果一个二叉树同此二叉树的镜像是同样的，定义其为对称的。\n\n解析，之前有一题是二叉树的镜像，递归交换左右子树即可求出镜像，然后递归比较两个树的每一个节点，则可以判断是否对称。\n\n    boolean isSymmetrical(TreeNode pRoot)\n        {\n            TreeNode temp = copyTree(pRoot);\n            Mirror(pRoot);\n            return isSameTree(temp, pRoot);\n        }\n\n\n​    \n        void Mirror(TreeNode root) {\n            if(root == null)return;\n            Mirror(root.left);\n            Mirror(root.right);\n            if(root.left!=null || root.right!=null)\n            {\n                TreeNode temp=root.left;\n                root.left=root.right;\n                root.right=temp;\n            }\n\n\n​    \n        }\n        boolean isSameTree(TreeNode t1,TreeNode t2){\n            if(t1==null && t2==null)return true;\n            else if(t1!=null && t2!=null && t1.val==t2.val) {\n                boolean left = isSameTree(t1.left, t2.left);\n                boolean right = isSameTree(t1.right, t2.right);\n                return left && right;\n            }\n            else return false;\n        }\n    \n        TreeNode copyTree (TreeNode root) {\n            if (root == null) return null;\n            TreeNode t = new TreeNode(root.val);\n            t.left = copyTree(root.left);\n            t.right = copyTree(root.right);\n            return t;\n        }\n## 把二叉树打印成多行\n\n题目描述\n从上到下按层打印二叉树，同一层结点从左至右输出。每一层输出一行。\n\n解析：1 首先要知道到本题的基础思想，层次遍历。\n\n2 然后是进阶的思想，按行打印二叉树并输出行号，方法是，一个节点last指向当前行的最后一个节点，一个节点nlast指向下一行最后一个节点。使用t表示现在遍历的节点，当t = last时，表示本行结束。此时last = nlast，开始下一行遍历。\n\n同时，当t的左右子树不为空时，令nlast = t的左子树和右子树。每当last 赋值为nlast时，行号加一即可。\n\n## 按之字形顺序打印二叉树\n\n请实现一个函数按照之字形打印二叉树，即第一行按照从左到右的顺序打印，第二层按照从右至左的顺序打印，第三行按照从左到右的顺序打印，其他行以此类推。\n\n解析：1 首先要知道到本题的基础思想，层次遍历。\n\n2 然后是进阶的思想，按行打印二叉树并输出行号，方法是，一个节点last指向当前行的最后一个节点，一个节点nlast指向下一行最后一个节点。使用t表示现在遍历的节点，当t = last时，表示本行结束。此时last = nlast，开始下一行遍历。\n\n同时，当t的左右子树不为空时，令nlast = t的左子树和右子树。每当last 赋值为nlast时，行号加一即可。\n\n3 基于第2步的思想，现在要z字型打印，只需把偶数行逆序即可。所以把每一行的数存起来，然后偶数行逆置即可。\n\n    ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {\n            LinkedList<TreeNode> queue = new LinkedList<>();\n            TreeNode root = pRoot;\n            if(root == null) {\n                return new ArrayList<>();\n            }\n            TreeNode last = root;\n            TreeNode nlast = root;\n            queue.offer(root);\n            ArrayList<Integer> list = new ArrayList<>();\n            list.add(root.val);\n            ArrayList<Integer> one = new ArrayList<>();\n            one.addAll(list);\n            ArrayList<ArrayList<Integer>> lists = new ArrayList<>();\n            lists.add(one);\n            list.clear();\n    \n            int row = 1;\n            while (!queue.isEmpty()){\n    \n                TreeNode t = queue.poll();\n    \n                if(t.left != null) {\n                    queue.offer(t.left);\n                    list.add(t.left.val);\n                    nlast = t.left;\n                }\n                if(t.right != null) {\n                    queue.offer(t.right);\n                    list.add(t.right.val);\n                    nlast = t.right;\n                }\n                if(t == last) {\n                    if(!queue.isEmpty()) {\n                        last = nlast;\n                        row ++;\n                        ArrayList<Integer> temp = new ArrayList<>();\n                        temp.addAll(list);\n                        list.clear();\n                        if (row % 2 == 0) {\n                            Collections.reverse(temp);\n                        }\n                        lists.add(temp);\n    \n                    }\n                }\n            }\n    \n            return lists;\n        }\n\n## 序列化和反序列化二叉树\n\n解析：序列化和反序列化关键是要确定序列化方式。我么使用字符串来序列化。\n\n用#代表空，用!分隔左右子树。\n\n比如 1\n    2 3\n   4   5\n\n使用先序遍历\n序列化结果是1!2!4!###3!#5!##\n\n反序列化先让根节点指向第一位字符，然后左子树递归进行连接，右子树\n\n\tpublic class Solution {\n\tpublic int index = -1;\n\tStringBuffer sb = new StringBuffer();\n\t\n\tString Serialize(TreeNode root) {\n\t       if(root == null) {\n\t        sb.append(\"#!\") ;\n\t    }\n\t    else {\n\t        sb.append(root.val + \"!\");\n\t        Serialize(root.left);\n\t        Serialize(root.right);\n\t    }\n\t\n\t    return sb.toString();\n\t  }\n\t    TreeNode Deserialize(String str) {\n\t        index ++;\n\t        int len = str.length();\n\t        if(index >= len) {\n\t            return null;\n\t        }\n\t        String[] strr = str.split(\"!\");\n\t        TreeNode node = null;\n\t        if(!strr[index].equals(\"#\")) {\n\t            node = new TreeNode(Integer.valueOf(strr[index]));\n\t            node.left = Deserialize(str);\n\t            node.right = Deserialize(str);\n\t        }\n\t        return node;\n\t  }\n\t}\n\n## 二叉搜索树的第k个结点\n\n    解析：二叉搜索树的中序遍历是有序的，只需要在中序中判断数字是否在第k个位置即可。\n    如果在左子树中发现了，那么递归返回该节点，如果在右子树出现，也递归返回该节点。注意必须要返回，否则结果会被递归抛弃掉。\n    \n    TreeNode KthNode(TreeNode pRoot, int k)\n        {\n            count = 0;\n            return inOrderSeq(pRoot, k);\n        }\n        static int count = 0;\n        public TreeNode inOrderSeq(TreeNode treeNode, int k) {\n            if (treeNode == null) return null;\n            TreeNode left = inOrderSeq(treeNode.left, k);\n            if (left != null) return left;\n            if (++ count == k) return treeNode;\n            TreeNode right = inOrderSeq(treeNode.right, k);\n            if (right != null) return right;\n            return null;\n        }\n\n# 栈和队列\n\n## 用两个队列实现栈，用两个栈实现队列。\n\n\n简单说下思路\n\n1 两个栈实现队列，要求先进先出，入队时节点先进入栈A，如果栈A满并且栈B空则把全部节点压入栈B。\n\n出队时，如果栈B为空，那么直接把栈A节点全部压入栈B，再从栈B出栈，如果栈B不为空，则从栈B出栈。\n\n2 两个队列实现栈，要求后进先出。入栈时，节点先加入队列A，出栈时，如果队列B不为空，则把头结点以后的节点出队并加入到队列B，然后自己出队。\n\n如果出栈时队列B不为空，则把B头结点以后的节点移到队列A，然后出队头结点，以此类推。\n\n## 包含min函数的栈\n\n/**\n * 设计一个返回最小值的栈\n * 定义栈的数据结构，请在该类型中实现一个能够得到栈最小元素的min函数。\n * Created by 周杰伦 on 2017/3/22.\n */\n\n    解析：这道题的解法也是非常巧妙的。因为每次进栈和出栈都有可能导致最小值发生改变。而我们要维护的是整个栈的最小值。\n    \n    如果单纯使用一个数来保存最小值，会出现一种情况，最小值出栈时，你此时的最小值只能改成栈顶元素，但这个元素不一定时最小值。\n    \n    所以需要一个数组来存放最小值，或者是一个栈。\n    \n    使用另一个栈B存放最小值，每次压栈时比较节点值和栈B顶端节点值，如果比它小则压栈，否则不压栈，这样就可以从b的栈顶到栈顶依次访问最小值，次小值。以此类推。\n    \n    当最小值节点出栈时，判断栈B顶部的节点和出栈节点是否相同，相同则栈B也出栈。\n    \n    这样就可以维护一个最小值的函数了。\n    \n    同理，最大值也是这样。\n    \n    \n    public class 包含min函数的栈 {\n        Stack<Integer> stack=new Stack<>();\n        Stack<Integer> minstack=new Stack<>();\n    \n        public void push(int node) {\n            if(stack.isEmpty())\n            {\n                stack.push(node);\n                minstack.push(node);\n            }\n            else if(node<stack.peek()){\n                stack.push(node);\n                minstack.push(node);\n            }\n            else {\n                stack.push(node);\n            }\n        }\n        \n        public void pop() {\n            if(stack.isEmpty())return;\n            if(stack.peek()==minstack.peek()){\n               stack.pop();\n               minstack.pop();\n            }\n            else {\n                stack.pop();\n            }\n        }\n        \n        public int top() {\n          return stack.peek();\n        }\n        \n        public int min() {\n            if(minstack.isEmpty())return 0;\n         return minstack.peek();\n        }\n    }\n\n## 栈的压入和弹出序列\n\n        题目描述\n    输入两个整数序列，第一个序列表示栈的压入顺序，请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序，序列4,5,3,2,1是该压栈序列对应的一个弹出序列，但4,3,5,1,2就不可能是该压栈序列的弹出序列。（注意：这两个序列的长度是相等的）\n    \n    解析：本题是比较抽象的，首先，根据入栈出栈的规则，我们可以建立一个栈A，用于保存压栈序列，然后压入第一个元素，比较出栈序列的第一个元素，如果不相等，继续压栈，直到二者相等，此时栈A元素出栈，然后重复上一步的操作。\n    \n    如果在每次压栈过程中，入栈序列已经全部入栈A但是还是找不到出栈序列的第一个元素时，则说明不是出栈序列。\n    \n    当栈A的元素全部压入并出栈后，如果出栈序列也出栈完毕，则满足题意。\n    \n    public static boolean IsPopOrder(int[] pushA, int[] popA) {\n            Stack<Integer> stack = new Stack<>();\n            int j = 0;\n            int i = 0;\n            while (i < pushA.length) {\n                stack.push(pushA[i]);\n                i++;\n                while (!stack.empty() && stack.peek() == popA[j]) {\n                    stack.pop();\n                    j++;\n                }\n                if (i == pushA.length) {\n                    if (!stack.empty()) {\n                        return false;\n                    } else return true;\n                }\n            }\n            return false;\n        }\n\n\n# 排序和查找\n\n## 旋转数组的最小数字\n\n    把一个数组最开始的若干个元素搬到数组的末尾，我们称之为数组的旋转。 输入一个非递减排序的数组的一个旋转，输出旋转数组的最小元素。 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转，该数组的最小值为1。 NOTE：给出的所有元素都大于0，若数组大小为0，请返回0。\n    \n    解析：这题的思路很巧妙，如果直接遍历复杂度为O（N），但是使用二分查找可以加快速度，因为两边的数组都是递增的最小值一定在两边数组的边缘，于是通过二分查找，逐渐缩短左右指针的距离，知道左指针和右指针只差一步，那么右指针所在的数就是最小值了。\n    复杂度是O（logN)\n    \n    //这段代码忽略了三者相等的情况\n    public int minNumberInRotateArray(int [] array) {\n            if (array.length == 0) return 0;\n            if (array.length == 1) return array[0];\n            int min = 0;\n    \n            int left = 0, right = array.length - 1;\n            //只有左边值大于右边值时，最小值才可能出现在中间\n            while (array[left] > array[right]) {\n                int mid = (left + right)/2;\n                if (right - left == 1) {\n                    min = array[right];\n                    break;\n                }\n                //如果左半部分递增，则最小值在右侧\n                if (array[left] < array[mid]) {\n                    left = mid;\n                }\n                //如果右半部分递增，则最小值在左侧。\n                //由于左边值比右边值大，所以两种情况不会同时发生\n                else if (array[right] > array[mid]) {\n                    right = mid ;\n                }\n            }\n            return array[min];\n    \n        }\n\n \n\n        注意：但是当arr[left] = arr[right] = arr[min]时。三个数都相等无法确定最小值，此时只能遍历。\n\n# 递归\n\n## 斐波那契数列\n\n1递归做法\n\n2记忆搜索，用数组存放使用过的元素。\n\n3DP，本题中dp就是记忆化搜索\n\n## 青蛙跳台阶\n\n一次跳两步或者跳一步，问一共多少种跳法到达n级，所以和斐波那契数列是一样的。\n\n## 变态跳台阶\n\n一次跳1到n步，问一共几种跳法，这题是找数字规律的，一共有2^(n-1)种方法\n\n## 矩形覆盖\n\n和上题一样，也是找规律，答案也是2^(n-1)\n\n# 位运算\n\n## 二进制中1的个数\n\n * Created by 周杰伦 on 2018/6/29.\n * 题目描述\n * 输入一个整数，输出该数二进制表示中1的个数。其中负数用补码表示。\n\n    解析:\n    1 循环右移数字n，每次判断最低位是否为1，但是可能会导致死循环。\n    \n    2 使用数字a = 1和n相与，a每次左移一位，再与n相与得到次低位，最多循环32次，当数字1左移32次也会等于0，所以结束循环。\n    \n    3 非常奇葩的做法，把一个整数减去1，再与原整数相与，会把最右边的一个1变成0，于是统计可以完成该操作的次数即可知道有多少1了。\n    \n        public class 二进制中1的个数 {\n            public static int NumberOf1(int n) {\n                int count = 0;\n                while (n != 0) {\n                    ++count;\n                    n = (n - 1) & n;\n                }\n                return count;\n            }\n        }\n    \n## 数组中只出现一次的数字\n\n题目描述\n一个整型数组里除了一个数字之外，其他的数字都出现了两次。请写程序找出这一个只出现一次的数字。\n\n解析：左神称之为神仙题。\n\n利用位运算的异或操作^。\n由于a^a = 0,0^b=b，所以。所有数执行异或操作，结果就是只出现一次的数。\n\n## 不用加减乘除做加法\n\n解析：不用加减乘，那么只能用二进制了。\n\n两个数a和b，如果不考虑进位，则0 + 1 = 1,1 + 1 = 0，0 + 0 = 0，这就相当于异或操作。\n如果考虑进位，则只有1 + 1有进位，所以使用相与左移的方法得到每一位的进位值，再通过异或操作和原来的数相加。当没有进位值的时候，运算结束。  \n    public static int Add(int num1,int num2) {\n        if( num2 == 0 )return num1;\n        if( num1 == 0 )return num2;\n\n        int temp = num2;\n        while(num2!=0) {\n            temp = num1 ^num2;\n            num2 = (num1 & num2)<<1;\n            num1 = temp;\n        }\n        return num1;\n    }\n\n# 回溯和DFS\n\n## 矩阵中的路径\n\n题目描述\n请设计一个函数，用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始，每一步可以在矩阵中向左，向右，向上，向下移动一个格子。如果一条路径经过了矩阵中的某一个格子，则之后不能再次进入这个格子。 例如 a b c e s f c s a d e e 这样的3 X 4 矩阵中包含一条字符串\"bcced\"的路径，但是矩阵中不包含\"abcb\"路径，因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后，路径不能再次进入该格子。\n\n解析：回溯法也就是特殊的dfs，需要找到所有的路径，所以每当到达边界条件或抵达目标时，递归返回，由于需要保存路径中的字母，所以递归返回时需要删除路径最后的节点，来保证路径合法。不过本题只有一个解，所以找到即可返回。\n\n    public class 矩阵中的路径 {\n        public static void main(String[] args) {\n            char[][]arr = {{'a','b','c','e'},{'s','f','c','s'},{'a','d','e','e'}};\n            char []str = {'b','c','c','e','d'};\n            System.out.println(hasPath(arr, arr.length, arr[0].length, str));\n        }\n        static int flag = 0;\n        public static boolean hasPath(char[][] matrix, int rows, int cols, char[] str)\n        {\n            int [][]visit = new int[rows][cols];\n            StringBuilder sb = new StringBuilder();\n            for (int i = 0;i < rows;i ++) {\n                for (int j = 0;j < cols;j ++) {\n                    if (matrix[i][j] == str[0]) {\n                        visit[i][j] = 1;\n                        sb.append(str[0]);\n                        dfs(matrix, i, j, visit, str, 1, sb);\n                        visit[i][j] = 0;\n                        sb.deleteCharAt(sb.length() - 1);\n                    }\n                }\n            }\n            return flag == 1;\n        }\n        public static void dfs(char [][]matrix, int row, int col, int [][]visit, char []str, int cur, StringBuilder sb) {\n            if (sb.length() == str.length) {\n    //            System.out.println(sb.toString());\n                flag = 1;\n                return;\n            }\n    \n            int [][]pos = {{1,0},{-1,0},{0,1},{0,-1}};\n            for (int i = 0;i < pos.length;i ++) {\n                int x = row + pos[i][0];\n                int y = col + pos[i][1];\n                if (x >= matrix.length || x < 0 || y >= matrix[0].length || y < 0) {\n                    continue;\n                }\n                if (visit[x][y] == 0 && matrix[x][y] == str[cur]) {\n                    sb.append(matrix[x][y]);\n                    visit[x][y] = 1;\n                    dfs(matrix, x, y, visit, str, cur + 1, sb);\n                    sb.deleteCharAt(sb.length() - 1);\n                    visit[x][y] = 0;\n                }\n            }\n        }\n\n\n​    \n## 机器人的运动范围\n\n题目描述\n地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动，每一次只能向左，右，上，下四个方向移动一格，但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如，当k为18时，机器人能够进入方格（35,37），因为3+5+3+7 = 18。但是，它不能进入方格（35,38），因为3+5+3+8 = 19。请问该机器人能够达到多少个格子？\n\n 解析：这是一个可达性问题，使用dfs方法，走到的每一格标记为走过，走到无路可走时就是最终的结果。每次都有四个方向可以选择，所以写四个递归即可。\n\n    public class Solution {\n        static int count = 0;\n        public static int movingCount(int threshold, int rows, int cols)\n        {\n            count = 0;\n            int [][]visit = new int[rows][cols];\n            dfs(0, 0, visit, threshold);\n            return count;\n        }\n    \n        public static void dfs(int row, int col, int[][]visit, int k) {\n            if (row >= visit.length || row < 0 || col >= visit[0].length || col < 0) {\n                return;\n            }\n            if (sum(row) + sum(col) > k) {\n                return;\n            }\n    \n            if (visit[row][col] == 1){\n                return;\n            }\n    \n            visit[row][col] = 1;\n            count ++;\n            dfs(row + 1,col,visit, k);\n            dfs(row - 1,col,visit, k);\n            dfs(row,col + 1,visit, k);\n            dfs(row,col - 1,visit, k);\n    \n        }\n    \n        public static int sum(int num) {\n            String s = String.valueOf(num);\n            int sum = 0;\n            for (int i = 0;i < s.length();i ++) {\n                sum += Integer.valueOf(s.substring(i, i + 1));\n            }\n            return sum;\n        }\n    }\n\n"
  },
  {
    "path": "docs/cs/network/计算机网络学习总结.md",
    "content": "# 目录\n\n  * [网卡和路由器](#网卡和路由器)\n  * [交换机](#交换机)\n  * [以太网](#以太网)\n  * [虚拟局域网VLAN](#虚拟局域网vlan)\n  * [DHCP协议(动态主机配置协议)](#dhcp协议动态主机配置协议)\n  * [ARP协议](#arp协议)\n  * [网关和NAT](#网关和nat)\n  * [DNS协议和http请求过程](#dns协议和http请求过程)\n  * [ICMP](#icmp)\n  * [虚拟专用网VPN和内网ip](#虚拟专用网vpn和内网ip)\n  * [应用层](#应用层)\n    * [http](#http)\n    * [http1.0 1.1和2.0](#http10-11和20)\n      * [1.0和1.1的主要变化](#10和11的主要变化)\n      * [http1.0和http2.0的区别。](#http10和http20的区别。)\n    * [get和post](#get和post)\n    * [](#)\n      * [session和cookie](#session和cookie)\n      * [token](#token)\n      * [cas单点登录](#cas单点登录)\n  * [web安全和https](#web安全和https)\n    * [密码加密](#密码加密)\n    * [xss跨站脚本攻击](#xss跨站脚本攻击)\n    * [跨站点请求伪造csrf](#跨站点请求伪造csrf)\n    * [SQL 注入攻击](#sql-注入攻击)\n    * [拒绝服务攻击](#拒绝服务攻击)\n    * [https](#https)\n  * [传输层](#传输层)\n    * [UDP报文](#udp报文)\n    * [TCP 首部格式](#tcp-首部格式)\n    * [三次握手和四次挥手](#三次握手和四次挥手)\n    * [半连接syn和洪泛法攻击](#半连接syn和洪泛法攻击)\n    * [为什么要三次握手](#为什么要三次握手)\n    * [time wait的作用](#time-wait的作用)\n  * [可靠传输协议](#可靠传输协议)\n    * [tcp的粘包拆包](#tcp的粘包拆包)\n  * [网络层](#网络层)\n  * [IP 数据报格式](#ip-数据报格式)\n  * [某个聚合路由地址划分网络给n台机器，是否符合要求。。](#某个聚合路由地址划分网络给n台机器，是否符合要求。。)\n    * [IP 地址编址方式](#ip-地址编址方式)\n    * [[](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C.md#1-%E5%88%86%E7%B1%BB)1\\. 分类](#[]httpsgithubcomcyc2018interview-notebookblobmasternotese8aea1e7ae97e69cbae7bd91e7bb9cmd1-e58886e7b1bb1-分类)\n    * [ip分片详谈](#ip分片详谈)\n    * [路由选择协议和算法](#路由选择协议和算法)\n  * [链路层](#链路层)\n\n\n\n---\ntitle: 计算机网络学习总结\ndate: 2018-07-09 22:32:57\ntags:\n\t- 计算机网络\ncategories:\n\t- 后端\n\t- 技术总结\n---\n这部分内容主要是基于一些关于计算机网络基础的学习总结，内容不全面，只讲述了其中的一小部分，后续会再补充，如有错误，还请见谅。\n\n<!-- more -->\n\n 计算机网络常见概念\n\n## 网卡和路由器\n\n网卡是一个有mac地址的物理设备，通过mac地址与局域网内的交换机通信，交换机可以识别mac地址。\n\n而单纯的中继器，集线器，双绞线等设备只识别物理层设备。\n\n路由器则工作在3层ip层，必须要有ip才能工作，所以路由器每一个接口都对应一个ip，维护一个可以识别ip的路由表，进行ip数据报转发。\n\n## 交换机\n交换机具有自学习能力，学习的是交换表的内容。交换表中存储着 MAC 地址到接口的映射。\n\n## 以太网\n以太网是一种星型拓扑结构局域网。\n\n早期使用集线器进行连接，它是一种物理层设备，作用于比特而不是帧，当一个比特到达接口时，集线器重新生成这个比特，并将其能量强度放大，从而扩大网络的传输距离。之后再将这个比特向其它所有接口。特别是，如果集线器同时收到同时从两个不同接口的帧，那么就发生了碰撞。\n\n目前以太网使用交换机替代了集线器，它不会发生碰撞，能根据 MAC 地址进行存储转发。\n\n## 虚拟局域网VLAN\n正常情况下，局域网中的链路层广播在整个局域网可达，而vlan可以在物理局域网中划分虚拟局域网，使广播帧只有在vlan当中的主机才能收到。\n\n虚拟局域网可以建立与物理位置无关的逻辑组，只有在同一个虚拟局域网中的成员才会收到链路层广播信息，例如下图中 (A1, A2, A3, A4) 属于一个虚拟局域网，A1 发送的广播会被 A2、A3、A4 收到，而其它站点收不到。\n\n## DHCP协议(动态主机配置协议)\n\n首先DHCP是为了让主机获得一个ip地址，所以主机会发一个0.0.0.0为发送方，255.255.255.255为接收方的ip数据报，也就是广播数据报，并且广播数据包只在局域网中有效，然后链路层解析为数据帧，发送给局域网内的DHCP服务器。\n\n## ARP协议\narp负责把ip地址解析成局域网内的一个mac地址，只在局域网中有效。逆arp则把mac地址解析成ip地址。\n\n网络层实现主机之间的通信，而链路层实现具体每段链路之间的通信。因此在通信过程中，IP 数据报的源地址和目的地址始终不变，而 MAC 地址随着链路的改变而改变。\n\n[![](https://github.com/CyC2018/Interview-Notebook/raw/master/pics/66192382-558b-4b05-a35d-ac4a2b1a9811.jpg)](https://github.com/CyC2018/Interview-Notebook/blob/master/pics/66192382-558b-4b05-a35d-ac4a2b1a9811.jpg)\n\nARP 实现由 IP 地址得到 MAC 地址。\n\n[![](https://github.com/CyC2018/Interview-Notebook/raw/master/pics/b9d79a5a-e7af-499b-b989-f10483e71b8b.jpg)](https://github.com/CyC2018/Interview-Notebook/blob/master/pics/b9d79a5a-e7af-499b-b989-f10483e71b8b.jpg)\n\n每个主机都有一个 ARP 高速缓存，里面有本局域网上的各主机和路由器的 IP 地址到硬件地址的映射表。\n\n> 如果主机 A 知道主机 B 的 IP 地址，但是 ARP 高速缓存中没有该 IP 地址到 MAC 地址的映射，此时主机 A 通过广播的方式发送 ARP 请求分组，主机 B 收到该请求后会发送 ARP 响应分组给主机 A 告知其 MAC 地址，随后主机 A 向其高速缓存中写入主机 B 的 IP 地址到 MAC 地址的映射。\n\n## 网关和NAT\n当需要和外部局域网访问时，需要经过网关服务器以便兼容不同协议栈。局域网内部使用内网ip，经过网关时要转成外网ip，网关会帮你完成改写操作，当收到数据报时，网关又会帮你把ip改为内网ip。这种修改ip隐藏内部网络的方式叫做NAT。\n\nnat穿透的方式是主机和网关服务器协定一个ip地址作为主机服务的ip，所以主机可以通过这个ip和外网交流。\n\n## DNS协议和http请求过程\n访问一个域名时，会发送dns报文请求（应用层）给本地的DNS服务器，解析出域名对应的ip，然后三次握手建立连接，（当然TCP数据报由本地局域网经过网关转给外网，再经过多次路由才到达目标主机），然后发送http请求获得响应报文\n\n## ICMP\nICMP 是为了更有效地转发 IP 数据报和提高交付成功的机会。它封装在 IP 数据报中，但是不属于高层协议。\n\n[![](https://github.com/CyC2018/Interview-Notebook/raw/master/pics/e3124763-f75e-46c3-ba82-341e6c98d862.jpg)](https://github.com/CyC2018/Interview-Notebook/blob/master/pics/e3124763-f75e-46c3-ba82-341e6c98d862.jpg)\n\nICMP 报文分为差错报告报文和询问报文。\n\n[![](https://github.com/CyC2018/Interview-Notebook/raw/master/pics/aa29cc88-7256-4399-8c7f-3cf4a6489559.png)](https://github.com/CyC2018/Interview-Notebook/blob/master/pics/aa29cc88-7256-4399-8c7f-3cf4a6489559.png)\n\n\n    1. Ping\n    Ping 是 ICMP 的一个重要应用，主要用来测试两台主机之间的连通性。\n    \n    Ping 发送的 IP 数据报封装的是无法交付的 UDP 用户数据报。\n    \n    2. Traceroute\n    Traceroute 是 ICMP 的另一个应用，用来跟踪一个分组从源点到终点的路径，事实上，traceroute也封装着无法交付的udp，和ping类似。。\n\n源主机向目的主机发送一连串的 IP 数据报，每个数据包的ttl时间不同，所以可以跟踪每一跳路由的信息。\n\n==但是因为数据报封装的是无法交付的UDP报文，因此目的主机要向源主机发送 ICMP终点不可达差错报告报文。之后源主机知道了到达目的主机所经过的路由器 IP地址以及到达每个路由器的往返时间。==\n\n## 虚拟专用网VPN和内网ip\n\n由于 IP 地址的紧缺，一个机构能申请到的 IP 地址数往往远小于本机构所拥有的主机数。并且一个机构并不需要把所有的主机接入到外部的互联网中，机构内的计算机可以使用仅在本机构有效的 IP 地址（专用地址）。\n\n有三个专用地址块：\n\n*   10.0.0.0 ~ 10.255.255.255\n*   172.16.0.0 ~ 172.31.255.255\n*   192.168.0.0 ~ 192.168.255.255\n\n这些ip也称为内网ip，用于局域网间的通信，只能通过网关抵达公网。\n\n[![](https://github.com/CyC2018/Interview-Notebook/raw/master/pics/1556770b-8c01-4681-af10-46f1df69202c.jpg)](https://github.com/CyC2018/Interview-Notebook/blob/master/pics/1556770b-8c01-4681-af10-46f1df69202c.jpg)\n\n使用隧道技术实现vpn。\n\n    原理是；普通的内网ip无法被访问到，一般可以使用nat技术让网关作为中转人，而ip数据报也会改写成网关服务器的地址。\n    \n    如果想让数据报保留内网地址，并且实现跨公网访问，那么只能通过隧道技术，把内网数据报加密包装在公网ip数据报中，然后通过公网ip抵达对方的专用网络，进行拆包和发送。\n    \n    为什么vpn能翻墙呢，因为我们通过对vpn服务器的连接，可以将内网ip数据报装在里面，发送给vpn，vpn解析后再发送给真正的服务器。\n    \n    由于本地网关阻拦了某些网站的请求，所以我们要把这个请求加密封装，然后通过隧道把数据发给一个海外服务器，让他真正完成请求。\n\n## 应用层\n\n应用层的协议主要是http，ftp这类协议，http访问超文本html，而ftp访问文件系统。\n\n### http\n\n通过浏览器可以方便地进行dns解析，建立tcp连接，发送http请求，得到http响应，这些工作都是浏览器完成的。\n\n### http1.0 1.1和2.0\n\n#### 1.0和1.1的主要变化\n\n    1 http1.0经过多年发展，在1.1提出了改进。\n    首先是提出了长连接，http请求可以在一次tcp连接中不断发送。\n    \n    2 然后是http1.1支持只发送header而不发送body。原因是先用header判断能否成功，再发数据，节约带宽，事实上，post请求默认就是这样做的。\n    \n    3 http1.1的host字段。由于虚拟主机可以支持多个域名，所以一般将域名解析后得到host。\n\n#### http1.0和http2.0的区别。\n\n    http2.0变化巨大。\n    \n    1 http支持多路复用，同一个连接可以并发处理多个请求，方法是把http数据包拆为多个帧，并发有序的发送，根据序号在另一端进行重组，而不需要一个个http请求顺序到达。\n    \n    2 http2.0支持服务端推送，就是服务端在http请求到达后，除了返回数据之外，还推送了额外的内容给客户端。\n    \n    3HTTP2.0压缩了请求头，同时基本单位是二进制帧流，这样的数据占用空间更少。\n    \n    4http2.0只适用于https场景，因为其在http和tcp中间加了一层ssl层。\n\n### get和post\n\n    get和post本质都是http请求，只不过对他们的作用做了界定和适配，并且让他们适应各自的场景。\n    \n    1本质区别是get只是一次http请求，post先发请求体再发请求体，实际上是两次请求\n    \n    2表面区别：\n    \n    get可以cache而post不能，因为浏览器是这么安排的\n    \n    一般设计get是幂等的而post不是\n    \n    get的参数放在url传递，而post放在请求体里，因为get没有请求体。\n    所以get请求不安全，并且有长度限制（url不能太长），而post几乎没有限制，请求体可以很大。\n\n### \n\n\n#### session和cookie\n\n并且浏览器还维护了cookie以便记录用于对网站的一些信息，下次请求时在http报文中带上这些数据，服务器接收以后根据cookie中的sessionid获取对应的session即可\n\n#### token\n\nsession一般维护在内存中，有时候也会持久化到数据库，但是如果session由单点维护可能出现宕机等情况，于是一般会采用分布式的方案。\n\n\n    session存放的几种方案。\n    0 存在内存中。用sessionid标识用户。\n    这样的session十分依赖于cookie。如果浏览器禁用了cookie则session无用武之地。\n    \n    当然也可以把内容存在数据库里，缺点是数据库访问压力较大。\n    \n    1有做法会将session内容存在cookie中，但前提是经过了加密，然后下次服务器对其进行解密，但是这样浏览器需要维护太多内容了。\n    \n    2当用户登录或者执行某些操作，则使用用户的一部分字段信息进行加密算法得到一串字符串成为token，用于唯一标识用户，或者是某些操作，比如登录，支付，服务端生成该token返回给用户，用户提交请求时必须带上这个token，就可以确认用户信息以及操作是否合法了。\n    \n    这样我们不需要存session，只需要在想得到用户信息时解密token即可。\n    \n    token还有一个好处就是可以在移动端和pc端兼容，因为移动端不支持cookie。\n    \n    3token和oauth。经常有第三方授权登录的例子，本质就是使用token。首先我们打开授权登录页，登陆后服务端返回token，我们提交第三方的请求时，带上这个token，第三方不知道他是啥意思，并且token过段时间就过期了。\n\n#### cas单点登录\n\n单点登录是为了多个平台之间公用一个授权系统，做法是，所有登录都要指向统一登录服务，登陆成功以后在认证中心建立session，并且得到ticket，然后重定向页面，此时页面也会向认证中心确认ticket是否合法，然后就可以访问其他系统的页面了。\n\n从而访问其他系统时，由于已经有了认证中心的cookie，所以直接带上ticket访问即可。\n\n每次访问新系统时需要在认证中心注册session，然后单点退出时再把这些session退出，才能实现用户登出。\n\n## web安全和https\n\n### 密码加密\n\nMD5等加密方法可以用来对密码进行加密。一般还会加盐\n\n### xss跨站脚本攻击\n\n利用有输入功能网站的输入框来注入JavaScript脚本代码，用户访问该页面时会自动执行某些脚本代码，导致cookie等个人信息泄露，可能会被转发到其他网站。\n\n解决办法是对输入进行检验，利用一个些工具类就可以做到。\n\n### 跨站点请求伪造csrf\n首先用户访问了一个网站并登陆，会把cookie保留在浏览器，\n然后某些网站用一些隐性链接诱导用户点击，点击时发送请求会携带浏览器中的cookie，比如支付宝的账号密码，通过该cookie再去伪造一个支付宝支付请求，达到伪造请求的目的。 \n\n解决这个问题的办法就是禁止js请求跨域名。但是他为ajax提供了特殊定制。\n\n### SQL 注入攻击\n1. 概念\n服务器上的数据库运行非法的 SQL 语句，主要通过拼接来完成。\n\n3. 防范手段\n（一）使用参数化查询\n\n以下以 Java 中的 PreparedStatement 为例，它是预先编译的 SQL 语句，可以传入适当参数并且多次执行。由于没有拼接的过程，因此可以防止 SQL 注入的发生。\n\n（二）单引号转换\n\n将传入的参数中的单引号转换为连续两个单引号，PHP 中的 Magic quote 可以完成这个功能。\n\n### 拒绝服务攻击\n拒绝服务攻击（denial-of-service attack，DoS），亦称洪水攻击，其目的在于使目标电脑的网络或系统资源耗尽，使服务暂时中断或停止，导致其正常用户无法访问。\n\n分布式拒绝服务攻击（distributed denial-of-service attack，DDoS），指攻击者使用网络上两个或以上被攻陷的电脑作为“僵尸”向特定的目标发动“拒绝服务”式攻击。\n\nDDoS攻击通过大量合法的请求占用大量网络资源，以达到瘫痪网络的目的。 \n\n这种攻击方式可分为以下几种：\n\n    通过使网络过载来干扰甚至阻断正常的网络通讯；\n    通过向服务器提交大量请求，使服务器超负荷；\n    阻断某一用户访问服务器；\n    阻断某服务与特定系统或个人的通讯。\n\n攻击现象\n\n    被攻击主机上有大量等待的TCP连接；\n    网络中充斥着大量的无用的数据包；\n    源地址为假 制造高流量无用数据，造成网络拥塞，使受害主机无法正常和外界通讯；\n    利用受害主机提供的传输协议上的缺陷反复高速的发出特定的服务请求，使主机无法处理所有正常请求；\n    严重时会造成系统死机。\n\n总体来说，对DoS和DDoS的防范主要从下面几个方面考虑：\n\n    尽可能对系统加载最新补丁，并采取有效的合规性配置，降低漏洞利用风险；\n    \n    采取合适的安全域划分，配置防火墙、入侵检测和防范系统，减缓攻击。\n    \n    采用分布式组网、负载均衡、提升系统容量等可靠性措施，增强总体服务能力。\n### https\n\nhttps博大精深，首先先来看看他的基础知识\n\n    1对称加密和非对称加密\n    \n    对称加密两方使用同一把密钥加密和解密，传输密钥时如果丢失就会被破解。\n    \n    2非对称加密两方各有一把私钥，而公钥公开，A用私钥加密，把公钥和数据传给B，B用公钥解密。同理，B用私钥对数据进行加密，返回给A，A也用公钥进行解密。\n    \n    3非对称加密只要私钥不丢就很安全，但是效率比较低，所以一般使用非对称加密传输对称加密的密钥，使用对称加密完成数据传输。\n    \n    4数字签名，为了避免数据在传输过程中被替换，比如黑客修改了你的报文内容，但是你并不知道，所以我们让发送端做一个数字签名，把数据的摘要消息进行一个加密，比如MD5，得到一个签名，和数据一起发送。然后接收端把数据摘要进行md5加密，如果和签名一样，则说明数据确实是真的。\n    \n    5数字证书，对称加密中，双方使用公钥进行解密。虽然数字签名可以保证数据不被替换，但是数据是由公钥加密的，如果公钥也被替换，则仍然可以伪造数据，因为用户不知道对方提供的公钥其实是假的。\n    \n    所以为了保证发送方的公钥是真的，CA证书机构会负责颁发一个证书，里面的公钥保证是真的，用户请求服务器时，服务器将证书发给用户，这个证书是经由系统内置证书的备案的。\n\n\n​    \n​    \n    6 https过程\n    \n    用户发送请求，服务器返回一个数字证书。\n    \n    用户在浏览器端生成一个随机数，使用证书中的公钥加密，发送给服务端。\n    \n    服务端使用公钥解密该密文，得到随机数。\n    \n    往后两者使用该随机数作为公钥进行对称加密。\n\n\n    番外：关于公钥加密私钥解密与私钥加密公钥解密说明\n    第一种是签名,使用私钥加密,公钥解密,用于让所有公钥所有者验证私钥所有者的身份并且用来防止私钥所有者发布的内容被篡改.但是不用来保证内容不被他人获得. \n    \n    第二种是加密,用公钥加密,私钥解密,用于向公钥所有者发布信息,这个信息可能被他人篡改,但是无法被他人获得.搜索 \n\n\n## 传输层\n\n    UDP 和 TCP 的特点\n    用户数据报协议 UDP（User Datagram Protocol）是无连接的，尽最大可能交付，没有拥塞控制，面向报文（对于应用程序传下来的报文不合并也不拆分，只是添加 UDP 首部），支持一对一、一对多、多对一和多对多的交互通信。\n    \n    传输控制协议 TCP（Transmission Control Protocol）是面向连接的，提供可靠交付，有流量控制，拥塞控制，提供全双工通信，面向字节流（把应用层传下来的报文看成字节流，把字节流组织成大小不等的数据块），每一条 TCP 连接只能是点对点的（一对一）。\n\nTCP是传输层最重要的协议。\n> \n> 由于网络层只提供最大交付的服务，尽可能地完成路由转发，以及把链路层报文传送给任意一台主机。他做的工作很专注，所以不会提供其他的可靠性保证。\n> \n> 但是真实网络环境下随时会发生丢包，乱序，数据内容出错等情况，这些情况必须得到处理，于是我们使用传输层tcp来解决这些问题。\n\n### UDP报文\n\n伪首部的意义：伪首部并非TCP&UDP数据报中实际的有效成分。伪首部是一个虚拟的数据结构，其中的信息是从数据报所在IP分组头的分组头中提取的，既不向下传送也不向上递交，而仅仅是为计算校验和。\n\n![image](https://github.com/CyC2018/Interview-Notebook/raw/master/pics/d4c3a4a1-0846-46ec-9cc3-eaddfca71254.jpg)\n\n首部字段只有 8 个字节，包括源端口、目的端口、长度、检验和。12 字节的伪首部是为了计算检验和临时添加的。\n\n### TCP 首部格式\n\n[![](https://github.com/CyC2018/Interview-Notebook/raw/master/pics/55dc4e84-573d-4c13-a765-52ed1dd251f9.png)](https://github.com/CyC2018/Interview-Notebook/blob/master/pics/55dc4e84-573d-4c13-a765-52ed1dd251f9.png)\n\n*   **序号** ：用于对字节流进行编号，例如序号为 301，表示第一个字节的编号为 301，如果携带的数据长度为 100 字节，那么下一个报文段的序号应为 401。\n\n*   **确认号** ：期望收到的下一个报文段的序号。例如 B 正确收到 A 发送来的一个报文段，序号为 501，携带的数据长度为 200 字节，因此 B 期望下一个报文段的序号为 701，B 发送给 A 的确认报文段中确认号就为 701。\n\n*   **数据偏移** ：指的是数据部分距离报文段起始处的偏移量，实际上指的是首部的长度。\n\n*   **确认 ACK** ：当 ACK=1 时确认号字段有效，否则无效。TCP 规定，在连接建立后所有传送的报文段都必须把 ACK 置 1。\n\n*   **同步 SYN** ：在连接建立时用来同步序号。当 SYN=1，ACK=0 时表示这是一个连接请求报文段。若对方同意建立连接，则响应报文中 SYN=1，ACK=1。\n\n*   **终止 FIN** ：用来释放一个连接，当 FIN=1 时，表示此报文段的发送方的数据已发送完毕，并要求释放连接。\n\n*   **窗口** ：窗口值作为接收方让发送方设置其发送窗口的依据。之所以要有这个限制，是因为接收方的数据缓存空间是有限的。\n*  \n\n###     三次握手和四次挥手\n    为了保证tcp的可靠传输，需要建立起一条通路，也就是所谓连接。这条通路必须保证有效并且能正确结束。\n\n[![](https://github.com/CyC2018/Interview-Notebook/raw/master/pics/e92d0ebc-7d46-413b-aec1-34a39602f787.png)](https://github.com/CyC2018/Interview-Notebook/blob/master/pics/e92d0ebc-7d46-413b-aec1-34a39602f787.png)\n\n\n​    \n    三次握手\n    \n    1 首先客户端发送连接请求syn，携带随机数x。\n    2 服务端返回请求ack，x + 1,说明服务端对x进行了回复。\n    3 客户端返回请求ack，y，说明接受到了信息并且开始传输数据，起始数据为y。\n    \n    客户端状态时syn_send和establish\n    服务端则是从listen到syn_rcvd，再到establish\n\n[![](https://github.com/CyC2018/Interview-Notebook/raw/master/pics/f87afe72-c2df-4c12-ac03-9b8d581a8af8.jpg)](https://github.com/CyC2018/Interview-Notebook/blob/master/pics/f87afe72-c2df-4c12-ac03-9b8d581a8af8.jpg)\n\n    四次挥手\n    \n    1 首先客户端请求断开连接，发送fin请求，服务端返回fin的ack，继续处理断开前需要处理完的数据。\n    \n    2 过了一会，服务端处理完数据发送给客户端ack，表明已经关闭，客户端最后再发一个ack给服务端，如果服务端已关闭则无反应，客户端经过两个ttl后挥手完毕，确认服务端断开。这两个ttl成为time wait状态，用于确定服务端真的关闭。\n    \n    3 客户端发完fin后的状态从establish变为fin1——wait，服务端发完ack后的状态从establish变为closewait。\n    \n    4 客户端收到第一个ack后进入fin_2wait状态，服务端过了一会发送last——ack给客户端，说明关闭好了，客户端收到ack后进入timewait，然后发送ack。双方都closed。\n\n### 半连接syn和洪泛法攻击\n\n黑客开启大量的syn请求而不发送ack，服务端开启半连接等待ack，直到资源耗尽，所以必须检测来访ip\n### 为什么要三次握手\n\n三次握手的原因\n\n第三次握手是为了防止失效的连接请求到达服务器，让服务器错误打开连接。\n\n也就是说，如果只有两次握手，服务端返回ack后直接通信，那么如果客户端因为网络问题没有收到ack，可能会再次请求连接，但时服务端不知道这其实是同一个请求，于是又打开了一个连接，相当于维护了很多的无用连接。\n### time wait的作用\n\n1 需要服务端可靠地终止连接，如果处于time_wait客户端发给服务端的ack报文丢失，则服务端会再发一次fin，此时客户端不应该关闭。\n\n2 保证迟来的tcp报文有时间被丢弃，因为2msl里超时抵达的报文都会被丢弃。\n\n## 可靠传输协议\n\nTCP协议有三个重要属性。\n\n    可靠传输，主要通过有序接收，确认后发送，以及超时重传来实现，并且使用分片来提高发送效率，通过检验和避免错误。\n    \n    流量控制，主要通过窗口限制接收和发送速率。\n    \n    拥塞控制，主要通过不同拥塞状态的算法来处理拥塞，一开始发的比较慢，然后指数增加，当丢包时再降低速度，重新开始第一阶段，避免拥塞。\n\n总结以下就是几个特点：\n\nTCP 可靠传输\n\n    TCP 使用超时重传来实现可靠传输：\n    \n    1 如果一个已经发送的报文段在超时时间内没有收到确认，那么就重传这个报文段。\n\n\n​    \n    2 滑动窗口可以连续发送多个数据再统一进行确认。\n    \n       因为发送端希望在收到确认前，继续发送其它报文段。比如说在收到0号报文的确认前还发出了1-3号的报文，这样提高了信道的利用率。\n       \n    3 滑动窗口只重传丢失的数据报\n       \n    但可以想想，0-4发出去后可能要重传，所以需要一个缓冲区维护这些报文，所以就有了窗口。\n    \n    4每当完成一个确认窗口往前滑动一格，可以传新的一个数据，因此可以顺序发送顺序确认\n\nTCP 流量控制\n\n    流量控制是为了控制发送方发送速率，保证接收方来得及接收。\n    \n    接收方发送的确认报文中的窗口字段可以用来控制发送方窗口大小，从而影响发送方的发送速率。将窗口字段设置为 0，则发送方不能发送数据。\n\nTCP 拥塞控制\n\n    如果网络出现拥塞，分组将会丢失，此时发送方会继续重传，从而导致网络拥塞程度更高。因此当出现拥塞时，应当控制发送方的速率。这一点和流量控制很像，但是出发点不同。流量控制是为了让接收方能来得及接受，而拥塞控制是为了降低整个网络的拥塞程度。\n    \n    TCP 主要通过四种算法来进行拥塞控制：慢开始、拥塞避免、快重传、快恢复。\n    \n    一般刚开始时慢开始，然后拥塞避免，出现个别丢包时（连续三个包序号不对），\n    \n    则执行快重传，然后进入快恢复阶段，接着继续拥塞避免。如果发生多次超时也就是拥塞时，直接进入慢开始。\n>     \n>这种情况下，只是丢失个别报文段，而不是网络拥塞，因此执行快恢复，令 ssthresh = cwnd/2 ，cwnd = ssthresh，注意到此时直接进入拥塞避免。慢开始和快恢复的快慢指的是 cwnd 的设定值，而不是 cwnd 的增长速率。慢开始 cwnd 设定为 1，而快恢复 cwnd 设定为 ssthresh。\n\n ==发送方需要维护一个叫做拥塞窗口（cwnd）的状态变量，注意拥塞窗口与发送方窗口的区别：拥塞窗口只是一个状态变量，实际决定发送方能发送多少数据的是发送方窗口==。\n\n滑动窗口协议综合实现了上述这一些内容：\n\n为什么要使用滑动窗口，因为滑动窗口可以实现可靠传输，流量控制和拥塞控制（拥塞控制用的是拥塞窗口变量）\n\n### tcp的粘包拆包\n\ntcp报文是流式的数据，没有标识数据结束，只有序号等字段，tcp协议自动完成数据报的切分。由于tcp使用缓冲区发送，又没有标识结束，当缓冲区的数据没清空又有新数据进来，就会发生粘包，如果数据太大存装不下，就会被拆包。\n\n## 网络层\n\n## IP 数据报格式\n\n[![](https://github.com/CyC2018/Interview-Notebook/raw/master/pics/85c05fb1-5546-4c50-9221-21f231cdc8c5.jpg)](https://github.com/CyC2018/Interview-Notebook/blob/master/pics/85c05fb1-5546-4c50-9221-21f231cdc8c5.jpg)\n\n*   **版本** : 有 4（IPv4）和 6（IPv6）两个值；\n\n*   **首部长度** : 占 4 位，因此最大值为 15。\n\n*   **总长度** : 包括首部长度和数据部分长度。\n\n*   **生存时间** ：TTL，它的存在是为了防止无法交付的数据报在互联网中不断兜圈子。以路由器跳数为单位，当 TTL 为 0 时就丢弃数据报。\n\n==*   **协议** ：指出携带的数据应该上交给哪个协议进行处理，例如 ICMP、TCP、UDP 等。==\n\n*   **首部检验和** ：因为数据报每经过一个路由器，都要重新计算检验和，因此检验和不包含数据部分可以减少计算的工作量。\n\n\n*   **片偏移** : 和标识符一起，用于发生分片的情况。片偏移的单位为 8 字节。\n\n[![](https://github.com/CyC2018/Interview-Notebook/raw/master/pics/23ba890e-e11c-45e2-a20c-64d217f83430.png)](https://github.com/CyC2018/Interview-Notebook/blob/master/pics/23ba890e-e11c-45e2-a20c-64d217f83430.png)\n\n总结：\n\n    ip层只保证尽最大努力交付，他所承载的一切都是对路由，转发，已经网络传输最友好的设计。\n    \n    路由器负责记录路由表和转发ip数据报，路由表记录着ip地址和下一跳路由的端口的对应关系。\n    \n    由于路由聚合的缘故，一般用170.177.233.0/24就可以标识好几个网络了。\n    \n    以前会使用A，B，C类地址，和子网，现在直接使用地址聚合，前24位是网络号，后面8位是主机号。\n    \n    ## 某个聚合路由地址划分网络给n台机器，是否符合要求。。\n    \n    要看这个网络中的主机号能否达到n个。\n\n### IP 地址编址方式\n\nIP 地址的编址方式经历了三个历史阶段：\n\n*   分类\n*   子网划分\n*   无分类\n\n### 分类\n\n由两部分组成，网络号和主机号，其中不同分类具有不同的网络号长度，并且是固定的。\n\nIP 地址 ::= {< 网络号 >, < 主机号 >}\n\n[![](https://github.com/CyC2018/Interview-Notebook/raw/master/pics/cbf50eb8-22b4-4528-a2e7-d187143d57f7.png)](https://github.com/CyC2018/Interview-Notebook/blob/master/pics/cbf50eb8-22b4-4528-a2e7-d187143d57f7.png)  \n\n2. 子网划分\n\n\n    通过在主机号字段中拿一部分作为子网号，把两级 IP 地址划分为三级 IP 地址。注意，外部网络看不到子网的存在。\n    \n    IP 地址 ::= {< 网络号 >, < 子网号 >, < 主机号 >}\n    \n    要使用子网，必须配置子网掩码。一个 B 类地址的默认子网掩码为 255.255.0.0，如果 B 类地址的子网占两个比特，那么子网掩码为 11111111 11111111 11000000 00000000，也就是 255.255.192.0。\n\n3. 无分类\n\n\n    无分类编址 CIDR 消除了传统 A 类、B 类和 C 类地址以及划分子网的概念，使用网络前缀和主机号来对 IP 地址进行编码，网络前缀的长度可以根据需要变化。\n    \n    IP 地址 ::= {< 网络前缀号 >, < 主机号 >}\n    \n    CIDR 的记法上采用在 IP 地址后面加上网络前缀长度的方法，例如 128.14.35.7/20 表示前 20 位为网络前缀。\n    \n    CIDR 的地址掩码可以继续称为子网掩码，子网掩码首 1 长度为网络前缀的长度。\n\n一个 CIDR 地址块中有很多地址，一个 CIDR 表示的网络就可以表示原来的很多个网络，并且在路由表中只需要一个路由就可以代替原来的多个路由，减少了路由表项的数量。\n\n把这种通过使用网络前缀来减少路由表项的方式称为路由聚合，也称为 构成超网 。\n\n在路由表中的项目由“网络前缀”和“下一跳地址”组成，在查找时可能会得到不止一个匹配结果，应当采用最长前缀匹配来确定应该匹配哪一个。\n\n总结\n    \n    使用分类法的ip必须标识是哪一类地址，比较麻烦，而且一旦设置为某类地址它就只能使用那一部分地址空间了。\n    \n    使用子网掩码可以避免使用分类并且更灵活地决定网络号和主机号的划分。但是需要配置子网掩码，比较复杂。\n    \n    CIDR 138.1.2.11/24\n    使用CIDR避免了子网划分，直接使用后n位作为网络号，简化了子网的配置（实际上用n代替了子网掩码）。并且在路由器中可以使用地址聚合，一个ip可以聚合多个网络号。\n\n### ip分片详谈\n\n在TCP/IP分层中，数据链路层用MTU（Maximum Transmission Unit，最大传输单元）来限制所能传输的数据包大小，MTU是指一次传送的数据最大长度，不包括数据链路层数据帧的帧头，如以太网的MTU为1500字节，实际上数据帧的最大长度为1512字节，其中以太网数据帧的帧头为12字节。\n\n当发送的IP数据报的大小超过了MTU时，IP层就需要对数据进行分片，否则数据将无法发送成功。\n\nIP分片的实现\n    \n> IP分片发生在IP层，不仅源端主机会进行分片，中间的路由器也有可能分片，因为不同的网络的MTU是不一样的，如果传输路径上的某个网络的MTU比源端网络的MTU要小，路由器就可能对IP数据报再次进行分片。而分片数据的重组只会发生在目的端的IP层。\n\n==避免IP分片==\n> 在网络编程中，我们要避免出现IP分片，那么为什么要避免呢？原因是IP层是没有超时重传机制的，如果IP层对一个数据包进行了分片，只要有一个分片丢失了，只能依赖于传输层进行重传，结果是所有的分片都要重传一遍，这个代价有点大。由此可见，IP分片会大大降低传输层传送数据的成功率，所以我们要避免IP分片。\n>         \n\n    对于UDP包，我们需要在应用层去限制每个包的大小，一般不要超过1472字节，即以太网MTU（1500）—UDP首部（8）—IP首部（20）。\n    \n    对于TCP数据，应用层就不需要考虑这个问题了，因为传输层已经帮我们做了。\n\n在建立连接的三次握手的过程中，连接双方会相互通告MSS（Maximum Segment =Size，最大报文段长度），MSS一般是MTU—IP首部（20）—TCP首部（20），每次发送的TCP数据都不会超过双方MSS的最小值，所以就保证了IP数据报不会超过MTU，避免了IP分片。\n\n3. 外部网关协议 BGP\n\n\n    BGP（Border Gateway Protocol，边界网关协议）\n    \n    AS 之间的路由选择很困难，主要是因为互联网规模很大。并且各个 AS 内部使用不同的路由选择协议，就无法准确定义路径的度量。并且 AS 之间的路由选择必须考虑有关的策略，比如有些 AS 不愿意让其它 AS 经过。\n    \n    BGP 只能寻找一条比较好的路由，而不是最佳路由。\n    \n    每个 AS 都必须配置 BGP 发言人，通过在两个相邻 BGP 发言人之间建立 TCP 连接来交换路由信息。\n\n### 路由选择协议和算法\n> \n> 路由选择协议\n> 路由选择协议都是自适应的，能随着网络通信量和拓扑结构的变化而自适应地进行调整。\n> \n> 互联网可以划分为许多较小的自治系统 AS，一个 AS 可以使用一种和别的 AS 不同的路由选择协议。\n> \n> 可以把路由选择协议划分为两大类：\n> \n> 自治系统内部的路由选择：RIP 和 OSPF\n> 自治系统间的路由选择：BGP\n\n\n总结：\n\n    1. 内部网关协议 RIP\n    RIP 是一种基于距离向量的路由选择协议。距离是指跳数，直接相连的路由器跳数为 1，跳数最多为 15，超过 15 表示不可达。\n    \n    RIP 按固定的时间间隔仅和相邻路由器交换自己的路由表，经过若干次交换之后，所有路由器最终会知道到达本自治系统中任何一个网络的最短距离和下一跳路由器地址。\n    \n    2. 内部网关协议 OSPF\n    开放最短路径优先 OSPF，是为了克服 RIP 的缺点而开发出来的。\n    \n    开放表示 OSPF 不受某一家厂商控制，而是公开发表的；最短路径优先表示使用了 Dijkstra 提出的最短路径算法 SPF。\n\nOSPF 具有以下特点：\n    \n    计算出最短路径，然后向本自治系统中的所有路由器发送信息，这种方法是洪泛法。\n    \n    发送的信息就是与相邻路由器的链路状态，链路状态包括与哪些路由器相连以及链路的度量，度量用费用、距离、时延、带宽等来表示。\n    \n    变化时，路由器才会发送信息。\n        \n    所有路由器都具有全网的拓扑结构图，并且是一致的。相比于 RIP，OSPF 的更新过程收敛的很快。\n\n总结：\n\n    AS是一个自治域，一般是指相似度很大公用一个协议的路由器族，比如同一个运营商的网络。\n    \n    因特网中AS之间的路由选择协议是BGP。\n    \n    AS内的路由选择协议有RIP和OSPF。\n    \n    RIP两两交换，最后大家都同步。\n    \n    OSPF找到最短路径。告诉大家。\n\n## 链路层\n链路层最主要是指局域网内的网络交互了，使用mac地址通过交换机进行通信，其中用得最多的局域网协议就是以太网。\n\n链路层使用MTU表示最大传输帧长度，报文长度不能超过MTU,否则会进行分片，比如比较大的IP数据报就会被分片，为了避免被分片。一般要控制IP报文长度。\n\n广播：\n\n要理解什么是广播风暴，就必须先理解网络通信技术。 网络上的一个节点，它发送一个数据帧或包，被传输到由广播域定义的本地网段上的每个节点就是广播。\n\n> 网络广播分为第2层广播和第3层广播。第2层广播也称硬件广播，用于在局域网内向所有的结点发送数据，通常不会穿过局域网的边界（路由器），除非它变成一个单播。广播将是一个二进制的全1或者十六进制全F的地址。而第3层广播用于在这个网络内向所有的结点发送数据。\n\n帧的传输方式，即单播帧（Unicast Frame）、多播帧（Multicast Frame）和广播帧（Broadcast Frame）。\n\n    1、单播帧\n    单播帧也称“点对点”通信。此时帧的接收和传递只在两个节点之间进行，帧的目的MAC地址就是对方的MAC地址，网络设备（指交换机和路由器）根据帧中的目的MAC地址，将帧转发出去。\n    \n    2、多播帧\n    多播帧可以理解为一个人向多个人（但不是在场的所有人）说话，这样能够提高通话的效率。多播占网络中的比重并不多，主要应用于网络设备内部通信、网上视频会议、网上视频点播等。\n    \n    3、广播帧\n    广播帧可以理解为一个人对在场的所有人说话，这样做的好处是通话效率高，信息一下子就可以传递到全体。在广播帧中，帧头中的目的MAC地址是“FF.FF.FF.FF.FF.FF”，代表网络上所有主机网卡的MAC地址。\n    \n    广播帧在网络中是必不可少的，如客户机通过DHCP自动获得IP地址的过程就是通过广播帧来实现的。而且，由于设备之间也需要相互通信，因此在网络中即使没有用户人为地发送广播帧，网络上也会出现一定数量的广播帧。\n    \n    同单播和多播相比，广播几乎占用了子网内网络的所有带宽。网络中不能长时间出现大量的广播帧，否则就会出现所谓的“广播风暴”（每秒的广播帧数在1000以上）。拿开会打一个比方，在会场上只能有一个人发言，如果所有人都同时发言的话，会场上就会乱成一锅粥。广播风暴就是网络长时间被大量的广播数据包所占用，使正常的点对点通信无法正常进行，其外在表现为网络速度奇慢无比。出现广播风暴的原因有很多，一块故障网卡就可能长时间地在网络上发送广播包而导致广播风暴。\n    \n    使用路由器或三层交换机能够实现在不同子网间隔离广播风暴的作用。当路由器或三层交换机收到广播帧时并不处理它，使它无法再传递到其他子网中，从而达到隔离广播风暴的目的。因此在由几百台甚至上千台电脑构成的大中型局域网中，为了隔离广播风暴，都要进行子网划分。\n    使用vlan完全可以隔离广播风暴。\n\n> 在交换以太网上运行TCP/IP环境下：\n> 二层广播是在数据链路层的广播，它 的广播范围是二层交换机连接的所有端口；二层广播不能通过路由器。\n> \n> 三层广播就是在网络层的广播，它的范围是同一IP子网内的设备，子网广播也不能通过路由器。\n> \n> 第三层的数据必须通过第二层的封装再发送，所以三层广播必然通过二层广播来实现。\n> \n> 设想在同一台二层交换机上连接2个ip子网的设备，所有的设备都可以接收到二层广播，但三层广播只对本子网设备有效，非本子网的设备也会接收到广播包，但会被丢弃。\n\n广播风暴（broadcast storm）\n\n简单的讲是指当广播数据充斥网络无法处理，并占用大量网络带宽，导致正常业务不能运行，甚至彻底瘫痪，这就发生了“广播风暴”\n\n。一个数据帧或包被传输到本地网段 （由广播域定义）上的每个节点就是广播；由于网络拓扑的设计和连接问题，或其他原因导致广播在网段内大量复制，传播数据帧，导致网络性能下降，甚至网络瘫痪，这就是广播风暴。\n\n要避免广播风暴，可以采用恰当划分VLAN、缩小广播域、隔离广播风暴，还可在千兆以太网口上启用广播风暴控制，最大限度地避免网络再次陷入瘫痪。\n\n\n"
  },
  {
    "path": "docs/cs/operating-system/Linux内核与基础命令学习总结.md",
    "content": "# 目录\n\n  * [文件系统和VFS](#文件系统和vfs)\n  * [进程和线程](#进程和线程)\n  * [fork方法](#fork方法)\n  * [写时复制](#写时复制)\n  * [父子进程，僵尸进程，孤儿进程，守护进程](#父子进程，僵尸进程，孤儿进程，守护进程)\n  * [进程组和会话](#进程组和会话)\n  * [守护进程](#守护进程)\n  * [硬连接和软连接](#硬连接和软连接)\n  * [线程](#线程)\n  * [线程模型](#线程模型)\n  * [内核线程实现](#内核线程实现)\n  * [文件系统](#文件系统)\n  * [IO操作](#io操作)\n* [文件描述符](#文件描述符)\n* [write函数](#write函数)\n* [read函数](#read函数)\n* [**read/write的语义：为什么会阻塞？**](#readwrite的语义：为什么会阻塞？)\n  * [Linux常用命令和基础知识](#linux常用命令和基础知识)\n    * [查看进程](#查看进程)\n* [ps -l](#ps--l)\n* [ps aux](#ps-aux)\n* [ps aux | grep threadx](#ps-aux--grep-threadx)\n* [top -d 2](#top--d-2)\n* [pstree -A](#pstree--a)\n* [netstat -anp | grep port](#netstat--anp--grep-port)\n    * [文件操作](#文件操作)\n    * [权限操作](#权限操作)\n  * [连接操作](#连接操作)\n* [ln /etc/crontab .](#ln-etccrontab-)\n* [ll -i /etc/crontab crontab](#ll--i-etccrontab-crontab)\n* [ll -i /etc/crontab /root/crontab2](#ll--i-etccrontab-rootcrontab2)\n  * [获取内容](#获取内容)\n  * [搜索和定位](#搜索和定位)\n* [locate [-ir] keyword](#locate-[-ir]-keyword)\n* [find [basedir] [option]](#find-[basedir]-[option])\n  * [压缩](#压缩)\n  * [管道指令](#管道指令)\n  * [正则](#正则)\n  * [linux指令实践和常见场景](#linux指令实践和常见场景)\n  * [查看进程状态](#查看进程状态)\n  * [strace](#strace)\n  * [tcpdump](#tcpdump)\n  * [nc](#nc)\n  * [curl](#curl)\n  * [lsof](#lsof)\n  * [ss](#ss)\n  * [awk/sed](#awksed)\n  * [vim](#vim)\n  * [crontab](#crontab)\n  * [service](#service)\n  * [free](#free)\n  * [top](#top)\n  * [df](#df)\n  * [kill](#kill)\n  * [mount](#mount)\n  * [chmod](#chmod)\n  * [chown](#chown)\n  * [ifconfig](#ifconfig)\n  * [uname](#uname)\n  * [实际场景问题](#实际场景问题)\n\n\n\n---\ntitle: Linux内核与基础命令学习总结\ndate: 2018-07-09 22:33:14\ntags:\n\t- Linux\ncategories:\n\t- 后端\n\t- 技术总结\n---\n这部分内容主要是基于一些关于Linux系统的内核基础和基本命令的学习总结，内容不全面，只讲述了其中的一小部分，后续会再补充，如有错误，还请见谅。\n\n\nLinux操作系统\n\n\n\nLinux操作系统博大精深，其中对线程，IO，文件系统等概念的实现都很有借鉴意义。\n<!-- more -->\n\n\n​        \n## 文件系统和VFS\n\n文件系统的inode上面讲过了。VFS主要用于屏蔽底层的不同文件系统，比如接入网络中的nfs文件系统，亦或是windows文件系统，正常情况下难以办到，而vfs通过使用IO操作的posix规范来规定所有文件读写操作，每个文件系统只需要实现这些操作就可以接入VFS，不需要重新安装文件系统。\n## 进程和线程\n\n    > 进程、程序与线程\n    > \n    > 程序\n    > \n    >  程序，简单的来说就是存在磁盘上的二进制文件，是可以内核所执行的代码 \n    > \n    > 进程\n    > \n    >  当一个用户启动一个程序，将会在内存中开启一块空间，这就创造了一个进程，一个进程包含一个独一无二的PID，和执行者的权限属性参数，以及程序所需代码与相关的资料。\n    >  进程是系统分配资源的基本单位。\n    >  一个进程可以衍生出其他的子进程，子进程的相关权限将会沿用父进程的相关权限。\n    > \n    > 线程\n    > \n    >  每个进程包含一个或多个线程，线程是进程内的活动单元，是负责执行代码和管理进程运行状态的抽象。\n    >  线程是独立运行和调度的基本单位。\n\n\n\n>  子进程和父进程\n> 进程的层次结构（父进程与子进程）在进程执行的过程中可能会衍生出其他的进程，称之为子进程，子进程拥有一个指明其父进程PID的PPID。子进程可以继承父进程的环境变量和权限参数。\n> \n> 于是，linux系统中就诞生了进程的层次结构——进程树。\n> 进程树的根是第一个进程（init进程）。\n> \n> 过程调用的流程： fork & exec一个进程生成子进程的过程是，系统首先复制(fork)一份父进程，生成一个暂存进程，这个暂存进程和父进程的区别是pid不一样，而且拥有一个ppid，这时候系统再去执行(exec)这个暂存进程，让他加载实际要运行的程序，最终成为一个子进程的存在。\n> \n> 服务与进程\n> \n> 简单的说服务(daemon)就是常驻内存的进程，通常服务会在开机时通过init.d中的一段脚本被启动。\n> \n> 进程通信\n> \n> 进程通信的几种基本方式：管道，信号量，消息队列，共享内存，快速用户控件互斥。 \n> \n## fork方法\n  一个进程，包括代码、数据和分配给进程的资源。fork（）函数通过系统调用创建一个与原来进程几乎完全相同的进程，\n\n也就是两个进程可以做完全相同的事，但如果初始参数或者传入的变量不同，两个进程也可以做不同的事。\n\n\n    一个进程调用fork（）函数后，系统先给新的进程分配资源，例如存储数据和代码的空间。然后把原来的进程的所有值都\n\n复制到新的新进程中，只有少数值与原来的进程的值不同。相当于克隆了一个自己。\n\n    fork调用的一个奇妙之处就是它仅仅被调用一次，却能够返回两次，它可能有三种不同的返回值：\n        1）在父进程中，fork返回新创建子进程的进程ID；\n        2）在子进程中，fork返回0；\n        3）如果出现错误，fork返回一个负值；\n\n如何理解pid在父子进程中不同？\n\n其实就相当于链表，进程形成了链表，父进程的pid指向了子进程的pid，因为子进程没有子进程，所以pid为0。\n\n## 写时复制\n\n    传统的fork机制是，调用fork时，内核会复制所有的内部数据结构，复制进程的页表项，然后把父进程的地址空间按页复制给子进程（非常耗时）。\n    \n    现代的fork机制采用了一种惰性算法的优化策略。\n    \n    为了避免复制时系统开销，就尽可能的减少“复制”操作，当多个进程需要读取他们自己那部分资源的副本时，并不复制多个副本出来，而是为每个进程设定一个文件指针，让它们读取同一个实际文件。\n    \n    显然这样的方式会在写入时产生冲突（类似并发），于是当某个进程想要修改自己的那个副本时，再去复制该资源，（只有写入时才复制，所以叫写时复制）这样就减少了复制的频率。\n\n## 父子进程，僵尸进程，孤儿进程，守护进程\n\n父进程通过fork产生子进程。\n\n孤儿进程：当子进程未结束时父进程异常退出，原本需要由父进程进行处理的子进程变成了孤儿进程，init系统进程会把这些进程领养，避免他们成为孤儿。\n\n僵尸进程：当子进程结束时，会在内存中保留一部分数据结构等待父亲进程显式结束，如果父进程没有执行结束操作，则会导致子进程的剩余结构无法被释放，占用空间造成严重后果。\n\n守护进程：守护进程用于监控其他进程，当发现大量僵尸进程时，会找到他们的父节点并杀死，同时让init线程认养他们以便释放这些空间。\n\n僵尸进程是有害的，孤儿进程由于内核进程的认养不会造成危害。\n\n## 进程组和会话\n\n> 会话和进程组进程组每个进程都属于某个进程组，进程组就是由一个或者多个为了实现作业控制而相互关联的进程组成的。\n> \n> 一个进程组的id是进程组首进程的pid（如果一个进程组只有一个进程，那进程组和进程其实没啥区别）。\n> \n> 进程组的意义在于，信号可以发送给进程组中的所有进程。这样可以实现对多个进程的同时操作。\n> 会话会话是一个或者多个进程组的集合。\n> \n> 一般来说，会话(session)和shell没有什么本质上的区别。\n> 我们通常使用用户登录一个终端进行一系列操作这样的例子来描述一次会话。\n\n举例\n\n$cat ship-inventory.txt | grep\n\nbooty|sort上面就是在某次会话中的一个shell命令，它会产生一个由3个进程组成的进程组。\n## 守护进程\n守护进程（服务）守护进程(daemon)运行在后台，不与任何控制终端相关联。通常在系统启动时通过init脚本被调用而开始运行。\n\n在linux系统中，守护进程和服务没有什么区别。\n对于一个守护进程，有两个基本的要求：其一：必须作为init进程的子进程运行，其二：不与任何控制终端交互。\n\n\n## 硬连接和软连接\n\n硬链接指的是不同的文件名指向同一个inode节点，比如某个目录下的a和另一个目录下的b，建立一个软连接让a指向b，则a和b共享同一个inode。\n\n软连接是指一个文件的inode节点不存数据，而是存储着另一个文件的绝对路径，访问文件内容时实际上是去访问对应路径下的文件inode，这样的话文件发生改动或者移动都会导致软连接失效。\n\n## 线程\n\n线程基础概念线程是进程内的执行单元（比进程更低一层的概念），具体包括 虚拟处理器，堆栈，程序状态等。\n可以认为 线程是操作系统调度的最小执行单元。\n\n现代操作系统对用户空间做两个基础抽象:虚拟内存和虚拟处理器。这使得进程内部“感觉”自己独占机器资源。\n\n虚拟内存系统会为每个进程分配独立的内存空间，这会让进程以为自己独享全部的RAM。\n\n但是同一个进程内的所有线程共享该进程的内存空间。\n虚拟处理器这是一个针对线程的概念，它让每个线程都“感觉”自己独享CPU。实际上对于进程也是一样的。\n\n## 线程模型\n\n线程模型线程的概念同时存在于内核和用户空间中。下面介绍三种线程模型。\n\n    内核级线程模型每个内核线程直接转换成用户空间的线程。即内核线程：用户空间线程=1：1\n    \n    用户级线程模型这种模型下，一个保护了n个线程的用户进程只会映射到一个内核进程。即n:1。\n    可以减少上下文切换的成本，但在linux下没什么意义，因为linux下进程间的上下文切换本身就没什么消耗，所以很少使用。\n    \n    混合式线程模型上述两种模型的混合，即n:m型。\n    很难实现。\n## 内核线程实现\n\n系统线程实现：PThreads\n原始的linux系统调用中，没有像C++11或者是Java那样完整的线程库。\n\n整体看来pthread的api比较冗余和复杂，但是基本操作也主要是 创建、退出等。\n\n1.创建线程\n\n      int pthread_create\n      \n      (若线程创建成功，则返回0。若线程创建失败，则返回出错编号)\n      \n    　　注意:线程创建者和新建线程之间没有fork()调用那样的父子关系，它们是对等关系。调用pthread_create()创建线程后，线程创建者和新建线程哪个先运行是不确定的，特别是在多处理机器上。\n\n2.终止线程\n    \n      void pthread_exit(void *value_ptr);\n      \n         线程调用pthread_exit()结束自己，参数value_ptr作为线程的返回值被调用pthread_join的线程使用。由于一个进程中的多个线程是共享数据段的，因此通常在线程退出之后，退出线程所占用的资源并不会随着线程的终止而得到释放，但是可以用pthread_join()函数来同步并释放资源\n\n\n3.取消线程\n\n      int pthread_cancel(pthread_t thread);\n      \n    　　注意：若是在整个程序退出时，要终止各个线程，应该在成功发送 CANCEL指令后，使用 pthread_join函数，等待指定的线程已经完全退出以后，再继续执行；否则，很容易产生 “段错误”。\n\n4.连接线程（阻塞）\n\n      int pthread_join(pthread_t thread, void **value_ptr);\n      \n    　　等待线程thread结束，并设置*value_ptr为thread的返回值。pthread_join阻塞调用者，一直到线程thread结束为止。当函数返回时，被等待线程的资源被收回。如果进程已经结束，那么该函数会立即返回。并且thread指定的线程必须是joinable的。\n    \n    需要留意的一点是linux机制下，线程存在一个被称为joinable的状态。下面简要了解一下：\n\nJoin和Detach\n这块的概念，非常类似于之前父子进程那部分，等待子进程退出的内容（一系列的wait函数）。\n\nlinux机制下，线程存在两种不同的状态：joinable和unjoinable。\n\n    如果一个线程被标记为joinable时，即便它的线程函数执行完了，或者使用了pthread_exit()结束了该线程，它所占用的堆栈资源和进程描述符都不会被释放（类似僵尸进程），这种情况应该由线程的创建者调用pthread_join()来等待线程的结束并回收其资源（类似wait系函数）。默认情况下创建的线程都是这种状态。\n    \n    如果一个线程被标记成unjoinable，称它被分离(detach)了，这时候如果该线程结束，所有它的资源都会被自动回收。省去了给它擦屁股的麻烦。\n    \n    因为创建的线程默认都是joinable的，所以要么在父线程调用pthread_detach(thread_id)将其分离，要么在线程内部，调用pthread_detach(pthread_self())来把自己标记成分离的。\n\n\n\n## 文件系统\n\n    文件描述符在linux内核中，文件是用一个整数来表示的，称为 文件描述符，通俗的来说，你可以理解它是文件的id（唯一标识符）\n    \n    普通文件\n    普通文件就是字节流组织的数据。\n    文件并不是通过和文件名关联来实现的，而是通过关联索引节点来实现的，文件节点拥有文件系统为普通文件分配的唯一整数值(ino)，并且存放着一些文件的相关元数据。\n    \n    目录与链接\n    正常情况下文件是通过文件名来打开的。\n    目录是可读名称到索引编号之间的映射，名称和索引节点之间的配对称为链接。\n    可以把目录看做普通文件，只是它包含着文件名称到索引节点的映射（链接）\n\n\n\n文件系统是基于底层存储建立的一个树形文件结构。比较经典的是Linux的文件系统，首先在硬盘的超级块中安装文件系统，磁盘引导时会加载文件系统的信息。\n\nlinux使用inode来标识任意一个文件。inode存储除了文件名以外的文件信息，包括创建时间，权限，以及一个指向磁盘存储位置的指针，那里才是真正存放数据的地方。\n\n一个目录也是一个inode节点。\n\n详细阐述一次文件访问的过程：\n    \n    首先用户ls查看目录。由于一个目录也是一个文件，所以相当于是看目录文件下有哪些东西。\n    \n    实际上目录文件是一个特殊的inode节点，它不需要存储实际数据，而只是维护一个文件名到inode的映射表。\n    \n    于是我们ls到另一个目录。同理他也是一个inode。我们在这个inode下执行vi操作打开某个文件，于是linux通过inode中的映射表找到了我们请求访问的文件名对应的inode。\n    \n    然后寻道到对应的磁盘位置，读取内容到缓冲区，通过系统调用把内容读到内存中，最后进行访问。\n## IO操作\n\n# 文件描述符\n\n　　对于内核而言，所有打开的文件都通过文件描述符引用。文件描述符是一个非负整数。当打开一个现有文件或创建一个新文件时，内核向进程返回一个文件描述符。当读或写一个文件时，使用open或create返回的文件描述符表示该文件，将其作为参数传给read或write函数。\n\n# write函数\n\n 　　write函数定义如下：\n\n\n\n#include <unistd> ssize_t write(int filedes, void *buf, size_t nbytes); // 返回：若成功则返回写入的字节数，若出错则返回-1 // filedes：文件描述符 // buf:待写入数据缓存区 // nbytes:要写入的字节数目录\n\n\n\n　　同样，为了保证写入数据的完整性，在《UNIX网络编程 卷1》中，作者将该函数进行了封装，具体程序如下：\n\n![](http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)\n\n\n\n![复制代码](http://common.cnblogs.com/images/copycode.gif)\n\n 1 ssize_t                        /* Write \"n\" bytes to a descriptor. */\n 2 writen(int fd, const void *vptr, size_t n)\n 3 {\n 4     size_t nleft;\n 5     ssize_t nwritten;\n 6     const char *ptr;\n 7 \n 8     ptr = vptr; 9     nleft = n; 10     while (nleft > 0) { 11         if ( (nwritten = write(fd, ptr, nleft)) <= 0) { 12             if (nwritten < 0 && errno == EINTR) 13                 nwritten = 0;        /* and call write() again */\n14             else\n15                 return(-1);            /* error */\n16 } 17 \n18         nleft -= nwritten; 19         ptr   += nwritten; 20 } 21     return(n); 22 } 23 /* end writen */\n24 \n25 void\n26 Writen(int fd, void *ptr, size_t nbytes) 27 { 28     if (writen(fd, ptr, nbytes) != nbytes) 29         err_sys(\"writen error\"); 30 }目录\n\n![复制代码](http://common.cnblogs.com/images/copycode.gif)\n\n\n\n\n\n# read函数\n\n　　read函数定义如下：\n\n\n\n#include <unistd> ssize_t read(int filedes, void *buf, size_t nbytes); // 返回：若成功则返回读到的字节数，若已到文件末尾则返回0，若出错则返回-1 // filedes：文件描述符 // buf:读取数据缓存区 // nbytes:要读取的字节数目录\n\n\n\n 　　有几种情况可使实际读到的字节数少于要求读的字节数：\n\n　　1）读普通文件时，在读到要求字节数之前就已经达到了文件末端。例如，若在到达文件末端之前还有30个字节，而要求读100个字节，则read返回30，下一次再调用read时，它将返回0（文件末端）。\n\n　　2）当从终端设备读时，通常一次最多读一行。\n\n　　3）当从网络读时，网络中的缓存机构可能造成返回值小于所要求读的字结束。\n\n　　4）当从管道或FIFO读时，如若管道包含的字节少于所需的数量，那么read将只返回实际可用的字节数。\n\n　　5）当从某些面向记录的设备（例如磁带）读时，一次最多返回一个记录。\n\n　　6）当某一个信号造成中断，而已经读取了部分数据。\n\n　　在《UNIX网络编程 卷1》中，作者将该函数进行了封装，以确保数据读取的完整，具体程序如下：\n\n![](http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)\n\n\n\n![复制代码](http://common.cnblogs.com/images/copycode.gif)\n\n 1 ssize_t                        /* Read \"n\" bytes from a descriptor. */\n 2 readn(int fd, void *vptr, size_t n)\n 3 {\n 4     size_t nleft;\n 5     ssize_t nread;\n 6     char *ptr;\n 7 \n 8     ptr = vptr; 9     nleft = n; 10     while (nleft > 0) { 11         if ( (nread = read(fd, ptr, nleft)) < 0) { 12             if (errno == EINTR) 13                 nread = 0;        /* and call read() again */\n14             else\n15                 return(-1); 16         } else if (nread == 0) 17             break;                /* EOF */\n18 \n19         nleft -= nread; 20         ptr   += nread; 21 } 22     return(n - nleft);        /* return >= 0 */\n23 } 24 /* end readn */\n25 \n26 ssize_t 27 Readn(int fd, void *ptr, size_t nbytes) 28 { 29 ssize_t        n; 30 \n31     if ( (n = readn(fd, ptr, nbytes)) < 0) 32         err_sys(\"readn error\"); 33     return(n); 34 }目录\n\n![复制代码](http://common.cnblogs.com/images/copycode.gif)\n\n\n\n\n\n本文下半部分摘自博文[浅谈TCP/IP网络编程中socket的行为](http://www.cnblogs.com/promise6522/archive/2012/03/03/2377935.html)。\n\n# **read/write的语义：为什么会阻塞？**\n\n　　先从write说起：\n\n\n\n#include <unistd.h> ssize_t write(int fd, const void *buf, size_t count);目录\n\n\n\n　　首先，write成功返回，**只是buf中的数据被复制到了kernel中的TCP发送缓冲区。**至于数据什么时候被发往网络，什么时候被对方主机接收，什么时候被对方进程读取，系统调用层面不会给予任何保证和通知。\n\n　　write在什么情况下会阻塞？当kernel的该socket的发送缓冲区已满时。对于每个socket，拥有自己的send buffer和receive buffer。从Linux 2.6开始，两个缓冲区大小都由系统来自动调节（autotuning），但一般在default和max之间浮动。\n\n\n\n# 获取socket的发送/接受缓冲区的大小：（后面的值是在Linux 2.6.38 x86_64上测试的结果）目录\n\nsysctl net.core.wmem_default       #126976\nsysctl net.core.wmem_max　　　　    #131071目录\n\n\n\n　　已经发送到网络的数据依然需要暂存在send buffer中，只有收到对方的ack后，kernel才从buffer中清除这一部分数据，为后续发送数据腾出空间。接收端将收到的数据暂存在receive buffer中，自动进行确认。但如果socket所在的进程不及时将数据从receive buffer中取出，最终导致receive buffer填满，由于TCP的滑动窗口和拥塞控制，接收端会阻止发送端向其发送数据。这些控制皆发生在TCP/IP栈中，对应用程序是透明的，应用程序继续发送数据，最终导致send buffer填满，write调用阻塞。\n\n　　一般来说，由于**接收端进程从socket读数据的速度**跟不上**发送端进程向socket写数据的速度**，最终导致**发送端write调用阻塞。**\n\n　　而read调用的行为相对容易理解，从socket的receive buffer中拷贝数据到应用程序的buffer中。read调用阻塞，通常是发送端的数据没有到达。\n\n## Linux常用命令和基础知识\n\n### 查看进程\n\n    1. ps\n    查看某个时间点的进程信息\n    \n    示例一：查看自己的进程\n    \n    # ps -l\n    示例二：查看系统所有进程\n    \n    # ps aux\n    示例三：查看特定的进程\n    \n    # ps aux | grep threadx\n    \n    2. top\n    实时显示进程信息\n    \n    示例：两秒钟刷新一次\n    \n    # top -d 2\n    3. pstree\n    查看进程树\n    \n    示例：查看所有进程树\n    \n    # pstree -A\n    4. netstat\n    查看占用端口的进程\n    \n    示例：查看特定端口的进程\n    \n    # netstat -anp | grep port\n\n### 文件操作\nls -a  ,all列出全部文件包括隐藏\n\nls -l，list显示文件的全部属性\n\nls -d,仅列出目录本身\n\ncd mkdir rmdir 常用不解释 rm -rf永久删除 cp复制 mv移动或改名\n\ntouch，更新文件时间或者建立新文件。\n\n\n### 权限操作\n\n    chmod rwx 分别对应 421\n    \n     chmod 754 .bashrc 将权限改为rwxr-xr--\n    \n    对应权限分配是对于 拥有者，所属群组，以及其他人。\n\n\n文件默认权限\n\n文件默认权限：文件默认没有可执行权限，因此为 666，也就是 -rw-rw-rw- 。\n\n目录默认权限：目录必须要能够进入，也就是必须拥有可执行权限，因此为 777 ，也就是 drwxrwxrwx。\n\n\n目录的权限\n\nps:拥有目录权限才能修改文件名，拥有文件权限是没用的\n\n    文件名不是存储在一个文件的内容中，而是存储在一个文件所在的目录中。因此，拥有文件的 w 权限并不能对文件名进行修改。\n\n目录存储文件列表，一个目录的权限也就是对其文件列表的权限。因此，目录的 r 权限表示可以读取文件列表；w 权限表示可以修改文件列表，具体来说，就是添加删除文件，对文件名进行修改；x 权限可以让该目录成为工作目录，x 权限是 r 和 w 权限的基础，如果不能使一个目录成为工作目录，也就没办法读取文件列表以及对文件列表进行修改了。\n\n\n## 连接操作\n\n\n硬链接：\n\n    使用ln建立了一个硬连接，通过ll -i获得他们的inode节点。发现他们的inode节点是相同的。符合硬连接规定。\n\n\n​    \n    # ln /etc/crontab .\n    # ll -i /etc/crontab crontab\n    \n    34474855 -rw-r--r--. 2 root root 451 Jun 10 2014 crontab\n    34474855 -rw-r--r--. 2 root root 451 Jun 10 2014 /etc/crontab\n\n软连接：\n\n    符号链接文件保存着源文件所在的绝对路径，在读取时会定位到源文件上，可以理解为 Windows 的快捷方式。\n    \n    当源文件被删除了或者被移动到其他位置了，链接文件就打不开了。\n    \n    可以为目录建立链接。\n    \n    # ll -i /etc/crontab /root/crontab2\n    \n    34474855 -rw-r--r--. 2 root root 451 Jun 10 2014 /etc/crontab\n    53745909 lrwxrwxrwx. 1 root root 12 Jun 23 22:31 /root/crontab2 -> /etc/crontab\n\n## 获取内容\n\ncat 读取内容 加上-n 按行打印\n\ntac是cat的反向操作\n\nmore允许翻页查看，而不像cat一次显示全部内容\n\nless可以先前翻页和向后翻页，more只能向前翻页\n\nhead 和tail 负责取得文件的前几行和后几行\n\n## 搜索和定位\n\n    1 which负责指令搜索，并显示第一条 比如which pwd，会找到pwd对应的程序。加-a 打印全部。\n    \n    2 whereis负责搜索文件， 后面接上dirname/filename\n    \n    文件搜索。速度比较快，因为它只搜索几个特定的目录。\n    比如 whereis /bin hello.c\n    \n    3 locate\n    文件搜索。可以用关键字或者正则表达式进行搜索。\n    \n    locate 使用 /var/lib/mlocate/ 这个数据库来进行搜索，它存储在内存中，并且每天更新一次，所以无法用 locate 搜索新建的文件。可以使用 updatedb 来立即更新数据库。\n    \n    # locate [-ir] keyword\n    -r：正则表达式\n    \n    locate hello \n    locate he*\n    vi heeee\n    updatedb\n    locate he?\n    \n    4. find\n    文件搜索。可以使用文件的属性和权限进行搜索。\n    \n    # find [basedir] [option]\n    example: find . -name \"shadow*\"\n    \n    find -name \"hike\"\n    find +属性后缀 \"属性\"\n    \n    （一）与时间有关的选项\n    \n    -mtime  n ：列出在 n 天前的那一天修改过内容的文件\n    \n    （二）与文件拥有者和所属群组有关的选项\n    \n    -uid n\n    -gid n\n    -user name\n    \n    （三）与文件权限和名称有关的选项\n    \n    -name filename\n    -size [+-]SIZE：搜寻比 SIZE 还要大 (+) 或小 (-) 的文件。这个 SIZE 的规格有：c: 代表 byte，k: 代表 1024bytes。所以，要找比 50KB 还要大的文件，就是 -size +50k\n    -type TYPE\n\n## 压缩\n    gzip压缩和解压，还有bzip，xz等压缩\n    \n    而tar可以用打包，打包的时候也可以执行压缩\n    \n    压缩指令只能对一个文件进行压缩，而打包能够将多个文件打包成一个大文件。tar 不仅可以用于打包，也可以使用 gip、bzip2、xz 将打包文件进行压缩。\n    \n    $ tar [-z|-j|-J] [cv] [-f 新建的 tar 文件] filename...  ==打包压缩\n    $ tar [-z|-j|-J] [tv] [-f 已有的 tar 文件]              ==查看\n    $ tar [-z|-j|-J] [xv] [-f 已有的 tar 文件] [-C 目录]    ==解压缩\n\n\n## 管道指令\n\n1 |\n\n2 cut切分数据，分成多列，last显示登陆者信息\n\n## 正则\n\n\ngrep\n\n    g/re/p（globally search a regular expression and print)，使用正则表示式进行全局查找并打印。\n    \n    $ grep [-acinv] [--color=auto] 搜寻字符串 filename\n    -c ： 计算找到个数\n    -i ： 忽略大小写\n    -n ： 输出行号\n    -v ： 反向选择，亦即显示出没有 搜寻字符串 内容的那一行\n    --color=auto ：找到的关键字加颜色显示\n\nawk\n\n    $ awk '条件类型 1 {动作 1} 条件类型 2 {动作 2} ...' filename\n    示例 2：/etc/passwd 文件第三个字段为 UID，对 UID 小于 10 的数据进行处理。\n    \n    $ cat /etc/passwd | awk 'BEGIN {FS=\":\"} $3 < 10 {print $1 \"\\t \" $3}'\n    root 0\n    bin 1\n    daemon 2\n    sed\n    \n    示例 3：输出正在处理的行号，并显示每一行有多少字段\n    \n    $ last -n 5 | awk '{print $1 \"\\t lines: \" NR \"\\t columns: \" NF}'\n    dmtsai lines: 1 columns: 10\n    dmtsai lines: 2 columns: 10\n    dmtsai lines: 3 columns: 10\n    dmtsai lines: 4 columns: 10\n    dmtsai lines: 5 columns: 9\n\nsed:\n\n    awk用于匹配每一行中的内容并打印\n    而sed负责把文件内容重定向到输出，所以sed读取完文件并重定向到输出并且通过awk匹配这些内容并打印。\n    \n    他们俩经常搭配使用。\n\n## linux指令实践和常见场景\n\n## 查看进程状态\n\nLinux进程状态(ps stat)之R、S、D、T、Z、X\n\n\n\n    D    不可中断     Uninterruptible sleep (usually IO)\n    R    正在运行，或在队列中的进程\n    S    处于休眠状态\n    T    停止或被追踪\n    Z    僵尸进程\n    W    进入内存交换（从内核2.6开始无效）\n    X    死掉的进程\n\n\n    <    高优先级\n    N    低优先级\n    L    有些页被锁进内存\n    s    包含子进程\n    +    位于后台的进程组；\n    l    多线程，克隆线程  multi-threaded (using CLONE_THREAD, like NPTL pthreads do)\n\n\nps aux\n\n![](https://img-blog.csdn.net/20180703221736541?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2E3MjQ4ODg=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)\n\n## strace\nstrace用于跟踪程序执行过程中的系统调用，如跟踪test进程，只需要：\n\nstrace -p [test_pid] 或直接strace ./test\n\n比如，跟踪pid为12345的进程中所有线程的read和write系统调用，输出字符串的长度限制为1024：\n\nstrace -s 1024 -f -e trace=read,write -p 12345\n\n## tcpdump\ntcpdump是Linux上的抓包工具，如抓取eth0网卡上的包，使用:\n\nsudo tcpdump -i eth0\n\n比如，抓取80端口的HTTP报文，以文本形式展示：\n\nsudo tcpdump -i any port 80 -A\n这样你就可以清楚看到GET、POST请求的内容了。\n\n## nc\n\nnc可以在Linux上开启TCP Server、TCP Client、UDP Server、UDP Client。\n\n如在端口号12345上开启TCP Server和Client模拟TCP通信：\n\nServer:  nc -l 127.0.0.1 12345\nClient:  nc 127.0.0.1 12345 \n在端口号12345上开启UDP Server和Client模拟TCP通信：\n\nServer:  nc -ul 127.0.0.1 12345\nClient:  nc -u 127.0.0.1 12345\nUnix Socket通信示例:\n\nServer:  nc -Ul /tmp/1.sock\nClient:  nc -U /tmp/1.sock\n\n## curl\ncurl用于模拟HTTP请求，在终端模拟请求时常用，如最基本的用法：\n\ncurl http://www.baidu.com\n\n## lsof\n\nlsof命令主要用法包括：\n\nsudo lsof -i :[port] 查看端口占用进程信息，经常用于端口绑定失败时确认端口被哪个进程占用\n\nsudo lsof -p [pid] 查看进程打开了哪些文件或套接字\n\n## ss\nLinux上的ss命令可以用于替换netstat，ss直接读取解析/proc/net下的统计信息，相比netstat遍历/proc下的每个PID目录，速度快很多。\n\n## awk/sed\nawk和sed在文本处理方面十分强大，其中，awk按列进行处理，sed按行进行处理。\n\n如采用冒号分隔数据，输出第一列数据（$0代表行全部列数据，$1代表第一列，$2代表第二列...）\n\nawk -F \":\" '{print $1}'\n在awk的结果基础上，结合sort、uniq和head等命令可以轻松完成频率统计等功能\n\n查看文件的第100行到第200行：\nsed -n '100,200p' log.txt\n替换字符串中的特定子串\necho \"int charset=gb2312 float\"|sed \"s/charset=gb2312/charset=UTF-8/g\"\n替换test文件每行匹配ab的部分为cd\nsed -i 's/ab/cd/g' test\n\n## vim\n打开文件并跳到第10行\n\n$ vim +10 filename.txt\n打开文件跳到第一个匹配的行\n\n$ vim +/search-term filename.txt\n以只读模式打开文件\n\n$ vim -R /etc/passwd\n\n## crontab\n查看某个用户的crontab入口\n\n$ crontab -u john -l\n设置一个每十分钟执行一次的计划任务\n\n*/10 * * * * /home/ramesh/check-disk-space\n更多示例：Linux Crontab: 15 Awesome Cron Job Examples\n\n## service\nservice命令用于运行System V init脚本，这些脚本一般位于/etc/init.d文件下，这个命令可以直接运行这个文件夹里面的脚本，而不用加上路径\n\n查看服务状态\n\n$ service ssh status\n查看所有服务状态\n\n$ service --status-all\n重启服务\n\n$ service ssh restart\n\n## free\n这个命令用于显示系统当前内存的使用情况，包括已用内存、可用内存和交换内存的情况\n\n默认情况下free会以字节为单位输出内存的使用量\n\n    $ free\n                 total       used       free     shared    buffers     cached\n    Mem:       3566408    1580220    1986188          0     203988     902960\n    -/+ buffers/cache:     473272    3093136\n    Swap:      4000176          0    4000176\n\n如果你想以其他单位输出内存的使用量，需要加一个选项，-g为GB，-m为MB，-k为KB，-b为字节\n\n    $ free -g\n                 total       used       free     shared    buffers     cached\n    Mem:             3          1          1          0          0          0\n    -/+ buffers/cache:          0          2\n    Swap:            3          0          3\n\n如果你想查看所有内存的汇总，请使用-t选项，使用这个选项会在输出中加一个汇总行\n    \n    ramesh@ramesh-laptop:~$ free -t\n                 total       used       free     shared    buffers     cached\n    Mem:       3566408    1592148    1974260          0     204260     912556\n    -/+ buffers/cache:     475332    3091076\n    Swap:      4000176          0    4000176\n    Total:     7566584    1592148    5974436\n\n## top\n\ntop命令会显示当前系统中占用资源最多的一些进程（默认以CPU占用率排序）如果你想改变排序方式，可以在结果列表中点击O（大写字母O）会显示所有可用于排序的列，这个时候你就可以选择你想排序的列\n\n    Current Sort Field:  P  for window 1:Def\n    Select sort field via field letter, type any other key to return\n    \n      a: PID        = Process Id              v: nDRT       = Dirty Pages count\n      d: UID        = User Id                 y: WCHAN      = Sleeping in Function\n      e: USER       = User Name               z: Flags      = Task Flags\n      ........\n\n如果只想显示某个特定用户的进程，可以使用-u选项\n\n$ top -u oracle\n\n## df\n显示文件系统的磁盘使用情况，默认情况下df -k 将以字节为单位输出磁盘的使用量\n\n$ df -k\n\n    Filesystem           1K-blocks      Used Available Use% Mounted on\n    /dev/sda1             29530400   3233104  24797232  12% /\n    /dev/sda2            120367992  50171596  64082060  44% /home\n\n使用-h选项可以以更符合阅读习惯的方式显示磁盘使用量\n\n$ df -h\n\n    Filesystem                  Size   Used  Avail Capacity  iused      ifree %iused  Mounted on\n    /dev/disk0s2               232Gi   84Gi  148Gi    37% 21998562   38864868   36%   /\n    devfs                      187Ki  187Ki    0Bi   100%      648          0  100%   /dev\n    map -hosts                   0Bi    0Bi    0Bi   100%        0          0  100%   /net\n    map auto_home                0Bi    0Bi    0Bi   100%        0          0  100%   /home\n    /dev/disk0s4               466Gi   45Gi  421Gi    10%   112774  440997174    0%   /Volumes/BOOTCAMP\n    //app@izenesoft.cn/public  2.7Ti  1.3Ti  1.4Ti    48% \n\n## kill\nkill用于终止一个进程。一般我们会先用ps -ef查找某个进程得到它的进程号，然后再使用kill -9 进程号终止该进程。你还可以使用killall、pkill、xkill来终止进程\n\n$ ps -ef | grep vim\nramesh    7243  7222  9 22:43 pts/2    00:00:00 vim\n\n$ kill -9 7243\n\n## mount\n如果要挂载一个文件系统，需要先创建一个目录，然后将这个文件系统挂载到这个目录上\n\nmkdir /u01\nmount /dev/sdb1 /u01\n也可以把它添加到fstab中进行自动挂载，这样任何时候系统重启的时候，文件系统都会被加载\n\n/dev/sdb1 /u01 ext2 defaults 0 2\n## chmod\nchmod用于改变文件和目录的权限\n\n给指定文件的属主和属组所有权限(包括读、写、执行)\n\n$ chmod ug+rwx file.txt\n删除指定文件的属组的所有权限\n\n$ chmod g-rwx file.txt\n修改目录的权限，以及递归修改目录下面所有文件和子目录的权限\n\n$ chmod -R ug+rwx file.txt\n更多示例：7 Chmod Command Examples for Beginners\n\n## chown\nchown用于改变文件属主和属组\n\n同时将某个文件的属主改为oracle，属组改为db\n\n$ chown oracle:dba dbora.sh\n使用-R选项对目录和目录下的文件进行递归修改\n\n$ chown -R oracle:dba /home/oracle\n\n## ifconfig\nifconfig用于查看和配置Linux系统的网络接口\n\n## uname\nuname可以显示一些重要的系统信息，例如内核名称、主机名、内核版本号、处理器类型之类的信息 \n\n## 实际场景问题\n\n    1 cpu占用率\n    \n    top可以看\n    ps看不了\n    但是ps -aux可以看到各个线程的cpu和内存占用\n    \n    2 进程状态：\n    \n    ps -ef看不了\n    ps aux可以看进程状态S R之类\n    \n    3 IO\n    iostat查看io状态\n    \n    4网络\n    netstat查看tcp连接状态和socket情况，\n    \n    ipconfig查看网络设备\n    \n    lsof可以查看端口使用情况\n    \n    5内存\n    free\n\n"
  },
  {
    "path": "docs/cs/operating-system/操作系统学习总结.md",
    "content": "# 目录\n\n  * [CPU](#cpu)\n    * [程序执行过程](#程序执行过程)\n    * [高速缓存 读写缓冲区](#高速缓存-读写缓冲区)\n  * [内存管理和虚拟内存](#内存管理和虚拟内存)\n    * [分页](#分页)\n    * [虚拟内存](#虚拟内存)\n    * [页表和页面置换算法](#页表和页面置换算法)\n    * [中断和缺页中断](#中断和缺页中断)\n    * [分页和分段](#分页和分段)\n  * [进程与线程](#进程与线程)\n    * [进程](#进程)\n    * [多进程](#多进程)\n    * [线程](#线程)\n    * [多线程](#多线程)\n    * [线程通信和进程通信](#线程通信和进程通信)\n    * [进程调度](#进程调度)\n    * [死锁](#死锁)\n  * [IO和磁盘](#io和磁盘)\n    * [磁盘和寻址](#磁盘和寻址)\n    * [IO设备](#io设备)\n    * [文件系统](#文件系统)\n\n\n\n---\ntitle: 操作系统学习总结\ndate: 2018-07-09 22:33:03\ntags:\n\t- 操作系统\ncategories:\n\t- 后端\n\t- 技术总结\n---\n这部分内容主要是基于一些关于操作系统基础的学习总结，内容不全面，只讲述了其中的一小部分，后续会再补充，如有错误，还请见谅。\n操作系统\n<!-- more -->\n\n\n## CPU\ncpu是中央处理器，他是计算机的核心。\ncpu通过和寄存器，高速缓存，以及内存交互来执行程序。\n\n主板分为南桥和北桥，北桥主要是内存总线，通往内存。\n而南桥主要是慢速设备的IO总线，包括硬盘，网卡等IO设备。\n\n32位cpu最多寻址4g内存，而64位cpu目前来说没有上限。\n\n### 程序执行过程\ncpu发出指令，将硬盘上的一段程序读入内存，由于cpu和硬盘的速度差距更大，一般使用中断，dma等方式来将硬盘数据载入内存。\n然后cpu通过寄存器以及指令集执行指令，cpu读取内存上的代码，内存上的方法执行是一个栈调用的过程。\n\n### 高速缓存 读写缓冲区\n为了弥补cpu和内存的速度差，cpu配有多级缓存。\n\n一般有一级缓存和二级缓存，缓存根据局部性原理把经常使用的代码加载如缓存，能比直接访问内存快上几百倍。\n\n同样的，内存和硬盘间的速度差距也很大，需要通过读写缓冲区来进行速度匹配，内存写入磁盘时先写入缓冲区，读数据时从缓冲区读取硬盘准备好的数据。\n\n\n## 内存管理和虚拟内存\n\n由于程序的大小越来越大，而计算机想要支持多道程序，当一个程序遇到IO操作时转而去执行另一个程序，这就要求内存中装有多个程序的代码了。\n\n然而程序的内存消耗与日俱增，同时装载多个程序越来越困难，所以人们提出了，只在程序需要使用到的时候再把他装入内存，平时把代码放在硬盘中即可。\n\n### 分页\n\n由于内存需要装载硬盘中的数据，所以需要约定一个存储单元，操作系统把它叫做页，一个页一般长度是8kb或者16kb。内存从硬盘读取数据时读取的是一个页或多个页。\n\n### 虚拟内存\n\n\n由于这些代码持久化在硬盘中，占用了一部分空间，并且需要运行时会加载到内存中，所以这部分的空间一般也成为虚拟内存的空间（大小）。\n\n### 页表和页面置换算法\n\n为了知道每个程序对应的代码存在硬盘中的位置，操作系统需要维护一个程序到页面的映射表，cpu要内存加载一个页面时，首先要访问页表，来得知页面在硬盘中的位置，然后让内存去该位置把对应的页面调入内存中。\n\n为了提升页面调入的效率，也使用了多种的页面置换算法，比如lru最近最久未使用，fifo先进先出，时钟法，多级队列法等。\n\n当然，为了进一步提高效率，还会使用多级页表的方式，不过一般需要硬件维护一个页表映射页面的快速转换结构，以便能迅速地完成页面解析和调度。\n\n### 中断和缺页中断\n\n计算机提供一系列中断指令与硬件进行交互，操作系统可以使用这些中断去控制硬件，比如使用中断通知cpu硬盘的IO操作已经准备好，键盘通过一次又一次的中断来输入字符。\n\n中断一般适用于快速返回的字符设备，比如鼠标键盘，而硬盘这类耗时IO操作，使用中断后cpu仍然需要等待硬盘把数据装入内存。是非常缓慢的。\n\n于是才会使用DMA来完成IO操作，DMA额外提供一个处理器去处理IO操作，当硬盘数据加载到内存中后再去通知CPU操作已完成。\n\n    缺页中断就是因为内存中没有cpu需要访问的页面，必须根据页表到硬盘中获取页面，并根据置换算法进行页面调度。如果找不到对应页面，则程序执行会报错。\n\n### 分页和分段\n\n分页是上述所说的，通过内存和硬盘的约定，将调度单元设定为一个页面。\n\n    分段则不同，分段并不是物理意义上的分段，而是逻辑上把代码使用到的空间划分成多个部分，比如受保护部分，公开部分，核心部分等，这样可以更好地描述每个段的代码信息，为使用者提供便利，为了支持程序员的这种需求，操作系统加入了分段的概念，将代码对应的一段虚拟内存划分为不同的逻辑段。\n    \n    同时为了根据不同段来以不同方式访问内存，操作系统需要另外维护一个段表，以用于段的映射。\n\n由于分段只是逻辑上的概念，所以底层的内存分页仍然是必不可少的，因此在逻辑段的基础上，物理上又会划分为多个页，一个段中可能包含了多个页面。\n\n因此，完善的虚拟内存管理器需要支持段页表，先映射段，再映射页面。\n\n## 进程与线程\n\n### 进程\n进程是资源分配的资本单位，操作系统为进程开辟一段内存空间，内存空间从高位向低位，包括函数调用栈，变量以及其他区域。cpu根据这些信息配合寄存器进行函数调用和程序执行。\n\n### 多进程\n由于计算机是分时系统，所以多进程的使用不可避免，操作系统需要进行进程的切换，方法是内存指针指向新位置，保存原来的进程信息，同时刷新寄存器等数据。然后开始执行新的进程.\n\n一般操作系统会使用pcb结构来记录进程的信息和上下文。通过他和cpu配合来完成进程切换。\n\n### 线程\n线程是系统调度的基本单位，没有线程以前，一般会使用多进程模型，一个程序往往需要多个进程配合使用，但是多进程之间并没有共享内存，导致进程间通信非常麻烦。\n\n比如文本输入工具，一边键入文字一边需要保存数据，如果是单进程执行，则每次输入都触发IO操作，非常费时，如果一个进程负责输入展示一个进程负责输入保存，确实速度很快，但是两个进程没办法共享数据。除非使用额外的通讯手段。\n### 多线程\n\n而多线程就好办了，多线程都是基于进程产生的，线程被创建后只需要分配少量空间支持堆栈操作即可，同时线程还共享进程中的内存，所以一般使用进程分配资源，线程进行调度的方式。\n\n    操作系统对线程的支持：\n    一般情况下操作系统都支持线程，并且可以创建内核级线程，内核可以识别线程并且为其分配空间，进行线程调度。\n    但是内核级线程实现比较复杂，使用起来也不甚方便。\n    \n    所以往往开发人员会使用用户级线程，用户级线程比如Java的多线程，通过简单的api实现，当需要操作系统支持时，才使用底层调用api接口进行内核级的系统调用。\n    \n    但是一般情况下用户级线程是不被内核识别的，也就是说，用户级线程会被内核认为是一个进程，从而执行进程的调度。这样的话就没有意义了。\n    \n    所以一般情况下用户级线程会映射到对应的内核级线程中，内核为进程创建一定数量的内核级线程以供使用。Java中的线程基本上都是使用这种方式实现的。\n\n### 线程通信和进程通信\n\n线程通信一般只需要使用共享内存的方式即可实现。\n\n而进程通信则需要额外的通信机制。\n\n> 1 信号量，一般多进程间的同步使用信号量来完成，系统为临界区添加支持并发量为n的信号量,多进程访问临界区资源时，首先需要执行p操作来减少信号量，如果信号量等于0则操作失败，并且挂起进程，否则成功进入临界区执行。\n> \n> 当进程退出临界区时，执行v操作，将信号量加一，并唤醒挂起的进程进行操作。\n> \n> 2 管程，管程是对信号量的一个包装，避免使用信号量时出错。\n> \n> 3 管道，直接连接两个进程，一个进程写入管道，另一个进程可以读取管道，但是他不支持全双工，并且只能在父子进程间使用，所以局限性比较大\n> \n> 4 消息队列\n> \n> 操作系统维护一个消息队列，进程将消息写入队列中，其他进程轮询消息队列看是否有自己的消息，增加了轮询的开销，但是提高了消息的可靠性和易用性，同时支持了订阅消息。\n> \n> 5 socket\n> \n> socket一般用于不同主机上的进程通信，双方通过ip和port的方式寻址到对方主机并找到监听该端口的进程，为了完成通信，他们先建立tcp连接，在此基础上交换数据，也就完成了进程间的通信。\n\n### 进程调度\n\n不小心把这茬忘了，进程调度算法有几种，fifo先来先服务，短作业优先，时间片轮转，优先级调度，多级反馈队列等。\n基本上可以通过名字知道算法的大概实现。\n\n### 死锁\n\n死锁的必要条件：\n    \n    1互斥：资源必须是互斥的，只能给一个进程使用\n    \n    2占有和等待：占有资源时可以请求其他资源\n    \n    3不可抢占：资源占有时不会被抢\n    \n    4环路等待：有两个以上的进程组成一个环路，每个进程都在等待下一个进程的资源释放。\n\n死锁的处理方法：\n\n1鸵鸟\n\n2死锁预防\n\n在程序运行之前预防发生死锁。\n\n    （一）破坏互斥条件\n    \n    例如假脱机打印机技术允许若干个进程同时输出，唯一真正请求物理打印机的进程是打印机守护进程。\n    \n    这样子就破坏了互斥条件，转而使用单个队列串行执行操作。\n    \n    （二）破坏占有和等待条件\n    \n    一种实现方式是规定所有进程在开始执行前请求所需要的全部资源。\n    \n    分配了全部资源就不需要去等待其他资源。\n    \n    （三）破坏不可抢占条件\n    \n    允许抢占式调度。\n    \n    （四）破坏环路等待\n    \n    给资源统一编号，进程只能按编号顺序来请求资源。\n    \n    按正确顺序请求资源，就不会发生死锁。\n3死锁避免\n\n==银行家算法用于在程序运行时避免发生死锁。==\n\n银行家算法用于在程序运行时判断资源的分配情况是否是安全状态，如果某一步骤使程序可能发生死锁，银行家算法会拒绝该操作执行，从而避免进入不安全状态。\n\n（一）安全状态\n\n[![](https://github.com/CyC2018/Interview-Notebook/raw/master/pics/ed523051-608f-4c3f-b343-383e2d194470.png)](https://github.com/CyC2018/Interview-Notebook/blob/master/pics/ed523051-608f-4c3f-b343-383e2d194470.png)\n\n图 a 的第二列 Has 表示已拥有的资源数，第三列 Max 表示总共需要的资源数，Free 表示还有可以使用的资源数。从图 a 开始出发，先让 B 拥有所需的所有资源（图 b），运行结束后释放 B，此时 Free 变为 5（图 c）；接着以同样的方式运行 C 和 A，使得所有进程都能成功运行，因此可以称图 a 所示的状态时安全的。\n\n定义：如果没有死锁发生，并且即使所有进程突然请求对资源的最大需求，也仍然存在某种调度次序能够使得每一个进程运行完毕，则称该状态是安全的。\n\n（二）单个资源的银行家算法\n\n一个小城镇的银行家，他向一群客户分别承诺了一定的贷款额度，算法要做的是判断对请求的满足是否会进入不安全状态，如果是，就拒绝请求；否则予以分配。\n\n[![](https://github.com/CyC2018/Interview-Notebook/raw/master/pics/d160ec2e-cfe2-4640-bda7-62f53e58b8c0.png)](https://github.com/CyC2018/Interview-Notebook/blob/master/pics/d160ec2e-cfe2-4640-bda7-62f53e58b8c0.png)\n\n上图 c 为不安全状态，因此算法会拒绝之前的请求，从而避免进入图 c 中的状态。\n\n4死锁检测和恢复\n\n==银行家算法检测程序并且阻止死锁发生，而死锁检测和恢复则\n不试图阻止死锁，而是当检测到死锁发生时，采取措施进行恢复。==\n\n（一）每种类型一个资源的死锁检测\n\n[![](https://github.com/CyC2018/Interview-Notebook/raw/master/pics/b1fa0453-a4b0-4eae-a352-48acca8fff74.png)](https://github.com/CyC2018/Interview-Notebook/blob/master/pics/b1fa0453-a4b0-4eae-a352-48acca8fff74.png)\n\n上图为==资源分配图==，其中方框表示资源，圆圈表示进程。资源指向进程表示该资源已经分配给该进程，进程指向资源表示进程请求获取该资源。\n\n如果有环则说明有死锁。\n\n（二）每种类型多个资源的死锁检测\n\n[![](https://github.com/CyC2018/Interview-Notebook/raw/master/pics/e1eda3d5-5ec8-4708-8e25-1a04c5e11f48.png)](https://github.com/CyC2018/Interview-Notebook/blob/master/pics/e1eda3d5-5ec8-4708-8e25-1a04c5e11f48.png)\n\n==资源分配矩阵==\n    \n上图中，有三个进程四个资源，每个数据代表的含义如下：\n\n*   E 向量：资源总量\n*   A 向量：资源剩余量\n*   C 矩阵：每个进程所拥有的资源数量，每一行都代表一个进程拥有资源的数量\n*   R 矩阵：每个进程请求的资源数量\n\n如果存在请求数量无法被满足时，就会出现死锁。\n\n==（三）死锁恢复==\n\n利用抢占恢复，允许其他进程抢占资源。\n\n利用回滚恢复，回滚进程操作，释放其资源。\n\n通过杀死进程恢复，杀死进程后释放资源。\n\n## IO和磁盘\n\n磁盘是块设备，键盘是字符设备，网卡是网络设备。他们都接在IO总线上，属于慢速设备。\n\n### 磁盘和寻址\n\n磁盘的结构比较复杂，主要通过扇区，盘面和磁头位置决定当前访问的磁盘位置。\n\ncpu为了能够访问磁盘内容，首先要把磁盘的内容载入内存中，于是要和内存约定统一的寻址单元，cpu指定一个起始位置，访问该位置以后的n个存储单元，一般存储单元是一个页，16K或者8K。\n\n这一操作中，指向起始位置需要随机寻址，而接下来的访问操作是顺序访问，磁盘的随机读写和顺序读写的速度差距是很大的。所以一般会通过缓冲区来缓存IO数据。\n\n    磁盘内部一般也会分为很多部分，比如操作系统会将磁盘做一个分区，使用磁盘的一些位置存储元数据信息，以保证磁盘能够支持操作系统以及文件系统。\n    \n    一般在物理分区的起始位置会有一个引导区和分区表,BIOS自动将磁盘中引导区的内核程序载入内存，此时操作系统才开始运行，并且根据分区表操作系统可以知道每个分区的起始位置在哪。\n\n读写一个磁盘块的时间的影响因素有：\n\n旋转时间（主轴旋转磁盘，使得磁头移动到适当的扇区上）\n寻道时间（制动手臂移动，使得磁头移动到适当的磁道上）\n实际的数据传输时间\n其中，寻道时间最长，因此磁盘调度的主要目标是使磁盘的平均寻道时间最短。\n\n1. 先来先服务\nFCFS, First Come First Served\n\n按照磁盘请求的顺序进行调度。\n\n优点是公平和简单。缺点也很明显，因为未对寻道做任何优化，使平均寻道时间可能较长。\n\n2. 最短寻道时间优先\nSSTF, Shortest Seek Time First\n\n优先调度与当前磁头所在磁道距离最近的磁道。\n\n虽然平均寻道时间比较低，但是不够公平。如果新到达的磁道请求总是比一个在等待的磁道请求近，那么在等待的磁道请求会一直等待下去，也就是出现饥饿现象。具体来说，两边的磁道请求更容易出现饥饿现象。\n\n3. 电梯算法\nSCAN\n\n电梯总是保持一个方向运行，直到该方向没有请求为止，然后改变运行方向。\n\n电梯算法（扫描算法）和电梯的运行过程类似，总是按一个方向来进行磁盘调度，直到该方向上没有未完成的磁盘请求，然后改变方向。\n\n因为考虑了移动方向，因此所有的磁盘请求都会被满足，解决了 SSTF 的饥饿问题。\n\n### IO设备\n\n除了硬盘以外，还有键盘，网卡等IO设备，这些设备需要操作系统通过驱动程序来进行交互，驱动程序用于适配这些设备。\n\n为了执行IO操作，内核一般要为IO设备提供一个缓存区，比如网卡的IO操作，会为socket提供一个缓存区，当多个socket使用一个缓冲区进行通信，就是复用了缓冲区，也就是IO复用的一种方式。\n\n同时，内核还会维护一个IO请求的列表，当IO请求就绪时，让几个线程去执行IO操作，实现了线程的复用。\n\n### 文件系统\n\n文件系统是基于底层存储建立的一个树形文件结构。比较经典的是Linux的文件系统，首先在硬盘的超级块中安装文件系统，磁盘引导时会加载文件系统的信息。\n\nlinux使用inode来标识任意一个文件。inode存储除了文件名以外的文件信息，包括创建时间，权限，以及一个指向磁盘存储位置的指针，那里才是真正存放数据的地方。\n\n一个目录也是一个inode节点。\n\n详细阐述一次文件访问的过程：\n    \n    首先用户ls查看目录。由于一个目录也是一个文件，所以相当于是看目录文件下有哪些东西。\n    \n    实际上目录文件是一个特殊的inode节点，它不需要存储实际数据，而只是维护一个文件名到inode的映射表。\n    \n    于是我们ls到另一个目录。同理他也是一个inode。我们在这个inode下执行vi操作打开某个文件，于是linux通过inode中的映射表找到了我们请求访问的文件名对应的inode。\n    \n    然后寻道到对应的磁盘位置，读取内容到缓冲区，通过系统调用把内容读到内存中，最后进行访问。\n\n\n"
  },
  {
    "path": "docs/database/Mysql原理与实践总结.md",
    "content": "# 目录\n* [数据库原理](#数据库原理)\n  * [范式 反范式](#范式-反范式)\n  * [主键 外键](#主键-外键)\n  * [锁 共享锁和排它锁](#锁-共享锁和排它锁)\n  * [存储过程与视图](#存储过程与视图)\n  * [事务与隔离级别](#事务与隔离级别)\n  * [索引](#索引)\n* [mysql原理](#mysql原理)\n  * [mysql客户端，服务端，存储引擎，文件系统](#mysql客户端，服务端，存储引擎，文件系统)\n  * [mysql常用语法](#mysql常用语法)\n  * [MySQL的存储原理](#mysql的存储原理)\n    * [数据页page](#数据页page)\n  * [mysql的索引，b树，聚集索引](#mysql的索引，b树，聚集索引)\n  * [mysql的explain 慢查询日志](#mysql的explain-慢查询日志)\n  * [mysql的binlog,redo log和undo log。](#mysql的binlogredo-log和undo-log。)\n  * [mysql的数据类型](#mysql的数据类型)\n  * [mysql的sql优化。](#mysql的sql优化。)\n  * [MySQL的事务实现和锁](#mysql的事务实现和锁)\n  * [分库分表](#分库分表)\n  * [主从复制，读写分离](#主从复制，读写分离)\n  * [分布式数据库](#分布式数据库)\n\n\n\n本文根据自己对MySQL的学习和实践以及各类文章与书籍总结而来。\n囊括了MySQL数据库的基本原理和技术。本文主要是我的一个学习总结，基于之前的系列文章做了一个概括，如有错误，还望指出，谢谢。\n\n详细内容请参考我的系列文章：\n\n重新学习MySQL与Redis\n\nhttps://blog.csdn.net/column/details/21877.html\n<!-- more -->\n\n# 数据库原理\nMysql是关系数据库。\n\n## 范式 反范式\n范式设计主要是避免冗余，以及数据不一致。反范式设计主要是避免多表连接，增加了冗余。\n\n## 主键 外键\n主键是一个表中一行数据的唯一标识。\n外键则是值某一列的键值是其他表的主键，外键的作用一般用来作为两表连接的键，并且保证数据的一致性。\n\n## 锁 共享锁和排它锁\n数据库的锁用来进行并发控制，排它锁也叫写锁，共享锁也叫行锁，根据不同粒度可以分为行锁和表锁。\n\n## 存储过程与视图\n存储过程是对sql语句进行预编译并且以文件形式包装为一个可以快速执行的程序。但是缺点是不易修改，稍微改动语句就需要重新开发储存过程，优点是执行效率快。视图就是对其他一个或多个表进行重新包装，是一个外观模式，对视图数据的改动也会影响到数据报本身。\n\n## 事务与隔离级别   \n事务的四个性质：原子性，一致性，持久性，隔离性。\n\n原子性：一个事务中的操作要么全部成功要么全部失败。\n\n一致性：事务执行成功的状态都是一致的，即使失败回滚了，也应该和事务执行前的状态是一致的。\n\n隔离性：两个事务之间互不相干，不能互相影响。\n\n事务的隔离级别\n读未提交：事务A和事务B，A事务中执行的操作，B也可以看得到，因为级别是未提交读，别人事务中还没提交的数据你也看得到。这是没有任何并发措施的级别，也是默认级别。这个问题叫做脏读，为了解决这个问题，提出了读已提交。\n\n读已提交：事务A和B，A中的操作B看不到，只有A提交后，在B中才看得到。虽然A的操作B看不到，但是B可以修改A用到的数据，导致A读两次的数据结果不同。这就是不可重读问题。\n\n可重复读：事务A和B，事务A和B，A在数据行上加读锁，B虽然看得到但是改不了。所以是可重复读的，但是A的其他行仍然会被B访问并修改，所以导致了幻读问题。\n\n序列化：数据库强制事务A和B串行化操作，避免了并发问题，但是效率比较低。\n\n后面可以看一下mysql对隔离级别的实现。\n\n## 索引\n\n索引的作用就和书的目录类似，比如根据书名做索引，然后我们通过书名就可以直接翻到某一页。数据表中我们要找一条数据，也可以根据它的主键来找到对应的那一页。当然数据库的搜索不是翻书，如果一页一页翻书，就相当于是全表扫描了，效率很低，所以人翻书肯定也是跳着翻。数据库也会基于类似的原理\"跳着”翻书，快速地找到索引行。\n\n# mysql原理\n\nMySQL是oracle公司的免费数据库，作为关系数据库火了很久了。所以我们要学他。\n\n## mysql客户端，服务端，存储引擎，文件系统\n\nMySQL数据库的架构可以分为客户端，服务端，存储引擎和文件系统。\n\n详细可以看下架构图，我稍微总结下\n\n    最高层的客户端，通过tcp连接mysql的服务器，然后执行sql语句，其中涉及了查询缓存，执行计划处理和优化，接下来再到存储引擎层执行查询，底层实际上访问的是主机的文件系统。\n\n![image](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/39b96aa41090f9bb.png)\n## mysql常用语法\n\n1 登录mysql\n\nmysql -h 127.0.0.1 -u 用户名 -p\n\n2 创建表\n语法还是比较复杂的，之前有腾讯面试官问这个，然后答不上来。\n````    \n    CREATE TABLE `user_accounts` (\n      `id`             int(100) unsigned NOT NULL AUTO_INCREMENT primary key,\n      `password`       varchar(32)       NOT NULL DEFAULT '' COMMENT '用户密码',\n      `reset_password` tinyint(32)       NOT NULL DEFAULT 0 COMMENT '用户类型：0－不需要重置密码；1-需要重置密码',\n      `mobile`         varchar(20)       NOT NULL DEFAULT '' COMMENT '手机',\n      `create_at`      timestamp(6)      NOT NULL DEFAULT CURRENT_TIMESTAMP(6),\n      `update_at`      timestamp(6)      NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),\n      -- 创建唯一索引，不允许重复\n      UNIQUE INDEX idx_user_mobile(`mobile`)\n    )\n    ENGINE=InnoDB DEFAULT CHARSET=utf8\n````\n\n3 crud比较简单，不谈\n\n4 join用于多表连接，查询的通常是两个表的字段。\n\nunion用于组合同一种格式的多个select查询。\n\n6 聚合函数，一般和group by一起使用，比如查找某部门员工的工资平均值。\n就是select AVE(money) from departmentA group by department\n\n7 建立索引\n\n\n唯一索引(UNIQUE)\n语法：ALTER TABLE 表名字 ADD UNIQUE (字段名字)\n\n添加多列索引\n语法：\n\nALTER TABLE table_name ADD INDEX index_name ( column1, column2, column3)\n\n8 修改添加列\n\n添加列\n语法：alter table 表名 add 列名 列数据类型 [after 插入位置];\n\n删除列\n语法：alter table 表名 drop 列名称;\n\n9 清空表数据\n方法一：delete from 表名;\n方法二：truncate from \"表名\";\n\nDELETE:1. DML语言;2. 可以回退;3. 可以有条件的删除;\n\nTRUNCATE:1. DDL语言;2. 无法回退;3. 默认所有的表内容都删除;4. 删除速度比delete快。\n\n## MySQL的存储原理\n\n下面我们讨论的是innodb的存储原理\n\ninnodb的存储引擎将数据存储单元分为多层。按此不表\n\nMySQL中的逻辑数据库只是一个shchme。事实上物理数据库只有一个。\n\nmysql使用两个文件分别存储数据库的元数据和数据库的真正数据。\n### 数据页page\n\n数据页结构\n页是 InnoDB 存储引擎管理数据的最小磁盘单位，而 B-Tree 节点就是实际存放表中数据的页面，我们在这里将要介绍页是如何组织和存储记录的；首先，一个 InnoDB 页有以下七个部分：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405195759.png)\n每一个页中包含了两对 header/trailer：内部的 Page Header/Page Directory 关心的是页的状态信息，而 Fil Header/Fil Trailer 关心的是记录页的头信息。\n\n    也就是说，外部的h-t对用来和其他页形成联系，而内部的h-t用来是保存内部记录的状态。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405195900.png)\nUser Records 就是整个页面中真正用于存放行记录的部分，而 Free Space 就是空余空间了，它是一个链表的数据结构，为了保证插入和删除的效率，整个页面并不会按照主键顺序对所有记录进行排序，它会自动从左侧向右寻找空白节点进行插入，行记录在物理存储上并不是按照顺序的，它们之间的顺序是由 next_record 这一指针控制的。\n    \n    也就是说，一个页中存了非常多行的数据，而每一行数据和相邻行使用指针进行链表连接。\n\n## mysql的索引，b树，聚集索引\n\n1 MySQL的innodb支持聚簇索引，myisam不支持聚簇索引。\n\ninnodb在建表时自动按照第一个非空字段或者主键建立聚簇索引。mysql使用B+树建立索引。\n\n每一个非叶子结点只存储主键值，而叶子节点则是一个数据页，这个数据页就是上面所说的存储数据的page页。\n\n一个节点页对应着多行数据，每个节点按照顺序使用指针连成一个链表。mysql使用索引访问一行数据时，先通过log2n的时间访问到叶子节点，然后在数据页中按照行数链表执行顺序查找，直到找到那一行数据。\n\n2 b+树索引可以很好地支持范围搜索，因为叶子节点通过指针相连。\n\n## mysql的explain 慢查询日志\n\nexplain主要用于检查sql语句的执行计划，然后分析sql是否使用到索引，是否进行了全局扫描等等。\n\nmysql慢查询日志可以在mysql的,my.cnf文件中配置开启，然后执行操作超过设置时间就会记录慢日志。\n\n比如分析一个sql：\n\n    explain查看执行计划\n````    \n    id\tselect_type\ttable\tpartitions\ttype\tpossible_keys\tkey\tkey_len\tref\trows\tfiltered\tExtra\n    \n    1\tSIMPLE\tvote_record\t\\N\tALL\tvotenum,vote\t\\N\t\\N\t\\N\t996507\t50.00\tUsing where\n````\n\n    \n    \n    还是没用到索引，因为不符合最左前缀匹配。查询需要3.5秒左右\n\n\n    \n    \n    最后修改一下sql语句\n````    \n    EXPLAIN SELECT * FROM vote_record WHERE id > 0 AND vote_num > 1000;\n    \n    id\tselect_type\ttable\tpartitions\ttype\tpossible_keys\tkey\tkey_len\tref\trows\tfiltered\tExtra\n    \n    1\tSIMPLE\tvote_record\t\\N\trange\tPRIMARY,votenum,vote\tPRIMARY\t4\t\\N\t498253\t50.00\tUsing where\n````\n\n    \n    \n    用到了索引，但是只用到了主键索引。再修改一次\n\n\n    \n````    \n    EXPLAIN SELECT * FROM vote_record WHERE id > 0 AND vote_num = 1000;\n\n\n    \n    \n    id\tselect_type\ttable\tpartitions\ttype\tpossible_keys\tkey\tkey_len\tref\trows\tfiltered\tExtra\n    \n    1\tSIMPLE\tvote_record\t\\N\tindex_merge\tPRIMARY,votenum,vote\tvotenum,PRIMARY\t8,4\t\\N\t51\t100.00\tUsing intersect(votenum,PRIMARY); Using where\n\n\n    \n````    \n    用到了两个索引，votenum,PRIMARY。\n\n## mysql的binlog,redo log和undo log。\n\nbinlog就是二进制日志，用于记录用户数据操作的日志。用于主从复制。\n\nredolog负责事务的重做，记录事务中的每一步操作，记录完再执行操作，并且在数据刷入磁盘前刷入磁盘，保证可以重做成功。\n\nundo日志负责事务的回滚，记录事务操作中的原值，记录完再执行操作，在事务提交前刷入磁盘，保证可以回滚成功。\n\n这两个日志也是实现分布式事务的基础。\n\n## mysql的数据类型\n\nmysql一般提供多种数据类型，int，double，varchar，tinyint，datatime等等。文本的话有fulltext，mediumtext等。没啥好说的。\n\n## mysql的sql优化。\n\nsql能优化的点是在有点多。\n\n比如基本的，不使用null判断，不使用><\n分页的时候利用到索引，查询的时候注意顺序。\n\n如果是基于索引的优化，则要注意索引列是否能够使用到\n\n    1 索引列不要使用>< != 以及 null，还有exists等。\n    \n    2 索引列不要使用聚集函数。\n    \n    3 如果是联合索引，排在第一位的索引一定要用到，否则后面的也会失效，为什么呢，因为第一列索引不同时才会找第二列，如果没有第一列索引，后续的索引页没有意义。\n    \n    举个例子。联合索引A,B,C。查询时必须要用到A，但是A的位置无所谓，只要用到就行，A,B,C或者C,B,A都可以。\n    \n    4 分页时直接limit n 5可能用不到索引，假设索引列是ID，那么我们使用where id > n limit 5就可以实现上述操作了。\n\n## MySQL的事务实现和锁    \n\n\ninnodb支持行级锁和事务，而myisam只支持表锁，它的所有操作都需要加锁。\n\n1 锁\n\n    锁可以分为共享锁和排它锁，也叫读锁和写锁。\n    \n    select操作默认不加锁，需要加锁时会用for update加排它锁，或者用in share mode表示加共享锁。\n    \n    这里的锁都是行锁。\n    innodb会使用行锁配合mvcc一同完成事务的实现。\n    并且使用next-key lock来实现可重复读，而不必加表锁或者串行化执行。\n\n2   MVCC\n\n    MVCC是多版本控制协议。\n    \n    通过时间戳来判断先后顺序，并且是无锁的。但是需要额外存一个字段。\n    \n    读操作比较自己的版本号，自动读取比自己版本号新的版本。不读。\n    \n    写操作自动覆盖写版本号比自己的版本号早的版本。否则不写。\n    \n    这样保证一定程度上的一致性。\n    \n    MVCC比较好地支持读多写少的情景。\n    \n    但是偶尔需要加锁时才会进行加锁。\n\n3 事务\n\n所以看看innodb如何实现事务的。\n\n首先，innodb的行锁是加在索引上的，因为innodb默认有聚簇索引，但实际上的行锁是对整个索引节点进行加锁，锁了该节点所有的行。\n\n看看innodb如何实现隔离级别以及解决一致问题\n\n    未提交读，会导致脏读，没有并发措施\n    \n    已提交读，写入时需要加锁，使用行级写锁锁加锁指定行，其他事务就看不到未提交事务的数据了。但是会导致不可重读，\n    \n    可重复读：在原来基础上，在读取行时也需要加行级读锁，这样其他事务不能修改这些数据。就避免了不可重读。\n    但是这样会导致幻读。\n    \n    序列化：序列化会串行化读写操作来避免幻读，事实上就是事务在读取数据时加了表级读锁。\n\n但是实际上。mysql的新版innodb引擎已经解决了幻读的问题，并且使用的是可重复读级别就能解决幻读了。\n\n实现的原理是next-key lock。是gap lock的加强版。不会锁住全表，只会锁住被读取行前后的间隙行。\n\n\n    \n    \n## 分库分表\n\n分库分表的方案比较多，首先看下分表。\n\n当一个大表没办法继续优化的时候，可以使用分表，横向拆分的方案就是把一个表的数据放到多个表中。一般可以按照某个键来分表。比如最常用的id，1-100w放在表一。100w-200w在表二，以此类推。\n\n如果是纵向分表，则可以按列拆分，比如用户信息的字段放在一个表，用户使用数据放在另一个表，这其实就是一次性拆表了。\n\n分库的话就是把数据表存到多个库中了，和横向分表的效果差不多。\n\n如果只是单机的分表分库，其性能瓶颈在于主机。\n\n我们需要考虑扩展性，所以需要使用分布式的数据库。\n\n分布式数据库解决方案mycat\n    \n    mycat是一款支持分库分表的数据库中间件，支持单机也支持分布式。\n    \n    首先部署mycat，mycat的访问方式和一个mysqlserver是类似的。里面可以配置数据库和数据表。\n    \n    然后在mycat的配置文件中，我们可以指定分片，比如按照id分片，然后在每个分片下配置mysql节点，可以是本地的数据库实例也可以是其他主机上的数据库。\n    \n    这样的话，每个分片都能找到对应机器上的数据库和表了。\n    \n    用户连接mycat执行数据库操作，实际上会根据id映射到对应的数据库和表中，\n\n## 主从复制，读写分离\n\n主从复制大法好，为了避免单点mysql宕机和丢失数据，我们一般使用主从部署，主节点将操作日志写入binlog，然后日志文件通过一个连接传给从节点的relaylog。从节点定时去relaylog读取日志，并且执行操作。这样保证了主从的同步。\n\n读写分离大法好，为了避免主库的读写压力太大，由于业务以读操作为主，所以主节点一般作为主库，读节点作为从库，从库负责读，主库负责写，写入主库的数据通过日志同步给从库。这样的部署就是读写分离。\n\n使用mycat中间件也可以配置读写分离，只需在分片时指定某个主机是读节点还是写节点即可。\n\n## 分布式数据库\n\n分布式关系数据库无非就是关系数据库的分布式部署方案。\n\n真正的分布式数据库应该是nosql数据库，比如基于hdfs的hbase数据库。底层就是分布式的。\n\nredis的分布式部署方案也比较成熟。\n\n\n"
  },
  {
    "path": "docs/database/重新学习MySQL数据库：Innodb中的事务隔离级别和锁的关系.md",
    "content": "# 目录\n\n  * [Innodb中的事务隔离级别和锁的关系](#innodb中的事务隔离级别和锁的关系)\n  * [事务中的加锁方式](#事务中的加锁方式)\n  * [MySQL中锁的种类](#mysql中锁的种类)\n    * [Read Committed（读取提交内容）](#read-committed（读取提交内容）)\n    * [Repeatable Read（可重读）](#repeatable-read（可重读）)\n    * [不可重复读和幻读的区别](#不可重复读和幻读的区别)\n    * [悲观锁和乐观锁](#悲观锁和乐观锁)\n  * [MVCC在MySQL的InnoDB中的实现](#mvcc在mysql的innodb中的实现)\n  * [“读”与“读”的区别](#读与读的区别)\n                                                                                                                                                                                                                                                  * [](#-2)\n\n\n本文转自互联网\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本也将整理到我的个人博客：\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n本文是《重新学习MySQL数据库》系列其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，从sql基本的使用方法，从MySQL执行引擎再到索引、事务等知识，一步步地学习MySQL相关技术的实现原理，更好地了解如何基于这些知识来优化sql，减少SQL执行时间，通过执行计划对SQL性能进行分析，再到MySQL的主从复制、主备部署等内容，以便让你更完整地了解整个MySQL方面的技术体系，形成自己的知识框架。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n\n## Innodb中的事务隔离级别和锁的关系\n\n> 前言:\n> \n> 我们都知道事务的几种性质，数据库为了维护这些性质，尤其是一致性和隔离性，一般使用加锁这种方式。同时数据库又是个高并发的应用，同一时间会有大量的并发访问，如果加锁过度，会极大的降低并发处理能力。所以对于加锁的处理，可以说就是数据库对于事务处理的精髓所在。这里通过分析MySQL中InnoDB引擎的加锁机制，来抛砖引玉，让读者更好的理解，在事务处理中数据库到底做了什么。\n\n一次封锁or两段锁？因为有大量的并发访问，为了预防死锁，一般应用中推荐使用**一次封锁法**，就是**在方法的开始阶段，已经预先知道会用到哪些数据，然后全部锁住，在方法运行之后，再全部解锁**。这种方式可以有效的避免循环死锁，但在数据库中却不适用，因为在事务开始阶段，**数据库并不知道会用到哪些数据**。数据库遵循的是两段锁协议，将事务分成两个阶段，加锁阶段和解锁阶段（所以叫两段锁）\n\n*   加锁阶段：在该阶段可以进行加锁操作。在对任何数据进行读操作之前要申请并获得S锁（共享锁，其它事务可以继续加共享锁，但不能加排它锁），在进行写操作之前要申请并获得X锁（排它锁，其它事务不能再获得任何锁）。**加锁不成功，则事务进入等待状态，直到加锁成功才继续执行。**\n\n*   解锁阶段：当事务释放了一个封锁以后，事务进入解锁阶段，在该阶段只能进行解锁操作不能再进行加锁操作。\n\n| 事务 | 加锁/解锁处理 |\n| --- | --- |\n| begin； |  |\n| insert into test ..... | 加insert对应的锁 |\n| update test set... | 加update对应的锁 |\n| delete from test .... | 加delete对应的锁 |\n| commit; | 事务提交时，同时释放insert、update、delete对应的锁 |\n\n这种方式虽然**无法避免死锁**，但是两段锁协议**可以保证事务的并发调度是串行化**（串行化很重要，尤其是在数据恢复和备份的时候）的。\n\n## 事务中的加锁方式\n\n##事务的四种隔离级别在数据库操作中，为了有效保证并发读取数据的正确性，提出的事务隔离级别。我们的数据库锁，也是为了构建这些隔离级别存在的。\n\n| 隔离级别 | 脏读（Dirty Read） | 不可重复读（NonRepeatable Read） | 幻读（Phantom Read） |\n| --- | --- | --- | --- |\n| 未提交读（Read uncommitted） | 可能 | 可能 | 可能 |\n| 已提交读（Read committed） | 不可能 | 可能 | 可能 |\n| 可重复读（Repeatable read） | 不可能 | 不可能 | 可能 |\n| 可串行化（Serializable ） | 不可能 | 不可能 | 不可能 |\n\n*   未提交读(Read Uncommitted)：允许脏读，也就是可能读取到其他会话中未提交事务修改的数据\n\n*   提交读(Read Committed)：只能读取到已经提交的数据。Oracle等多数数据库默认都是该级别 (不重复读)\n\n*   可重复读(Repeated Read)：可重复读。在同一个事务内的查询都是事务开始时刻一致的，InnoDB默认级别。在SQL标准中，该隔离级别消除了不可重复读，但是还存在幻读\n\n*   串行读(Serializable)：完全串行化的读，每次读都需要获得**表级共享锁**，读写相互都会阻塞\n\nRead Uncommitted这种级别，数据库一般都不会用，而且任何操作都不会加锁，这里就不讨论了。\n\n## MySQL中锁的种类\n\nMySQL中锁的种类很多，有常见的表锁和行锁，也有新加入的Metadata Lock等等,表锁是对一整张表加锁，虽然可分为读锁和写锁，但毕竟是锁住整张表，会导致并发能力下降，一般是做ddl处理时使用。\n\n行锁则是锁住数据行，这种加锁方法比较复杂，但是由于只锁住有限的数据，对于其它数据不加限制，所以并发能力强，MySQL一般都是用行锁来处理并发事务。这里主要讨论的也就是行锁。\n\n### Read Committed（读取提交内容）\n\n在RC级别中，数据的读取都是不加锁的，但是数据的写入、修改和删除是需要加锁的。效果如下\n````\nMySQL> show create table class_teacher \\G\\\n\n\n\nTable: class_teacher\n\n\n\nCreate Table: CREATE TABLE `class_teacher` (\n\n\n\n `id` int(11) NOT NULL AUTO_INCREMENT,\n\n\n\n `class_name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,\n\n\n\n `teacher_id` int(11) NOT NULL,\n\n\n\n PRIMARY KEY (`id`),\n\n\n\n KEY `idx_teacher_id` (`teacher_id`)\n\n\n\n) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci\n\n\n\n1 row in set (0.02 sec)\n\n\n\nMySQL> select * from class_teacher;\n\n\n\n+----+--------------+------------+\n\n\n\n| id | class_name  | teacher_id |\n\n\n\n+----+--------------+------------+\n\n\n\n| 1 | 初三一班   |     1 |\n\n\n\n| 3 | 初二一班   |     2 |\n\n\n\n| 4 | 初二二班   |     2 |\n\n\n\n+----+--------------+------------+\n````\n由于MySQL的InnoDB默认是使用的RR级别，所以我们先要将该session开启成RC级别，并且设置binlog的模式\n````\nSET session transaction isolation level read committed;\n\n\n\nSET SESSION binlog_format = 'ROW';（或者是MIXED）\n````\n| 事务A | 事务B |\n| --- | --- |\n| begin; | begin; |\n| update class_teacher set class_name='初三二班' where teacher_id=1; | update class_teacher set class_name='初三三班' where teacher_id=1; |\n|  | ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction |\n| commit; |  |\n\n为了防止并发过程中的修改冲突，事务A中MySQL给teacher_id=1的数据行加锁，并一直不commit（释放锁），那么事务B也就一直拿不到该行锁，wait直到超时。\n\n这时我们要注意到，teacher_id是有索引的，如果是没有索引的class_name呢？update class_teacher set teacher_id=3 where class_name = '初三一班';那么MySQL会给整张表的所有数据行的加行锁。这里听起来有点不可思议，但是当sql运行的过程中，MySQL并不知道哪些数据行是 class_name = '初三一班'的（没有索引嘛），如果一个条件无法通过索引快速过滤，存储引擎层面就会将所有记录加锁后返回，再由MySQL Server层进行过滤。\n\n但在实际使用过程当中，MySQL做了一些改进，在MySQL Server过滤条件，发现不满足后，会调用unlock_row方法，把不满足条件的记录释放锁 (违背了二段锁协议的约束)。这样做，保证了最后只会持有满足条件记录上的锁，但是每条记录的加锁操作还是不能省略的。可见即使是MySQL，为了效率也是会违反规范的。（参见《高性能MySQL》中文第三版p181）\n\n这种情况同样适用于MySQL的默认隔离级别RR。所以对一个数据量很大的表做批量修改的时候，如果无法使用相应的索引，MySQL Server过滤数据的的时候特别慢，就会出现虽然没有修改某些行的数据，但是它们还是被锁住了的现象。\n\n### Repeatable Read（可重读）\n\n这是MySQL中InnoDB默认的隔离级别。我们姑且分“读”和“写”两个模块来讲解。\n\n####读读就是可重读，可重读这个概念是一事务的多个实例在并发读取数据时，会看到同样的数据行，有点抽象，我们来看一下效果。\n\nRC（不可重读）模式下的展现\n\n| 事务A | 事务B |\n| --- | --- |\n| begin; | begin; |\n| select id,class_name,teacher_id from class_teacher where teacher_id=1; idclass_name,teacher_id1初三二班12初三一班1 |  |\n|  | update class_teacher set class_name='初三三班' where id=1; |\n|  | commit; |\n| select id,class_name,teacher_id from class_teacher where teacher_id=1; idclass_name,teacher_id1初三三班12初三一班1 读到了事务B修改的数据，和第一次查询的结果不一样，是不可重读的。 |  |\n| commit; |  |\n\n事务B修改id=1的数据提交之后，事务A同样的查询，后一次和前一次的结果不一样，这就是不可重读（重新读取产生的结果不一样）。这就很可能带来一些问题，那么我们来看看在RR级别中MySQL的表现：\n\n| 事务A | 事务B | 事务C |\n| --- | --- | --- |\n| begin; | begin; | begin; |\n| select id,class_name,teacher_id from class_teacher where teacher_id=1;idclass_nameteacher_id1初三二班12初三一班1 |  |  |\n|  | update class_teacher set class_name='初三三班' where id=1;commit; |  |\n|  |  | insert into class_teacher values (null,'初三三班',1); commit; |\n| select id,class_name,teacher_id from class_teacher where teacher_id=1;idclass_nameteacher_id1初三二班12初三一班1 没有读到事务B修改的数据，和第一次sql读取的一样，是可重复读的。没有读到事务C新添加的数据。 |  |  |\n| commit; |  |  |\n\n我们注意到，当teacher_id=1时，事务A先做了一次读取，事务B中间修改了id=1的数据，并commit之后，事务A第二次读到的数据和第一次完全相同。所以说它是可重读的。那么MySQL是怎么做到的呢？这里姑且卖个关子，我们往下看。\n\n### 不可重复读和幻读的区别\n\n很多人容易搞混不可重复读和幻读，确实这两者有些相似。但不可重复读重点在于update和delete，而幻读的重点在于insert。\n\n如果使用锁机制来实现这两种隔离级别，在可重复读中，该sql第一次读取到数据后，就将这些数据加锁，其它事务无法修改这些数据，就可以实现可重复读了。但这种方法却无法锁住insert的数据，所以当事务A先前读取了数据，或者修改了全部数据，事务B还是可以insert数据提交，这时事务A就会发现莫名其妙多了一条之前没有的数据，这就是幻读，不能通过行锁来避免。需要Serializable隔离级别 ，读用读锁，写用写锁，读锁和写锁互斥，这么做可以有效的避免幻读、不可重复读、脏读等问题，但会极大的降低数据库的并发能力。\n\n所以说不可重复读和幻读最大的区别，就在于如何通过锁机制来解决他们产生的问题。\n\n上文说的，是使用悲观锁机制来处理这两种问题，但是MySQL、ORACLE、PostgreSQL等成熟的数据库，出于性能考虑，都是使用了以乐观锁为理论基础的MVCC（多版本并发控制）来避免这两种问题。\n\n### 悲观锁和乐观锁\n\n*   悲观锁\n\n正如其名，它指的是对数据被外界（包括本系统当前的其他事务，以及来自外部系统的事务处理）修改持保守态度，因此，在整个数据处理过程中，将数据处于锁定状态。悲观锁的实现，往往依靠数据库提供的锁机制（也只有数据库层提供的锁机制才能真正保证数据访问的排他性，否则，即使在本系统中实现了加锁机制，也无法保证外部系统不会修改数据）。\n\n在悲观锁的情况下，为了保证事务的隔离性，就需要一致性锁定读。读取数据时给加锁，其它事务无法修改这些数据。修改删除数据时也要加锁，其它事务无法读取这些数据。\n\n*   乐观锁\n\n相对悲观锁而言，乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现，以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销，特别是对长事务而言，这样的开销往往无法承受。\n\n而乐观锁机制在一定程度上解决了这个问题。乐观锁，大多是基于数据版本（ Version ）记录机制实现。何谓数据版本？即为数据增加一个版本标识，在基于数据库表的版本解决方案中，一般是通过为数据库表增加一个 “version” 字段来实现。读取出数据时，将此版本号一同读出，之后更新时，对此版本号加一。此时，将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对，如果提交的数据版本号大于数据库表当前版本号，则予以更新，否则认为是过期数据。\n\n要说明的是，MVCC的实现没有固定的规范，每个数据库都会有不同的实现方式，这里讨论的是InnoDB的MVCC。\n\n## MVCC在MySQL的InnoDB中的实现\n\n在InnoDB中，会在**每行数据后添加两个额外的隐藏的值来实现MVCC**，这两个值一个记录这行数据何时被创建，另外一个记录这行数据何时过期（或者被删除）。 在实际操作中，**存储的并不是时间，而是事务的版本号**，每开启一个新事务，事务的版本号就会递增。 在**可重读**Repeatable reads事务隔离级别下：\n\n*   **SELECT时，读取创建版本号<=当前事务版本号，删除版本号为空或>当前事务版本号。**\n\n*   **INSERT时，保存当前事务版本号为行的创建版本号**\n\n*   **DELETE时，保存当前事务版本号为行的删除版本号**\n\n*   **UPDATE时，插入一条新纪录，保存当前事务版本号为行创建版本号，同时保存当前事务版本号到原来删除的行**\n\n通过MVCC，虽然每行记录都需要额外的存储空间，更多的行检查工作以及一些额外的维护工作，但可以减少锁的使用，大多数读操作**都不用加锁**，读数据操作很简单，性能很好，并且也能保证只会读取到符合标准的行，也只锁住必要行。\n\n我们不管从数据库方面的教课书中学到，还是从网络上看到，大都是上文中事务的四种隔离级别这一模块列出的意思，RR级别是可重复读的，但无法解决幻读，而只有在Serializable级别才能解决幻读。于是我就加了一个事务C来展示效果。在事务C中添加了一条teacher_id=1的数据commit，RR级别中应该会有幻读现象，事务A在查询teacher_id=1的数据时会读到事务C新加的数据。但是测试后发现，在MySQL中是不存在这种情况的，在事务C提交后，事务A还是不会读到这条数据。可见在MySQL的RR级别中，是解决了幻读的读问题的。参见下图\n\n读问题解决了，根据MVCC的定义，并发提交数据时会出现冲突，那么冲突时如何解决呢？我们再来看看InnoDB中RR级别对于写数据的处理。\n\n## “读”与“读”的区别\n\n可能有读者会疑惑，事务的隔离级别其实都是对于读数据的定义，但到了这里，就被拆成了读和写两个模块来讲解。这主要是因为MySQL中的读，和事务隔离级别中的读，是不一样的。\n\n我们且看，在**RR级别中，通过MVCC机制，虽然让数据变得可重复读**，但我们读到的数据可能是历史数据，是不及时的数据，不是数据库当前的数据！这在一些对于数据的时效特别敏感的业务中，就很可能出问题。\n\n对于这种读取历史数据的方式，我们叫它**快照读 (snapshot read)**，而读取数据库当前版本数据的方式，叫当前读 (current read)。很显然，在MVCC中：\n\n*   快照读：就是select\n\n    *   select * from table ....;\n\n*   当前读：特殊的读操作，插入/更新/删除操作，属于当前读，处理的都是当前的数据，需要加锁。\n\n    *   select * from table where ? lock in share mode;\n\n    *   select * from table where ? for update;\n\n    *   insert;\n\n    *   update ;\n\n    *   delete;\n\n事务的隔离级别实际上都是定义了当前读的级别，MySQL为了减少锁处理（包括等待其它锁）的时间，提升并发能力，引入了快照读的概念，使得select不用加锁。而update、insert这些“当前读”，就需要另外的模块来解决了。\n\n写（\"当前读\"）事务的隔离级别中虽然只定义了读数据的要求，实际上这也可以说是写数据的要求。上文的“读”，实际是讲的快照读；而这里说的“写”就是当前读了。为了解决当前读中的幻读问题，MySQL事务使用了Next-Key锁。\n\nNext-Key锁Next-Key锁是行锁和GAP（间隙锁）的合并，行锁上文已经介绍了，接下来说下GAP间隙锁。\n\n行锁可以防止不同事务版本的数据修改提交时造成数据冲突的情况。但如何避免别的事务插入数据就成了问题。我们可以看看RR级别和RC级别的对比\n\nRC级别：\n\n| 事务A | 事务B |\n| --- | --- |\n| begin; | begin; |\n| select id,class_name,teacher_id from class_teacher where teacher_id=30;idclass_nameteacher_id2初三二班30 |  |\n| update class_teacher set class_name='初三四班' where teacher_id=30; |  |\n|  | insert into class_teacher values (null,'初三二班',30);commit; |\n| select id,class_name,teacher_id from class_teacher where teacher_id=30;idclass_nameteacher_id2初三四班3010初三二班30 |  |\n\nRR级别：\n\n| 事务A | 事务B |\n| --- | --- |\n| begin; | begin; |\n| select id,class_name,teacher_id from class_teacher where teacher_id=30;idclass_nameteacher_id2初三二班30 |  |\n| update class_teacher set class_name='初三四班' where teacher_id=30; |  |\n|  | insert into class_teacher values (null,'初三二班',30);waiting.... |\n| select id,class_name,teacher_id from class_teacher where teacher_id=30;idclass_nameteacher_id2初三四班30 |  |\n| commit; | 事务Acommit后，事务B的insert执行。 |\n\n通过对比我们可以发现，在RC级别中，事务A修改了所有teacher_id=30的数据，但是当事务B insert进新数据后，事务A发现莫名其妙多了一行teacher_id=30的数据，而且没有被之前的update语句所修改，这就是“当前读”的幻读。\n\nRR级别中，事务A在update后加锁，事务B无法插入新数据，这样事务A在update前后读的数据保持一致，避免了幻读。这个锁，就是Gap锁。\n\nMySQL是这么实现的：\n\n在class_teacher这张表中，teacher_id是个索引，那么它就会维护一套B+树的数据关系，为了简化，我们用链表结构来表达（实际上是个树形结构，但原理相同）\n\n如图所示，InnoDB使用的是聚集索引，teacher_id身为二级索引，就要维护一个索引字段和主键id的树状结构（这里用链表形式表现），并保持顺序排列。\n\nInnodb将这段数据分成几个个区间\n\n*   (negative infinity, 5],\n\n*   (5,30],\n\n*   (30,positive infinity)；\n\nupdate class_teacher set class_name='初三四班' where teacher_id=30;不仅用行锁，锁住了相应的数据行；同时也在两边的区间，（5,30]和（30，positive infinity），都加入了gap锁。这样事务B就无法在这个两个区间insert进新数据。\n\n受限于这种实现方式，Innodb很多时候会锁住不需要锁的区间。如下所示：\n\n| 事务A | 事务B | 事务C |\n| --- | --- | --- |\n| begin; | begin; | begin; |\n| select id,class_name,teacher_id from class_teacher;idclass_nameteacher_id1初三一班52初三二班30 |  |  |\n| update class_teacher set class_name='初一一班' where teacher_id=20; |  |  |\n|  | insert into class_teacher values (null,'初三五班',10);waiting ..... | insert into class_teacher values (null,'初三五班',40); |\n| commit; | 事务A commit之后，这条语句才插入成功 | commit; |\n|  | commit; |  |\n\nupdate的teacher_id=20是在(5，30]区间，即使没有修改任何数据，Innodb也会在这个区间加gap锁，而其它区间不会影响，事务C正常插入。\n\n如果使用的是没有索引的字段，比如update class_teacher set teacher_id=7 where class_name='初三八班（即使没有匹配到任何数据）',那么会给全表加入gap锁。同时，它不能像上文中行锁一样经过MySQL Server过滤自动解除不满足条件的锁，因为没有索引，则这些字段也就没有排序，也就没有区间。除非该事务提交，否则其它事务无法插入任何数据。\n\n行锁防止别的事务修改或删除，GAP锁防止别的事务新增，行锁和GAP锁结合形成的的Next-Key锁共同解决了RR级别在写数据时的幻读问题。\n\n###Serializable这个级别很简单，**读加共享锁，写加排他锁，读写互斥**。使用的**悲观锁的理论**，实现简单，数据更加安全，但是并发能力非常差。如果你的业务并发的特别少或者没有并发，同时又要求数据及时可靠的话，可以使用这种模式。\n\n这里要吐槽一句，**不要看到select就说不会加锁了，在Serializable这个级别，还是会加锁的**！\n"
  },
  {
    "path": "docs/database/重新学习MySQL数据库：MySQL的事务隔离级别实战.md",
    "content": "\n# 目录\n  * [事务的基本要素（ACID）](#事务的基本要素（acid）)\n  * [事务的并发问题](#事务的并发问题)\n  * [MySQL事务隔离级别](#mysql事务隔离级别)\n\n\n本文转自:https://blog.csdn.net/sinat_27143551/article/details/80876127\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本也将整理到我的个人博客：\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n本文是《重新学习MySQL数据库》系列其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，从sql基本的使用方法，从MySQL执行引擎再到索引、事务等知识，一步步地学习MySQL相关技术的实现原理，更好地了解如何基于这些知识来优化sql，减少SQL执行时间，通过执行计划对SQL性能进行分析，再到MySQL的主从复制、主备部署等内容，以便让你更完整地了解整个MySQL方面的技术体系，形成自己的知识框架。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n\n## 事务的基本要素（ACID）\n\n　　1、原子性（Atomicity）：事务开始后所有操作，要么全部做完，要么全部不做，不可能停滞在中间环节。事务执行过程中出错，会回滚到事务开始前的状态，所有的操作就像没有发生一样。也就是说事务是一个不可分割的整体，就像化学中学过的原子，是物质构成的基本单位。\n\n　　2、一致性（Consistency）：事务开始前和结束后，数据库的完整性约束没有被破坏 。比如A向B转账，不可能A扣了钱，B却没收到。\n\n　　 3、隔离性（Isolation）：同一时间，只允许一个事务请求同一数据，不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱，在A取钱的过程结束前，B不能向这张卡转账。\n\n　　 4、持久性（Durability）：事务完成后，事务对数据库的所有更新将被保存到数据库，不能回滚。\n\n## 事务的并发问题\n\n　　1、脏读：事务A读取了事务B更新的数据，然后B回滚操作，那么A读取到的数据是脏数据\n\n　　2、不可重复读：事务 A 多次读取同一数据，事务 B 在事务A多次读取的过程中，对数据作了更新并提交，导致事务A多次读取同一数据时，结果 不一致。\n\n　　3、幻读：系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级，但是系统管理员B就在这个时候插入了一条具体分数的记录，当系统管理员A改结束后发现还有一条记录没有改过来，就好像发生了幻觉一样，这就叫幻读。\n\n　　小结：不可重复读的和幻读很容易混淆，不可重复读侧重于修改，幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行，解决幻读需要锁表\n\n## MySQL事务隔离级别\n\n事务隔离级别\t脏读\t不可重复读\t幻读\n读未提交（read-uncommitted）\t是\t是\t是\n读已提交（read-committed）\t否\t是\t是\n可重复读（repeatable-read）\t否\t否\t是\n串行化（serializable）\t否\t否\t否\n默认事务隔离级别：\n\n\n\n1、 读未提交例子\n\n（1）打开客户端A，设置事务模式为read uncommitted，查询表\n\n\n\n（1）在A提交事务之前，打开客户端B，更新表\n\n\n\n\n\n（3）此时，B的事务没提交，但是A已经可以查到B更新的数据\n\n\n\n（4）一旦B的事务因某原因回滚，则A查到的数据就是脏数据。\n\n\n\n（5）在A执行更新语句，在不知道有其他事务回滚时，会发现结果好奇怪。要解决这个问题可以采用读已提交的事务隔离级别。\n\n\n\n2、 读已提交\n\n（1）在客户端A设置事务模式为read committed;\n\n\n\n（2）在客户端A事务提交之前，打开客户端B，起事务更新表\n\n\n\n（3）B的事务还未提交，A不能查到已经更新的数据，解决了脏读问题：\n\n\n\n（4）此时提交客户端B的事务\n\n\n\n（5）A执行与上一步相同的查询，结果发现与上一步不同，这就是不可重复读的问题：\n\n\n\n3、 可重复读\n\n（1）打开客户端A，设置事务模式为repeatable read。\n\n\n\n（2）在A事务提交之前，打开客户端B，更新表account并提交：\n\n\n\n（3）在客户端A执行步骤1的查询，zhangsan的balance依然是450与步骤（1）查询结果一致，没有出现不可重复读的问题；接着执行update balance = balance – 50 where id=4;balance没有变成450-50=400；zhangsan的balance值用的是步骤 （2）中的400来算的，所以是350。数据的一致性没有被破坏。\n\n\n\n（4）在客户端A提交事务\n\n\n\n（5）在客户端A开启事务，随后在客户端B开启事务，新增一条数据。提交\n\n\n\n（6）在A计算balance之和，值为350+16000+2400=18750，没有把客户端B新增的数据算进去，客户端A提交后再计算balance之和，居然变成了19350，这时因为把客户端B的600算进去了。站在客户的角度，客户是看不到客户端B的，他会觉得天上掉馅饼了，多了600块，这就是幻读，站在开发者的角度，数据的一致性没有破坏。但是在应用程序中，我们的代码可能会把18750提交给用户了，如果一定要避免这种小概率状况的发生，那么就要采取“串行化”的事务隔离级别了。\n\n\n\n4、 串行化\n\n（1）打开客户端A，设置事务隔离级别为serializable并开启事务。\n\n\n\n（2）打开客户端B，同样设置事务隔离级别为serializable，开启事务插入数据，报错。表被锁了，插入失败，mysql中事务隔离级别为serializable时会锁表，因此不会出现幻读的情况，这种隔离级别并发性很低，开发中很少用到。\n\n\n\n　　补充：\n\n　　1、SQL规范所规定的标准，不同的数据库具体的实现可能会有些差异\n\n　　2、mysql中默认事务隔离级别是可重复读时并不会锁住读取到的行\n\n　　3、事务隔离级别为读提交时，写数据只会锁住相应的行\n\n　　4、事务隔离级别为可重复读时，如果有索引（包括主键索引）的时候，以索引列为条件更新数据，会存在间隙锁间隙锁、行锁、下一键锁的问题，从而锁住一些行；如果没有索引，更新数据时会锁住整张表。\n\n　　5、事务隔离级别为串行化时，读写数据都会锁住整张表\n\n　　6、隔离级别越高，越能保证数据的完整性和一致性，但是对并发性能的影响也越大，鱼和熊掌不可兼得啊。对于多数应用程序，可以优先考虑把数据库系统的隔离级别设为Read Committed，它能够避免脏读取，而且具有较好的并发性能。尽管它会导致不可重复读、幻读这些并发问题，在可能出现这类问题的个别场合，可以由应用程序采用悲观锁或乐观锁来控制。\n"
  },
  {
    "path": "docs/database/重新学习MySQL数据库：MySQL里的那些日志们.md",
    "content": "# 目录\n  * [重新学习MySQL数据库10：MySQL里的那些日志们](#重新学习mysql数据库10：mysql里的那些日志们)\n    * [1.MySQL日志文件系统的组成](#1mysql日志文件系统的组成)\n    * [2.错误日志](#2错误日志)\n    * [3.InnoDB中的日志](#3innodb中的日志)\n    * [4- 慢查询日志](#4--慢查询日志)\n    * [5.二进制日志](#5二进制日志)\n  * [总结](#总结)\n\n\n本文转自互联网\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本也将整理到我的个人博客：\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n本文是《重新学习MySQL数据库》系列其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，从sql基本的使用方法，从MySQL执行引擎再到索引、事务等知识，一步步地学习MySQL相关技术的实现原理，更好地了解如何基于这些知识来优化sql，减少SQL执行时间，通过执行计划对SQL性能进行分析，再到MySQL的主从复制、主备部署等内容，以便让你更完整地了解整个MySQL方面的技术体系，形成自己的知识框架。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n## 重新学习MySQL数据库10：MySQL里的那些日志们\n\n同大多数关系型数据库一样，日志文件是MySQL数据库的重要组成部分。MySQL有几种不同的日志文件，通常包括错误日志文件，二进制日志，通用日志，慢查询日志，等等。这些日志可以帮助我们定位mysqld内部发生的事件，数据库性能故障，记录数据的变更历史，用户恢复数据库等等。本文主要描述错误日志文件。\n\n### 1.MySQL日志文件系统的组成\n\na、错误日志：记录启动、运行或停止mysqld时出现的问题。b、通用日志：记录建立的客户端连接和执行的语句。c、更新日志：记录更改数据的语句。该日志在MySQL 5.1中已不再使用。d、二进制日志：记录所有更改数据的语句。还用于复制。e、慢查询日志：记录所有执行时间超过long_query_time秒的所有查询或不使用索引的查询。f、Innodb日志：innodb redo log 和undo log\n\n缺省情况下，所有日志创建于mysqld数据目录中。可以通过刷新日志，来强制mysqld来关闭和重新打开日志文件（或者在某些情况下切换到一个新的日志）。当你执行一个FLUSH LOGS语句或执行mysqladmin flush-logs或mysqladmin refresh时，则日志被老化。对于存在MySQL复制的情形下，从复制服务器将维护更多日志文件，被称为接替日志。\n\n### 2.错误日志\n\n错误日志是一个文本文件。错误日志记录了MySQL Server每次启动和关闭的详细信息以及运行过程中所有较为严重的警告和错误信息。可以用--log-error[=file_name]选项来开启mysql错误日志，该选项指定mysqld保存错误日志文件的位置。对于指定--log-error[=file_name]选项而未给定file_name值，mysqld使用错误日志名host_name.err 并在数据目录中写入日志文件。在mysqld正在写入错误日志到文件时，执行FLUSH LOGS 或者mysqladmin flush-logs时，服务器将关闭并重新打开日志文件。建议在flush之前手动重命名错误日志文件，之后mysql服务将使用原始文件名打开一个新文件。以下为错误日志备份方法：shell> mv host_name.err host_name.err-oldshell> mysqladmin flush-logsshell> mv host_name.err-old backup-directory\n\n### 3.InnoDB中的日志\n\nMySQL数据库InnoDB存储引擎Log漫游\n\n1 – Undo Log\n\n**Undo Log 是为了实现事务的原子性**，在MySQL数据库InnoDB存储引擎中，还用Undo Log来实现多版本并发控制(简称：MVCC)。\n\n*   事务的原子性(Atomicity)事务中的所有操作，要么全部完成，要么不做任何操作，不能只做部分操作。如果在执行的过程中发生了错误，要回滚(Rollback)到事务开始前的状态，就像这个事务从来没有执行过。\n\n*   原理Undo Log的原理很简单，为了满足事务的原子性，在操作任何数据之前，首先将数据备份到一个地方（这个存储数据备份的地方称为Undo Log）。然后进行数据的修改。如果出现了错误或者用户执行了ROLLBACK语句，系统可以利用Undo Log中的备份将数据恢复到事务开始之前的状态。\n\n除了可以保证事务的原子性，**Undo Log也可以用来辅助完成事务的持久化**。\n\n*   事务的持久性(Durability)事务一旦完成，该事务对数据库所做的所有修改都会持久的保存到数据库中。为了保证持久性，数据库系统会将修改后的数据完全的记录到持久的存储上。\n\n*   用Undo Log实现原子性和持久化的事务的简化过程假设有A、B两个数据，值分别为1,2。A.事务开始.B.记录A=1到undo log.C.修改A=3.D.记录B=2到undo log.E.修改B=4.F.将undo log写到磁盘。G.将数据写到磁盘。H.事务提交这里有一个隐含的前提条件：‘数据都是先读到内存中，然后修改内存中的数据，最后将数据写回磁盘’。\n\n    之所以能同时保证原子性和持久化，是因为以下特点：A. 更新数据前记录Undo log。B. 为了保证持久性，必须将数据在事务提交前写到磁盘。只要事务成功提交，数据必然已经持久化。C. Undo log必须先于数据持久化到磁盘。如果在G,H之间系统崩溃，undo log是完整的，可以用来回滚事务。D. 如果在A-F之间系统崩溃,因为数据没有持久化到磁盘。所以磁盘上的数据还是保持在事务开始前的状态。\n\n缺陷：每个事务提交前将数据和Undo Log写入磁盘，这样会导致大量的磁盘IO，因此性能很低。\n\n如果能够将数据缓存一段时间，就能减少IO提高性能。但是这样就会丧失事务的持久性。因此引入了另外一种机制来实现持久化，即Redo Log.\n\n2 – Redo Log\n\n*   原理和Undo Log相反，**Redo Log记录的是新数据的备份**。在事务提交前，只要将Redo Log持久化即可，不需要将数据持久化。当系统崩溃时，虽然数据没有持久化，但是Redo Log已经持久化。系统可以根据Redo Log的内容，将所有数据恢复到最新的状态。\n\n*   Undo + Redo事务的简化过程假设有A、B两个数据，值分别为1,2.A.事务开始.B.记录A=1到undo log.C.修改A=3.D.记录A=3到redo log.E.记录B=2到undo log.F.修改B=4.G.记录B=4到redo log.H.将redo log写入磁盘。I.事务提交\n\n    **undo log 保存的是修改前的数据,并且保存到内存中,回滚的时候在读取里面的内容(从而实现了原子性),redolog保存的是修改后的数据(对新数据的备份,同时也会将redo log备份),在事务提交写入到磁盘,从而保证了持久性**\n\n### 4- 慢查询日志\n\n概述　　数据库查询快慢是影响项目性能的一大因素，对于数据库，我们除了要优化 SQL，更重要的是得先找到需要优化的 SQL。如何找到低效的 SQL 是写这篇文章的主要目的。\n\n　　MySQL 数据库有一个“慢查询日志”功能，用来记录查询时间超过某个设定值的SQL，这将极大程度帮助我们快速定位到问题所在，以便对症下药。至于查询时间的多少才算慢，每个项目、业务都有不同的要求，传统企业的软件允许查询时间高于某个值，但是把这个标准放在互联网项目或者访问量大的网站上，估计就是一个bug，甚至可能升级为一个功能性缺陷。\n\n　　为避免误导读者，特申明本文的讨论限制在 Win 64位 + MySQL 5.6 范围内。其他平台或数据库种类及版本，我没有尝试过，不做赘述。\n\n设置日志功能关于慢查询日志，主要涉及到下面几个参数：\n\nslow_query_log ：是否开启慢查询日志功能（必填）long_query_time ：超过设定值，将被视作慢查询，并记录至慢查询日志文件中（必填）log-slow-queries ：慢查询日志文件（不可填），自动在 \\data\\ 创建一个 [hostname]-slow.log 文件　　也就是说，只有满足以上三个条件，“慢查询功能”才可能正确开启或关闭。\n\n### 5.二进制日志\n\n*   主从复制的基础：binlog日志和relaylog日志\n\n什么是MySQL主从复制简单来说就是保证主SQL（Master）和从SQL（Slave）的数据是一致性的，向Master插入数据后，Slave会自动从Master把修改的数据同步过来（有一定的延迟），通过这种方式来保证数据的一致性，就是主从复制\n\n复制方式MySQL5.6开始主从复制有两种方式：基于日志（binlog）、基于GTID（全局事务标示符）。本文只涉及基于日志binlog的主从配置\n\n复制原理1、Master将数据改变记录到二进制日志(binary log)中，也就是配置文件log-bin指定的文件，这些记录叫做二进制日志事件(binary log events)2、Slave通过I/O线程读取Master中的binary log events并写入到它的中继日志(relay log)3、Slave重做中继日志中的事件，把中继日志中的事件信息一条一条的在本地执行一次，完成数据在本地的存储，从而实现将改变反映到它自己的数据(数据重放)\n\n1、什么是binlogbinlog是一个二进制格式的文件，用于记录用户对数据库更新的SQL语句信息，例如更改数据库表和更改内容的SQL语句都会记录到binlog里，但是对库表等内容的查询不会记录。\n\n默认情况下，binlog日志是二进制格式的，不能使用查看文本工具的命令（比如，cat，vi等）查看，而使用mysqlbinlog解析查看。\n\n2.binlog的作用当有数据写入到数据库时，还会同时把更新的SQL语句写入到对应的binlog文件里，这个文件就是上文说的binlog文件。使用mysqldump备份时，只是对一段时间的数据进行全备，但是如果备份后突然发现数据库服务器故障，这个时候就要用到binlog的日志了。\n\n主要作用是用于数据库的主从复制及数据的增量恢复。\n\n1.啥是binlog? 记录数据库增删改,不记录查询的二进制日志.2.作用:用于数据同步.3、如何开启binlog日志功能在mysql的配置文件my.cnf中，增加log_bin参数即可开启binlog日志，也可以通过赋值来指定binlog日志的文件名，实例如下：\n\n[root@DB02 ~]# grep log_bin /etc/my.cnflog_bin = /application/mysql/logs/dadong-bin\n\n\nlog_bin\n\n[root@DB02 ~]#提示：也可以按“log_bin = /application/mysql/logs/dadong-bin”命名，目录要存在为什么要刷新binlog?找到全备数据和binlog文件的恢复临界点.\n\n## 总结\n\nmysql数据库的binlog和relay log日志有着举足轻重的作用，并且relay log仅仅存在于mysql 的slave库，它的作用就是记录slave库中的io进程接收的从主库传过来的binlog,然后等待slave库的sql进程去读取和应用，保证主从同步，但是binlog主库和从库（slave）都可以存在，记录对数据发生或潜在发生更改的SQL语句，并以二进制的形式保存在磁盘，所以可以通过binlog来实时备份和恢复数据库。\n\n1、什么是binlogbinlog是一个二进制格式的文件，用于记录用户对数据库更新的SQL语句信息，例如更改数据库表和更改内容的SQL语句都会记录到binlog里，但是对库表等内容的查询不会记录。\n\n默认情况下，binlog日志是二进制格式的，不能使用查看文本工具的命令（比如，cat，vi等）查看，而使用mysqlbinlog解析查看。\n\n2.binlog的作用当有数据写入到数据库时，还会同时把更新的SQL语句写入到对应的binlog文件里，这个文件就是上文说的binlog文件。使用mysqldump备份时，只是对一段时间的数据进行全备，但是如果备份后突然发现数据库服务器故障，这个时候就要用到binlog的日志了。\n\n主要作用是用于数据库的主从复制及数据的增量恢复。\n"
  },
  {
    "path": "docs/database/重新学习MySQL数据库：Mysql主从复制，读写分离，分表分库策略与实践.md",
    "content": "# 目录\n* [二、分表实现策略](#二、分表实现策略)\n* [三、分库实现策略](#三、分库实现策略)\n* [四、分库与分表实现策略](#四、分库与分表实现策略)\n* [五、分库分表总结](#五、分库分表总结)\n* [六、总结](#六、总结)\n* [Mycat实现主从复制，读写分离，以及分库分表的实践](#mycat实现主从复制，读写分离，以及分库分表的实践)\n    * [Mycat是什么](#mycat是什么)\n    * [一、分区分表](#一、分区分表)\n    * [二、Mycat 数据分片的种类](#二、mycat-数据分片的种类)\n    * [三、Mycat 垂直切分、水平切分实战](#三、mycat-垂直切分、水平切分实战)\n        * [1、垂直切分](#1、垂直切分)\n        * [2、水平切分](#2、水平切分)\n  * [为什么需要读写分离](#为什么需要读写分离)\n  * [MySQL主从复制](#mysql主从复制)\n  * [Mycat读写分离设置](#mycat读写分离设置)\n  * [配置Mycat用户](#配置mycat用户)\n  * [配置Mycat逻辑库](#配置mycat逻辑库)\n  * [schema](#schema)\n  * [dataNode](#datanode)\n  * [dataHost](#datahost)\n\n\n本文转自互联网\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本也将整理到我的个人博客：\n\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n本文是《重新学习MySQL数据库》系列其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，从sql基本的使用方法，从MySQL执行引擎再到索引、事务等知识，一步步地学习MySQL相关技术的实现原理，更好地了解如何基于这些知识来优化sql，减少SQL执行时间，通过执行计划对SQL性能进行分析，再到MySQL的主从复制、主备部署等内容，以便让你更完整地了解整个MySQL方面的技术体系，形成自己的知识框架。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n一、MySQL扩展具体的实现方式\n\n随着业务规模的不断扩大，需要选择合适的方案去应对数据规模的增长，以应对逐渐增长的访问压力和数据量。\n\n关于数据库的扩展主要包括：业务拆分、主从复制、读写分离、数据库分库与分表等。这篇文章主要讲述数据库分库与分表\n\n（1）业务拆分\n\n在[大型网站应用之海量数据和高并发解决方案总结一二](http://blog.csdn.net/xlgen157387/article/details/53230138)一篇文章中也具体讲述了为什么要对业务进行拆分。\n\n业务起步初始，为了加快应用上线和快速迭代，很多应用都采用集中式的架构。随着业务系统的扩大，系统变得越来越复杂，越来越难以维护，开发效率变得越来越低，并且对资源的消耗也变得越来越大，通过硬件提高系统性能的方式带来的成本也越来越高。\n\n因此，在选型初期，一个优良的架构设计是后期系统进行扩展的重要保障。\n\n例如：电商平台，包含了用户、商品、评价、订单等几大模块，最简单的做法就是在一个数据库中分别创建users、shops、comment、order四张表。\n\n![这里写图片描述](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190824171414385-1549042254.png)\n\n但是，随着业务规模的增大，访问量的增大，我们不得不对业务进行拆分。每一个模块都使用单独的数据库来进行存储，不同的业务访问不同的数据库，将原本对一个数据库的依赖拆分为对4个数据库的依赖，这样的话就变成了4个数据库同时承担压力，系统的吞吐量自然就提高了。\n\n![这里写图片描述](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190824171414592-1710110308.png)\n\n（2）主从复制\n\n一般是主写从读，一主多从\n\n1、[MySQL5.6 数据库主从（Master/Slave）同步安装与配置详解](http://blog.csdn.net/xlgen157387/article/details/51331244)\n\n2、[MySQL主从复制的常见拓扑、原理分析以及如何提高主从复制的效率总结](http://blog.csdn.net/xlgen157387/article/details/52451613)\n\n3、[使用mysqlreplicate命令快速搭建 Mysql 主从复制](http://blog.csdn.net/xlgen157387/article/details/52452394)\n\n上述三篇文章中，讲述了如何配置主从数据库，以及如何实现数据库的读写分离，这里不再赘述，有需要的选择性点击查看。\n\n![这里写图片描述](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190824171415114-995239105.png)\n\n上图是网上的一张关于MySQL的Master和Slave之间数据同步的过程图。\n\n主要讲述了MySQL主从复制的原理：数据复制的实际就是Slave从Master获取Binary log文件，然后再本地镜像的执行日志中记录的操作。由于主从复制的过程是异步的，因此Slave和Master之间的数据有可能存在延迟的现象，此时只能保证数据最终的一致性。\n\n（3）数据库分库与分表\n\n我们知道每台机器无论配置多么好它都有自身的物理上限，所以当我们应用已经能触及或远远超出单台机器的某个上限的时候，我们惟有寻找别的机器的帮助或者继续升级的我们的硬件，但常见的方案还是通过添加更多的机器来共同承担压力。\n\n我们还得考虑当我们的业务逻辑不断增长，我们的机器能不能通过线性增长就能满足需求？因此，使用数据库的分库分表，能够立竿见影的提升系统的性能，关于为什么要使用数据库的分库分表的其他原因这里不再赘述，主要讲具体的实现策略。请看下边章节。\n\n## 二、分表实现策略\n\n关键字：用户ID、表容量\n\n对于大部分数据库的设计和业务的操作基本都与用户的ID相关，因此使用用户ID是最常用的分库的路由策略。用户的ID可以作为贯穿整个系统用的重要字段。因此，使用用户的ID我们不仅可以方便我们的查询，还可以将数据平均的分配到不同的数据库中。（当然，还可以根据类别等进行分表操作，分表的路由策略还有很多方式）\n\n接着上述电商平台假设，订单表order存放用户的订单数据，sql脚本如下（只是为了演示，省略部分细节）：\n\n```\nCREATE TABLE `order` (\n  `order_id` bigint(32) primary key auto_increment,\n  `user_id` bigint(32),\n   ...\n) \n```\n\n当数据比较大的时候，对数据进行分表操作，首先要确定需要将数据平均分配到多少张表中，也就是：表容量。\n\n这里假设有100张表进行存储，则我们在进行存储数据的时候，首先对用户ID进行取模操作，根据`user_id%100`获取对应的表进行存储查询操作，示意图如下：\n\n![这里写图片描述](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190824171415339-1589002733.png)\n\n例如，`user_id = 101`那么，我们在获取值的时候的操作，可以通过下边的sql语句：\n\n```\nselect * from order_1 where user_id= 101\n```\n\n其中，`order_1`是根据`101%100`计算所得，表示分表之后的第一章order表。\n\n注意：\n\n在实际的开发中，如果你使用MyBatis做持久层的话，MyBatis已经提供了很好得支持数据库分表的功能，例如上述sql用MyBatis实现的话应该是：\n\n接口定义：\n\n```\n/**\n  * 获取用户相关的订单详细信息\n  * @param tableNum 具体某一个表的编号\n  * @param userId 用户ID\n  * @return 订单列表\n  */\npublic List<Order> getOrder(@Param(\"tableNum\") int tableNum,@Param(\"userId\") int userId);\n```\n\nxml配置映射文件：\n\n```\n<select id=\"getOrder\" resultMap=\"BaseResultMap\">\n    select * from order_${tableNum}\n    where user_id = #{userId}\n  </select>\n```\n\n其中`${tableNum}`含义是直接让参数加入到sql中，这是MyBatis支持的特性。\n\n注意：\n\n```\n另外，在实际的开发中，我们的用户ID更多的可能是通过UUID生成的，这样的话，我们可以首先将UUID进行hash获取到整数值，然后在进行取模操作。\n```\n\n## 三、分库实现策略\n\n数据库分表能够解决单表数据量很大的时候数据查询的效率问题，但是无法给数据库的并发操作带来效率上的提高，因为分表的实质还是在一个数据库上进行的操作，很容易受数据库IO性能的限制。\n\n因此，如何将数据库IO性能的问题平均分配出来，很显然将数据进行分库操作可以很好地解决单台数据库的性能问题。\n\n分库策略与分表策略的实现很相似，最简单的都是可以通过取模的方式进行路由。\n\n还是上例，将用户ID进行取模操作，这样的话获取到具体的某一个数据库，同样关键字有：\n\n用户ID、库容量\n\n路由的示意图如下：\n\n![这里写图片描述](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190824171415519-516549009.png)\n\n上图中库容量为100。\n\n同样，如果用户ID为UUID请先hash然后在进行取模。\n\n## 四、分库与分表实现策略\n\n上述的配置中，数据库分表可以解决单表海量数据的查询性能问题，分库可以解决单台数据库的并发访问压力问题。\n\n有时候，我们需要同时考虑这两个问题，因此，我们既需要对单表进行分表操作，还需要进行分库操作，以便同时扩展系统的并发处理能力和提升单表的查询性能，就是我们使用到的分库分表。\n\n分库分表的策略相对于前边两种复杂一些，一种常见的路由策略如下：\n\n```\n１、中间变量　＝ user_id%（库数量*每个库的表数量）;\n２、库序号　＝　取整（中间变量／每个库的表数量）;\n３、表序号　＝　中间变量％每个库的表数量;\n```\n\n例如：数据库有256 个，每一个库中有1024个数据表，用户的user_id＝262145，按照上述的路由策略，可得：\n\n```\n１、中间变量　＝ 262145%（256*1024）= 1;\n２、库序号　＝　取整（1／1024）= 0;\n３、表序号　＝　1％1024 = 1;\n```\n\n这样的话，对于user_id＝262145，将被路由到第０个数据库的第１个表中。\n\n示意图如下：\n\n![这里写图片描述](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190824171416563-1109228018.png)\n\n## 五、分库分表总结\n\n关于分库分表策略的选择有很多种，上文中根据用户ID应该是比较简单的一种。其他方式比如使用号段进行分区或者直接使用hash进行路由等。有兴趣的可以自行查找学习。\n\n关于上文中提到的，如果用户的ID是通过UUID的方式生成的话，我们需要单独的进行一次hash操作，然后在进行取模操作等，其实hash本身就是一种分库分表的策略，使用hash进行路由策略的时候，我们需要知道的是，也就是hash路由策略的优缺点，优点是：数据分布均匀；缺点是：数据迁移的时候麻烦，不能按照机器性能分摊数据。\n\n上述的分库和分表操作，查询性能和并发能力都得到了提高，但是还有一些需要注意的就是，例如：原本跨表的事物变成了分布式事物；由于记录被切分到不同的数据库和不同的数据表中，难以进行多表关联查询，并且不能不指定路由字段对数据进行查询。分库分表之后，如果我们需要对系统进行进一步的扩阵容（路由策略变更），将变得非常不方便，需要我们重新进行数据迁移。\n\n* * *\n\n最后需要指出的是，分库分表目前有很多的中间件可供选择，最常见的是使用淘宝的中间件Cobar。\n\nGitHub地址：[https://github.com/alibaba/cobara](https://github.com/alibaba/cobara)\n\n文档地址为：[https://github.com/alibaba/cobar/wiki](https://github.com/alibaba/cobar/wiki)\n\n关于淘宝的中间件Cobar本篇内容不具体介绍，会在后边的学习中在做介绍。\n\n另外Spring也可以实现数据库的读写分离操作，后边的文章，会进一步学习。\n\n## 六、总结\n\n上述中，我们学到了如何进行数据库的读写分离和分库分表，那么，是不是可以实现一个可扩展、高性能、高并发的网站那？很显然还不可以!一个大型的网站使用到的技术远不止这些，可以说，这些都是其中的最基础的一个环节，因为还有很多具体的细节我们没有掌握到，比如：数据库的集群控制，集群的负载均衡，灾难恢复，故障自动切换，事务管理等等技术。因此，还有很多需要去学习去研究的地方。\n\n总之：\n\n```\n路漫漫其修远兮，吾将上下而求索。\n```\n\n前方道路美好而光明，2017年新征程，不泄步！\n\n## Mycat实现主从复制，读写分离，以及分库分表的实践\n\n### Mycat是什么\n\n一个彻底开源的，面向企业应用开发的大数据库集群\n\n支持事务、ACID、可以替代MySQL的加强版数据库\n\n一个可以视为MySQL集群的企业级数据库，用来替代昂贵的Oracle集群\n\n一个融合内存缓存技术、NoSQL技术、HDFS大数据的新型SQL Server\n\n结合传统数据库和新型分布式数据仓库的新一代企业级数据库产品\n\n一个新颖的数据库中间件产品\n\n以上内容来自[Mycat官网](http://www.mycat.io/)，简单来说，Mycat就是一个数据库中间件，对于我们开发来说，就像是一个代理，当我们需要使用到多个数据库和需要进行分库分表的时候，我们只需要在mycat里面配置好相关规则，程序无需做任何修改，只是需要将原本的数据源链接到mycat而已，当然如果以前有多个数据源，需要将数据源切换为单个数据源，这样有个好处就是当我们的数据量已经很大的时候，需要开始分库分表或者做读写分离的时候，不用修改代码（只需要改一下数据源的链接地址）\n\n**使用Mycat分表分库实践**\n\nhaha,首先这不是一篇入门Mycat的博客但小编感觉又很入门的博客!这篇博客主要讲解Mycat中数据分片的相关知识，同时小编将会在本机数据库上进行测试验证，图文并茂展示出来。\n\n数据库分区分表，咋一听非常地高大上，总有一种高高在上，望尘莫及的感觉，但小编想说的是，其实，作为一个开发人员，该来的总是会来，该学的东西你还是得学，区别只是时间先后顺序的问题。\n\n### 一、分区分表\n\n分区就是把一个数据表的文件和索引分散存储在不同的物理文件中。\n\nmysql支持的分区类型包括Range、List、Hash、Key，其中Range比较常用：\n\nRANGE分区：基于属于一个给定连续区间的列值，把多行分配给分区。\n\nLIST分区：类似于按RANGE分区，区别在于LIST分区是基于列值匹配一个离散值集合中的某个值来进行选择。\n\nHASH分区：基于用户定义的表达式的返回值来进行选择的分区，该表达式使用将要插入到表中的这些行的列值进行计算。这个函数可以包含MySQL 中有效的、产生非负整数值的任何表达式。\n\nKEY分区：类似于按HASH分区，区别在于KEY分区只支持计算一列或多列，且MySQL服务器提供其自身的哈希函数。必须有一列或多列包含整数值。\n\n分表是指在逻辑上将一个表拆分成多个逻辑表，在整体上看是一张表，分表有水平拆分和垂直拆分两种,举个例子，将一张大的存储商户信息的表按照商户号的范围进行分表，将不同范围的记录分布到不同的表中。\n\n### 二、Mycat 数据分片的种类\n\nMycat 的分片其实和分表差不多意思，就是当数据库过于庞大，尤其是写入过于频繁且很难由一台主机支撑是，这时数据库就会面临瓶颈。我们将存放在同一个数据库实例中的数据分散存放到多个数据库实例（主机）上，进行多台设备存取以提高性能，在切分数据的同时可以提高系统的整体性。\n\n数据分片是指将数据全局地划分为相关的逻辑片段，有水平切分、垂直切分、混合切分三种类型，下面主要讲下Mycat的水平和垂直切分。有一点很重要，那就是Mycat是分布式的，因此分出来的数据片分布到不同的物理机上是正常的，靠网络通信进行协作。\n\n水平切分\n\n就是按照某个字段的某种规则分散到多个节点库中，每个节点中包含一部分数据。可以将数据水平切分简单理解为按照数据行进行切分，就是将表中的某些行切分到一个节点，将另外某些行切分到其他节点，从分布式的整体来看它们是一个整体的表。\n\n垂直切分\n\n一个数据库由很多表构成，每个表对应不同的业务，垂直切分是指按照业务将表进行分类并分不到不同的节点上。垂直拆分简单明了，拆分规则明确，应用程序模块清晰、明确、容易整合，但是某个表的数据量达到一定程度后扩展起来比较困难。\n\n混合切分\n\n为水平切分和垂直切分的结合。\n\n### 三、Mycat 垂直切分、水平切分实战\n\n#### 1、垂直切分\n\n上面说到，垂直切分主要是根据具体业务来进行拆分的，那么，我们可以想象这么一个场景，假设我们有一个非常大的电商系统，那么我们需要将订单表、流水表、用户表、用户评论表等分别分不到不同的数据库中来提高吞吐量，架构图大概如下：\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190824171417040-1009330909.webp)\n\n\n由于小编是在一台机器上测试，因此就只有host1这个节点，但不同的表还是依旧对应不同的数据库，只不过是所有数据库属于同一个数据库实例（主机）而已，后期不同主机只需增加`<dataHost>`节点即可。\n\nmycat配置文件如下：\n\n`server.xml`\n\n```\n<user name=\"root\">\n    <property name=\"password\">root</property>\n    // 对应四个逻辑库\n    <property name=\"schemas\">order,trade,user,comment</property>\n</user>\n\n```\n\n`schema.xml`\n\n```\n<?xml version=\"1.0\"?>\n<!DOCTYPE mycat:schema SYSTEM \"schema.dtd\">\n<mycat:schema xmlns:mycat=\"http://io.mycat/\">\n\n    <!-- 4个逻辑库，对应4个不同的分片节点 -->\n    <schema name=\"order\" checkSQLschema=\"false\" sqlMaxLimit=\"100\" dataNode=\"database1\" />\n    <schema name=\"trade\" checkSQLschema=\"false\" sqlMaxLimit=\"100\" dataNode=\"database2\" />\n    <schema name=\"user\" checkSQLschema=\"false\" sqlMaxLimit=\"100\" dataNode=\"database3\" />\n    <schema name=\"comment\" checkSQLschema=\"false\" sqlMaxLimit=\"100\" dataNode=\"database4\" />\n\n    <!-- 四个分片，对应四个不同的数据库 -->\n    <dataNode name=\"database1\" dataHost=\"localhost1\" database=\"database1\" />\n    <dataNode name=\"database2\" dataHost=\"localhost1\" database=\"database2\" />\n    <dataNode name=\"database3\" dataHost=\"localhost1\" database=\"database3\" />\n    <dataNode name=\"database4\" dataHost=\"localhost1\" database=\"database4\" />\n\n    <!-- 实际物理主机，只有这一台 -->\n    <dataHost name=\"localhost1\" maxCon=\"1000\" minCon=\"10\" balance=\"0\"\n                writeType=\"0\" dbType=\"mysql\" dbDriver=\"native\" switchType=\"1\"  slaveThreshold=\"100\">\n        <heartbeat>select user()</heartbeat>\n        <writeHost host=\"hostM1\" url=\"localhost:3306\" user=\"root\"\n                password=\"root\">\n        </writeHost>\n    </dataHost>\n</mycat:schema>\n\n```\n\n登陆本机mysql，创建`order,trade,user,comment`4个数据库:\n\n```\ncreate database database1 character set utf8;\ncreate database database2 character set utf8;\ncreate database database3 character set utf8;\ncreate database database4 character set utf8;\n\n```\n\n执行`bin`目录下的`startup_nowrap.bat`文件，如果输出下面内容，则说明已经启动mycat成功，如果没有，请检查`order,trade,user,comment`4个数据库是否已经创建。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190824171417782-1932957742.webp)\n\n\n采用下面语句登陆Mycat服务器：\n\n`mysql -uroot -proot -P8066 -h127.0.0.1`\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190824171418203-391358451.webp)\n\n\n在`comment`数据库中创建`Comment`表，并插入一条数据\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1092007-20190824171418351-1853467698.webp)\n\n\n上图1处新建一个`Comment`表，2处插入一条记录，3处查看记录插入到哪个数据节点中，即`database4`。\n\n#### 2、水平切分\n\n`server.xml`\n\n```\n<user name=\"root\">\n    <property name=\"password\">root</property>\n    <property name=\"schemas\">TESTDB</property>\n</user>\n\n```\n\n`schema.xml`\n\n```\n<?xml version=\"1.0\"?>\n<!DOCTYPE mycat:schema SYSTEM \"schema.dtd\">\n<mycat:schema xmlns:mycat=\"http://io.mycat/\">\n    <schema name=\"TESTDB\" checkSQLschema=\"false\" sqlMaxLimit=\"100\">\n        <table name=\"travelrecord\" dataNode=\"dn1,dn2,dn3\" rule=\"auto-sharding-long\" />\n    </schema>\n\n    <dataNode name=\"dn1\" dataHost=\"localhost1\" database=\"db1\" />\n    <dataNode name=\"dn2\" dataHost=\"localhost1\" database=\"db2\" />\n    <dataNode name=\"dn3\" dataHost=\"localhost1\" database=\"db3\" />\n\n    <dataHost name=\"localhost1\" maxCon=\"1000\" minCon=\"10\" balance=\"0\"\n                writeType=\"0\" dbType=\"mysql\" dbDriver=\"native\" switchType=\"1\"  slaveThreshold=\"100\">\n    <heartbeat>select user()</heartbeat>\n    <!-- can have multi write hosts -->\n    <writeHost host=\"hostM1\" url=\"localhost:3306\" user=\"root\"\n       password=\"root\">\n    </writeHost>\n    </dataHost>\n</mycat:schema>\n\n```\n\n`rule.xml`\n\n```\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mycat:rule SYSTEM \"rule.dtd\">\n<mycat:rule xmlns:mycat=\"http://io.mycat/\">\n    <tableRule name=\"auto-sharding-long\">\n        <rule>\n            <columns>id</columns>\n            rang-long\n        </rule>\n    </tableRule>\n\n    <function name=\"rang-long\"\n            class=\"io.mycat.route.function.AutoPartitionByLong\">\n        <property name=\"mapFile\">autopartition-long.txt</property>\n    </function>\n</mycat:rule>\n\n```\n\n`conf`目录下的`autopartition-long.txt`\n\n```\n# range start-end ,data node index\n# K=1000,M=10000.\n0-500M=0\n500M-1000M=1\n1000M-1500M=2\n\n```\n\n上面的配置创建了一个名为`TESTDB`的逻辑库，并指定了需要切分的表`<table>`标签，表名为`travelrecord`,分区的策略采用`rang-long`算法，即根据`id`数据列值的范围进行切分，具体的规则在`autopartition-long.txt`文件中定义，即`id`在`0-500*10000`范围内的记录存放在`db1`的`travelrecord`表中，`id`在`500*10000 - 1000*10000`范围内的记录存放在`db2`数据库的`travelrecord`表中，下面我们插入两条数据，验证是否和分片规则一致。\n\n创建`db1,db2,db3`数据库\n\n```\ncreate database db1 character set utf8;\ncreate database db2 character set utf8;\ncreate database db3 character set utf8;\n\n```\n\n确实是这样的，到此我们就完成了mycat数据库的水平切分，这个例子只是演示按照id列值得范围进行切分，mycat还支持很多的分片算法，如取模、一致性哈希算法、按日期分片算法等等，大家可以看《分布式数据库架构及企业实战----基于Mycat中间件》这本书深入学习。\n\n#### 为什么需要读写分离\n\n至于为什么需要读写分离，在我之前的文章有介绍过了，相信看到这篇文章的人也知道为什么需要读写分离了，当然如果你也需要了解一下，那么欢迎查看我之前的文章[SpringBoot Mybatis 读写分离配置](http://raye.wang/2018/02/03/springboot-mybatis-du-xie-fen-chi-pei-zhi/),顺便也可以了解一下怎么通过代码进行读写分离的\n\n#### MySQL主从复制\n\n主从复制是读写分离的关键，不管通过什么方式进行读写分离，前提就是MySQL有主从复制，当前双机主从也行，但是关键的关键，是要能保证2个库的数据能一致（出掉刚写入主库从库还未能及时反应过来的情况），如果2个库的数据不一致，那么读写分离也有没有任何意义了，具体MySQL怎么做主从复制可以查看我之前的文章[MySQL主从复制搭建，基于日志（binlog）](http://raye.wang/2017/04/14/mysqlzhu-cong-fu-zhi-da-jian-ji-yu-ri-zhi-binlog/)\n\n#### Mycat读写分离设置\n\n##### 配置Mycat用户\n\nMycat的用户就跟MySQL用户是同一个意思，主要配置链接到Mycat的用户名以及密码，以及能使用的逻辑库，用户信息主要在server.xml中配置的，具体如下\n\n```\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>  \n<!-- - - Licensed under the Apache License, Version 2.0 (the \"License\");  \n    - you may not use this file except in compliance with the License. - You \n    may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 \n    - - Unless required by applicable law or agreed to in writing, software - \n    distributed under the License is distributed on an \"AS IS\" BASIS, - WITHOUT \n    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the \n    License for the specific language governing permissions and - limitations \n    under the License. -->\n<!DOCTYPE mycat:server SYSTEM \"server.dtd\">  \n<mycat:server xmlns:mycat=\"http://io.mycat/\">  \n    <system>\n    <property name=\"defaultSqlParser\">druidparser</property>\n      <!--  <property name=\"useCompression\">1</property>--> <!--1为开启mysql压缩协议-->\n    <!-- <property name=\"processorBufferChunk\">40960</property> -->\n    <!-- \n    <property name=\"processors\">1</property> \n    <property name=\"processorExecutor\">32</property> \n     -->\n        <!--默认是65535 64K 用于sql解析时最大文本长度 -->\n        <!--<property name=\"maxStringLiteralLength\">65535</property>-->\n        <!--<property name=\"sequnceHandlerType\">0</property>-->\n        <!--<property name=\"backSocketNoDelay\">1</property>-->\n        <!--<property name=\"frontSocketNoDelay\">1</property>-->\n        <!--<property name=\"processorExecutor\">16</property>-->\n        <!-- \n            <property name=\"mutiNodeLimitType\">1</property> 0：开启小数量级（默认） ；1：开启亿级数据排序\n            <property name=\"mutiNodePatchSize\">100</property> 亿级数量排序批量\n            <property name=\"processors\">32</property> <property name=\"processorExecutor\">32</property> \n            <property name=\"serverPort\">8066</property> <property name=\"managerPort\">9066</property> \n            <property name=\"idleTimeout\">300000</property> <property name=\"bindIp\">0.0.0.0</property> \n            <property name=\"frontWriteQueueSize\">4096</property> <property name=\"processors\">32</property> -->\n    </system>\n    <user name=\"raye\">\n        <property name=\"password\">rayewang</property>\n        <property name=\"schemas\">separate</property>\n    </user>\n\n        </host> \n</mycat:server>  \n\n```\n\n其中`<user name=\"raye\">`定义了一个名为raye的用户，标签user中的`<property name=\"password\">rayewang</property>`定义了用户的密码，`<property name=\"schemas\">separate</property>`定义了用户可以使用的逻辑库\n\n##### 配置Mycat逻辑库\n\nMycat的配置有很多，不过因为我们只是使用Mycat的读写分类的功能，所以用到的配置并不多，只需要配置一些基本的，当然本文也只是会介绍到读写分离相关的配置，其他配置建议读者自己查看一下文档，或者通过其他方式了解，逻辑库是在`schema.xml`中配置的\n\n首先介绍Mycat逻辑库中的一些配置标签\n\n###### schema\n\n`schema`标签是用来定义逻辑库的，`schema`有四个属性`dataNode`,`checkSQLschema`,`sqlMaxLimit`,`name`\n\n`dataNode`标签属性用于绑定逻辑库到某个具体的 database 上，1.3 版本如果配置了 dataNode，则不可以配置分片表，1.4 可以配置默认分片，只需要配置需要分片的表即可\n\n`name`是定义当前逻辑库的名字的，方便`server.xml`中定义用户时的引用\n\n`checkSQLschema`当该值设置为 true 时，如果我们执行语句select * from separate.users;则 MyCat 会把语句修改 为select * from users;。即把表示 schema 的字符去掉，避免发送到后端数据库执行时报（ERROR 1146 (42S02): Table ‘separate.users’ doesn’t exist）。不过，即使设置该值为 true ，如果语句所带的是并非是 schema 指定的名字，例如：select * from db1.users;那么 MyCat 并不会删除 db1 这个字段，如果没有定义该库的话则会报错，所以在提供 SQL语句的最好是不带这个字段。\n\n`sqlMaxLimit`当该值设置为某个数值时。每条执行的 SQL 语句，如果没有加上 limit 语句，MyCat 也会自动的加上所对应的值。例如设置值为 100，执行select * from users;的效果为和执行select * from users limit 100;相同。设置该值的话，MyCat 默认会把查询到的信息全部都展示出来，造成过多的输出。所以，在正常使用中，还是建议加上一个值，用于减少过多的数据返回。当然 SQL 语句中也显式的指定 limit 的大小，不受该属性的约束。需要注意的是，如果运行的 schema 为非拆分库的，那么该属性不会生效。需要手动添加 limit 语句。\n\n`schema`标签中有标签`table`用于定义不同的表分片信息，不过我们只是做读写分离，并不会用到，所以这里就不多介绍了\n\n###### dataNode\n\n`dataNode`dataNode 标签定义了 MyCat 中的数据节点，也就是我们通常说所的数据分片。一个 dataNode 标签就是一个独立的数据分片,`dataNode`有3个属性:`name`,`dataHost`,`database`。\n\n`name`定义数据节点的名字，这个名字需要是唯一的，此名字是用于`table`标签和`schema`标签中引用的\n\n`dataHost`该属性用于定义该分片属于哪个数据库实例的，属性值是引用 dataHost 标签上定义的 name 属性\n\n`database`该属性用于定义该分片属性哪个具体数据库实例上的具体库，因为这里使用两个纬度来定义分片，就是：实例+具体的库。因为每个库上建立的表和表结构是一样的。所以这样做就可以轻松的对表进行水平拆分\n\n###### dataHost\n\n`dataHost`是定义真实的数据库连接的标签，该标签在 mycat 逻辑库中也是作为最底层的标签存在，直接定义了具体的数据库实例、读写分离配置和心跳语句，`dataHost`有7个属性：`name`,`maxCon`,`minCon`,`balance`,`writeType`,`dbType`,`dbDriver`,有2个标签`heartbeat`,`writeHost`,其中`writeHost`标签中又包含一个`readHost`标签\n\n`name`唯一标识 dataHost 标签，供`dataNode`标签使用\n\n`maxCon`指定每个读写实例连接池的最大连接。也就是说，标签内嵌套的 writeHost、readHost 标签都会使用这个属性的值来实例化出连接池的最大连接数\n\n`minCon`指定每个读写实例连接池的最小连接，初始化连接池的大小\n\n`balance`读取负载均衡类型\n\n1.  balance=\"0\", 不开启读写分离机制，所有读操作都发送到当前可用的 writeHost 上。\n\n2.  balance=\"1\"，全部的 readHost 与 stand by writeHost 参与 select 语句的负载均衡，简单的说，当双主双从模式(M1->S1，M2->S2，并且 M1 与 M2 互为主备)，正常情况下，M2,S1,S2 都参与 select 语句的负载均衡。\n\n3.  balance=\"2\"，所有读操作都随机的在 writeHost、readhost 上分发。\n\n4.  balance=\"3\"，所有读请求随机的分发到 wiriterHost 对应的 readhost 执行，writerHost 不负担读压力\n\n`writeType`写入负载均衡类型，目前的取值有 3 种：\n\n1.  writeType=\"0\", 所有写操作发送到配置的第一个 writeHost，第一个挂了切到还生存的第二个writeHost，重新启动后已切换后的为准，切换记录在配置文件中:dnindex.properties .\n\n2.  writeType=\"1\"，所有写操作都随机的发送到配置的 writeHost\n\n`dbType`指定后端连接的数据库类型，目前支持二进制的 mysql 协议，还有其他使用 JDBC 连接的数据库。例如：mongodb、oracle、spark 等\n\n`dbDriver`指定连接后端数据库使用的 Driver，目前可选的值有 native 和 JDBC。使用 native 的话，因为这个值执行的 是二进制的 mysql 协议，所以可以使用 mysql 和 maridb。其他类型的数据库则需要使用 JDBC 驱动来支持。从 1.6 版本开始支持 postgresql 的 native 原始协议。 如果使用 JDBC 的话需要将符合 JDBC 4 标准的驱动 JAR 包放到 MYCAT\\lib 目录下，并检查驱动 JAR 包中包括如下目录结构的文件：META-INF\\services\\java.sql.Driver。在这个文件内写上具体的 Driver 类名，例如： com.mysql.jdbc.Driver。\n\n`heartbeat`这个标签内指明用于和后端数据库进行心跳检查的语句。例如,MYSQL 可以使用 select user()，Oracle 可以使用 select 1 from dual 等。 这个标签还有一个 connectionInitSql 属性，主要是当使用 Oracla 数据库时，需要执行的初始化 SQL 语句就这个放到这里面来。例如：alter session set nlsdateformat='yyyy-mm-dd hh24:mi:ss'\n\n`writeHost`，`readHost`这两个标签都指定后端数据库的相关配置给 mycat，用于实例化后端连接池。唯一不同的是，writeHost 指定写实例、readHost 指定读实例，组着这些读写实例来满足系统的要求。 在一个 dataHost 内可以定义多个 writeHost 和 readHost。但是，如果 writeHost 指定的后端数据库宕机，那么这个 writeHost 绑定的所有 readHost 都将不可用。另一方面，由于这个 writeHost 宕机系统会自动的检测到，并切换到备用的 writeHost 上去,这2个标签属性都一致，拥有`host`,`url`,`password`,`user`,`weight`,`usingDecrypt`等属性\n\n`host`用于标识不同实例，一般 writeHost 我们使用M1，readHost 我们用S1\n\n`url`真实数据库的实例的链接地址，如果是使用 native 的 dbDriver，则一般为 address:port 这种形式。用 JDBC 或其他的dbDriver，则需要特殊指定。当使用 JDBC 时则可以这么写：jdbc:mysql://localhost:3306/\n\n`user`真实数据库实例的链接用户名\n\n`password`真实数据库实例的链接密码\n\n`weight`权重 配置在 readhost 中作为读节点的权重,主要用于多台读取的数据库实例机器配置不同的情况，可以根据权重调整访问量\n\n`usingDecrypt`是否对密码加密默认 0 否 如需要开启配置 1，同时使用加密程序对密码加密\n\n注意，readHost是在writeHost标签内的，不是单独的\n\n以下是我的读写分离配置文件\n\n```\n<?xml version=\"1.0\"?>  \n<!DOCTYPE mycat:schema SYSTEM \"schema.dtd\">  \n<mycat:schema xmlns:mycat=\"http://io.mycat/\">\n\n    <schema name=\"separate\" checkSQLschema=\"false\" sqlMaxLimit=\"100\" dataNode=\"dn1\"/>\n    <dataNode name=\"dn1\" dataHost=\"localhost1\" database=\"test\" />\n\n    <dataHost name=\"localhost1\" maxCon=\"1000\" minCon=\"10\" balance=\"3\"\n              writeType=\"0\" dbType=\"mysql\" dbDriver=\"native\" switchType=\"1\"  slaveThreshold=\"100\">\n        <heartbeat>select user()</heartbeat>\n        <!-- can have multi write hosts -->\n        <writeHost host=\"hostM1\" url=\"192.168.1.126:3307\" user=\"root\"\n                   password=\"123456\">\n            <!-- can have multi read hosts -->\n            <readHost host=\"hostS2\" url=\"192.168.1.126:3308\" user=\"root\" password=\"123456\" />\n        </writeHost>\n\n    </dataHost>\n\n</mycat:schema>  \n\n```\n\n前面已经差不多都解释清楚了，因为我只是用的基本的主从复制，所以我的将`dataHost`的`balance`设置成了3\n\n启动mycat，然后用数据库连接工具连接到mycat，可以测试是否配置成功，最简单的就是通过修改从库的数据，这样方便查看到底是运行到哪个库上面了，另外由于我是基于docker启动的mycat，所以如果是直接在系统中运行的mycat的，可以去看官方文档，看看到底怎么启动mycat\n"
  },
  {
    "path": "docs/database/重新学习MySQL数据库：Mysql存储引擎与数据存储原理.md",
    "content": "# 目录\n\n  * [**一、 MySQL记录存储（页为单位）**](#一、-mysql记录存储（页为单位）)\n    * [**页头**](#页头)\n    * [**虚记录**](#虚记录)\n    * [**记录堆**](#记录堆)\n    * [**自由空间链表**](#自由空间链表)\n    * [**未分配空间**](#未分配空间)\n    * [**Slot区**](#slot区)\n    * [**页内记录维护**](#页内记录维护)\n  * [**二、 MySQL InnoDB存储引擎内存管理**](#二、-mysql-innodb存储引擎内存管理)\n    * [**预分配内存空间**](#预分配内存空间)\n    * [**数据以页为单位加载 （减少io访问次数）**](#数据以页为单位加载-（减少io访问次数）)\n    * [**数据内外存交换**](#数据内外存交换)\n    * [**页面管理**](#页面管理)\n    * [**页面淘汰**](#页面淘汰)\n    * [**全表扫描对内存的影响？**](#全表扫描对内存的影响？)\n    * [**页面淘汰**](#页面淘汰-1)\n    * [**位置移动**](#位置移动)\n  * [**三、MySQL事务实现原理**](#三、mysql事务实现原理)\n    * [**1、事务特性**](#1、事务特性)\n    * [**2、并发问题**](#2、并发问题)\n    * [**3、隔离级别**](#3、隔离级别)\n    * [**MySQL事务实现原理（事务管理机制）**](#mysql事务实现原理（事务管理机制）)\n    * [**1、MVCC 多版本并发控制**](#1、mvcc-多版本并发控制)\n    * [**2、undo log**](#2、undo-log)\n    * [**3、redo log**](#3、redo-log)\n    * [**意义**](#意义)\n  * [**四、MySQL锁实现原理**](#四、mysql锁实现原理)\n\n\n## **一、 MySQL记录存储（页为单位）**\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405205306.png)\n\n### **页头**\n\n记录页面的控制信息，共占56字节，包括页的左右兄弟页面指针、页面空间使用情况等。\n\n### **虚记录**\n\n最大虚记录：比页内最大主键还大 最小虚记录：比页内最小主键还小 (作用：比如说我们要查看一个记录是否在这个页面里，就要看这个记录是否在最大最小虚记录范围内)\n\n### **记录堆**\n\n行记录存储区，分为有效记录和已删除记录两种\n\n### **自由空间链表**\n\n已删除记录组成的链表 (重复利用空间)\n\n### **未分配空间**\n\n页面未使用的存储空间；\n\n### **Slot区**\n\n页尾 页面最后部分，占8个字节，主要存储页面的校验信息；\n\n### **页内记录维护**\n\n**顺序保证**\n\n物理有序(利于查询，不利于插入删除)\n\n![img](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-db41a4ce70476cb846cbcd92d3cb6efd_720w.webp)\n\n**逻辑有序(插入删除性能高，查询效率低)** 默认\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405205330.png)\n\n所以`MySQL`是像下图所示这样子有序的组织数据的。\n\n![img](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-5c927bccdfd6815a65b5bb1aebb13aae_720w.webp)\n\n**2、插入策略**\n\n**自由空间链表**（优先利用自由空间链表）\n\n**未使用空间**\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405205347.png)\n\n**3、页内查询**\n\n**遍历**\n\n**二分查找(数据不一样大，不能用二分)**\n\n![img](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-0b09abe7fa1b04a6274003c2dd08f733_720w.webp)\n\n利用槽位做二分，实现近似的二分查找，近似于跳表 。\n\n## **二、 MySQL InnoDB存储引擎内存管理**\n\n### **预分配内存空间**\n\n内存池\n\n### **数据以页为单位加载 （减少io访问次数）**\n\n内存页面管理\n\n- 页面映射（记录哪块磁盘上的数据加载到哪块内存上了）\n- 页面数据管理\n\n### **数据内外存交换**\n\n数据淘汰\n\n- 内存页耗尽\n- 需要加载新数据\n\n![img](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-369ec920b95324b4f2359cbe505c9a75_720w.webp)\n\n### **页面管理**\n\n- 空闲页\n- 数据页\n- 脏页（需刷回磁盘）\n\n### **页面淘汰**\n\nLRU(淘汰冷数据)\n\n![img](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-065e91777cbc08461d0e5a6d6c5de606_720w.webp)\n\n\n\n```\n某时刻状态->访问P2->访问新页P7\n```\n\n### **全表扫描对内存的影响？**\n\n可能会把内存中的热数据淘汰掉（比如说对一个几乎没有访问量的表进行全表扫描）\n\n所以`MySQL`不是单纯的利用`LRU算法`\n\n解决问题：如何避免热数据被淘汰？\n\n解决方案：访问时间 + 频率(redis)\n\n两个`LRU`表\n\n`MySQL`的解决方案\n\n![img](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-9bf6f401a22185ae7852b596f58c0fa4_720w.webp)\n\nMySQL内存管理—LRU\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405205410.png)\n\n\n**页面装载**\n\n磁盘数据到内存\n\n![img](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-3ad4cc1b1a83d8a7c0ecc8f7b9befd12_720w.webp)\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405205420.png)\n\n没有空闲页怎么办？`Free list中取 > LRU中淘汰 > LRU Flush`\n\n### **页面淘汰**\n\n`LRU`尾部淘汰`Flush LRU`淘汰\n\n`LRU`链表中将第一个脏页刷盘并“释放”，放到`LRU`尾部？直接放`FreeList`？\n\n### **位置移动**\n\nold 到 new new 到 old\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405205431.png)\n\n思考：移动时机是什么？`innodb_old_blocks_time`old区存活时间，大于此值，有机会进入new区\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405205437.png)\n\n`LRU_new`的操作 链表操作效率很高，有访问移动到表头？`Lock！！！MySQL`设计思路：减少移动次数\n\n两个重要参考：1、`freed_page_clock：Buffer Pool`淘汰页数 2、`LRU_new`长度1/4\n\n当前`freed_page_clock` - 上次移动到Header时`freed_page_clock>LRU_new长度1/4`\n\n\n\n## **三、MySQL事务实现原理**\n\nMySQL事务基本概念。\n\n### **1、事务特性**\n\nA（`Atomicity`原子性）：全部成功或全部失败\n\nI（`Isolation`隔离性）：并行事务之间互不干扰\n\nD（`Durability`持久性）：事务提交后，永久生效\n\nC（`Consistency`一致性）：通过AID保证\n\n### **2、并发问题**\n\n脏读(`Drity Read`)：读取到未提交的数据\n\n不可重复读(`Non-repeatable read`)：两次读取结果不同\n\n幻读(`Phantom Read`)：select 操作得到的结果所表征的数据状态无法支撑后续的业务操作\n\n### **3、隔离级别**\n\n`Read Uncommitted`（未提交读）：最低隔离级别，会读取到其他事务未提交的数据。脏读；\n\n`Read Committed`（提交读）：事务过程中可以读取到其他事务已提交的数据。不可重复读；\n\n`Repeatable Read`（可重复读）：每次读取相同结果集，不管其他事务是否提交，幻读；（两次当前读不会产生幻读）\n\n`Serializable`（串行化）：事务排队，隔离级别最高，性能最差；\n\n### **MySQL事务实现原理（事务管理机制）**\n\n### **1、MVCC 多版本并发控制**\n\n解决读-写冲突 如何工作：隐藏列\n\n–当前读（读在存储引擎中存储的那个数据）\n\n![img](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-241cfc7589ea0de0cac531355324d0a7_720w.webp)\n\nRR级别下\n\n![img](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-70c832b4a5b81c944c99a96557bcddd6_720w.webp)\n\n### **2、undo log**\n\n回滚日志 保证事务原子性 实现数据多版本`delete undo log`：用于回滚，提交即清理；`update undo log`：用于回滚，同时实现快照读，不能随便删除\n\n![img](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-1e9ecbc408164c034a73c3415aa9105d_720w.webp)\n\n思考：undolog如何清理？依据系统活跃的最小活跃事务ID Read view 为什么InnoDB count（*）这么慢？因为\n\n### **3、redo log**\n\n实现事务持久性\n\n![img](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-029212b1ddd7fb111cefdfe9e6648bb7_720w.webp)\n\n写入流程 l 记录页的修改，状态为prepare l 事务提交，讲事务记录为commit状态\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405205454.png)\n\n![img](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-7d685adf553d18f2e8dcdcd632d8696f_720w.webp)\n\n### **意义**\n\n体积小，记录页的修改，比写入页代价低 末尾追加，随机写变顺序写，发生改变的页不固定\n\n## **四、MySQL锁实现原理**\n\n![img](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-44403b5fcb1bd988f68f72172c95627b_720w.webp)\n\n所有当前读加排他锁，都有哪些是当前读？`SELECT FOR UPDATEUPDATEDELETE`\n\n![img](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-6161f7636962904b6e17d57e1a2540dc_720w.webp)\n\n![img](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-c6b3c1d5c628b0c2fc104421ddc0faad_720w.webp)\n\n唯一索引/非唯一索引 `* RC/RR`4种情况逐一分析\n\n![img](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-323f7b0fc7e2bfbdee1bb19e8f7dea0f_720w.webp)\n\n会出现幻读问题，不可重复读了\n\n![img](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-3e9c8bda25b0b6788952f5024819e4ff_720w.webp)\n\n![img](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-7f9ba333592f1253ffa6adf619448aaf_720w.webp)\n\n![img](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-55b01d4fd841e1c417004c86a80746b7_720w.webp)\n\n![img](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-b155fff4fd294066127c2e6c2650f559_720w.webp)\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405205509.png)\n\n![img](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-e87ad5dc811f52789e20adb3b53b8ba1_720w.webp)\n\n死锁在库表中有记录，通过kill 那个锁删除。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/%E5%86%B3%E5%AE%9A%E7%89%88.jpeg)\n"
  },
  {
    "path": "docs/database/重新学习MySQL数据库：Mysql索引实现原理和相关数据结构算法.md",
    "content": "\n# 目录\n\n* [MySQL索引](#mysql索引)\n  * [一、简介](#一、简介)\n  * [二、语句](#二、语句)\n  * [三、索引类型](#三、索引类型)\n  * [四、缺点](#四、缺点)\n  * [五、注意事项](#五、注意事项)\n* [摘要](#摘要)\n* [数据结构及算法基础](#数据结构及算法基础)\n  * [索引的本质](#索引的本质)\n  * [B-Tree和B+Tree](#b-tree和btree)\n    * [B-Tree](#b-tree)\n    * [B+Tree](#btree)\n    * [带有顺序访问指针的B+Tree](#带有顺序访问指针的btree)\n  * [为什么使用B-Tree（B+Tree）](#为什么使用b-tree（btree）)\n    * [主存存取原理](#主存存取原理)\n    * [磁盘存取原理](#磁盘存取原理)\n    * [局部性原理与磁盘预读](#局部性原理与磁盘预读)\n    * [B-/+Tree索引的性能分析](#b-tree索引的性能分析)\n  * [MySQL索引实现](#mysql索引实现)\n    * [MyISAM索引实现](#myisam索引实现)\n    * [InnoDB索引实现](#innodb索引实现)\n    * [索引使用策略及优化](#索引使用策略及优化)\n    * [示例数据库](#示例数据库)\n    * [最左前缀原理与相关优化](#最左前缀原理与相关优化)\n    * [情况一：全列匹配。](#情况一：全列匹配。)\n    * [情况二：最左前缀匹配。](#情况二：最左前缀匹配。)\n    * [情况三：查询条件用到了索引中列的精确匹配，但是中间某个条件未提供。](#情况三：查询条件用到了索引中列的精确匹配，但是中间某个条件未提供。)\n    * [情况四：查询条件没有指定索引第一列。](#情况四：查询条件没有指定索引第一列。)\n    * [情况五：匹配某列的前缀字符串。](#情况五：匹配某列的前缀字符串。)\n    * [情况六：范围查询。](#情况六：范围查询。)\n    * [情况七：查询条件中含有函数或表达式。](#情况七：查询条件中含有函数或表达式。)\n  * [索引选择性与前缀索引](#索引选择性与前缀索引)\n  * [InnoDB的主键选择与插入优化](#innodb的主键选择与插入优化)\n* [后记](#后记)\n* [参考文献](#参考文献)\n\n\n本文转自互联网\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本也将整理到我的个人博客：\n\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n本文是《重新学习MySQL数据库》系列其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，从sql基本的使用方法，从MySQL执行引擎再到索引、事务等知识，一步步地学习MySQL相关技术的实现原理，更好地了解如何基于这些知识来优化sql，减少SQL执行时间，通过执行计划对SQL性能进行分析，再到MySQL的主从复制、主备部署等内容，以便让你更完整地了解整个MySQL方面的技术体系，形成自己的知识框架。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n## MySQL索引\n\n### 一、简介\n\nMySQL目前主要有以下几种索引类型：\n1.普通索引\n2.唯一索引\n3.主键索引\n4.组合索引\n5.全文索引\n\n### 二、语句\n\n\n\nCREATE TABLE table_name[col_name data type]\n[unique|fulltext][index|key][index_name](col_name[length])[asc|desc]\n\n1.unique|fulltext为可选参数，分别表示唯一索引、全文索引\n2.index和key为同义词，两者作用相同，用来指定创建索引\n3.col_name为需要创建索引的字段列，该列必须从数据表中该定义的多个列中选择\n4.index_name指定索引的名称，为可选参数，如果不指定，默认col_name为索引值\n5.length为可选参数，表示索引的长度，只有字符串类型的字段才能指定索引长度\n6.asc或desc指定升序或降序的索引值存储\n\n### 三、索引类型\n\n1.普通索引\n是最基本的索引，它没有任何限制。它有以下几种创建方式：\n（1）直接创建索引\n\n````\n\nCREATE INDEX index_name ON table(column(length))\n````\n\n\n（2）修改表结构的方式添加索引\n\n\n````\nALTER TABLE table_name ADD INDEX index_name ON (column(length))\n````\n\n\n（3）创建表的时候同时创建索引\n\n````\nCREATE TABLE `table` (\n    `id` int(11) NOT NULL AUTO_INCREMENT ,\n    `title` char(255) CHARACTER NOT NULL ,\n    `content` text CHARACTER NULL ,\n    `time` int(10) NULL DEFAULT NULL , PRIMARY KEY (`id`), INDEX index_name (title(length))\n)\n````\n\n\n（4）删除索引\n\n\n````\nDROP INDEX index_name ON table\n````\n\n\n2.唯一索引\n与前面的普通索引类似，不同的就是：索引列的值必须唯一，但允许有空值。如果是组合索引，则列值的组合必须唯一。它有以下几种创建方式：\n（1）创建唯一索引\n\n\n````\nCREATE UNIQUE INDEX indexName ON table(column(length))\n````\n\n\n（2）修改表结构\n\n\n````\nALTER TABLE table_name ADD UNIQUE indexName ON (column(length))\n````\n\n\n（3）创建表的时候直接指定\n\n````\nCREATE TABLE `table` (\n    `id` int(11) NOT NULL AUTO_INCREMENT ,\n    `title` char(255) CHARACTER NOT NULL ,\n    `content` text CHARACTER NULL ,\n    `time` int(10) NULL DEFAULT NULL , UNIQUE indexName (title(length))\n);\n````\n\n\n\n3.主键索引\n是一种特殊的唯一索引，一个表只能有一个主键，不允许有空值。一般是在建表的时候同时创建主键索引：\n\n````\nCREATE TABLE `table` (\n    `id` int(11) NOT NULL AUTO_INCREMENT ,\n    `title` char(255) NOT NULL , PRIMARY KEY (`id`)\n);\n````\n\n\n4.组合索引\n指多个字段上创建的索引，只有在查询条件中使用了创建索引时的第一个字段，索引才会被使用。使用组合索引时遵循最左前缀集合\n\n\n````\nALTER TABLE `table` ADD INDEX name_city_age (name,city,age); \n````\n\n\n5.全文索引\n主要用来查找文本中的关键字，而不是直接与索引中的值相比较。fulltext索引跟其它索引大不相同，它更像是一个搜索引擎，而不是简单的where语句的参数匹配。fulltext索引配合match against操作使用，而不是一般的where语句加like。\n\n它可以在create table，alter table ，create index使用，不过目前只有char、varchar，text 列上可以创建全文索引。值得一提的是，在数据量较大时候，现将数据放入一个没有全局索引的表中，然后再用CREATE index创建fulltext索引，要比先为一张表建立fulltext然后再将数据写入的速度快很多。\n（1）创建表的适合添加全文索引\n\n````\nCREATE TABLE `table` (\n    `id` int(11) NOT NULL AUTO_INCREMENT ,\n    `title` char(255) CHARACTER NOT NULL ,\n    `content` text CHARACTER NULL ,\n    `time` int(10) NULL DEFAULT NULL , PRIMARY KEY (`id`),\n    FULLTEXT (content)\n);\n````\n\n\n（2）修改表结构添加全文索引\n\n\n````\nALTER TABLE article ADD FULLTEXT index_content(content)\n````\n\n\n（3）直接创建索引\n\n\n````\nCREATE FULLTEXT INDEX index_content ON article(content)\n````\n\n\n### 四、缺点\n\n1.虽然索引大大提高了查询速度，同时却会降低更新表的速度，如对表进行insert、update和delete。因为更新表时，不仅要保存数据，还要保存一下索引文件。\n\n2.建立索引会占用磁盘空间的索引文件。一般情况这个问题不太严重，但如果你在一个大表上创建了多种组合索引，索引文件的会增长很快。\n索引只是提高效率的一个因素，如果有大数据量的表，就需要花时间研究建立最优秀的索引，或优化查询语句。\n\n### 五、注意事项\n\n使用索引时，有以下一些技巧和注意事项：\n\n1.索引不会包含有null值的列\n只要列中包含有null值都将不会被包含在索引中，复合索引中只要有一列含有null值，那么这一列对于此复合索引就是无效的。所以我们在数据库设计时不要让字段的默认值为null。\n\n2.使用短索引\n对串列进行索引，如果可能应该指定一个前缀长度。例如，如果有一个char(255)的列，如果在前10个或20个字符内，多数值是惟一的，那么就不要对整个列进行索引。短索引不仅可以提高查询速度而且可以节省磁盘空间和I/O操作。\n\n3.索引列排序\n查询只使用一个索引，因此如果where子句中已经使用了索引的话，那么order by中的列是不会使用索引的。因此数据库默认排序可以符合要求的情况下不要使用排序操作；尽量不要包含多个列的排序，如果需要最好给这些列创建复合索引。\n\n4.like语句操作\n一般情况下不推荐使用like操作，如果非使用不可，如何使用也是一个问题。like “%aaa%” 不会使用索引而like “aaa%”可以使用索引。\n5.不要在列上进行运算\n这将导致索引失效而进行全表扫描，例如\n\n\n````\nSELECT * FROM table_name WHERE YEAR(column_name)<2017;\n````\n\n\n6.不使用not in和<>操作\n\n# 摘要\n\n本文以MySQL数据库为研究对象，讨论与数据库索引相关的一些话题。特别需要说明的是，MySQL支持诸多存储引擎，而各种存储引擎对索引的支持也各不相同，因此MySQL数据库支持多种索引类型，如BTree索引，哈希索引，全文索引等等。为了避免混乱，本文将只关注于BTree索引，因为这是平常使用MySQL时主要打交道的索引，至于哈希索引和全文索引本文暂不讨论。\n\n文章主要内容分为三个部分。\n\n第一部分主要从数据结构及算法理论层面讨论MySQL数据库索引的数理基础。\n\n第二部分结合MySQL数据库中MyISAM和InnoDB数据存储引擎中索引的架构实现讨论聚集索引、非聚集索引及覆盖索引等话题。\n\n第三部分根据上面的理论基础，讨论MySQL中高性能使用索引的策略。\n\n# 数据结构及算法基础\n\n## 索引的本质\n\nMySQL官方对索引的定义为：索引（Index）是帮助MySQL高效获取数据的数据结构。提取句子主干，就可以得到索引的本质：索引是数据结构。\n\n我们知道，数据库查询是数据库的最主要功能之一。我们都希望查询数据的速度能尽可能的快，因此数据库系统的设计者会从查询算法的角度进行优化。最基本的查询算法当然是[顺序查找](http://en.wikipedia.org/wiki/Linear_search)（linear search），这种复杂度为O(n)的算法在数据量很大时显然是糟糕的，好在计算机科学的发展提供了很多更优秀的查找算法，例如[二分查找](http://en.wikipedia.org/wiki/Binary_search_algorithm)（binary search）、[二叉树查找](http://en.wikipedia.org/wiki/Binary_search_tree)（binary tree search）等。如果稍微分析一下会发现，每种查找算法都只能应用于特定的数据结构之上，例如二分查找要求被检索数据有序，而二叉树查找只能应用于[二叉查找树](http://en.wikipedia.org/wiki/Binary_search_tree)上，但是数据本身的组织结构不可能完全满足各种数据结构（例如，理论上不可能同时将两列都按顺序进行组织），所以，在数据之外，数据库系统还维护着满足特定查找算法的数据结构，这些数据结构以某种方式引用（指向）数据，这样就可以在这些数据结构上实现高级查找算法。这种数据结构，就是索引。\n\n看一个例子：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1.png)\n\n图1\n\n图1展示了一种可能的索引方式。左边是数据表，一共有两列七条记录，最左边的是数据记录的物理地址（注意逻辑上相邻的记录在磁盘上也并不是一定物理相邻的）。为了加快Col2的查找，可以维护一个右边所示的二叉查找树，每个节点分别包含索引键值和一个指向对应数据记录物理地址的指针。\n\n虽然这是一个货真价实的索引，但是实际的数据库系统几乎没有使用二叉查找树或其进化品种[红黑树](http://en.wikipedia.org/wiki/Red-black_tree)（red-black tree）实现的，原因会在下文介绍。\n\n## B-Tree和B+Tree\n\n目前大部分数据库系统及文件系统都采用B-Tree或其变种B+Tree作为索引结构，在本文的下一节会结合存储器原理及计算机存取原理讨论为什么B-Tree和B+Tree在被如此广泛用于索引，这一节先单纯从数据结构角度描述它们。\n\n### B-Tree\n\n为了描述B-Tree，首先定义一条数据记录为一个二元组[key, data]，key为记录的键值，对于不同数据记录，key是互不相同的；data为数据记录除key外的数据。那么B-Tree是满足下列条件的数据结构：\n\nd为大于1的一个正整数，称为B-Tree的度。\n\nh为一个正整数，称为B-Tree的高度。\n\n每个非叶子节点由n-1个key和n个指针组成，其中d<=n<=2d。\n\n每个叶子节点最少包含一个key和两个指针，最多包含2d-1个key和2d个指针，叶节点的指针均为null 。\n\n所有叶节点具有相同的深度，等于树高h。\n\nkey和指针互相间隔，节点两端是指针。\n\n一个节点中的key从左到右非递减排列。\n\n所有节点组成树结构。\n\n每个指针要么为null，要么指向另外一个节点。\n\n如果某个指针在节点node最左边且不为null，则其指向节点的所有key小于。\n\n图2是一个d=2的B-Tree示意图。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/2.png)\n\n图2\n\n由于B-Tree的特性，在B-Tree中按key检索数据的算法非常直观：首先从根节点进行二分查找，如果找到则返回对应节点的data，否则对相应区间的指针指向的节点递归进行查找，直到找到节点或找到null指针，前者查找成功，后者查找失败。B-Tree上查找算法的伪代码如下：\n\n\n\n````\nBTree_Search(node, key)  {\nif(node ==  null)  return  null;\nforeach(node.key)\n{\nif(node.key[i]  == key)  return node.data[i];\nif(node.key[i]  > key)  return  BTree_Search(point[i]->node);\n}\nreturn  BTree_Search(point[i+1]->node);\n}\n data =  BTree_Search(root, my_key);\n````\n\n\n关于B-Tree有一系列有趣的性质，例如一个度为d的B-Tree，设其索引N个key，从这点可以看出，B-Tree是一个非常有效率的索引数据结构。\n\n另外，由于插入删除新的数据记录会破坏B-Tree的性质，因此在插入删除时，需要对树进行一个分裂、合并、转移等操作以保持B-Tree性质，本文不打算完整讨论B-Tree这些内容，因为已经有许多资料详细说明了B-Tree的数学性质及插入删除算法，有兴趣的朋友可以在本文末的参考文献一栏找到相应的资料进行阅读。\n\n### B+Tree\n\nB-Tree有许多变种，其中最常见的是B+Tree，例如MySQL就普遍使用B+Tree实现其索引结构。\n\n与B-Tree相比，B+Tree有以下不同点：\n\n每个节点的指针上限为2d而不是2d+1。\n\n内节点不存储data，只存储key；叶子节点不存储指针。\n\n图3是一个简单的B+Tree示意。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/3.png)\n\n图3\n\n由于并不是所有节点都具有相同的域，因此B+Tree中叶节点和内节点一般大小不同。这点与B-Tree不同，虽然B-Tree中不同节点存放的key和指针可能数量不一致，但是每个节点的域和上限是一致的，所以在实现中B-Tree往往对每个节点申请同等大小的空间。\n\n一般来说，B+Tree比B-Tree更适合实现外存储索引结构，具体原因与外存储器原理及计算机存取原理有关，将在下面讨论。\n\n### 带有顺序访问指针的B+Tree\n\n一般在数据库系统或文件系统中使用的B+Tree结构都在经典B+Tree的基础上进行了优化，增加了顺序访问指针。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/4.png)\n\n如图4所示，在B+Tree的每个叶子节点增加一个指向相邻叶子节点的指针，就形成了带有顺序访问指针的B+Tree。做这个优化的目的是为了提高区间访问的性能，例如图4中如果要查询key为从18到49的所有数据记录，当找到18后，只需顺着节点和指针顺序遍历就可以一次性访问到所有数据节点，极大提到了区间查询效率。\n\n这一节对B-Tree和B+Tree进行了一个简单的介绍，下一节结合存储器存取原理介绍为什么目前B+Tree是数据库系统实现索引的首选数据结构。\n\n## 为什么使用B-Tree（B+Tree）\n\n上文说过，红黑树等数据结构也可以用来实现索引，但是文件系统及数据库系统普遍采用B-/+Tree作为索引结构，这一节将结合计算机组成原理相关知识讨论B-/+Tree作为索引的理论基础。\n\n一般来说，索引本身也很大，不可能全部存储在内存中，因此索引往往以索引文件的形式存储的磁盘上。这样的话，索引查找过程中就要产生磁盘I/O消耗，相对于内存存取，I/O存取的消耗要高几个数量级，所以评价一个数据结构作为索引的优劣最重要的指标就是在查找过程中磁盘I/O操作次数的渐进复杂度。换句话说，索引的结构组织要尽量减少查找过程中磁盘I/O的存取次数。下面先介绍内存和磁盘存取原理，然后再结合这些原理分析B-/+Tree作为索引的效率。\n\n### 主存存取原理\n\n目前计算机使用的主存基本都是随机读写存储器（RAM），现代RAM的结构和存取原理比较复杂，这里本文抛却具体差别，抽象出一个十分简单的存取模型来说明RAM的工作原理。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/5.png)\n\n从抽象角度看，主存是一系列的存储单元组成的矩阵，每个存储单元存储固定大小的数据。每个存储单元有唯一的地址，现代主存的编址规则比较复杂，这里将其简化成一个二维地址：通过一个行地址和一个列地址可以唯一定位到一个存储单元。图5展示了一个4 x 4的主存模型。\n\n主存的存取过程如下：\n\n当系统需要读取主存时，则将地址信号放到地址总线上传给主存，主存读到地址信号后，解析信号并定位到指定存储单元，然后将此存储单元数据放到数据总线上，供其它部件读取。\n\n写主存的过程类似，系统将要写入单元地址和数据分别放在地址总线和数据总线上，主存读取两个总线的内容，做相应的写操作。\n\n这里可以看出，主存存取的时间仅与存取次数呈线性关系，因为不存在机械操作，两次存取的数据的“距离”不会对时间有任何影响，例如，先取A0再取A1和先取A0再取D3的时间消耗是一样的。\n\n### 磁盘存取原理\n\n上文说过，索引一般以文件形式存储在磁盘上，索引检索需要磁盘I/O操作。与主存不同，磁盘I/O存在机械运动耗费，因此磁盘I/O的时间消耗是巨大的。\n\n图6是磁盘的整体结构示意图。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/6.png)\n\n一个磁盘由大小相同且同轴的圆形盘片组成，磁盘可以转动（各个磁盘必须同步转动）。在磁盘的一侧有磁头支架，磁头支架固定了一组磁头，每个磁头负责存取一个磁盘的内容。磁头不能转动，但是可以沿磁盘半径方向运动（实际是斜切向运动），每个磁头同一时刻也必须是同轴的，即从正上方向下看，所有磁头任何时候都是重叠的（不过目前已经有多磁头独立技术，可不受此限制）。\n\n图7是磁盘结构的示意图。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/7.png)\n\n盘片被划分成一系列同心环，圆心是盘片中心，每个同心环叫做一个磁道，所有半径相同的磁道组成一个柱面。磁道被沿半径线划分成一个个小的段，每个段叫做一个扇区，每个扇区是磁盘的最小存储单元。为了简单起见，我们下面假设磁盘只有一个盘片和一个磁头。\n\n当需要从磁盘读取数据时，系统会将数据逻辑地址传给磁盘，磁盘的控制电路按照寻址逻辑将逻辑地址翻译成物理地址，即确定要读的数据在哪个磁道，哪个扇区。为了读取这个扇区的数据，需要将磁头放到这个扇区上方，为了实现这一点，磁头需要移动对准相应磁道，这个过程叫做寻道，所耗费时间叫做寻道时间，然后磁盘旋转将目标扇区旋转到磁头下，这个过程耗费的时间叫做旋转时间。\n\n### 局部性原理与磁盘预读\n\n由于存储介质的特性，磁盘本身存取就比主存慢很多，再加上机械运动耗费，磁盘的存取速度往往是主存的几百分分之一，因此为了提高效率，要尽量减少磁盘I/O。为了达到这个目的，磁盘往往不是严格按需读取，而是每次都会预读，即使只需要一个字节，磁盘也会从这个位置开始，顺序向后读取一定长度的数据放入内存。这样做的理论依据是计算机科学中著名的局部性原理：\n\n当一个数据被用到时，其附近的数据也通常会马上被使用。\n\n程序运行期间所需要的数据通常比较集中。\n\n由于磁盘顺序读取的效率很高（不需要寻道时间，只需很少的旋转时间），因此对于具有局部性的程序来说，预读可以提高I/O效率。\n\n预读的长度一般为页（page）的整倍数。页是计算机管理存储器的逻辑块，硬件及操作系统往往将主存和磁盘存储区分割为连续的大小相等的块，每个存储块称为一页（在许多操作系统中，页得大小通常为4k），主存和磁盘以页为单位交换数据。当程序要读取的数据不在主存中时，会触发一个缺页异常，此时系统会向磁盘发出读盘信号，磁盘会找到数据的起始位置并向后连续读取一页或几页载入内存中，然后异常返回，程序继续运行。\n\n### B-/+Tree索引的性能分析\n\n到这里终于可以分析B-/+Tree索引的性能了。\n\n上文说过一般使用磁盘I/O次数评价索引结构的优劣。先从B-Tree分析，根据B-Tree的定义，可知检索一次最多需要访问h个节点。数据库系统的设计者巧妙利用了磁盘预读原理，将一个节点的大小设为等于一个页，这样每个节点只需要一次I/O就可以完全载入。为了达到这个目的，在实际实现B-Tree还需要使用如下技巧：\n\n每次新建节点时，直接申请一个页的空间，这样就保证一个节点物理上也存储在一个页里，加之计算机存储分配都是按页对齐的，就实现了一个node只需一次I/O。\n\n综上所述，用B-Tree作为索引结构效率是非常高的。\n\n而红黑树这种结构，h明显要深的多。由于逻辑上很近的节点（父子）物理上可能很远，无法利用局部性，所以红黑树的I/O渐进复杂度也为O(h)，效率明显比B-Tree差很多。\n\nfloor表示向下取整。由于B+Tree内节点去掉了data域，因此可以拥有更大的出度，拥有更好的性能。\n\n这一章从理论角度讨论了与索引相关的数据结构与算法问题，下一章将讨论B+Tree是如何具体实现为MySQL中索引，同时将结合MyISAM和InnDB存储引擎介绍非聚集索引和聚集索引两种不同的索引实现形式。\n\n## MySQL索引实现\n\n在MySQL中，索引属于存储引擎级别的概念，不同存储引擎对索引的实现方式是不同的，本文主要讨论MyISAM和InnoDB两个存储引擎的索引实现方式。\n\n### MyISAM索引实现\n\nMyISAM引擎使用B+Tree作为索引结构，叶节点的data域存放的是数据记录的地址。下图是MyISAM索引的原理图：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/8.png)\n\n这里设表一共有三列，假设我们以Col1为主键，则图8是一个MyISAM表的主索引（Primary key）示意。可以看出MyISAM的索引文件仅仅保存数据记录的地址。在MyISAM中，主索引和辅助索引（Secondary key）在结构上没有任何区别，只是主索引要求key是唯一的，而辅助索引的key可以重复。如果我们在Col2上建立一个辅助索引，则此索引的结构如下图所示：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/9.png)\n\n同样也是一颗B+Tree，data域保存数据记录的地址。因此，MyISAM中索引检索的算法为首先按照B+Tree搜索算法搜索索引，如果指定的Key存在，则取出其data域的值，然后以data域的值为地址，读取相应数据记录。\n\nMyISAM的索引方式也叫做“非聚集”的，之所以这么称呼是为了与InnoDB的聚集索引区分。\n\n### InnoDB索引实现\n\n虽然InnoDB也使用B+Tree作为索引结构，但具体实现方式却与MyISAM截然不同。\n\n第一个重大区别是InnoDB的数据文件本身就是索引文件。从上文知道，MyISAM索引文件和数据文件是分离的，索引文件仅保存数据记录的地址。而在InnoDB中，表数据文件本身就是按B+Tree组织的一个索引结构，这棵树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键，因此InnoDB表数据文件本身就是主索引。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/10.png)\n\n图10是InnoDB主索引（同时也是数据文件）的示意图，可以看到叶节点包含了完整的数据记录。这种索引叫做聚集索引。因为InnoDB的数据文件本身要按主键聚集，所以InnoDB要求表必须有主键（MyISAM可以没有），如果没有显式指定，则MySQL系统会自动选择一个可以唯一标识数据记录的列作为主键，如果不存在这种列，则MySQL自动为InnoDB表生成一个隐含字段作为主键，这个字段长度为6个字节，类型为长整形。\n\n第二个与MyISAM索引的不同是InnoDB的辅助索引data域存储相应记录主键的值而不是地址。换句话说，InnoDB的所有辅助索引都引用主键作为data域。例如，图11为定义在Col3上的一个辅助索引：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/11.png)\n\n这里以英文字符的ASCII码作为比较准则。聚集索引这种实现方式使得按主键的搜索十分高效，但是辅助索引搜索需要检索两遍索引：首先检索辅助索引获得主键，然后用主键到主索引中检索获得记录。\n\n了解不同存储引擎的索引实现方式对于正确使用和优化索引都非常有帮助，例如知道了InnoDB的索引实现后，就很容易明白为什么不建议使用过长的字段作为主键，因为所有辅助索引都引用主索引，过长的主索引会令辅助索引变得过大。再例如，用非单调的字段作为主键在InnoDB中不是个好主意，因为InnoDB数据文件本身是一颗B+Tree，非单调的主键会造成在插入新记录时数据文件为了维持B+Tree的特性而频繁的分裂调整，十分低效，而使用自增字段作为主键则是一个很好的选择。\n\n下一章将具体讨论这些与索引有关的优化策略。\n\n### 索引使用策略及优化\n\nMySQL的优化主要分为结构优化（Scheme optimization）和查询优化（Query optimization）。本章讨论的高性能索引策略主要属于结构优化范畴。本章的内容完全基于上文的理论基础，实际上一旦理解了索引背后的机制，那么选择高性能的策略就变成了纯粹的推理，并且可以理解这些策略背后的逻辑。\n\n### 示例数据库\n\n为了讨论索引策略，需要一个数据量不算小的数据库作为示例。本文选用MySQL官方文档中提供的示例数据库之一：employees。这个数据库关系复杂度适中，且数据量较大。下图是这个数据库的E-R关系图（引用自MySQL官方手册）：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/12.png)\n\nMySQL官方文档中关于此数据库的页面为[http://dev.mysql.com/doc/employee/en/employee.html](http://dev.mysql.com/doc/employee/en/employee.html \"http://dev.mysql.com/doc/employee/en/employee.html\")。里面详细介绍了此数据库，并提供了下载地址和导入方法，如果有兴趣导入此数据库到自己的MySQL可以参考文中内容。\n\n### 最左前缀原理与相关优化\n\n高效使用索引的首要条件是知道什么样的查询会使用到索引，这个问题和B+Tree中的“最左前缀原理”有关，下面通过例子说明最左前缀原理。\n\n这里先说一下联合索引的概念。在上文中，我们都是假设索引只引用了单个的列，实际上，MySQL中的索引可以以一定顺序引用多个列，这种索引叫做联合索引，一般的，一个联合索引是一个有序元组，其中各个元素均为数据表的一列，实际上要严格定义索引需要用到关系代数，但是这里我不想讨论太多关系代数的话题，因为那样会显得很枯燥，所以这里就不再做严格定义。另外，单列索引可以看成联合索引元素数为1的特例。\n\n以employees.titles表为例，下面先查看其上都有哪些索引：\n\n\n````\n\n1.  SHOW INDEX FROM employees.titles;\n2.  +--------+------------+----------+--------------+-------------+-----------+-------------+------+------------+\n3.  |  Table  |  Non_unique  |  Key_name  |  Seq_in_index  |  Column_name  |  Collation  |  Cardinality  |  Null  |  Index_type  |\n4.  +--------+------------+----------+--------------+-------------+-----------+-------------+------+------------+\n5.  | titles |  0  | PRIMARY |  1  | emp_no | A | NULL |  | BTREE |\n6.  | titles |  0  | PRIMARY |  2  | title | A | NULL |  | BTREE |\n7.  | titles |  0  | PRIMARY |  3  | from_date | A |  443308  |  | BTREE |\n8.  | titles |  1  | emp_no |  1  | emp_no | A |  443308  |  | BTREE |\n9.  +--------+------------+----------+--------------+-------------+-----------+-------------+------+------------+\n````\n\n\n从结果中可以到titles表的主索引为<emp_no, title, from_date>，还有一个辅助索引<emp_no>。为了避免多个索引使事情变复杂（MySQL的SQL优化器在多索引时行为比较复杂），这里我们将辅助索引drop掉：\n\n\n````\n\n1.  ALTER TABLE employees.titles DROP INDEX emp_no;\n\n````\n\n这样就可以专心分析索引PRIMARY的行为了。\n\n### 情况一：全列匹配。\n\n\n````\n\n1.  EXPLAIN SELECT * FROM employees.titles WHERE emp_no='10001' AND title='Senior Engineer' AND from_date='1986-06-26';\n2.  +----+-------------+--------+-------+---------------+---------+---------+-------------------+------+-------+\n3.  | id | select_type | table | type | possible_keys | key | key_len |  ref  | rows |  Extra  |\n4.  +----+-------------+--------+-------+---------------+---------+---------+-------------------+------+-------+\n5.  |  1  | SIMPLE | titles |  const  | PRIMARY | PRIMARY |  59  |  const,const,const  |  1  |  |\n6.  +----+-------------+--------+-------+---------------+---------+---------+-------------------+------+-------+\n````\n\n\n很明显，当按照索引中所有列进行精确匹配（这里精确匹配指“=”或“IN”匹配）时，索引可以被用到。这里有一点需要注意，理论上索引对顺序是敏感的，但是由于MySQL的查询优化器会自动调整where子句的条件顺序以使用适合的索引，例如我们将where中的条件顺序颠倒：\n\n\n````\n\n1.  EXPLAIN SELECT * FROM employees.titles WHERE from_date='1986-06-26' AND emp_no='10001' AND title='Senior Engineer';\n2.  +----+-------------+--------+-------+---------------+---------+---------+-------------------+------+-------+\n3.  | id | select_type | table | type | possible_keys | key | key_len |  ref  | rows |  Extra  |\n4.  +----+-------------+--------+-------+---------------+---------+---------+-------------------+------+-------+\n5.  |  1  | SIMPLE | titles |  const  | PRIMARY | PRIMARY |  59  |  const,const,const  |  1  |  |\n6.  +----+-------------+--------+-------+---------------+---------+---------+-------------------+------+-------+\n````\n\n\n效果是一样的。\n\n### 情况二：最左前缀匹配。\n\n\n\n````\n1.  EXPLAIN SELECT * FROM employees.titles WHERE emp_no='10001';\n2.  +----+-------------+--------+------+---------------+---------+---------+-------+------+-------+\n3.  | id | select_type | table | type | possible_keys | key | key_len |  ref  | rows |  Extra  |\n4.  +----+-------------+--------+------+---------------+---------+---------+-------+------+-------+\n5.  |  1  | SIMPLE | titles |  ref  | PRIMARY | PRIMARY |  4  |  const  |  1  |  |\n6.  +----+-------------+--------+------+---------------+---------+---------+-------+------+-------+\n\n````\n\n当查询条件精确匹配索引的左边连续一个或几个列时，如<emp_no>或<emp_no, title>，所以可以被用到，但是只能用到一部分，即条件所组成的最左前缀。上面的查询从分析结果看用到了PRIMARY索引，但是key_len为4，说明只用到了索引的第一列前缀。\n\n### 情况三：查询条件用到了索引中列的精确匹配，但是中间某个条件未提供。\n\n\n````\n\n1.  EXPLAIN SELECT * FROM employees.titles WHERE emp_no='10001' AND from_date='1986-06-26';\n2.  +----+-------------+--------+------+---------------+---------+---------+-------+------+-------------+\n3.  | id | select_type | table | type | possible_keys | key | key_len |  ref  | rows |  Extra  |\n4.  +----+-------------+--------+------+---------------+---------+---------+-------+------+-------------+\n5.  |  1  | SIMPLE | titles |  ref  | PRIMARY | PRIMARY |  4  |  const  |  1  |  Using  where  |\n6.  +----+-------------+--------+------+---------------+---------+---------+-------+------+-------------+\n\n````\n\n此时索引使用情况和情况二相同，因为title未提供，所以查询只用到了索引的第一列，而后面的from_date虽然也在索引中，但是由于title不存在而无法和左前缀连接，因此需要对结果进行扫描过滤from_date（这里由于emp_no唯一，所以不存在扫描）。如果想让from_date也使用索引而不是where过滤，可以增加一个辅助索引<emp_no, from_date>，此时上面的查询会使用这个索引。除此之外，还可以使用一种称之为“隔离列”的优化方法，将emp_no与from_date之间的“坑”填上。\n\n首先我们看下title一共有几种不同的值：\n\n\n\n````\n1.  SELECT DISTINCT(title) FROM employees.titles;\n2.  +--------------------+\n3.  | title |\n4.  +--------------------+\n5.  |  Senior  Engineer  |\n6.  |  Staff  |\n7.  |  Engineer  |\n8.  |  Senior  Staff  |\n9.  |  Assistant  Engineer  |\n10.  |  Technique  Leader  |\n11.  |  Manager  |\n12.  +--------------------+\n````\n\n\n只有7种。在这种成为“坑”的列值比较少的情况下，可以考虑用“IN”来填补这个“坑”从而形成最左前缀：\n\n\n\n````\n1.  EXPLAIN SELECT * FROM employees.titles\n2.  WHERE emp_no='10001'\n3.  AND title IN ('Senior Engineer',  'Staff',  'Engineer',  'Senior Staff',  'Assistant Engineer',  'Technique Leader',  'Manager')\n4.  AND from_date='1986-06-26';\n5.  +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+\n6.  | id | select_type | table | type | possible_keys | key | key_len |  ref  | rows |  Extra  |\n7.  +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+\n8.  |  1  | SIMPLE | titles | range | PRIMARY | PRIMARY |  59  | NULL |  7  |  Using  where  |\n9.  +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+\n````\n\n\n这次key_len为59，说明索引被用全了，但是从type和rows看出IN实际上执行了一个range查询，这里检查了7个key。看下两种查询的性能比较：\n\n\n\n````\n1.  SHOW PROFILES;\n2.  +----------+------------+-------------------------------------------------------------------------------+\n3.  |  Query_ID  |  Duration  |  Query  |\n4.  +----------+------------+-------------------------------------------------------------------------------+\n5.  |  10  |  0.00058000  | SELECT * FROM employees.titles WHERE emp_no='10001' AND from_date='1986-06-26'|\n6.  |  11  |  0.00052500  | SELECT * FROM employees.titles WHERE emp_no='10001' AND title IN ...  |\n7.  +----------+------------+-------------------------------------------------------------------------------+\n````\n\n\n“填坑”后性能提升了一点。如果经过emp_no筛选后余下很多数据，则后者性能优势会更加明显。当然，如果title的值很多，用填坑就不合适了，必须建立辅助索引。\n\n### 情况四：查询条件没有指定索引第一列。\n\n\n\n````\n1.  EXPLAIN SELECT * FROM employees.titles WHERE from_date='1986-06-26';\n2.  +----+-------------+--------+------+---------------+------+---------+------+--------+-------------+\n3.  | id | select_type | table | type | possible_keys | key | key_len |  ref  | rows |  Extra  |\n4.  +----+-------------+--------+------+---------------+------+---------+------+--------+-------------+\n5.  |  1  | SIMPLE | titles | ALL | NULL | NULL | NULL | NULL |  443308  |  Using  where  |\n6.  +----+-------------+--------+------+---------------+------+---------+------+--------+-------------+\n````\n\n\n由于不是最左前缀，索引这样的查询显然用不到索引。\n\n### 情况五：匹配某列的前缀字符串。\n\n\n\n````\n1.  EXPLAIN SELECT * FROM employees.titles WHERE emp_no='10001' AND title LIKE 'Senior%';\n2.  +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+\n3.  | id | select_type | table | type | possible_keys | key | key_len |  ref  | rows |  Extra  |\n4.  +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+\n5.  |  1  | SIMPLE | titles | range | PRIMARY | PRIMARY |  56  | NULL |  1  |  Using  where  |\n6.  +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+\n````\n\n\n此时可以用到索引，~~但是如果通配符不是只出现在末尾，则无法使用索引。~~（原文表述有误，如果通配符%不出现在开头，则可以用到索引，但根据具体情况不同可能只会用其中一个前缀）\n\n### 情况六：范围查询。\n\n\n\n````\n1.  EXPLAIN SELECT * FROM employees.titles WHERE emp_no <  '10010'  and title='Senior Engineer';\n2.  +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+\n3.  | id | select_type | table | type | possible_keys | key | key_len |  ref  | rows |  Extra  |\n4.  +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+\n5.  |  1  | SIMPLE | titles | range | PRIMARY | PRIMARY |  4  | NULL |  16  |  Using  where  |\n6.  +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+\n````\n\n\n范围列可以用到索引（必须是最左前缀），但是范围列后面的列无法用到索引。同时，索引最多用于一个范围列，因此如果查询条件中有两个范围列则无法全用到索引。\n\n\n\n````\n1.  EXPLAIN SELECT * FROM employees.titles\n2.  WHERE emp_no <  '10010'\n3.  AND title='Senior Engineer'\n4.  AND from_date BETWEEN '1986-01-01' AND '1986-12-31';\n5.  +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+\n6.  | id | select_type | table | type | possible_keys | key | key_len |  ref  | rows |  Extra  |\n7.  +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+\n8.  |  1  | SIMPLE | titles | range | PRIMARY | PRIMARY |  4  | NULL |  16  |  Using  where  |\n9.  +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+\n````\n\n\n可以看到索引对第二个范围索引无能为力。这里特别要说明MySQL一个有意思的地方，那就是仅用explain可能无法区分范围索引和多值匹配，因为在type中这两者都显示为range。同时，用了“between”并不意味着就是范围查询，例如下面的查询：\n\n\n````\n\n1.  EXPLAIN SELECT * FROM employees.titles\n2.  WHERE emp_no BETWEEN '10001' AND '10010'\n3.  AND title='Senior Engineer'\n4.  AND from_date BETWEEN '1986-01-01' AND '1986-12-31';\n5.  +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+\n6.  | id | select_type | table | type | possible_keys | key | key_len |  ref  | rows |  Extra  |\n7.  +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+\n8.  |  1  | SIMPLE | titles | range | PRIMARY | PRIMARY |  59  | NULL |  16  |  Using  where  |\n9.  +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+\n\n````\n\n看起来是用了两个范围查询，但作用于emp_no上的“BETWEEN”实际上相当于“IN”，也就是说emp_no实际是多值精确匹配。可以看到这个查询用到了索引全部三个列。因此在MySQL中要谨慎地区分多值匹配和范围匹配，否则会对MySQL的行为产生困惑。\n\n### 情况七：查询条件中含有函数或表达式。\n\n很不幸，如果查询条件中含有函数或表达式，则MySQL不会为这列使用索引（虽然某些在数学意义上可以使用）。例如：\n\n\n````\n\n1.  EXPLAIN SELECT * FROM employees.titles WHERE emp_no='10001' AND left(title,  6)='Senior';\n2.  +----+-------------+--------+------+---------------+---------+---------+-------+------+-------------+\n3.  | id | select_type | table | type | possible_keys | key | key_len |  ref  | rows |  Extra  |\n4.  +----+-------------+--------+------+---------------+---------+---------+-------+------+-------------+\n5.  |  1  | SIMPLE | titles |  ref  | PRIMARY | PRIMARY |  4  |  const  |  1  |  Using  where  |\n6.  +----+-------------+--------+------+---------------+---------+---------+-------+------+-------------+\n\n````\n\n虽然这个查询和情况五中功能相同，但是由于使用了函数left，则无法为title列应用索引，而情况五中用LIKE则可以。再如：\n\n\n\n````\n1.  EXPLAIN SELECT * FROM employees.titles WHERE emp_no -  1='10000';\n2.  +----+-------------+--------+------+---------------+------+---------+------+--------+-------------+\n3.  | id | select_type | table | type | possible_keys | key | key_len |  ref  | rows |  Extra  |\n4.  +----+-------------+--------+------+---------------+------+---------+------+--------+-------------+\n5.  |  1  | SIMPLE | titles | ALL | NULL | NULL | NULL | NULL |  443308  |  Using  where  |\n6.  +----+-------------+--------+------+---------------+------+---------+------+--------+-------------+\n````\n\n\n显然这个查询等价于查询emp_no为10001的函数，但是由于查询条件是一个表达式，MySQL无法为其使用索引。看来MySQL还没有智能到自动优化常量表达式的程度，因此在写查询语句时尽量避免表达式出现在查询中，而是先手工私下代数运算，转换为无表达式的查询语句。\n\n## 索引选择性与前缀索引\n\n既然索引可以加快查询速度，那么是不是只要是查询语句需要，就建上索引？答案是否定的。因为索引虽然加快了查询速度，但索引也是有代价的：索引文件本身要消耗存储空间，同时索引会加重插入、删除和修改记录时的负担，另外，MySQL在运行时也要消耗资源维护索引，因此索引并不是越多越好。一般两种情况下不建议建索引。\n\n第一种情况是表记录比较少，例如一两千条甚至只有几百条记录的表，没必要建索引，让查询做全表扫描就好了。至于多少条记录才算多，这个个人有个人的看法，我个人的经验是以2000作为分界线，记录数不超过 2000可以考虑不建索引，超过2000条可以酌情考虑索引。\n\n另一种不建议建索引的情况是索引的选择性较低。所谓索引的选择性（Selectivity），是指不重复的索引值（也叫基数，Cardinality）与表记录数（#T）的比值：\n\nIndex Selectivity = Cardinality / #T\n\n显然选择性的取值范围为(0, 1]，选择性越高的索引价值越大，这是由B+Tree的性质决定的。例如，上文用到的employees.titles表，如果title字段经常被单独查询，是否需要建索引，我们看一下它的选择性：\n\n\n\n````\n1.  SELECT count(DISTINCT(title))/count(*) AS Selectivity FROM employees.titles;\n2.  +-------------+\n3.  |  Selectivity  |\n4.  +-------------+\n5.  |  0.0000  |\n6.  +-------------+\n````\n\n\ntitle的选择性不足0.0001（精确值为0.00001579），所以实在没有什么必要为其单独建索引。\n\n有一种与索引选择性有关的索引优化策略叫做前缀索引，就是用列的前缀代替整个列作为索引key，当前缀长度合适时，可以做到既使得前缀索引的选择性接近全列索引，同时因为索引key变短而减少了索引文件的大小和维护开销。下面以employees.employees表为例介绍前缀索引的选择和使用。\n\n从图12可以看到employees表只有一个索引<emp_no>，那么如果我们想按名字搜索一个人，就只能全表扫描了：\n\n\n\n````\n1.  EXPLAIN SELECT * FROM employees.employees WHERE first_name='Eric' AND last_name='Anido';\n2.  +----+-------------+-----------+------+---------------+------+---------+------+--------+-------------+\n3.  | id | select_type | table | type | possible_keys | key | key_len |  ref  | rows |  Extra  |\n4.  +----+-------------+-----------+------+---------------+------+---------+------+--------+-------------+\n5.  |  1  | SIMPLE | employees | ALL | NULL | NULL | NULL | NULL |  300024  |  Using  where  |\n6.  +----+-------------+-----------+------+---------------+------+---------+------+--------+-------------+\n````\n\n\n如果频繁按名字搜索员工，这样显然效率很低，因此我们可以考虑建索引。有两种选择，建<first_name>或<first_name, last_name>，看下两个索引的选择性：\n\n\n\n````\n1.  SELECT count(DISTINCT(first_name))/count(*) AS Selectivity FROM employees.employees;\n2.  +-------------+\n3.  |  Selectivity  |\n4.  +-------------+\n5.  |  0.0042  |\n6.  +-------------+\n7.  SELECT count(DISTINCT(concat(first_name, last_name)))/count(*) AS Selectivity FROM employees.employees;\n8.  +-------------+\n9.  |  Selectivity  |\n10.  +-------------+\n11.  |  0.9313  |\n12.  +-------------+\n````\n\n\n<first_name>显然选择性太低，<first_name, last_name>选择性很好，但是first_name和last_name加起来长度为30，有没有兼顾长度和选择性的办法？可以考虑用first_name和last_name的前几个字符建立索引，例如<first_name, left(last_name, 3)>，看看其选择性：\n\n\n\n````\n1.  SELECT count(DISTINCT(concat(first_name, left(last_name,  3))))/count(*) AS Selectivity FROM employees.employees;\n2.  +-------------+\n3.  |  Selectivity  |\n4.  +-------------+\n5.  |  0.7879  |\n6.  +-------------+\n````\n\n\n选择性还不错，但离0.9313还是有点距离，那么把last_name前缀加到4：\n\n\n\n````\n1.  SELECT count(DISTINCT(concat(first_name, left(last_name,  4))))/count(*) AS Selectivity FROM employees.employees;\n2.  +-------------+\n3.  |  Selectivity  |\n4.  +-------------+\n5.  |  0.9007  |\n6.  +-------------+\n````\n\n\n这时选择性已经很理想了，而这个索引的长度只有18，比<first_name, last_name>短了接近一半，我们把这个前缀索引 建上：\n\n\n\n````\n1.  ALTER TABLE employees.employees\n2.  ADD INDEX `first_name_last_name4`  (first_name, last_name(4));\n````\n\n\n此时再执行一遍按名字查询，比较分析一下与建索引前的结果：\n\n\n\n````\n1.  SHOW PROFILES;\n2.  +----------+------------+---------------------------------------------------------------------------------+\n3.  |  Query_ID  |  Duration  |  Query  |\n4.  +----------+------------+---------------------------------------------------------------------------------+\n5.  |  87  |  0.11941700  | SELECT * FROM employees.employees WHERE first_name='Eric' AND last_name='Anido'  |\n6.  |  90  |  0.00092400  | SELECT * FROM employees.employees WHERE first_name='Eric' AND last_name='Anido'  |\n7.  +----------+------------+---------------------------------------------------------------------------------+\n````\n\n\n性能的提升是显著的，查询速度提高了120多倍。\n\n前缀索引兼顾索引大小和查询速度，但是其缺点是不能用于ORDER BY和GROUP BY操作，也不能用于Covering index（即当索引本身包含查询所需全部数据时，不再访问数据文件本身）。\n\n## InnoDB的主键选择与插入优化\n\n在使用InnoDB存储引擎时，如果没有特别的需要，请永远使用一个与业务无关的自增字段作为主键。\n\n经常看到有帖子或博客讨论主键选择问题，有人建议使用业务无关的自增主键，有人觉得没有必要，完全可以使用如学号或身份证号这种唯一字段作为主键。不论支持哪种论点，大多数论据都是业务层面的。如果从数据库索引优化角度看，使用InnoDB引擎而不使用自增主键绝对是一个糟糕的主意。\n\n上文讨论过InnoDB的索引实现，InnoDB使用聚集索引，数据记录本身被存于主索引（一颗B+Tree）的叶子节点上。这就要求同一个叶子节点内（大小为一个内存页或磁盘页）的各条数据记录按主键顺序存放，因此每当有一条新的记录插入时，MySQL会根据其主键将其插入适当的节点和位置，如果页面达到装载因子（InnoDB默认为15/16），则开辟一个新的页（节点）。\n\n如果表使用自增主键，那么每次插入新的记录，记录就会顺序添加到当前索引节点的后续位置，当一页写满，就会自动开辟一个新的页。如下图所示：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/13.png)\n\n图13\n\n这样就会形成一个紧凑的索引结构，近似顺序填满。由于每次插入时也不需要移动已有数据，因此效率很高，也不会增加很多开销在维护索引上。\n\n如果使用非自增主键（如果身份证号或学号等），由于每次插入主键的值近似于随机，因此每次新纪录都要被插到现有索引页得中间某个位置：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/14.png)\n\n图14\n\n此时MySQL不得不为了将新记录插到合适位置而移动数据，甚至目标页面可能已经被回写到磁盘上而从缓存中清掉，此时又要从磁盘上读回来，这增加了很多开销，同时频繁的移动、分页操作造成了大量的碎片，得到了不够紧凑的索引结构，后续不得不通过OPTIMIZE TABLE来重建表并优化填充页面。\n\n因此，只要可以，请尽量在InnoDB上采用自增字段做主键。\n\n# 后记\n\n这篇文章断断续续写了半个月，主要内容就是上面这些了。不可否认，这篇文章在一定程度上有纸上谈兵之嫌，因为我本人对MySQL的使用属于菜鸟级别，更没有太多数据库调优的经验，在这里大谈数据库索引调优有点大言不惭。就当是我个人的一篇学习笔记了。\n\n其实数据库索引调优是一项技术活，不能仅仅靠理论，因为实际情况千变万化，而且MySQL本身存在很复杂的机制，如查询优化策略和各种引擎的实现差异等都会使情况变得更加复杂。但同时这些理论是索引调优的基础，只有在明白理论的基础上，才能对调优策略进行合理推断并了解其背后的机制，然后结合实践中不断的实验和摸索，从而真正达到高效使用MySQL索引的目的。\n\n另外，MySQL索引及其优化涵盖范围非常广，本文只是涉及到其中一部分。如与排序（ORDER BY）相关的索引优化及覆盖索引（Covering index）的话题本文并未涉及，同时除B-Tree索引外MySQL还根据不同引擎支持的哈希索引、全文索引等等本文也并未涉及。如果有机会，希望再对本文未涉及的部分进行补充吧。\n\n# 参考文献\n\n[1] Baron Scbwartz等 著，王小东等 译；高性能MySQL（High Performance MySQL）；电子工业出版社，2010\n\n[2] Michael Kofler 著，杨晓云等 译；MySQL5权威指南（The Definitive Guide to MySQL5）；人民邮电出版社，2006\n\n[3] 姜承尧 著；MySQL技术内幕-InnoDB存储引擎；机械工业出版社，2011\n\n[4] D Comer, Ubiquitous B-tree; ACM Computing Surveys (CSUR), 1979\n\n[5] Codd, E. F. (1970). \"A relational model of data for large shared data banks\". Communications of the ACM, , Vol. 13, No. 6, pp. 377-387\n\n[6] MySQL5.1参考手册 -[http://dev.mysql.com/doc/refman/5.1/zh/index.html](http://dev.mysql.com/doc/refman/5.1/zh/index.html \"http://dev.mysql.com/doc/refman/5.1/zh/index.html\")\n"
  },
  {
    "path": "docs/database/重新学习MySQL数据库：『浅入浅出』MySQL和InnoDB.md",
    "content": "# 目录\n  * [数据库的定义](#数据库的定义)\n    * [数据库和实例](#数据库和实例)\n    * [MySQL 的架构](#mysql-的架构)\n  * [数据的存储](#数据的存储)\n    * [如何存储表](#如何存储表)\n    * [如何存储记录](#如何存储记录)\n    * [数据页结构](#数据页结构)\n  * [索引](#索引)\n    * [索引的数据结构](#索引的数据结构)\n    * [聚集索引和辅助索引](#聚集索引和辅助索引)\n    * [索引的设计](#索引的设计)\n  * [锁](#锁)\n    * [并发控制机制](#并发控制机制)\n    * [锁的种类](#锁的种类)\n    * [锁的粒度](#锁的粒度)\n    * [锁的算法](#锁的算法)\n    * [死锁的发生](#死锁的发生)\n  * [事务与隔离级别](#事务与隔离级别)\n    * [几种隔离级别](#几种隔离级别)\n    * [脏读](#脏读)\n    * [不可重复读](#不可重复读)\n    * [幻读](#幻读)\n  * [总结](#总结)\n  * [Innodb与Myisam引擎的区别与应用场景](#[innodb与myisam引擎的区别与应用场景])\n\n\n本文转自互联网\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本也将整理到我的个人博客：\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n本文是《重新学习MySQL数据库》系列其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，从sql基本的使用方法，从MySQL执行引擎再到索引、事务等知识，一步步地学习MySQL相关技术的实现原理，更好地了解如何基于这些知识来优化sql，减少SQL执行时间，通过执行计划对SQL性能进行分析，再到MySQL的主从复制、主备部署等内容，以便让你更完整地了解整个MySQL方面的技术体系，形成自己的知识框架。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n作为一名开发人员，在日常的工作中会难以避免地接触到数据库，无论是基于文件的 sqlite 还是工程上使用非常广泛的 MySQL、PostgreSQL，但是一直以来也没有对数据库有一个非常清晰并且成体系的认知，所以最近两个月的时间看了几本数据库相关的书籍并且阅读了 MySQL 的官方文档，希望对各位了解数据库的、不了解数据库的有所帮助。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405202853.png)\n本文中对于数据库的介绍以及研究都是在 MySQL 上进行的，如果涉及到了其他数据库的内容或者实现会在文中单独指出。\n\n## 数据库的定义\n\n很多开发者在最开始时其实都对数据库有一个比较模糊的认识，觉得数据库就是一堆数据的集合，但是实际却比这复杂的多，数据库领域中有两个词非常容易混淆，也就是_数据库_和_实例_：\n\n*   数据库：物理操作文件系统或其他形式文件类型的集合；\n*   实例：MySQL 数据库由后台线程以及一个共享内存区组成；\n\n> 对于数据库和实例的定义都来自于[MySQL 技术内幕：InnoDB 存储引擎](https://book.douban.com/subject/24708143/)一书，想要了解 InnoDB 存储引擎的读者可以阅读这本书籍。\n\n### 数据库和实例\n\n在 MySQL 中，实例和数据库往往都是一一对应的，而我们也无法直接操作数据库，而是要通过数据库实例来操作数据库文件，可以理解为数据库实例是数据库为上层提供的一个专门用于操作的接口。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405202622.png)\n\n在 Unix 上，启动一个 MySQL 实例往往会产生两个进程，`mysqld`就是真正的数据库服务守护进程，而`mysqld_safe`是一个用于检查和设置`mysqld`启动的控制程序，它负责监控 MySQL 进程的执行，当`mysqld`发生错误时，`mysqld_safe`会对其状态进行检查并在合适的条件下重启。\n\n### MySQL 的架构\n\nMySQL 从第一个版本发布到现在已经有了 20 多年的历史，在这么多年的发展和演变中，整个应用的体系结构变得越来越复杂：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405202911.png)\n\n最上层用于连接、线程处理的部分并不是 MySQL 『发明』的，很多服务都有类似的组成部分；第二层中包含了大多数 MySQL 的核心服务，包括了对 SQL 的解析、分析、优化和缓存等功能，存储过程、触发器和视图都是在这里实现的；而第三层就是 MySQL 中真正负责数据的存储和提取的存储引擎，例如：[InnoDB](https://en.wikipedia.org/wiki/InnoDB)、[MyISAM](https://en.wikipedia.org/wiki/MyISAM)等，文中对存储引擎的介绍都是对 InnoDB 实现的分析。\n\n## 数据的存储\n\n在整个数据库体系结构中，我们可以使用不同的存储引擎来存储数据，而绝大多数存储引擎都以二进制的形式存储数据；这一节会介绍 InnoDB 中对数据是如何存储的。\n\n在 InnoDB 存储引擎中，所有的数据都被**逻辑地**存放在表空间中，表空间（tablespace）是存储引擎中最高的存储逻辑单位，在表空间的下面又包括段（segment）、区（extent）、页（page）：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405202927.png)\n\n同一个数据库实例的所有表空间都有相同的页大小；默认情况下，表空间中的页大小都为 16KB，当然也可以通过改变`innodb_page_size`选项对默认大小进行修改，需要注意的是不同的页大小最终也会导致区大小的不同：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405202938.png)\n\n从图中可以看出，在 InnoDB 存储引擎中，一个区的大小最小为 1MB，页的数量最少为 64 个。\n\n### 如何存储表\n\nMySQL 使用 InnoDB 存储表时，会将**表的定义**和**数据索引**等信息分开存储，其中前者存储在`.frm`文件中，后者存储在`.ibd`文件中，这一节就会对这两种不同的文件分别进行介绍。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405203010.png)\n.frm 文件\n\n无论在 MySQL 中选择了哪个存储引擎，所有的 MySQL 表都会在硬盘上创建一个`.frm`文件用来描述表的格式或者说定义；`.frm`文件的格式在不同的平台上都是相同的。\n\n```\n CREATE TABLE test_frm(    column1 CHAR(5),    column2 INTEGER);\n```\n\n当我们使用上面的代码创建表时，会在磁盘上的`datadir`文件夹中生成一个`test_frm.frm`的文件，这个文件中就包含了表结构相关的信息：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405203026.png)\n> MySQL 官方文档中的[11.1 MySQL .frm File Format](https://dev.mysql.com/doc/internals/en/frm-file-format.html)一文对于`.frm`文件格式中的二进制的内容有着非常详细的表述，在这里就不展开介绍了。\n\n.ibd 文件\n\nInnoDB 中用于存储数据的文件总共有两个部分，一是系统表空间文件，包括`ibdata1`、`ibdata2`等文件，其中存储了 InnoDB 系统信息和用户数据库表数据和索引，是所有表公用的。\n\n当打开`innodb_file_per_table`选项时，`.ibd`文件就是每一个表独有的表空间，文件存储了当前表的数据和相关的索引数据。\n\n### 如何存储记录\n\n与现有的大多数存储引擎一样，InnoDB 使用页作为磁盘管理的最小单位；数据在 InnoDB 存储引擎中都是按行存储的，每个 16KB 大小的页中可以存放 2-200 行的记录。\n\n当 InnoDB 存储数据时，它可以使用不同的行格式进行存储；MySQL 5.7 版本支持以下格式的行存储方式：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405203135.png)\n> Antelope 是 InnoDB 最开始支持的文件格式，它包含两种行格式 Compact 和 Redundant，它最开始并没有名字；Antelope 的名字是在新的文件格式 Barracuda 出现后才起的，Barracuda 的出现引入了两种新的行格式 Compressed 和 Dynamic；InnoDB 对于文件格式都会向前兼容，而官方文档中也对之后会出现的新文件格式预先定义好了名字：Cheetah、Dragon、Elk 等等。\n\n两种行记录格式 Compact 和 Redundant 在磁盘上按照以下方式存储：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405203147.png)\n\nCompact 和 Redundant 格式最大的不同就是记录格式的第一个部分；在 Compact 中，行记录的第一部分倒序存放了一行数据中列的长度（Length），而 Redundant 中存的是每一列的偏移量（Offset），从总体上上看，Compact 行记录格式相比 Redundant 格式能够减少 20% 的存储空间。\n\n行溢出数据\n\n当 InnoDB 使用 Compact 或者 Redundant 格式存储极长的 VARCHAR 或者 BLOB 这类大对象时，我们并不会直接将所有的内容都存放在数据页节点中，而是将行数据中的前 768 个字节存储在数据页中，后面会通过偏移量指向溢出页。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405203203.png)\n\n但是当我们使用新的行记录格式 Compressed 或者 Dynamic 时都只会在行记录中保存 20 个字节的指针，实际的数据都会存放在溢出页面中。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405203320.png)\n\n当然在实际存储中，可能会对不同长度的 TEXT 和 BLOB 列进行优化，不过这就不是本文关注的重点了。\n\n> 想要了解更多与 InnoDB 存储引擎中记录的数据格式的相关信息，可以阅读[InnoDB Record Structure](https://dev.mysql.com/doc/internals/en/innodb-record-structure.html)\n\n### 数据页结构\n\n页是 InnoDB 存储引擎管理数据的最小磁盘单位，而 B-Tree 节点就是实际存放表中数据的页面，我们在这里将要介绍页是如何组织和存储记录的；首先，一个 InnoDB 页有以下七个部分：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405203334.png)\n每一个页中包含了两对 header/trailer：内部的 Page Header/Page Directory 关心的是页的状态信息，而 Fil Header/Fil Trailer 关心的是记录页的头信息。\n\n在页的头部和尾部之间就是用户记录和空闲空间了，每一个数据页中都包含 Infimum 和 Supremum 这两个**虚拟**的记录（可以理解为占位符），Infimum 记录是比该页中任何主键值都要小的值，Supremum 是该页中的最大值：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405203346.png)\nUser Records 就是整个页面中真正用于存放行记录的部分，而 Free Space 就是空余空间了，它是一个链表的数据结构，为了保证插入和删除的效率，整个页面并不会按照主键顺序对所有记录进行排序，它会自动从左侧向右寻找空白节点进行插入，行记录在物理存储上并不是按照顺序的，它们之间的顺序是由`next_record`这一指针控制的。\n\nB+ 树在查找对应的记录时，并不会直接从树中找出对应的行记录，它只能获取记录所在的页，将整个页加载到内存中，再通过 Page Directory 中存储的稀疏索引和`n_owned`、`next_record`属性取出对应的记录，不过因为这一操作是在内存中进行的，所以通常会忽略这部分查找的耗时。\n\nInnoDB 存储引擎中对数据的存储是一个非常复杂的话题，这一节中也只是对表、行记录以及页面的存储进行一定的分析和介绍，虽然作者相信这部分知识对于大部分开发者已经足够了，但是想要真正消化这部分内容还需要很多的努力和实践。\n\n## 索引\n\n索引是数据库中非常非常重要的概念，它是存储引擎能够快速定位记录的秘密武器，对于提升数据库的性能、减轻数据库服务器的负担有着非常重要的作用；**索引优化是对查询性能优化的最有效手段**，它能够轻松地将查询的性能提高几个数量级。\n\n### 索引的数据结构\n\n在上一节中，我们谈了行记录的存储和页的存储，在这里我们就要从更高的层面看 InnoDB 中对于数据是如何存储的；InnoDB 存储引擎在绝大多数情况下使用 B+ 树建立索引，这是关系型数据库中查找最为常用和有效的索引，但是 B+ 树索引并不能找到一个给定键对应的具体值，它只能找到数据行对应的页，然后正如上一节所提到的，数据库把整个页读入到内存中，并在内存中查找具体的数据行。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405203520.png)\n\nB+ 树是平衡树，它查找任意节点所耗费的时间都是完全相同的，比较的次数就是 B+ 树的高度；在这里，我们并不会深入分析或者动手实现一个 B+ 树，只是对它的特性进行简单的介绍。\n\n### 聚集索引和辅助索引\n\n数据库中的 B+ 树索引可以分为聚集索引（clustered index）和辅助索引（secondary index），它们之间的最大区别就是，聚集索引中存放着一条行记录的全部信息，而辅助索引中只包含索引列和一个用于查找对应行记录的『书签』。\n\n聚集索引\n\nInnoDB 存储引擎中的表都是使用索引组织的，也就是按照键的顺序存放；聚集索引就是按照表中主键的顺序构建一颗 B+ 树，并在叶节点中存放表中的行记录数据。\n\n```\n CREATE TABLE users(    id INT NOT NULL,    first_name VARCHAR(20) NOT NULL,    last_name VARCHAR(20) NOT NULL,    age INT NOT NULL,    PRIMARY KEY(id),    KEY(last_name, first_name, age)    KEY(first_name));\n```\n\n如果使用上面的 SQL 在数据库中创建一张表，B+ 树就会使用`id`作为索引的键，并在叶子节点中存储一条记录中的**所有**信息。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405203541.png)\n\n> 图中对 B+ 树的描述与真实情况下 B+ 树中的数据结构有一些差别，不过这里想要表达的主要意思是：聚集索引叶节点中保存的是整条行记录，而不是其中的一部分。\n\n聚集索引与表的物理存储方式有着非常密切的关系，所有正常的表应该**有且仅有一个**聚集索引（绝大多数情况下都是主键），表中的所有行记录数据都是按照**聚集索引**的顺序存放的。\n\n当我们使用聚集索引对表中的数据进行检索时，可以直接获得聚集索引所对应的整条行记录数据所在的页，不需要进行第二次操作。\n\n辅助索引\n\n数据库将所有的非聚集索引都划分为辅助索引，但是这个概念对我们理解辅助索引并没有什么帮助；辅助索引也是通过 B+ 树实现的，但是它的叶节点并不包含行记录的全部数据，仅包含索引中的所有键和一个用于查找对应行记录的『书签』，在 InnoDB 中这个书签就是当前记录的主键。\n\n辅助索引的存在并不会影响聚集索引，因为聚集索引构成的 B+ 树是数据实际存储的形式，而辅助索引只用于加速数据的查找，所以一张表上往往有多个辅助索引以此来提升数据库的性能。\n\n> 一张表一定包含一个聚集索引构成的 B+ 树以及若干辅助索引的构成的 B+ 树。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405203552.png)\n\n如果在表`users`中存在一个辅助索引`(first_name, age)`，那么它构成的 B+ 树大致就是上图这样，按照`(first_name, age)`的字母顺序对表中的数据进行排序，当查找到主键时，再通过聚集索引获取到整条行记录。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405203606.png)\n\n上图展示了一个使用辅助索引查找一条表记录的过程：通过辅助索引查找到对应的主键，最后在聚集索引中使用主键获取对应的行记录，这也是通常情况下行记录的查找方式。\n\n### 索引的设计\n\n索引的设计其实是一个非常重要的内容，同时也是一个非常复杂的内容；索引的设计与创建对于提升数据库的查询性能至关重要，不过这不是本文想要介绍的内容，有关索引的设计与优化可以阅读[数据库索引设计与优化](https://draveness.me/%E6%95%B0%E6%8D%AE%E5%BA%93%E7%B4%A2%E5%BC%95%E8%AE%BE%E8%AE%A1%E4%B8%8E%E4%BC%98%E5%8C%96)一书，书中提供了一种非常科学合理的方法能够帮助我们在数据库中建立最适合的索引，当然作者也可能会在之后的文章中对索引的设计进行简单的介绍和分析。\n\n## 锁\n\n我们都知道锁的种类一般分为乐观锁和悲观锁两种，InnoDB 存储引擎中使用的就是悲观锁，而按照锁的粒度划分，也可以分成行锁和表锁。\n\n### 并发控制机制\n\n乐观锁和悲观锁其实都是并发控制的机制，同时它们在原理上就有着本质的差别；\n\n*   乐观锁是一种思想，它其实并不是一种真正的『锁』，它会先尝试对资源进行修改，在写回时判断资源是否进行了改变，如果没有发生改变就会写回，否则就会进行重试，在整个的执行过程中其实都**没有对数据库进行加锁**；\n*   悲观锁就是一种真正的锁了，它会在获取资源前对资源进行加锁，确保同一时刻只有有限的线程能够访问该资源，其他想要尝试获取资源的操作都会进入等待状态，直到该线程完成了对资源的操作并且释放了锁后，其他线程才能重新操作资源；\n\n虽然乐观锁和悲观锁在本质上并不是同一种东西，一个是一种思想，另一个是一种真正的锁，但是它们都是一种并发控制机制。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405203616.png)\n\n乐观锁不会存在死锁的问题，但是由于更新后验证，所以当**冲突频率**和**重试成本**较高时更推荐使用悲观锁，而需要非常高的**响应速度**并且**并发量**非常大的时候使用乐观锁就能较好的解决问题，在这时使用悲观锁就可能出现严重的性能问题；在选择并发控制机制时，需要综合考虑上面的四个方面（冲突频率、重试成本、响应速度和并发量）进行选择。\n\n### 锁的种类\n\n对数据的操作其实只有两种，也就是读和写，而数据库在实现锁时，也会对这两种操作使用不同的锁；InnoDB 实现了标准的行级锁，也就是共享锁（Shared Lock）和互斥锁（Exclusive Lock）；共享锁和互斥锁的作用其实非常好理解：\n\n*   **共享锁（读锁）**：允许事务对一条行数据进行读取；\n*   **互斥锁（写锁）**：允许事务对一条行数据进行删除或更新；\n\n而它们的名字也暗示着各自的另外一个特性，共享锁之间是兼容的，而互斥锁与其他任意锁都不兼容：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405203627.png)\n\n稍微对它们的使用进行思考就能想明白它们为什么要这么设计，因为共享锁代表了读操作、互斥锁代表了写操作，所以我们可以在数据库中**并行读**，但是只能**串行写**，只有这样才能保证不会发生线程竞争，实现线程安全。\n\n### 锁的粒度\n\n无论是共享锁还是互斥锁其实都只是对某一个数据行进行加锁，InnoDB 支持多种粒度的锁，也就是行锁和表锁；为了支持多粒度锁定，InnoDB 存储引擎引入了意向锁（Intention Lock），意向锁就是一种表级锁。\n\n与上一节中提到的两种锁的种类相似的是，意向锁也分为两种：\n\n*   **意向共享锁**：事务想要在获得表中某些记录的共享锁，需要在表上先加意向共享锁；\n*   **意向互斥锁**：事务想要在获得表中某些记录的互斥锁，需要在表上先加意向互斥锁；\n\n随着意向锁的加入，锁类型之间的兼容矩阵也变得愈加复杂：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405203640.png)\n\n意向锁其实不会阻塞全表扫描之外的任何请求，它们的主要目的是为了表示**是否有人请求锁定表中的某一行数据**。\n\n> 有的人可能会对意向锁的目的并不是完全的理解，我们在这里可以举一个例子：如果没有意向锁，当已经有人使用行锁对表中的某一行进行修改时，如果另外一个请求要对全表进行修改，那么就需要对所有的行是否被锁定进行扫描，在这种情况下，效率是非常低的；不过，在引入意向锁之后，当有人使用行锁对表中的某一行进行修改之前，会先为表添加意向互斥锁（IX），再为行记录添加互斥锁（X），在这时如果有人尝试对全表进行修改就不需要判断表中的每一行数据是否被加锁了，只需要通过等待意向互斥锁被释放就可以了。\n\n### 锁的算法\n\n到目前为止已经对 InnoDB 中锁的粒度有一定的了解，也清楚了在对数据库进行读写时会获取不同的锁，在这一小节将介绍锁是如何添加到对应的数据行上的，我们会分别介绍三种锁的算法：Record Lock、Gap Lock 和 Next-Key Lock。\n\nRecord Lock\n\n记录锁（Record Lock）是加到**索引记录**上的锁，假设我们存在下面的一张表`users`：\n\n```\n CREATE TABLE users(    id INT NOT NULL AUTO_INCREMENT,    last_name VARCHAR(255) NOT NULL,    first_name VARCHAR(255),    age INT,    PRIMARY KEY(id),    KEY(last_name),    KEY(age));\n```\n\n如果我们使用`id`或者`last_name`作为 SQL 中`WHERE`语句的过滤条件，那么 InnoDB 就可以通过索引建立的 B+ 树找到行记录并添加索引，但是如果使用`first_name`作为过滤条件时，由于 InnoDB 不知道待修改的记录具体存放的位置，也无法对将要修改哪条记录提前做出判断就会锁定整个表。\n\nGap Lock\n\n记录锁是在存储引擎中最为常见的锁，除了记录锁之外，InnoDB 中还存在间隙锁（Gap Lock），间隙锁是对索引记录中的一段连续区域的锁；当使用类似`SELECT * FROM users WHERE id BETWEEN 10 AND 20 FOR UPDATE;`的 SQL 语句时，就会阻止其他事务向表中插入`id = 15`的记录，因为整个范围都被间隙锁锁定了。\n\n> 间隙锁是存储引擎对于性能和并发做出的权衡，并且只用于某些事务隔离级别。\n\n虽然间隙锁中也分为共享锁和互斥锁，不过它们之间并不是互斥的，也就是不同的事务可以同时持有一段相同范围的共享锁和互斥锁，它唯一阻止的就是**其他事务向这个范围中添加新的记录**。\n\nNext-Key Lock\n\nNext-Key 锁相比前两者就稍微有一些复杂，它是记录锁和记录前的间隙锁的结合，在`users`表中有以下记录：\n\n```\n +------|-------------|--------------|-------+|   id | last_name   | first_name   |   age ||------|-------------|--------------|-------||    4 | stark       | tony         |    21 ||    1 | tom         | hiddleston   |    30 ||    3 | morgan      | freeman      |    40 ||    5 | jeff        | dean         |    50 ||    2 | donald      | trump        |    80 |+------|-------------|--------------|-------+\n```\n\n如果使用 Next-Key 锁，那么 Next-Key 锁就可以在需要的时候锁定以下的范围：\n\n```\n (-∞, 21](21, 30](30, 40](40, 50](50, 80](80, ∞)\n```\n\n> 既然叫 Next-Key 锁，锁定的应该是当前值和后面的范围，但是实际上却不是，Next-Key 锁锁定的是当前值和前面的范围。\n\n当我们更新一条记录，比如`SELECT * FROM users WHERE age = 30 FOR UPDATE;`，InnoDB 不仅会在范围`(21, 30]`上加 Next-Key 锁，还会在这条记录后面的范围`(30, 40]`加间隙锁，所以插入`(21, 40]`范围内的记录都会被锁定。\n\nNext-Key 锁的作用其实是为了解决幻读的问题，我们会在下一节谈事务的时候具体介绍。\n\n### 死锁的发生\n\n既然 InnoDB 中实现的锁是悲观的，那么不同事务之间就可能会互相等待对方释放锁造成死锁，最终导致事务发生错误；想要在 MySQL 中制造死锁的问题其实非常容易：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405203653.png)\n两个会话都持有一个锁，并且尝试获取对方的锁时就会发生死锁，不过 MySQL 也能在发生死锁时及时发现问题，并保证其中的一个事务能够正常工作，这对我们来说也是一个好消息。\n\n## 事务与隔离级别\n\n在介绍了锁之后，我们再来谈谈数据库中一个非常重要的概念 —— 事务；相信只要是一个合格的软件工程师就对事务的特性有所了解，其中被人经常提起的就是事务的原子性，在数据提交工作时，要么保证所有的修改都能够提交，要么就所有的修改全部回滚。\n\n但是事务还遵循包括原子性在内的 ACID 四大特性：原子性（Atomicity）、一致性（Consistency）、隔离性（Isolation）和持久性（Durability）；文章不会对这四大特性全部展开进行介绍，相信你能够通过 Google 和数据库相关的书籍轻松获得有关它们的概念，本文最后要介绍的就是事务的四种隔离级别。\n\n### 几种隔离级别\n\n事务的隔离性是数据库处理数据的几大基础之一，而隔离级别其实就是提供给用户用于在性能和可靠性做出选择和权衡的配置项。\n\nISO 和 ANIS SQL 标准制定了四种事务隔离级别，而 InnoDB 遵循了 SQL:1992 标准中的四种隔离级别：`READ UNCOMMITED`、`READ COMMITED`、`REPEATABLE READ`和`SERIALIZABLE`；每个事务的隔离级别其实都比上一级多解决了一个问题：\n\n*   `RAED UNCOMMITED`：使用查询语句不会加锁，可能会读到未提交的行（Dirty Read）；\n*   `READ COMMITED`：只对记录加记录锁，而不会在记录之间加间隙锁，所以允许新的记录插入到被锁定记录的附近，所以再多次使用查询语句时，可能得到不同的结果（Non-Repeatable Read）；\n*   `REPEATABLE READ`：多次读取同一范围的数据会返回第一次查询的快照，不会返回不同的数据行，但是可能发生幻读（Phantom Read）；\n*   `SERIALIZABLE`：InnoDB 隐式地将全部的查询语句加上共享锁，解决了幻读的问题；\n\nMySQL 中默认的事务隔离级别就是`REPEATABLE READ`，但是它通过 Next-Key 锁也能够在某种程度上解决幻读的问题。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405203715.png)\n接下来，我们将数据库中创建如下的表并通过个例子来展示在不同的事务隔离级别之下，会发生什么样的问题：\n\n```\n CREATE TABLE test(    id INT NOT NULL,    UNIQUE(id));\n```\n\n### 脏读\n\n> 在一个事务中，读取了其他事务未提交的数据。\n\n当事务的隔离级别为`READ UNCOMMITED`时，我们在`SESSION 2`中插入的**未提交**数据在`SESSION 1`中是可以访问的。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405203738.png)\n### 不可重复读\n\n> 在一个事务中，同一行记录被访问了两次却得到了不同的结果。\n\n当事务的隔离级别为`READ COMMITED`时，虽然解决了脏读的问题，但是如果在`SESSION 1`先查询了**一行**数据，在这之后`SESSION 2`中修改了同一行数据并且提交了修改，在这时，如果`SESSION 1`中再次使用相同的查询语句，就会发现两次查询的结果不一样。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405203753.png)\n不可重复读的原因就是，在`READ COMMITED`的隔离级别下，存储引擎不会在查询记录时添加行锁，锁定`id = 3`这条记录。\n\n### 幻读\n\n> 在一个事务中，同一个范围内的记录被读取时，其他事务向这个范围添加了新的记录。\n\n重新开启了两个会话`SESSION 1`和`SESSION 2`，在`SESSION 1`中我们查询全表的信息，没有得到任何记录；在`SESSION 2`中向表中插入一条数据并提交；由于`REPEATABLE READ`的原因，再次查询全表的数据时，我们获得到的仍然是空集，但是在向表中插入同样的数据却出现了错误。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405203803.png)\n这种现象在数据库中就被称作幻读，虽然我们使用查询语句得到了一个空的集合，但是插入数据时却得到了错误，好像之前的查询是幻觉一样。\n\n在标准的事务隔离级别中，幻读是由更高的隔离级别`SERIALIZABLE`解决的，但是它也可以通过 MySQL 提供的 Next-Key 锁解决：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405203813.png)\n\n`REPERATABLE READ`和`READ UNCOMMITED`其实是矛盾的，如果保证了前者就看不到已经提交的事务，如果保证了后者，就会导致两次查询的结果不同，MySQL 为我们提供了一种折中的方式，能够在`REPERATABLE READ`模式下加锁访问已经提交的数据，其本身并不能解决幻读的问题，而是通过文章前面提到的 Next-Key 锁来解决。\n\n## 总结\n\n> 文章中的内容大都来自于[高性能 MySQL](https://book.douban.com/subject/23008813/)、[MySQL 技术内幕：InnoDB 存储引擎](https://book.douban.com/subject/24708143/)、[数据库索引设计与优化](https://book.douban.com/subject/26419771/)以及 MySQL 的[官方文档](https://dev.mysql.com/doc/)。\n\n由于篇幅所限仅能对数据库中一些重要内容进行简单的介绍和总结，文中内容难免有所疏漏，如果对文章内容的有疑问，可以在博客下面评论留言。\n\n## Innodb与Myisam引擎的区别与应用场景\n\n1. 区别：\n\n（1）事务处理：\n\nMyISAM是非事务安全型的，而InnoDB是事务安全型的（支持事务处理等高级处理）；\n\n（2）锁机制不同：\n\nMyISAM是表级锁，而InnoDB是行级锁；\n\n（3）select ,update ,insert ,delete 操作：\n\nMyISAM：如果执行大量的SELECT，MyISAM是更好的选择\n\nInnoDB：如果你的数据执行大量的INSERT或UPDATE，出于性能方面的考虑，应该使用InnoDB表\n\n（4）查询表的行数不同：\n\nMyISAM：select count(*) from table,MyISAM只要简单的读出保存好的行数，注意的是，当count(*)语句包含 where条件时，两种表的操作是一样的\n\nInnoDB：InnoDB 中不保存表的具体行数，也就是说，执行select count(*) from table时，InnoDB要扫描一遍整个表来计算有多少行\n\n（5）外键支持：\n\nmysiam表不支持外键，而InnoDB支持\n\n2. 为什么MyISAM会比Innodb 的查询速度快。\n\nINNODB在做SELECT的时候，要维护的东西比MYISAM引擎多很多；\n1）数据块，INNODB要缓存，MYISAM只缓存索引块，这中间还有换进换出的减少；\n2）innodb寻址要映射到块，再到行，MYISAM 记录的直接是文件的OFFSET，定位比INNODB要快\n3）INNODB还需要维护MVCC一致；虽然你的场景没有，但他还是需要去检查和维护\n\nMVCC ( Multi-Version Concurrency Control )多版本并发控制\n\n3. 应用场景\n\nMyISAM适合：(1)做很多count 的计算；(2)插入不频繁，查询非常频繁；(3)没有事务。\n\nInnoDB适合：(1)可靠性要求比较高，或者要求事务；(2)表更新和查询都相当的频繁，并且行锁定的机会比较大的情况。\n"
  },
  {
    "path": "docs/database/重新学习MySQL数据库：从实践sql语句优化开始.md",
    "content": "# 目录\n\n* [字段](#字段)\n* [索引](#索引)\n* [查询SQL](#查询sql)\n* [引擎](#引擎)\n  * [MyISAM](#myisam)\n  * [InnoDB](#innodb)\n  * [0、自己写的海量数据sql优化实践](#0、自己写的海量数据sql优化实践)\n* [mysql百万级分页优化](#mysql百万级分页优化)\n  * [普通分页](#普通分页)\n  * [优化分页](#优化分页)\n* [总结](#总结)\n\n\n本文转自互联网\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本也将整理到我的个人博客：\n\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n本文是《重新学习MySQL数据库》系列其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，从sql基本的使用方法，从MySQL执行引擎再到索引、事务等知识，一步步地学习MySQL相关技术的实现原理，更好地了解如何基于这些知识来优化sql，减少SQL执行时间，通过执行计划对SQL性能进行分析，再到MySQL的主从复制、主备部署等内容，以便让你更完整地了解整个MySQL方面的技术体系，形成自己的知识框架。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n\n除非单表数据未来会一直不断上涨，否则不要一开始就考虑拆分，拆分会带来逻辑、部署、运维的各种复杂度，一般以整型值为主的表在`千万级`以下，字符串为主的表在`五百万`以下是没有太大问题的。而事实上很多时候MySQL单表的性能依然有不少优化空间，甚至能正常支撑千万级以上的数据量：\n\n## 字段\n\n*   尽量使用`TINYINT`、`SMALLINT`、`MEDIUM_INT`作为整数类型而非`INT`，如果非负则加上`UNSIGNED`\n\n*   `VARCHAR`的长度只分配真正需要的空间\n\n*   使用枚举或整数代替字符串类型\n\n*   尽量使用`TIMESTAMP`而非`DATETIME`，\n\n*   单表不要有太多字段，建议在20以内\n\n*   避免使用NULL字段，很难查询优化且占用额外索引空间\n\n*   用整型来存IP\n\n## 索引\n\n*   索引并不是越多越好，要根据查询有针对性的创建，考虑在`WHERE`和`ORDER BY`命令上涉及的列建立索引，可根据`EXPLAIN`来查看是否用了索引还是全表扫描\n\n*   应尽量避免在`WHERE`子句中对字段进行`NULL`值判断，否则将导致引擎放弃使用索引而进行全表扫描\n\n*   值分布很稀少的字段不适合建索引，例如\"性别\"这种只有两三个值的字段\n\n*   字符字段只建前缀索引\n\n*   字符字段最好不要做主键\n\n*   不用外键，由程序保证约束\n\n*   尽量不用`UNIQUE`，由程序保证约束\n\n*   使用多列索引时主意顺序和查询条件保持一致，同时删除不必要的单列索引\n\n## 查询SQL\n\n*   可通过开启慢查询日志来找出较慢的SQL\n\n*   不做列运算：`SELECT id WHERE age + 1 = 10`，任何对列的操作都将导致表扫描，它包括数据库教程函数、计算表达式等等，查询时要尽可能将操作移至等号右边\n\n*   sql语句尽可能简单：一条sql只能在一个cpu运算；大语句拆小语句，减少锁时间；一条大sql可以堵死整个库\n\n*   不用`SELECT *`\n\n*   `OR`改写成`IN`：`OR`的效率是n级别，`IN`的效率是log(n)级别，in的个数建议控制在200以内\n\n*   不用函数和触发器，在应用程序实现\n\n*   避免`%xxx`式查询\n\n*   少用`JOIN`\n\n*   使用同类型进行比较，比如用`'123'`和`'123'`比，`123`和`123`比\n\n*   尽量避免在`WHERE`子句中使用!=或<>操作符，否则将引擎放弃使用索引而进行全表扫描\n\n*   对于连续数值，使用`BETWEEN`不用`IN`：`SELECT id FROM t WHERE num BETWEEN 1 AND 5`\n\n*   列表数据不要拿全表，要使用`LIMIT`来分页，每页数量也不要太大\n\n## 引擎\n\n目前广泛使用的是MyISAM和InnoDB两种引擎：\n\n### MyISAM\n\nMyISAM引擎是MySQL 5.1及之前版本的默认引擎，它的特点是：\n\n*   不支持行锁，读取时对需要读到的所有表加锁，写入时则对表加排它锁\n\n*   不支持事务\n\n*   不支持外键\n\n*   不支持崩溃后的安全恢复\n\n*   在表有读取查询的同时，支持往表中插入新纪录\n\n*   支持`BLOB`和`TEXT`的前500个字符索引，支持全文索引\n\n*   支持延迟更新索引，极大提升写入性能\n\n*   对于不会进行修改的表，支持压缩表，极大减少磁盘空间占用\n\n### InnoDB\n\nInnoDB在MySQL 5.5后成为默认索引，它的特点是：\n\n*   支持行锁，采用MVCC来支持高并发\n\n*   支持事务\n\n*   支持外键\n\n*   支持崩溃后的安全恢复\n\n*   不支持全文索引\n\n总体来讲，MyISAM适合`SELECT`密集型的表，而InnoDB适合`INSERT`和`UPDATE`密集型的表\n\n### 0、自己写的海量数据sql优化实践\n\n首先是建表和导数据的过程。\n\n参考[https://nsimple.top/archives/mysql-create-million-data.html](https://nsimple.top/archives/mysql-create-million-data.html)\n\n> 有时候我们需要对大数据进行测试，本地一般没有那么多数据，就需要我们自己生成一些。下面会借助内存表的特点进行生成百万条测试数据。\n\n1.  创建一个临时内存表, 做数据插入的时候会比较快些\n\nSQL\n\n```\n-- 创建一个临时内存表DROP TABLE IF EXISTS `vote_record_memory`;CREATE TABLE `vote_record_memory` (    `id` int(10) unsigned NOT NULL AUTO_INCREMENT,    `user_id` varchar(20) NOT NULL DEFAULT '',    `vote_num` int(10) unsigned NOT NULL DEFAULT '0',    `group_id` int(10) unsigned NOT NULL DEFAULT '0',    `status` tinyint(2) unsigned NOT NULL DEFAULT '1',    `create_time` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',    PRIMARY KEY (`id`),    KEY `index_user_id` (`user_id`) USING HASH) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; \n```\n\n1.  -- 创建一个普通表，用作模拟大数据的测试用例\n\nSQL\n\n```\nDROP TABLE IF EXISTS `vote_record`;CREATE TABLE `vote_record` (    `id` int(10) unsigned NOT NULL AUTO_INCREMENT,    `user_id` varchar(20) NOT NULL DEFAULT '' COMMENT '用户Id',    `vote_num` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '投票数',    `group_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '用户组id 0-未激活用户 1-普通用户 2-vip用户 3-管理员用户',    `status` tinyint(2) unsigned NOT NULL DEFAULT '1' COMMENT '状态 1-正常 2-已删除',    `create_time` int(10) unsigned NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '创建时间',    PRIMARY KEY (`id`),    KEY `index_user_id` (`user_id`) USING HASH COMMENT '用户ID哈希索引') ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='投票记录表'; \n```\n\n1.  为了数据的随机性和真实性，我们需要创建一个可生成长度为n的随机字符串的函数。\n\nSQL\n\n```\n-- 创建生成长度为n的随机字符串的函数DELIMITER // -- 修改MySQL delimiter：'//'DROP FUNCTION IF EXISTS `rand_string` //SET NAMES utf8 //CREATE FUNCTION `rand_string` (n INT) RETURNS VARCHAR(255) CHARSET 'utf8'BEGIN     DECLARE char_str varchar(100) DEFAULT 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';    DECLARE return_str varchar(255) DEFAULT '';    DECLARE i INT DEFAULT 0;    WHILE i < n DO        SET return_str = concat(return_str, substring(char_str, FLOOR(1 + RAND()*62), 1));        SET i = i+1;    END WHILE;    RETURN return_str;END // \n```\n\n1.  为了操作方便，我们再创建一个插入数据的存储过程\n\nSQL\n\n```\n-- 创建插入数据的存储过程DROP PROCEDURE IF EXISTS `add_vote_record_memory` //CREATE PROCEDURE `add_vote_record_memory`(IN n INT)BEGIN    DECLARE i INT DEFAULT 1;    DECLARE vote_num INT DEFAULT 0;    DECLARE group_id INT DEFAULT 0;    DECLARE status TINYINT DEFAULT 1;    WHILE i < n DO        SET vote_num = FLOOR(1 + RAND() * 10000);        SET group_id = FLOOR(0 + RAND()*3);        SET status = FLOOR(1 + RAND()*2);        INSERT INTO `vote_record_memory` VALUES (NULL, rand_string(20), vote_num, group_id, status, NOW());        SET i = i + 1;    END WHILE;END //DELIMITER ;  -- 改回默认的 MySQL delimiter：';' \n```\n\n1.  开始执行存储过程，等待生成数据(10W条生成大约需要40分钟)\n\nSQL\n\n```\n-- 调用存储过程 生成100W条数据CALL add_vote_record_memory(1000000); \n```\n\n1.  查询内存表已生成记录(为了下步测试，目前仅生成了105645条)\n\nSQL\n\n```\nSELECT count(*) FROM `vote_record_memory`;-- count(*)-- 105646 \n```\n\n1.  把数据从内存表插入到普通表中(10w条数据13s就插入完了)\n\nSQL\n\n```\nINSERT INTO vote_record SELECT * FROM `vote_record_memory`; \n```\n\n1.  查询普通表已的生成记录\n\nSQL\n\n```\nSELECT count(*) FROM `vote_record`;-- count(*)-- 105646 \n```\n\n1.  如果一次性插入普通表太慢，可以分批插入，这就需要写个存储过程了：\n\nSQL\n\n```\n-- 参数n是每次要插入的条数-- lastid是已导入的最大idCREATE PROCEDURE `copy_data_from_tmp`(IN n INT)BEGIN    DECLARE lastid INT DEFAULT 0;    SELECT MAX(id) INTO lastid FROM `vote_record`;    INSERT INTO `vote_record` SELECT * FROM `vote_record_memory` where id > lastid LIMIT n;END \n```\n\n1.  调用存储过程:\n\nSQL\n\n```\n-- 调用存储过程 插入60w条CALL copy_data_from_tmp(600000);\n```\n\nSELECT * FROM vote_record；\n\n全表查询\n\n建完表以后开启慢查询日志，具体参考下面的例子，然后学会用explain。windows慢日志的位置在c盘，另外，使用client工具也可以记录慢日志，所以不一定要用命令行来执行测试，否则大表数据在命令行中要显示的非常久。\n\n**1 全表扫描select * from vote_record**\n\n* * *\n\n**慢日志**\n\nSET timestamp=1529034398; select * from vote_record;\n\nTime: 2018-06-15T03:52:58.804850Z\n\nUser[@Host](https://my.oschina.net/u/116016): root[root] @ localhost [::1] Id:  74\n\nQuery_time: 3.166424 Lock_time: 0.000000 Rows_sent: 900500 Rows_examined: 999999\n\n耗时3秒，我设置的门槛是一秒。所以记录了下来。\n\n**explain执行计划**\n\n````\nid select_type table partitions type possible_keys key key_len ref rows filtered Extra\n\n1 SIMPLE vote_record \\N ALL \\N \\N \\N \\N 996507 100.00 \\N\n````\n\n全表扫描耗时3秒多，用不到索引。\n\n````\n**2 select * from vote_record where vote_num > 1000**\n````\n\n没有索引，所以相当于全表扫描，一样是3.5秒左右\n\n````\n**3 select * from vote_record where vote_num > 1000**\n````\n\n**加索引create**\n\n````\n**CREATE INDEX vote ON vote_record(vote_num);**\n````\n\n**explain查看执行计划**\n\n````\nid select_type table partitions type possible_keys key key_len ref rows filtered Extra\n\n1 SIMPLE vote_record \\N ALL votenum,vote \\N \\N \\N 996507 50.00 Using where\n````\n\n还是没用到索引，因为不符合最左前缀匹配。查询需要3.5秒左右\n\n最后修改一下sql语句\n\n````\nEXPLAIN SELECT * FROM vote_record WHERE id > 0 AND vote_num > 1000;\n\nid select_type table partitions type possible_keys key key_len ref rows filtered Extra\n\n1 SIMPLE vote_record \\N range PRIMARY,votenum,vote PRIMARY 4 \\N 498253 50.00 Using where\n````\n\n用到了索引，但是只用到了主键索引。再修改一次\n\n````\nEXPLAIN SELECT * FROM vote_record WHERE id > 0 AND vote_num = 1000;\n\nid select_type table partitions type possible_keys key key_len ref rows filtered Extra\n\n1 SIMPLE vote_record \\N index_merge PRIMARY,votenum,vote votenum,PRIMARY 8,4 \\N 51 100.00 Using intersect(votenum,PRIMARY); Using where\n````\n\n用到了两个索引，votenum,PRIMARY。\n\n这是为什么呢。\n\n再看一个语句\n\n````\nEXPLAIN SELECT * FROM vote_record WHERE id = 1000 AND vote_num > 1000\n\nid select_type table partitions type possible_keys key key_len ref rows filtered Extra\n\n1 SIMPLE vote_record \\N const PRIMARY,votenum PRIMARY 4 const 1 100.00 \\N\n````\n\n也只有主键用到了索引。这是因为只有最左前缀索引可以用>或<，其他索引用<或者>会导致用不到索引。\n\n下面是几个网上参考的例子：\n\n一：索引是sql语句优化的关键，学会使用慢日志和执行计划分析sql\n\n背景：使用A电脑安装mysql,B电脑通过xshell方式连接，数据内容我都已经创建好，现在我已正常的进入到mysql中\n\n步骤1：设置慢查询日志的超时时间，先查看日志存放路径查询慢日志的地址，因为有慢查询的内容，就会到这个日志中：\n\nshow global variables like \"%slow%\";\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/318ac710c3fcf1b293f4a280bbb4642a951.jpg)\n\n2.开启慢查询日志\n\nset global slow_query_log=on;\n\n3.查看慢查询日志的设置时间，是否是自己需要的\n\nshow global variables like \"%long%\";\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/25c18a8fbb6cc43a48b554bde844191d75d.jpg)\n\n4.如果不是自己想的时间，修改慢查询时间，只要超过了以下的设置时间，查询的日志就会到刚刚的日志中，我设置查询时间超过1S就进入到慢查询日志中\n\nset global long_query_time=1;\n\n5.大数据已准备，进行数据的查询，xshell最好开两个窗口，一个查看日志，一个执行内容\n\nSql查询语句：select sql_no_cache * from employees_tmp where first_name='Duangkaew' and gender='M'\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/19fa580673fef04c767680a8010c444b786.jpg)\n\n发现查数据的总时间去掉了17.74S\n\n查看日志：打开日志\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/a55efa460d5393061ddb62d6d225d62532a.jpg)\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/290fac71753491a5a18fe66015adc1f7687.jpg)\n\n标记1：执行的sql语句\n\n标记2：执行sql的时间，我的是10点52执行的\n\n标记3：使用那台机器\n\n标记4：执行时间，query_tims,查询数据的时间\n\n标记5：不知道是干嘛的\n\n标记6：执行耗时的sql语句，我在想我1的应该是截取错了！但是记住最后一定是显示耗时是因为执行什么sql造成的\n\n6.执行打印计划，主要是查看是否使用了索引等其他内容,主要就是在sql前面加上explain 关键字\n\n````\nexplain select sql_no_cache * from employees_tmp where first_name='Duangkaew' and gender='M';\n````\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/c15fd4c1db312050661a019557401f4b1cf.jpg)\n\n描述extra中，表示只使用了where条件，没有其他什么索引之类的\n\n7.进行sql优化，建一个fist_name的索引，索引就是将你需要的数据先给筛选出来，这样就可以节省很多扫描时间\n\n````\ncreate index firstname on employees_tmp(first_name);\n````\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/325f92967bec30c8fcec3376725d21ea9ad.jpg)\n\n注：创建索引时会很慢，是对整个表做了一个复制功能，并进行数据的一些分类（我猜是这样，所以会很慢）\n\n8.查看建立的索引\n\n````\nshow index from employees_tmp;\n````\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/240bdc203ec8db9c4e2f9a1715816dcf928.jpg)\n\n9.在执行查询语句，查看语句的执行时间\n\n````\nselect sql_no_cache * from employees_tmp where first_name='Duangkaew' and gender='M'\n````\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/addc98d4a52648e09b8c29f773865cf00ba.jpg)\n\n发现时间已经有所提升了，其实选择索引也不一开始就知道，我们在试试使用性别，gender进行索引\n\n10.删除已经有的索引，删除索引：\n\n````\ndrop index first_name on employees_tmp;\n````\n\n11.创建性别的索引(性别是不怎么好的索引方式，因为有很多重复数据)\n\n````\ncreate index index_gendar on employees_tmp(gender);\n````\n\n在执行sql语句查询数据，查看查询执行时间，没有创建比较优秀的索引，导致查询时间还变长了，\n\n为嘛还变长了，这个我没有弄懂\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/d043c97d87b9fa6eb65f17b281db14d4df8.jpg)\n\n12.我们在试试使用创建组合索引，使用性别和姓名\n\n````\nalter table employees_tmp add index idx_union (first_name,gender);\n````\n\n在执行sql查看sql数据的执行时间\n\n````\nselect sql_no_cache * from employees_tmp where first_name='Duangkaew' and gender='M'\n````\n\n速度提升了N多倍啊\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/a2da1ce647c8e31ec16d9a6750f1e29b014.jpg)\n\n查看创建的索引\n\n````\nshow index from employees_tmp;\n````\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/9704ee337b205ce64e31dbe4c12af495c5b.jpg)\n\n索引建的好真的一个好帮手，建不好就是费时的一个操作\n\n目前还不知道为什么建立性别的索引会这么慢\n\n二：sql优化注意要点，比如索引是否用到，查询优化是否改变了执行计划，以及一些细节\n\n场景\n\n我用的数据库是mysql5.6，下面简单的介绍下场景\n\n课程表\n\n```\ncreate table Course( c_id int PRIMARY KEY, name varchar(10) )\n```\n\n数据100条\n\n学生表:\n\n```\ncreate table Student( id int PRIMARY KEY, name varchar(10) )\n```\n\n数据70000条\n\n学生成绩表SC\n\n```\nCREATE table SC(     sc_id int PRIMARY KEY,     s_id int,     c_id int,     score int )\n```\n\n数据70w条\n\n查询目的：\n\n查找语文考100分的考生\n\n查询语句：\n\n```\nselect s.* from Student s where s.s_id in (select s_id from SC sc where sc.c_id = 0 and sc.score = 100 )\n\n```\n\n执行时间：30248.271s\n\n晕,为什么这么慢，先来查看下查询计划：\n\n```\nEXPLAIN  select s.* from Student s where s.s_id in (select s_id from SC sc where sc.c_id = 0 and sc.score = 100 )\n```\n\n![image](http://static.codeceo.com/images/2015/04/d70d7123827c0d988fdc69074a97105b.png \"image\")\n\n发现没有用到索引，type全是ALL，那么首先想到的就是建立一个索引，建立索引的字段当然是在where条件的字段。\n\n先给sc表的c_id和score建个索引\n\n```\nCREATE index sc_c_id_index on SC(c_id);\n\n```\n\n```\nCREATE index sc_score_index on SC(score);\n\n```\n\n再次执行上述查询语句，时间为: 1.054s\n\n快了3w多倍，大大缩短了查询时间，看来索引能极大程度的提高查询效率，看来建索引很有必要，很多时候都忘记建\n\n索引了，数据量小的的时候压根没感觉，这优化感觉挺爽。\n\n但是1s的时间还是太长了，还能进行优化吗，仔细看执行计划：\n\n\n查看优化后的sql:\n\n```\nSELECT    `YSB`.`s`.`s_id` AS `s_id`,    `YSB`.`s`.`name` AS `name`FROM    `YSB`.`Student` `s`WHERE    < in_optimizer > (        `YSB`.`s`.`s_id` ,< EXISTS > (            SELECT                1            FROM                `YSB`.`SC` `sc`            WHERE                (                    (`YSB`.`sc`.`c_id` = 0)                    AND (`YSB`.`sc`.`score` = 100)                    AND (                        < CACHE > (`YSB`.`s`.`s_id`) = `YSB`.`sc`.`s_id`                    )                )        )    )\n```\n\n补充：这里有网友问怎么查看优化后的语句\n\n方法如下：\n\n在命令窗口执行\n\n有type=all\n\n按照我之前的想法，该sql的执行的顺序应该是先执行子查询\n\n```\nselect s_id from SC sc where sc.c_id = 0 and sc.score = 100\n\n```\n\n耗时：0.001s\n\n然后再执行\n\n```\nselect s.* from Student s where s.s_id in(7,29,5000)\n\n```\n\n耗时：0.001s\n\n这样就是相当快了啊，Mysql竟然不是先执行里层的查询，而是将sql优化成了exists子句，并出现了EPENDENT SUBQUERY，\n\nmysql是先执行外层查询，再执行里层的查询，这样就要循环70007*11=770077次。\n\n那么改用连接查询呢？\n\n```\nSELECT s.* from  Student s INNER JOIN SC sc on sc.s_id = s.s_id where sc.c_id=0 and sc.score=100\n```\n\n这里为了重新分析连接查询的情况，先暂时删除索引sc_c_id_index，sc_score_index\n\n执行时间是：0.057s\n\n效率有所提高，看看执行计划：\n\n这里有连表的情况出现，我猜想是不是要给sc表的s_id建立个索引\n\nCREATE index sc_s_id_index on SC(s_id);\n\nshow index from SC\n\n在执行连接查询\n\n时间: 1.076s，竟然时间还变长了，什么原因？查看执行计划：\n\n优化后的查询语句为：\n\n```\nSELECT    `YSB`.`s`.`s_id` AS `s_id`,    `YSB`.`s`.`name` AS `name`FROM    `YSB`.`Student` `s`JOIN `YSB`.`SC` `sc`WHERE    (        (            `YSB`.`sc`.`s_id` = `YSB`.`s`.`s_id`        )        AND (`YSB`.`sc`.`score` = 100)        AND (`YSB`.`sc`.`c_id` = 0)    )\n```\n\n貌似是先做的连接查询，再执行的where过滤\n\n回到前面的执行计划：\n\n这里是先做的where过滤，再做连表，执行计划还不是固定的，那么我们先看下标准的sql执行顺序：\n\n\n正常情况下是先join再where过滤，但是我们这里的情况，如果先join，将会有70w条数据发送join做操，因此先执行where\n\n过滤是明智方案，现在为了排除mysql的查询优化，我自己写一条优化后的sql\n\n```\nSELECT    s.*FROM    (        SELECT            *        FROM            SC sc        WHERE            sc.c_id = 0        AND sc.score = 100    ) tINNER JOIN Student s ON t.s_id = s.s_id\n```\n\n即先执行sc表的过滤，再进行表连接，执行时间为：0.054s\n\n和之前没有建s_id索引的时间差不多\n\n查看执行计划：\n\n先提取sc再连表，这样效率就高多了，现在的问题是提取sc的时候出现了扫描表，那么现在可以明确需要建立相关索引\n\n```\nCREATE index sc_c_id_index on SC(c_id);\n\n```\n\n```\nCREATE index sc_score_index on SC(score);\n\n```\n\n再执行查询：\n\n```\nSELECT    s.*FROM    (        SELECT            *        FROM            SC sc        WHERE            sc.c_id = 0        AND sc.score = 100    ) tINNER JOIN Student s ON t.s_id = s.s_id\n```\n\n执行时间为：0.001s，这个时间相当靠谱，快了50倍\n\n执行计划：\n\n我们会看到，先提取sc，再连表，都用到了索引。\n\n那么再来执行下sql\n\n```\nSELECT s.* from  Student s INNER JOIN SC sc on sc.s_id = s.s_id where sc.c_id=0 and sc.score=100\n```\n\n执行时间0.001s\n\n执行计划：\n\n这里是mysql进行了查询语句优化，先执行了where过滤，再执行连接操作，且都用到了索引。\n\n总结：\n\n1.mysql嵌套子查询效率确实比较低\n\n2.可以将其优化成连接查询\n\n3.建立合适的索引\n\n4.学会分析sql执行计划，mysql会对sql进行优化，所以分析执行计划很重要\n\n由于时间问题，这篇文章先写到这里，后续再分享其他的sql优化经历。\n\n三、海量数据分页查找时如何使用主键索引进行优化\n\n## mysql百万级分页优化\n\n### 普通分页\n\n数据分页在网页中十分多见，分页一般都是limit start,offset,然后根据页码page计算start\n\n　select * from user limit **1**,**20**\n\n这种分页在几十万的时候分页效率就会比较低了，MySQL需要从头开始一直往后计算，这样大大影响效率\n\nSELECT * from user limit **100001**,**20**; //time **0**.151s explain SELECT * from user limit **100001**,**20**;\n\n我们可以用explain分析下语句，没有用到任何索引，MySQL执行的行数是16W+，于是我们可以想用到索引去实现分页\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/d327669b28bc017d62dfe25833ba98245cf.jpg)\n\n### 优化分页\n\n使用主键索引来优化数据分页\n\n select * from user where id>(select id from user where id>=**100000** limit **1**) limit **20**; //time **0**.003s\n\n使用explain分析语句，MySQL这次扫描的行数是8W+，时间也大大缩短。\n\n explain select * from user where id>(select id from user where id>=**100000** limit **1**) limit **20**;\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/05fffbffc5e3ef9add4719846ad53f25099.jpg)\n\n## 总结\n\n在数据量比较大的时候，我们尽量去利用索引来优化语句。上面的优化方法如果id不是主键索引，查询效率比第一种还要低点。我们可以先使用explain来分析语句，查看语句的执行顺序和执行性能。\n\n\n\n转载于:https://my.oschina.net/alicoder/blog/3097141\n"
  },
  {
    "path": "docs/database/重新学习MySQL数据库：以Java的视角来聊聊SQL注入.md",
    "content": "# 目录\n  * [前言](#前言)\n    * [靶场准备](#靶场准备)\n    * [SQL注入测试](#sql注入测试)\n  * [注入原理分析](#注入原理分析)\n  * [JDBC的预处理](#jdbc的预处理)\n  * [Mybatis下注入防范](#mybatis下注入防范)\n  * [JPA注入防范](#jpa注入防范)\n  * [SQL注入的其他防范办法](#sql注入的其他防范办法)\n  * [小结](#小结)\n\n\n本文转自互联网\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本也将整理到我的个人博客：\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n本文是《重新学习MySQL数据库》系列其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，从sql基本的使用方法，从MySQL执行引擎再到索引、事务等知识，一步步地学习MySQL相关技术的实现原理，更好地了解如何基于这些知识来优化sql，减少SQL执行时间，通过执行计划对SQL性能进行分析，再到MySQL的主从复制、主备部署等内容，以便让你更完整地了解整个MySQL方面的技术体系，形成自己的知识框架。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n本文转自互联网\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n文章首发于我的个人博客：\n> www.how2playlife.com\n\n本文是微信公众号【Java技术江湖】的《重新学习MySQL数据库》其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，从sql基本的使用方法，从MySQL执行引擎再到索引、事务等知识，一步步地学习MySQL相关技术的实现原理，更好地了解如何基于这些知识来优化sql，减少SQL执行时间，通过执行计划对SQL性能进行分析，再到MySQL的主从复制、主备部署等内容，以便让你更完整地了解整个MySQL方面的技术体系，形成自己的知识框架。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n\n## 前言\n\n\n\n\n### 靶场准备\n\n首先我们来准备一个web接口服务，该服务可以提供管理员的信息查询，这里我们采用springboot + jersey 来构建web服务框架，数据库则采用最常用的mysql。下面，我们来准备测试环境，首先建立一张用户表jwtk_admin，SQL如下：\n\n然后插入默认的管理员：\n\n这样我们就有了两位系统内置管理员了，管理员密码采用MD5进行Hash，当然这是一个很简单的为了作为研究靶场的表，所以没有很全的字段。\n\n接下来，我们创建 spring boot + jersey 构建的RESTFul web服务，这里我们提供了一个通过管理员用户名查询管理员具体信息的接口，如下：\n\n\n\n### SQL注入测试\n\n首先我们以开发者正向思维向web服务发送管理员查询请求，这里我们用PostMan工具发送一个GET请求\n\n不出我们和开发者所料，Web接口返回了我们想要的结果，用户名为admin的管理员信息。OK，现在开发任务完成，Git Push，Jira任务点为待测试，那么这样的接口就真的没有问题了吗？现在我们发送这样一条GET请求：\n\n发送该请求后，我们发现PostMan没有接收到返回结果，而Web服务后台却开始抛 MySQLSyntaxErrorException异常了，错误如下：\n\nYou have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ‘‘xxxx’’’ at line 1\n\n原因是在我们查询的 xxxx’ 处sql语句语法不正确导致。这里我们先不讨论SQL语法问题，我们继续实验，再次构造一条GET查询请求:\n\n此时，我们可以惊讶的发现，查询接口非但没有报错，反而将我们数据库jwti_admin表中的所有管理员信息都查询出来了：\n\n这是什么鬼，难道管理员表中还有 name=xxxx’or’a’='a 的用户？这就是 SQL Injection。\n\n## 注入原理分析\n\n在接口中接受了一个String类型的name参数，并且通过字符串拼接的方式构建了查询语句。在正常情况下，用户会传入合法的name进行查询，但是黑客却会传入精心构造的参数，只要参数通过字符串拼接后依然是一句合法的SQL查询，此时SQL注入就发生了。正如我们上文输入的name=xxxx’or’a’='a与我们接口中的查询语句进行拼接后构成如下SQL语句：\n\n当接口执行此句SQL后，系统后台也就相当于拱手送给黑客了，黑客一看到管理员密码这个hash，都不用去cmd5查了，直接就用123456密码去登录你的后台系统了。Why？因为123456的md5哈希太常见了，别笑，这就是很多中小网站的现实，弱口令横行，不见棺材不落泪！\n\n好了，现在我们应该明白了，SQL Injection原因就是由于传入的参数与系统的SQL拼接成了合法的SQL而导致的，而其本质还是将用户输入的数据当做了代码执行。在系统中只要有一个SQL注入点被黑客发现，那么黑客基本上可以执行任意想执行的SQL语句了，例如添加一个管理员，查询所有表，甚至“脱裤” 等等，当然本文不是讲解SQL注入技巧的文章，这里我们只探讨SQL注入发生的原因与防范方法。\n\n## JDBC的预处理\n\n在上文的接口中，DAO使用了比较基础的JDBC的方式进行数据库操作，直接使JDBC构建DAO在比较老的系统中还是很常见的，但这并不意味着使用JDBC就一定不安全，如果我将传入的参数 xxxx’or’a’='a 整体作为参数进行name查询，那就不会产生SQL注入。在JDBC中，提供了 PreparedStatement （预处理执行语句）的方式，可以对SQL语句进行查询参数化，使用预处理后的代码如下：\n\n同样，我们使用上文的注入方式注入 ，此时我们发现，SQL注入没能成功。现在，我们来打印一下被被预处理后的SQL，看看有什么变化：\n\n看到了吗?所有的 ’ 都被 ’ 转义掉了,从而可以确保SQL的查询参数就是参数，不会被恶意执行，从而防止了SQL注入。\n\n## Mybatis下注入防范\n\nMyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架， 其几乎避免了所有的 JDBC 代码和手动设置参数以及获取结果集。同时，MyBatis 可以对配置和原生Map使用简单的 XML 或注解，将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录，因此mybatis现在在市场中采用率也非常高。这里我们定义如下一个mapper，来实现通过用户名查询管理员的接口：\n\n同样提供Web访问接口：\n\n接下来，我们尝试SQL注入name字段，可以发现注入并没有成功，通过打印mybatis的Log可以看到mybatis框架对参数进行了预处理处理，从而防止了注入：\n\n那是否只要使用了mybatis就一定可以避免SQL注入的危险？我们把mapper做如下修改，将参数#{name}修改为${name}，并使用name=‘xxxx’ or ‘a’=‘a’ 作为GET请求的参数，可以发现SQL注入还是发生了：\n\n那这是为什么，mybatis ${}与#{}的差别在哪里？\n\n原来在mybatis中如果以形式声明为SQL传递参数，mybatis将不会进行参数预处理，会直接动态拼接SQL语句，此时就会存在被注入的风险，所以在使用mybatis作为持久框架时应尽量避免采用\n\n形式声明为SQL传递参数，mybatis将不会进行参数预处理，会直接动态拼接SQL语句，此时就会存在被注入的风险，所以在使用mybatis作为持久框架时应尽量避免采用形式声明为SQL传递参数。\n\nmybatis将不会进行参数预处理，会直接动态拼接SQL语句，此时就会存在被注入的风险，所以在使用mybatis作为持久框架时应尽量避免采用{}的形式进行参数传递，如果无法避免（有些SQL如like、in、order by等，程序员可能依旧会选择${}的方式传参），那就需要对传入参数自行进行转义过滤。\n\n## JPA注入防范\n\nJPA是Sun公司用来整合ORM技术，实现天下归一的ORM标准而定义的Java Persistence API（java持久层API），JPA只是一套接口，目前引入JPA的项目都会采用Hibernate作为其具体实现，随着无配置Spring Boot框架的流行，JPA越来越具有作为持久化首选的技术，因为其能让程序员写更少的代码，就能完成现有的功能。\n\n例如强大的JpaRepository，常规的SQL查询只需按照命名规则定义接口，便可以不写SQL（JPQL/SQL）就可以实现数据的查询操作，从SQL注入防范的角度来说，这种将安全责任抛给框架远比依靠程序员自身控制来的保险。因此如果项目使用JPA作为数据访问层，基本上可以很大程度的消除SQL注入的风险。\n\n但是话不能说的太死，在我见过的一个Spring Boot项目中，虽然采用了JPA作为持久框架，但是有一位老程序员不熟悉于使用JPQL来构建查询接口，依旧使用字符串拼接的方式来实现业务，而为项目安全埋下了隐患。\n\n安全需要一丝不苟，安全是100 - 1 = 0的业务，即使你防御了99%的攻击，那还不算胜利，只要有一次被入侵了，那就有可能给公司带来很严重的后果。\n\n关于JPA的SQL注入，我们就不详细讨论了，因为框架下的注入漏洞属于框架漏洞范畴（如CVE-2016-6652），程序员只要遵循JPA的开发规范，就无需担心注入问题，框架都为你做好幕后工作了。\n\n## SQL注入的其他防范办法\n\n很多公司都会存在老系统中有大量SQL注入风险代码的问题，但是由于其已稳定支持公司业务很久，不宜采用大面积代码更新的方式来消除注入隐患，所以需要考虑其采用他方式来防范SQL注入。除了在在SQL执行方式上防范SQL注入，很多时候还可以通过架构上，或者通过其他过滤方式来达到防止SQL注入的效果。\n\n一切输入都是不安全的：对于接口的调用参数，要进行格式匹配，例如admin的通过name查询的接口，与之匹配的Path应该使用正则匹配（因为用户名中不应该存在特殊字符），从而确保传入参数是程序控制范围之内的参数，即只接受已知的良好输入值，拒绝不良输入。注意：验证参数应将它与输出编码技术结合使用。\n\n利用分层设计来避免危险：前端尽量静态化，尽量少的暴露可以访问到DAO层的接口到公网环境中，如果现有项目，很难修改存在注入的代码，可以考虑在web服务之前增加WAF进行流量过滤，当然代码上就不给hacker留有攻击的漏洞才最好的方案。也可以在拥有nginx的架构下，采用OpenRestry做流量过滤，将一些特殊字符进行转义处理。\n\n尽量使用预编译SQL语句：由于动态SQL语句是引发SQL注入的根源。应使用预编译语句来组装SQL查询。\n\n规范化：将输入安装规定编码解码后再进行输入参数过滤和输出编码处理；拒绝一切非规范格式的编码。\n\n## 小结\n\n其实随着ORM技术的发展，Java web开发在大趋势上已经越来越远离SQL注入的问题了，而有着Entity Framework框架支持的ASP.NET MVC从来都是高冷范。在现在互联网中，使用PHP和Python构建的web应用是目前SQL注入的重灾区。本文虽然是从JAVA的角度来研究SQL注入的问题，但原理上同样适用于其他开发语言，希望读者可以通过此文，触类旁通。\n\n珍爱数据，远离拼接，有输入的地方就会有江湖…\n\n\n\n\n\n"
  },
  {
    "path": "docs/database/重新学习MySQL数据库：无废话MySQL入门.md",
    "content": "# 目录\n  * [前言](#前言)\n    * [登录MySQL](#登录mysql)\n    * [创建数据库](#创建数据库)\n    * [创建数据库表](#创建数据库表)\n  * [增删改查](#增删改查)\n    * [SELECT](#select)\n    * [UPDATE](#update)\n    * [INSERT](#insert)\n    * [DELETE](#delete)\n  * [WHERE](#where)\n  * [AND 和 OR](#and-和-or)\n    * [AND](#and)\n    * [OR](#or)\n  * [ORDER BY](#order-by)\n  * [IN](#in)\n  * [NOT](#not)\n  * [UNION](#union)\n  * [AS](#as)\n  * [JOIN](#join)\n  * [SQL 函数](#sql-函数)\n    * [COUNT](#count)\n    * [MAX](#max)\n  * [触发器](#触发器)\n  * [添加索引](#添加索引)\n    * [普通索引(INDEX)](#普通索引index)\n    * [主键索引(PRIMARY key)](#主键索引primary-key)\n    * [唯一索引(UNIQUE)](#唯一索引unique)\n    * [全文索引(FULLTEXT)](#全文索引fulltext)\n    * [添加多列索引](#添加多列索引)\n    * [建立索引的时机](#建立索引的时机)\n  * [创建后表的修改](#创建后表的修改)\n    * [添加列](#添加列)\n    * [修改列](#修改列)\n    * [删除列](#删除列)\n    * [重命名表](#重命名表)\n    * [清空表数据](#清空表数据)\n    * [删除整张表](#删除整张表)\n    * [删除整个数据库](#删除整个数据库)\n\n\n本文转自互联网\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本也将整理到我的个人博客：\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n本文是《重新学习MySQL数据库》系列其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，从sql基本的使用方法，从MySQL执行引擎再到索引、事务等知识，一步步地学习MySQL相关技术的实现原理，更好地了解如何基于这些知识来优化sql，减少SQL执行时间，通过执行计划对SQL性能进行分析，再到MySQL的主从复制、主备部署等内容，以便让你更完整地了解整个MySQL方面的技术体系，形成自己的知识框架。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n\n## 前言 \n开始使用\n\n我下面所有的SQL语句是基于MySQL 5.6+运行。\n\nMySQL 为关系型数据库(Relational Database Management System)，一个关系型数据库由一个或数个表格组成, 如图所示的一个表格：\n\n*   `表头(header)`: 每一列的名称;\n\n*   `列(col)`: 具有相同数据类型的数据的集合;\n\n*   `行(row)`: 每一行用来描述某个人/物的具体信息;\n\n*   `值(value)`: 行的具体信息, 每个值必须与该列的数据类型相同;\n\n*   `键(key)`: 表中用来识别某个特定的人物的方法, 键的值在当前列中具有唯一性。\n\n### 登录MySQL\n\n```\nmysql -h 127.0.0.1 -u 用户名 -pmysql -D 所选择的数据库名 -h 主机名 -u 用户名 -pmysql> exit # 退出 使用 “quit;” 或 “\\q;” 一样的效果mysql> status;  # 显示当前mysql的version的各种信息mysql> select version(); # 显示当前mysql的version信息mysql> show global variables like 'port'; # 查看MySQL端口号\n```\n\n### 创建数据库\n\n对于表的操作需要先进入库`use 库名;`\n\n```\n-- 创建一个名为 samp_db 的数据库，数据库字符编码指定为 gbkcreate database samp_db character set gbk;drop database samp_db; -- 删除 库名为samp_db的库show databases;        -- 显示数据库列表。use samp_db;     -- 选择创建的数据库samp_dbshow tables;     -- 显示samp_db下面所有的表名字describe 表名;    -- 显示数据表的结构delete from 表名; -- 清空表中记录\n```\n\n### 创建数据库表\n\n> 使用 create table 语句可完成对表的创建, create table 的常见形式:\n> 语法：create table 表名称(列声明);\n\n```\nCREATE TABLE `user_accounts` (  `id`             int(100) unsigned NOT NULL AUTO_INCREMENT primary key,  `password`       varchar(32)       NOT NULL DEFAULT '' COMMENT '用户密码',  `reset_password` tinyint(32)       NOT NULL DEFAULT 0 COMMENT '用户类型：0－不需要重置密码；1-需要重置密码',  `mobile`         varchar(20)       NOT NULL DEFAULT '' COMMENT '手机',  `create_at`      timestamp(6)      NOT NULL DEFAULT CURRENT_TIMESTAMP(6),  `update_at`      timestamp(6)      NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),  -- 创建唯一索引，不允许重复  UNIQUE INDEX idx_user_mobile(`mobile`))ENGINE=InnoDB DEFAULT CHARSET=utf8COMMENT='用户表信息';\n```\n\n数据类型的属性解释\n\n*   `NULL`：数据列可包含NULL值；\n\n*   `NOT NULL`：数据列不允许包含NULL值；\n\n*   `DEFAULT`：默认值；\n\n*   `PRIMARY`：KEY 主键；\n\n*   `AUTO_INCREMENT`：自动递增，适用于整数类型；\n\n*   `UNSIGNED`：是指数值类型只能为正数；\n\n*   `CHARACTER SET name`：指定一个字符集；\n\n*   `COMMENT`：对表或者字段说明；\n\n## 增删改查\n\n### SELECT\n\n> SELECT 语句用于从表中选取数据。\n> 语法：`SELECT 列名称 FROM 表名称`\n> 语法：`SELECT * FROM 表名称`\n\n```\n-- 表station取个别名叫s，表station中不包含 字段id=13或者14 的，并且id不等于4的 查询出来，只显示idSELECT s.id from station s WHERE id in (13,14) and user_id not in (4); -- 从表 Persons 选取 LastName 列的数据SELECT LastName FROM Persons -- 结果集中会自动去重复数据SELECT DISTINCT Company FROM Orders -- 表 Persons 字段 Id_P 等于 Orders 字段 Id_P 的值，-- 结果集显示 Persons表的 LastName、FirstName字段，Orders表的OrderNo字段SELECT p.LastName, p.FirstName, o.OrderNo FROM Persons p, Orders o WHERE p.Id_P = o.Id_P  -- gbk 和 utf8 中英文混合排序最简单的办法 -- ci是 case insensitive, 即 “大小写不敏感”SELECT tag, COUNT(tag) from news GROUP BY tag order by convert(tag using gbk) collate gbk_chinese_ci;SELECT tag, COUNT(tag) from news GROUP BY tag order by convert(tag using utf8) collate utf8_unicode_ci;\n```\n\n### UPDATE\n\n> Update 语句用于修改表中的数据。\n> 语法：`UPDATE 表名称 SET 列名称 = 新值 WHERE 列名称 = 某值`\n\n```\n-- update语句设置字段值为另一个结果取出来的字段update user set name = (select name from user1 where user1 .id = 1 )where id = (select id from user2 where user2 .name='小苏');-- 更新表 orders 中 id=1 的那一行数据更新它的 title 字段UPDATE `orders` set title='这里是标题' WHERE id=1;\n```\n\n### INSERT\n\n> INSERT INTO 语句用于向表格中插入新的行。\n> 语法：`INSERT INTO 表名称 VALUES (值1, 值2,....)`\n> 语法：`INSERT INTO 表名称 (列1, 列2,...) VALUES (值1, 值2,....)`\n\n```\n-- 向表 Persons 插入一条字段 LastName = JSLite 字段 Address = shanghaiINSERT INTO Persons (LastName, Address) VALUES ('JSLite', 'shanghai');-- 向表 meeting 插入 字段 a=1 和字段 b=2INSERT INTO meeting SET a=1,b=2;-- -- SQL实现将一个表的数据插入到另外一个表的代码-- 如果只希望导入指定字段，可以用这种方法：-- INSERT INTO 目标表 (字段1, 字段2, ...) SELECT 字段1, 字段2, ... FROM 来源表;INSERT INTO orders (user_account_id, title) SELECT m.user_id, m.title FROM meeting m where m.id=1;\n```\n\n### DELETE\n\n> DELETE 语句用于删除表中的行。\n> 语法：`DELETE FROM 表名称 WHERE 列名称 = 值`\n\n```\n-- 在不删除table_name表的情况下删除所有的行，清空表。DELETE FROM table_name-- 或者DELETE * FROM table_name-- 删除 Person表字段 LastName = 'JSLite' DELETE FROM Person WHERE LastName = 'JSLite' -- 删除 表meeting id 为2和3的两条数据DELETE from meeting where id in (2,3);\n```\n\n## WHERE\n\n> WHERE 子句用于规定选择的标准。\n> 语法：`SELECT 列名称 FROM 表名称 WHERE 列 运算符 值`\n\n```\n-- 从表 Persons 中选出 Year 字段大于 1965 的数据SELECT * FROM Persons WHERE Year>1965\n```\n\n## AND 和 OR\n\n> AND - 如果第一个条件和第二个条件都成立；\n> OR - 如果第一个条件和第二个条件中只要有一个成立；\n\n### AND\n\n```\n-- 删除 meeting 表字段 -- id=2 并且 user_id=5 的数据  和-- id=3 并且 user_id=6 的数据 DELETE from meeting where id in (2,3) and user_id in (5,6); -- 使用 AND 来显示所有姓为 \"Carter\" 并且名为 \"Thomas\" 的人：SELECT * FROM Persons WHERE FirstName='Thomas' AND LastName='Carter';\n```\n\n### OR\n\n```\n-- 使用 OR 来显示所有姓为 \"Carter\" 或者名为 \"Thomas\" 的人：SELECT * FROM Persons WHERE firstname='Thomas' OR lastname='Carter'\n```\n\n## ORDER BY\n\n> 语句默认按照升序对记录进行排序。\n> `ORDER BY`- 语句用于根据指定的列对结果集进行排序。\n> `DESC`- 按照降序对记录进行排序。\n> `ASC`- 按照顺序对记录进行排序。\n\n```\n-- Company在表Orders中为字母，则会以字母顺序显示公司名称SELECT Company, OrderNumber FROM Orders ORDER BY Company -- 后面跟上 DESC 则为降序显示SELECT Company, OrderNumber FROM Orders ORDER BY Company DESC -- Company以降序显示公司名称，并OrderNumber以顺序显示SELECT Company, OrderNumber FROM Orders ORDER BY Company DESC, OrderNumber ASC\n```\n\n## IN\n\n> IN - 操作符允许我们在 WHERE 子句中规定多个值。\n> IN - 操作符用来指定范围，范围中的每一条，都进行匹配。IN取值规律，由逗号分割，全部放置括号中。\n> 语法：`SELECT \"字段名\"FROM \"表格名\"WHERE \"字段名\" IN ('值一', '值二', ...);`\n\n```\n-- 从表 Persons 选取 字段 LastName 等于 Adams、CarterSELECT * FROM Persons WHERE LastName IN ('Adams','Carter')\n```\n\n## NOT\n\n> NOT - 操作符总是与其他操作符一起使用，用在要过滤的前面。\n\n```\nSELECTvend_id, prod_nameFROMProductsWHERENOTvend_id ='DLL01'ORDERBYprod_name;\n```\n\n## UNION\n\n> UNION - 操作符用于合并两个或多个 SELECT 语句的结果集。\n\n```\n-- 列出所有在中国表（Employees_China）和美国（Employees_USA）的不同的雇员名SELECT E_Name FROM Employees_China UNION SELECT E_Name FROM Employees_USA -- 列出 meeting 表中的 pic_url，-- station 表中的 number_station 别名设置成 pic_url 避免字段不一样报错-- 按更新时间排序SELECT id,pic_url FROM meeting UNION ALL SELECT id,number_station AS pic_url FROM station  ORDER BY update_at;\n```\n\n## AS\n\n> as - 可理解为：用作、当成，作为；别名\n> 一般是重命名列名或者表名。\n> 语法：`select column_1 as 列1,column_2 as 列2 from table as 表`\n\n```\nSELECT * FROM Employee AS emp-- 这句意思是查找所有Employee 表里面的数据，并把Employee表格命名为 emp。-- 当你命名一个表之后，你可以在下面用 emp 代替 Employee.-- 例如 SELECT * FROM emp. SELECT MAX(OrderPrice) AS LargestOrderPrice FROM Orders-- 列出表 Orders 字段 OrderPrice 列最大值，-- 结果集列不显示 OrderPrice 显示 LargestOrderPrice -- 显示表 users_profile 中的 name 列SELECT t.name from (SELECT * from users_profile a) AS t; -- 表 user_accounts 命名别名 ua，表 users_profile 命名别名 up-- 满足条件 表 user_accounts 字段 id 等于 表 users_profile 字段 user_id-- 结果集只显示mobile、name两列SELECT ua.mobile,up.name FROM user_accounts as ua INNER JOIN users_profile as up ON ua.id = up.user_id;\n```\n\n## JOIN\n\n> 用于根据两个或多个表中的列之间的关系，从这些表中查询数据。\n\n*   `JOIN`: 如果表中有至少一个匹配，则返回行\n\n*   `INNER JOIN`:在表中存在至少一个匹配时，INNER JOIN 关键字返回行。\n\n*   `LEFT JOIN`: 即使右表中没有匹配，也从左表返回所有的行\n\n*   `RIGHT JOIN`: 即使左表中没有匹配，也从右表返回所有的行\n\n*   `FULL JOIN`: 只要其中一个表中存在匹配，就返回行\n\n```\nSELECT Persons.LastName, Persons.FirstName, Orders.OrderNoFROM PersonsINNER JOIN OrdersON Persons.Id_P = Orders.Id_PORDER BY Persons.LastName;\n```\n\n## SQL 函数\n\n### COUNT\n\n> COUNT 让我们能够数出在表格中有多少笔资料被选出来。\n> 语法：`SELECT COUNT(\"字段名\") FROM \"表格名\";`\n\n```\n-- 表 Store_Information 有几笔 store_name 栏不是空白的资料。-- \"IS NOT NULL\" 是 \"这个栏位不是空白\" 的意思。SELECT COUNT (Store_Name) FROM Store_Information WHERE Store_Name IS NOT NULL; -- 获取 Persons 表的总数SELECT COUNT(1) AS totals FROM Persons;-- 获取表 station 字段 user_id 相同的总数select user_id, count(*) as totals from station group by user_id;\n```\n\n### MAX\n\n> MAX 函数返回一列中的最大值。NULL 值不包括在计算中。\n> 语法：`SELECT MAX(\"字段名\") FROM \"表格名\"`\n\n```\n-- 列出表 Orders 字段 OrderPrice 列最大值，-- 结果集列不显示 OrderPrice 显示 LargestOrderPriceSELECT MAX(OrderPrice) AS LargestOrderPrice FROM Orders\n```\n\n## 触发器\n\n> 语法：\n> create trigger <触发器名称>\n> { before | after} # 之前或者之后出发\n> insert | update | delete # 指明了激活触发程序的语句的类型\n> on <表名> # 操作哪张表\n> for each row # 触发器的执行间隔，for each row 通知触发器每隔一行执行一次动作，而不是对整个表执行一次。\n> <触发器SQL语句>\n\n```\nDELIMITER $ -- 自定义结束符号CREATE TRIGGER set_userdate BEFORE INSERT on `message`for EACH ROWBEGIN  UPDATE `user_accounts` SET status=1 WHERE openid=NEW.openid;END$DELIMITER ; -- 恢复结束符号\n```\n\nOLD和NEW不区分大小写\n\n*   NEW 用NEW.col_name，没有旧行。在DELETE触发程序中，仅能使用OLD.col_name，没有新行。\n\n*   OLD 用OLD.col_name来引用更新前的某一行的列\n\n## 添加索引\n\n### 普通索引(INDEX)\n\n> 语法：ALTER TABLE`表名字`ADD INDEX 索引名字 (`字段名字`)\n\n```\n-- –直接创建索引CREATE INDEX index_user ON user(title)-- –修改表结构的方式添加索引ALTER TABLE table_name ADD INDEX index_name ON (column(length))-- 给 user 表中的 name字段 添加普通索引(INDEX)ALTER TABLE `table` ADD INDEX index_name (name)-- –创建表的时候同时创建索引CREATE TABLE `table` (    `id` int(11) NOT NULL AUTO_INCREMENT ,    `title` char(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL ,    `content` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL ,    `time` int(10) NULL DEFAULT NULL ,    PRIMARY KEY (`id`),    INDEX index_name (title(length)))-- –删除索引DROP INDEX index_name ON table\n```\n\n### 主键索引(PRIMARY key)\n\n> 语法：ALTER TABLE`表名字`ADD PRIMARY KEY (`字段名字`)\n\n```\n-- 给 user 表中的 id字段 添加主键索引(PRIMARY key)ALTER TABLE `user` ADD PRIMARY key (id);\n```\n\n### 唯一索引(UNIQUE)\n\n> 语法：ALTER TABLE`表名字`ADD UNIQUE (`字段名字`)\n\n```\n-- 给 user 表中的 creattime 字段添加唯一索引(UNIQUE)ALTER TABLE `user` ADD UNIQUE (creattime);\n```\n\n### 全文索引(FULLTEXT)\n\n> 语法：ALTER TABLE`表名字`ADD FULLTEXT (`字段名字`)\n\n```\n-- 给 user 表中的 description 字段添加全文索引(FULLTEXT)ALTER TABLE `user` ADD FULLTEXT (description);\n```\n\n### 添加多列索引\n\n> 语法：\n\nALTER TABLE`table_name`ADD INDEX index_name (`column1`,`column2`,`column3`)\n\n```\n-- 给 user 表中的 name、city、age 字段添加名字为name_city_age的普通索引(INDEX)ALTER TABLE user ADD INDEX name_city_age (name(10),city,age); \n```\n\n### 建立索引的时机\n\n在`WHERE`和`JOIN`中出现的列需要建立索引，但也不完全如此：\n\n*   MySQL只对`<`，`<=`，`=`，`>`，`>=`，`BETWEEN`，`IN`使用索引\n\n*   某些时候的`LIKE`也会使用索引。\n\n*   在`LIKE`以通配符%和_开头作查询时，MySQL不会使用索引。\n\n```\n-- 此时就需要对city和age建立索引，-- 由于mytable表的userame也出现在了JOIN子句中，也有对它建立索引的必要。SELECT t.Name  FROM mytable t LEFT JOIN mytable m ON t.Name=m.username WHERE m.age=20 AND m.city='上海'; SELECT * FROM mytable WHERE username like'admin%'; -- 而下句就不会使用：SELECT * FROM mytable WHEREt Name like'%admin'; -- 因此，在使用LIKE时应注意以上的区别。\n```\n\n索引的注意事项\n\n*   索引不会包含有NULL值的列\n\n*   使用短索引\n\n*   不要在列上进行运算 索引会失效\n\n## 创建后表的修改\n\n### 添加列\n\n> 语法：`alter table 表名 add 列名 列数据类型 [after 插入位置];`\n\n示例:\n\n```\n-- 在表students的最后追加列 address: alter table students add address char(60);-- 在名为 age 的列后插入列 birthday: alter table students add birthday date after age;-- 在名为 number_people 的列后插入列 weeks: alter table students add column `weeks` varchar(5) not null default \"\" after `number_people`;\n```\n\n### 修改列\n\n> 语法：`alter table 表名 change 列名称 列新名称 新数据类型;`\n\n```\n-- 将表 tel 列改名为 telphone: alter table students change tel telphone char(13) default \"-\";-- 将 name 列的数据类型改为 char(16): alter table students change name name char(16) not null;-- 修改 COMMENT 前面必须得有类型属性alter table students change name name char(16) COMMENT '这里是名字';-- 修改列属性的时候 建议使用modify,不需要重建表-- change用于修改列名字，这个需要重建表alter table meeting modify `weeks` varchar(20) NOT NULL DEFAULT \"\" COMMENT \"开放日期 周一到周日：0~6，间隔用英文逗号隔开\";\n```\n\n### 删除列\n\n> 语法：`alter table 表名 drop 列名称;`\n\n```\n-- 删除表students中的 birthday 列: alter table students drop birthday;\n```\n\n### 重命名表\n\n> 语法：`alter table 表名 rename 新表名;`\n\n```\n-- 重命名 students 表为 workmates: alter table students rename workmates;\n```\n\n### 清空表数据\n\n> 方法一：`delete from 表名;`\n> 方法二：`truncate from \"表名\";`\n\n*   `DELETE:`1\\. DML语言;2\\. 可以回退;3\\. 可以有条件的删除;\n\n*   `TRUNCATE:`1\\. DDL语言;2\\. 无法回退;3\\. 默认所有的表内容都删除;4\\. 删除速度比delete快。\n\n```\n-- 清空表为 workmates 里面的数据，不删除表。 delete from workmates;-- 删除workmates表中的所有数据，且无法恢复truncate from workmates;\n```\n\n### 删除整张表\n\n> 语法：`drop table 表名;`\n\n```\n-- 删除 workmates 表: drop table workmates;\n```\n\n### 删除整个数据库\n\n> 语法：`drop database 数据库名;`\n\n```\n-- 删除 samp_db 数据库: drop database samp_db;\n```\n"
  },
  {
    "path": "docs/database/重新学习MySQL数据库：根据MySQL索引原理进行分析与优化.md",
    "content": "# 目录\n* [一：Mysql原理与慢查询](#一：mysql原理与慢查询)\n* [一个慢查询引发的思考](#一个慢查询引发的思考)\n* [二：索引建立](#二：索引建立)\n* [三：浅析explain用法](#三：浅析explain用法)\n    * [有什么用？](#有什么用？)\n    * [怎么使用？](#怎么使用？)\n* [参数介绍](#参数介绍)\n    * [id](#id)\n    * [select_type](#select_type)\n    * [extra](#extra)\n    * [possible_keys](#possible_keys)\n    * [key](#key)\n    * [key_len](#key_len)\n    * [ref](#ref)\n    * [rows](#rows)\n* [四：慢查询优化](#四：慢查询优化)\n    * [建索引的几大原则](#建索引的几大原则)\n    * [回到开始的慢查询](#回到开始的慢查询)\n    * [查询优化神器 - explain命令](#查询优化神器---explain命令)\n    * [慢查询优化基本步骤](#慢查询优化基本步骤)\n* [五：最左前缀原理与相关优化](#五：最左前缀原理与相关优化)\n    * [情况一：全列匹配。](#情况一：全列匹配。)\n    * [情况二：最左前缀匹配。](#情况二：最左前缀匹配。)\n    * [情况三：查询条件用到了索引中列的精确匹配，但是中间某个条件未提供。](#情况三：查询条件用到了索引中列的精确匹配，但是中间某个条件未提供。)\n    * [情况四：查询条件没有指定索引第一列。](#情况四：查询条件没有指定索引第一列。)\n    * [情况五：匹配某列的前缀字符串。](#情况五：匹配某列的前缀字符串。)\n    * [情况六：范围查询。](#情况六：范围查询。)\n    * [情况七：查询条件中含有函数或表达式。](#情况七：查询条件中含有函数或表达式。)\n* [索引选择性与前缀索引](#索引选择性与前缀索引)\n* [六：InnoDB的主键选择与插入优化](#六：innodb的主键选择与插入优化)\n\n\n本文转自互联网\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本也将整理到我的个人博客：\n\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n本文是《重新学习MySQL数据库》系列其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，从sql基本的使用方法，从MySQL执行引擎再到索引、事务等知识，一步步地学习MySQL相关技术的实现原理，更好地了解如何基于这些知识来优化sql，减少SQL执行时间，通过执行计划对SQL性能进行分析，再到MySQL的主从复制、主备部署等内容，以便让你更完整地了解整个MySQL方面的技术体系，形成自己的知识框架。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n<!-- more -->\n\n## 一：Mysql原理与慢查询\n\nMySQL凭借着出色的性能、低廉的成本、丰富的资源，已经成为绝大多数互联网公司的首选关系型数据库。虽然性能出色，但所谓“好马配好鞍”，如何能够更好的使用它，已经成为开发工程师的必修课，我们经常会从职位描述上看到诸如“精通MySQL”、“SQL语句优化”、“了解数据库原理”等要求。我们知道一般的应用系统，读写比例在10:1左右，而且插入操作和一般的更新操作很少出现性能问题，遇到最多的，也是最容易出问题的，还是一些复杂的查询操作，所以查询语句的优化显然是重中之重。\n\n本人从13年7月份起，一直在美团核心业务系统部做慢查询的优化工作，共计十余个系统，累计解决和积累了上百个慢查询案例。随着业务的复杂性提升，遇到的问题千奇百怪，五花八门，匪夷所思。本文旨在以开发工程师的角度来解释数据库索引的原理和如何优化慢查询。\n\n## 一个慢查询引发的思考\n\n```\nselect   count(*) from   task where   status=2    and operator_id=20839    and operate_time>1371169729    and operate_time<1371174603    and type=2;\n```\n\n系统使用者反应有一个功能越来越慢，于是工程师找到了上面的SQL。\n并且兴致冲冲的找到了我，“这个SQL需要优化，给我把每个字段都加上索引”\n我很惊讶，问道“为什么需要每个字段都加上索引？”\n“把查询的字段都加上索引会更快”工程师信心满满\n“这种情况完全可以建一个联合索引，因为是最左前缀匹配，所以operate_time需要放到最后，而且还需要把其他相关的查询都拿来，需要做一个综合评估。”\n“联合索引？最左前缀匹配？综合评估？”工程师不禁陷入了沉思。\n多数情况下，我们知道索引能够提高查询效率，但应该如何建立索引？索引的顺序如何？许多人却只知道大概。其实理解这些概念并不难，而且索引的原理远没有想象的那么复杂。\n\n\n\n## 二：索引建立\n\n1. 主键索引\n\nprimary key() 要求关键字不能重复，也不能为null,同时增加主键约束\n主键索引定义时，不能命名\n\n2. 唯一索引\n\nunique index() 要求关键字不能重复，同时增加唯一约束\n\n3. 普通索引\n\nindex() 对关键字没有要求\n\n4. 全文索引\n\nfulltext key() 关键字的来源不是所有字段的数据，而是字段中提取的特别关键字\n\n**关键字：可以是某个字段或多个字段，多个字段称为复合索引**\n\n```\n建表：creat table student(    stu_id int unsigned not null auto_increment,    name varchar(32) not null default '',    phone char(11) not null default '',    stu_code varchar(32) not null default '',    stu_desc text,    primary key ('stu_id'),     //主键索引    unique index 'stu_code' ('stu_code'), //唯一索引    index 'name_phone' ('name','phone'),  //普通索引，复合索引    fulltext index 'stu_desc' ('stu_desc'), //全文索引) engine=myisam charset=utf8; 更新：alert table student    add primary key ('stu_id'),     //主键索引    add unique index 'stu_code' ('stu_code'), //唯一索引    add index 'name_phone' ('name','phone'),  //普通索引，复合索引    add fulltext index 'stu_desc' ('stu_desc'); //全文索引 删除：alert table sutdent    drop primary key,    drop index 'stu_code',    drop index 'name_phone',    drop index 'stu_desc';\n```\n\n## 三：浅析explain用法\n\n### 有什么用？\n\n在MySQL中，当数据量增长的特别大的时候就需要用到索引来优化SQL语句，而如何才能判断我们辛辛苦苦写出的SQL语句是否优良？这时候**explain**就派上了用场。\n\n### 怎么使用？\n\n```\nexplain + SQL语句即可 如：explain select * from table;\n\n```\n\n如下\n\n![explain参数](https://user-gold-cdn.xitu.io/2018/5/17/1636ce849c800023?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)\n\n相信第一次使用explain参数的朋友一定会疑惑这一大堆参数究竟有什么用呢？笔者搜集了一些资料，在这儿做一个总结希望能够帮助大家理解。\n\n* * *\n\n## 参数介绍\n\n### id\n\n```\n如果是子查询，id的序号会递增，id的值越大优先级越高，越先被执行\n\n```\n\n### select_type\n\n查询的类型，主要用于区别普通查询、联合查询、子查询等的复杂查询 SIMPLE:简单的select查询，查询中不包含子查询或者UNION PRIMARY:查询中若包含任何复杂的子部分，最外层查询则被标记为PRIMARY（最后加载的那一个 ） SUBQUERY:在SELECT或WHERE列表中包含了子查询 DERIVED:在FROM列表中包含的子查询被标记为DERIVED（衍生）Mysql会递归执行这些子查询，把结果放在临时表里。 UNION:若第二个SELECT出现在UNION之后，则被标记为UNION；若UNION包含在FROM字句的查询中，外层SELECT将被标记为:DERIVED UNION RESULT:从UNION表获取结果的SELECT type\n\n显示查询使用了何种类型\n\n从最好到最差依次是System>const>eq_ref>range>index>All（**全表扫描**）\t一般来说**至少达到range级别，最好达到ref**\n\nSystem:表只有一行记录，这是const类型的特例，平时不会出现(忽略不计)const:表示通过索引一次就找到了,const用于比较primary key或者unique索引，因为只匹配一行数据，所以很快。如将主键置于where列表中，MySQL就能将该查询转换为一个常量。\n\neq_ref:唯一性索引扫描，对于每个索引键，表中只有一条记录与之匹配。常见于主键或唯一索引扫描。\n\nref：非唯一索引扫描，返回匹配某个单独值的行，本质上也是一种索引访问，它返回所有匹配某个单独值的行，然而它可能会找到多个符合条件的行，所以它应该属于查找和扫描的混合体range：只检索给定范围的行，使用一个索引来选择行。\n\nkey列显示使用了哪个索引，一般就是在你的where语句中出现了between、<、>、in等的查询。这种范围扫描索引比全表扫描要好，因为它只需要开始于索引的某一点，而结束于另一点，不用扫描全部索引。index:FULL INDEX SCAN,index与all区别为index类型只遍历索引树。这通常比all快，因为索引文件通常比数据文件小。\n\n### extra\n\n包含不适合在其他列中显示但十分重要的额外信息 包含的信息： **（危险!）**Using\n\nfilesort:说明mysql会对数据使用一个外部的索引排序，而不是按照表内的索引顺序进行读取，MYSQL中无法利用索引完成的排序操作称为“文件排序” **（特别危险!）**Using\n\ntemporary:使用了临时表保存中间结果，MYSQL在对查询结果排序时使用临时表。常见于排序order by 和分组查询 group by Using\n\nindex:表示相应的select操作中使用了覆盖索引，避免访问了表的数据行，效率不错。如果同时出现using\n\nwhere，表明索引被用来执行索引键值的查找；如果没有同时出现using where，表明索引用来读取数据而非执行查找操作。\n\n### possible_keys\n\n显示可能应用在这张表中的索引，一个或多个。查询涉及到的字段上若存在索引，则该索引将被列出， 但不一定被查询实际使用\n\n### key\n\n实际使用的索引，如果为NULL，则没有使用索引。查询中若使用了覆盖索引，则该索引仅出现在key列表中，key参数可以作为使用了索引的判断标准\n\n### key_len\n\n:表示索引中使用的字节数，可通过该列计算查询中索引的长度，在不损失精确性的情况下，长度越短越好，key_len显示的值为索引字段的最大可能长度，并非实际使用长度，即key_len是根据表定义计算而得，不是通过表内检索出的。\n\n### ref\n\n显示索引的哪一列被使用了，如果可能的话，是一个常数。哪些列或常量被用于查找索引上的值。\n\n### rows\n\n根据表统计信息及索引选用情况，大致估算出找到所需记录所需要读取的行数\n\n## 四：慢查询优化\n\n关于MySQL索引原理是比较枯燥的东西，大家只需要有一个感性的认识，并不需要理解得非常透彻和深入。我们回头来看看一开始我们说的慢查询，了解完索引原理之后，大家是不是有什么想法呢？先总结一下索引的几大基本原则\n\n### 建索引的几大原则\n\n1.最左前缀匹配原则，非常重要的原则，mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配，比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引，d是用不到索引的，如果建立(a,b,d,c)的索引则都可以用到，a,b,d的顺序可以任意调整。\n\n2.=和in可以乱序，比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序，mysql的查询优化器会帮你优化成索引可以识别的形式\n\n3.尽量选择区分度高的列作为索引,区分度的公式是count(distinct col)/count(*)，表示字段不重复的比例，比例越大我们扫描的记录数越少，唯一键的区分度是1，而一些状态、性别字段可能在大数据面前区分度就是0，那可能有人会问，这个比例有什么经验值吗？使用场景不同，这个值也很难确定，一般需要join的字段我们都要求是0.1以上，即平均1条扫描10条记录\n\n4.索引列不能参与计算，保持列“干净”，比如from_unixtime(create_time) = ’2014-05-29’就不能使用到索引，原因很简单，b+树中存的都是数据表中的字段值，但进行检索时，需要把所有元素都应用函数才能比较，显然成本太大。所以语句应该写成create_time = unix_timestamp(’2014-05-29’);\n5.尽量的扩展索引，不要新建索引。比如表中已经有a的索引，现在要加(a,b)的索引，那么只需要修改原来的索引即可\n\n### 回到开始的慢查询\n\n根据最左匹配原则，最开始的sql语句的索引应该是status、operator_id、type、operate_time的联合索引；其中status、operator_id、type的顺序可以颠倒，所以我才会说，把这个表的所有相关查询都找到，会综合分析；\n\n比如还有如下查询\n\n```\nselect * from task where status = 0 and type = 12 limit 10;\n\n```\n\n```\nselect count(*) from task where status = 0 ;\n\n```\n\n那么索引建立成(status,type,operator_id,operate_time)就是非常正确的，因为可以覆盖到所有情况。这个就是利用了索引的最左匹配的原则\n\n### 查询优化神器 - explain命令\n\n关于explain命令相信大家并不陌生，具体用法和字段含义可以参考官网[explain-output](http://dev.mysql.com/doc/refman/5.5/en/explain-output.html)，这里需要强调rows是核心指标，绝大部分rows小的语句执行一定很快（有例外，下面会讲到）。所以优化语句基本上都是在优化rows。\n\n### 慢查询优化基本步骤\n\n0.先运行看看是否真的很慢，注意设置SQL_NO_CACHE\n1.where条件单表查，锁定最小返回记录表。这句话的意思是把查询语句的where都应用到表中返回的记录数最小的表开始查起，单表每个字段分别查询，看哪个字段的区分度最高\n2.explain查看执行计划，是否与1预期一致（从锁定记录较少的表开始查询）\n3.order by limit 形式的sql语句让排序的表优先查\n4.了解业务方使用场景\n5.加索引时参照建索引的几大原则\n\n6.观察结果，不符合预期继续从0分析\n\n## 五：最左前缀原理与相关优化\n\n高效使用索引的首要条件是知道什么样的查询会使用到索引，这个问题和B+Tree中的“最左前缀原理”有关，下面通过例子说明最左前缀原理。\n\n这里先说一下联合索引的概念。在上文中，我们都是假设索引只引用了单个的列，实际上，MySQL中的索引可以以一定顺序引用多个列，这种索引叫做联合索引，一般的，一个联合索引是一个有序元组，其中各个元素均为数据表的一列，实际上要严格定义索引需要用到关系代数，但是这里我不想讨论太多关系代数的话题，因为那样会显得很枯燥，所以这里就不再做严格定义。另外，单列索引可以看成联合索引元素数为1的特例。\n\n以employees.titles表为例，下面先查看其上都有哪些索引：\n````\n1.  SHOW INDEX FROM employees.titles;\n2.  +--------+------------+----------+--------------+-------------+-----------+-------------+------+------------+\n3.  |Table|Non_unique|Key_name|Seq_in_index|Column_name|Collation|Cardinality|Null|Index_type|\n4.  +--------+------------+----------+--------------+-------------+-----------+-------------+------+------------+\n5.  |titles|0|PRIMARY|1|emp_no|A|NULL||BTREE|\n6.  |titles|0|PRIMARY|2|title|A|NULL||BTREE|\n7.  |titles|0|PRIMARY|3|from_date|A|443308||BTREE|\n8.  |titles|1|emp_no|1|emp_no|A|443308||BTREE|\n9.  +--------+------------+----------+--------------+-------------+-----------+-------------+------+------------+\n````\n从结果中可以到titles表的主索引为<emp_no, title, from_date>，还有一个辅助索引<emp_no>。为了避免多个索引使事情变复杂（MySQL的SQL优化器在多索引时行为比较复杂），这里我们将辅助索引drop掉：\n\n1.  ALTER TABLE employees.titles DROP INDEX emp_no;\n\n这样就可以专心分析索引PRIMARY的行为了。\n\n### 情况一：全列匹配。\n````\n1.  EXPLAIN SELECT*FROM employees.titles WHERE emp_no='10001'AND title='Senior Engineer'AND from_date='1986-06-26';\n2.  +----+-------------+--------+-------+---------------+---------+---------+-------------------+------+-------+\n3.  |id|select_type|table|type|possible_keys|key|key_len|ref|rows|Extra|\n4.  +----+-------------+--------+-------+---------------+---------+---------+-------------------+------+-------+\n5.  |1|SIMPLE|titles|const|PRIMARY|PRIMARY|59|const,const,const|1||\n6.  +----+-------------+--------+-------+---------------+---------+---------+-------------------+------+-------+\n````\n很明显，当按照索引中所有列进行精确匹配（这里精确匹配指“=”或“IN”匹配）时，索引可以被用到。这里有一点需要注意，理论上索引对顺序是敏感的，但是由于MySQL的查询优化器会自动调整where子句的条件顺序以使用适合的索引，例如我们将where中的条件顺序颠倒：\n````\n1.  EXPLAIN SELECT*FROM employees.titles WHERE from_date='1986-06-26'AND emp_no='10001'AND title='Senior Engineer';\n2.  +----+-------------+--------+-------+---------------+---------+---------+-------------------+------+-------+\n3.  |id|select_type|table|type|possible_keys|key|key_len|ref|rows|Extra|\n4.  +----+-------------+--------+-------+---------------+---------+---------+-------------------+------+-------+\n5.  |1|SIMPLE|titles|const|PRIMARY|PRIMARY|59|const,const,const|1||\n6.  +----+-------------+--------+-------+---------------+---------+---------+-------------------+------+-------+\n````\n效果是一样的。\n\n### 情况二：最左前缀匹配。\n````\n1.  EXPLAIN SELECT*FROM employees.titles WHERE emp_no='10001';\n2.  +----+-------------+--------+------+---------------+---------+---------+-------+------+-------+\n3.  |id|select_type|table|type|possible_keys|key|key_len|ref|rows|Extra|\n4.  +----+-------------+--------+------+---------------+---------+---------+-------+------+-------+\n5.  |1|SIMPLE|titles|ref|PRIMARY|PRIMARY|4|const|1||\n6.  +----+-------------+--------+------+---------------+---------+---------+-------+------+-------+\n````\n当查询条件精确匹配索引的左边连续一个或几个列时，如<emp_no>或<emp_no, title>，所以可以被用到，但是只能用到一部分，即条件所组成的最左前缀。上面的查询从分析结果看用到了PRIMARY索引，但是key_len为4，说明只用到了索引的第一列前缀。\n\n### 情况三：查询条件用到了索引中列的精确匹配，但是中间某个条件未提供。\n````\n1.  EXPLAIN SELECT*FROM employees.titles WHERE emp_no='10001'AND from_date='1986-06-26';\n2.  +----+-------------+--------+------+---------------+---------+---------+-------+------+-------------+\n3.  |id|select_type|table|type|possible_keys|key|key_len|ref|rows|Extra|\n4.  +----+-------------+--------+------+---------------+---------+---------+-------+------+-------------+\n5.  |1|SIMPLE|titles|ref|PRIMARY|PRIMARY|4|const|1|Usingwhere|\n6.  +----+-------------+--------+------+---------------+---------+---------+-------+------+-------------+\n````\n此时索引使用情况和情况二相同，因为title未提供，所以查询只用到了索引的第一列，而后面的from_date虽然也在索引中，但是由于title不存在而无法和左前缀连接，因此需要对结果进行扫描过滤from_date（这里由于emp_no唯一，所以不存在扫描）。\n\n如果想让from_date也使用索引而不是where过滤，可以增加一个辅助索引<emp_no, from_date>，此时上面的查询会使用这个索引。除此之外，还可以使用一种称之为“隔离列”的优化方法，将emp_no与from_date之间的“坑”填上。\n\n首先我们看下title一共有几种不同的值：\n````\n1.  SELECT DISTINCT(title)FROM employees.titles;\n2.  +--------------------+\n3.  |title|\n4.  +--------------------+\n5.  |SeniorEngineer|\n6.  |Staff|\n7.  |Engineer|\n8.  |SeniorStaff|\n9.  |AssistantEngineer|\n10.  |TechniqueLeader|\n11.  |Manager|\n12.  +--------------------+\n````\n只有7种。在这种成为“坑”的列值比较少的情况下，可以考虑用“IN”来填补这个“坑”从而形成最左前缀：\n````\n1.  EXPLAIN SELECT*FROM employees.titles\n2.  WHERE emp_no='10001'\n3.  AND title IN('Senior Engineer','Staff','Engineer','Senior Staff','Assistant Engineer','Technique Leader','Manager')\n4.  AND from_date='1986-06-26';\n5.  +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+\n6.  |id|select_type|table|type|possible_keys|key|key_len|ref|rows|Extra|\n7.  +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+\n8.  |1|SIMPLE|titles|range|PRIMARY|PRIMARY|59|NULL|7|Usingwhere|\n9.  +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+\n````\n这次key_len为59，说明索引被用全了，但是从type和rows看出IN实际上执行了一个range查询，这里检查了7个key。看下两种查询的性能比较：\n````\n1.  SHOW PROFILES;\n2.  +----------+------------+-------------------------------------------------------------------------------+\n3.  |Query_ID|Duration|Query|\n4.  +----------+------------+-------------------------------------------------------------------------------+\n5.  |10|0.00058000|SELECT*FROM employees.titles WHERE emp_no='10001'AND from_date='1986-06-26'|\n6.  |11|0.00052500|SELECT*FROM employees.titles WHERE emp_no='10001'AND title IN...|\n7.  +----------+------------+-------------------------------------------------------------------------------+\n````\n“填坑”后性能提升了一点。如果经过emp_no筛选后余下很多数据，则后者性能优势会更加明显。当然，如果title的值很多，用填坑就不合适了，必须建立辅助索引。\n\n### 情况四：查询条件没有指定索引第一列。\n````\n1.  EXPLAIN SELECT*FROM employees.titles WHERE from_date='1986-06-26';\n2.  +----+-------------+--------+------+---------------+------+---------+------+--------+-------------+\n3.  |id|select_type|table|type|possible_keys|key|key_len|ref|rows|Extra|\n4.  +----+-------------+--------+------+---------------+------+---------+------+--------+-------------+\n5.  |1|SIMPLE|titles|ALL|NULL|NULL|NULL|NULL|443308|Usingwhere|\n6.  +----+-------------+--------+------+---------------+------+---------+------+--------+-------------+\n````\n由于不是最左前缀，索引这样的查询显然用不到索引。\n\n### 情况五：匹配某列的前缀字符串。\n````\n1.  EXPLAIN SELECT*FROM employees.titles WHERE emp_no='10001'AND title LIKE'Senior%';\n2.  +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+\n3.  |id|select_type|table|type|possible_keys|key|key_len|ref|rows|Extra|\n4.  +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+\n5.  |1|SIMPLE|titles|range|PRIMARY|PRIMARY|56|NULL|1|Usingwhere|\n6.  +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+\n````\n此时可以用到索引，但是如果通配符不是只出现在末尾，则无法使用索引。（原文表述有误，如果通配符%不出现在开头，则可以用到索引，但根据具体情况不同可能只会用其中一个前缀）\n\n### 情况六：范围查询。\n````\n1.  EXPLAIN SELECT*FROM employees.titles WHERE emp_no<'10010'andtitle='Senior Engineer';\n2.  +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+\n3.  |id|select_type|table|type|possible_keys|key|key_len|ref|rows|Extra|\n4.  +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+\n5.  |1|SIMPLE|titles|range|PRIMARY|PRIMARY|4|NULL|16|Usingwhere|\n6.  +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+\n````\n范围列可以用到索引（必须是最左前缀），但是范围列后面的列无法用到索引。同时，索引最多用于一个范围列，因此如果查询条件中有两个范围列则无法全用到索引。\n````\n1.  EXPLAIN SELECT*FROM employees.titles\n2.  WHERE emp_no<'10010'\n3.  AND title='Senior Engineer'\n4.  AND from_date BETWEEN'1986-01-01'AND'1986-12-31';\n5.  +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+\n6.  |id|select_type|table|type|possible_keys|key|key_len|ref|rows|Extra|\n7.  +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+\n8.  |1|SIMPLE|titles|range|PRIMARY|PRIMARY|4|NULL|16|Usingwhere|\n9.  +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+\n````\n可以看到索引对第二个范围索引无能为力。这里特别要说明MySQL一个有意思的地方，那就是仅用explain可能无法区分范围索引和多值匹配，因为在type中这两者都显示为range。同时，用了“between”并不意味着就是范围查询，例如下面的查询：\n````\n1.  EXPLAIN SELECT*FROM employees.titles\n2.  WHERE emp_no BETWEEN'10001'AND'10010'\n3.  AND title='Senior Engineer'\n4.  AND from_date BETWEEN'1986-01-01'AND'1986-12-31';\n5.  +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+\n6.  |id|select_type|table|type|possible_keys|key|key_len|ref|rows|Extra|\n7.  +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+\n8.  |1|SIMPLE|titles|range|PRIMARY|PRIMARY|59|NULL|16|Usingwhere|\n9.  +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+\n````\n看起来是用了两个范围查询，但作用于emp_no上的“BETWEEN”实际上相当于“IN”，也就是说emp_no实际是多值精确匹配。可以看到这个查询用到了索引全部三个列。因此在MySQL中要谨慎地区分多值匹配和范围匹配，否则会对MySQL的行为产生困惑。\n\n### 情况七：查询条件中含有函数或表达式。\n\n很不幸，如果查询条件中含有函数或表达式，则MySQL不会为这列使用索引（虽然某些在数学意义上可以使用）。例如：\n````\n1.  EXPLAIN SELECT*FROM employees.titles WHERE emp_no='10001'AND left(title,6)='Senior';\n2.  +----+-------------+--------+------+---------------+---------+---------+-------+------+-------------+\n3.  |id|select_type|table|type|possible_keys|key|key_len|ref|rows|Extra|\n4.  +----+-------------+--------+------+---------------+---------+---------+-------+------+-------------+\n5.  |1|SIMPLE|titles|ref|PRIMARY|PRIMARY|4|const|1|Usingwhere|\n6.  +----+-------------+--------+------+---------------+---------+---------+-------+------+-------------+\n````\n虽然这个查询和情况五中功能相同，但是由于使用了函数left，则无法为title列应用索引，而情况五中用LIKE则可以。再如：\n````\n1.  EXPLAIN SELECT*FROM employees.titles WHERE emp_no-1='10000';\n2.  +----+-------------+--------+------+---------------+------+---------+------+--------+-------------+\n3.  |id|select_type|table|type|possible_keys|key|key_len|ref|rows|Extra|\n4.  +----+-------------+--------+------+---------------+------+---------+------+--------+-------------+\n5.  |1|SIMPLE|titles|ALL|NULL|NULL|NULL|NULL|443308|Usingwhere|\n6.  +----+-------------+--------+------+---------------+------+---------+------+--------+-------------+\n````\n显然这个查询等价于查询emp_no为10001的函数，但是由于查询条件是一个表达式，MySQL无法为其使用索引。看来MySQL还没有智能到自动优化常量表达式的程度，因此在写查询语句时尽量避免表达式出现在查询中，而是先手工私下代数运算，转换为无表达式的查询语句。\n\n## 索引选择性与前缀索引\n\n既然索引可以加快查询速度，那么是不是只要是查询语句需要，就建上索引？答案是否定的。因为索引虽然加快了查询速度，但索引也是有代价的：索引文件本身要消耗存储空间，同时索引会加重插入、删除和修改记录时的负担，另外，MySQL在运行时也要消耗资源维护索引，因此索引并不是越多越好。一般两种情况下不建议建索引。\n\n第一种情况是表记录比较少，例如一两千条甚至只有几百条记录的表，没必要建索引，让查询做全表扫描就好了。至于多少条记录才算多，这个个人有个人的看法，我个人的经验是以2000作为分界线，记录数不超过 2000可以考虑不建索引，超过2000条可以酌情考虑索引。\n\n另一种不建议建索引的情况是索引的选择性较低。所谓索引的选择性（Selectivity），是指不重复的索引值（也叫基数，Cardinality）与表记录数（#T）的比值：\n````\nIndex Selectivity = Cardinality / #T\n````\n显然选择性的取值范围为(0, 1]，选择性越高的索引价值越大，这是由B+Tree的性质决定的。例如，上文用到的employees.titles表，如果title字段经常被单独查询，是否需要建索引，我们看一下它的选择性：\n````\n1.  SELECT count(DISTINCT(title))/count(*)ASSelectivityFROM employees.titles;\n2.  +-------------+\n3.  |Selectivity|\n4.  +-------------+\n5.  |0.0000|\n6.  +-------------+\n````\ntitle的选择性不足0.0001（精确值为0.00001579），所以实在没有什么必要为其单独建索引。\n\n有一种与索引选择性有关的索引优化策略叫做前缀索引，就是用列的前缀代替整个列作为索引key，当前缀长度合适时，可以做到既使得前缀索引的选择性接近全列索引，同时因为索引key变短而减少了索引文件的大小和维护开销。下面以employees.employees表为例介绍前缀索引的选择和使用。\n\n从图12可以看到employees表只有一个索引<emp_no>，那么如果我们想按名字搜索一个人，就只能全表扫描了：\n````\n1.  EXPLAIN SELECT*FROM employees.employees WHERE first_name='Eric'AND last_name='Anido';\n2.  +----+-------------+-----------+------+---------------+------+---------+------+--------+-------------+\n3.  |id|select_type|table|type|possible_keys|key|key_len|ref|rows|Extra|\n4.  +----+-------------+-----------+------+---------------+------+---------+------+--------+-------------+\n5.  |1|SIMPLE|employees|ALL|NULL|NULL|NULL|NULL|300024|Usingwhere|\n6.  +----+-------------+-----------+------+---------------+------+---------+------+--------+-------------+\n````\n如果频繁按名字搜索员工，这样显然效率很低，因此我们可以考虑建索引。有两种选择，建<first_name>或<first_name, last_name>，看下两个索引的选择性：\n````\n1.  SELECT count(DISTINCT(first_name))/count(*)ASSelectivityFROM employees.employees;\n2.  +-------------+\n3.  |Selectivity|\n4.  +-------------+\n5.  |0.0042|\n6.  +-------------+\n7.  SELECT count(DISTINCT(concat(first_name,last_name)))/count(*)ASSelectivityFROM employees.employees;\n8.  +-------------+\n9.  |Selectivity|\n10.  +-------------+\n11.  |0.9313|\n12.  +-------------+\n````\n<first_name>显然选择性太低，<first_name, last_name>选择性很好，但是first_name和last_name加起来长度为30，有没有兼顾长度和选择性的办法？可以考虑用first_name和last_name的前几个字符建立索引，例如<first_name, left(last_name, 3)>，看看其选择性：\n````\n1.  SELECT count(DISTINCT(concat(first_name,left(last_name,3))))/count(*)ASSelectivityFROM employees.employees;\n2.  +-------------+\n3.  |Selectivity|\n4.  +-------------+\n5.  |0.7879|\n6.  +-------------+\n````\n选择性还不错，但离0.9313还是有点距离，那么把last_name前缀加到4：\n````\n1.  SELECT count(DISTINCT(concat(first_name,left(last_name,4))))/count(*)ASSelectivityFROM employees.employees;\n2.  +-------------+\n3.  |Selectivity|\n4.  +-------------+\n5.  |0.9007|\n6.  +-------------+\n````\n这时选择性已经很理想了，而这个索引的长度只有18，比<first_name, last_name>短了接近一半，我们把这个前缀索引 建上：\n````\n1.  ALTER TABLE employees.employees\n2.  ADD INDEX`first_name_last_name4`(first_name,last_name(4));\n````\n此时再执行一遍按名字查询，比较分析一下与建索引前的结果：\n````\n1.  SHOW PROFILES;\n2.  +----------+------------+---------------------------------------------------------------------------------+\n3.  |Query_ID|Duration|Query|\n4.  +----------+------------+---------------------------------------------------------------------------------+\n5.  |87|0.11941700|SELECT*FROM employees.employees WHERE first_name='Eric'AND last_name='Anido'|\n6.  |90|0.00092400|SELECT*FROM employees.employees WHERE first_name='Eric'AND last_name='Anido'|\n7.  +----------+------------+---------------------------------------------------------------------------------+\n````\n性能的提升是显著的，查询速度提高了120多倍。\n\n前缀索引兼顾索引大小和查询速度，但是其缺点是不能用于ORDER BY和GROUP BY操作，也不能用于Covering index（即当索引本身包含查询所需全部数据时，不再访问数据文件本身\n\n## 六：InnoDB的主键选择与插入优化\n\n在使用InnoDB存储引擎时，如果没有特别的需要，请永远使用一个与业务无关的自增字段作为主键。\n\n经常看到有帖子或博客讨论主键选择问题，有人建议使用业务无关的自增主键，有人觉得没有必要，完全可以使用如学号或身份证号这种唯一字段作为主键。不论支持哪种论点，大多数论据都是业务层面的。如果从数据库索引优化角度看，使用InnoDB引擎而不使用自增主键绝对是一个糟糕的主意。\n\n上文讨论过InnoDB的索引实现，InnoDB使用聚集索引，数据记录本身被存于主索引（一颗B+Tree）的叶子节点上。这就要求同一个叶子节点内（大小为一个内存页或磁盘页）的各条数据记录按主键顺序存放，因此每当有一条新的记录插入时，MySQL会根据其主键将其插入适当的节点和位置，如果页面达到装载因子（InnoDB默认为15/16），则开辟一个新的页（节点）。\n\n如果表使用自增主键，那么每次插入新的记录，记录就会顺序添加到当前索引节点的后续位置，当一页写满，就会自动开辟一个新的页。如下图所示：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/13.png)\n\n图13\n\n这样就会形成一个紧凑的索引结构，近似顺序填满。由于每次插入时也不需要移动已有数据，因此效率很高，也不会增加很多开销在维护索引上。\n\n如果使用非自增主键（如果身份证号或学号等），由于每次插入主键的值近似于随机，因此每次新纪录都要被插到现有索引页得中间某个位置：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/14.png)\n\n图14\n\n此时MySQL不得不为了将新记录插到合适位置而移动数据，甚至目标页面可能已经被回写到磁盘上而从缓存中清掉，此时又要从磁盘上读回来，这增加了很多开销，同时频繁的移动、分页操作造成了大量的碎片，得到了不够紧凑的索引结构，后续不得不通过OPTIMIZE TABLE来重建表并优化填充页面。\n\n因此，只要可以，请尽量在InnoDB上采用自增字段做主键。\n"
  },
  {
    "path": "docs/database/重新学习MySQL数据库：浅谈MySQL的中事务与锁.md",
    "content": "# 目录\n  * [『浅入深出』MySQL 中事务的实现](#『浅入深出』mysql-中事务的实现)\n    * [原子性](#原子性)\n    * [回滚日志](#回滚日志)\n    * [事务的状态](#事务的状态)\n    * [并行事务的原子性](#并行事务的原子性)\n    * [持久性](#持久性)\n    * [重做日志](#重做日志)\n    * [回滚日志和重做日志](#回滚日志和重做日志)\n    * [隔离性](#隔离性)\n    * [事务的隔离级别](#事务的隔离级别)\n    * [隔离级别的实现](#隔离级别的实现)\n    * [锁](#锁)\n    * [时间戳](#时间戳)\n    * [多版本和快照隔离](#多版本和快照隔离)\n    * [隔离性与原子性](#隔离性与原子性)\n    * [一致性](#一致性)\n    * [ACID](#acid)\n  * [总结](#总结)\n  * [浅谈数据库并发控制 - 锁和 MVCC](#浅谈数据库并发控制---锁和-mvcc)\n    * [概述](#概述)\n    * [悲观并发控制](#悲观并发控制)\n    * [读写锁](#读写锁)\n    * [两阶段锁协议](#两阶段锁协议)\n    * [死锁的处理](#死锁的处理)\n    * [预防死锁](#预防死锁)\n    * [死锁检测和恢复](#死锁检测和恢复)\n    * [锁的粒度](#锁的粒度)\n    * [乐观并发控制](#乐观并发控制)\n    * [基于时间戳的协议](#基于时间戳的协议)\n    * [基于验证的协议](#基于验证的协议)\n    * [多版本并发控制](#多版本并发控制)\n  * [MySQL 与 MVCC](#mysql-与-mvcc)\n    * [PostgreSQL 与 MVCC](#postgresql-与-mvcc)\n\n\n本文转自互联网\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本也将整理到我的个人博客：\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n本文是《重新学习MySQL数据库》系列其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，从sql基本的使用方法，从MySQL执行引擎再到索引、事务等知识，一步步地学习MySQL相关技术的实现原理，更好地了解如何基于这些知识来优化sql，减少SQL执行时间，通过执行计划对SQL性能进行分析，再到MySQL的主从复制、主备部署等内容，以便让你更完整地了解整个MySQL方面的技术体系，形成自己的知识框架。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n## 『浅入深出』MySQL 中事务的实现\n\n在关系型数据库中，事务的重要性不言而喻，只要对数据库稍有了解的人都知道事务具有 ACID 四个基本属性，而我们不知道的可能就是数据库是如何实现这四个属性的；在这篇文章中，我们将对事务的实现进行分析，尝试理解数据库是如何实现事务的，当然我们也会在文章中简单对 MySQL 中对 ACID 的实现进行简单的介绍。\n\n事务其实就是并发控制的基本单位；相信我们都知道，事务是一个序列操作，其中的操作要么都执行，要么都不执行，它是一个不可分割的工作单位；数据库事务的 ACID 四大特性是事务的基础，了解了 ACID 是如何实现的，我们也就清除了事务的实现，接下来我们将依次介绍数据库是如何实现这四个特性的。\n\n### 原子性\n\n在学习事务时，经常有人会告诉你，事务就是一系列的操作，要么全部都执行，要都不执行，这其实就是对事务原子性的刻画；虽然事务具有原子性，但是原子性并不是只与事务有关系，它的身影在很多地方都会出现。\n\n由于操作并不具有原子性，并且可以再分为多个操作，当这些操作出现错误或抛出异常时，整个操作就可能不会继续执行下去，而已经进行的操作造成的副作用就可能造成数据更新的丢失或者错误。\n\n事务其实和一个操作没有什么太大的区别，它是一系列的数据库操作（可以理解为 SQL）的集合，如果事务不具备原子性，那么就没办法保证同一个事务中的所有操作都被执行或者未被执行了，整个数据库系统就既不可用也不可信。\n\n### 回滚日志\n\n想要保证事务的原子性，就需要在异常发生时，对已经执行的操作进行回滚，而在 MySQL 中，恢复机制是通过回滚日志（undo log）实现的，所有事务进行的修改都会先记录到这个回滚日志中，然后在对数据库中的对应行进行写入。\n\n这个过程其实非常好理解，为了能够在发生错误时撤销之前的全部操作，肯定是需要将之前的操作都记录下来的，这样在发生错误时才可以回滚。\n\n回滚日志除了能够在发生错误或者用户执行ROLLBACK时提供回滚相关的信息，它还能够在整个系统发生崩溃、数据库进程直接被杀死后，当用户再次启动数据库进程时，还能够立刻通过查询回滚日志将之前未完成的事务进行回滚，这也就需要回滚日志必须先于数据持久化到磁盘上，是我们需要先写日志后写数据库的主要原因。\n\n回滚日志并不能将数据库物理地恢复到执行语句或者事务之前的样子；它是逻辑日志，当回滚日志被使用时，它只会按照日志逻辑地将数据库中的修改撤销掉看，可以理解为，我们在事务中使用的每一条INSERT都对应了一条DELETE，每一条UPDATE也都对应一条相反的UPDATE语句。\n\n在这里，我们并不会介绍回滚日志的格式以及它是如何被管理的，本文重点关注在它到底是一个什么样的东西，究竟解决了、如何解决了什么样的问题，如果想要了解具体实现细节的读者，相信网络上关于回滚日志的文章一定不少。\n\n### 事务的状态\n\n因为事务具有原子性，所以从远处看的话，事务就是密不可分的一个整体，事务的状态也只有三种：Active、Commited 和 Failed，事务要不就在执行中，要不然就是成功或者失败的状态：\n\n但是如果放大来看，我们会发现事务不再是原子的，其中包括了很多中间状态，比如部分提交，事务的状态图也变得越来越复杂。\n\n> 事务的状态图以及状态的描述取自[Database System Concepts](https://www.amazon.com/Database-System-Concepts-Computer-Science/dp/0073523321 \"按住 Ctrl 点击访问 https://www.amazon.com/Database-System-Concepts-Computer-Science/dp/0073523321\")一书中第 14 章的内容。\n\n*   Active：事务的初始状态，表示事务正在执行；\n\n*   Partially Commited：在最后一条语句执行之后；\n\n*   Failed：发现事务无法正常执行之后；\n\n*   Aborted：事务被回滚并且数据库恢复到了事务进行之前的状态之后；\n\n*   Commited：成功执行整个事务；\n\n虽然在发生错误时，整个数据库的状态可以恢复，但是如果我们在事务中执行了诸如：向标准输出打印日志、向外界发出邮件、没有通过数据库修改了磁盘上的内容甚至在事务执行期间发生了转账汇款，那么这些操作作为可见的外部输出都是没有办法回滚的；这些问题都是由应用开发者解决和负责的，在绝大多数情况下，我们都需要在整个事务提交后，再触发类似的无法回滚的操作\n\n以订票为例，哪怕我们在整个事务结束之后，才向第三方发起请求，由于向第三方请求并获取结果是一个需要较长事件的操作，如果在事务刚刚提交时，数据库或者服务器发生了崩溃，那么我们就非常有可能丢失发起请求这一过程，这就造成了非常严重的问题；而这一点就不是数据库所能保证的，开发者需要在适当的时候查看请求是否被发起、结果是成功还是失败。\n\n### 并行事务的原子性\n\n到目前为止，所有的事务都只是串行执行的，一直都没有考虑过并行执行的问题；然而在实际工作中，并行执行的事务才是常态，然而并行任务下，却可能出现非常复杂的问题：\n\n当 Transaction1 在执行的过程中对id = 1的用户进行了读写，但是没有将修改的内容进行提交或者回滚，在这时 Transaction2 对同样的数据进行了读操作并提交了事务；也就是说 Transaction2 是依赖于 Transaction1 的，当 Transaction1 由于一些错误需要回滚时，因为要保证事务的原子性，需要对 Transaction2 进行回滚，但是由于我们已经提交了 Transaction2，所以我们已经没有办法进行回滚操作，在这种问题下我们就发生了问题，[Database System Concepts](https://www.amazon.com/Database-System-Concepts-Computer-Science/dp/0073523321 \"按住 Ctrl 点击访问 https://www.amazon.com/Database-System-Concepts-Computer-Science/dp/0073523321\")一书中将这种现象称为不可恢复安排（Nonrecoverable Schedule），那什么情况下是可以恢复的呢？\n\n> A recoverable schedule is one where, for each pair of transactions Ti and Tj such that Tj reads a data item previously written by Ti , the commit operation of Ti appears before the commit operation of Tj .\n\n简单理解一下，如果 Transaction2 依赖于事务 Transaction1，那么事务 Transaction1 必须在 Transaction2 提交之前完成提交的操作：\n\n然而这样还不算完，当事务的数量逐渐增多时，整个恢复流程也会变得越来越复杂，如果我们想要从事务发生的错误中恢复，也不是一件那么容易的事情。\n\n在上图所示的一次事件中，Transaction2 依赖于 Transaction1，而 Transaction3 又依赖于 Transaction1，当 Transaction1 由于执行出现问题发生回滚时，为了保证事务的原子性，就会将 Transaction2 和 Transaction3 中的工作全部回滚，这种情况也叫做级联回滚（Cascading Rollback），级联回滚的发生会导致大量的工作需要撤回，是我们难以接受的，不过如果想要达到绝对的原子性，这件事情又是不得不去处理的，我们会在文章的后面具体介绍如何处理并行事务的原子性。\n\n### 持久性\n\n既然是数据库，那么一定对数据的持久存储有着非常强烈的需求，如果数据被写入到数据库中，那么数据一定能够被安全存储在磁盘上；而事务的持久性就体现在，一旦事务被提交，那么数据一定会被写入到数据库中并持久存储起来。\n\n当事务已经被提交之后，就无法再次回滚了，唯一能够撤回已经提交的事务的方式就是创建一个相反的事务对原操作进行『补偿』，这也是事务持久性的体现之一。\n\n### 重做日志\n\n与原子性一样，事务的持久性也是通过日志来实现的，MySQL 使用重做日志（redo log）实现事务的持久性，重做日志由两部分组成，一是内存中的重做日志缓冲区，因为重做日志缓冲区在内存中，所以它是易失的，另一个就是在磁盘上的重做日志文件，它是持久的\n\n当我们在一个事务中尝试对数据进行修改时，它会先将数据从磁盘读入内存，并更新内存中缓存的数据，然后生成一条重做日志并写入重做日志缓存，当事务真正提交时，MySQL 会将重做日志缓存中的内容刷新到重做日志文件，再将内存中的数据更新到磁盘上，图中的第 4、5 步就是在事务提交时执行的。\n\n在 InnoDB 中，重做日志都是以 512 字节的块的形式进行存储的，同时因为块的大小与磁盘扇区大小相同，所以重做日志的写入可以保证原子性，不会由于机器断电导致重做日志仅写入一半并留下脏数据。\n\n除了所有对数据库的修改会产生重做日志，因为回滚日志也是需要持久存储的，它们也会创建对应的重做日志，在发生错误后，数据库重启时会从重做日志中找出未被更新到数据库磁盘中的日志重新执行以满足事务的持久性。\n\n### 回滚日志和重做日志\n\n到现在为止我们了解了 MySQL 中的两种日志，回滚日志（undo log）和重做日志（redo log）；在数据库系统中，事务的原子性和持久性是由事务日志（transaction log）保证的，在实现时也就是上面提到的两种日志，前者用于对事务的影响进行撤销，后者在错误处理时对已经提交的事务进行重做，它们能保证两点：\n\n1.  发生错误或者需要回滚的事务能够成功回滚（原子性）；\n\n2.  在事务提交后，数据没来得及写会磁盘就宕机时，在下次重新启动后能够成功恢复数据（持久性）；\n\n在数据库中，这两种日志经常都是一起工作的，我们可以将它们整体看做一条事务日志，其中包含了事务的 ID、修改的行元素以及修改前后的值。\n\n一条事务日志同时包含了修改前后的值，能够非常简单的进行回滚和重做两种操作，在这里我们也不会对重做和回滚日志展开进行介绍，可能会在之后的文章谈一谈数据库系统的恢复机制时提到两种日志的使用。\n\n### 隔离性\n\n其实作者在之前的文章[『浅入浅出』MySQL 和 InnoDB](http://draveness.me/mysql-innodb.html \"按住 Ctrl 点击访问 http://draveness.me/mysql-innodb.html\")就已经介绍过数据库事务的隔离性，不过为了保证文章的独立性和完整性，我们还会对事务的隔离性进行介绍，介绍的内容可能稍微有所不同。\n\n事务的隔离性是数据库处理数据的几大基础之一，如果没有数据库的事务之间没有隔离性，就会发生在[并行事务的原子性](https://draveness.me/mysql-transaction#%E5%B9%B6%E8%A1%8C%E4%BA%8B%E5%8A%A1%E7%9A%84%E5%8E%9F%E5%AD%90%E6%80%A7 \"按住 Ctrl 点击访问 https://draveness.me/mysql-transaction#%E5%B9%B6%E8%A1%8C%E4%BA%8B%E5%8A%A1%E7%9A%84%E5%8E%9F%E5%AD%90%E6%80%A7\")一节中提到的级联回滚等问题，造成性能上的巨大损失。如果所有的事务的执行顺序都是线性的，那么对于事务的管理容易得多，但是允许事务的并行执行却能能够提升吞吐量和资源利用率，并且可以减少每个事务的等待时间。\n\n当多个事务同时并发执行时，事务的隔离性可能就会被违反，虽然单个事务的执行可能没有任何错误，但是从总体来看就会造成数据库的一致性出现问题，而串行虽然能够允许开发者忽略并行造成的影响，能够很好地维护数据库的一致性，但是却会影响事务执行的性能。\n\n### 事务的隔离级别\n\n所以说数据库的隔离性和一致性其实是一个需要开发者去权衡的问题，为数据库提供什么样的隔离性层级也就决定了数据库的性能以及可以达到什么样的一致性；在 SQL 标准中定义了四种数据库的事务的隔离级别：READ UNCOMMITED、READ COMMITED、REPEATABLE READ和SERIALIZABLE；每个事务的隔离级别其实都比上一级多解决了一个问题：\n\n*   RAED UNCOMMITED：使用查询语句不会加锁，可能会读到未提交的行（Dirty Read）；\n\n*   READ COMMITED：只对记录加记录锁，而不会在记录之间加间隙锁，所以允许新的记录插入到被锁定记录的附近，所以再多次使用查询语句时，可能得到不同的结果（Non-Repeatable Read）；\n\n*   REPEATABLE READ：多次读取同一范围的数据会返回第一次查询的快照，不会返回不同的数据行，但是可能发生幻读（Phantom Read）；\n\n*   SERIALIZABLE：InnoDB 隐式地将全部的查询语句加上共享锁，解决了幻读的问题；\n\n以上的所有的事务隔离级别都不允许脏写入（Dirty Write），也就是当前事务更新了另一个事务已经更新但是还未提交的数据，大部分的数据库中都使用了 READ COMMITED 作为默认的事务隔离级别，但是 MySQL 使用了 REPEATABLE READ 作为默认配置；从 RAED UNCOMMITED 到 SERIALIZABLE，随着事务隔离级别变得越来越严格，数据库对于并发执行事务的性能也逐渐下降。\n\n对于数据库的使用者，从理论上说，并不需要知道事务的隔离级别是如何实现的，我们只需要知道这个隔离级别解决了什么样的问题，但是不同数据库对于不同隔离级别的是实现细节在很多时候都会让我们遇到意料之外的坑。\n\n如果读者不了解脏读、不可重复读和幻读究竟是什么，可以阅读之前的文章[『浅入浅出』MySQL 和 InnoDB](http://draveness.me/mysql-innodb.html \"按住 Ctrl 点击访问 http://draveness.me/mysql-innodb.html\")，在这里我们仅放一张图来展示各个隔离层级对这几个问题的解决情况。\n\n### 隔离级别的实现\n\n数据库对于隔离级别的实现就是使用并发控制机制对在同一时间执行的事务进行控制，限制不同的事务对于同一资源的访问和更新，而最重要也最常见的并发控制机制，在这里我们将简单介绍三种最重要的并发控制器机制的工作原理。\n\n### 锁\n\n锁是一种最为常见的并发控制机制，在一个事务中，我们并不会将整个数据库都加锁，而是只会锁住那些需要访问的数据项， MySQL 和常见数据库中的锁都分为两种，共享锁（Shared）和互斥锁（Exclusive），前者也叫读锁，后者叫写锁。\n\n读锁保证了读操作可以并发执行，相互不会影响，而写锁保证了在更新数据库数据时不会有其他的事务访问或者更改同一条记录造成不可预知的问题。\n\n### 时间戳\n\n除了锁，另一种实现事务的隔离性的方式就是通过时间戳，使用这种方式实现事务的数据库，例如 PostgreSQL 会为每一条记录保留两个字段；读时间戳中报错了所有访问该记录的事务中的最大时间戳，而记录行的写时间戳中保存了将记录改到当前值的事务的时间戳。\n\n使用时间戳实现事务的隔离性时，往往都会使用乐观锁，先对数据进行修改，在写回时再去判断当前值，也就是时间戳是否改变过，如果没有改变过，就写入，否则，生成一个新的时间戳并再次更新数据，乐观锁其实并不是真正的锁机制，它只是一种思想，在这里并不会对它进行展开介绍。\n\n### 多版本和快照隔离\n\n通过维护多个版本的数据，数据库可以允许事务在数据被其他事务更新时对旧版本的数据进行读取，很多数据库都对这一机制进行了实现；因为所有的读操作不再需要等待写锁的释放，所以能够显著地提升读的性能，MySQL 和 PostgreSQL 都对这一机制进行自己的实现，也就是 MVCC，虽然各自实现的方式有所不同，MySQL 就通过文章中提到的回滚日志实现了 MVCC，保证事务并行执行时能够不等待互斥锁的释放直接获取数据。\n\n### 隔离性与原子性\n\n在这里就需要简单提一下在在原子性一节中遇到的级联回滚等问题了，如果一个事务对数据进行了写入，这时就会获取一个互斥锁，其他的事务就想要获得改行数据的读锁就必须等待写锁的释放，自然就不会发生级联回滚等问题了。\n\n不过在大多数的数据库，比如 MySQL 中都使用了 MVCC 等特性，也就是正常的读方法是不需要获取锁的，在想要对读取的数据进行更新时需要使用SELECT ... FOR UPDATE尝试获取对应行的互斥锁，以保证不同事务可以正常工作。\n\n### 一致性\n\n作者认为数据库的一致性是一个非常让人迷惑的概念，原因是数据库领域其实包含两个一致性，一个是 ACID 中的一致性、另一个是 CAP 定义中的一致性。\n\n这两个数据库的一致性说的完全不是一个事情，很多很多人都对这两者的概念有非常深的误解，当我们在讨论数据库的一致性时，一定要清楚上下文的语义是什么，尽量明确的问出我们要讨论的到底是 ACID 中的一致性还是 CAP 中的一致性。\n\n### ACID\n\n数据库对于 ACID 中的一致性的定义是这样的：如果一个事务原子地在一个一致地数据库中独立运行，那么在它执行之后，数据库的状态一定是一致的。对于这个概念，它的第一层意思就是对于数据完整性的约束，包括主键约束、引用约束以及一些约束检查等等，在事务的执行的前后以及过程中不会违背对数据完整性的约束，所有对数据库写入的操作都应该是合法的，并不能产生不合法的数据状态。\n\n> A transaction must preserve database consistency - if a transaction is run atomically in isolation starting from a consistent database, the database must again be consistent at the end of the transaction.\n\n我们可以将事务理解成一个函数，它接受一个外界的 SQL 输入和一个一致的数据库，它一定会返回一个一致的数据库。\n\n而第二层意思其实是指逻辑上的对于开发者的要求，我们要在代码中写出正确的事务逻辑，比如银行转账，事务中的逻辑不可能只扣钱或者只加钱，这是应用层面上对于数据库一致性的要求。\n\n> Ensuring consistency for an individual transaction is the responsibility of the application programmer who codes the transaction. -[Database System Concepts](https://www.amazon.com/Database-System-Concepts-Computer-Science/dp/0073523321 \"按住 Ctrl 点击访问 https://www.amazon.com/Database-System-Concepts-Computer-Science/dp/0073523321\")\n\n数据库 ACID 中的一致性对事务的要求不止包含对数据完整性以及合法性的检查，还包含应用层面逻辑的正确。\n\nCAP 定理中的数据一致性，其实是说分布式系统中的各个节点中对于同一数据的拷贝有着相同的值；而 ACID 中的一致性是指数据库的规则，如果 schema 中规定了一个值必须是唯一的，那么一致的系统必须确保在所有的操作中，该值都是唯一的，由此来看 CAP 和 ACID 对于一致性的定义有着根本性的区别。\n\n## 总结\n\n事务的 ACID 四大基本特性是保证数据库能够运行的基石，但是完全保证数据库的 ACID，尤其是隔离性会对性能有比较大影响，在实际的使用中我们也会根据业务的需求对隔离性进行调整，除了隔离性，数据库的原子性和持久性相信都是比较好理解的特性，前者保证数据库的事务要么全部执行、要么全部不执行，后者保证了对数据库的写入都是持久存储的、非易失的，而一致性不仅是数据库对本身数据的完整性的要求，同时也对开发者提出了要求 - 写出逻辑正确并且合理的事务。\n\n最后，也是最重要的，当别人在将一致性的时候，一定要搞清楚他的上下文，如果对文章的内容有疑问，可以在评论中留言。\n\n## 浅谈数据库并发控制 - 锁和 MVCC\n\n转自https://draveness.me/database-concurrency-control\n\n在学习几年编程之后，你会发现所有的问题都没有简单、快捷的解决方案，很多问题都需要权衡和妥协，而本文介绍的就是数据库在并发性能和可串行化之间做的权衡和妥协 - 并发控制机制。\n\n如果数据库中的所有事务都是串行执行的，那么它非常容易成为整个应用的性能瓶颈，虽然说没法水平扩展的节点在最后都会成为瓶颈，但是串行执行事务的数据库会加速这一过程；而并发（Concurrency）使一切事情的发生都有了可能，它能够解决一定的性能问题，但是它会带来更多诡异的错误。\n\n引入了并发事务之后，如果不对事务的执行进行控制就会出现各种各样的问题，你可能没有享受到并发带来的性能提升就已经被各种奇怪的问题折磨的欲仙欲死了。\n\n### 概述\n\n如何控制并发是数据库领域中非常重要的问题之一，不过到今天为止事务并发的控制已经有了很多成熟的解决方案，而这些方案的原理就是这篇文章想要介绍的内容，文章中会介绍最为常见的三种并发控制机制：\n\n分别是悲观并发控制、乐观并发控制和多版本并发控制，其中悲观并发控制其实是最常见的并发控制机制，也就是锁；而乐观并发控制其实也有另一个名字：乐观锁，乐观锁其实并不是一种真实存在的锁，我们会在文章后面的部分中具体介绍；最后就是多版本并发控制（MVCC）了，与前两者对立的命名不同，MVCC 可以与前两者中的任意一种机制结合使用，以提高数据库的读性能。\n\n既然这篇文章介绍了不同的并发控制机制，那么一定会涉及到不同事务的并发，我们会通过示意图的方式分析各种机制是如何工作的。\n\n### 悲观并发控制\n\n控制不同的事务对同一份数据的获取是保证数据库的一致性的最根本方法，如果我们能够让事务在同一时间对同一资源有着独占的能力，那么就可以保证操作同一资源的不同事务不会相互影响。\n\n最简单的、应用最广的方法就是使用锁来解决，当事务需要对资源进行操作时需要先获得资源对应的锁，保证其他事务不会访问该资源后，在对资源进行各种操作；在悲观并发控制中，数据库程序对于数据被修改持悲观的态度，在数据处理的过程中都会被锁定，以此来解决竞争的问题。\n\n### 读写锁\n\n为了最大化数据库事务的并发能力，数据库中的锁被设计为两种模式，分别是共享锁和互斥锁。当一个事务获得共享锁之后，它只可以进行读操作，所以共享锁也叫读锁；而当一个事务获得一行数据的互斥锁时，就可以对该行数据进行读和写操作，所以互斥锁也叫写锁。\n\n共享锁和互斥锁除了限制事务能够执行的读写操作之外，它们之间还有『共享』和『互斥』的关系，也就是多个事务可以同时获得某一行数据的共享锁，但是互斥锁与共享锁和其他的互斥锁并不兼容，我们可以很自然地理解这么设计的原因：多个事务同时写入同一数据难免会发生各种诡异的问题。\n\n如果当前事务没有办法获取该行数据对应的锁时就会陷入等待的状态，直到其他事务将当前数据对应的锁释放才可以获得锁并执行相应的操作。\n\n### 两阶段锁协议\n\n两阶段锁协议（2PL）是一种能够保证事务可串行化的协议，它将事务的获取锁和释放锁划分成了增长（Growing）和缩减（Shrinking）两个不同的阶段。\n\n在增长阶段，一个事务可以获得锁但是不能释放锁；而在缩减阶段事务只可以释放锁，并不能获得新的锁，如果只看 2PL 的定义，那么到这里就已经介绍完了，但是它还有两个变种：\n\n1.  Strict 2PL：事务持有的互斥锁必须在提交后再释放；\n\n2.  Rigorous 2PL：事务持有的所有锁必须在提交后释放；\n\n虽然锁的使用能够为我们解决不同事务之间由于并发执行造成的问题，但是两阶段锁的使用却引入了另一个严重的问题，死锁；不同的事务等待对方已经锁定的资源就会造成死锁，我们在这里举一个简单的例子：\n\n两个事务在刚开始时分别获取了 draven 和 beacon 资源面的锁，然后再请求对方已经获得的锁时就会发生死锁，双方都没有办法等到锁的释放，如果没有死锁的处理机制就会无限等待下去，两个事务都没有办法完成。\n\n### 死锁的处理\n\n死锁在多线程编程中是经常遇到的事情，一旦涉及多个线程对资源进行争夺就需要考虑当前的几个线程或者事务是否会造成死锁；解决死锁大体来看有两种办法，一种是从源头杜绝死锁的产生和出现，另一种是允许系统进入死锁的状态，但是在系统出现死锁时能够及时发现并且进行恢复。\n\n### 预防死锁\n\n有两种方式可以帮助我们预防死锁的出现，一种是保证事务之间的等待不会出现环，也就是事务之间的等待图应该是一张有向无环图，没有循环等待的情况或者保证一个事务中想要获得的所有资源都在事务开始时以原子的方式被锁定，所有的资源要么被锁定要么都不被锁定。\n\n但是这种方式有两个问题，在事务一开始时很难判断哪些资源是需要锁定的，同时因为一些很晚才会用到的数据被提前锁定，数据的利用率与事务的并发率也非常的低。一种解决的办法就是按照一定的顺序为所有的数据行加锁，同时与 2PL 协议结合，在加锁阶段保证所有的数据行都是从小到大依次进行加锁的，不过这种方式依然需要事务提前知道将要加锁的数据集。\n\n另一种预防死锁的方法就是使用抢占加事务回滚的方式预防死锁，当事务开始执行时会先获得一个时间戳，数据库程序会根据事务的时间戳决定事务应该等待还是回滚，在这时也有两种机制供我们选择，一种是 wait-die 机制：\n\n当执行事务的时间戳小于另一事务时，即事务 A 先于 B 开始，那么它就会等待另一个事务释放对应资源的锁，否则就会保持当前的时间戳并回滚。\n\n另一种机制叫做 wound-wait，这是一种抢占的解决方案，它和 wait-die 机制的结果完全相反，当前事务如果先于另一事务执行并请求了另一事务的资源，那么另一事务会立刻回滚，将资源让给先执行的事务，否则就会等待其他事务释放资源：\n\n两种方法都会造成不必要的事务回滚，由此会带来一定的性能损失，更简单的解决死锁的方式就是使用超时时间，但是超时时间的设定是需要仔细考虑的，否则会造成耗时较长的事务无法正常执行，或者无法及时发现需要解决的死锁，所以它的使用还是有一定的局限性。\n\n### 死锁检测和恢复\n\n如果数据库程序无法通过协议从原理上保证死锁不会发生，那么就需要在死锁发生时及时检测到并从死锁状态恢复到正常状态保证数据库程序可以正常工作。在使用检测和恢复的方式解决死锁时，数据库程序需要维护数据和事务之间的引用信息，同时也需要提供一个用于判断当前数据库是否进入死锁状态的算法，最后需要在死锁发生时提供合适的策略及时恢复。\n\n在上一节中我们其实提到死锁的检测可以通过一个有向的等待图来进行判断，如果一个事务依赖于另一个事务正在处理的数据，那么当前事务就会等待另一个事务的结束，这也就是整个等待图中的一条边：\n\n如上图所示，如果在这个有向图中出现了环，就说明当前数据库进入了死锁的状态TransB -> TransE -> TransF -> TransD -> TransB，在这时就需要死锁恢复机制接入了。\n\n如何从死锁中恢复其实非常简单，最常见的解决办法就是选择整个环中一个事务进行回滚，以打破整个等待图中的环，在整个恢复的过程中有三个事情需要考虑：\n\n每次出现死锁时其实都会有多个事务被波及，而选择其中哪一个任务进行回滚是必须要做的事情，在选择牺牲品（Victim）时的黄金原则就是最小化代价，所以我们需要综合考虑事务已经计算的时间、使用的数据行以及涉及的事务等因素；当我们选择了牺牲品之后就可以开始回滚了，回滚其实有两种选择一种是全部回滚，另一种是部分回滚，部分回滚会回滚到事务之前的一个检查点上，如果没有检查点那自然没有办法进行部分回滚。\n\n> 在死锁恢复的过程中，其实还可能出现某些任务在多次死锁时都被选择成为牺牲品，一直都不会成功执行，造成饥饿（Starvation），我们需要保证事务会在有穷的时间内执行，所以要在选择牺牲品时将时间戳加入考虑的范围。\n\n### 锁的粒度\n\n到目前为止我们都没有对不同粒度的锁进行讨论，一直以来我们都讨论的都是数据行锁，但是在有些时候我们希望将多个节点看做一个数据单元，使用锁直接将这个数据单元、表甚至数据库锁定起来。这个目标的实现需要我们在数据库中定义不同粒度的锁：\n\n当我们拥有了不同粒度的锁之后，如果某个事务想要锁定整个数据库或者整张表时只需要简单的锁住对应的节点就会在当前节点加上显示（explicit）锁，在所有的子节点上加隐式（implicit）锁；虽然这种不同粒度的锁能够解决父节点被加锁时，子节点不能被加锁的问题，但是我们没有办法在子节点被加锁时，立刻确定父节点不能被加锁。\n\n在这时我们就需要引入意向锁来解决这个问题了，当需要给子节点加锁时，先给所有的父节点加对应的意向锁，意向锁之间是完全不会互斥的，只是用来帮助父节点快速判断是否可以对该节点进行加锁：\n\n这里是一张引入了两种意向锁，意向共享锁和意向互斥锁之后所有的锁之间的兼容关系；到这里，我们通过不同粒度的锁和意向锁加快了数据库的吞吐量。\n\n### 乐观并发控制\n\n除了悲观并发控制机制 - 锁之外，我们其实还有其他的并发控制机制，乐观并发控制（Optimistic Concurrency Control）。乐观并发控制也叫乐观锁，但是它并不是真正的锁，很多人都会误以为乐观锁是一种真正的锁，然而它只是一种并发控制的思想。\n\n在这一节中，我们将会先介绍基于时间戳的并发控制机制，然后在这个协议的基础上进行扩展，实现乐观的并发控制机制。\n\n### 基于时间戳的协议\n\n锁协议按照不同事务对同一数据项请求的时间依次执行，因为后面执行的事务想要获取的数据已将被前面的事务加锁，只能等待锁的释放，所以基于锁的协议执行事务的顺序与获得锁的顺序有关。在这里想要介绍的基于时间戳的协议能够在事务执行之前先决定事务的执行顺序。\n\n每一个事务都会具有一个全局唯一的时间戳，它即可以使用系统的时钟时间，也可以使用计数器，只要能够保证所有的时间戳都是唯一并且是随时间递增的就可以。\n\n基于时间戳的协议能够保证事务并行执行的顺序与事务按照时间戳串行执行的效果完全相同；每一个数据项都有两个时间戳，读时间戳和写时间戳，分别代表了当前成功执行对应操作的事务的时间戳。\n\n该协议能够保证所有冲突的读写操作都能按照时间戳的大小串行执行，在执行对应的操作时不需要关注其他的事务只需要关心数据项对应时间戳的值就可以了：\n\n无论是读操作还是写操作都会从左到右依次比较读写时间戳的值，如果小于当前值就会直接被拒绝然后回滚，数据库系统会给回滚的事务添加一个新的时间戳并重新执行这个事务。\n\n### 基于验证的协议\n\n乐观并发控制其实本质上就是基于验证的协议，因为在多数的应用中只读的事务占了绝大多数，事务之间因为写操作造成冲突的可能非常小，也就是说大多数的事务在不需要并发控制机制也能运行的非常好，也可以保证数据库的一致性；而并发控制机制其实向整个数据库系统添加了很多的开销，我们其实可以通过别的策略降低这部分开销。\n\n而验证协议就是我们找到的解决办法，它根据事务的只读或者更新将所有事务的执行分为两到三个阶段：\n\n在读阶段，数据库会执行事务中的全部读操作和写操作，并将所有写后的值存入临时变量中，并不会真正更新数据库中的内容；在这时候会进入下一个阶段，数据库程序会检查当前的改动是否合法，也就是是否有其他事务在 RAED PHASE 期间更新了数据，如果通过测试那么直接就进入 WRITE PHASE 将所有存在临时变量中的改动全部写入数据库，没有通过测试的事务会直接被终止。\n\n为了保证乐观并发控制能够正常运行，我们需要知道一个事务不同阶段的发生时间，包括事务开始时间、验证阶段的开始时间以及写阶段的结束时间；通过这三个时间戳，我们可以保证任意冲突的事务不会同时写入数据库，一旦由一个事务完成了验证阶段就会立即写入，其他读取了相同数据的事务就会回滚重新执行。\n\n作为乐观的并发控制机制，它会假定所有的事务在最终都会通过验证阶段并且执行成功，而锁机制和基于时间戳排序的协议是悲观的，因为它们会在发生冲突时强制事务进行等待或者回滚，哪怕有不需要锁也能够保证事务之间不会冲突的可能。\n\n### 多版本并发控制\n\n到目前为止我们介绍的并发控制机制其实都是通过延迟或者终止相应的事务来解决事务之间的竞争条件（Race condition）来保证事务的可串行化；虽然前面的两种并发控制机制确实能够从根本上解决并发事务的可串行化的问题，但是在实际环境中数据库的事务大都是只读的，读请求是写请求的很多倍，如果写请求和读请求之前没有并发控制机制，那么最坏的情况也是读请求读到了已经写入的数据，这对很多应用完全是可以接受的。\n\n在这种大前提下，数据库系统引入了另一种并发控制机制 -多版本并发控制（Multiversion Concurrency Control），每一个写操作都会创建一个新版本的数据，读操作会从有限多个版本的数据中挑选一个最合适的结果直接返回；在这时，读写操作之间的冲突就不再需要被关注，而管理和快速挑选数据的版本就成了 MVCC 需要解决的主要问题。\n\nMVCC 并不是一个与乐观和悲观并发控制对立的东西，它能够与两者很好的结合以增加事务的并发量，在目前最流行的 SQL 数据库 MySQL 和 PostgreSQL 中都对 MVCC 进行了实现；但是由于它们分别实现了悲观锁和乐观锁，所以 MVCC 实现的方式也不同。\n\n## MySQL 与 MVCC\n\nMySQL 中实现的多版本两阶段锁协议（Multiversion 2PL）将 MVCC 和 2PL 的优点结合了起来，每一个版本的数据行都具有一个唯一的时间戳，当有读事务请求时，数据库程序会直接从多个版本的数据项中具有最大时间戳的返回。\n\n更新操作就稍微有些复杂了，事务会先读取最新版本的数据计算出数据更新后的结果，然后创建一个新版本的数据，新数据的时间戳是目前数据行的最大版本＋1：\n\n数据版本的删除也是根据时间戳来选择的，MySQL 会将版本最低的数据定时从数据库中清除以保证不会出现大量的遗留内容。\n\n### PostgreSQL 与 MVCC\n\n与 MySQL 中使用悲观并发控制不同，PostgreSQL 中都是使用乐观并发控制的，这也就导致了 MVCC 在于乐观锁结合时的实现上有一些不同，最终实现的叫做多版本时间戳排序协议（Multiversion Timestamp Ordering），在这个协议中，所有的的事务在执行之前都会被分配一个唯一的时间戳，每一个数据项都有读写两个时间戳：\n\n当 PostgreSQL 的事务发出了一个读请求，数据库直接将最新版本的数据返回，不会被任何操作阻塞，而写操作在执行时，事务的时间戳一定要大或者等于数据行的读时间戳，否则就会被回滚。\n\n这种 MVCC 的实现保证了读事务永远都不会失败并且不需要等待锁的释放，对于读请求远远多于写请求的应用程序，乐观锁加 MVCC 对数据库的性能有着非常大的提升；虽然这种协议能够针对一些实际情况做出一些明显的性能提升，但是也会导致两个问题，一个是每一次读操作都会更新读时间戳造成两次的磁盘写入，第二是事务之间的冲突是通过回滚解决的，所以如果冲突的可能性非常高或者回滚代价巨大，数据库的读写性能还不如使用传统的锁等待方式。\n\n1\\. MVCC简介与实践\n\nMySQL 在InnoDB引擎下有当前读和快照读两种模式。\n\n1 当前读即加锁读，读取记录的最新版本号，会加锁保证其他并发事物不能修改当前记录，直至释放锁。插入/更新/删除操作默认使用当前读，显示的为select语句加lock in share mode或for update的查询也采用当前读模式。\n\n2 快照读：不加锁，读取记录的快照版本，而非最新版本，使用MVCC机制，最大的好处是读取不需要加锁，读写不冲突，用于读操作多于写操作的应用，因此在不显示加[lock in share mode]/[for update]的select语句，即普通的一条select语句默认都是使用快照读MVCC实现模式。所以楼主的为了让大家明白所做的演示操作，既有当前读也有快照读……\n\n1.1 什么是MVCC\n\nMVCC是一种多版本并发控制机制。\n\n1.2 MVCC是为了解决什么问题?\n\n*   大多数的MYSQL事务型存储引擎,如,InnoDB，Falcon以及PBXT都不使用一种简单的行锁机制.事实上,他们都和MVCC–多版本并发控制来一起使用.\n\n*   大家都应该知道,锁机制可以控制并发操作,但是其系统开销较大,而MVCC可以在大多数情况下代替行级锁,使用MVCC,能降低其系统开销.\n\n1.3 MVCC实现\n\nMVCC是通过保存数据在某个时间点的快照来实现的. 不同存储引擎的MVCC. 不同存储引擎的MVCC实现是不同的,典型的有乐观并发控制和悲观并发控制.\n\n2.MVCC 具体实现分析\n\n下面,我们通过InnoDB的MVCC实现来分析MVCC使怎样进行并发控制的. InnoDB的MVCC,是通过在每行记录后面保存两个隐藏的列来实现的,这两个列，分别保存了这个行的创建时间，一个保存的是行的删除时间。这里存储的并不是实际的时间值,而是系统版本号(可以理解为事务的ID)，没开始一个新的事务，系统版本号就会自动递增，事务开始时刻的系统版本号会作为事务的ID.下面看一下在REPEATABLE READ隔离级别下,MVCC具体是如何操作的.\n\n2.1简单的小例子\n\ncreate table yang( id int primary key auto_increment, name varchar(20));\n\n> 假设系统的版本号从1开始.\n\nINSERT\n\nInnoDB为新插入的每一行保存当前系统版本号作为版本号. 第一个事务ID为1；\n\nstart transaction; insert into yang values(NULL,'yang')  ; insert into yang values(NULL,'long'); insert into yang values(NULL,'fei'); commit;\n\n\n\n\n对应在数据中的表如下(后面两列是隐藏列,我们通过查询语句并看不到)\n\n| id | name | 创建时间(事务ID) | 删除时间(事务ID) |\n| --- | --- | --- | --- |\n| 1 | yang | 1 | undefined |\n| 2 | long | 1 | undefined |\n| 3 | fei | 1 | undefined |\n\nSELECT\n\nInnoDB会根据以下两个条件检查每行记录: a.InnoDB只会查找版本早于当前事务版本的数据行(也就是,行的系统版本号小于或等于事务的系统版本号)，这样可以确保事务读取的行，要么是在事务开始前已经存在的，要么是事务自身插入或者修改过的. b.行的删除版本要么未定义,要么大于当前事务版本号,这可以确保事务读取到的行，在事务开始之前未被删除. 只有a,b同时满足的记录，才能返回作为查询结果.\n\nDELETE\n\nInnoDB会为删除的每一行保存当前系统的版本号(事务的ID)作为删除标识. 看下面的具体例子分析: 第二个事务,ID为2;\n\nstart transaction; select *  from yang;  //(1) select *  from yang;  //(2) commit;\n\n假设1\n\n假设在执行这个事务ID为2的过程中,刚执行到(1),这时,有另一个事务ID为3往这个表里插入了一条数据; 第三个事务ID为3;\n\nstart transaction; insert into yang values(NULL,'tian'); commit;\n\n这时表中的数据如下:\n\n| id | name | 创建时间(事务ID) | 删除时间(事务ID) |\n| --- | --- | --- | --- |\n| 1 | yang | 1 | undefined |\n| 2 | long | 1 | undefined |\n| 3 | fei | 1 | undefined |\n| 4 | tian | 3 | undefined |\n\n然后接着执行事务2中的(2),由于id=4的数据的创建时间(事务ID为3),执行当前事务的ID为2,而InnoDB只会查找事务ID小于等于当前事务ID的数据行,所以id=4的数据行并不会在执行事务2中的(2)被检索出来,在事务2中的两条select 语句检索出来的数据都只会下表:\n\n| id | name | 创建时间(事务ID) | 删除时间(事务ID) |\n| --- | --- | --- | --- |\n| 1 | yang | 1 | undefined |\n| 2 | long | 1 | undefined |\n| 3 | fei | 1 | undefined |\n\n假设2\n\n假设在执行这个事务ID为2的过程中,刚执行到(1),假设事务执行完事务3后，接着又执行了事务4; 第四个事务:\n````\nstart   transaction;  delete  from yang where id=1; commit;  \n````\n\n\n\n此时数据库中的表如下:\n\n| id | name | 创建时间(事务ID) | 删除时间(事务ID) |\n| --- | --- | --- | --- |\n| 1 | yang | 1 | 4 |\n| 2 | long | 1 | undefined |\n| 3 | fei | 1 | undefined |\n| 4 | tian | 3 | undefined |\n\n接着执行事务ID为2的事务(2),根据SELECT 检索条件可以知道,它会检索创建时间(创建事务的ID)小于当前事务ID的行和删除时间(删除事务的ID)大于当前事务的行,而id=4的行上面已经说过,而id=1的行由于删除时间(删除事务的ID)大于当前事务的ID,所以事务2的(2)select * from yang也会把id=1的数据检索出来.所以,事务2中的两条select 语句检索出来的数据都如下:\n\n| id | name | 创建时间(事务ID) | 删除时间(事务ID) |\n| --- | --- | --- | --- |\n| 1 | yang | 1 | 4 |\n| 2 | long | 1 | undefined |\n| 3 | fei | 1 | undefined |\n\nUPDATE\n\nInnoDB执行UPDATE，实际上是新插入了一行记录，并保存其创建时间为当前事务的ID，同时保存当前事务ID到要UPDATE的行的删除时间.\n\n假设3\n\n假设在执行完事务2的(1)后又执行,其它用户执行了事务3,4,这时，又有一个用户对这张表执行了UPDATE操作: 第5个事务:\n\nstart  transaction; update yang set name='Long' where id=2; commit;\n\n\n\n\n根据update的更新原则:会生成新的一行,并在原来要修改的列的删除时间列上添加本事务ID,得到表如下:\n\n| id | name | 创建时间(事务ID) | 删除时间(事务ID) |\n| --- | --- | --- | --- |\n| 1 | yang | 1 | 4 |\n| 2 | long | 1 | 5 |\n| 3 | fei | 1 | undefined |\n| 4 | tian | 3 | undefined |\n| 2 | Long | 5 | undefined |\n\n继续执行事务2的(2),根据select 语句的检索条件,得到下表:\n\n| id | name | 创建时间(事务ID) | 删除时间(事务ID) |\n| --- | --- | --- | --- |\n| 1 | yang | 1 | 4 |\n| 2 | long | 1 | 5 |\n| 3 | fei | 1 | undefined |\n\n还是和事务2中(1)select 得到相同的结果.\n"
  },
  {
    "path": "docs/database/重新学习MySQL数据库：详解MyIsam与InnoDB引擎的锁实现.md",
    "content": "# 目录\n\n* [三类常见引擎：](#三类常见引擎：)\n    * [如何选择存储引擎：](#如何选择存储引擎：)\n    * [Mysql中的锁](#mysql中的锁)\n    * [MyISAM的锁机制：](#myisam的锁机制：)\n    * [并发插入](#并发插入)\n    * [锁调度](#锁调度)\n    * [InnoDB锁模式](#innodb锁模式)\n    * [锁的实现方式：](#锁的实现方式：)\n    * [何时在InnoDB中使用表锁：](#何时在innodb中使用表锁：)\n    * [死锁：](#死锁：)\n    * [避免死锁：](#避免死锁：)\n\n\n本文转自互联网\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本也将整理到我的个人博客：\n\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n本文是《重新学习MySQL数据库》系列其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。\n\n该系列博文会告诉你如何从入门到进阶，从sql基本的使用方法，从MySQL执行引擎再到索引、事务等知识，一步步地学习MySQL相关技术的实现原理，更好地了解如何基于这些知识来优化sql，减少SQL执行时间，通过执行计划对SQL性能进行分析，再到MySQL的主从复制、主备部署等内容，以便让你更完整地了解整个MySQL方面的技术体系，形成自己的知识框架。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n`\n\n说到锁机制之前，先来看看Mysql的存储引擎，毕竟不同的引擎的锁机制也随着不同。\n\n## 三类常见引擎：\n\nMyIsam：不支持事务，不支持外键，所以访问速度快。锁机制是表锁，支持全文索引\n\nInnoDB：支持事务、支持外键，所以对比MyISAM，InnoDB的处理效率差一些，并要占更多的磁盘空间保留数据和索引。锁机制是行锁，不支持全文索引\n\nMemory：数据是存放在内存中的，默认哈希索引，非常适合存储临时数据，服务器关闭后，数据会丢失掉。\n\n\n\n\n\n### 如何选择存储引擎：\n\nMyISAM：应用是以读操作和插入操作为主，只有很少的更新和删除操作，并且对事务的完整性、并发性要求不是很高。\n\nInnoDB：用于事务处理应用程序，支持外键，如果应用对事务的完整性有比较高的要求，在并发条件下要求数据的一致性。更新删除等频繁（InnoDB可以有效的降低由于删除和更新导致的锁定），对于数据准确性要求比较高的，此引擎适合。\n\nMemory：通常用于更新不太频繁的小表，用以快速得到访问结果。\n\n### Mysql中的锁\n\n如果熟悉多线程，那么对锁肯定是有概念的，锁是计算机协调多个进程或线程对某一资源并发访问的机制。\n\nMysql中的锁分为表锁和行锁：\n\n顾名思义，表锁就是锁住一张表，而行锁就是锁住一行。\n\n表锁的特点：开销小，不会产生死锁，发生锁冲突的概率高，并且并发度低。\n\n行锁的特点：开销大，会产生死锁，发生锁冲突的概率低，并发度高。\n\n因此MyISAM和Memory引擎采用的是表锁，而InnoDB存储引擎采用的是行锁。\n\n### MyISAM的锁机制：\n\n分为共享读锁和独占写锁。\n\n读锁是：当某一进程对某张表进行读操作时（select），其他线程也可以读，但是不能写。简单的理解就是，我读的时候你不能写。\n\n写锁是：当某一进程对某种表某张表的写时（insert，update，，delete），其他线程不能写也不能读。可以理解为，我写的时候，你不能读，也不能写。\n\n因此MyISAM的读操作和写操作，以及写操作之间是串行的！MyISAM在执行读写操作的时候会自动给表加相应的锁（也就是说不用显示的使用locktable命令），MyISAM总是一次获得SQL语句所需要的全部锁，这也是MyISAM不会出现死锁的原因。\n\n下面分别举关于写锁和读锁的例子：\n\n写锁：\n\n\n\n| 事务1                                                        | 事务2                                     |\n| ------------------------------------------------------------ | ----------------------------------------- |\n| 取得first_test表的写锁：mysql>locktablefirst_testwrite;QueryOK,0rowsaffected(0.00sec) |                                           |\n| 当前事务对查询、更新和插入操作都可以执行mysql>select*fromfirst_test;+----+------+ | id                                        |\n| mysql>unlocktable;QueryOK,0rowsaffected(0.00sec)             | 等待                                      |\n|                                                              | mysql>select*fromfirst_test;+----+------+ |\n\n\n\n读锁例子如下：\n\n\n\n| 事务1                                                        | 事务2                                                        |\n| ------------------------------------------------------------ | ------------------------------------------------------------ |\n| 获得表first_read的锁定mysql>locktablefirst_testread;QueryOK,0rowsaffected(0.00sec) |                                                              |\n| 当前事务可以查询该表记录：mysql>select*fromfirst_test;+----+------+ | id                                                           |\n| 但是当前事务不能查询没有锁定的表：mysql>select*fromgoods;ERROR1100(HY000):Table'goods'wasnotlockedwithLOCKTABLES | 其他事务可以查询或更新未锁定的表：mysql>select*fromgoods;+----+------------+------+ |\n| 而且插入更新锁定的表都会报错：mysql>insertintofirst_test(age)values(15);ERROR1099(HY000):Table'first_test'waslockedwithaREADlockandcan'tbe updatedmysql>updatefirst_testsetage=100whereid=1;ERROR1099(HY000):Table'first_test'waslockedwithaREADlockandcan'tbe updated | 当更新被锁定的表时会等待：mysql>updatefirst_testsetage=100whereid=1;等待...... |\n| mysql>unlocktable;QueryOK,0rowsaffected(0.00sec)             | mysql>updatefirst_testsetage=100whereid=1;QueryOK,1rowaffected(38.82sec)Rowsmatched:1Changed:1Warnings:0 |\n\n\n\n### 并发插入\n\n刚说到Mysql在插入和修改的时候都是串行的，但是MyISAM也支持查询和插入的并发操作。\n\nMyISAM中有一个系统变量concurrent_insert（默认为1），用以控制并发插入（用户在表尾插入数据）行为。\n\n当concurrent_insert为0时，不允许并发插入。\n\n当concurrent_insert为1时，如果表中没有空洞（中间没有被删除的行），MyISAM允许一个进程在读表的同时，另一个进程从表尾插入记录。\n\n当concurrent_insert为2时，无论MyISAM表中有没有空洞，都可以在末尾插入记录\n\n\n\n| 事务1                                                        | 事务2                                                        |\n| ------------------------------------------------------------ | ------------------------------------------------------------ |\n| mysql>locktablefirst_testreadlocal;QueryOK,0rowsaffected(0.00sec)--加入local选项是说明，在表满足并发插入的前提下，允许在末尾插入数据 |                                                              |\n| 当前进程不能进行插入和更新操作mysql>insertintofirst_test(age)values(15);ERROR1099(HY000):Table'first_test'waslockedwithaREADlockandcan'tbeupdatedmysql>updatefirst_testsetage=200whereid=1;ERROR1099(HY000):Table'first_test'waslockedwithaREADlockandcan'tbeupdated | 其他进程可以进行插入，但是更新会等待：mysql>insertintofirst_test(age)values(15);QueryOK,1rowaffected(0.00sec)mysql>updatefirst_testsetage=200whereid=2;等待..... |\n| 当前进程不能不能访问其他进程插入的数据mysql>select*fromfirst_test;+----+------+ | id                                                           |\n| 释放锁以后皆大欢喜mysql>unlocktable;QueryOK,0rowsaffected(0.00sec) | 等待                                                         |\n| 插入的和更新的都出来的：mysql>select*fromfirst_test;+----+------+ | id                                                           |\n\n\n\n需要注意的：\n\n并发插入是解决对同一表中的查询和插入的锁争用。\n\n如果对有空洞的表进行并发插入会产生碎片，所以在空闲时可以利用optimizetable命令回收因删除记录产生的空洞。\n\n### 锁调度\n\n在MyISAM中当一个进程请求某张表的读锁，而另一个进程同时也请求写锁，Mysql会先让后者获得写锁。即使读请求比写请求先到达锁等待队列，写锁也会插入到读锁之前。\n\n因为Mysql总是认为写请求一般比读请求重要，这也就是MyISAM不太适合有大量的读写操作的应用的原因，因为大量的写请求会让查询操作很难获取到读锁，有可能永远阻塞。\n\n处理办法：\n\n1、指定Insert、update、delete语句的low_priority属性，降低其优先级。\n\n2、指定启动参数low-priority-updates，使得MyISAM默认给读请求优先的权利。\n\n3、执行命令setlow_priority_updates=1，使该连接发出的请求降低。\n\n4、指定max_write_lock_count设置一个合适的值，当写锁达到这个值后，暂时降低写请求的优先级，让读请求获取锁。\n\n但是上面的处理办法造成的原因就是当遇到复杂的查询语句时，写请求可能很难获取到锁，这是一个很纠结的问题，所以我们一般避免使用复杂的查询语句，如果如法避免，则可以再数据库空闲阶段（深夜）执行。\n\n我们知道mysql在以前，存储引擎默认是MyISAM，但是随着对事务和并发的要求越来越高，便引入了InnoDB引擎，它具有支持事务安全等一系列特性。\n\n\n\n### InnoDB锁模式\n\nInnoDB实现了两种类型的行锁。\n\n共享锁（S）：允许一个事务去读一行，阻止其他事务获得相同的数据集的排他锁。\n\n排他锁（X）：允许获得排他锁的事务更新数据，但是组织其他事务获得相同数据集的共享锁和排他锁。\n\n可以这么理解：\n\n共享锁就是我读的时候，你可以读，但是不能写。排他锁就是我写的时候，你不能读也不能写。其实就是MyISAM的读锁和写锁，但是针对的对象不同了而已。\n\n除此之外InnoDB还有两个表锁：\n\n意向共享锁（IS）：表示事务准备给数据行加入共享锁，也就是说一个数据行加共享锁前必须先取得该表的IS锁\n\n意向排他锁（IX）：类似上面，表示事务准备给数据行加入排他锁，说明事务在一个数据行加排他锁前必须先取得该表的IX锁。\n\n\n\nInnoDB行锁模式兼容列表：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405210259.png)\n\n\n注意：\n\n当一个事务请求的锁模式与当前的锁兼容，InnoDB就将请求的锁授予该事务；反之如果请求不兼容，则该事务就等待锁释放。\n\n意向锁是InnoDB自动加的，不需要用户干预。\n\n对于insert、update、delete，InnoDB会自动给涉及的数据加排他锁（X）；对于一般的Select语句，InnoDB不会加任何锁，事务可以通过以下语句给显示加共享锁或排他锁。\n\n共享锁：select*fromtable_namewhere.....lockinsharemode\n\n排他锁：select*fromtable_namewhere.....forupdate\n\n加入共享锁的例子：\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405210309.png)\n\n利用select....forupdate加入排他锁\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405210359.png)\n\n### 锁的实现方式：\n\nInnoDB行锁是通过给索引项加锁实现的，如果没有索引，InnoDB会通过隐藏的聚簇索引来对记录加锁。\n\n也就是说：如果不通过索引条件检索数据，那么InnoDB将对表中所有数据加锁，实际效果跟表锁一样。\n\n行锁分为三种情形：\n\nRecordlock：对索引项加锁，即锁定一条记录。\n\nGaplock：对索引项之间的‘间隙’、对第一条记录前的间隙或最后一条记录后的间隙加锁，即锁定一个范围的记录，不包含记录本身\n\nNext-keyLock：锁定一个范围的记录并包含记录本身（上面两者的结合）。\n\n注意：InnoDB默认级别是repeatable-read级别，所以下面说的都是在RR级别中的。\n\n\n\n之前一直搞不懂GapLock和Next-keyLock的区别，直到在网上看到一句话豁然开朗，希望对各位有帮助。\n\nNext-KeyLock是行锁与间隙锁的组合，这样，当InnoDB扫描索引记录的时候，会首先对选中的索引记录加上行锁（RecordLock），再对索引记录两边的间隙加上间隙锁（GapLock）。如果一个间隙被事务T1加了锁，其它事务是不能在这个间隙插入记录的。\n\n干巴巴的说没意思，我们来看看具体实例：\n\n假设我们有一张表：\n\n+----+------+\n\n|id|age|\n\n+----+------+\n\n|1|3|\n\n|2|6|\n\n|3|9|\n\n+----+------+\n\n表结构如下：\n\nCREATE TABLE `test` (\n`id` int(11) NOT NULL AUTO_INCREMENT,\n`age` int(11) DEFAULT NULL,\nPRIMARY KEY (`id`),\nKEY `keyname` (`age`)\n) ENGINE=InnoDB AUTO_INCREMENT=302 DEFAULT CHARSET=gbk ;\n\n这样我们age段的索引就分为\n\n(negativeinfinity,3],\n\n(3,6],\n\n(6,9],\n\n(9,positiveinfinity)；\n\n\n\n\n\n我们来看一下几种情况：\n\n1、当事务A执行以下语句：\n\nmysql>select*fromfenyewhereage=6forupdate;\n\n不仅使用行锁锁住了相应的数据行，同时也在两边的区间，（5,6]和（6，9]都加入了gap锁。\n\n这样事务B就无法在这个两个区间insert进新数据,但是事务B可以在两个区间外的区间插入数据。\n\n2、当事务A执行\n\nselect*fromfenyewhereage=7 forupdate;\n\n那么就会给(6,9]这个区间加锁，别的事务无法在此区间插入或更新数据。\n\n3、如果查询的数据不再范围内，\n\n比如事务A执行select*fromfenyewhereage=100forupdate;\n\n那么加锁区间就是(9,positiveinfinity)。\n\n小结：\n\n行锁防止别的事务修改或删除，GAP锁防止别的事务新增，行锁和GAP锁结合形成的的Next-Key锁共同解决了RR级别在写数据时的幻读问题。\n\n\n### 何时在InnoDB中使用表锁：\n\nInnoDB在绝大部分情况会使用行级锁，因为事务和行锁往往是我们选择InnoDB的原因，但是有些情况我们也考虑使用表级锁。\n\n1、当事务需要更新大部分数据时，表又比较大，如果使用默认的行锁，不仅效率低，而且还容易造成其他事务长时间等待和锁冲突。\n\n2、事务比较复杂，很可能引起死锁导致回滚。\n\n### 死锁：\n\n我们说过MyISAM中是不会产生死锁的，因为MyISAM总是一次性获得所需的全部锁，要么全部满足，要么全部等待。而在InnoDB中，锁是逐步获得的，就造成了死锁的可能。\n\n在上面的例子中我们可以看到，当两个事务都需要获得对方持有的锁才能够继续完成事务，导致双方都在等待，产生死锁。\n\n发生死锁后，InnoDB一般都可以检测到，并使一个事务释放锁回退，另一个获取锁完成事务。\n\n### 避免死锁：\n\n有多种方法可以避免死锁，这里只介绍常见的三种：\n\n1、如果不同程序会并发存取多个表，尽量约定以相同的顺序访问表，可以大大降低死锁机会。\n\n2、在同一个事务中，尽可能做到一次锁定所需要的所有资源，减少死锁产生概率；\n\n3、对于非常容易产生死锁的业务部分，可以尝试使用升级锁定颗粒度，通过表级锁定来减少死锁产生的概率；\n"
  },
  {
    "path": "docs/distributed/basic/分布式系统理论基础 ：CAP.md",
    "content": "# 目录\n\n  * [**引言**](#引言)\n  * [**CAP定理**](#cap定理)\n  * [**CAP的工程启示**](#cap的工程启示)\n    * [**1、关于 P 的理解**](#1、关于-p-的理解)\n    * [**2、CA非0/1的选择**](#2、ca非01的选择)\n    * [**3、跳出CAP**](#3、跳出cap)\n    * [**小结**](#小结)\n\n\n本文转自：https://www.cnblogs.com/bangerlee/p/5328888.html\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本文也将同步到我的个人博客：\n\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n该系列博文会告诉你什么是分布式系统，这对后端工程师来说是很重要的一门学问，我们会逐步了解分布式理论中的基本概念，常见算法、以及一些较为复杂的分布式原理，同时也需要进一步了解zookeeper的实现，以及CAP、一致性原理等一些常见的分布式理论基础，以便让你更完整地了解分布式理论的基础，为后续学习分布式技术内容做好准备。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->  \n## **引言**\n\nCAP是分布式系统、特别是分布式存储领域中被讨论最多的理论，“[什么是CAP定理？](https://www.quora.com/What-Is-CAP-Theorem-1)”在Quora 分布式系统分类下排名 FAQ 的 No.1。CAP在程序员中也有较广的普及，它不仅仅是“C、A、P不能同时满足，最多只能3选2”，以下尝试综合各方观点，从发展历史、工程实践等角度讲述CAP理论。希望大家透过本文对CAP理论有更多地了解和认识。\n\n## **CAP定理**\n\nCAP由[Eric Brewer](https://en.wikipedia.org/wiki/Eric_Brewer_(scientist))在2000年PODC会议上提出<sup>[1][2]</sup>，是Eric Brewer在Inktomi<sup>[3]</sup>期间研发搜索引擎、分布式web缓存时得出的关于数据一致性(consistency)、服务可用性(availability)、分区容错性(partition-tolerance)的猜想：\n\n> It is impossible for a web service to provide the three following guarantees : Consistency, Availability and Partition-tolerance.\n\n该猜想在提出两年后被证明成立<sup>[4]</sup>，成为我们熟知的CAP定理：\n\n*   **数据一致性**(consistency)：如果系统对一个写操作返回成功，那么之后的读请求都必须读到这个新数据；如果返回失败，那么所有读操作都不能读到这个数据，对调用者而言数据具有强一致性(strong consistency) (又叫原子性 atomic、线性一致性 linearizable consistency)<sup>[5]</sup>*   **服务可用性**(availability)：所有读写请求在一定时间内得到响应，可终止、不会一直等待\n*   **分区容错性**(partition-tolerance)：在网络分区的情况下，被分隔的节点仍能正常对外服务\n\n在某时刻如果满足AP，分隔的节点同时对外服务但不能相互通信，将导致状态不一致，即不能满足C；如果满足CP，网络分区的情况下为达成C，请求只能一直等待，即不满足A；如果要满足CA，在一定时间内要达到节点状态一致，要求不能出现网络分区，则不能满足P。\n\nC、A、P三者最多只能满足其中两个，和FLP定理一样，CAP定理也指示了一个不可达的结果(impossibility result)。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407204500.png)\n\n## **CAP的工程启示**\n\nCAP理论提出7、8年后，NoSql圈将CAP理论当作对抗传统关系型数据库的依据、阐明自己放宽对数据一致性(consistency)要求的正确性<sup>[6]</sup>，随后引起了大范围关于CAP理论的讨论。\n\nCAP理论看似给我们出了一道3选2的选择题，但在工程实践中存在很多现实限制条件，需要我们做更多地考量与权衡，避免进入CAP认识误区<sup>[7]</sup>。\n\n### **1、关于 P 的理解**\n\nPartition字面意思是网络分区，即因网络因素将系统分隔为多个单独的部分，有人可能会说，网络分区的情况发生概率非常小啊，是不是不用考虑P，保证CA就好<sup>[8]</sup>。要理解P，我们看回CAP证明<sup>[4]</sup>中P的定义：\n\n> In order to model partition tolerance, the network will be allowed to lose arbitrarily many messages sent from one node to another.\n\n网络分区的情况符合该定义，网络丢包的情况也符合以上定义，另外节点宕机，其他节点发往宕机节点的包也将丢失，这种情况同样符合定义。现实情况下我们面对的是一个不可靠的网络、有一定概率宕机的设备，这两个因素都会导致Partition，因而分布式系统实现中 P 是一个必须项，而不是可选项<sup>[9][10]</sup>。\n\n对于分布式系统工程实践，CAP理论更合适的描述是：在满足分区容错的前提下，没有算法能同时满足数据一致性和服务可用性<sup>[11]</sup>：\n\n> In a network subject to communication failures, it is impossible for any web service to implement an atomic read/write shared memory that guarantees a response to every request.\n\n### **2、CA非0/1的选择**\n\nP 是必选项，那3选2的选择题不就变成数据一致性(consistency)、服务可用性(availability) 2选1？工程实践中一致性有不同程度，可用性也有不同等级，在保证分区容错性的前提下，放宽约束后可以兼顾一致性和可用性，两者不是非此即彼<sup>[12]</sup>。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/116770-20160401221124957-2025686892.jpg)\n\nCAP定理证明中的一致性指强一致性，强一致性要求多节点组成的被调要能像单节点一样运作、操作具备原子性，数据在时间、时序上都有要求。如果放宽这些要求，还有其他一致性类型：\n\n*   序列一致性(sequential consistency)<sup>[13]</sup>：不要求时序一致，A操作先于B操作，在B操作后如果所有调用端读操作得到A操作的结果，满足序列一致性\n*   最终一致性(eventual consistency)<sup>[14]</sup>：放宽对时间的要求，在被调完成操作响应后的某个时间点，被调多个节点的数据最终达成一致\n\n可用性在CAP定理里指所有读写操作必须要能终止，实际应用中从主调、被调两个不同的视角，可用性具有不同的含义。当P(网络分区)出现时，主调可以只支持读操作，通过牺牲部分可用性达成数据一致。\n\n工程实践中，较常见的做法是通过异步拷贝副本(asynchronous replication)、quorum/NRW，实现在调用端看来数据强一致、被调端最终一致，在调用端看来服务可用、被调端允许部分节点不可用(或被网络分隔)的效果<sup>[15]</sup>。\n\n### **3、跳出CAP**\n\nCAP理论对实现分布式系统具有指导意义，但CAP理论并没有涵盖分布式工程实践中的所有重要因素。\n\n例如延时(latency)，它是衡量系统可用性、与用户体验直接相关的一项重要指标<sup>[16]</sup>。CAP理论中的可用性要求操作能终止、不无休止地进行，除此之外，我们还关心到底需要多长时间能结束操作，这就是延时，它值得我们设计、实现分布式系统时单列出来考虑。\n\n延时与数据一致性也是一对“冤家”，如果要达到强一致性、多个副本数据一致，必然增加延时。加上延时的考量，我们得到一个CAP理论的修改版本PACELC<sup>[17]</sup>：如果出现P(网络分区)，如何在A(服务可用性)、C(数据一致性)之间选择；否则，如何在L(延时)、C(数据一致性)之间选择。\n\n### **小结**\n\n以上介绍了CAP理论的源起和发展，介绍了CAP理论给分布式系统工程实践带来的启示。\n\nCAP理论对分布式系统实现有非常重大的影响，我们可以根据自身的业务特点，在数据一致性和服务可用性之间作出倾向性地选择。通过放松约束条件，我们可以实现在不同时间点满足CAP(此CAP非CAP定理中的CAP，如C替换为最终一致性)<sup>[18][19][20]</sup>。\n\n有非常非常多文章讨论和研究CAP理论，希望这篇对你认识和了解CAP理论有帮助。\n\n[1] [Harvest, Yield, and Scalable Tolerant Systems](https://cs.uwaterloo.ca/~brecht/servers/readings-new2/harvest-yield.pdf), Armando Fox , Eric Brewer, 1999\n\n[2] [Towards Robust Distributed Systems](http://www.cs.berkeley.edu/~brewer/cs262b-2004/PODC-keynote.pdf), Eric Brewer, 2000\n\n[3] [Inktomi's wild ride - A personal view of the Internet bubble](https://www.youtube.com/watch?v=E91oEn1bnXM), Eric Brewer, 2004\n\n[4] [Brewer’s Conjecture and the Feasibility of Consistent, Available, Partition-Tolerant Web](https://pdfs.semanticscholar.org/24ce/ce61e2128780072bc58f90b8ba47f624bc27.pdf), Seth Gilbert, Nancy Lynch, 2002\n\n[5] [Linearizability: A Correctness Condition for Concurrent Objects](http://cs.brown.edu/~mph/HerlihyW90/p463-herlihy.pdf), Maurice P. Herlihy,Jeannette M. Wing, 1990\n\n[6] [Brewer's CAP Theorem - The kool aid Amazon and Ebay have been drinking](http://julianbrowne.com/article/viewer/brewers-cap-theorem), Julian Browne, 2009\n\n[7] [CAP Theorem between Claims and Misunderstandings: What is to be Sacrificed?](http://www.sersc.org/journals/IJAST/vol56/1.pdf), Balla Wade Diack,Samba Ndiaye,Yahya Slimani, 2013\n\n[8] [Errors in Database Systems, Eventual Consistency, and the CAP Theorem](http://cacm.acm.org/blogs/blog-cacm/83396-errors-in-database-systems-eventual-consistency-and-the-cap-theorem/fulltext), Michael Stonebraker, 2010\n\n[9] [CAP Confusion: Problems with 'partition tolerance'](http://blog.cloudera.com/blog/2010/04/cap-confusion-problems-with-partition-tolerance/), Henry Robinson, 2010\n\n[10] [You Can’t Sacrifice Partition Tolerance](https://codahale.com/you-cant-sacrifice-partition-tolerance/), Coda Hale, 2010\n\n[11] [Perspectives on the CAP Theorem](https://groups.csail.mit.edu/tds/papers/Gilbert/Brewer2.pdf), Seth Gilbert, Nancy Lynch, 2012\n\n[12] [CAP Twelve Years Later: How the \"Rules\" Have Changed](https://www.computer.org/cms/Computer.org/ComputingNow/homepage/2012/0512/T_CO2_CAP12YearsLater.pdf), Eric Brewer, 2012\n\n[13] [How to Make a Multiprocessor Computer That Correctly Executes Multiprocess Programs](http://research.microsoft.com/en-us/um/people/lamport/pubs/multi.pdf), Lamport Leslie, 1979\n\n[14] [Eventual Consistent Databases: State of the Art](http://www.ronpub.com/publications/OJDB-v1i1n03_Elbushra.pdf), Mawahib Elbushra , Jan Lindström, 2014\n\n[15] [Eventually Consistent](http://www.allthingsdistributed.com/2008/12/eventually_consistent.html), Werner Vogels, 2008\n\n[16] [Speed Matters for Google Web Search](http://www.isaacsunyer.com/wp-content/uploads/2009/09/test_velocidad_google.pdf), Jake Brutlag, 2009\n\n[17] [Consistency Tradeoffs in Modern Distributed Database System Design](http://cs-www.cs.yale.edu/homes/dna/papers/abadi-pacelc.pdf), Daniel J. Abadi, 2012\n\n[18] [A CAP Solution (Proving Brewer Wrong)](http://guysblogspot.blogspot.com/2008/09/cap-solution-proving-brewer-wrong.html), Guy's blog, 2008\n\n[19] [How to beat the CAP theorem](http://nathanmarz.com/blog/how-to-beat-the-cap-theorem.html), nathanmarz , 2011\n\n[20] [The CAP FAQ](https://github.com/henryr/cap-faq), Henry Robinson\n"
  },
  {
    "path": "docs/distributed/basic/分布式系统理论基础： 一致性、PC和PC.md",
    "content": "# 目录\n\n  * [**引言**](#引言)\n    * [**一致性(consensus)**](#一致性consensus)\n    * [**2PC**](#2pc)\n    * [**3PC**](#3pc)\n    * [**小结**](#小结)\n\n\n本文转自 https://www.cnblogs.com/bangerlee/p/5268485.html\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本文也将同步到我的个人博客：\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n该系列博文会告诉你什么是分布式系统，这对后端工程师来说是很重要的一门学问，我们会逐步了解分布式理论中的基本概念，常见算法、以及一些较为复杂的分布式原理，同时也需要进一步了解zookeeper的实现，以及CAP、一致性原理等一些常见的分布式理论基础，以便让你更完整地了解分布式理论的基础，为后续学习分布式技术内容做好准备。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->  \n\n转自：https://www.cnblogs.com/bangerlee/p/5268485.html\n\n## **引言**\n\n\n\n\n\n狭义的分布式系统指由网络连接的计算机系统，每个节点独立地承担计算或存储任务，节点间通过网络协同工作。广义的分布式系统是一个相对的概念，正如[Leslie Lamport](https://en.wikipedia.org/wiki/Leslie_Lamport)所说<sup>[1]</sup>：\n\n> What is a distributed systeme.**Distribution is in the eye of the beholder.**  \n> To the user sitting at the keyboard, his IBM personal computer is a nondistributed system.> To a flea crawling around on the circuit board, or to the engineer who designed it, it's very much a distributed system.\n\n一致性是分布式理论中的根本性问题，近半个世纪以来，科学家们围绕着一致性问题提出了很多理论模型，依据这些理论模型，业界也出现了很多工程实践投影。下面我们从一致性问题、特定条件下解决一致性问题的两种方法(2PC、3PC)入门，了解最基础的分布式系统理论。\n\n### **一致性(consensus)**\n\n何为一致性问题？简单而言，一致性问题就是相互独立的节点之间如何达成一项决议的问题。分布式系统中，进行数据库事务提交(commit transaction)、Leader选举、序列号生成等都会遇到一致性问题。这个问题在我们的日常生活中也很常见，比如牌友怎么商定几点在哪打几圈麻将：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/116770-20160313132041413-375351900.jpg)\n\n_《赌圣》，1990_\n\n假设一个具有N个节点的分布式系统，当其满足以下条件时，我们说这个系统满足一致性：\n\n1.  **全认同(agreement)**: 所有N个节点都认同一个结果\n2.  **值合法(validity)**: 该结果必须由N个节点中的节点提出\n3.  **可结束(termination)**: 决议过程在一定时间内结束，不会无休止地进行下去\n\n有人可能会说，决定什么时候在哪搓搓麻将，4个人商量一下就ok，这不很简单吗？\n\n但就这样看似简单的事情，分布式系统实现起来并不轻松，因为它面临着这些问题：\n\n*   **消息传递异步无序(asynchronous)**: 现实网络不是一个可靠的信道，存在消息延时、丢失，节点间消息传递做不到同步有序(synchronous)\n*   **节点宕机(fail-stop)**: 节点持续宕机，不会恢复\n*   **节点宕机恢复(fail-recover)**: 节点宕机一段时间后恢复，在分布式系统中最常见\n*   **网络分化(network partition)**: 网络链路出现问题，将N个节点隔离成多个部分\n*   **拜占庭将军问题(byzantine failure)**<sup>[2]</sup>: 节点或宕机或逻辑失败，甚至不按套路出牌抛出干扰决议的信息\n\n假设现实场景中也存在这样的问题，我们看看结果会怎样：\n\n\n\n\n我: 老王，今晚7点老地方，搓够48圈不见不散！  \n……  \n（第二天凌晨3点） 隔壁老王: 没问题！ // 消息延迟  \n我: ……  \n----------------------------------------------  \n我: 小张，今晚7点老地方，搓够48圈不见不散！  \n小张: No …… （两小时后……）  \n小张: No problem！ // 宕机节点恢复  \n我: ……  \n-----------------------------------------------  \n我: 老李头，今晚7点老地方，搓够48圈不见不散！  \n老李: 必须的，大保健走起！ // 拜占庭将军 （这是要打麻将呢？还是要大保健？还是一边打麻将一边大保健……）目录  \n\n\n\n\n还能不能一起愉快地玩耍...![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/116770-20160313010025194-2394933.png)\n\n我们把以上所列的问题称为系统模型(system model)，讨论分布式系统理论和工程实践的时候，必先划定模型。例如有以下两种模型：\n\n1.  异步环境(asynchronous)下，节点宕机(fail-stop)\n2.  异步环境(asynchronous)下，节点宕机恢复(fail-recover)、网络分化(network partition)\n\n2比1多了节点恢复、网络分化的考量，因而对这两种模型的理论研究和工程解决方案必定是不同的，在还没有明晰所要解决的问题前谈解决方案都是一本正经地耍流氓。\n\n一致性还具备两个属性，一个是强一致(safety)，它要求所有节点状态一致、共进退；一个是可用(liveness)，它要求分布式系统24*7无间断对外服务。FLP定理(FLP impossibility)<sup>[3][4]</sup>已经证明在一个收窄的模型中(异步环境并只存在节点宕机)，不能同时满足 safety 和 liveness。\n\nFLP定理是分布式系统理论中的基础理论，正如物理学中的能量守恒定律彻底否定了永动机的存在，FLP定理否定了同时满足safety 和 liveness 的一致性协议的存在。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/116770-20160314181639599-564845788.jpg)\n\n_《怦然心动 (Flipped)》，2010_   \n工程实践上根据具体的业务场景，或保证强一致(safety)，或在节点宕机、网络分化的时候保证可用(liveness)。2PC、3PC是相对简单的解决一致性问题的协议，下面我们就来了解2PC和3PC。\n\n### **2PC**\n\n2PC(tow phase commit)两阶段提交<sup>[5]</sup>顾名思义它分成两个阶段，先由一方进行提议(propose)并收集其他节点的反馈(vote)，再根据反馈决定提交(commit)或中止(abort)事务。我们将提议的节点称为协调者(coordinator)，其他参与决议节点称为参与者(participants, 或cohorts)：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/116770-20160313202532507-1396598167.png)\n\n_2PC, phase one_\n\n在阶段1中，coordinator发起一个提议，分别问询各participant是否接受。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/116770-20160313203429600-179395429.png)\n\n_2PC, phase two_\n\n在阶段2中，coordinator根据participant的反馈，提交或中止事务，如果participant全部同意则提交，只要有一个participant不同意就中止。\n\n在异步环境(asynchronous)并且没有节点宕机(fail-stop)的模型下，2PC可以满足全认同、值合法、可结束，是解决一致性问题的一种协议。但如果再加上节点宕机(fail-recover)的考虑，2PC是否还能解决一致性问题呢？\n\ncoordinator如果在发起提议后宕机，那么participant将进入阻塞(block)状态、一直等待coordinator回应以完成该次决议。这时需要另一角色把系统从不可结束的状态中带出来，我们把新增的这一角色叫协调者备份(coordinator watchdog)。coordinator宕机一定时间后，watchdog接替原coordinator工作，通过问询(query) 各participant的状态，决定阶段2是提交还是中止。这也要求coordinator/participant 记录(logging)历史状态，以备coordinator宕机后watchdog对participant查询、coordinator宕机恢复后重新找回状态。\n\n从coordinator接收到一次事务请求、发起提议到事务完成，经过2PC协议后增加了2次RTT(propose+commit)，带来的时延(latency)增加相对较少。\n\n### **3PC**\n\n3PC(three phase commit)即三阶段提交<sup>[6][7]</sup>，既然2PC可以在异步网络+节点宕机恢复的模型下实现一致性，那还需要3PC做什么，3PC是什么鬼？\n\n在2PC中一个participant的状态只有它自己和coordinator知晓，假如coordinator提议后自身宕机，在watchdog启用前一个participant又宕机，其他participant就会进入既不能回滚、又不能强制commit的阻塞状态，直到participant宕机恢复。这引出两个疑问：\n\n1.  能不能去掉阻塞，使系统可以在commit/abort前回滚(rollback)到决议发起前的初始状态\n2.  当次决议中，participant间能不能相互知道对方的状态，又或者participant间根本不依赖对方的状态\n\n相比2PC，3PC增加了一个准备提交(prepare to commit)阶段来解决以上问题：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/116770-20160314002734304-489496391.png)\n\n_图片截取自wikipedia_\n\ncoordinator接收完participant的反馈(vote)之后，进入阶段2，给各个participant发送准备提交(prepare to commit)指令。participant接到准备提交指令后可以锁资源，但要求相关操作必须可回滚。coordinator接收完确认(ACK)后进入阶段3、进行commit/abort，3PC的阶段3与2PC的阶段2无异。协调者备份(coordinator watchdog)、状态记录(logging)同样应用在3PC。\n\nparticipant如果在不同阶段宕机，我们来看看3PC如何应对：\n\n*   **阶段1**:coordinator或watchdog未收到宕机participant的vote，直接中止事务；宕机的participant恢复后，读取logging发现未发出赞成vote，自行中止该次事务\n*   **阶段2**:coordinator未收到宕机participant的precommit ACK，但因为之前已经收到了宕机participant的赞成反馈(不然也不会进入到阶段2)，coordinator进行commit；watchdog可以通过问询其他participant获得这些信息，过程同理；宕机的participant恢复后发现收到precommit或已经发出赞成vote，则自行commit该次事务\n*   **阶段3**: 即便coordinator或watchdog未收到宕机participant的commit ACK，也结束该次事务；宕机的participant恢复后发现收到commit或者precommit，也将自行commit该次事务\n\n因为有了准备提交(prepare to commit)阶段，3PC的事务处理延时也增加了1个RTT，变为3个RTT(propose+precommit+commit)，但是它防止participant宕机后整个系统进入阻塞态，增强了系统的可用性，对一些现实业务场景是非常值得的。\n\n### **小结**\n\n以上介绍了分布式系统理论中的部分基础知识，阐述了一致性(consensus)的定义和实现一致性所要面临的问题，最后讨论在异步网络(asynchronous)、节点宕机恢复(fail-recover)模型下2PC、3PC怎么解决一致性问题。\n\n阅读前人对分布式系统的各项理论研究，其中有严谨地推理、证明，有一种数学的美；观现实中的分布式系统实现，是综合各种因素下妥协的结果。\n\n[1][Solved Problems, Unsolved Problems and Problems in Concurrency](http://research.microsoft.com/en-us/um/people/lamport/pubs/solved-and-unsolved.pdf),Leslie Lamport, 1983\n\n[2][The Byzantine Generals Problem](http://research.microsoft.com/en-us/um/people/lamport/pubs/byz.pdf),Leslie Lamport,Robert Shostak and Marshall Pease, 1982\n\n[3][Impossibility of Distributed Consensus with One Faulty Process](http://cs-www.cs.yale.edu/homes/arvind/cs425/doc/fischer.pdf),Fischer, Lynch and Patterson, 1985\n\n[4][FLP Impossibility的证明](http://danielw.cn/FLP-proof/),Daniel Wu, 2015\n\n[5][Consensus Protocols: Two-Phase Commit](http://the-paper-trail.org/blog/consensus-protocols-two-phase-commit/),Henry Robinson, 2008\n\n[6][Consensus Protocols: Three-phase Commit](http://the-paper-trail.org/blog/consensus-protocols-three-phase-commit/),Henry Robinson, 2008\n\n[7][Three-phase commit protocol](https://en.wikipedia.org/wiki/Three-phase_commit_protocol),Wikipedia\n"
  },
  {
    "path": "docs/distributed/basic/分布式系统理论基础： 时间、时钟和事件顺序.md",
    "content": "# 目录\n\n  * [**物理时钟 vs 逻辑时钟**](#物理时钟-vs-逻辑时钟)\n  * [**Lamport timestamps**](#lamport-timestamps)\n  * [**Vector clock**](#vector-clock)\n  * [**Version vector**](#version-vector)\n  * [**小结**](#小结)\n\n\n转自：https://www.cnblogs.com/bangerlee/p/5448766.html\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本文也将同步到我的个人博客：\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n该系列博文会告诉你什么是分布式系统，这对后端工程师来说是很重要的一门学问，我们会逐步了解分布式理论中的基本概念，常见算法、以及一些较为复杂的分布式原理，同时也需要进一步了解zookeeper的实现，以及CAP、一致性原理等一些常见的分布式理论基础，以便让你更完整地了解分布式理论的基础，为后续学习分布式技术内容做好准备。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->  \n\n> 十六号…… 四月十六号。一九六零年四月十六号下午三点之前的一分钟你和我在一起，因为你我会记住这一分钟。从现在开始我们就是一分钟的朋友，这是事实，你改变不了，因为已经过去了。我明天会再来。\n> >   —— 《阿飞正传》\n\n现实生活中时间是很重要的概念，时间可以记录事情发生的时刻、比较事情发生的先后顺序。分布式系统的一些场景也需要记录和比较不同节点间事件发生的顺序，但不同于日常生活使用物理时钟记录时间，分布式系统使用逻辑时钟记录事件顺序关系，下面我们来看分布式系统中几种常见的逻辑时钟。\n\n## **物理时钟 vs 逻辑时钟**\n\n可能有人会问，为什么分布式系统不使用物理时钟(physical clock)记录事件？每个事件对应打上一个时间戳，当需要比较顺序的时候比较相应时间戳就好了。\n\n这是因为现实生活中物理时间有统一的标准，而分布式系统中每个节点记录的时间并不一样，即使设置了[NTP](http://www.zhihu.com/question/24960940)时间同步节点间也存在毫秒级别的偏差<sup>[1][2]</sup>。因而分布式系统需要有另外的方法记录事件顺序关系，这就是逻辑时钟(logical clock)。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/116770-20160501132311347-349996615.jpg)\n\n## **Lamport timestamps**\n\n[Leslie](https://en.wikipedia.org/wiki/Leslie_Cheung)[Lamport](https://en.wikipedia.org/wiki/Leslie_Lamport)在1978年提出逻辑时钟的概念，并描述了一种逻辑时钟的表示方法，这个方法被称为Lamport时间戳(Lamport timestamps)<sup>[3]</sup>。\n\n分布式系统中按是否存在节点交互可分为三类事件，一类发生于节点内部，二是发送事件，三是接收事件。Lamport时间戳原理如下：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/116770-20160501174922566-1686627384.png)\n\n_图1: Lamport timestamps space time (图片来源: wikipedia)_\n\n1.  每个事件对应一个Lamport时间戳，初始值为0\n2.  如果事件在节点内发生，时间戳加1\n3.  如果事件属于发送事件，时间戳加1并在消息中带上该时间戳\n4.  如果事件属于接收事件，时间戳 = Max(本地时间戳，消息中的时间戳) + 1\n\n假设有事件a、b，C(a)、C(b)分别表示事件a、b对应的Lamport时间戳，如果C(a) < C(b)，则有a发生在b之前(happened before)，记作 a -> b，例如图1中有 C1 -> B1。通过该定义，事件集中Lamport时间戳不等的事件可进行比较，我们获得事件的[偏序关系](https://en.wikipedia.org/wiki/Partially_ordered_set#Formal_definition)(partial order)。\n\n如果C(a) = C(b)，那a、b事件的顺序又是怎样的？假设a、b分别在节点P、Q上发生，P<sub>i、</sub>Q<sub>j</sub>分别表示我们给P、Q的编号，如果C(a) = C(b) 并且P<sub>i</sub><Q<sub>j</sub>，同样定义为a发生在b之前，记作 a => b。假如我们对图1的A、B、C分别编号A<sub>i</sub>= 1、B<sub>j</sub>= 2、C<sub>k</sub>= 3，因 C(B4) = C(C3) 并且 B<sub>j</sub>< C<sub>k</sub>，则 B4 => C3。\n\n通过以上定义，我们可以对所有事件排序、获得事件的[全序关系](https://en.wikipedia.org/wiki/Total_order)(total order)。上图例子，我们可以从C1到A4进行排序。\n\n## **Vector clock**\n\nLamport时间戳帮助我们得到事件顺序关系，但还有一种顺序关系不能用Lamport时间戳很好地表示出来，那就是同时发生关系(concurrent)<sup>[4]</sup>。例如图1中事件B4和事件C3没有因果关系，属于同时发生事件，但Lamport时间戳定义两者有先后顺序。\n\nVector clock是在Lamport时间戳基础上演进的另一种逻辑时钟方法，它通过vector结构不但记录本节点的Lamport时间戳，同时也记录了其他节点的Lamport时间戳<sup>[5][6]</sup>。Vector clock的原理与Lamport时间戳类似，使用图例如下：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/116770-20160502134654404-1109556515.png)\n\n_图2: Vector clock space time (_图片来源: wikipedia)__\n\n假设有事件a、b分别在节点P、Q上发生，Vector clock分别为T<sub>a</sub>、T<sub>b</sub>，如果 T<sub>b</sub>[Q] > T<sub>a</sub>[Q] 并且 T<sub>b</sub>[P] >= T<sub>a</sub>[P]，则a发生于b之前，记作 a -> b。到目前为止还和Lamport时间戳差别不大，那Vector clock怎么判别同时发生关系呢？\n\n如果T<sub>b</sub>[Q] > T<sub>a</sub>[Q] 并且 T<sub>b</sub>[P] < T<sub>a</sub>[P]，则认为a、b同时发生，记作 a <-> b。例如图2中节点B上的第4个事件 (A:2，B:4，C:1) 与节点C上的第2个事件 (B:3，C:2) 没有因果关系、属于同时发生事件。\n\n## **Version vector**\n\n基于Vector clock我们可以获得任意两个事件的顺序关系，结果或为先后顺序或为同时发生，识别事件顺序在工程实践中有很重要的引申应用，最常见的应用是发现数据冲突(detect conflict)。\n\n分布式系统中数据一般存在多个副本(replication)，多个副本可能被同时更新，这会引起副本间数据不一致<sup>[7]</sup>，Version vector的实现与Vector clock非常类似<sup>[8]</sup>，目的用于发现数据冲突<sup>[9]</sup>。下面通过一个例子说明Version vector的用法<sup>[10]</sup>：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/116770-20160502183034013-800335383.png)\n\n_图3: Version vector_\n\n*   client端写入数据，该请求被S<sub>x</sub>处理并创建相应的vector ([S<sub>x</sub>, 1])，记为数据D1\n*   第2次请求也被S<sub>x</sub>处理，数据修改为D2，vector修改为([S<sub>x</sub>, 2])\n*   第3、第4次请求分别被S<sub>y</sub>、S<sub>z</sub>处理，client端先读取到D2，然后D3、D4被写入S<sub>y</sub>、S<sub>z</sub>\n*   第5次更新时client端读取到D2、D3和D4 3个数据版本，通过类似Vector clock判断同时发生关系的方法可判断D3、D4存在数据冲突，最终通过一定方法解决数据冲突并写入D5\n\nVector clock只用于发现数据冲突，不能解决数据冲突。如何解决数据冲突因场景而异，具体方法有以最后更新为准(last write win)，或将冲突的数据交给client由client端决定如何处理，或通过quorum决议事先避免数据冲突的情况发生<sup>[11]</sup>。\n\n由于记录了所有数据在所有节点上的逻辑时钟信息，Vector clock和Versionvector在实际应用中可能面临的一个问题是vector过大，用于数据管理的元数据(meta data)甚至大于数据本身<sup>[12]</sup>。\n\n解决该问题的方法是使用server id取代client id创建vector (因为server的数量相对client稳定)，或设定最大的size、如果超过该size值则淘汰最旧的vector信息<sup>[10][13]</sup>。\n\n## **小结**\n\n以上介绍了分布式系统里逻辑时钟的表示方法，通过Lamport timestamps可以建立事件的全序关系，通过Vector clock可以比较任意两个事件的顺序关系并且能表示无因果关系的事件，将Vector clock的方法用于发现数据版本冲突，于是有了Version vector。\n\n[1][Time is an illusion](https://queue.acm.org/detail.cfm?id=2878574), George Neville-Neil, 2016\n\n[2][There is No Now](https://queue.acm.org/detail.cfm?id=2745385&__hstc=53389751.f1483a2189ec5c779270b00cdb849993.1461983406379.1461983406379.1461997241982.2&__hssc=53389751.1.1461997241982&__hsfp=1028666893),Justin Sheehy, 2015\n\n[3][Time, Clocks, and the Ordering of Events in a Distributed System](http://research.microsoft.com/en-us/um/people/lamport/pubs/time-clocks.pdf), Leslie Lamport, 1978\n\n[4][Timestamps in Message-Passing Systems That Preserve the Partial Ordering](http://zoo.cs.yale.edu/classes/cs426/2012/lab/bib/fidge88timestamps.pdf), Colin J. Fidge, 1988\n\n[5][Virtual Time and Global States of Distributed Systems](http://www.vs.inf.ethz.ch/publ/papers/VirtTimeGlobStates.pdf), Friedemann Mattern, 1988\n\n[6][Why Vector Clocks are Easy](http://basho.com/posts/technical/why-vector-clocks-are-easy/), Bryan Fink, 2010\n\n[7][Conflict Management](http://guide.couchdb.org/draft/conflicts.html), CouchDB\n\n[8][Version Vectors are not Vector Clocks](https://haslab.wordpress.com/2011/07/08/version-vectors-are-not-vector-clocks/?__hstc=53389751.f1483a2189ec5c779270b00cdb849993.1461983406379.1461983406379.1461997241982.2&__hssc=53389751.1.1461997241982&__hsfp=1028666893), Carlos Baquero,2011\n\n[9][Detection of Mutual Inconsistency in Distributed Systems](http://zoo.cs.yale.edu/classes/cs422/2013/bib/parker83detection.pdf), IEEE Transactions on Software Engineering , 1983\n\n[10][Dynamo: Amazon’s Highly Available Key-value Store](http://s3.amazonaws.com/AllThingsDistributed/sosp/amazon-dynamo-sosp2007.pdf), Amazon, 2007\n\n[11][Conflict Resolution](http://pl.atyp.us/wordpress/?p=2601), Jeff Darcy , 2010\n\n[12][Why Vector Clocks Are Hard](http://basho.com/posts/technical/why-vector-clocks-are-hard/), Justin Sheehy, 2010\n\n[13][Causality Is Expensive (and What To Do About It)](http://www.bailis.org/blog/causality-is-expensive-and-what-to-do-about-it/), Peter Bailis ,2014\n"
  },
  {
    "path": "docs/distributed/basic/分布式系统理论基础：Paxos.md",
    "content": "# 目录\n\n  * [**引言**](#引言)\n  * [**Basic Paxos**](#basic-paxos)\n  * [**Multi Paxos**](#multi-paxos)\n  * [**小结**](#小结)\n\n\n本文转自：https://www.cnblogs.com/bangerlee/p/5655754.html\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本文也将同步到我的个人博客：\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n该系列博文会告诉你什么是分布式系统，这对后端工程师来说是很重要的一门学问，我们会逐步了解分布式理论中的基本概念，常见算法、以及一些较为复杂的分布式原理，同时也需要进一步了解zookeeper的实现，以及CAP、一致性原理等一些常见的分布式理论基础，以便让你更完整地了解分布式理论的基础，为后续学习分布式技术内容做好准备。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->  \n\n## **引言**\n\n\n[《分布式系统理论基础 - 一致性、2PC和3PC》](http://www.cnblogs.com/bangerlee/p/5268485.html)一文介绍了一致性、达成一致性需要面临的各种问题以及2PC、3PC模型，Paxos协议在节点宕机恢复、消息无序或丢失、网络分化的场景下能保证决议的一致性，是被讨论最广泛的一致性协议。\n\nPaxos协议同时又以其“艰深晦涩”著称，下面结合[Paxos Made Simple](http://research.microsoft.com/en-us/um/people/lamport/pubs/paxos-simple.pdf)、[The Part-Time Parliament](http://research.microsoft.com/en-us/um/people/lamport/pubs/lamport-paxos.pdf)两篇论文，尝试通过Paxos推演、学习和了解Paxos协议。\n\n## **Basic Paxos**\n\n何为一致性问题？简单而言，一致性问题是在节点宕机、消息无序等场景可能出现的情况下，相互独立的节点之间如何达成决议的问题，作为解决一致性问题的协议，Paxos的核心是节点间如何确定并只确定一个值(value)。\n\n也许你会疑惑只确定一个值能起什么作用，在Paxos协议里确定并只确定一个值是确定多值的基础，如何确定多值将在第二部分Multi Paxos中介绍，这部分我们聚焦在“Paxos如何确定并只确定一个值”这一问题上。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/116770-20160711232543717-973749854.gif)\n\n和2PC类似，Paxos先把节点分成两类，发起提议(proposal)的一方为proposer，参与决议的一方为acceptor。假如只有一个proposer发起提议，并且节点不宕机、消息不丢包，那么acceptor做到以下这点就可以确定一个值：\n\n\n\n**P1**. 一个acceptor接受它收到的第一项提议\n\n\n\n当然上面要求的前提条件有些严苛，节点不能宕机、消息不能丢包，还只能由一个proposer发起提议。我们尝试放宽条件，假设多个proposer可以同时发起提议，又怎样才能做到确定并只确定一个值呢？\n\n首先proposer和acceptor需要满足以下两个条件：\n\n1\\. proposer发起的每项提议分别用一个ID标识，提议的组成因此变为(ID, value)\n\n2.acceptor可以接受(accept)不止一项提议，当多数(quorum)acceptor接受一项提议时该提议被确定(chosen)\n\n_(注: 注意以上“接受”和“确定”的区别）_\n\n我们约定后面发起的提议的ID比前面提议的ID大，并假设可以有多项提议被确定，为做到确定并只确定一个值acceptor要做到以下这点：\n\n\n\n**P2**. 如果一项值为v的提议被确定，那么后续只确定值为v的提议\n\n\n\n_(注: 乍看这个条件不太好理解，谨记目标是“确定并只确定一个值”)_\n\n由于一项提议被确定(chosen)前必须先被多数派acceptor接受(accepted)，为实现P2，实质上acceptor需要做到：\n\n\n\n**P2a**. 如果一项值为v的提议被确定，那么acceptor后续只接受值为v的提议\n\n\n\n满足P2a则P2成立 (P2a => P2)。\n\n目前在多个proposer可以同时发起提议的情况下，满足P1、P2a即能做到确定并只确定一个值。如果再加上节点宕机恢复、消息丢包的考量呢？\n\n假设acceptor c 宕机一段时间后恢复，c 宕机期间其他acceptor已经确定了一项值为v的决议但c 因为宕机并不知晓；c 恢复后如果有proposer马上发起一项值不是v的提议，由于条件P1，c 会接受该提议，这与P2a矛盾。为了避免这样的情况出现，进一步地我们对proposer作约束：\n\n\n\n**P2b**. 如果一项值为v的提议被确定，那么proposer后续只发起值为v的提议\n\n\n\n满足P2b则P2a成立 (P2b =>P2a => P2)。\n\nP2b约束的是提议被确定(chosen)后proposer的行为，我们更关心提议被确定前proposer应该怎么做：\n\n\n\n**P2c**. 对于提议(n,v)，acceptor的多数派S中，如果存在acceptor最近一次(即ID值最大)接受的提议的值为v'，那么要求v = v'；否则v可为任意值\n\n\n\n满足P2c则P2b成立 (P2c =>P2b => P2a => P2)。\n\n条件P2c是Basic Paxos的核心，光看P2c的描述可能会觉得一头雾水，我们通过[The Part-Time Parliament](http://research.microsoft.com/en-us/um/people/lamport/pubs/lamport-paxos.pdf)中的例子加深理解：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/116770-20160712103935326-2086911417.png)\n\n假设有A~E 5个acceptor，- 表示acceptor因宕机等原因缺席当次决议，x 表示acceptor不接受提议，o 表示接受提议；多数派acceptor接受提议后提议被确定，以上表格对应的决议过程如下：\n\n1.  ID为2的提议最早提出，根据P2c其提议值可为任意值，这里假设为a\n2.  acceptor A/B/C/E 在之前的决议中没有接受(accept)任何提议，因而ID为5的提议的值也可以为任意值，这里假设为b\n3.  acceptor B/D/E，其中D曾接受ID为2的提议，根据P2c，该轮ID为14的提议的值必须与ID为2的提议的值相同，为a\n4.  acceptor A/C/D，其中D曾接受ID为2的提议、C曾接受ID为5的提议，相比之下ID 5较ID 2大，根据P2c，该轮ID为27的提议的值必须与ID为5的提议的值相同，为b；该轮决议被多数派acceptor接受，因此该轮决议得以确定\n5.  acceptor B/C/D，3个acceptor之前都接受过提议，相比之下C、D曾接受的ID 27的ID号最大，该轮ID为29的提议的值必须与ID为27的提议的值相同，为b\n\n以上提到的各项约束条件可以归纳为3点，如果proposer/acceptor满足下面3点，那么在少数节点宕机、网络分化隔离的情况下，在“确定并只确定一个值”这件事情上可以保证一致性(consistency)：\n\n*   B1(ß):ß中每一轮决议都有唯一的ID标识\n*   B2(ß): 如果决议B被acceptor多数派接受，则确定决议B\n*   B3(ß):对于ß中的任意提议B(n,v)，acceptor的多数派中如果存在acceptor最近一次(即ID值最大)接受的提议的值为v'，那么要求v = v'；否则v可为任意值\n\n_(注: 希腊字母ß表示多轮决议的集合，字母B表示一轮决议)_\n\n另外为保证P2c，我们对acceptor作两个要求：\n\n1\\. 记录曾接受的ID最大的提议，因proposer需要问询该信息以决定提议值\n\n2\\. 在回应提议ID为n的proposer自己曾接受过ID最大的提议时，acceptor同时保证(promise)不再接受ID小于n的提议\n\n至此，proposer/acceptor完成一轮决议可归纳为prepare和accept两个阶段。prepare阶段proposer发起提议问询提议值、acceptor回应问询并进行promise；accept阶段完成决议，图示如下：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/116770-20160712125617045-527200085.png)\n\n还有一个问题需要考量，假如proposer A发起ID为n的提议，在提议未完成前proposer B又发起ID为n+1的提议，在n+1提议未完成前proposer C又发起ID为n+2的提议…… 如此acceptor不能完成决议、形成活锁(livelock)，虽然这不影响一致性，但我们一般不想让这样的情况发生。解决的方法是从proposer中选出一个leader，提议统一由leader发起。\n\n最后我们再引入一个新的角色：learner，learner依附于acceptor，用于习得已确定的决议。以上决议过程都只要求acceptor多数派参与，而我们希望尽量所有acceptor的状态一致。如果部分acceptor因宕机等原因未知晓已确定决议，宕机恢复后可经本机learner采用pull的方式从其他acceptor习得。\n\n## **Multi Paxos**\n\n通过以上步骤分布式系统已经能确定一个值，“只确定一个值有什么用？这可解决不了我面临的问题。” 你心中可能有这样的疑问。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/116770-20160712150303811-1635028332.gif)\n\n其实不断地进行“确定一个值”的过程、再为每个过程编上序号，就能得到具有全序关系(total order)的系列值，进而能应用在数据库副本存储等很多场景。我们把单次“确定一个值”的过程称为实例(instance)，它由proposer/acceptor/learner组成，下图说明了A/B/C三机上的实例：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/116770-20160712212514107-1914374126.png)\n\n不同序号的实例之间互相不影响，A/B/C三机输入相同、过程实质等同于执行相同序列的状态机(state machine)指令 ，因而将得到一致的结果。\n\nproposer leader在Multi Paxos中还有助于提升性能，常态下统一由leader发起提议，可节省prepare步骤(leader不用问询acceptor曾接受过的ID最大的提议、只有leader提议也不需要acceptor进行promise)直至发生leader宕机、重新选主。\n\n## **小结**\n\n以上介绍了Paxos的推演过程、如何在Basic Paxos的基础上通过状态机构建Multi Paxos。Paxos协议比较“艰深晦涩”，但多读几遍论文一般能理解其内涵，更难的是如何将Paxos真正应用到工程实践。\n\n微信后台开发同学实现并开源了一套基于Paxos协议的多机状态拷贝类库[PhxPaxos](https://github.com/tencent-wechat/phxpaxos)，PhxPaxos用于将单机服务扩展到多机，其经过线上系统验证并在一致性保证、性能等方面作了很多考量。\n  \n--  \n\n本文提到的一些概念包括一致性(consistency)、一致性系统模型(system model)、多数派(quorum)、全序关系(total order)等，在以下文章中有介绍 :)\n\n[《分布式系统理论基础 - 一致性、2PC和3PC》](http://www.cnblogs.com/bangerlee/p/5268485.html)\n\n[《分布式系统理论基础 - 选举、多数派和租约》](http://www.cnblogs.com/bangerlee/p/5767845.html)\n\n[《分布式系统理论基础 - 时间、时钟和事件顺序》](http://www.cnblogs.com/bangerlee/p/5448766.html)\n\n[《分布式系统理论基础 - CAP》](http://www.cnblogs.com/bangerlee/p/5328888.html)\n"
  },
  {
    "path": "docs/distributed/basic/分布式系统理论基础：Raft、Zab.md",
    "content": "# 目录\n\n  * [**引言**](#引言)\n  * [**Raft**](#raft)\n  * [**Zab**](#zab)\n  * [**Paxos、Raft、Zab再比较**](#paxos、raft、zab再比较)\n  * [**小结**](#小结)\n\n\n本文转自：[https://www.cnblogs.com/bangerlee/p/5991417.html](https://www.cnblogs.com/bangerlee/p/5991417.html)\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n\n> [https://github.com/h2pl/Java-Tutorial](https://github.com/h2pl/Java-Tutorial)\n\n喜欢的话麻烦点下Star哈\n\n本文也将同步到我的个人博客：\n\n> [www.how2playlife.com](www.how2playlife.com)\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n该系列博文会告诉你什么是分布式系统，这对后端工程师来说是很重要的一门学问，我们会逐步了解分布式理论中的基本概念，常见算法、以及一些较为复杂的分布式原理，同时也需要进一步了解zookeeper的实现，以及CAP、一致性原理等一些常见的分布式理论基础，以便让你更完整地了解分布式理论的基础，为后续学习分布式技术内容做好准备。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n## **引言**\n\n[《分布式系统理论进阶 - Paxos》](http://www.cnblogs.com/bangerlee/p/5655754.html)介绍了一致性协议Paxos，今天我们来学习另外两个常见的一致性协议——Raft和Zab。通过与Paxos对比，了解Raft和Zab的核心思想、加深对一致性协议的认识。\n\n## **Raft**\n\nPaxos偏向于理论、对如何应用到工程实践提及较少。理解的难度加上现实的骨感，在生产环境中基于Paxos实现一个正确的分布式系统非常难[1]：\n\n> **There are significant gaps between the description of the Paxos algorithm and the needs of a real-world system**. In order to build a real-world system, an expert needs to use numerous ideas scattered in the literature and make several relatively small protocol extensions. The cumulative effort will be substantial and**the final system will be based on an unproven protocol**.\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/116770-20161023233740326-287853098.png)\n\nRaft[2](#)在2013年提出，提出的时间虽然不长，但已经有很多系统基于Raft实现。相比Paxos，Raft的买点就是更利于理解、更易于实行。\n\n为达到更容易理解和实行的目的，Raft将问题分解和具体化：Leader统一处理变更操作请求，一致性协议的作用具化为保证节点间操作日志副本(log replication)一致，以term作为逻辑时钟(logical clock)保证时序，节点运行相同状态机(state machine)[4]得到一致结果。Raft协议具体过程如下：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/116770-20161024005549560-244386650.png)\n\n1.  Client发起请求，每一条请求包含操作指令\n\n2.  请求交由Leader处理，Leader将操作指令(entry)追加(append)至操作日志，紧接着对Follower发起AppendEntries请求、尝试让操作日志副本在Follower落地\n\n3.  如果Follower多数派(quorum)同意AppendEntries请求，Leader进行commit操作、把指令交由状态机处理\n\n4.  状态机处理完成后将结果返回给Client\n\n\n指令通过log index(指令id)和term number保证时序，正常情况下Leader、Follower状态机按相同顺序执行指令，得出相同结果、状态一致。\n\n宕机、网络分化等情况可引起Leader重新选举(每次选举产生新Leader的同时，产生新的term)、Leader/Follower间状态不一致。Raft中Leader为自己和所有Follower各维护一个nextIndex值，其表示Leader紧接下来要处理的指令id以及将要发给Follower的指令id，LnextIndex不等于FnextIndex时代表Leader操作日志和Follower操作日志存在不一致，这时将从Follower操作日志中最初不一致的地方开始，由Leader操作日志覆盖Follower，直到LnextIndex、FnextIndex相等。\n\nPaxos中Leader的存在是为了提升决议效率，Leader的有无和数目并不影响决议一致性，Raft要求具备唯一Leader，并把一致性问题具体化为保持日志副本的一致性，以此实现相较Paxos而言更容易理解、更容易实现的目标。\n\n## **Zab**\n\nZab[5](#)的全称是Zookeeper atomic broadcast protocol，是Zookeeper内部用到的一致性协议。相比Paxos，Zab最大的特点是保证强一致性(strong consistency，或叫线性一致性linearizable consistency)。\n\n和Raft一样，Zab要求唯一Leader参与决议，Zab可以分解成discovery、sync、broadcast三个阶段：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/116770-20161025133734734-658183229.jpg)\n\n-   **discovery**: 选举产生PL(prospective leader)，PL收集Follower epoch(cepoch)，根据Follower的反馈PL产生newepoch(每次选举产生新Leader的同时产生新epoch，类似Raft的term)\n\n-   **sync**: PL补齐相比Follower多数派缺失的状态、之后各Follower再补齐相比PL缺失的状态，PL和Follower完成状态同步后PL变为正式Leader(established leader)\n\n-   **broadcast**: Leader处理Client的写操作，并将状态变更广播至Follower，Follower多数派通过之后Leader发起将状态变更落地(deliver/commit)\n\n\nLeader和Follower之间通过心跳判别健康状态，正常情况下Zab处在broadcast阶段，出现Leader宕机、网络隔离等异常情况时Zab重新回到discovery阶段。\n\n了解完Zab的基本原理，我们再来看Zab怎样保证强一致性，Zab通过约束事务先后顺序达到强一致性，先广播的事务先commit、FIFO，Zab称之为primary order(以下简称PO)。实现PO的核心是zxid。\n\nZab中每个事务对应一个zxid，它由两部分组成：<e, c>，e即Leader选举时生成的epoch，c表示当次epoch内事务的编号、依次递增。假设有两个事务的zxid分别是z、z'，当满足z.e < z'.e 或者z.e = z'.e && z.c < z'.c时，定义z先于z'发生(z < z')。\n\n为实现PO，Zab对Follower、Leader有以下约束：\n\n1.  有事务z和z'，如果Leader先广播z，则Follower需保证先commit z对应的事务\n\n2.  有事务z和z'，z由Leader p广播，z'由Leader q广播，Leader p先于Leader q，则Follower需保证先commit z对应的事务\n\n3.  有事务z和z'，z由Leader p广播，z'由Leader q广播，Leader p先于Leader q，如果Follower已经commit z，则q需保证已commit z才能广播z'\n\n\n第1、2点保证事务FIFO，第3点保证Leader上具备所有已commit的事务。\n\n相比Paxos，Zab约束了事务顺序、适用于有强一致性需求的场景。\n\n## **Paxos、Raft、Zab再比较**\n\n除Paxos、Raft和Zab外，Viewstamped Replication(简称VR)[7](#)也是讨论比较多的一致性协议。这些协议包含很多共同的内容(Leader、quorum、state machine等)，因而我们不禁要问：Paxos、Raft、Zab和VR等分布式一致性协议区别到底在哪，还是根本就是一回事？[9]\n\nPaxos、Raft、Zab和VR都是解决一致性问题的协议，Paxos协议原文倾向于理论，Raft、Zab、VR倾向于实践，一致性保证程度等的不同也导致这些协议间存在差异。下图帮助我们理解这些协议的相似点和区别[10]：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/116770-20161025213003515-974965973.jpg)\n\n相比Raft、Zab、VR，Paxos更纯粹、更接近一致性问题本源，尽管Paxos倾向理论，但不代表Paxos不能应用于工程。基于Paxos的工程实践，须考虑具体需求场景(如一致性要达到什么程度)，再在Paxos原始语意上进行包装。\n\n## **小结**\n\n以上介绍分布式一致性协议Raft、Zab的核心思想，分析Raft、Zab与Paxos的异同。实现分布式系统时，先从具体需求和场景考虑，Raft、Zab、VR、Paxos等协议没有绝对地好与不好，只是适不适合。\n\n[1][Paxos made live - An engineering perspective](http://www.cs.utexas.edu/users/lorenzo/corsi/cs380d/papers/paper2-1.pdf), Tushar Chandra, Robert Griesemer and Joshua Redstone, 2007\n\n[2][In Search of an Understandable Consensus Algorithm](http://files.catwell.info/misc/mirror/raft/raft.pdf),Diego Ongaro and John Ousterhout, 2013\n\n[3][In Search of an Understandable Consensus Algorithm (Extended Version)](https://www.baidu.com/link?url=59S8Pf5DhEUcoYrNaNCLLF9d-rKeHrakboBuYcNcn86jxgxEIH-LzSzP_isdeyhTA3hkn7AYn64x1KCNAvSy22SJEMKGlqG5Ypum8iTcd9AT2QQwHoHAwVuTR7yBFc4n&wd=&eqid=8c4a598b000007f000000006580cec0c), Diego Ongaro and John Ousterhout, 2013\n\n[4][Implementing Fault-Tolerant Services Using the State Machine](http://www.infosys.tuwien.ac.at/teaching/courses/AdvancedDistributedSystems/download/1990_Schneider_Implementing%20fault-tolerant%20services%20using%20the%20state%20machine%20approach.pdf), Fred B. Schneider, 1990\n\n[5][Zab:High-performance broadcast for primary-backup systems](http://www.cs.cornell.edu/courses/cs6452/2012sp/papers/zab-ieee.pdf), FlavioP.Junqueira,BenjaminC.Reed,andMarcoSeraﬁni, 2011\n\n[6][ZooKeeper's atomic broadcast protocol: Theory and practice](http://www.tcs.hut.fi/Studies/T-79.5001/reports/2012-deSouzaMedeiros.pdf), Andr´e Medeiros, 2012\n\n[7][Viewstamped Replication A New Primary Copy Method to Support Highly-Available Distributed Systems](http://pmg.csail.mit.edu/papers/vr.pdf), Brian M.Oki and Barbar H.Liskov, 1988\n\n[8][Viewstamped Replication Revisited](http://pmg.csail.mit.edu/papers/vr-revisited.pdf), Barbara Liskov and James Cowling, Barbara Liskov and James Cowling ,2012\n\n[9][Can’t we all just agree?](https://blog.acolyer.org/2015/03/01/cant-we-all-just-agree/)The morning paper, 2015\n\n[10][Vive La Difference: Paxos vs. Viewstamped Replication vs. Zab](https://arxiv.org/pdf/1309.5671.pdf),Robbert van Renesse, Nicolas Schiper and Fred B. Schneider, 2014\n"
  },
  {
    "path": "docs/distributed/basic/分布式系统理论基础：zookeeper分布式协调服务.md",
    "content": "# 目录\n\n  * [**ZK API**](#zk-api)\n  * [**ZK应用场景**](#zk应用场景)\n  * [**Leader选举**](#leader选举)\n  * [**配置管理**](#配置管理)\n  * [**ZK监控**](#zk监控)\n  * [**小结**](#小结)\n\n\n本文转自 https://www.cnblogs.com/bangerlee/p/5268485.html\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本文也将同步到我的个人博客：\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n该系列博文会告诉你什么是分布式系统，这对后端工程师来说是很重要的一门学问，我们会逐步了解分布式理论中的基本概念，常见算法、以及一些较为复杂的分布式原理，同时也需要进一步了解zookeeper的实现，以及CAP、一致性原理等一些常见的分布式理论基础，以便让你更完整地了解分布式理论的基础，为后续学习分布式技术内容做好准备。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->  \n\n\n\nzookeeper在分布式系统中作为协调员的角色，可应用于Leader选举、分布式锁、配置管理等服务的实现。以下我们从zookeeper供的API、应用场景和监控三方面学习和了解zookeeper（以下简称ZK）。\n\n\n\n\n\n## **ZK API**\n\nZK以Unix文件系统树结构的形式管理存储的数据，图示如下：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/142337054797921.png)\n\n其中每个树节点被称为**znode**，每个znode类似一个文件，包含文件元信息(meta data)和数据。\n\n以下我们用**server**表示ZK服务的提供方，**client**表示ZK服务的使用方，当client连接ZK时，相应创建**session**会话信息。\n\n有两种类型的znode：\n\n**Regular**: 该类型znode只能由client端显式创建或删除\n\n**Ephemeral**: client端可创建或删除该类型znode；当session终止时，ZK亦会删除该类型znode\n\nznode创建时还可以被打上**sequential**标志，被打上该标志的znode，将自行加上自增的数字后缀\n\nZK提供了以下API，供client操作znode和znode中存储的数据：\n\n*   create(path, data, flags)：创建路径为path的znode，在其中存储data[]数据，flags可设置为Regular或Ephemeral，并可选打上sequential标志。\n*   delete(path, version)：删除相应path/version的znode\n*   exists(path,watch)：如果存在path对应znode，则返回true；否则返回false，watch标志可设置监听事件\n*   getData(path, watch)：返回对应znode的数据和元信息（如version等）\n*   setData(path, data, version)：将data[]数据写入对应path/version的znode\n*   getChildren(path, watch)：返回指定znode的子节点集合\n\n## **ZK应用场景**\n\n基于以上ZK提供的znode和znode数据的操作，可轻松实现Leader选举、分布式锁、配置管理等服务。\n\n## **Leader选举**\n\n利用打上sequential标志的Ephemeral，我们可以实现Leader选举。假设需要从三个client中选取Leader，实现过程如下：\n\n**1**、各自创建Ephemeral类型的znode，并打上sequential标志：\n\n\n\n[zk: localhost:2181(CONNECTED) 4] ls /master  \n[lock-0000000241, lock-0000000243, lock-0000000242]\n\n\n\n**2**、检查 /master 路径下的所有znode，如果自己创建的znode序号最小，则认为自己是Leader；否则记录序号比自己次小的znode\n\n**3**、非Leader在次小序号znode上设置监听事件，并重复执行以上步骤2\n\n假如以上 /master/lock-0000000241节点被删除（相应client服务异常或网络异常等原因），那么 /master/lock-0000000242相应的znode将提升自己为Leader。client只关心自己创建的znode和序号次小的znode，这避免了惊群效应(Herd Effect)。\n\n分布式锁的实现与以上Leader选举的实现相同，稍作修改，我们还可以基于ZK实现lease机制（有期限的授权服务）。\n\n## **配置管理**\n\nznode可以存储数据，基于这一点，我们可以用ZK实现分布式系统的配置管理，假设有服务A，A扩容设备时需要将相应新增的ip/port同步到全网服务器的A.conf配置，实现过程如下：\n\n**1**、A扩容时，相应在ZK上新增znode，该znode数据形式如下：\n\n\n\n[zk: localhost:2181(CONNECTED) 30] get /A/blk-0000340369 {\"svr_info\": [{\"ip\": \"1.1.1.1.\", \"port\": \"11000\"}]}  \ncZxid = 0x2ffdeda3be ……\n\n\n\n**2**、全网机器监听 /A，当该znode下有新节点加入时，调用相应处理函数，将服务A的新增ip/port加入A.conf\n\n**3**、完成步骤2后，继续设置对 /A监听\n\n服务缩容的步骤类似，机器下线时将ZK相应节点删除，全网机器监听到该事件后将配置中的设备剔除。\n\n## **ZK监控**\n\nZK自身提供了一些“四字命令”，通过这些四字命令，我们可以获得ZK集群中，某台ZK的角色、znode数、健康状态等信息：\n\n\n\n\n# echo \"mntr\" | /usr/bin/netcat 127.0.0.1 2181 zk_version 3.4.3-1240972, built on 02/06/2012 10:48 GMT  \nzk_packets_received 267044485 zk_packets_sent 267069992 zk_outstanding_requests 0 zk_server_state follower  \nzk_znode_count 16216\n\n\n\n\n常用的四字命令有：\n\n*   **mntr**：显示自身角色、znode数、平均调用耗时、收包发包数等信息\n*   **ruok**：诊断自身状态是否ok\n*   **cons**：展示当前的client连接\n\n像不能问一个醉酒的人是否喝醉一样，我们也不能确信一台回复\"imok\"的ZK就是真的ok，我们可以通过ZK自带的zkCli.sh模拟client创建/删除znode：\n\n\n\n/usr/local/zookeeper/bin/zkCli.sh create /zookeeper/test 'test' >/dev/null 2>&1  \n/usr/local/zookeeper/bin/zkCli.sh delete /zookeeper/test >/dev/null 2>&1\n\n\n\n再根据返回值判断添加、删除znode是否成功，从而判断该台ZK状态是否正常。\n\n## **小结**\n\nzookeeper以目录树的形式管理数据，提供znode监听、数据设置等接口，基于这些接口，我们可以实现Leader选举、配置管理、命名服务等功能。结合四字命令，加上模拟zookeeper client 创建/删除znode，我们可以实现对zookeeper的有效监控。在各种分布式系统中，我们经常可以看到zookeeper的身影。\n"
  },
  {
    "path": "docs/distributed/basic/分布式系统理论基础：选举、多数派和租约.md",
    "content": "# 目录\n\n  * [**选举(electioin)**](#选举electioin)\n  * [**多数派(quorum)**](#多数派quorum)\n  * [**租约(lease)**](#租约lease)\n  * [**小结**](#小结)\n\n\n本文转自：https://www.cnblogs.com/bangerlee/p/5767845.html\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本文也将同步到我的个人博客：\n\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n该系列博文会告诉你什么是分布式系统，这对后端工程师来说是很重要的一门学问，我们会逐步了解分布式理论中的基本概念，常见算法、以及一些较为复杂的分布式原理，同时也需要进一步了解zookeeper的实现，以及CAP、一致性原理等一些常见的分布式理论基础，以便让你更完整地了解分布式理论的基础，为后续学习分布式技术内容做好准备。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n<small></small> <small>选举(election)是分布式系统实践中常见的问题，通过打破节点间的对等关系，选得的leader(或叫master、coordinator)有助于实现事务原子性、提升决议效率。 多数派(quorum)的思路帮助我们在网络分化的情况下达成决议一致性，在leader选举的场景下帮助我们选出唯一leader。租约(lease)在一定期限内给予节点特定权利，也可以用于实现leader选举。</small>\n\n\n\n\n\n下面我们就来学习分布式系统理论中的选举、多数派和租约。\n\n## **选举(electioin)**\n\n一致性问题(consistency)是独立的节点间如何达成决议的问题，选出大家都认可的leader本质上也是一致性问题，因而如何应对宕机恢复、网络分化等在leader选举中也需要考量。\n\nBully算法<sup>[1]</sup>是最常见的选举算法，其要求每个节点对应一个序号，序号最高的节点为leader。leader宕机后次高序号的节点被重选为leader，过程如下：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407204901.png)\n\n(a). 节点4发现leader不可达，向序号比自己高的节点发起重新选举，重新选举消息中带上自己的序号\n\n(b)(c). 节点5、6接收到重选信息后进行序号比较，发现自身的序号更大，向节点4返回OK消息并各自向更高序号节点发起重新选举\n\n(d). 节点5收到节点6的OK消息，而节点6经过超时时间后收不到更高序号节点的OK消息，则认为自己是leader\n\n(e). 节点6把自己成为leader的信息广播到所有节点\n\n回顾[《分布式系统理论基础 - 一致性、2PC和3PC》](http://www.cnblogs.com/bangerlee/p/5268485.html)就可以看到，Bully算法中有2PC的身影，都具有提议(propose)和收集反馈(vote)的过程。\n\n在一致性算法[Paxos](http://www.cnblogs.com/bangerlee/p/5655754.html)、ZAB<sup>[2]</sup>、Raft<sup>[3]</sup>中，为提升决议效率均有节点充当leader的角色。ZAB、Raft中描述了具体的leader选举实现，与Bully算法类似ZAB中使用zxid标识节点，具有最大zxid的节点表示其所具备的事务(transaction)最新、被选为leader。\n\n## **多数派(quorum)**\n\n在网络分化的场景下以上Bully算法会遇到一个问题，被分隔的节点都认为自己具有最大的序号、将产生多个leader，这时候就需要引入多数派(quorum)<sup>[4]</sup>。多数派的思路在分布式系统中很常见，其确保网络分化情况下决议唯一。\n\n多数派的原理说起来很简单，假如节点总数为2f+1，则一项决议得到多于 f 节点赞成则获得通过。leader选举中，网络分化场景下只有具备多数派节点的部分才可能选出leader，这避免了多leader的产生。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407204912.png)\n\n多数派的思路还被应用于副本(replica)管理，根据业务实际读写比例调整写副本数V<sub>w</sub>、读副本数V<sub>r</sub>，用以在可靠性和性能方面取得平衡<sup>[5]</sup>。\n\n## **租约(lease)**\n\n选举中很重要的一个问题，以上尚未提到：怎么判断leader不可用、什么时候应该发起重新选举？最先可能想到会通过心跳(heart beat)判别leader状态是否正常，但在网络拥塞或瞬断的情况下，这容易导致出现双主。\n\n租约(lease)是解决该问题的常用方法，其最初提出时用于解决分布式缓存一致性问题<sup>[6]</sup>，后面在分布式锁<sup>[7]</sup>等很多方面都有应用。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/116770-20160821195833933-818514275.png)\n\n租约的原理同样不复杂，中心思想是每次租约时长内只有一个节点获得租约、到期后必须重新颁发租约。假设我们有租约颁发节点Z，节点0、1和2竞选leader，租约过程如下：\n\n(a). 节点0、1、2在Z上注册自己，Z根据一定的规则(例如先到先得)颁发租约给节点，该租约同时对应一个有效时长；这里假设节点0获得租约、成为leader\n\n(b). leader宕机时，只有租约到期(timeout)后才重新发起选举，这里节点1获得租约、成为leader\n\n租约机制确保了一个时刻最多只有一个leader，避免只使用心跳机制产生双主的问题。在实践应用中，zookeeper、ectd可用于租约颁发。\n\n## **小结**\n\n在分布式系统理论和实践中，常见leader、quorum和lease的身影。分布式系统内不一定事事协商、事事民主，leader的存在有助于提升决议效率。\n\n本文以leader选举作为例子引入和讲述quorum、lease，当然quorum和lease是两种思想，并不限于leader选举应用。\n\n最后提一个有趣的问题与大家思考，leader选举的本质是一致性问题，Paxos、Raft和ZAB等解决一致性问题的协议和算法本身又需要或依赖于leader，怎么理解这个看似“蛋生鸡、鸡生蛋”的问题？<sup>[8]</sup>\n\n[1][Elections in a Distributed Computing System](http://homepage.divms.uiowa.edu/~ghosh/Bully.pdf), Hector Garcia-Molina, 1982\n\n[2][ZooKeeper’s atomic broadcast protocol: Theory and practice](http://www.tcs.hut.fi/Studies/T-79.5001/reports/2012-deSouzaMedeiros.pdf), Andre Medeiros, 2012\n\n[3][In Search of an Understandable Consensus Algorithm](https://ramcloud.atlassian.net/wiki/download/attachments/6586375/raft.pdf), Diego Ongaro and John Ousterhout, 2013\n\n[4][A quorum-based commit protocol](https://ecommons.cornell.edu/bitstream/handle/1813/6323/82-483.pdf?sequence=1), Dale Skeen, 1982\n\n[5][Weighted Voting for Replicated Data](http://lass.cs.umass.edu/~shenoy/courses/spring04/677/readings/gifford.pdf), David K. Gifford, 1979\n\n[6][Leases: An Efficient Fault-Tolerant Mechanism for Distributed File Cache Consistency](http://web.stanford.edu/class/cs240/readings/89-leases.pdf), Cary G. Gray and David R. Cheriton, 1989\n\n[7][The Chubby lock service for loosely-coupled distributed systems](http://research.google.com/archive/chubby-osdi06.pdf), Mike Burrows, 2006\n\n[8][Why is Paxos leader election not done using Paxos?](http://stackoverflow.com/questions/23798724/why-is-paxos-leader-election-not-done-using-paxos)\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "docs/distributed/basic/分布式系统理论进阶：Paxos变种和优化.md",
    "content": "# 目录\n  * [**引言**](#引言)\n  * [**Multi Paxos**](#multi-paxos)\n  * [**Fast Paxos**](#fast-paxos)\n  * [**EPaxos**](#epaxos)\n  * [**小结**](#小结)\n\n\n本文转自：https://www.cnblogs.com/bangerlee/p/6189646.html\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本文也将同步到我的个人博客：\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n该系列博文会告诉你什么是分布式系统，这对后端工程师来说是很重要的一门学问，我们会逐步了解分布式理论中的基本概念，常见算法、以及一些较为复杂的分布式原理，同时也需要进一步了解zookeeper的实现，以及CAP、一致性原理等一些常见的分布式理论基础，以便让你更完整地了解分布式理论的基础，为后续学习分布式技术内容做好准备。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->  \n\n## **引言**\n\n[《分布式系统理论进阶 - Paxos》](http://www.cnblogs.com/bangerlee/p/5655754.html)中我们了解了Basic Paxos、Multi Paxos的基本原理，但如果想把Paxos应用于工程实践，了解基本原理还不够。\n\n有很多基于Paxos的优化，在保证一致性协议正确(safety)的前提下，减少Paxos决议通信步骤、避免单点故障、实现节点负载均衡，从而降低时延、增加吞吐量、提升可用性，下面我们就来了解这些Paxos变种。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/116770-20161217185911917-43631009.jpg)\n\n## **Multi Paxos**\n\n首先我们来回顾一下Multi Paxos，Multi Paxos在Basic Paxos的基础上确定一系列值，其决议过程如下：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/116770-20161218102045714-754820695.png)\n\nphase1a: leader提交提议给acceptor\n\nphase1b: acceptor返回最近一次接受的提议(即曾接受的最大的提议ID和对应的value)，未接受过提议则返回空\n\nphase2a: leader收集acceptor的应答，分两种情况处理\n\nphase2a.1: 如果应答内容都为空，则自由选择一个提议value\n\nphase2a.2: 如果应答内容不为空，则选择应答里面ID最大的提议的value\n\nphase2b: acceptor将决议同步给learner\n\nMulti Paxos中leader用于避免活锁，但leader的存在会带来其他问题，一是如何选举和保持唯一leader(虽然无leader或多leader不影响一致性，但影响决议进程progress)，二是充当leader的节点会承担更多压力，如何均衡节点的负载。Mencius<sup>[1]</sup>提出节点轮流担任leader，以达到均衡负载的目的；租约(lease)可以帮助实现唯一leader，但leader故障情况下可导致服务短期不可用。\n\n## **Fast Paxos**\n\n在Multi Paxos中，proposer -> leader -> acceptor -> learner，从提议到完成决议共经过3次通信，能不能减少通信步骤？\n\n对Multi Paxos phase2a，如果可以自由提议value，则可以让proposer直接发起提议、leader退出通信过程，变为proposer -> acceptor -> learner，这就是Fast Paxos<sup>[2]</sup>的由来。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/116770-20161218102011683-1409659558.png)\n\nMulti Paxos里提议都由leader提出，因而不存在一次决议出现多个value，Fast Paxos里由proposer直接提议，一次决议里可能有多个proposer提议、出现多个value，即出现提议冲突(collision)。leader起到初始化决议进程(progress)和解决冲突的作用，当冲突发生时leader重新参与决议过程、回退到3次通信步骤。\n\nPaxos自身隐含的一个特性也可以达到减少通信步骤的目标，如果acceptor上一次确定(chosen)的提议来自proposerA，则当次决议proposerA可以直接提议减少一次通信步骤。如果想实现这样的效果，需要在proposer、acceptor记录上一次决议确定(chosen)的历史，用以在提议前知道哪个proposer的提议上一次被确定、当次决议能不能节省一次通信步骤。\n\n## **EPaxos**\n\n除了从减少通信步骤的角度提高Paxos决议效率外，还有其他方面可以降低Paxos决议时延，比如Generalized Paxos<sup>[3]</sup>提出不冲突的提议(例如对不同key的写请求)可以同时决议、以降低Paxos时延。\n\n更进一步地，EPaxos<sup>[4]</sup>(Egalitarian Paxos)提出一种既支持不冲突提议同时提交降低时延、还均衡各节点负载、同时将通信步骤减少到最少的Paxos优化方法。\n\n为达到这些目标，EPaxos的实现有几个要点。一是EPaxos中没有全局的leader，而是每一次提议发起提议的proposer作为当次提议的leader(command leader)；二是不相互影响(interfere)的提议可以同时提交；三是跳过prepare，直接进入accept阶段。EPaxos决议的过程如下：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/116770-20161218173608104-1507680298.png)\n\n左侧展示了互不影响的两个update请求的决议过程，右侧展示了相互影响的两个update请求的决议。Multi Paxos、Mencius、EPaxos时延和吞吐量对比：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/116770-20161218180622104-945213222.png)\n\n为判断决议是否相互影响，实现EPaxos得记录决议之间的依赖关系。\n\n## **小结**\n\n以上介绍了几个基于Paxos的变种，Mencius中节点轮流做leader、均衡节点负载，Fast Paxos减少一次通信步骤，Generalized Paxos允许互不影响的决议同时进行，EPaxos无全局leader、各节点平等分担负载。\n\n优化无止境，对Paxos也一样，应用在不同场景和不同范围的Paxos变种和优化将继续不断出现。\n\n[1][Mencius: Building Efficient Replicated State Machines for WANs](http://cseweb.ucsd.edu/classes/wi09/cse223a/mencius.pdf),Yanhua Mao,Flavio P. Junqueira,Keith Marzullo, 2018\n\n[2][Fast Paxos](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/tr-2005-112.pdf),Leslie Lamport, 2005\n\n[3][Generalized Consensus and Paxos](http://diyhpl.us/~bryan/papers2/distributed/distributed-systems/generalized-consensus-and-paxos.2004.pdf),Leslie Lamport, 2004\n\n[4][There Is More Consensus in Egalitarian Parliaments](http://sigops.org/sosp/sosp13/papers/p358-moraru.pdf),Iulian Moraru, David G. Andersen, Michael Kaminsky, 2013\n"
  },
  {
    "path": "docs/distributed/practice/搞懂分布式技术：LVS实现负载均衡的原理与实践.md",
    "content": "\n# 目录\n\n* [负载均衡的原理](#负载均衡的原理)\n  * [隐藏真实服务器](#隐藏真实服务器)\n  * [偷天换日](#偷天换日)\n  * [四层还是七层?](#四层还是七层)\n  * [责任分离](#责任分离)\n* [1 什么是负载均衡（Load balancing）](#1-什么是负载均衡（load-balancing）)\n* [2 负载均衡分类](#2-负载均衡分类)\n* [3 常用负载均衡工具](#3-常用负载均衡工具)\n  * [3.1 LVS](#31-lvs)\n  * [3.2 Nginx](#32-nginx)\n  * [3.3 HAProxy](#33-haproxy)\n* [4 常见负载均衡算法](#4-常见负载均衡算法)\n* [负载均衡的几种算法Java实现代码](#负载均衡的几种算法java实现代码)\n  * [轮询](#轮询)\n  * [加权随机负载均衡算法](#加权随机负载均衡算法)\n  * [随机负载均衡算法](#随机负载均衡算法)\n  * [负载均衡 ip_hash算法](#负载均衡-ip_hash算法)\n\n\n\n原创： 刘欣 码农翻身 4月23日\n本文内容参考网络，侵删\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本文也将同步到我的个人博客：\n\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n该系列博文会告诉你什么是分布式系统，这对后端工程师来说是很重要的一门学问，我们会逐步了解常见的分布式技术、以及一些较为常见的分布式系统概念，同时也需要进一步了解zookeeper、分布式事务、分布式锁、负载均衡等技术，以便让你更完整地了解分布式技术的具体实战方法，为真正应用分布式技术做好准备。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n# 负载均衡的原理\n\n\n这是1998年一个普通的上午。\n\n一上班，老板就把张大胖叫进了办公室，一边舒服地喝茶一边发难：“大胖啊，我们公司开发的这个网站，现在怎么越来越慢了？ ”\n\n还好张大胖也注意到了这个问题，他早有准备，一脸无奈地说： “唉，我昨天检查了一下系统，现在的访问量已经越来越大了，无论是CPU，还是硬盘、内存都不堪重负了，高峰期的响应速度越来越慢。”\n\n顿了一下，他试探地问道：“老板，能不能买个好机器？ 把现在的‘老破小’服务器给替换掉。我听说IBM的服务器挺好的，性能强劲，要不来一台？”（码农翻身注：这叫垂直扩展 Scale Up）\n\n“好你个头，你知道那机器得多贵吗？! 我们小公司，用不起啊！” 抠门的老板立刻否决。“这……” 大胖表示黔驴技穷了。“你去和CTO Bill 商量下， 明天给我弄个方案出来。”\n\n老板不管过程，只要结果。\n\n## 隐藏真实服务器\n\n大胖悻悻地去找Bill。他将老板的指示声情并茂地做了传达。\n\nBill笑了：“我最近也在思考这件事，想和你商量一下，看看能不能买几台便宜的服务器，把系统多部署几份，横向扩展(Scale Out)一下。 ”\n\n横向扩展？ 张大胖心中寻思着，如果把系统部署到几个服务器上，用户的访问请求就可以分散到各个服务器，那单台服务器的压力就小得多了。\n\n“可是，” 张大胖问道 ，“机器多了，每个机器一个IP， 用户可能就迷糊了，到底访问哪一个？”\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407213404.png)\n\n“肯定不能把这些服务器暴露出去，从客户角度看来，最好是只有一个服务器。” Bill 说道。\n\n张大胖眼前一亮， 突然有了主意：“有了！我们有个中间层啊，对，就是DNS，我们可以设置一下，让我们网站的域名映射到多个服务器的IP，用户面对的是我们系统的域名，然后我们可以采用一种轮询的方式， 用户1的机器做域名解析的时候，DNS返回IP1, 用户2的机器做域名解析的时候，DNS返回IP2…… 这样不就可以实现各个机器的负载相对均衡了吗？”\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407213411.png)\n\nBill 思考片刻，发现了漏洞：“这样做有个很要命的问题，由于DNS这个分层的系统中有缓存，用户端的机器也有缓存，如果某个机器出故障，域名解析仍然会返回那个出问题机器的IP，那所有访问该机器的用户都会出问题， 即使我们把这个机器的IP从DNS中删除也不行， 这就麻烦了。”\n\n张大胖确实是没想到这个缓存带来的问题， 他挠挠头：“那就不好办了。”\n\n## 偷天换日\n\n“要不我们自己开发一个软件实现负载均衡怎么样？” Bill另辟蹊径。\n\n为了展示自己的想法， 他在白板上画了一张图， “看到中间那个蓝色服务器没有，我们可以把它称为Load Balancer （简称LB）， 用户的请求都发给他，然后它再发给各个服务器。”\n\n张大胖仔细审视这个图。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407213424.png)\n\nLoad Balancer 简称LB ， 有两个IP，一个对外（115.39.19.22），一个对内(192.168.0.100)。用户看到的是那个对外的IP。 后面的真正提供服务的服务器有三个，称为RS1, RS2,RS3， 他们的网关都指向LB。\n\n“但是怎么转发请求呢？嗯， 用户的请求到底是什么东西？” 张大胖迷糊了。\n\n“你把计算机网络都忘了吧？ 就是用户发过来的数据包嘛！ 你看这个层层封装的数据包，用户发了一个HTTP的请求，想要访问我们网站的首页，这个HTTP请求被放到一个TCP报文中，再被放到一个IP数据报中， 最终的目的地就是我们的Load Balancer（115.39.19.22）。”\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407213439.png)\n\n（注： 客户发给LB的数据包， 没有画出数据链路层的帧）\n\n“但是这个数据包一看就是发给Load Balancer的， 怎么发给后面的服务器？”\n\nBill 说： “可以偷天换日，比如Load Balancer想把这个数据包发给RS1（192.168.0.10）, 就可以做点手脚，把这个数据包改成这样， 然后这个IP数据包就可以转发给RS1去处理了。”\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407213504.png)\n\n（LB动了手脚，把目的地IP和端口改为RS1的）\n\n“RS1处理完了，要返回首页的HTML，还要把HTTP报文层层封装：” 张大胖明白怎么回事了：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407213510.png)\n\n（RS1处理完了，要发送结果给客户端）\n\n“由于LB是网关，它还会收到这个数据包，它就可以再次施展手段，把源地址和源端口都替换为自己的，然后发给客户就可以了。”\n\n(LB再次动手脚，把源地址和端口改成自己的， 让客户端毫无察觉)\n\n张大胖总结了一下数据的流向：客户端 –> Load Balancer –> RS –> Load Balancer –> 客户端\n\n他兴奋地说：“这招瞒天过海真是妙啊，客户端根本就感受不到后面有好几台服务器在工作，它一直以为只有Load Balancer在干活。”\n\nBill此刻在思考Load Balancer 怎么样才能选取后面的各个真实的服务器， 可以有很多种策略，他在白板上写到：\n\n轮询： 这个最简单，就是一个挨一个轮换。\n\n加权轮询： 为了应对某些服务器性能好，可以让他们的权重高一点，被选中的几率大一点。\n\n最少连接： 哪个服务器处理的连接少，就发给谁。\n\n加权最少连接：在最少连接的基础上，也加上权重……还有些其他的算法和策略，以后慢慢想。\n\n## 四层还是七层?\n\n张大胖却想到了另外一个问题： 对于用户的一个请求来说，可能会被分成多个数据包来发送， 如果这些数据包被我们的Load Balancer发到了不同的机器上，那就完全乱套了啊！ 他把自己的想法告诉了Bill。\n\nBill说：“这个问题很好啊，我们的Load Balancer必须得维护一个表，这个表需要记录下客户端的数据包被我们转发到了哪个真实的服务器上， 这样当下一个数据包到来时，我们就可以把它转发到同一个服务器上去。”\n\n“看来这个负载均衡软件需要是面向连接的，也就是OSI网络体系的第4层， 可以称为四层负载均衡”Bill做了一个总结。\n\n“既然有四层负载均衡，那是不是也可以搞个七层的负载均衡啊？” 张大胖突发奇想。\n\n“那是肯定的，如果我们的Load Balancer把HTTP层的报文数据取出来，根据其中的URL，浏览器，语言等信息，把请求分发到后面真实的服务器去，那就是七层的负载均衡了。不过我们现阶段先实现一个四层的吧，七层的以后再说。”\n\nBill 吩咐张大胖组织人力把这个负载均衡软件给开发出来。\n\n张大胖不敢怠慢，由于涉及到协议的细节问题，张大胖还买了几本书：《TCP/IP详解》 卷一,卷二,卷三， 带着人快速复习了C语言， 然后开始疯狂开发。\n\n## 责任分离\n\n三个月后，Load Balancer的第一版开发出来了，这是运行在Linux上的一个软件， 公司试用了一下，感觉还真是不错，仅仅用几台便宜的服务器就可以实现负载均衡了。\n\n老板看到没花多少钱就解决了问题，非常满意，给张大胖所在的开发组发了1000块钱奖金，组织大家出去搓了一顿。\n\n张大胖他们看到老板很抠门，虽略有不满，但是想到通过这个软件的开发，学到了很多底层的知识，尤其是TCP协议，也就忍了。\n\n可是好景不长，张大胖发现这个Load Balancer存在这瓶颈：所有的流量都要通过它，它要修改客户发来的数据包， 还要修改发给客户的数据包。\n\n网络访问还有个极大的特点，那就是请求报文较短而响应报文往往包含大量的数据。这是很容易理解的，一个HTTP GET请求短得可怜，可是返回的HTML却是极长 – 这就进一步加剧了Load Balancer修改数据包的工作。\n\n张大胖赶紧去找Bill ，Bill说：“这确实是个问题，我们把请求和响应分开处理吧，让Load Balancer只处理请求，让各个服务器把响应直接发给客户端，这样瓶颈不就消除了吗？”\n\n“怎么分开处理？”\n\n“首先让所有的服务器都有同一个IP， 我们把他称为VIP吧（如图中115.39.19.22）。”\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407213522.png)\n\n张大胖通过第一版Load Balancer的开发，积累了丰富的经验。\n\n他问道：“你这是把每个实际服务器的loopback都绑定了那个VIP， 不过有问题啊，这么多服务器都有同样的IP , 当IP数据包来的时候，到底应该由哪个服务器来处理？”\n\n“注意，IP数据包其实是通过数据链路层发过来的，你看看这个图。”\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407213528.png)\n\n张大胖看到了客户端的HTTP报文再次被封装储层TCP报文，端口号是80， 然后IP数据报中的目的地是115.39.19.22(VIP)。\n\n图中的问号是目的地的MAC地址， 该怎么得到呢？\n\n对， 是使用ARP协议，把一个IP地址（115.39.19.22）给广播出去，然后具有此IP机器就会回复自己的MAC地址。 但是现在有好几台机器都有同一个IP（115.39.19.22）， 怎么办？\n\nBill 说道：“我们只让Load Balancer 响应这个VIP地址（115.39.19.22）的ARP请求，对于RS1,RS2,RS3, 抑制住对这个VIP地址的ARP响应，不就可以唯一地确定Load Balancer了？ ”\n\n原来如此！张大胖恍然大悟。\n\n既然Load Balancer得到了这个IP数据包， 它就可以用某个策略从RS1, RS2,RS3中选取一个服务器，例如RS1（192.168.0.10），把IP数据报原封不动， 封装成数据链路层的包（目的地是RS1的MAC地址），直接转发就可以了。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407213534.png)\n\nRS1（192.168.0.10）这个服务器收到了数据包，拆开一看，目的地IP是115.39.19.22，是自己的IP， 那就可以处理了。\n\n处理完了以后，RS1可以直接响应发回给客户端，完全不用再通过Load Balancer。因为自己的地址就是115.39.19.22。\n\n对于客户端来说，它看到的还是那个唯一的地址115.39.19.22， 并不知道后台发生了什么事情。\n\nBill补充到：“由于Load Balancer 根本不会修改IP数据报，其中的TCP的端口号自然也不会修改，这就要求RS1, RS2,RS3上的端口号必须得和Load Balancer一致才行。”\n\n像之前一样，张大胖总结了一下数据的流向：\n\n客户端 –> Load Balancer –> RS –> 客户端\n\nBill 说道：“怎么样？ 这个办法还可以吧？”\n\n张大胖又想了想，这种方式似乎没有漏洞，并且效率很高，Load Balancer只负责把用户请求发给特定的服务器就万事大吉了， 剩下的事由具体的服务器来处理，和它没有关系了。\n\n他高兴地说：“不错，我着手带人去实现了。”\n\n后记：本文所描述的，其实就是著名开源软件LVS的原理，上面讲的两种负载均衡的方式，就是LVS的NAT和DR。LVS是章文嵩博士在1998年5月成立的自由软件项目，现在已经是Linux内核的一部分。想想那时候我还在不亦乐乎地折腾个人网页，学会安装和使用Linux 没多久 , 服务器端开发也仅限于ASP，像LVS这种负载均衡的概念压根就没有听说过。编程语言可以学，差距也能弥补，但是这种境界和眼光的差距，简直就是巨大的鸿沟，难以跨越啊！读故事笔记：关于LVS的文章也读过几篇，往往只是记住了概念，不能设身处地的思考为何而来，刘欣老师每每都能以人物设定的场景，让我再次回到那个年代去思考、推演，还原当时如何一步步的演进成后来的LVS。\n\n本人也混迹软件开发十几年，多数时间都是做着行业领域的软件开发，自我安慰是做着xx行业与计算机行业的交叉领域，实则一直未能深入计算机系统领域。行业应用软件开发，行业知识本身就牵扯了太多了精力，软件开发更多选择一种合适的架构来完成系统的设计、开发和维护。如你要成为一个计算机高手，有机会还是应当就计算机某一个领域深入研究，如Linux内核、搜索、图形图像、数据库、分布式存储，当然还有人工智能等等。\n\n分布式架构实践——负载均衡\n\n也许当我老了，也一样写代码；不为别的，只为了爱好。\n\n# 1 什么是负载均衡（Load balancing）\n\n在网站创立初期，我们一般都使用单台机器对台提供集中式服务，但是随着业务量越来越大，无论是性能上还是稳定性上都有了更大的挑战。这时候我们就会想到通过扩容的方式来提供更好的服务。我们一般会把多台机器组成一个集群对外提供服务。然而，我们的网站对外提供的访问入口都是一个的，比如[www.taobao.com](https://www.cnblogs.com/itxiaok/archive/2019/02/08/www.taobao.com)。那么当用户在浏览器输入[www.taobao.com](https://www.cnblogs.com/itxiaok/archive/2019/02/08/www.taobao.com)的时候如何**将用户的请求分发到集群中不同的机器上**呢，这就是负载均衡在做的事情。\n\n当前大多数的互联网系统都使用了服务器集群技术，集群即**将相同服务部署在多台服务器上构成一个集群整体对外提供服务**，这些集群可以是Web应用服务器集群，也可以是数据库服务器集群，还可以是分布式缓存服务器集群等等。\n\n在实际应用中，在Web服务器集群之前总会有一台负载均衡服务器，负载均衡设备的任务就是作为Web服务器流量的入口，挑选最合适的一台Web服务器，将客户端的请求转发给它处理，实现客户端到真实服务端的透明转发。最近几年很火的「云计算」以及分布式架构，本质上也是将后端服务器作为计算资源、存储资源，由某台管理服务器封装成一个服务对外提供，客户端不需要关心真正提供服务的是哪台机器，在它看来，就好像它面对的是一台拥有近乎无限能力的服务器，而本质上，真正提供服务的，是后端的集群。软件负载解决的两个核心问题是：**选谁、转发**，其中最著名的是LVS（Linux Virtual Server）。\n\n![img](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1845730-5f905fa709e1df07.png)\n\n一个典型的互联网应用的拓扑结构是这样的：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1845730-bd11059c51ee2830.png)\n\n# 2 负载均衡分类\n\n现在我们知道，负载均衡就是一种计算机网络技术，用来在多个计算机（计算机集群）、网络连接、CPU、磁碟驱动器或其他资源中分配负载，以达到最佳化资源使用、最大化吞吐率、最小化响应时间、同时避免过载的目的。那么，这种计算机技术的实现方式有多种。大致可以分为以下几种，其中最常用的是**四层和七层负载均衡**：\n\n**二层负载均衡**负载均衡服务器对外依然**提供一个VIP（虚IP）**，集群中不同的机器采用**相同IP地址**，但是机器的**MAC地址不一样**。当负载均衡服务器接受到请求之后，通过**改写报文的目标MAC地址**的方式将请求转发到目标机器实现负载均衡。\n\n**三层负载均衡**和二层负载均衡类似，负载均衡服务器对外依然提供一个VIP（虚IP），但是集群中不同的机器**采用不同的IP地址**。当负载均衡服务器接受到请求之后，根据不同的**负载均衡算法**，通过IP将请求转发至不同的真实服务器。\n\n**四层负载均衡**四层负载均衡工作在OSI模型的**传输层**，由于在传输层，只有TCP/UDP协议，这两种协议中除了包含源IP、目标IP以外，还包含源端口号及目的端口号。四层负载均衡服务器在接受到客户端请求后，以后通过**修改数据包的地址信息（IP+端口号）**将流量转发到应用服务器。\n\n**七层负载均衡**七层负载均衡工作在OSI模型的**应用层**，应用层协议较多，常用**http、radius、dns**等。**七层负载就可以基于这些协议来负载**。这些应用层协议中会包含很多有意义的内容。比如同一个Web服务器的负载均衡，除了**根据IP加端口进行负载外，还可根据七层的URL、浏览器类别、语言来决定是否要进行负载均衡**。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1845730-9c384abf51199902.png)\n\n对于一般的应用来说，有了Nginx就够了。Nginx可以用于七层负载均衡。但是对于一些大的网站，一般会采用DNS+四层负载+七层负载的方式进行多层次负载均衡。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1845730-9343234e816150c4.png)\n\n# 3 常用负载均衡工具\n\n硬件负载均衡性能优越，功能全面，但是价格昂贵，一般适合初期或者土豪级公司长期使用。因此软件负载均衡在互联网领域大量使用。常用的软件负载均衡软件有Nginx，Lvs，HaProxy等。Nginx/LVS/HAProxy是目前使用最广泛的三种负载均衡软件。\n\n## 3.1 LVS\n\nLVS（Linux Virtual Server），也就是Linux虚拟服务器, 是一个由章文嵩博士发起的自由软件项目。使用LVS技术要达到的目标是：通过LVS提供的负载均衡技术和Linux操作系统实现一个高性能、高可用的服务器群集，它具有良好可靠性、可扩展性和可操作性。从而以低廉的成本实现最优的服务性能。LVS主要用来做**四层负载均衡。**\n\n**LVS架构**LVS架设的服务器集群系统有三个部分组成：最前端的负载均衡层（Loader Balancer），中间的服务器群组层，用Server Array表示，最底层的数据共享存储层，用Shared Storage表示。在用户看来所有的应用都是透明的，用户只是在使用一个虚拟服务器提供的高性能服务。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1845730-501f50d9626ed5c3.png)\n\nLVS的体系架构.png\n\nLVS的各个层次的详细介绍：\n\nLoad Balancer层：位于整个集群系统的最前端，由一**台或者多台负载调度器**（Director Server）组成，LVS模块就安装在Director Server上，而**Director的主要作用类似于一个路由器**，它含有完成LVS功能所设定的路由表，通过这些路由表把用户的请求分发给Server Array层的应用服务器（Real Server）上。同时，在Director Server上还要安装对Real Server服务的监控模块Ldirectord，此模块用于监测各个Real Server服务的健康状况。在Real Server不可用时把它从LVS路由表中剔除，恢复时重新加入。\n\nServer Array层：由**一组实际运行应用服务的机器组成**，Real Server可以是WEB服务器、MAIL服务器、FTP服务器、DNS服务器、视频服务器中的一个或者多个，每个Real Server之间通过高速的LAN或分布在各地的WAN相连接。在实际的应用中，Director Server也可以同时兼任Real Server的角色。\n\nShared Storage层：是为**所有Real Server提供共享存储空间和内容一致性的存储区域**，在物理上，一般有磁盘阵列设备组成，为了提供内容的一致性，一般可以通过NFS网络文件系统共享数 据，但是NFS在繁忙的业务系统中，性能并不是很好，此时可以采用集群文件系统，例如Red hat的GFS文件系统，oracle提供的OCFS2文件系统等。\n\n从整个LVS结构可以看出，**Director Server是整个LVS的核心**，目前，用于Director Server的操作系统只能是Linux和FreeBSD，linux2.6内核不用任何设置就可以支持LVS功能，而FreeBSD作为 Director Server的应用还不是很多，性能也不是很好。对于Real Server，几乎可以是所有的系统平台，Linux、windows、Solaris、AIX、BSD系列都能很好的支持。\n\n## 3.2 Nginx\n\nNginx（发音同engine x）是一个网页服务器，它能反向代理HTTP, HTTPS, SMTP, POP3, IMAP的协议链接，以及一个负载均衡器和一个HTTP缓存。Nginx主要用来做**七层负载均衡**。并发性能：官方支持每秒**5万**并发，实际国内一般到每秒2万并发，有优化到每秒10万并发的。具体性能看应用场景。\n\n特点\n\n模块化设计：良好的扩展性，可以通过模块方式进行功能扩展。高可靠性：主控进程和worker是同步实现的，一个worker出现问题，会立刻启动另一个worker。内存消耗低：一万个长连接（keep-alive）,仅消耗2.5MB内存。支持热部署：不用停止服务器，实现更新配置文件，更换日志文件、更新服务器程序版本。并发能力强：官方数据每秒支持5万并发；功能丰富：优秀的反向代理功能和灵活的负载均衡策略Nginx的基本工作模式\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1845730-82b386d1049c5348.jpg)\n\nNginx的基本工作模式.jpg\n\n一个master进程，生成一个或者多个worker进程。但是这里**master是使用root身份启动的**，因为nginx要工作在80端口。而只有管理员才有权限启动小于低于1023的端口。**master主要是负责的作用只是启动worker，加载配置文件，负责系统的平滑升级**。其它的工作是交给worker。那么当worker被启动之后，也只是负责一些web最简单的工作，而其他的工作都是由worker中调用的模块来实现的。**模块之间是以流水线的方式实现功能的**。流水线，指的是一个用户请求，由多个模块组合各自的功能依次实现完成的。比如：第一个模块只负责分析请求首部，第二个模块只负责查找数据，第三个模块只负责压缩数据，依次完成各自工作。来**实现整个工作的完成**。他们是如何实现热部署的呢？其实是这样的，我们前面说master不负责具体的工作，而是调用worker工作，他只是负责读取配置文件，因此当一个模块修改或者配置文件发生变化，是由master进行读取，因此此时不会影响到worker工作**。在master进行读取配置文件之后，不会立即把修改的配置文件告知worker。而是让被修改的worker继续使用老的配置文件工作，当worker工作完毕之后，直接杀死这个子进程，更换新的子进程，使用新的规则。**\n\n## 3.3 HAProxy\n\nHAProxy也是使用较多的一款负载均衡软件。HAProxy提供高可用性、负载均衡以及基于TCP和HTTP应用的代理，支持虚拟主机，是免费、快速并且可靠的一种解决方案。特别适用于那些**负载特大的web站点。**运行模式使得它可以很简单安全的整合到当前的架构中，同时可以保护你的web服务器不被暴露到网络上。HAProxy是一个使用C语言编写的自由及开放源代码软件，其提供高可用性、负载均衡，以及基于TCP和HTTP的应用程序代理。Haproxy主要用来做七层负载均衡。\n\n# 4 常见负载均衡算法\n\n上面介绍负载均衡技术的时候提到过，负载均衡服务器在决定将请求转发到具体哪台真实服务器的时候，是通过负载均衡算法来实现的。负载均衡算法可以分为两类：静态负载均衡算法和动态负载均衡算法。\n\n静态负载均衡算法包括：**轮询，比率，优先权**\n\n动态负载均衡算法包括: 最少连接数,最快响应速度，观察方法，预测法，动态性能分配，动态服务器补充，服务质量，服务类型，规则模式。\n\n**轮询（Round Robin）**：顺序循环将请求一次顺序循环地连接每个服务器。当其中某个服务器发生第二到第7 层的故障，BIG-IP 就把其从顺序循环队列中拿出，不参加下一次的轮询，直到其恢复正常。以轮询的方式依次请求调度不同的服务器； 实现时，一般为服务器带上权重；这样有两个好处：针对服务器的性能差异可分配不同的负载；当需要将某个结点剔除时，只需要将其权重设置为0即可；优点：实现简单、高效；易水平扩展；缺点：请求到目的结点的不确定，造成其无法适用于有写的场景（缓存，数据库写）应用场景：数据库或应用服务层中只有读的场景；随机方式：请求随机分布到各个结点；在数据足够大的场景能达到一个均衡分布；优点：实现简单、易水平扩展；缺点：同Round Robin，无法用于有写的场景；**应用场景：数据库负载均衡，也是只有读的场景**；\n\n**哈希方式**：根据key来计算需要落在的结点上，可以保证一个同一个键一定落在相同的服务器上；优点：相同key一定落在同一个结点上，这样就可用于有写有读的缓存场景；缺点：在某个结点故障后，会导致哈希键重新分布，造成命中率大幅度下降；解决：一致性哈希 or 使用keepalived保证任何一个结点的高可用性，故障后会有其它结点顶上来；应用场景：缓存，有读有写；\n\n**一致性哈希**：在服务器一个结点出现故障时，受影响的只有这个结点上的key，最大程度的保证命中率； 如twemproxy中的ketama方案； 生产实现中还可以规划指定子key哈希，从而保证局部相似特征的键能分布在同一个服务器上；优点：结点故障后命中率下降有限；应用场景：缓存；\n\n**根据键的范围来负载**：根据键的范围来负载，前1亿个键都存放到第一个服务器，1~2亿在第二个结点；优点：水平扩展容易，存储不够用时，加服务器存放后续新增数据；缺点：负载不均；数据库的分布不均衡；（数据有冷热区分，一般最近注册的用户更加活跃，这样造成后续的服务器非常繁忙，而前期的结点空闲很多）**适用场景：数据库分片负载均衡；**\n\n**根据键对服务器结点数取模来负载**：根据键对服务器结点数取模来负载；比如有4台服务器，key取模为0的落在第一个结点，1落在第二个结点上。优点：数据冷热分布均衡，数据库结点负载均衡分布；缺点：水平扩展较难；适用场景：**数据库分片负载均衡**；\n\n**纯动态结点负载均衡**：根据CPU、IO、网络的处理能力来决策接下来的请求如何调度；优点：充分利用服务器的资源，保证个结点上负载处理均衡；缺点：实现起来复杂，真实使用较少；\n\n**不用主动负载均衡**：使用消息队列转为异步模型，将负载均衡的问题消灭；负载均衡是一种推模型，一直向你发数据，那么，将所有的用户请求发到消息队列中，**所有的下游结点谁空闲**，谁上来取数据处理；转为拉模型之后，消除了对下行结点负载的问题；优点：通过消息队列的缓冲，保护后端系统，请求剧增时不会冲垮后端服务器；水平扩展容易，加入新结点后，直接取queue即可；缺点：不具有实时性；应用场景：**不需要实时返回的场景**；比如，12036下订单后，立刻返回提示信息：您的订单进去排队了...等处理完毕后，再异步通知；\n\n**比率（Ratio）**：给每个服务器分配一个**加权值**为比例，根椐这个比例，把用户的请求分配到每个服务器。当其中某个服务器发生第二到第7 层的故障，BIG-IP 就把其从服务器队列中拿出，不参加下一次的用户请求的分配, 直到其恢复正常。\n\n**优先权（Priority）**：给所有服务器分组,给每个组定义优先权，BIG-IP 用户的请求，分配给优先级最高的服务器组（在同一组内，采用轮询或比率算法，分配用户的请求）；当最高优先级中所有服务器出现故障，BIG-IP 才将请求送给次优先级的服务器组。这种方式，实际为用户提供一种热备份的方式。\n\n**最少的连接方式（Least Connection）**：传递新的连接给那些进行**最少连接**处理的服务器。当其中某个服务器发生第二到第7 层的故障，BIG-IP 就把其从服务器队列中拿出，不参加下一次的用户请求的分配, 直到其恢复正常。\n\n**最快模式（Fastest）**：传递连接给那些**响应最快的服务器**。当其中某个服务器发生第二到第7 层的故障，BIG-IP 就把其从服务器队列中拿出，不参加下一次的用户请求的分配，直到其恢复正常。\n\n**观察模式（Observed）**：连接数目和响应时间以这两项的最佳平衡为依据为新的请求选择服务器。当其中某个服务器发生第二到第7 层的故障，BIG-IP就把其从服务器队列中拿出，不参加下一次的用户请求的分配，直到其恢复正常。\n\n**预测模式（Predictive）**：BIG-IP利用收集到的服务器当前的性能指标，进行预测分析，选择一台服务器在下一个时间片内，其性能将达到最佳的服务器相应用户的请求。(被BIG-IP 进行检测)\n\n**动态性能分配(Dynamic Ratio-APM):**BIG-IP 收集到的应用程序和应用服务器的**各项性能参数**，动态调整流量分配。\n\n**动态服务器补充(Dynamic Server Act.)**:当主服务器群中因故障导致数量减少时，动态地将备份服务器补充至主服务器群。\n\n**服务质量(QoS）**:按**不同的优先级**对数据流进行分配。\n\n**服务类型(ToS)**: 按不**同的服务类型**（在Type of Field中标识）负载均衡对数据流进行分配。\n\n**规则模式**：针对不同的数据流设置导向**规则**，用户可自行。\n\n# 负载均衡的几种算法Java实现代码\n\n## 轮询\n````\n package com.boer.tdf.act.test;\n import java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Set;\n /**\n * 負載均衡算法，輪詢法.\n */\npublic class TestRoundRobin {\n static Map<String,Integer> serverWeigthMap = new HashMap<String,Integer>();\n static {\n serverWeigthMap.put(\"192.168.1.12\", 1);\n serverWeigthMap.put(\"192.168.1.13\", 1);\n serverWeigthMap.put(\"192.168.1.14\", 2);\n serverWeigthMap.put(\"192.168.1.15\", 2);\n serverWeigthMap.put(\"192.168.1.16\", 3);\n serverWeigthMap.put(\"192.168.1.17\", 3);\n serverWeigthMap.put(\"192.168.1.18\", 1);\n serverWeigthMap.put(\"192.168.1.19\", 2);\n }\n Integer pos = 0;\n public String roundRobin() {\n // 重新建立一個map,避免出現由於服務器上線和下線導致的並發問題\n Map<String,Integer> serverMap = new HashMap<String,Integer>();\n serverMap.putAll(serverWeigthMap);\n // 獲取ip列表list\n Set<String> keySet = serverMap.keySet();\n ArrayList<String> keyList = new ArrayList<String>();\n keyList.addAll(keySet);\n String server = null;\n synchronized (pos) {\n if(pos >=keySet.size()){\n pos = 0;\n }\n server = keyList.get(pos);\n pos ++;\n }\n return server;\n }\n public static void main(String[] args) {\n TestRoundRobin robin = new TestRoundRobin();\n for (int i = 0; i < 20; i++) {\n String serverIp = robin.roundRobin();\n System.out.println(serverIp);\n }\n }\n /**\n * 运行结果:\n * 192.168.1.12\n 192.168.1.14\n 192.168.1.13\n 192.168.1.16\n 192.168.1.15\n 192.168.1.18\n 192.168.1.17\n 192.168.1.19\n 192.168.1.12\n 192.168.1.14\n 192.168.1.13\n 192.168.1.16\n 192.168.1.15\n 192.168.1.18\n 192.168.1.17\n 192.168.1.19\n 192.168.1.12\n 192.168.1.14\n 192.168.1.13\n 192.168.1.16\n */\n }\n\n````\n\n## 加权随机负载均衡算法\n````\n package com.boer.tdf.act.test;\n import java.util.*;\n /**\n * 加权随机负载均衡算法.\n */\npublic class TestWeightRandom {\n static Map<String,Integer> serverWeigthMap = new HashMap<String,Integer>();\n static {\n serverWeigthMap.put(\"192.168.1.12\", 1);\n serverWeigthMap.put(\"192.168.1.13\", 1);\n serverWeigthMap.put(\"192.168.1.14\", 2);\n serverWeigthMap.put(\"192.168.1.15\", 2);\n serverWeigthMap.put(\"192.168.1.16\", 3);\n serverWeigthMap.put(\"192.168.1.17\", 3);\n serverWeigthMap.put(\"192.168.1.18\", 1);\n serverWeigthMap.put(\"192.168.1.19\", 2);\n }\n public static String weightRandom()\n {\n // 重新建立一個map,避免出現由於服務器上線和下線導致的並發問題\n Map<String,Integer> serverMap = new HashMap<String,Integer>();\n serverMap.putAll(serverWeigthMap);\n // 獲取ip列表list\n Set<String> keySet = serverMap.keySet();\n Iterator<String> it = keySet.iterator();\n List<String> serverList = new ArrayList<String>();\n while (it.hasNext()) {\n String server = it.next();\n Integer weight = serverMap.get(server);\n for (int i = 0; i < weight; i++) {\n serverList.add(server);\n }\n }\n Random random = new Random();\n int randomPos = random.nextInt(serverList.size());\n String server = serverList.get(randomPos);\n return server;\n }\n public static void main(String[] args) {\n String serverIp = weightRandom();\n System.out.println(serverIp);\n /**\n * 运行结果:\n * 192.168.1.16\n */\n }\n }\n````\n````\n\n## 随机负载均衡算法\n````\n package com.boer.tdf.act.test;\n import java.util.*;\n /**\n * 随机负载均衡算法.\n */\npublic class TestRandom {\n static Map<String,Integer> serverWeigthMap = new HashMap<String,Integer>();\n static {\n serverWeigthMap.put(\"192.168.1.12\", 1);\n serverWeigthMap.put(\"192.168.1.13\", 1);\n serverWeigthMap.put(\"192.168.1.14\", 2);\n serverWeigthMap.put(\"192.168.1.15\", 2);\n serverWeigthMap.put(\"192.168.1.16\", 3);\n serverWeigthMap.put(\"192.168.1.17\", 3);\n serverWeigthMap.put(\"192.168.1.18\", 1);\n serverWeigthMap.put(\"192.168.1.19\", 2);\n }\n public static String random() {\n // 重新建立一個map,避免出現由於服務器上線和下線導致的並發問題\n Map<String,Integer> serverMap = new HashMap<String,Integer>();\n serverMap.putAll(serverWeigthMap);\n // 獲取ip列表list\n Set<String> keySet = serverMap.keySet();\n ArrayList<String> keyList = new ArrayList<String>();\n keyList.addAll(keySet);\n Random random = new Random();\n int randomPos = random.nextInt(keyList.size());\n String server = keyList.get(randomPos);\n return server;\n }\n public static void main(String[] args) {\n String serverIp = random();\n System.out.println(serverIp);\n }\n /**\n * 运行结果:\n * 192.168.1.16\n */\n }\n````\n\n\n## 负载均衡 ip_hash算法\n````\n package com.boer.tdf.act.test;\n import java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Set;\n /**\n * 负载均衡 ip_hash算法.\n */\npublic class TestIpHash {\n static Map<String,Integer> serverWeigthMap = new HashMap<String,Integer>();\n static {\n serverWeigthMap.put(\"192.168.1.12\", 1);\n serverWeigthMap.put(\"192.168.1.13\", 1);\n serverWeigthMap.put(\"192.168.1.14\", 2);\n serverWeigthMap.put(\"192.168.1.15\", 2);\n serverWeigthMap.put(\"192.168.1.16\", 3);\n serverWeigthMap.put(\"192.168.1.17\", 3);\n serverWeigthMap.put(\"192.168.1.18\", 1);\n serverWeigthMap.put(\"192.168.1.19\", 2);\n }\n /**\n * 获取请求服务器地址\n * @param remoteIp 负载均衡服务器ip\n * @return\n */\n public static String ipHash(String remoteIp) {\n // 重新建立一個map,避免出現由於服務器上線和下線導致的並發問題\n Map<String,Integer> serverMap = new HashMap<String,Integer>();\n serverMap.putAll(serverWeigthMap);\n // 獲取ip列表list\n Set<String> keySet = serverMap.keySet();\n ArrayList<String> keyList = new ArrayList<String>();\n keyList.addAll(keySet);\n int hashCode =remoteIp.hashCode();\n int serverListSize = keyList.size();\n int serverPos = hashCode % serverListSize;\n return keyList.get(serverPos);\n }\n public static void main(String[] args) {\n String serverIp = ipHash(\"192.168.1.12\");\n System.out.println(serverIp);\n /**\n * 运行结果:\n * 192.168.1.18\n */\n }\n }\n\n````\n"
  },
  {
    "path": "docs/distributed/practice/搞懂分布式技术：SpringBoot使用注解集成Redis缓存.md",
    "content": "\n\n# 目录\n\n* [Redis配置](#redis配置)\n* [连接超时时间（毫秒）](#连接超时时间（毫秒）)\n    * [一、创建 Caching 配置类](#一、创建-caching-配置类)\n    * [二、创建需要缓存数据的类](#二、创建需要缓存数据的类)\n    * [三、测试方法](#三、测试方法)\n\n本文内容参考网络，侵删\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本文也将同步到我的个人博客：\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n该系列博文会告诉你什么是分布式系统，这对后端工程师来说是很重要的一门学问，我们会逐步了解常见的分布式技术、以及一些较为常见的分布式系统概念，同时也需要进一步了解zookeeper、分布式事务、分布式锁、负载均衡等技术，以便让你更完整地了解分布式技术的具体实战方法，为真正应用分布式技术做好准备。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n本文转载自 linkedkeeper.com\n\nSpring Boot 熟悉后，集成一个外部扩展是一件很容易的事，集成Redis也很简单，看下面步骤配置：\n\n一、添加pom依赖\n\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-redis</artifactId>\n        </dependency>\n\n二、创建 RedisClient.java\n注意该类存放的package\n````\n    package org.springframework.data.redis.connection.jedis;\n    \n    import java.io.ByteArrayInputStream;\n    import java.io.ByteArrayOutputStream;\n    import java.io.IOException;\n    import java.io.ObjectInputStream;\n    import java.io.ObjectOutputStream;\n    import java.io.UnsupportedEncodingException;\n    \n    import org.apache.commons.lang3.StringUtils;\n    import org.slf4j.Logger;\n    import org.slf4j.LoggerFactory;\n    \n    import redis.clients.jedis.Jedis;\n    import redis.clients.jedis.Protocol;\n    import redis.clients.jedis.exceptions.JedisException;\n    \n    /**\n     * 工具类 RedisClient\n     * 因为本类中获取JedisPool调用的是JedisConnectionFactory中protected修饰的方法fetchJedisConnector()\n     * 所以该类需要与JedisConnectionFactory在同一个package中\n     *\n     * @author 单红宇(CSDN CATOOP)\n     * @create 2017年4月9日\n     */\n    public class RedisClient {\n    \n        private static Logger logger = LoggerFactory.getLogger(RedisClient.class);\n    \n        private JedisConnectionFactory factory;\n    \n        public RedisClient(JedisConnectionFactory factory) {\n            super();\n            this.factory = factory;\n        }\n    \n        /**\n         * put操作（存储序列化对象）+ 生效时间\n         * \n         * @param key\n         * @param value\n         * @return\n         */\n        public void putObject(final String key, final Object value, final int cacheSeconds) {\n            if (StringUtils.isNotBlank(key)) {\n                redisTemplete(key, new RedisExecute<Object>() {\n                    @Override\n                    public Object doInvoker(Jedis jedis) {\n                        try {\n                            jedis.setex(key.getBytes(Protocol.CHARSET), cacheSeconds, serialize(value));\n                        } catch (UnsupportedEncodingException e) {\n                        }\n    \n                        return null;\n                    }\n                });\n            }\n        }\n    \n        /**\n         * get操作（获取序列化对象）\n         * \n         * @param key\n         * @return\n         */\n        public Object getObject(final String key) {\n            return redisTemplete(key, new RedisExecute<Object>() {\n                @Override\n                public Object doInvoker(Jedis jedis) {\n                    try {\n                        byte[] byteKey = key.getBytes(Protocol.CHARSET);\n                        byte[] byteValue = jedis.get(byteKey);\n                        if (byteValue != null) {\n                            return deserialize(byteValue);\n                        }\n                    } catch (UnsupportedEncodingException e) {\n                        return null;\n                    }\n                    return null;\n                }\n            });\n        }\n    \n        /**\n         * setex操作\n         * \n         * @param key\n         *            键\n         * @param value\n         *            值\n         * @param cacheSeconds\n         *            超时时间，0为不超时\n         * @return\n         */\n        public String set(final String key, final String value, final int cacheSeconds) {\n            return redisTemplete(key, new RedisExecute<String>() {\n                @Override\n                public String doInvoker(Jedis jedis) {\n                    if (cacheSeconds == 0) {\n                        return jedis.set(key, value);\n                    }\n                    return jedis.setex(key, cacheSeconds, value);\n                }\n            });\n        }\n    \n        /**\n         * get操作\n         * \n         * @param key\n         *            键\n         * @return 值\n         */\n        public String get(final String key) {\n            return redisTemplete(key, new RedisExecute<String>() {\n                @Override\n                public String doInvoker(Jedis jedis) {\n                    String value = jedis.get(key);\n                    return StringUtils.isNotBlank(value) && !\"nil\".equalsIgnoreCase(value) ? value : null;\n                }\n            });\n        }\n    \n        /**\n         * del操作\n         * \n         * @param key\n         *            键\n         * @return\n         */\n        public long del(final String key) {\n            return redisTemplete(key, new RedisExecute<Long>() {\n                @Override\n                public Long doInvoker(Jedis jedis) {\n                    return jedis.del(key);\n                }\n            });\n        }\n    \n        /**\n         * 获取资源\n         * \n         * @return\n         * @throws JedisException\n         */\n        public Jedis getResource() throws JedisException {\n            Jedis jedis = null;\n            try {\n                jedis = factory.fetchJedisConnector();\n            } catch (JedisException e) {\n                logger.error(\"getResource.\", e);\n                returnBrokenResource(jedis);\n                throw e;\n            }\n            return jedis;\n        }\n    \n        /**\n         * 获取资源\n         * \n         * @return\n         * @throws JedisException\n         */\n        public Jedis getJedis() throws JedisException {\n            return getResource();\n        }\n    \n        /**\n         * 归还资源\n         * \n         * @param jedis\n         * @param isBroken\n         */\n        public void returnBrokenResource(Jedis jedis) {\n            if (jedis != null) {\n                jedis.close();\n            }\n        }\n    \n        /**\n         * 释放资源\n         * \n         * @param jedis\n         * @param isBroken\n         */\n        public void returnResource(Jedis jedis) {\n            if (jedis != null) {\n                jedis.close();\n            }\n        }\n    \n        /**\n         * 操作jedis客户端模板\n         * \n         * @param key\n         * @param execute\n         * @return\n         */\n        public <R> R redisTemplete(String key, RedisExecute<R> execute) {\n            Jedis jedis = null;\n            try {\n                jedis = getResource();\n                if (jedis == null) {\n                    return null;\n                }\n    \n                return execute.doInvoker(jedis);\n            } catch (Exception e) {\n                logger.error(\"operator redis api fail,{}\", key, e);\n            } finally {\n                returnResource(jedis);\n            }\n            return null;\n        }\n    \n        /**\n         * 功能简述: 对实体Bean进行序列化操作.\n         * \n         * @param source\n         *            待转换的实体\n         * @return 转换之后的字节数组\n         * @throws Exception\n         */\n        public static byte[] serialize(Object source) {\n            ByteArrayOutputStream byteOut = null;\n            ObjectOutputStream ObjOut = null;\n            try {\n                byteOut = new ByteArrayOutputStream();\n                ObjOut = new ObjectOutputStream(byteOut);\n                ObjOut.writeObject(source);\n                ObjOut.flush();\n            } catch (IOException e) {\n                e.printStackTrace();\n            } finally {\n                try {\n                    if (null != ObjOut) {\n                        ObjOut.close();\n                    }\n                } catch (IOException e) {\n                    ObjOut = null;\n                }\n            }\n            return byteOut.toByteArray();\n        }\n    \n        /**\n         * 功能简述: 将字节数组反序列化为实体Bean.\n         * \n         * @param source\n         *            需要进行反序列化的字节数组\n         * @return 反序列化后的实体Bean\n         * @throws Exception\n         */\n        public static Object deserialize(byte[] source) {\n            ObjectInputStream ObjIn = null;\n            Object retVal = null;\n            try {\n                ByteArrayInputStream byteIn = new ByteArrayInputStream(source);\n                ObjIn = new ObjectInputStream(byteIn);\n                retVal = ObjIn.readObject();\n            } catch (Exception e) {\n                e.printStackTrace();\n            } finally {\n                try {\n                    if (null != ObjIn) {\n                        ObjIn.close();\n                    }\n                } catch (IOException e) {\n                    ObjIn = null;\n                }\n            }\n            return retVal;\n        }\n    \n        interface RedisExecute<T> {\n            T doInvoker(Jedis jedis);\n        }\n    }\n````\n三、创建Redis配置类\n\n    RedisConfig.java\n````    \n    package com.shanhy.example.redis;\n    \n    import org.springframework.context.annotation.Bean;\n    import org.springframework.context.annotation.Configuration;\n    import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;\n    import org.springframework.data.redis.connection.jedis.RedisClient;\n    import org.springframework.data.redis.core.RedisTemplate;\n    import org.springframework.data.redis.serializer.StringRedisSerializer;\n    \n    /**\n     * Redis配置\n     * \n     * @author 单红宇(CSDN catoop)\n     * @create 2016年9月12日\n     */\n    @Configuration\n    public class RedisConfig {\n    \n        @Bean\n        public RedisTemplate<String, Object> redisTemplate(JedisConnectionFactory factory) {\n            RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();\n            template.setConnectionFactory(factory);\n            template.setKeySerializer(new StringRedisSerializer());\n            template.setValueSerializer(new RedisObjectSerializer());\n            template.afterPropertiesSet();\n            return template;\n        }\n    \n        @Bean\n        public RedisClient redisClient(JedisConnectionFactory factory){\n            return new RedisClient(factory);\n        }\n    }\n\n    RedisObjectSerializer.java\n    \n    package com.shanhy.example.redis;\n    \n    import org.springframework.core.convert.converter.Converter;\n    import org.springframework.core.serializer.support.DeserializingConverter;\n    import org.springframework.core.serializer.support.SerializingConverter;\n    import org.springframework.data.redis.serializer.RedisSerializer;\n    import org.springframework.data.redis.serializer.SerializationException;\n\n    /**\n     * 实现对象的序列化接口\n     * @author   单红宇(365384722)\n     * @myblog  http://blog.csdn.net/catoop/\n     * @create    2017年4月9日\n     */\n    public class RedisObjectSerializer implements RedisSerializer<Object> {\n    \n        private Converter<Object, byte[]> serializer = new SerializingConverter();\n        private Converter<byte[], Object> deserializer = new DeserializingConverter();\n    \n        static final byte[] EMPTY_ARRAY = new byte[0];\n    \n        @Override\n        public Object deserialize(byte[] bytes) {\n            if (isEmpty(bytes)) {\n                return null;\n            }\n    \n            try {\n                return deserializer.convert(bytes);\n            } catch (Exception ex) {\n                throw new SerializationException(\"Cannot deserialize\", ex);\n            }\n        }\n    \n        @Override\n        public byte[] serialize(Object object) {\n            if (object == null) {\n                return EMPTY_ARRAY;\n            }\n    \n            try {\n                return serializer.convert(object);\n            } catch (Exception ex) {\n                return EMPTY_ARRAY;\n            }\n        }\n    \n        private boolean isEmpty(byte[] data) {\n            return (data == null || data.length == 0);\n        }\n    \n    }\n````\n四、创建测试方法\n下面代码随便放一个Controller里\n````\n    @Autowired\n    private RedisTemplate<String, Object> redisTemplate;\n\n    /**\n     * 缓存测试\n     *\n     * @return\n     * @author  SHANHY\n     * @create  2016年9月12日\n     */\n    @RequestMapping(\"/redisTest\")\n    public String redisTest() {\n        try {\n            redisTemplate.opsForValue().set(\"test-key\", \"redis测试内容\", 2, TimeUnit.SECONDS);// 缓存有效期2秒\n\n            logger.info(\"从Redis中读取数据：\" + redisTemplate.opsForValue().get(\"test-key\").toString());\n\n            TimeUnit.SECONDS.sleep(3);\n\n            logger.info(\"等待3秒后尝试读取过期的数据：\" + redisTemplate.opsForValue().get(\"test-key\"));\n        } catch (InterruptedException e) {\n            e.printStackTrace();\n        }\n\n        return \"OK\";\n    }\n````\n五、配置文件配置Redis\n    application.yml\n````    \n    spring:\n      # Redis配置\n      redis:\n        host: 192.168.1.101\n        port: 6379\n        password:\n        # 连接超时时间（毫秒）\n        timeout: 10000\n        pool:\n          max-idle: 20\n          min-idle: 5\n          max-active: 20\n          max-wait: 2\n````\n这样就完成了Redis的配置，可以正常使用 redisTemplate 了。\n\n\n### 一、创建 Caching 配置类\n\nRedisKeys.java\n\n```\npackage com.shanhy.example.redis;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport javax.annotation.PostConstruct;\n\nimport org.springframework.stereotype.Component;\n\n/**\n * 方法缓存key常量\n * \n * @author SHANHY\n */\n@Component\npublic class RedisKeys {\n\n    // 测试 begin\n    public static final String _CACHE_TEST = \"_cache_test\";// 缓存key\n    public static final Long _CACHE_TEST_SECOND = 20L;// 缓存时间\n    // 测试 end\n\n    // 根据key设定具体的缓存时间\n    private Map<String, Long> expiresMap = null;\n\n    @PostConstruct\n    public void init(){\n        expiresMap = new HashMap<>();\n        expiresMap.put(_CACHE_TEST, _CACHE_TEST_SECOND);\n    }\n\n    public Map<String, Long> getExpiresMap(){\n        return this.expiresMap;\n    }\n}\n\n```\n\nCachingConfig.java\n\n```\npackage com.shanhy.example.redis;\n\nimport java.lang.reflect.Method;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport org.springframework.cache.CacheManager;\nimport org.springframework.cache.annotation.CachingConfigurerSupport;\nimport org.springframework.cache.annotation.EnableCaching;\nimport org.springframework.cache.interceptor.KeyGenerator;\nimport org.springframework.cache.interceptor.SimpleKeyGenerator;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.data.redis.cache.RedisCacheManager;\nimport org.springframework.data.redis.core.RedisTemplate;\n\n/**\n * 注解式环境管理\n * \n * @author 单红宇(CSDN catoop)\n * @create 2016年9月12日\n */\n@Configuration\n@EnableCaching\npublic class CachingConfig extends CachingConfigurerSupport {\n\n    /**\n     * 在使用@Cacheable时，如果不指定key，则使用找个默认的key生成器生成的key\n     *\n     * @return\n     * \n     * @author 单红宇(CSDN CATOOP)\n     * @create 2017年3月11日\n     */\n    @Override\n    public KeyGenerator keyGenerator() {\n        return new SimpleKeyGenerator() {\n\n            /**\n             * 对参数进行拼接后MD5\n             */\n            @Override\n            public Object generate(Object target, Method method, Object... params) {\n                StringBuilder sb = new StringBuilder();\n                sb.append(target.getClass().getName());\n                sb.append(\".\").append(method.getName());\n\n                StringBuilder paramsSb = new StringBuilder();\n                for (Object param : params) {\n                    // 如果不指定，默认生成包含到键值中\n                    if (param != null) {\n                        paramsSb.append(param.toString());\n                    }\n                }\n\n                if (paramsSb.length() > 0) {\n                    sb.append(\"_\").append(paramsSb);\n                }\n                return sb.toString();\n            }\n\n        };\n\n    }\n\n    /**\n     * 管理缓存\n     *\n     * @param redisTemplate\n     * @return\n     */\n    @Bean\n    public CacheManager cacheManager(RedisTemplate<String, Object> redisTemplate, RedisKeys redisKeys) {\n        RedisCacheManager rcm = new RedisCacheManager(redisTemplate);\n        // 设置缓存默认过期时间（全局的）\n        rcm.setDefaultExpiration(1800);// 30分钟\n\n        // 根据key设定具体的缓存时间，key统一放在常量类RedisKeys中\n        rcm.setExpires(redisKeys.getExpiresMap());\n\n        List<String> cacheNames = new ArrayList<String>(redisKeys.getExpiresMap().keySet());\n        rcm.setCacheNames(cacheNames);\n\n        return rcm;\n    }\n\n}\n\n```\n\n### 二、创建需要缓存数据的类\n\nTestService.java\n\n```\npackage com.shanhy.example.service;\n\nimport org.apache.commons.lang3.RandomStringUtils;\nimport org.springframework.cache.annotation.Cacheable;\nimport org.springframework.stereotype.Service;\n\nimport com.shanhy.example.redis.RedisKeys;\n\n@Service\npublic class TestService {\n\n    /**\n     * 固定key\n     *\n     * @return\n     * @author SHANHY\n     * @create  2017年4月9日\n     */\n    @Cacheable(value = RedisKeys._CACHE_TEST, key = \"'\" + RedisKeys._CACHE_TEST + \"'\")\n    public String testCache() {\n        return RandomStringUtils.randomNumeric(4);\n    }\n\n    /**\n     * 存储在Redis中的key自动生成，生成规则详见CachingConfig.keyGenerator()方法\n     *\n     * @param str1\n     * @param str2\n     * @return\n     * @author SHANHY\n     * @create  2017年4月9日\n     */\n    @Cacheable(value = RedisKeys._CACHE_TEST)\n    public String testCache2(String str1, String str2) {\n        return RandomStringUtils.randomNumeric(4);\n    }\n}\n\n```\n\n说明一下，其中 @Cacheable 中的 value 值是在 CachingConfig的cacheManager 中配置的，那里是为了配置我们的缓存有效时间。其中 methodKeyGenerator 为 CachingConfig 中声明的 KeyGenerator。\n另外，Cache 相关的注解还有几个，大家可以了解下，不过我们常用的就是 @Cacheable，一般情况也可以满足我们的大部分需求了。还有 @Cacheable 也可以配置表达式根据我们传递的参数值判断是否需要缓存。\n注： TestService 中 testCache 中的 mapper.get 大家不用关心，这里面我只是访问了一下数据库而已，你只需要在这里做自己的业务代码即可。\n\n### 三、测试方法\n\n下面代码，随便放一个 Controller 中\n\n```\npackage com.shanhy.example.controller;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.data.redis.connection.jedis.RedisClient;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport com.shanhy.example.service.TestService;\n\n/**\n * 测试Controller\n * \n * @author 单红宇(365384722)\n * @myblog http://blog.csdn.net/catoop/\n * @create 2017年4月9日\n */\n@RestController\n@RequestMapping(\"/test\")\npublic class TestController {\n\n    private static final Logger LOG = LoggerFactory.getLogger(TestController.class);\n\n    @Autowired\n    private RedisClient redisClient;\n\n    @Autowired\n    private TestService testService;\n\n    @GetMapping(\"/redisCache\")\n    public String redisCache() {\n        redisClient.set(\"shanhy\", \"hello,shanhy\", 100);\n        LOG.info(\"getRedisValue = {}\", redisClient.get(\"shanhy\"));\n        testService.testCache2(\"aaa\", \"bbb\");\n        return testService.testCache();\n    }\n}\n\n```\n\n至此完毕！\n\n最后说一下，这个 @Cacheable 基本是可以放在所有方法上的，Controller 的方法上也是可以的（这个我没有测试 ^_^）。\n"
  },
  {
    "path": "docs/distributed/practice/搞懂分布式技术：ZAB协议概述与选主流程详解.md",
    "content": "# 目录\n\n  * [ZAB协议](#zab协议)\n  * [消息广播模式](#消息广播模式)\n  * [崩溃恢复](#崩溃恢复)\n    * [数据同步](#数据同步)\n  * [ZAB协议原理](#zab协议原理)\n  * [Zookeeper设计目标](#zookeeper设计目标)\n* [ZAB与FastLeaderElection选主算法流程详解](#zab与fastleaderelection选主算法流程详解)\n  * [选择机制中的概念](#选择机制中的概念)\n    * [服务器ID](#服务器id)\n    * [数据ID](#数据id)\n    * [逻辑时钟](#逻辑时钟)\n    * [选举状态](#选举状态)\n  * [选举消息内容](#选举消息内容)\n  * [选举流程图](#选举流程图)\n    * [判断是否已经胜出](#判断是否已经胜出)\n  * [选举流程简述](#选举流程简述)\n  * [几种领导选举场景](#几种领导选举场景)\n    * [集群启动领导选举](#集群启动领导选举)\n    * [Follower重启](#follower重启)\n    * [Leader重启](#leader重启)\n* [一致性保证](#一致性保证)\n  * [Commit过的数据不丢失](#commit过的数据不丢失)\n  * [未Commit过的消息对客户端不可见\")未Commit过的消息对客户端不可见](#未commit过的消息对客户端不可见未commit过的消息对客户端不可见)\n* [总结](#总结)\n\n\n本文内容参考网络，侵删\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本文也将同步到我的个人博客：\n\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n该系列博文会告诉你什么是分布式系统，这对后端工程师来说是很重要的一门学问，我们会逐步了解常见的分布式技术、以及一些较为常见的分布式系统概念，同时也需要进一步了解zookeeper、分布式事务、分布式锁、负载均衡等技术，以便让你更完整地了解分布式技术的具体实战方法，为真正应用分布式技术做好准备。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->  \n\n## ZAB协议\n\n1.  ZAB协议是专门为zookeeper实现分布式协调功能而设计。zookeeper主要是根据ZAB协议是实现分布式系统数据一致性。\n2.  zookeeper根据ZAB协议建立了主备模型完成zookeeper集群中数据的同步。这里所说的主备系统架构模型是指，在zookeeper集群中，只有一台leader负责处理外部客户端的事物请求(或写操作)，然后leader服务器将客户端的写操作数据同步到所有的follower节点中。  \n    ![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407210824.png)\n3.  ZAB的协议核心是在整个zookeeper集群中只有一个节点即Leader将客户端的写操作转化为事物(或提议proposal)。Leader节点再数据写完之后，将向所有的follower节点发送数据广播请求(或数据复制)，等待所有的follower节点反馈。在ZAB协议中，只要超过半数follower节点反馈OK，Leader节点就会向所有的follower服务器发送commit消息。即将leader节点上的数据同步到follower节点之上。  \n    ![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407210834.png)\n4.  ZAB协议中主要有两种模式，第一是消息广播模式；第二是崩溃恢复模式\n\n## 消息广播模式\n\n1.  在zookeeper集群中数据副本的传递策略就是采用消息广播模式。zookeeper中数据副本的同步方式与二阶段提交相似但是却又不同。二阶段提交的要求协调者必须等到所有的参与者全部反馈ACK确认消息后，再发送commit消息。要求所有的参与者要么全部成功要么全部失败。二阶段提交会产生严重阻塞问题。\n2.  ZAB协议中Leader等待follower的ACK反馈是指”只要半数以上的follower成功反馈即可，不需要收到全部follower反馈”\n3.  图中展示了消息广播的具体流程图  \n    ![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407210854.png)\n4.  zookeeper中消息广播的具体步骤如下：  \n    4.1\\. 客户端发起一个写操作请求  \n    4.2\\. Leader服务器将客户端的request请求转化为事物proposql提案，同时为每个proposal分配一个全局唯一的ID，即ZXID。  \n    4.3\\. leader服务器与每个follower之间都有一个队列，leader将消息发送到该队列  \n    4.4\\. follower机器从队列中取出消息处理完(写入本地事物日志中)毕后，向leader服务器发送ACK确认。  \n    4.5\\. leader服务器收到半数以上的follower的ACK后，即认为可以发送commit  \n    4.6\\. leader向所有的follower服务器发送commit消息。\n5.  zookeeper采用ZAB协议的核心就是只要有一台服务器提交了proposal，就要确保所有的服务器最终都能正确提交proposal。这也是CAP/BASE最终实现一致性的一个体现。\n6.  leader服务器与每个follower之间都有一个单独的队列进行收发消息，使用队列消息可以做到异步解耦。leader和follower之间只要往队列中发送了消息即可。如果使用同步方式容易引起阻塞。性能上要下降很多。\n\n## 崩溃恢复\n\n1.  zookeeper集群中为保证任何所有进程能够有序的顺序执行，只能是leader服务器接受写请求，即使是follower服务器接受到客户端的请求，也会转发到leader服务器进行处理。\n2.  如果leader服务器发生崩溃，则zab协议要求zookeeper集群进行崩溃恢复和leader服务器选举。\n3.  ZAB协议崩溃恢复要求满足如下2个要求：  \n    3.1.确保已经被leader提交的proposal必须最终被所有的follower服务器提交。  \n    3.2.确保丢弃已经被leader出的但是没有被提交的proposal。\n4.  根据上述要求，新选举出来的leader不能包含未提交的proposal，即新选举的leader必须都是已经提交了的proposal的follower服务器节点。同时，新选举的leader节点中含有最高的ZXID。这样做的好处就是可以避免了leader服务器检查proposal的提交和丢弃工作。\n5.  leader服务器发生崩溃时分为如下场景：  \n    5.1\\. leader在提出proposal时未提交之前崩溃，则经过崩溃恢复之后，新选举的leader一定不能是刚才的leader。因为这个leader存在未提交的proposal。  \n    5.2 leader在发送commit消息之后，崩溃。即消息已经发送到队列中。经过崩溃恢复之后，参与选举的follower服务器(刚才崩溃的leader有可能已经恢复运行，也属于follower节点范畴)中有的节点已经是消费了队列中所有的commit消息。即该follower节点将会被选举为最新的leader。剩下动作就是数据同步过程。\n\n### 数据同步\n\n1.  在zookeeper集群中新的leader选举成功之后，leader会将自身的提交的最大proposal的事物ZXID发送给其他的follower节点。follower节点会根据leader的消息进行回退或者是数据同步操作。最终目的要保证集群中所有节点的数据副本保持一致。\n2.  数据同步完之后，zookeeper集群如何保证新选举的leader分配的ZXID是全局唯一呢？这个就要从ZXID的设计谈起。  \n    2.1 ZXID是一个长度64位的数字，其中低32位是按照数字递增，即每次客户端发起一个proposal,低32位的数字简单加1。高32位是leader周期的epoch编号，至于这个编号如何产生(我也没有搞明白)，每当选举出一个新的leader时，新的leader就从本地事物日志中取出ZXID,然后解析出高32位的epoch编号，进行加1，再将低32位的全部设置为0。这样就保证了每次新选举的leader后，保证了ZXID的唯一性而且是保证递增的。  \n    ![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407210906.png)\n\n## ZAB协议原理\n\n1.  ZAB协议要求每个leader都要经历三个阶段，即发现，同步，广播。\n2.  发现：即要求zookeeper集群必须选择出一个leader进程，同时leader会维护一个follower可用列表。将来客户端可以这follower中的节点进行通信。\n3.  同步：leader要负责将本身的数据与follower完成同步，做到多副本存储。这样也是体现了CAP中高可用和分区容错。follower将队列中未处理完的请求消费完成后，写入本地事物日志中。\n4.  广播：leader可以接受客户端新的proposal请求，将新的proposal请求广播给所有的follower。\n\n## Zookeeper设计目标\n\n1.  zookeeper作为当今最流行的分布式系统应用协调框架，采用zab协议的最大目标就是建立一个高可用可扩展的分布式数据主备系统。即在任何时刻只要leader发生宕机，都能保证分布式系统数据的可靠性和最终一致性。\n2.  深刻理解ZAB协议，才能更好的理解zookeeper对于分布式系统建设的重要性。以及为什么采用zookeeper就能保证分布式系统中数据最终一致性，服务的高可用性。\n\nZab与Paxos  \nZab的作者认为Zab与paxos并不相同，只所以没有采用Paxos是因为Paxos保证不了全序顺序：  \nBecause multiple leaders can propose a value for a given instance two problems arise.  \nFirst, proposals can conflict. Paxos uses ballots to detect and resolve conflicting proposals.  \nSecond, it is not enough to know that a given instance number has been committed, processes must also be able to fi gure out which value has been committed.  \nPaxos算法的确是不关心请求之间的逻辑顺序，而只考虑数据之间的全序，但很少有人直接使用paxos算法，都会经过一定的简化、优化。\n\nPaxos算法优化  \nPaxos算法在出现竞争的情况下，其收敛速度很慢，甚至可能出现活锁的情况，例如当有三个及三个以上的proposer在发送prepare请求后，很难有一个proposer收到半数以上的回复而不断地执行第一阶段的协议。因此，为了避免竞争，加快收敛的速度，在算法中引入了一个Leader这个角色，在正常情况下同时应该最多只能有一个参与者扮演Leader角色，而其它的参与者则扮演Acceptor的角色。  \n在这种优化算法中，只有Leader可以提出议案，从而避免了竞争使得算法能够快速地收敛而趋于一致；而为了保证Leader的健壮性，又引入了Leader选举，再考虑到同步的阶段，渐渐的你会发现对Paxos算法的简化和优化已经和上面介绍的ZAB协议很相似了。\n\n总结  \nGoogle的粗粒度锁服务Chubby的设计开发者Burrows曾经说过：“所有一致性协议本质上要么是Paxos要么是其变体”。这句话还是有一定道理的，ZAB本质上就是Paxos的一种简化形式。\n\n# ZAB与FastLeaderElection选主算法流程详解\n\n这篇主要分析leader的选主机制，zookeeper提供了三种方式：\n\n*   LeaderElection\n*   AuthFastLeaderElection\n*   FastLeaderElection\n\n默认的算法是FastLeaderElection，所以这篇主要分析它的选举机制。\n\n## 选择机制中的概念\n\n### 服务器ID\n\n比如有三台服务器，编号分别是1,2,3。\n\n> 编号越大在选择算法中的权重越大。\n\n### 数据ID\n\n服务器中存放的最大数据ID.\n\n> 值越大说明数据越新，在选举算法中数据越新权重越大。\n\n### 逻辑时钟\n\n或者叫投票的次数，同一轮投票过程中的逻辑时钟值是相同的。每投完一次票这个数据就会增加，然后与接收到的其它服务器返回的投票信息中的数值相比，根据不同的值做出不同的判断。\n\n### 选举状态\n\n*   LOOKING，竞选状态。\n*   FOLLOWING，随从状态，同步leader状态，参与投票。\n*   OBSERVING，观察状态,同步leader状态，不参与投票。\n*   LEADING，领导者状态。\n\n## 选举消息内容\n\n在投票完成后，需要将投票信息发送给集群中的所有服务器，它包含如下内容。\n\n*   服务器ID\n*   数据ID\n*   逻辑时钟\n*   选举状态\n\n## 选举流程图\n\n因为每个服务器都是独立的，在启动时均从初始状态开始参与选举，下面是简易流程图。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/17071-20170220211539679-433574967.jpg)\n\n\n\n下面详细解释一下这个流程：\n\n首先给出几个名词定义：\n\n（1）Serverid：在配置server时，给定的服务器的标示id。\n\n（2）Zxid:服务器在运行时产生的数据id，zxid越大，表示数据越新。\n\n（3）Epoch：选举的轮数，即逻辑时钟。随着选举的轮数++\n\n（4）Server状态：LOOKING,FOLLOWING,OBSERVING,LEADING\n\n步骤：\n\n一、Server刚启动（宕机恢复或者刚启动）准备加入集群，此时读取自身的zxid等信息。\n\n二、所有Server加入集群时都会推荐自己为leader，然后将（leader id 、 zixd 、 epoch）作为广播信息，广播到集群中所有的服务器(Server)。然后等待集群中的服务器返回信息。\n\n三、收到集群中其他服务器返回的信息，此时要分为两类：该服务器处于looking状态，或者其他状态。\n\n（1）服务器处于looking状态\n\n首先判断逻辑时钟 Epoch:  \na)如果接收到Epoch大于自己目前的逻辑时钟（说明自己所保存的逻辑时钟落伍了）。更新本机逻辑时钟Epoch，同时 Clear其他服务发送来的选举数据（这些数据已经OUT了）。然后判断是否需要更新当前自己的选举情况（一开始选择的leader id 是自己）\n\n判断规则rules judging：保存的zxid最大值和leader Serverid来进行判断的。先看数据zxid,数据zxid大者胜出;其次再判断leaderServerid, leader Serverid大者胜出；然后再将自身最新的选举结果(也就是上面提到的三种数据（leader Serverid，Zxid，Epoch）广播给其他server)\n\nb)如果接收到的Epoch小于目前的逻辑时钟。说明对方处于一个比较OUT的选举轮数，这时只需要将自己的 （leader Serverid，Zxid，Epoch）发送给他即可。\n\nc)如果接收到的Epoch等于目前的逻辑时钟。再根据a)中的判断规则，将自身的最新选举结果广播给其他 server。\n\n同时Server还要处理2种情况：\n\na)如果Server接收到了其他所有服务器的选举信息，那么则根据这些选举信息确定自己的状态（Following,Leading），结束Looking，退出选举。\n\nb)即使没有收到所有服务器的选举信息，也可以判断一下根据以上过程之后最新的选举leader是不是得到了超过半数以上服务器的支持，如果是则尝试接受最新数据，倘若没有最新的数据到来，说明大家都已经默认了这个结果,同样也设置角色退出选举过程。\n\n（2）服务器处于其他状态（Following, Leading）\n\na)如果逻辑时钟Epoch相同,将该数据保存到recvset,如果所接收服务器宣称自己是leader,那么将判断是不是有半数以上的服务器选举它,如果是则设置选举状态退出选举过程\n\nb)否则这是一条与当前逻辑时钟不符合的消息，那么说明在另一个选举过程中已经有了选举结果，于是将该选举结果加入到outofelection集合中，再根据outofelection来判断是否可以结束选举,如果可以也是保存逻辑时钟，设置选举状态，退出选举过程。\n\n以上就是FAST选举过程。\n\nZookeeper具体的启动日志如下图所示：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407210921.png)![](https://img-blog.csdn.net/20161028191618720?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)\n\n以上就是我自己配置的Zookeeper选主日志，从一开始LOOKING,然后new election, my id = 1, proposedzxid=0x0 也就是选自己为Leader,之后广播选举并重复之前Fast选主算法，最终确定Leader。\n\n### 判断是否已经胜出\n\n默认是采用投票数大于半数则胜出的逻辑。\n\n## 选举流程简述\n\n目前有5台服务器，每台服务器均没有数据，它们的编号分别是1,2,3,4,5,按编号依次启动，它们的选择举过程如下：\n\n*   服务器1启动，给自己投票，然后发投票信息，由于其它机器还没有启动所以它收不到反馈信息，服务器1的状态一直属于Looking。\n*   服务器2启动，给自己投票，同时与之前启动的服务器1交换结果，由于服务器2的编号大所以服务器2胜出，但此时投票数没有大于半数，所以两个服务器的状态依然是LOOKING。\n*   服务器3启动，给自己投票，同时与之前启动的服务器1,2交换信息，由于服务器3的编号最大所以服务器3胜出，此时投票数正好大于半数，所以服务器3成为领导者，服务器1,2成为小弟。\n*   服务器4启动，给自己投票，同时与之前启动的服务器1,2,3交换信息，尽管服务器4的编号大，但之前服务器3已经胜出，所以服务器4只能成为小弟。\n*   服务器5启动，后面的逻辑同服务器4成为小弟。\n\n## 几种领导选举场景\n\n### 集群启动领导选举\n\n**_初始投票给自己_**\n\n集群刚启动时，所有服务器的logicClock都为1，zxid都为0。\n\n各服务器初始化后，都投票给自己，并将自己的一票存入自己的票箱，如下图所示。\n\n![fsdfsdfsdsdfsdfsfsdf](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/start_election_1.png)\n\n\n在上图中，(1, 1, 0)第一位数代表投出该选票的服务器的logicClock，第二位数代表被推荐的服务器的myid，第三位代表被推荐的服务器的最大的zxid。由于该步骤中所有选票都投给自己，所以第二位的myid即是自己的myid，第三位的zxid即是自己的zxid。\n\n此时各自的票箱中只有自己投给自己的一票。\n\n**_更新选票_**  \n服务器收到外部投票后，进行选票PK，相应更新自己的选票并广播出去，并将合适的选票存入自己的票箱，如下图所示。\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407211056.png)\n\n\n\n\n服务器1收到服务器2的选票（1, 2, 0）和服务器3的选票（1, 3, 0）后，由于所有的logicClock都相等，所有的zxid都相等，因此根据myid判断应该将自己的选票按照服务器3的选票更新为（1, 3, 0），并将自己的票箱全部清空，再将服务器3的选票与自己的选票存入自己的票箱，接着将自己更新后的选票广播出去。此时服务器1票箱内的选票为(1, 3)，(3, 3)。\n\n同理，服务器2收到服务器3的选票后也将自己的选票更新为（1, 3, 0）并存入票箱然后广播。此时服务器2票箱内的选票为(2, 3)，(3, ,3)。\n\n服务器3根据上述规则，无须更新选票，自身的票箱内选票仍为（3, 3）。\n\n服务器1与服务器2更新后的选票广播出去后，由于三个服务器最新选票都相同，最后三者的票箱内都包含三张投给服务器3的选票。\n\n**_根据选票确定角色_**  \n根据上述选票，三个服务器一致认为此时服务器3应该是Leader。因此服务器1和2都进入FOLLOWING状态，而服务器3进入LEADING状态。之后Leader发起并维护与Follower间的心跳。\n\n![Cluster start election step 3](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/start_election_3.png)\n\n\n### Follower重启\n\n**_Follower重启投票给自己_**  \nFollower重启，或者发生网络分区后找不到Leader，会进入LOOKING状态并发起新的一轮投票。\n\n![Follower restart election step 1](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/follower_restart_election_1.png)\n\n**_发现已有Leader后成为Follower_**  \n服务器3收到服务器1的投票后，将自己的状态LEADING以及选票返回给服务器1。服务器2收到服务器1的投票后，将自己的状态FOLLOWING及选票返回给服务器1。此时服务器1知道服务器3是Leader，并且通过服务器2与服务器3的选票可以确定服务器3确实得到了超过半数的选票。因此服务器1进入FOLLOWING状态。\n\n![Follower restart election step 2](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/follower_restart_election_2.png)\n\n\n### Leader重启\n\n**_Follower发起新投票_**  \nLeader（服务器3）宕机后，Follower（服务器1和2）发现Leader不工作了，因此进入LOOKING状态并发起新的一轮投票，并且都将票投给自己。\n\n![Leader restart election step 1](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/leader_restart_election_1.png)\n\n\n**_广播更新选票_**  \n服务器1和2根据外部投票确定是否要更新自身的选票。这里有两种情况\n\n*   服务器1和2的zxid相同。例如在服务器3宕机前服务器1与2完全与之同步。此时选票的更新主要取决于myid的大小\n*   服务器1和2的zxid不同。在旧Leader宕机之前，其所主导的写操作，只需过半服务器确认即可，而不需所有服务器确认。换句话说，服务器1和2可能一个与旧Leader同步（即zxid与之相同）另一个不同步（即zxid比之小）。此时选票的更新主要取决于谁的zxid较大\n\n在上图中，服务器1的zxid为11，而服务器2的zxid为10，因此服务器2将自身选票更新为（3, 1, 11），如下图所示。\n\n![Leader restart election step 2](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/leader_restart_election_2.png)\n\n**_选出新Leader_**  \n经过上一步选票更新后，服务器1与服务器2均将选票投给服务器1，因此服务器2成为Follower，而服务器1成为新的Leader并维护与服务器2的心跳。\n\n![Leader restart election step 3](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/leader_restart_election_3.png)\n\n**_旧Leader恢复后发起选举_**  \n旧的Leader恢复后，进入LOOKING状态并发起新一轮领导选举，并将选票投给自己。此时服务器1会将自己的LEADING状态及选票（3, 1, 11）返回给服务器3，而服务器2将自己的FOLLOWING状态及选票（3, 1, 11）返回给服务器3。如下图所示。\n\n![Leader restart election step 4](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/leader_restart_election_4.png)\n\n**_旧Leader成为Follower_**  \n服务器3了解到Leader为服务器1，且根据选票了解到服务器1确实得到过半服务器的选票，因此自己进入FOLLOWING状态。\n\n![Leader restart election step 5](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/leader_restart_election_5.png)\n\n\n# 一致性保证\n\nZAB协议保证了在Leader选举的过程中，已经被Commit的数据不会丢失，未被Commit的数据对客户端不可见。\n\n## Commit过的数据不丢失\n\n**_Failover前状态_**\n为更好演示Leader Failover过程，本例中共使用5个Zookeeper服务器。A作为Leader，共收到P1、P2、P3三条消息，并且Commit了1和2，且总体顺序为P1、P2、C1、P3、C2。根据顺序性原则，其它Follower收到的消息的顺序肯定与之相同。其中B与A完全同步，C收到P1、P2、C1，D收到P1、P2，E收到P1，如下图所示。\n\n![Leader Failover step 1](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/recovery_1.png)\n\n\n这里要注意\n\n*   由于A没有C3，意味着收到P3的服务器的总个数不会超过一半，也即包含A在内最多只有两台服务器收到P3。在这里A和B收到P3，其它服务器均未收到P3\n*   由于A已写入C1、C2，说明它已经Commit了P1、P2，因此整个集群有超过一半的服务器，即最少三个服务器收到P1、P2。在这里所有服务器都收到了P1，除E外其它服务器也都收到了P2\n\n**_选出新Leader_**  \n旧Leader也即A宕机后，其它服务器根据上述FastLeaderElection算法选出B作为新的Leader。C、D和E成为Follower且以B为Leader后，会主动将自己最大的zxid发送给B，B会将Follower的zxid与自身zxid间的所有被Commit过的消息同步给Follower，如下图所示。\n\n![Leader Failover step 2](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/recovery_2.png)\n\n\n在上图中\n\n*   P1和P2都被A Commit，因此B会通过同步保证P1、P2、C1与C2都存在于C、D和E中\n*   P3由于未被A Commit，同时幸存的所有服务器中P3未存在于大多数据服务器中，因此它不会被同步到其它Follower\n\n**_通知Follower可对外服务_**  \n同步完数据后，B会向D、C和E发送NEWLEADER命令并等待大多数服务器的ACK（下图中D和E已返回ACK，加上B自身，已经占集群的大多数），然后向所有服务器广播UPTODATE命令。收到该命令后的服务器即可对外提供服务。\n\n![Leader Failover step 3](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/recovery_3.png)\n\n\n## 未Commit过的消息对客户端不可见\")未Commit过的消息对客户端不可见\n\n在上例中，P3未被A Commit过，同时因为没有过半的服务器收到P3，因此B也未Commit P3（如果有过半服务器收到P3，即使A未Commit P3，B会主动Commit P3，即C3），所以它不会将P3广播出去。\n\n具体做法是，B在成为Leader后，先判断自身未Commit的消息（本例中即P3）是否存在于大多数服务器中从而决定是否要将其Commit。然后B可得出自身所包含的被Commit过的消息中的最小zxid（记为min_zxid）与最大zxid（记为max_zxid）。C、D和E向B发送自身Commit过的最大消息zxid（记为max_zxid）以及未被Commit过的所有消息（记为zxid_set）。B根据这些信息作出如下操作\n\n*   如果Follower的max_zxid与Leader的max_zxid相等，说明该Follower与Leader完全同步，无须同步任何数据\n*   如果Follower的max_zxid在Leader的(min_zxid，max_zxid)范围内，Leader会通过TRUNC命令通知Follower将其zxid_set中大于Follower的max_zxid（如果有）的所有消息全部删除\n\n上述操作保证了未被Commit过的消息不会被Commit从而对外不可见。\n\n上述例子中Follower上并不存在未被Commit的消息。但可考虑这种情况，如果将上述例子中的服务器数量从五增加到七，服务器F包含P1、P2、C1、P3，服务器G包含P1、P2。此时服务器F、A和B都包含P3，但是因为票数未过半，因此B作为Leader不会Commit P3，而会通过TRUNC命令通知F删除P3。如下图所示。\n\n![Leader Failover step 4](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/recovery_4.png)\n\n\n# 总结\n\n*   由于使用主从复制模式，所有的写操作都要由Leader主导完成，而读操作可通过任意节点完成，因此Zookeeper读性能远好于写性能，更适合读多写少的场景\n*   虽然使用主从复制模式，同一时间只有一个Leader，但是Failover机制保证了集群不存在单点失败（SPOF）的问题\n*   ZAB协议保证了Failover过程中的数据一致性\n*   服务器收到数据后先写本地文件再进行处理，保证了数据的持久性\n"
  },
  {
    "path": "docs/distributed/practice/搞懂分布式技术：Zookeeper典型应用场景及实践.md",
    "content": "\n# 目录\n\n* [一.ZooKeeper典型应用场景实践](#一zookeeper典型应用场景实践)\n* [1 Zookeeper数据模型](#1-zookeeper数据模型)\n* [2 如何使用Zookeeper](#2-如何使用zookeeper)\n  * [2.1 常用接口操作](#21-常用接口操作)\n* [3 ZooKeeper 典型的应用场景](#3-zookeeper-典型的应用场景)\n  * [3.1 统一命名服务（Name Service）](#31-统一命名服务（name-service）)\n  * [3.2 配置管理（Configuration Management）](#32-配置管理（configuration-management）)\n  * [3.3 集群管理（Group Membership）](#33-集群管理（group-membership）)\n  * [3.4 共享锁（Locks）](#34-共享锁（locks）)\n  * [3.5 队列管理](#35-队列管理)\n  * [3.6 负载均衡](#36-负载均衡)\n  * [3.7 分布式通知/协调](#37-分布式通知协调)\n  * [二:典型场景描述总结](#二典型场景描述总结)\n    * [**数据发布与订阅**(配置管理)](#数据发布与订阅配置管理)\n    * [**负载均衡**](#负载均衡)\n    * [**分布通知/协调**](#分布通知协调)\n    * [**命名服务**](#命名服务)\n    * [**分布式锁**](#分布式锁)\n    * [**集群管理**](#集群管理)\n    * [**分布式队列**](#分布式队列)\n\n本文内容参考网络，侵删\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本文也将同步到我的个人博客：\n\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n该系列博文会告诉你什么是分布式系统，这对后端工程师来说是很重要的一门学问，我们会逐步了解常见的分布式技术、以及一些较为常见的分布式系统概念，同时也需要进一步了解zookeeper、分布式事务、分布式锁、负载均衡等技术，以便让你更完整地了解分布式技术的具体实战方法，为真正应用分布式技术做好准备。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n# 一.ZooKeeper典型应用场景实践\n\nZooKeeper是一个`高可用的分布式数据管理与系统协调框架`。`基于对Paxos算法的实现，使该框架保证了分布式环境中数据的强一致性`，也正是基于这样的特性，使得ZooKeeper解决很多分布式问题。网上对ZK的应用场景也有不少介绍，本文将介绍比较常用的项目例子，系统地对ZK的应用场景进行一个分门归类的介绍。\n\n值得注意的是，`ZK并非天生就是为这些应用场景设计的，都是后来众多开发者根据其框架的特性`，利用其提供的一系列API接口（或者称为原语集），`摸索出来的典型使用方法`。因此，也非常欢迎读者分享你在ZK使用上的奇技淫巧。\n\n# 1 Zookeeper数据模型\n\nZookeeper 会维护`一个具有层次关系的数据结构`，它非常类似于一个标准的文件系统，如图所示：\n\n[![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/17163447_w7k1.png)](https://static.oschina.net/uploads/img/201511/17163447_w7k1.png)\n\n**图中的每个节点称为一个znode. 每个znode由3部分组成：**\n\n1.  stat. 此为状态信息, 描述该`znode的版本, 权限等信息`；\n\n2.  data. 与该znode`关联的数据`；\n\n3.  children. 该znode下的`子节点`；\n\n**Zookeeper 这种数据结构有如下这些特点：**\n\n1.  每个子目录项如 NameService 都被称作为 znode，这个 znode 是被它所在的路径唯一标识，如 Server1 这个 znode 的标识为 /NameService/Server1；\n\n2.  znode 可以有子节点目录，并且每个 znode 可以存储数据，注意`EPHEMERAL 类型的目录节点不能有子节点目录`；\n\n3.  znode 是有版本的，每个 znode 中存储的数据可以有多个版本，`也就是一个访问路径中可以存储多份数据`；\n\n4.  znode 可以是临时节点，`一旦创建这个 znode 的客户端与服务器失去联系，这个 znode 也将自动删除`，Zookeeper 的客户端和服务器通信`采用长连接方式，每个客户端和服务器通过心跳来保持连接`，这个连接状态称为 session，如果 znode 是临时节点，这个 session 失效，znode 也就删除了；\n\n5.  znode 的`目录名可以自动编号`，如 App1 已经存在，再创建的话，将会自动命名为 App2；\n\n6.  znode`可以被监控，包括这个目录节点中存储的数据的修改，子节点目录的变化等`，一旦变化可以通知设置监控的客户端，`这个是 Zookeeper 的核心特性`，Zookeeper 的很多功能都是基于这个特性实现的，后面在典型的应用场景中会有实例介绍；\n\n**znode节点的状态信息：**\n\n使用get命令获取指定节点的数据时,`同时也将返回该节点的状态信息, 称为Stat`. 其包含如下字段:\n\nczxid. 节点创建时的zxid；\nmzxid. 节点最新一次更新发生时的zxid；\nctime. 节点创建时的时间戳；\nmtime. 节点最新一次更新发生时的时间戳；\ndataVersion. 节点数据的更新次数；\ncversion. 其子节点的更新次数；\naclVersion. 节点ACL(授权信息)的更新次数；\nephemeralOwner. 如果该节点为ephemeral节点, ephemeralOwner值表示与该节点绑定的session id. 如果该节点不是              ephemeral节点, ephemeralOwner值为0\\. 至于什么是ephemeral节点；\ndataLength. 节点数据的字节数；\nnumChildren. 子节点个数；\n\n\n**zxid：**\n\nznode节点的状态信息中包含czxid和mzxid, 那么什么是zxid呢?\n\n`ZooKeeper状态的每一次改变, 都对应着一个递增的Transaction id, 该id称为zxid`. 由于zxid的递增性质, 如果zxid1小于zxid2, 那么zxid1肯定先于zxid2发生.`创建任意节点, 或者更新任意节点的数据, 或者删除任意节点, 都会导致Zookeeper状态发生改变, 从而导致zxid的值增加`.\n\n**session：**\n\n在client和server通信之前, 首先需要建立连接, 该连接称为session. 连接建立后, 如果发生连接超时, 授权失败, 或者显式关闭连接, 连接便处于CLOSED状态, 此时session结束.\n\n**节点类型：**\n\n讲述节点状态的ephemeralOwner字段时, 提到过有的节点是ephemeral节点, 而有的并不是. 那么节点都具有哪些类型呢? 每种类型的节点又具有哪些特点呢?\n\n`persistent. persistent节点不和特定的session绑定`, 不会随着创建该节点的session的结束而消失, 而是**一直存在, 除非该节点被显式删除**.\n\n`ephemeral. ephemeral(临时)节点是临时性的, 如果创建该节点的session结束了, 该节点就会被自动删除`.`ephemeral节点不能拥有子节点`. 虽然ephemeral节点与创建它的session绑定, 但只要该节点没有被删除, 其他session就可以读写该节点中关联的数据.`使用-e参数指定创建ephemeral节点`.\n\ncreate -e /xing/ei world\n\n`sequence. 严格的说, sequence(顺序)并非节点类型中的一种`.**sequence节点既可以是ephemeral的, 也可以是persistent的**.`创建sequence节点时, ZooKeeper server会在指定的节点名称后加上一个数字序列, 该数字序列是递增的`. 因此可以**多次创建相同的sequence节点, 而得到不同的节点**.`使用-s参数指定创建sequence节点`.\n\n````\n[zk: localhost:4180(CONNECTED) 0] create -s /xing/item world \nCreated /xing/item0000000001 \n[zk: localhost:4180(CONNECTED) 1] create -s /xing/item world \nCreated /xing/item0000000002 \n[zk: localhost:4180(CONNECTED) 2] create -s /xing/item world \nCreated /xing/item0000000003 \n[zk: localhost:4180(CONNECTED) 3] create -s /xing/item world \nCreated /xing/item0000000004\n````\n\n**watch：**\n\n`watch的意思是监听感兴趣的事件`. 在命令行中, 以下几个命令可以指定是否监听相应的事件.\n\nls命令. ls命令的第一个参数指定znode, 第二个参数如果为true, 则说明监听该znode的**子节点的增减**, 以及该znode**本身的删除**事件.\n\n````\n[zk: localhost:4180(CONNECTED) 21] ls /xing true\n[]\n[zk: localhost:4180(CONNECTED) 22] create /xing/item item000\nWATCHER::\n WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/xing\nCreated /xing/item\n\n`get命令. get命令的第一个参数指定znode, 第二个参数如果为true, 则说明监听该znode的更新和删除事件`.\n\n[zk: localhost:4180(CONNECTED) 39] get /xing true\nworld\ncZxid = 0x100000066\nctime = Fri May 17 22:30:01 CST 2013\nmZxid = 0x100000066\nmtime = Fri May 17 22:30:01 CST 2013\npZxid = 0x100000066\ncversion = 0\ndataVersion = 0\naclVersion = 0\nephemeralOwner = 0x0\ndataLength = 5\nnumChildren = 0\n[zk: localhost:4180(CONNECTED) 40] create /xing/item item000\nCreated /xing/item\n[zk: localhost:4180(CONNECTED) 41] rmr /xing\nWATCHER::\n WatchedEvent state:SyncConnected type:NodeDeleted path:/xing\n````\n\n# 2 如何使用Zookeeper\n\nZookeeper 作为一个分布式的服务框架，`主要用来解决分布式集群中应用系统的一致性问题`，它能提供基于类似于文件系统的目录节点树方式的数据存储，但是`Zookeeper 并不是用来专门存储数据的，它的作用主要是用来维护和监控你存储的数据的状态变化`。`通过监控这些数据状态的变化，从而可以达到基于数据的集群管理`，后面将会详细介绍 Zookeeper 能够解决的一些典型问题，这里先介绍一下，Zookeeper 的操作接口和简单使用示例。\n\n## 2.1 常用接口操作\n\n客户端要连接 Zookeeper 服务器可以通过创建`org.apache.zookeeper.ZooKeeper`的一个实例对象，然后调用这个类提供的接口来和服务器交互。\n\n前面说了`ZooKeeper 主要是用来维护和监控一个目录节点树中存储的数据的状态`，所有我们能够操作 ZooKeeper 的也和操作目录节点树大体一样，如创建一个目录节点，给某个目录节点设置数据，获取某个目录节点的所有子目录节点，给某个目录节点设置权限和监控这个目录节点的状态变化。\n\n**ZooKeeper 基本的操作示例：**\n\n````\npublic class ZkDemo {\n public static void main(String[] args) throws IOException, KeeperException, InterruptedException {\n // 创建一个与服务器的连接\n ZooKeeper zk = new ZooKeeper(\"127.0.0.1:2180\", 60000, new Watcher() {\n // 监控所有被触发的事件\n // 当对目录节点监控状态打开时，一旦目录节点的状态发生变化，Watcher 对象的 process 方法就会被调用。\n public void process(WatchedEvent event) {\n System.out.println(\"EVENT:\" + event.getType());\n }\n });\n // 查看根节点\n // 获取指定 path 下的所有子目录节点，同样 getChildren方法也有一个重载方法可以设置特定的 watcher 监控子节点的状态\n System.out.println(\"ls / => \" + zk.getChildren(\"/\", true));\n // 判断某个 path 是否存在，并设置是否监控这个目录节点，这里的 watcher 是在创建 ZooKeeper 实例时指定的 watcher；\n // exists方法还有一个重载方法，可以指定特定的 watcher\n if (zk.exists(\"/node\", true) == null) {\n // 创建一个给定的目录节点 path, 并给它设置数据；\n // CreateMode 标识有四种形式的目录节点，分别是：\n //   PERSISTENT：持久化目录节点，这个目录节点存储的数据不会丢失；\n //   PERSISTENT_SEQUENTIAL：顺序自动编号的目录节点，这种目录节点会根据当前已近存在的节点数自动加 1，然后返回给客户端已经成功创建的目录节点名；\n //   EPHEMERAL：临时目录节点，一旦创建这个节点的客户端与服务器端口也就是 session 超时，这种节点会被自动删除；\n //   EPHEMERAL_SEQUENTIAL：临时自动编号节点\n zk.create(\"/node\", \"conan\".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);\n System.out.println(\"create /node conan\");\n // 查看/node节点数据\n System.out.println(\"get /node => \" + new String(zk.getData(\"/node\", false, null)));\n // 查看根节点\n System.out.println(\"ls / => \" + zk.getChildren(\"/\", true));\n }\n // 创建一个子目录节点\n if (zk.exists(\"/node/sub1\", true) == null) {\n zk.create(\"/node/sub1\", \"sub1\".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);\n System.out.println(\"create /node/sub1 sub1\");\n // 查看node节点\n System.out.println(\"ls /node => \" + zk.getChildren(\"/node\", true));\n }\n // 修改节点数据\n if (zk.exists(\"/node\", true) != null) {\n // 给 path 设置数据，可以指定这个数据的版本号，如果 version 为 -1 怎可以匹配任何版本\n zk.setData(\"/node\", \"changed\".getBytes(), -1);\n // 查看/node节点数据\n // 获取这个 path 对应的目录节点存储的数据，数据的版本等信息可以通过 stat 来指定，同时还可以设置是否监控这个目录节点数据的状态\n System.out.println(\"get /node => \" + new String(zk.getData(\"/node\", false, null)));\n }\n // 删除节点\n if (zk.exists(\"/node/sub1\", true) != null) {\n // 删除 path 对应的目录节点，version 为 -1 可以匹配任何版本，也就删除了这个目录节点所有数据\n zk.delete(\"/node/sub1\", -1);\n zk.delete(\"/node\", -1);\n // 查看根节点\n System.out.println(\"ls / => \" + zk.getChildren(\"/\", true));\n }\n // 关闭连接\n zk.close();\n }\n}\n````\n\n# 3 ZooKeeper 典型的应用场景\n\nZookeeper 从设计模式角度来看，是一个`基于观察者模式设计的分布式服务管理框架，它负责存储和管理大家都关心的数据，然后接受观察者的注册，一旦这些数据的状态发生变化，Zookeeper 就将负责通知已经在 Zookeeper 上注册的那些观察者做出相应的反应，从而实现集群中类似 Master/Slave 管理模式`，关于 Zookeeper 的详细架构等内部细节可以阅读 Zookeeper 的源码。\n\n下面详细介绍这些典型的应用场景，也就是 Zookeeper 到底能帮我们解决那些问题？下面将给出答案。\n\n## 3.1 统一命名服务（Name Service）\n\n分布式应用中，通常需要有一套完整的命名规则，既能够产生唯一的名称又便于人识别和记住，通常情况下用树形的名称结构是一个理想的选择，**树形的名称结构是一个有层次的目录结构，既对人友好又不会重复**。说到这里你可能想到了 JNDI，没错 Zookeeper 的 Name Service**与 JNDI 能够完成的功能是差不多的**，它们都是将有层次的目录结构关联到一定资源上，但是 Zookeeper 的 Name Service 更加是广泛意义上的关联，也许你并不需要将名称关联到特定资源上，你可能只需要一个不会重复名称，**就像数据库中产生一个唯一的数字主键一样**。\n\n`Name Service 已经是 Zookeeper 内置的功能`，你只要调用 Zookeeper 的 API 就能实现。如调用 create 接口就可以很容易创建一个目录节点。\n\n命名服务也是分布式系统中比较常见的一类场景。在分布式系统中，**通过使用命名服务，客户端应用能够根据指定名字来获取资源或服务的地址，提供者等信息**`。`被命名的实体通常可以是集群中的机器，提供的服务地址，远程对象等等——这些我们都可以统称他们为名字（Name）`。其中较为常见的就是一些分布式服务框架中的服务地址列表。通过调用ZK提供的创建节点的API，能够很容易创建一个全局唯一的path，这个path就可以作为一个名称。\n\n**命名服务实例：**\n\n阿里巴巴集团开源的分布式服务框架**Dubbo中使用ZooKeeper来作为其命名服务，维护全局的服务地址列表**，在Dubbo实现中：\n\n`服务提供者在启动的时候`，向ZK上的指定节点`/dubbo/${serviceName}/providers`目录下写入自己的URL地址，这个操作就完成了服务的发布。\n\n`服务消费者启动的时候`，`订阅/dubbo/${serviceName}/providers`目录下的提供者URL地址， 并向`/dubbo/${serviceName} /consumers`目录下写入自己的URL地址。\n\n注意，**所有向ZK上注册的地址都是临时节点**，这样就能够保证服务提供者和消费者能够**自动感应资源的变化**。 另外，Dubbo还有**针对服务粒度的监控**，方法是订阅/dubbo/${serviceName}目录下所有提供者和消费者的信息。\n\n## 3.2 配置管理（Configuration Management）\n\n配置的管理在分布式应用环境中很常见，例如同一个应用系统需要多台 PC Server 运行，但是它们运行的应用系统的某些配置项是相同的，如果要修改这些相同的配置项，那么就必须同时修改每台运行这个应用系统的 PC Server，这样非常麻烦而且容易出错。\n\n像这样的配置信息完全可以交给 Zookeeper 来管理，`将配置信息保存在 Zookeeper 的某个目录节点中，然后将所有需要修改的应用机器监控配置信息的状态，一旦配置信息发生变化，每台应用机器就会收到 Zookeeper 的通知，然后从 Zookeeper 获取新的配置信息应用到系统中`。\n\n[![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/18093533_GnwN.png)](https://static.oschina.net/uploads/img/201511/18093533_GnwN.png)\n\n**发布与订阅模型，即所谓的配置中心**，顾名思义就是**发布者将数据发布到ZK节点上，供订阅者动态获取数据**，`实现配置信息的集中式管理和动态更新`。例如全局的配置信息，服务式服务框架的服务地址列表等就非常适合使用。\n\n**配置管理实例：**\n\n1.  `应用中用到的一些配置信息放到ZK上进行集中管理`。这类场景通常是这样：**应用在启动的时候会主动来获取一次配置`，同时，`在节点上注册一个Watcher，这样一来，以后每次配置有更新的时候，都会实时通知到订阅的客户端，从而达到获取最新配置信息的目的。**\n\n2.  `分布式搜索服务中`，索引的元信息和服务器集群机器的节点状态存放在ZK的一些指定节点，供各个客户端订阅使用。\n\n3.  `分布式日志收集系统`。这个系统的核心工作是收集分布在不同机器的日志。收集器通常是按照应用来分配收集任务单元，因此需要在ZK上创建一个以应用名作为path的节点P，并将这个应用的所有机器ip，以子节点的形式注册到节点P上，这样一来就能够实现机器变动的时候，能够实时通知到收集器调整任务分配。\n\n4.  `系统中有些信息需要动态获取，并且还会存在人工手动去修改这个信息的发问`。通常是暴露出接口，例如JMX接口，来获取一些运行时的信息。引入ZK之后，就不用自己实现一套方案了，只要将这些信息存放到指定的ZK节点上即可。\n\n注意：在上面提到的应用场景中，有个默认前提是：`数据量很小，但是数据更新可能会比较快的场景`。\n\n## 3.3 集群管理（Group Membership）\n\nZookeeper 能够很容易的实现集群管理的功能，如有多台 Server 组成一个服务集群，那么`必须要一个“总管”知道当前集群中每台机器的服务状态，一旦有机器不能提供服务，集群中其它集群必须知道`，从而做出调整重新分配服务策略。同样`当增加集群的服务能力时，就会增加一台或多台 Server，同样也必须让“总管”知道`。\n\nZookeeper 不仅能够帮你维护当前的集群中机器的服务状态，而且能够帮你选出一个“总管”，让这个总管来管理集群，这就是`Zookeeper 的另一个功能 Leader Election`。\n\n它们的实现方式都是在**Zookeeper 上创建一个 EPHEMERAL 类型的目录节点`，然后`每个 Server 在它们创建目录节点的父目录节点上调用 getChildren(String path, boolean watch) 方法并设置 watch 为 true`，由于是 EPHEMERAL 目录节点，当创建它的 Server 死去，这个目录节点也随之被删除，所以 Children 将会变化，这时 getChildren上的 Watch 将会被调用，所以其它 Server 就知道已经有某台 Server 死去了**。新增 Server 也是同样的原理。\n\nZookeeper 如何实现 Leader Election，也就是选出一个 Master Server。和前面的一样`每台 Server 创建一个 EPHEMERAL 目录节点，不同的是它还是一个 SEQUENTIAL 目录节点，所以它是个 EPHEMERAL_SEQUENTIAL 目录节点`。之所以它是**EPHEMERAL_SEQUENTIAL**目录节点，是因为我们可以给每台 Server 编号，我们可以`选择当前是最小编号的 Server 为 Master`，假如这个最小编号的 Server 死去，由于是 EPHEMERAL 节点，`死去的 Server 对应的节点也被删除，所以当前的节点列表中又出现一个最小编号的节点，我们就选择这个节点为当前 Master`。这样就实现了动态选择 Master，避免了传统意义上单 Master 容易出现单点故障的问题。\n\n[![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/18095931_70Ol.png)](https://static.oschina.net/uploads/img/201511/18095931_70Ol.png)\n\n**1\\. 集群机器监控**\n\n这通常用于那种`对集群中机器状态，机器在线率有较高要求的场景`，能够快速对集群中机器变化作出响应。这样的场景中，往往有一个监控系统，实时检测集群机器是否存活。过去的做法通常是：监控系统通过某种手段（比如ping）定时检测每个机器，或者每个机器自己定时向监控系统汇报“我还活着”。 这种做法可行，但是存在两个比较明显的问题：\n\n1.  集群中机器有变动的时候，牵连修改的东西比较多。\n\n2.  有一定的延时。\n\n利用ZooKeeper有两个特性，就可以实现另一种集群机器存活性监控系统：\n\n1.  `客户端在节点 x 上注册一个Watcher，那么如果 x 的子节点变化了，会通知该客户端`。\n\n2.  `创建EPHEMERAL类型的节点，一旦客户端和服务器的会话结束或过期，那么该节点就会消失`。\n\n`例如`：监控系统在 /clusterServers 节点上注册一个Watcher，以后每动态加机器，那么就往 /clusterServers 下创建一个 EPHEMERAL类型的节点：/clusterServers/{hostname}. 这样，监控系统就能够实时知道机器的增减情况，至于后续处理就是监控系统的业务了。\n\n**2\\. Master选举则是zookeeper中最为经典的应用场景了**\n\n在分布式环境中，相同的业务应用分布在不同的机器上，`有些业务逻辑（例如一些耗时的计算，网络I/O处理），往往只需要让整个集群中的某一台机器进行执行，其余机器可以共享这个结果`，这样可以大大减少重复劳动，提高性能，于是`这个master选举便是这种场景下的碰到的主要问题`。\n\n**利用ZooKeeper的强一致性，能够保证在分布式高并发情况下节点创建的全局唯一性**，即：同时有多个客户端请求创建 /currentMaster 节点，最终一定只有一个客户端请求能够创建成功。利用这个特性，就能很轻易的在分布式环境中进行集群选取了。\n\n另外，这种场景演化一下，就是`动态Master选举`。这就要用到`EPHEMERAL_SEQUENTIAL类型节点的特性了`。\n\n上文中提到，所有客户端创建请求，最终只有一个能够创建成功。在这里稍微变化下，就是**允许所有请求都能够创建成功，但是得有个创建顺序**，于是所有的请求最终在ZK上创建结果的一种可能情况是这样： /currentMaster/{sessionId}-1 ,/currentMaster/{sessionId}-2,/currentMaster/{sessionId}-3 …..`每次选取序列号最小的那个机器作为Master，如果这个机器挂了，由于他创建的节点会马上消失，那么之后最小的那个机器就是Master了`。\n\n**3\\. 在搜索系统中，如果集群中每个机器都生成一份全量索引，不仅耗时，而且不能保证彼此之间索引数据一致。**因此让集群中的Master来进行全量索引的生成，然后同步到集群中其它机器。另外，Master选举的容灾措施是，可以随时进行手动指定master，就是说应用在zk在无法获取master信息时，可以通过比如http方式，向一个地方获取master。\n\n**4\\. 在Hbase中，也是使用ZooKeeper来实现动态HMaster的选举。**在Hbase实现中，会在ZK上存储一些ROOT表的地址和HMaster的地址，HRegionServer也会把自己以临时节点（Ephemeral）的方式注册到Zookeeper中，使得HMaster可以随时感知到各个HRegionServer的存活状态，同时，一旦HMaster出现问题，会重新选举出一个HMaster来运行，从而避免了HMaster的单点问题。\n\n[![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/18102032_cFyc.png)](https://static.oschina.net/uploads/img/201511/18102032_cFyc.png)\n\n## 3.4 共享锁（Locks）\n\n共享锁在同一个进程中很容易实现，但是在跨进程或者在不同 Server 之间就不好实现了。Zookeeper 却很容易实现这个功能，实现方式也是`需要获得锁的 Server 创建一个 EPHEMERAL_SEQUENTIAL 目录节点，然后调用 getChildren方法获取当前的目录节点列表中最小的目录节点是不是就是自己创建的目录节点，如果正是自己创建的，那么它就获得了这个锁，如果不是那么它就调用 exists(String path, boolean watch) 方法并监控 Zookeeper 上目录节点列表的变化，一直到自己创建的节点是列表中最小编号的目录节点，从而获得锁，释放锁很简单，只要删除前面它自己所创建的目录节点就行了`。\n\n分布式锁，这个主要得益于ZooKeeper为我们保证了数据的强一致性。锁服务可以分为两类，`一个是保持独占，另一个是控制时序`。\n\n1.  **所谓保持独占，就是所有试图来获取这个锁的客户端，最终只有一个可以成功获得这把锁**。通常的做法是把zk上的一个znode看作是一把锁，通过create znode的方式来实现。`所有客户端都去创建 /distribute_lock 节点，最终成功创建的那个客户端也即拥有了这把锁`。\n\n2.  **控制时序，就是所有视图来获取这个锁的客户端，最终都是会被安排执行，只是有个全局时序了**。做法和上面基本类似，只是这里 /distribute_lock 已经预先存在，客户端在它下面创建临时有序节点（这个可以通过节点的属性控制：CreateMode.EPHEMERAL_SEQUENTIAL来指定）。**Zk的父节点（/distribute_lock）维持一份sequence,保证子节点创建的时序性，从而也形成了每个客户端的全局时序。**\n\n[![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/18103433_BJs7.png)](https://static.oschina.net/uploads/img/201511/18103433_BJs7.png)\n\n[![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/18103709_c4c3.png)](https://static.oschina.net/uploads/img/201511/18103709_c4c3.png)\n\n## 3.5 队列管理\n\nZookeeper 可以处理两种类型的队列：\n\n1.  `当一个队列的成员都聚齐时，这个队列才可用，否则一直等待所有成员到达`，这种是同步队列。\n\n2.  `队列按照 FIFO 方式进行入队和出队操作`，例如实现生产者和消费者模型。\n\n**同步队列用 Zookeeper 实现的实现思路如下：**\n\n创建一个父目录 /synchronizing，每个成员都监控标志（Set Watch）位目录 /synchronizing/start 是否存在，然后每个成员都加入这个队列，**加入队列的方式**就是创建 /synchronizing/member_i 的**临时目录节点**，然后每个成员获取 / synchronizing 目录的所有目录节点，也就是 member_i。判断 i 的值是否已经是成员的个数，如果小于成员个数等待 /synchronizing/start 的出现，`如果已经相等就创建 /synchronizing/start`。\n\n[![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/18104311_7kNc.png)](https://static.oschina.net/uploads/img/201511/18104311_7kNc.png)\n\n[![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/18104433_Rdxx.png)](https://static.oschina.net/uploads/img/201511/18104433_Rdxx.png)\n\n**FIFO 队列用 Zookeeper 实现思路如下：**\n\n实现的思路也非常简单，就是在特定的目录下**创建 SEQUENTIAL 类型的子目录 /queue_i**，这样就能保证所有成员加入队列时都是有编号的`，出队列时通过 getChildren( ) 方法可以返回当前所有的队列中的元素，然后消费其中最小的一个，这样就能保证 FIFO。\n\n[![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/18104614_ncNj.png)\n\n## 3.6 负载均衡\n\n这里说的负载均衡是指**软负载均衡**。在分布式环境中，为了保证高可用性，通常同一个应用或同一个服务的提供方都会部署多份，达到对等服务。而消费者就须要在这些对等的服务器中选择一个来执行相关的业务逻辑，`其中比较典型的是消息中间件中的生产者，消费者负载均衡`。\n\n`消息中间件中发布者和订阅者的负载均衡`，linkedin开源的KafkaMQ和阿里开源的metaq都是通过zookeeper**来做到生产者、消费者的负载均衡**`。这里以metaq为例如讲下：\n\n`生产者负载均衡`：metaq发送消息的时候，生产者在发送消息的时候必须选择一台broker上的一个分区来发送消息，因此metaq在运行过程中，会把所有broker和对应的分区信息全部注册到ZK指定节点上，默认的策略是一个依次轮询的过程，生产者在通过ZK获取分区列表之后，会按照brokerId和partition的顺序排列组织成一个有序的分区列表，发送的时候按照从头到尾循环往复的方式选择一个分区来发送消息。\n\n`消费负载均衡`： 在消费过程中，一个消费者会消费一个或多个分区中的消息，但是一个分区只会由一个消费者来消费。MetaQ的消费策略是：\n\n1. 每个分区针对同一个group只挂载一个消费者。\n\n2. 如果同一个group的消费者数目大于分区数目，则多出来的消费者将不参与消费。\n\n3. 如果同一个group的消费者数目小于分区数目，则有部分消费者需要额外承担消费任务。\n\n   在某个消费者故障或者重启等情况下，其他消费者会感知到这一变化（通过 zookeeper watch消费者列表），然后重新进行负载均衡，保证所有的分区都有消费者进行消费。\n\n## 3.7 分布式通知/协调\n\n`ZooKeeper中特有watcher注册与异步通知机制，能够很好的实现分布式环境下不同系统之间的通知与协调，实现对数据变更的实时处理`。使用方法通常是不同系统都对ZK上同一个znode进行注册，监听znode的变化（包括znode本身内容及子节点的），其中一个系统update了znode，那么另一个系统能够收到通知，并作出相应处理。\n\n1.  `另一种心跳检测机制`：检测系统和被检测系统之间并不直接关联起来，而是通过zk上某个节点关联，大大减少系统耦合。\n\n2.  `另一种系统调度模式`：某系统有控制台和推送系统两部分组成，控制台的职责是控制推送系统进行相应的推送工作。管理人员在控制台作的一些操作，实际上是修改了ZK上某些节点的状态，而ZK就把这些变化通知给他们注册Watcher的客户端，即推送系统，于是，作出相应的推送任务。\n\n3.  `另一种工作汇报模式`：一些类似于任务分发系统，子任务启动后，到zk来注册一个临时节点，并且定时将自己的进度进行汇报（将进度写回这个临时节点），这样任务管理者就能够实时知道任务进度。\n\n总之，使用zookeeper来进行分布式通知和协调能够大大降低系统之间的耦合。\n\n## 二:典型场景描述总结\n\n#### **数据发布与订阅**(配置管理)\n\n发布与订阅即所谓的配置管理，顾名思义就是将数据发布到zk节点上，供订阅者动态获取数据，实现配置信息的集中式管理和动态更新。\n\n例如全局的配置信息，地址列表等就非常适合使用。\n\n发布/订阅系统一般有两种设计模式，分别是推(Push)模式和拉(Pull)模式。\n\n推模式\n\n服务端主动将数据更新发送给所有订阅的客户端。\n\n拉模式\n\n客户端通过采用定时轮询拉取。\n\nZooKeeper采用的是推拉相结合的方式：客户端向服务端注册自己需要关注的节点，一旦该节点的数据发生变更，那么服务端就会向相应的客户端发送Watcher事件通知，客户端接收到这个消息通知之后，需要主动到服务端获取最新的数据。\n\n#### **负载均衡**\n\n负载均衡(Load Balance)是一种相当常见的计算机网络技术，用来对多个计算机(计算机集群)、网络连接、CPU、硬盘驱动器或其他资源进行分配负载，以达到优化资源使用、最大化吞吐率、最小化响应时间和避免过载的目的。通常，负载均衡可以分为硬件和软件负载均衡两类\n\n#### **分布通知/协调**\n\n分布式协调/通知是将不同的分布式组件有机结合起来的关键所在。对于一个在多台机器上部署运行的应用而言，通常需要一个协调者(Coordinator)来控制整个系统的运行流程，例如分布式事务的处理、机器间的相互协调等。同时，引入这样一个协调者，便于将分布式协调的职责从应用中分离出来，从而大大减少系统之间的耦合性，而且能够显著提高系统的可扩展性。\n\nZooKeeper 中特有watcher注册与异步通知机制，能够很好的实现分布式环境下不同系统之间的通知与协调，实现对数据变更的实时处理。\n\n使用方法通常是不同系统都对 ZK上同一个znode进行注册，监听znode的变化（包括znode本身内容及子节点的），其中一个系统update了znode，那么另一个系统能 够收到通知，并作出相应处理。\n\n\\1\\. 另一种心跳检测机制：检测系统和被检测系统之间并不直接关联起来，而是通过zk上某个节点关联，大大减少系统耦合。\n\n\\2\\. 另一种系统调度模式：某系统有控制台和推送系统两部分组成，控制台的职责是控制推送系统进行相应的推送工作。管理人员在控制台作的一些操作，实际上是修改 了ZK上某些节点的状态，而zk就把这些变化通知给他们注册Watcher的客户端，即推送系统，于是，作出相应的推送任务。\n\n\\3\\. 另一种工作汇报模式：一些类似于任务分发系统，子任务启动后，到zk来注册一个临时节点，并且定时将自己的进度进行汇报（将进度写回这个临时节点），这样任务管理者就能够实时知道任务进度。总之，使用zookeeper来进行分布式通知和协调能够大大降低系统之间的耦合。\n\n#### **命名服务**\n\n在分布式系统中，被命名的实体通常是集群中的机器、提供的服务地址或远程对象等--这些我们都可以统称他们为名字，其中比较常见的就是一些分布式服务框架(RPC、RMI)中的服务地址列表，通过使用命名服务，客户端应用能够根据指定名字来获取资源的实体、服务地址和提供者信息等。\n\nZooKeeper提供的命名服务功能与JNDI技术有类似的地方，都能够帮助应用系统通过一个资源引用的方式来实现对资源的定位与使用。另外，广义上命名服务的资源定位都不是真正意义的实体资源--在分布式环境中，上层应用仅仅需要一个全局唯一的名字，类似于数据库的唯一主键。，通过调用zk的create node api，能够很容易创建一个全局唯一的path，这个path就可以作为一个名称。所谓ID，就是一个能唯一标识某个对象的标识符。\n\n#### **分布式锁**\n\n分布式锁，这个主要得益于ZooKeeper为我们保证了数据的强一致性，即用户只要完全相信每时每刻，zk集群中任意节点（一个zk server）上的相同znode的数据是一定是相同的。锁服务可以分为两类，一个是保持独占，另一个是控制时序。\n\n保持独占:就是所有试图来获取这个锁的客户端，最终只有一个可以成功获得这把锁。通常的做法是把zk上的一个znode看作是一把锁，通过create znode的方式来实现。所有客户端都去创建 /distribute_lock 节点，最终成功创建的那个客户端也即拥有了这把锁。\n\n控制时序:就是所有视图来获取这个锁的客户端，最终都是会被安排执行，只是有个全局时序了。\n\n做法和上面基本类似，只是这里 /distribute_lock 已经预先存在，客户端在它下面创建临时有序节点\n\n（这个可以通过节点的属性控制：CreateMode.EPHEMERAL_SEQUENTIAL来指定）。Zk的父节点（/distribute_lock）维持一份sequence,保证子节点创建的时序性，从而也形成了每个客户端的全局时序。\n\n原理:(借助Zookeeper 可以实现这种分布式锁：需要获得锁的 Server 创建一个 EPHEMERAL_SEQUENTIAL 目录节点，然后调用 getChildren()方法获取列表中最小的目录节点，如果最小节点就是自己创建的目录节点，那么它就获得了这个锁，如果不是那么它就调用 exists() 方法并监控前一节点的变化，一直到自己创建的节点成为列表中最小编号的目录节点，从而获得锁。释放锁很简单，只要删除它自己所创建的目录节点就行了。)\n\n#### **集群管理**\n\n\\1\\. 集群机器监控：这通常用于那种对集群中机器状态，机器在线率有较高要求的场景，能够快速对集群中机器变化作出响应。这样的场景中，往往有一个监控系统，实时检测集群机器是否存活。过去的做法通常是：监控系统通过某种手段（比如ping）定时检测每个机器，或者每个机器自己定时向监控系统汇报“我还活着”。 这种做法可行，但是存在两个比较明显的问题：\n\n\\1\\. 集群中机器有变动的时候，牵连修改的东西比较多。\n\n\\2\\. 有一定的延时。利用ZooKeeper有两个特性，就可以实时另一种集群机器存活性监控系统：\n\na. 客户端在节点 x 上注册一个Watcher，那么如果 x 的子节点变化了，会通知该客户端。\n\nb. 创建EPHEMERAL类型的节点，一旦客户端和服务器的会话结束或过期，那么该节点就会消失。\n\n应用:\n\nMaster选举\n\n则是zookeeper中最为经典的使用场景了。\n\n在分布式环境中，相同的业务应用分布在不同的机器上，有些业务逻辑（例如一些耗时的计算，网络I/O处理），往往只需要让整个集群中的某一台机器进行执行， 其余机器可以共享这个结果，这样可以大大减少重复劳动，提高性能，于是这个master选举便是这种场景下的碰到的主要问题。\n\n利用ZooKeeper的强一致性，能够保证在分布式高并发情况下节点创建的全局唯一性，即：同时有多个客户端请求创建 /currentMaster 节点，最终一定只有一个客户端请求能够创建成功。\n\n#### **分布式队列**\n\n两种类型的队列：\n\n1、 同步队列，当一个队列的成员都聚齐时，这个队列才可用，否则一直等待所有成员到达。\n\n2、队列按照 FIFO 方式进行入队和出队操作。\n\n第一类，在约定目录下创建临时目录节点，监听节点数目是否是我们要求的数目。\n\n第二类，和分布式锁服务中的控制时序场景基本原理一致，入列有编号，出列按编号。\n\n同步队列。一个job由多个task组成，只有所有任务完成后，job才运行完成。可为job创建一个/job目录，然后在该目录下，为每个完成的task创建一个临时znode，一旦临时节点数目达到task总数，则job运行完成。\n"
  },
  {
    "path": "docs/distributed/practice/搞懂分布式技术：Zookeeper的配置与集群管理实战.md",
    "content": "\n\n# 目录\n\n* [4.1 配置文件](#41-配置文件)\n* [4.2 服务端命令](#42-服务端命令)\n* [4.3 客户端命令](#43-客户端命令)\n    * [4.3.1 查看节点列表](#431-查看节点列表)\n    * [4.3.2 创建新节点](#432-创建新节点)\n    * [4.3.3 查看节点数据](#433-查看节点数据)\n    * [4.3.4 修改节点数据](#434-修改节点数据)\n    * [4.3.5 删除节点](#435-删除节点)\n* [4.4 ZooKeeper四字命令](#44-zookeeper四字命令)\n* [5.1 集群配置](#51-集群配置)\n* [5.2 集群启动](#52-集群启动)\n* [5.3 集群容灾](#53-集群容灾)\n\n\n本文内容参考网络，侵删\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本文也将同步到我的个人博客：\n\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n该系列博文会告诉你什么是分布式系统，这对后端工程师来说是很重要的一门学问，我们会逐步了解常见的分布式技术、以及一些较为常见的分布式系统概念，同时也需要进一步了解zookeeper、分布式事务、分布式锁、负载均衡等技术，以便让你更完整地了解分布式技术的具体实战方法，为真正应用分布式技术做好准备。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n## 4.1 配置文件\n\nZooKeeper安装好之后，在安装目录的conf文件夹下可以找到一个名为“**zoo_sample.cfg**”的文件，是ZooKeeper配置文件的模板。\n\nZooKeeper启动时，会默认加载“conf/zoo.cfg”作为配置文件，所以需要将“**zoo_sample.cfg**”复制一份，命名为“**zoo.cfg**”，然后根据需要设定里面的配置项。\n\n配置项很简单，说明如下：\n\ntickTime=2000\n\n这个时间是作为 ZooKeeper服务器之间或客户端与服务器之间维持**心跳的时间间隔**，也就是每个 tickTime 时间就会发送一个心跳。单位为毫秒。\n\ninitLimit=10\n\n这个配置项是用来配置 Leader接受Follower 初始化连接时**最长能忍受多少个心跳时间间隔数**。当已经超过 10 个心跳的时间（也就是 tickTime）长度后 Leader还没有收到Follower的返回信息，那么表明这个Follower连接失败。总的时间长度就是 5*2000=10 秒。\n\nsyncLimit=5\n\n这个配置项标识 Leader 与 Follower 之间发送消息，**请求和应答时间长度，最长不能超过多少个tickTime 的时间长度**，总的时间长度就是5*2000=10 秒。\n\ndataDir=/tmp/zookeeper\n\n顾名思义就是**ZooKeeper保存数据的目录**，用于存放内存数据库快照的文件夹，同时用于集群的myid文件也存在这个文件夹里。默认情况下，ZooKeeper 将写数据的日志文件也保存在这个目录里。注意：一个配置文件只能包含一个dataDir字样，即使它被注释掉了。\n\nclientPort=2181\n\n这个端口就是客户端连接 ZooKeeper服务器的**端口**，ZooKeeper 会监听这个端口，接受客户端的访问请求。\n\nmaxClientCnxns=60\n\n**最大的客户端连接数**，默认为60.\n\nautopurge.snapRetainCount=3\n\nautopurge.purgeInterval=1\n\n客户端在与ZooKeeper交互过程中会产生非常多的日志，而且ZooKeeper也会将内存中的数据作为snapshot保存下来，这些数据是不会被自动删除的，这样磁盘中这些数据就会越来越多。不过可以通过这两个参数来设置，让zookeeper自动删除数据。autopurge.purgeInterval就是**设置多少小时清理一次**。而autopurge.snapRetainCount是**设置保留多少个snapshot，之前的则删除**。\n\n## 4.2 服务端命令\n\n“zkServer.sh”脚本用于执行Zookeeper的启动、停止及状态查看等操作\n\n利用“zkServer.sh help”命令，可以查看支持的参数：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407212604.png)\n\n可见，“zkServer.sh”可以附带的参数有：\n\n（1）start：用于启动服务端\n\n（2）stop：用于停止服务端\n\n（3）restart：用于重启服务端\n\n（4）status：用于查看服务端状态\n\n以及用于前台启动、更新等操作的其他参数。\n\n例如，使用命令“**zkServer.sh start**”启动ZooKeeper服务端，该命令后面可以附带参数，用于指定配置文件的路径，比如“zkServer.sh start ../conf/ZooKeeper.cfg”,代表使用ZooKeeper.cfg作为配置文件，如果不指定路径，默认加载“conf/zoo.cfg”文件：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407212614.png)\n\n使用**“zkServer.sh stop**”停止服务端：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407212621.png)\n\n## 4.3 客户端命令\n\n使用命令“**zkCli.sh -server 127.0.0.1:2181**”可以连接到IP为“127.0.0.1”，端口为“2181”的ZooKeeper服务器。如果连接本机的2181端口，则后面的参数可以省略。如：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407212629.png)\n\n此时，输入“help”可以查看命令参数：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407212637.png)\n\n### 4.3.1 查看节点列表\n\n在前面已经提到过，ZooKeeper维护者一个树形的数据结构，根节点为“/”。\n\n“**ls path**”用于查看路径path下的所有直接子节点：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407212645.png)\n\n可见，系统初始化的时候，根节点下会自动创建一个名为“zookeeper”的节点，用于存储ZooKeeper的管理信息。所以用户不能再根节点下创建同名的子节点。\n\n### 4.3.2 创建新节点\n\n“create path data”用于在path路径下创建一个新节点，携带数据data。\n\n例如，在根节点下新建一个名为“firstNode”节点，存储的数据为“HelloWorld”：\n\n./zkClient.sh -server 127.0.01\n\ncreate /firstNode HelloWorld\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407212654.png)\n\n### 4.3.3 查看节点数据\n\n“get path”用于获取path节点下的数据，例如：\n\nget /firstNode\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407212702.png)\n\n除了返回节点存储的数据之外，还有一系列的元信息，如代表节点创建时间的“cZxid”、“ctime”（两种表示方法）；节点的修改时间“mZxid”、“mtime”等。\n\n### 4.3.4 修改节点数据\n\n“**set path data**”用于将path节点下的数据更改为data。\n\n如，将“/firstNode”下的数据更改为“WorldHello”：\n\nset /firstNode WorldHello\n\n\n\n### 4.3.5 删除节点\n\n“delete path”用于删除path节点。\n\n如，删除“/firstNode”节点：\n\ndelete /firstNode\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407212739.png)\n\n此外，还有用于设置节点ACL、查看节点状态等其他命令，需要时可以查阅相关手册。\n\n## 4.4 ZooKeeper四字命令\n\nZooKeeper 支持某些特定的四字命令字母与其的交互。它们大多是查询命令，用来获取 ZooKeeper 服务的当前状态及相关信息。用户在客户端可以通过 telnet 或 nc 向 ZooKeeper 提交相应的命令。\n\n如:\n\n**ZooKeeper四字命令**\n\nconf\n\n**功能描述**\n\n输出相关服务配置的详细信息\n\ncons\n\n列出**所有连接到服务器的客户端的完全的连接 / 会话的详细信息**。包括“接受 / 发送”的包数量、会话 id 、操作延迟、最后的操作执行等等信息\n\ndump\n\n列出**未经处理的会话和临时节点**\n\nenvi\n\n输出关于**服务环境的详细信息**（区别于 conf 命令）\n\nreqs\n\n列出**未经处理的请求**\n\nruok\n\n测试服务是否处于正确状态。如果确实如此，那么服务返回“ imok ”，否则不做任何相应\n\nstat\n\n输出关于**性能和连接的客户端的列表**\n\nwchs\n\n列出**服务器 watch 的详细信息**\n\nwchc\n\n通过**session**列出服务器 watch 的详细信息，它的输出是一个与 watch 相关的会话的列表\n\nwchp\n\n通过**路径**列出服务器 watch 的详细信息。它输出一个与 session 相关的路径\n\n\n\n* * *\n\n\n\n例如，查看配置信息：\n\n“echo conf | nc 127.0.0.1 2181”：\n\nnc为“NetCat”工具提供的命令，通常的Linux发行版中都带有NetCat。NetCat在网络工具中有“瑞士军刀”美誉，被设计为一个简单、可靠的网络工具，可通过TCP或UDP协议传输读写数据。\n\n该命令的意思为，将“conf”命令传递给127.0.0.1的2181端口（即本机的ZooKeeper服务端口），并将响应打印出来：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407212748.png)\n\n在一台机器上运营一个ZooKeeper实例，称之为单机（Standalone）模式。单机模式有个致命的缺陷，一旦唯一的实例挂了，依赖ZooKeeper的应用全得完蛋。\n\n实际应用当中，一般都是采用集群模式来部署ZooKeeper，集群中的Server为奇数（**2N+1**）。只要集群中的多数（大于N+1台）Server活着，集群就能对外提供服务。\n\n在每台机器上部署一个ZooKeeper实例，多台机器组成集群，称之为完全分布式集群。此外，还可以在仅有的一台机器上部署多个ZooKeeper实例，以伪集群模式运行。\n\n## 5.1 集群配置\n\n下面我们来建一个3个实例的zookeeper伪分布式集群。\n\n首先，需要为三个实例创建不同的配置文件：\n\nzk1.cfg的配置项如下：\n````\n tickTime=2000\n\n initLimit=10\n\n syncLimit=5\n\n dataDir=/zk1/dataDir\n\n clientPort=2181\n\n server.1=127.0.0.1:2888:3888\n\n server.2=127.0.0.1:2889:3889\n\n server.3=127.0.0.1:2890:3890\n````\n\nzk2.cfg的配置项如下：\n````\n tickTime=2000\n\n initLimit=10\n\n syncLimit=5\n\n dataDir=/zk2/dataDir\n\n clientPort=2182\n\n server.1=127.0.0.1:2888:3888\n\n server.2=127.0.0.1:2889:3889\n\n server.3=127.0.0.1:2890:3890\n````\n\nzk3.cfg的配置项如下：\n````\ntickTime=2000\n\ninitLimit=10\n\nsyncLimit=5\n\ndataDir=/zk3/dataDir\n\nclientPort=2183\n\nserver.1=127.0.0.1:2888:3888\n\nserver.2=127.0.0.1:2889:3889\n\nserver.3=127.0.0.1:2890:3890\n````\n\n因为部署在同一台机器上，所以每个实例的dataDir、clientPort要做区分，其余配置保持一致。\n\n需要注意的是，集群中所有的实例作为一个整体对外提供服务，集群中每个实例之间都互相连接，所以，每个配置文件中都要列出所有实例的映射关系。\n\n在每个配置文件的末尾，有几行“server.A=B：C：D”这样的配置，其中，**A**是一个数字，表示这个是**第几号服务器**；B 是这个服务器的**ip 地址**；C 表示的是这个**服务器与集群中的 Leader 服务器交换信息的端口**；D 表示的是万一集群中的 Leader 服务器挂了，需要一个端口来重新进行选举，选出一个新的 Leader，而这个端口就是用来执行**选举时服务器相互通信的端口**。如果是伪集群的配置方式，由于 B 都是一样，所以不同的 Zookeeper 实例通信端口号不能一样，所以要给它们分配不同的端口号。\n\n除了修改 zoo.cfg 配置文件，集群模式下还要配置一个**myid**文件，这个文件**在 dataDir 目录下，文件里只有一个数据，就是 A 的值**(第几号服务器)，Zookeeper 启动时会读取这个文件，拿到里面的数据与配置信息比较从而判断到底是那个 Server。\n\n**上例中，需要在每个实例各自的dataDir目录下，新建myid文件，分别填写“1”、“2”、“3”。**\n\n## 5.2 集群启动\n\n依次启动三个实例：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407212755.png)\n\n查看Server状态：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407212802.png)\n\n可见，现在的集群中，zk2充当着Leader角色，而zk1与zk3充当着Follower角色。\n\n使用三个客户端连接三个Server，在zk1的客户端下，新增“/newNode”节点，储存数据“zk1”：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407212808.png)\n\n在zk2的客户端与查看该节点：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407212816.png)\n\n在zk3的客户端与查看该节点：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407212822.png)\n\n可见，集群中的Server保持着数据同步。\n\n## 5.3 集群容灾\n\n如果我们把身为Leader的zk2关闭，会发生什么呢？\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407212828.png)\n\n可见，集群自动完成了切换，zk3变成了Leader。实际应用中，如果集群中的Leader宕机了，或者Leader与超过半数的Follower失去联系，都会触发ZooKeeper的选举流程，选举出新的Leader之后继续对外服务。\n\n如果我们再把zk3关闭，会发生什么呢？\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407212836.png))\n\n可见，关闭zk3以后，由于集群中的可用Server只剩下一台（达不到集群总数的半数以上），**集群将处于不可用的状态。**\n"
  },
  {
    "path": "docs/distributed/practice/搞懂分布式技术：使用RocketMQ事务消息解决分布式事务.md",
    "content": "\n# 目录\n\n* [初步认识RocketMQ的核心模块](#初步认识rocketmq的核心模块)\n* [错误的方案0](#错误的方案0)\n* [方案1–业务方自己实现](#方案1业务方自己实现)\n* [方案2 – RocketMQ 事务消息](#方案2--rocketmq-事务消息)\n* [人工介入](#人工介入)\n\n\n本文转载自 linkedkeeper.com\n\n本文内容参考网络，侵删\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本文也将同步到我的个人博客：\n\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n该系列博文会告诉你什么是分布式系统，这对后端工程师来说是很重要的一门学问，我们会逐步了解常见的分布式技术、以及一些较为常见的分布式系统概念，同时也需要进一步了解zookeeper、分布式事务、分布式锁、负载均衡等技术，以便让你更完整地了解分布式技术的具体实战方法，为真正应用分布式技术做好准备。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n# 初步认识RocketMQ的核心模块\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/4943997-9842b6eec46bd737.png)\n\nrocketmq模块\n\n**rocketmq-broker：接受生产者发来的消息并存储（通过调用rocketmq-store），消费者从这里取得消息。**\n\n**rocketmq-client：提供发送、接受消息的客户端API。**\n\n**rocketmq-namesrv：NameServer，类似于Zookeeper，这里保存着消息的TopicName，队列等运行时的元信息。（有点NameNode的味道）**\n\n**rocketmq-common：通用的一些类，方法，数据结构等**\n\n**rocketmq-remoting：基于Netty4的client/server + fastjson序列化 + 自定义二进制协议**\n\n**rocketmq-store：消息、索引存储等**\n\n**rocketmq-filtersrv：消息过滤器Server，需要注意的是，要实现这种过滤，需要上传代码到MQ！【一般而言，我们利用Tag足以满足大部分的过滤需求，如果更灵活更复杂的过滤需求，可以考虑filtersrv组件】**\n\n**rocketmq-tools：命令行工具**\n\n[分布式消息队列RocketMQ--事务消息--解决分布式事务](https://www.cnblogs.com/ZJOE80/p/7810533.html)\n\n\n说到分布式事务，就会谈到那个经典的”账号转账”问题：2个账号，分布处于2个不同的DB，或者说2个不同的子系统里面，A要扣钱，B要加钱，如何保证原子性？\n\n一般的思路都是通过消息中间件来实现“最终一致性”：A系统扣钱，然后发条消息给中间件，B系统接收此消息，进行加钱。\n\n但这里面有个问题：A是先update DB，后发送消息呢？ 还是先发送消息，后update DB？\n\n假设先update DB成功，发送消息网络失败，重发又失败，怎么办？\n假设先发送消息成功，update DB失败。消息已经发出去了，又不能撤回，怎么办？\n\n所以，这里下个结论： 只要发送消息和update DB这2个操作不是原子的，无论谁先谁后，都是有问题的。\n\n那这个问题怎么解决呢？\n\n# 错误的方案0\n\n有人可能想到了，我可以把“发送消息”这个网络调用和update DB放在同1个事务里面，如果发送消息失败，update DB自动回滚。这样不就保证2个操作的原子性了吗？\n\n这个方案看似正确，其实是错误的，原因有2：\n\n（1）网络的2将军问题：发送消息失败，发送方并不知道是消息中间件真的没有收到消息呢？还是消息已经收到了，只是返回response的时候失败了？\n\n    如果是已经收到消息了，而发送端认为没有收到，执行update db的回滚操作。则会导致A账号的钱没有扣，B账号的钱却加了。\n\n（2）把网络调用放在DB事务里面，可能会因为网络的延时，导致DB长事务。严重的，会block整个DB。这个风险很大。\n\n    基于以上分析，我们知道，这个方案其实是错误的！\n\n# 方案1–业务方自己实现\n\n假设消息中间件没有提供“事务消息”功能，比如你用的是Kafka。那如何解决这个问题呢？\n\n解决方案如下：\n（1）Producer端准备1张消息表，把update DB和insert message这2个操作，放在一个DB事务里面。\n\n（2）准备一个后台程序，源源不断的把消息表中的message传送给消息中间件。失败了，不断重试重传。允许消息重复，但消息不会丢，顺序也不会打乱。\n\n（3）Consumer端准备一个判重表。处理过的消息，记在判重表里面。实现业务的幂等。但这里又涉及一个原子性问题：如果保证消息消费 + insert message到判重表这2个操作的原子性？\n\n消费成功，但insert判重表失败，怎么办？关于这个，在Kafka的源码分析系列，第1篇， exactly once问题的时候，有过讨论。\n\n通过上面3步，我们基本就解决了这里update db和发送网络消息这2个操作的原子性问题。\n\n但这个方案的一个缺点就是：需要设计DB消息表，同时还需要一个后台任务，不断扫描本地消息。导致消息的处理和业务逻辑耦合额外增加业务方的负担。\n\n# 方案2 – RocketMQ 事务消息\n\n为了能解决该问题，同时又不和业务耦合，RocketMQ提出了“事务消息”的概念。\n\n具体来说，就是把消息的发送分成了2个阶段：Prepare阶段和确认阶段。\n\n具体来说，上面的2个步骤，被分解成3个步骤：\n(1) 发送Prepared消息\n(2) update DB\n(3) 根据update DB结果成功或失败，Confirm或者取消Prepared消息。\n\n可能有人会问了，前2步执行成功了，最后1步失败了怎么办？这里就涉及到了RocketMQ的关键点：RocketMQ会定期（默认是1分钟）扫描所有的Prepared消息，询问发送方，到底是要确认这条消息发出去？还是取消此条消息？\n\n具体代码实现如下：\n\n也就是定义了一个checkListener，RocketMQ会回调此Listener，从而实现上面所说的方案。\n\n```\n// 也就是上文所说的，当RocketMQ发现`Prepared消息`时，会根据这个Listener实现的策略来决断事务\nTransactionCheckListener transactionCheckListener = new TransactionCheckListenerImpl();\n// 构造事务消息的生产者\nTransactionMQProducer producer = new TransactionMQProducer(\"groupName\");\n// 设置事务决断处理类\nproducer.setTransactionCheckListener(transactionCheckListener);\n// 本地事务的处理逻辑，相当于示例中检查Bob账户并扣钱的逻辑\nTransactionExecuterImpl tranExecuter = new TransactionExecuterImpl();\nproducer.start()\n// 构造MSG，省略构造参数\nMessage msg = new Message(......);\n// 发送消息\nSendResult sendResult = producer.sendMessageInTransaction(msg, tranExecuter, null);\nproducer.shutdown();\n\n```\n\n然后执行本地事务，具体代码如下\n\n```\npublic TransactionSendResult sendMessageInTransaction(.....)  {\n    // 逻辑代码，非实际代码\n    // 1.发送消息\n    sendResult = this.send(msg);\n    // sendResult.getSendStatus() == SEND_OK\n    // 2.如果消息发送成功，处理与消息关联的本地事务单元\n    LocalTransactionState localTransactionState = tranExecuter.executeLocalTransactionBranch(msg, arg);\n    // 3.结束事务\n    this.endTransaction(sendResult, localTransactionState, localException);\n}\n```\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407220221.png)\n\n上面所说的消息中间件上注册的listener，超时以后会回调producer的接口以确定事务执行情况\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407220230.png)\n\n总结：对比方案2和方案1，RocketMQ最大的改变，其实就是把“扫描消息表”这个事情，不让业务方做，而是消息中间件帮着做了。\n\n至于消息表，其实还是没有省掉。因为消息中间件要询问发送方，事物是否执行成功，还是需要一个“变相的本地消息表”，记录事物执行状态。\n\n# 人工介入\n\n可能有人又要说了，无论方案1，还是方案2，发送端把消息成功放入了队列，但消费端消费失败怎么办？\n\n消费失败了，重试，还一直失败怎么办？是不是要自动回滚整个流程？\n\n答案是人工介入。从工程实践角度讲，这种整个流程自动回滚的代价是非常巨大的，不但实现复杂，还会引入新的问题。比如自动回滚失败，又怎么处理？\n\n对应这种极低概率的case，采取人工处理，会比实现一个高复杂的自动化回滚系统，更加可靠，也更加简单。\n\n\n\n"
  },
  {
    "path": "docs/distributed/practice/搞懂分布式技术：分布式ID生成方案.md",
    "content": "# 目录\n\n  * [**一、需求缘起**](#一、需求缘起)\n  * [**二、常见方法、不足与优化**](#二、常见方法、不足与优化)\n  * [**方法二：单点批量ID生成服务**](#方法二：单点批量id生成服务)\n  * [**方法三：uuid/guid**](#方法三：uuidguid)\n  * [**方法四：取当前毫秒数**](#方法四：取当前毫秒数)\n\n\n\n本文内容参考网络，侵删\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本文也将同步到我的个人博客：\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n该系列博文会告诉你什么是分布式系统，这对后端工程师来说是很重要的一门学问，我们会逐步了解常见的分布式技术、以及一些较为常见的分布式系统概念，同时也需要进一步了解zookeeper、分布式事务、分布式锁、负载均衡等技术，以便让你更完整地了解分布式技术的具体实战方法，为真正应用分布式技术做好准备。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->  \n\n转自：58沈剑架构师之路2017-06-25\n\n## **一、需求缘起**\n\n几乎所有的业务系统，都有生成一个唯一记录标识的需求，例如：\n\n*   消息标识：message-id\n\n*   订单标识：order-id\n\n*   帖子标识：tiezi-id\n\n这个记录标识往往就是数据库中的**主键**，数据库上会建立**聚集索引**（cluster index），即在物理存储上以这个字段排序。\n\n这个记录标识上的查询，往往又有分页或者排序的业务需求，例如：\n\n*   拉取最新的一页消息\n\n    select message-id/ order by time/ limit 100\n\n*   拉取最新的一页订单\n\n    select order-id/ order by time/ limit 100\n\n*   拉取最新的一页帖子\n\n    select tiezi-id/ order by time/ limit 100\n\n所以往往要有一个time字段，并且在time字段上建立**普通索引**（non-cluster index）。\n\n普通索引存储的是实际记录的指针，其访问效率会比聚集索引慢，如果记录标识在生成时能够基本按照时间有序，则可以省去这个time字段的索引查询：\n\nselect message-id/ (order by message-id)/limit 100\n\n强调，能这么做的前提是，message-id的生成基本是**趋势时间递增的**。\n\n这就引出了记录标识生成（也就是上文提到的三个XXX-id）的两大核心需求：\n\n*   全局唯一\n\n*   趋势有序\n\n这也是本文要讨论的核心问题：**如何高效生成趋势有序的全局唯一ID。**\n\n## **二、常见方法、不足与优化**\n\n**方法一：使用数据库的auto_increment来生成全局唯一递增ID**\n\n**优点：**\n\n*   简单，使用数据库已有的功能\n\n*   能够保证唯一性\n\n*   能够保证递增性\n\n*   步长固定\n\n**缺点：**\n\n*   可用性难以保证：数据库常见架构是一主多从+读写分离，生成自增ID是写请求，主库挂了就玩不转了\n\n*   扩展性差，性能有上限：因为写入是单点，数据库主库的写性能决定ID的生成性能上限，并且难以扩展\n\n**改进方法：**\n\n*   冗余主库，避免写入单点\n\n*   数据水平切分，保证各主库生成的ID不重复\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/74f8cc978f9046e38e613d4d0d30698d.jpg)\n\n如上图所述，由1个写库变成3个写库，**每个写库设置不同的auto_increment初始值，以及相同的增长步长**，以保证每个数据库生成的ID是不同的（上图中库0生成0,3,6,9…，库1生成1,4,7,10，库2生成2,5,8,11…）\n\n改进后的架构保证了可用性，但**缺点**是：\n\n*   丧失了ID生成的“绝对递增性”：先访问库0生成0,3，再访问库1生成1，可能导致在非常短的时间内，ID生成不是绝对递增的（这个问题不大，目标是趋势递增，不是绝对递增）\n\n*   数据库的写压力依然很大，每次生成ID都要访问数据库\n\n为了解决上述两个问题，引出了第二个常见的方案。\n\n## **方法二：单点批量ID生成服务**\n\n分布式系统之所以难，很重要的原因之一是“没有一个全局时钟，难以保证绝对的时序”，要想保证绝对的时序，还是只能使用单点服务，用本地时钟保证“绝对时序”。\n\n数据库写压力大，是因为每次生成ID都访问了数据库，可以使用批量的方式降低数据库写压力。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/93ae092c4e8c4d3aa92a59afa81abb53.jpg)\n\n如上图所述，数据库使用双master保证可用性，数据库中只存储当前ID的最大值，例如0。\n\nID生成服务假设每次批量拉取6个ID，服务访问数据库，将当前ID的最大值修改为5，这样应用访问ID生成服务索要ID，ID生成服务不需要每次访问数据库，就能依次派发0,1,2,3,4,5这些ID了。\n\n当ID发完后，再将ID的最大值修改为11，就能再次派发6,7,8,9,10,11这些ID了，于是数据库的压力就降低到原来的1/6。\n\n**优点**：\n\n*   保证了ID生成的绝对递增有序\n\n*   大大的降低了数据库的压力，ID生成可以做到每秒生成几万几十万个\n\n**缺点**：\n\n*   服务仍然是单点\n\n*   如果服务挂了，服务重启起来之后，继续生成ID可能会不连续，中间出现空洞（服务内存是保存着0,1,2,3,4,5，数据库中max-id是5，分配到3时，服务重启了，下次会从6开始分配，4和5就成了空洞，不过这个问题也不大）\n\n*   虽然每秒可以生成几万几十万个ID，但毕竟还是有性能上限，无法进行水平扩展\n\n**改进方法**：\n\n单点服务的常用高可用优化方案是“备用服务”，也叫“影子服务”，所以我们能用以下方法优化上述缺点（1）：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/449c222f7c344209af44df830479ddde.jpg)\n\n如上图，对外提供的服务是主服务，有一个影子服务时刻处于备用状态，当主服务挂了的时候影子服务顶上。\n\n这个切换的过程对调用方是透明的，可以自动完成，常用的技术是vip+keepalived，具体就不在这里展开。\n\n另外，ID-gen-service也可以实施水平扩展，以解决上述缺点（3），但会引发一致性问题，具体解决方案详见《》。\n\n## **方法三：uuid/guid**\n\n不管是通过数据库，还是通过服务来生成ID，业务方Application都需要进行一次远程调用，比较耗时。\n\n有没有一种本地生成ID的方法，即高性能，又时延低呢？\n\nuuid是一种常见的方案：\n\nstring ID =GenUUID();\n\n**优点**：\n\n*   本地生成ID，不需要进行远程调用，时延低\n\n*   扩展性好，基本可以认为没有性能上限\n\n**缺点**：\n\n*   无法保证趋势递增\n\n*   uuid过长，往往用字符串表示，作为主键建立索引查询效率低，常见优化方案为“转化为两个uint64整数存储”或者“折半存储”（折半后不能保证唯一性）\n\n## **方法四：取当前毫秒数**\n\nuuid是一个本地算法，生成性能高，但无法保证趋势递增，且作为字符串ID检索效率低，有没有一种能保证递增的本地算法呢？\n\n取当前毫秒数是一种常见方案：\n\nuint64 ID = GenTimeMS();\n\n**优点**：\n\n*   本地生成ID，不需要进行远程调用，时延低\n\n*   生成的ID趋势递增\n\n*   生成的ID是整数，建立索引后查询效率高\n\n**缺点**：\n\n*   如果并发量超过1000，会生成重复的ID\n\n这个缺点要了命了，不能保证ID的唯一性。当然，使用微秒可以降低冲突概率，但每秒最多只能生成1000000个ID，再多的话就一定会冲突了，所以使用微秒并不从根本上解决问题。\n\n**方法五：类snowflake算法**\n\nsnowflake是twitter开源的分布式ID生成算法，其**核心思想为，**一个long型的ID：\n\n*   41bit作为毫秒数\n\n*   10bit作为机器编号\n\n*   12bit作为毫秒内序列号\n\n算法单机每秒内理论上最多可以生成1000*(2^12)，也就是400W的ID，完全能满足业务的需求。\n\n借鉴snowflake的思想，结合各公司的业务逻辑和并发量，可以实现**自己的分布式ID生成算法**。\n\n举例，假设某公司ID生成器服务的需求如下：\n\n*   单机高峰并发量小于1W，预计未来5年单机高峰并发量小于10W\n\n*   有2个机房，预计未来5年机房数量小于4个\n\n*   每个机房机器数小于100台\n\n*   目前有5个业务线有ID生成需求，预计未来业务线数量小于10个\n\n*   …\n\n分析过程如下：\n\n*   高位取从2017年1月1日到现在的毫秒数（假设系统ID生成器服务在这个时间之后上线），假设系统至少运行10年，那至少需要10年*365天*24小时*3600秒*1000毫秒=320*10^9，差不多预留39bit给毫秒数\n\n*   每秒的单机高峰并发量小于10W，即平均每毫秒的单机高峰并发量小于100，差不多预留7bit给每毫秒内序列号\n\n*   5年内机房数小于4个，预留2bit给机房标识\n\n*   每个机房小于100台机器，预留7bit给每个机房内的服务器标识\n\n*   业务线小于10个，预留4bit给业务线标识\n\n这样设计的64bit标识，可以保证：\n\n*   每个业务线、每个机房、每个机器生成的ID都是不同的\n\n*   同一个机器，每个毫秒内生成的ID都是不同的\n\n*   同一个机器，同一个毫秒内，以序列号区区分保证生成的ID是不同的\n\n*   将毫秒数放在最高位，保证生成的ID是趋势递增的\n\n**缺点**：\n\n*   由于“没有一个全局时钟”，每台服务器分配的ID是绝对递增的，但从全局看，生成的ID只是趋势递增的（有些服务器的时间早，有些服务器的时间晚）\n"
  },
  {
    "path": "docs/distributed/practice/搞懂分布式技术：分布式session解决方案与一致性hash.md",
    "content": "# 目录\n\n* [一、问题的提出](#一、问题的提出)\n  * [1. 什么是Session？](#1-什么是session？)\n  * [2. 什么是Session一致性问题？](#2-什么是session一致性问题？)\n* [二、Session一致性解决方案](#二、session一致性解决方案)\n  * [1. Session Stiky](#1-session-stiky)\n  * [2. Session Replication](#2-session-replication)\n  * [3. Session数据集中存储](#3-session数据集中存储)\n  * [4. Cookie Based](#4-cookie-based)\n* [三、总结](#三、总结)\n  * [一致性Hash概述](#一致性hash概述)\n  * [一致性hash的特性](#一致性hash的特性)\n  * [虚拟节点](#虚拟节点)\n  * [均匀一致性hash](#均匀一致性hash)\n  * [总结](#总结)\n\n\n\n本文内容参考网络，侵删\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本文也将同步到我的个人博客：\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n该系列博文会告诉你什么是分布式系统，这对后端工程师来说是很重要的一门学问，我们会逐步了解常见的分布式技术、以及一些较为常见的分布式系统概念，同时也需要进一步了解zookeeper、分布式事务、分布式锁、负载均衡等技术，以便让你更完整地了解分布式技术的具体实战方法，为真正应用分布式技术做好准备。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->  \n# 一、问题的提出\n\n## 1. 什么是Session？\n\n用户使用网站的服务，需要使用浏览器与Web服务器进行多次交互。HTTP协议本身是无状态的，需要基于HTTP协议支持会话状态（Session State）的机制。具体的实现方式是：在会话开始时，分配一个  \n唯一的会话标识（SessionID），并通过Cookie将这个标识告诉浏览器，以后每次请求的时候，浏览器都会带上这个会话标识SessionID来告诉Web服务器这个请求是属于哪个会话的。在Web服务器上，各个会话都有独立的存储，保存不同会话的信息。如果遇到禁用Cookie的情况，一般的做法就是把这个会话标识放到URL的参数中。\n\n## 2. 什么是Session一致性问题？\n\n当Web服务器从一台变为多台时，就会出现Session一致性问题。\n\n![](http://img.kiterunner.top/distributed_session_consistent_situation.png)\n\n如上图所示，当一个带有会话标识的HTTP请求到了Web服务器后，需要在HTTP请求的处理过程中找到对应的会话数据（Session）。但是，现在存在的问题就是：如果我第一次访问网站时请求落到了左边的服务器，那么我的Session就创建在左边的服务器上了，如果我们不做处理，就不能保证接下来的请求每次都落在同一边的服务器上了。这就是Session一致性问题。\n\n# 二、Session一致性解决方案\n\n## 1. Session Stiky\n\n在单机的情况下，会话保存在单机上，请求也是由这个机器处理，因此不会有问题。当Web服务器变为多台以后，如果保证同一个会话的请求都在同一个Web服务器上处理，则对该会话来说，与之前单机的情况是一样的。\n\n如果要做到这样，就需要负载均衡器能够根据每次请求的会话标识SessionID来进行请求转发，如下图所示。这种方式称之为**Session Stiky**方式。\n\n![](http://img.kiterunner.top/distributed_session_stiky.png)\n\n该方案本身非常简单，对于Web服务器来说，该方案和单机的情况是一样的，只是我们在负载均衡器上做了手脚。这个方案可以让同样Session的请求每次都发送到同一个Web服务器来处理，非常利于针对Session进行服务端本地的缓存。\n\n其所存在的问题包括：\n\n*   如果有一台Web服务器宕机或者重启，则该机器上的会话数据就会丢失。如果会话中有登录状态数据，则用户需要重新登陆。\n*   会话标识是应用层的信息，则负载均衡器要将同一个会话的请求都保存到同一个Web服务器上的话，就需要进行应用层（七层）的解析，这个开销比第四层的交换要大。\n*   负载均衡器变为了一个有状态的节点，要将会话保存到具体Web服务器的映射，因此内存消耗会更大，容灾会更麻烦。\n\n打个比方来说，对于Session Stiky，如果说Web服务器是我们每次吃饭的饭店，会话数据就是我们吃饭用的碗筷。要保证每次吃饭都用自己的碗筷，我就把餐具存在某一家，并且每次都去这家店吃，这是个不错的主意。\n\n## 2. Session Replication\n\n如果我们继续以去饭店吃饭类比，那么除了前面的方式之外，如果我在每个店都存放一套自己的餐具，就可以更加自由地选择饭店。Session Replication就是这样一种方式，如下图所示。\n\n![](http://img.kiterunner.top/distributed_session_replication.png)\n\n可以看到，在Session Replication方案中，不再要求负载均衡器来保证同一个会话地多次请求必须到同一个Web服务器上了。而我们的Web服务器之间则增加了会话数据的同步。通过同步就保证了不同Web服务器之间的Session数据的一致。\n\n但是，Session Replication方案也存在一些问题，包括：\n\n*   同步Session数据造成了网络带宽的开销。只要Session数据有变化，就需要将数据同步到其他所有机器上，机器数越多，同步带来的网络带宽开销就越大。\n\n*   每台Web服务器都要保存所有的Session数据，如果整个集群的Session数很多的话，每台机器用于保存Session数据的内容占用会很严重。\n\n这就是Session Replication方案。这个方案是靠应用容器来完成Session的复制从而使得应用解决Session问题的，应用本身并不关心这个事情。不过，这个方案并不适合集群机器数多的场景。如果只有几台机器，用该方案是可以的。\n\n## 3. Session数据集中存储\n\n同样是希望同一个会话的请求可以发到不同的Web服务器上，前面的Session Replication是一种方案，还有一种方案就是把Session数据集中存储起来，然后不同Web服务器从同样的地方来获取Session。其大概的结构如下图所示：\n\n![](http://img.kiterunner.top/distributed_session_center_store.png)\n\n可以看到，与Session Replication方案一样的部分是，会话请求经过负载均衡器后，不会被固定在同样的Web服务器上。不同的地方是，Web服务器之间没有Session数据复制，并且Session数据也不是保存在本机了，而是放在了另一个集中存储的地方。这样，无论是哪台Web服务器，也无论修改的是哪个Session的数据，最终的修改都发生在这个集中存储的地方，而Web服务器使用Session数据时，也是从这个集中存储Session数据的地方来读取。对于Session数据存储的具体方式，可以使用数据库，也可以使用其他分布式存储系统。这个方案解决了Session Replication方案中内存的问题，而对于网络带宽，该方案也比Session Replication要好。\n\n不过，该方案仍存在一些问题，包括：\n\n*   读写Session数据引入了网络操作，这相对于本机的数据读取来说，问题就在于存在时延和不稳定性，不过由于通信基本发生在内网，问题不大。\n*   如果集中存储Session的机器或者集群存在问题，这就会影响我们的应用。\n\n相对于Session Replication，当Web服务器数量比较大时、Session数比较多的时候，集中存储方案的优势是非常明显的。\n\n## 4. Cookie Based\n\n对于Cookie Based方案，它对同一个会话的不同请求也是不限制具体处理机器的。与Session Replication和Session数据集中管理的方案不同，这个方案是通过Cookie来传递Session数据的。具体如下图所示。\n\n![](http://img.kiterunner.top/distributed_session_cookie_based.png)\n\n可以看出，我们的Session数据存放在Cookie中，然后在Web服务器上从Cookie中生成对应的Session数据。这就好比我每次都把自己的碗筷带在身上，这样我去哪家饭店吃饭就可以随意选择了。相对于前面的集中存储，这个方案不会依赖外部的一个存储系统，也就不存在从外部系统获取、写入Session数据的网络时延和不稳定性了。\n\n不过，该方案依然存在不足，包括：\n\n*   Cookie长度限制。由于Cookie是有长度限制的，这也会限制Session数据的长度。\n*   安全性。Session数据本来都是服务器端数据，而这个方案是让这些服务端数据到了外部外部网络及客户端，因此存在安全性的问题。\n*   带宽消耗。这里指的不是内部Web服务器之间的带宽的消耗，而是我们数据中心的整体外部贷款的消耗。\n*   性能消耗。每次HTTP请求和响应都带有Session数据，对Web服务器来说，在同样的处理情况下，响应的结果输出越少，支持的并发请求就会越多。\n\n# 三、总结\n\n综合而言，上述所有方案都是解决session问题的方案，对于大型网站来说，Session Sticky和Session集中管理是比较好的方案。\n\n\n\n\n在解决分布式系统中负载均衡的问题时候可以使用Hash算法让固定的一部分请求落到同一台服务器上，这样每台服务器固定处理一部分请求（并维护这些请求的信息），起到负载均衡的作用。\n\n但是普通的余数hash（hash(比如用户id)%服务器机器数）算法伸缩性很差，当新增或者下线服务器机器时候，用户id与服务器的映射关系会大量失效。一致性hash则利用hash环对其进行了改进。\n\n## 一致性Hash概述\n\n为了能直观的理解一致性hash原理，这里结合一个简单的例子来讲解，假设有4台服务器，地址为ip1,ip2,ip3,ip4。\n\n一致性hash是首先计算四个ip地址对应的hash值hash(ip1),hash(ip2),hash(ip3),hash(ip3)，计算出来的hash值是0~最大正整数直接的一个值，这四个值在一致性hash环上呈现如下图：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407214157.png)\n\n\n\nhash环上顺时针从整数0开始，一直到最大正整数，我们根据四个ip计算的hash值肯定会落到这个hash环上的某一个点，至此我们把服务器的四个ip映射到了一致性hash环当用户在客户端进行请求时候，首先根据hash(用户id)计算路由规则（hash值），然后看hash值落到了hash环的那个地方，根据hash值在hash环上的位置顺时针找距离最近的ip作为路由ip.  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407214219.png)\n\n\n\n如上图可知user1,user2的请求会落到服务器ip2进行处理，User3的请求会落到服务器ip3进行处理，user4的请求会落到服务器ip4进行处理，user5,user6的请求会落到服务器ip1进行处理。\n\n下面考虑当ip2的服务器挂了的时候会出现什么情况？\n\n当ip2的服务器挂了的时候，一致性hash环大致如下图：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407214245.png)\n\n\n\n根据顺时针规则可知user1,user2的请求会被服务器ip3进行处理，而其它用户的请求对应的处理服务器不变，也就是只有之前被ip2处理的一部分用户的映射关系被破坏了，并且其负责处理的请求被顺时针下一个节点委托处理。\n\n下面考虑当新增机器的时候会出现什么情况？\n\n当新增一个ip5的服务器后，一致性hash环大致如下图：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407214301.png)\n\n\n\n根据顺时针规则可知之前user1的请求应该被ip1服务器处理，现在被新增的ip5服务器处理，其他用户的请求处理服务器不变，也就是新增的服务器顺时针最近的服务器的一部分请求会被新增的服务器所替代。\n\n## 一致性hash的特性\n\n单调性(Monotonicity)，单调性是指如果已经有一些请求通过哈希分派到了相应的服务器进行处理，又有新的服务器加入到系统中时候，应保证原有的请求可以被映射到原有的或者新的服务器中去，而不会被映射到原来的其它服务器上去。 这个通过上面新增服务器ip5可以证明，新增ip5后，原来被ip1处理的user6现在还是被ip1处理，原来被ip1处理的user5现在被新增的ip5处理。分散性(Spread)：分布式环境中，客户端请求时候可能不知道所有服务器的存在，可能只知道其中一部分服务器，在客户端看来他看到的部分服务器会形成一个完整的hash环。如果多个客户端都把部分服务器作为一个完整hash环，那么可能会导致，同一个用户的请求被路由到不同的服务器进行处理。这种情况显然是应该避免的，因为它不能保证同一个用户的请求落到同一个服务器。所谓分散性是指上述情况发生的严重程度。平衡性(Balance)：平衡性也就是说负载均衡，是指客户端hash后的请求应该能够分散到不同的服务器上去。一致性hash可以做到每个服务器都进行处理请求，但是不能保证每个服务器处理的请求的数量大致相同，如下图\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407214309.png)\n\n\n\n服务器ip1,ip2,ip3经过hash后落到了一致性hash环上，从图中hash值分布可知ip1会负责处理大概80%的请求，而ip2和ip3则只会负责处理大概20%的请求，虽然三个机器都在处理请求，但是明显每个机器的负载不均衡，这样称为一致性hash的倾斜，虚拟节点的出现就是为了解决这个问题。\n\n## 虚拟节点\n\n当服务器节点比较少的时候会出现上节所说的一致性hash倾斜的问题，一个解决方法是多加机器，但是加机器是有成本的，那么就加虚拟节点，比如上面三个机器，每个机器引入1个虚拟节点后的一致性hash环的图如下：  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407214322.png)\n\n\n\n其中ip1-1是ip1的虚拟节点，ip2-1是ip2的虚拟节点，ip3-1是ip3的虚拟节点。\n\n可知当物理机器数目为M，虚拟节点为N的时候，实际hash环上节点个数为M*（N+1）。比如当客户端计算的hash值处于ip2和ip3或者处于ip2-1和ip3-1之间时候使用ip3服务器进行处理。\n\n## 均匀一致性hash\n\n上节我们使用虚拟节点后的图看起来比较均衡，但是如果生成虚拟节点的算法不够好很可能会得到下面的环：  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407214331.png)\n\n\n可知每个服务节点引入1个虚拟节点后，情况相比没有引入前均衡性有所改善，但是并不均衡。\n\n均衡的一致性hash应该是如下图：  \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407214345.png)\n\n\n均匀一致性hash的目标是如果服务器有N台，客户端的hash值有M个，那么每个服务器应该处理大概M/N个用户的。也就是每台服务器负载尽量均衡。dubbo提供的一致性hash负载均衡算法就是不均匀的，我们自己实现了dubbo的spi扩展实现了均匀一致性hash.\n\n## 总结\n\n在分布式系统中一致性hash起着不可忽略的地位，无论是分布式缓存，还是分布式Rpc框架的负载均衡策略都有所使用。\n"
  },
  {
    "path": "docs/distributed/practice/搞懂分布式技术：分布式一致性协议与Paxos，Raft算法.md",
    "content": "\n\n# 目录\n\n* [2PC](#2pc)\n    * [1. 分布式事务](#1-分布式事务)\n    * [2. XA规范](#2-xa规范)\n    * [3. 二阶段提交（2PC）](#3-二阶段提交（2pc）)\n    * [小结](#小结)\n* [3PC](#3pc)\n    * [1. 三阶段提交的定义](#1-三阶段提交的定义)\n    * [2. 三阶段提交的过程](#2-三阶段提交的过程)\n    * [3. 小结](#3-小结)\n* [四、Paxos](#四、paxos)\n* [BasicPaxos与FastPaxos](#basicpaxos与fastpaxos)\n* [五、Raft](#五、raft)\n\n\n本文内容参考网络，侵删\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本文也将同步到我的个人博客：\n\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n该系列博文会告诉你什么是分布式系统，这对后端工程师来说是很重要的一门学问，我们会逐步了解常见的分布式技术、以及一些较为常见的分布式系统概念，同时也需要进一步了解zookeeper、分布式事务、分布式锁、负载均衡等技术，以便让你更完整地了解分布式技术的具体实战方法，为真正应用分布式技术做好准备。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n本文较为粗略地讲述了一致性协议与两种一致性算法，更加系统的理论可以参考后面的分布式系统理论专题文章。\n\n## 2PC\n\n由于BASE理论需要在一致性和可用性方面做出权衡，因此涌现了很多关于一致性的算法和协议。其中比较著名的有二阶提交协议（2 Phase Commitment Protocol），三阶提交协议（3 Phase Commitment Protocol）和Paxos算法。\n本文要介绍的2PC协议，分为两个阶段提交一个事务。并通过协调者和各个参与者的配合，实现分布式一致性。\n\n\n两个阶段事务提交协议，由协调者和参与者共同完成。\n\n角色\tXA概念\t作用\n协调者\t事务管理器\t协调各个参与者，对分布式事务进行提交或回滚\n参与者\t资源管理器\t分布式集群中的节点\n\n### 1. 分布式事务\n\n分布式事务是指会涉及到操作多个数据库的事务，其实就是将对同一库事务的概念扩大到了对多个库的事务。目的是为了保证分布式系统中的数据一致性。\n\n分布式事务处理的关键是：\n\n需要记录事务在任何节点所做的所有动作；\n事务进行的所有操作要么全部提交，要么全部回滚。\n\n### 2. XA规范\n\n2.1. XA规范的组成\nXA规范是由 X/Open组织（即现在的 Open Group ）定义的分布式事务处理模型。 X/Open DTP 模型（ 1994 ）包括：\n\n应用程序（ AP ）\n事务管理器（ TM ）：交易中间件等\n资源管理器（ RM ）：关系型数据库等\n通信资源管理器（ CRM ）：消息中间件等\n2.2. XA规范的定义\nXA规范定义了交易中间件与数据库之间的接口规范（即接口函数），交易中间件用它来通知数据库事务的开始、结束以及提交、回滚等。而XA接口函数由数据库厂商提供。\n\n二阶提交协议和三阶提交协议就是基于XA规范提出的其中，二阶段提交就是实现XA分布式事务的关键。\n\n2.3. XA规范编程规范\n配置TM，给TM注册RM作为数据源。其中，一个TM可以注册多个RM。\n\nAP向TM发起一个全局事务。这时，TM会发送一个XID（全局事务ID）通知各个RM。\n\nAP从TM获取资源管理器的代理（例如：使用JTA接口，从TM管理的上下文中，获取出这个TM所管理的RM的JDBC连接或JMS连接）。\n\nAP通过从TM中获取的连接，间接操作RM进行业务操作。TM在每次AP操作时把XID传递给RM，RM正是通过这个XID关联来操作和事务的关系的。\n\nAP结束全局事务时，TM会通知RM全局事务结束。开始二段提交，也就是prepare - commit的过程。\n\nXA规范的流程，大致如图所示：\n\n\n### 3. 二阶段提交（2PC）\n\n3.1. 二阶段提交的定义\n二阶段提交的算法思路可以概括为：每个参与者将操作成败通知协调者，再由协调者根据所有参与者的反馈情报，决定各参与者是否要提交操作还是中止操作。\n\n所谓的两个阶段分别是：\n\n第一阶段：准备阶段（投票阶段）\n第二阶段：提交阶段（执行阶段）\n3.1.1. 准备阶段\n准备阶段分为三个步骤：\n\n\na. 事务询问\n\n协调者向所有的参与者询问，是否准备好了执行事务，并开始等待各参与者的响应。\n\nb. 执行事务\n\n各参与者节点执行事务操作。如果本地事务成功，将Undo和Redo信息记入事务日志中，但不提交；否则，直接返回失败，退出执行。\n\nc. 各参与者向协调者反馈事务询问的响应\n\n如果参与者成功执行了事务操作，那么就反馈给协调者 Yes响应，表示事务可以执行提交；如果参与者没有成功执行事务，就返回No给协调者，表示事务不可以执行提交。\n\n3.1.2. 提交阶段\n在提交阶段中，会根据准备阶段的投票结果执行2种操作：执行事务提交，中断事务。\n\n提交事务过程如下：\n\n\na. 发送提交请求\n\n协调者向所有参与者发出commit请求。\n\nb. 事务提交\n\n参与者收到commit请求后，会正式执行事务提交操作，并在完成提交之后，释放整个事务执行期间占用的事务资源。\n\nc. 反馈事务提交结果\n\n参与者在完成事务提交之后，向协调者发送Ack信息。\n\nd. 事务提交确认\n\n协调者接收到所有参与者反馈的Ack信息后，完成事务。\n\n中断事务过程如下：\n\n\na. 发送回滚请求\n\n协调者向所有参与者发出Rollback请求。\n\nb. 事务回滚\n\n参与者接收到Rollback请求后，会利用其在提交阶段种记录的Undo信息，来执行事务回滚操作。在完成回滚之后，释放在整个事务执行期间占用的资源。\n\nc. 反馈事务回滚结果\n\n参与者在完成事务回滚之后，想协调者发送Ack信息。\n\nd. 事务中断确认\n\n协调者接收到所有参与者反馈的Ack信息后，完成事务中断。\n\n3.1. 二阶段提交的优缺点\n优点：原理简单，实现方便。\n缺点：同步阻塞，单点问题，数据不一致，容错性不好。\n同步阻塞\n在二阶段提交的过程中，所有的节点都在等待其他节点的响应，无法进行其他操作。这种同步阻塞极大的限制了分布式系统的性能。\n\n单点问题\n协调者在整个二阶段提交过程中很重要，如果协调者在提交阶段出现问题，那么整个流程将无法运转。更重要的是，其他参与者将会处于一直锁定事务资源的状态中，而无法继续完成事务操作。\n\n数据不一致\n假设当协调者向所有的参与者发送commit请求之后，发生了局部网络异常，或者是协调者在尚未发送完所有 commit请求之前自身发生了崩溃，导致最终只有部分参与者收到了commit请求。这将导致严重的数据不一致问题。\n\n容错性不好\n如果在二阶段提交的提交询问阶段中，参与者出现故障，导致协调者始终无法获取到所有参与者的确认信息，这时协调者只能依靠其自身的超时机制，判断是否需要中断事务。显然，这种策略过于保守。换句话说，二阶段提交协议没有设计较为完善的容错机制，任意一个节点是失败都会导致整个事务的失败。\n\n### 小结\n\n对于2PC协议存在的同步阻塞、单点问题，将在下一篇文章的3PC协议中引入解决方案。\n\n\n\n\n## 3PC\n\n由于二阶段提交存在着诸如同步阻塞、单点问题、脑裂等缺陷。所以，研究者们在二阶段提交的基础上做了改进，提出了三阶段提交。\n\n\n与两阶段提交不同的是，三阶段提交有两个改动点。\n\n引入超时机制 - 同时在协调者和参与者中都引入超时机制。\n在第一阶段和第二阶段中插入一个准备阶段，保证了在最后提交阶段之前各参与节点的状态是一致的。\n\n### 1. 三阶段提交的定义\n\n三阶段提交（Three-phase commit），也叫三阶段提交协议（Three-phase commit protocol），是二阶段提交（2PC）的改进版本。\n\n所谓的三个阶段分别是：询问，然后再锁资源，最后真正提交。\n\n第一阶段：CanCommit\n第二阶段：PreCommit\n第三阶段：Do Commit\n\n### 2. 三阶段提交的过程\n\n2.1. 阶段一：CanCommit\n3PC的CanCommit阶段其实和2PC的准备阶段很像。协调者向参与者发送commit请求，参与者如果可以提交就返回Yes响应，否则返回No响应。\n\na. 事务询问\n\n协调者向参与者发送CanCommit请求。询问是否可以执行事务提交操作。然后开始等待参与者的响应。\n\nb. 响应反馈\n\n参与者接到CanCommit请求之后，正常情况下，如果其自身认为可以顺利执行事务，则返回Yes响应，并进入预备状态；否则反馈No。\n\n2.2. 阶段二：PreCommit\n协调者在得到所有参与者的响应之后，会根据结果执行2种操作：执行事务预提交，或者中断事务。\n\n2.2.1. 执行事务预提交\na. 发送预提交请求\n\n协调者向所有参与者节点发出 preCommit 的请求，并进入 prepared 状态。\n\nb. 事务预提交\n\n参与者受到 preCommit 请求后，会执行事务操作，对应 2PC 准备阶段中的 “执行事务”，也会 Undo 和 Redo 信息记录到事务日志中。\n\nc. 各参与者响应反馈\n\n如果参与者成功执行了事务，就反馈 ACK 响应，同时等待指令：提交（commit） 或终止（abort）。\n\n2.2.2. 中断事务\na. 发送中断请求\n\n协调者向所有参与者节点发出 abort 请求 。\n\nb. 中断事务\n\n参与者如果收到 abort 请求或者超时了，都会中断事务。\n\n2.3. 阶段三：Do Commit\n该阶段进行真正的事务提交，也可以分为以下两种情况。\n\n2.3.1. 执行提交\na. 发送提交请求\n\n协调者接收到各参与者发送的ACK响应，那么他将从预提交状态进入到提交状态。并向所有参与者发送 doCommit 请求。\n\nb. 事务提交\n\n参与者接收到 doCommit 请求之后，执行正式的事务提交。并在完成事务提交之后释放所有事务资源。\n\nc. 响应反馈\n\n事务提交完之后，向协调者发送 ACK 响应。\n\nd. 完成事务\n\n协调者接收到所有参与者的 ACK 响应之后，完成事务。\n\n2.3.2. 中断事务\n协调者没有接收到参与者发送的 ACK 响应（可能是接受者发送的不是ACK响应，也可能响应超时），那么就会执行中断事务。\n\na. 发送中断请求\n\n协调者向所有参与者发送 abort 请求。\n\nb. 事务回滚\n\n参与者接收到 abort 请求之后，利用其在阶段二记录的 undo 信息来执行事务的回滚操作，并在完成回滚之后释放所有的事务资源。\n\nc. 反馈结果\n\n参与者完成事务回滚之后，向协调者发送 ACK 消息。\n\nd. 中断事务\n\n协调者接收到参与者反馈的 ACK 消息之后，完成事务的中断。\n\n### 3. 小结\n\n3.1. 三阶段提交的优点\n相对于二阶段提交，三阶段提交主要解决的单点故障问题，并减少了阻塞的时间。\n\n因为一旦参与者无法及时收到来自协调者的信息之后，他会默认执行 commit。而不会一直持有事务资源并处于阻塞状态。\n\n3.2. 三阶段提交的缺点\n三阶段提交也会导致数据一致性问题。由于网络原因，协调者发送的 abort 响应没有及时被参与者接收到，那么参与者在等待超时之后执行了 commit 操作。\n\n这样就和其他接到 abort 命令并执行回滚的参与者之间存在数据不一致的情况。\n\n\n## 四、Paxos\n\n用于达成共识性问题，即对多个节点产生的值，该算法能保证只选出唯一一个值。\n\n主要有三类节点：\n\n提议者（Proposer）：提议一个值；\n接受者（Acceptor）：对每个提议进行投票；\n告知者（Learner）：被告知投票的结果，不参与投票过程。\n\n\n执行过程\n规定一个提议包含两个字段：[n, v]，其中 n 为序号（具有唯一性），v 为提议值。\n\n下图演示了两个 Proposer 和三个 Acceptor 的系统中运行该算法的初始过程，每个 Proposer 都会向所有 Acceptor 发送提议请求。\n\n\n\n当 Acceptor 接收到一个提议请求，包含的提议为 [n1, v1]，并且之前还未接收过提议请求，那么发送一个提议响应，设置当前接收到的提议为 [n1, v1]，并且保证以后不会再接受序号小于 n1 的提议。\n\n如下图，Acceptor X 在收到 [n=2, v=8] 的提议请求时，由于之前没有接收过提议，因此就发送一个 [no previous] 的提议响应，设置当前接收到的提议为 [n=2, v=8]，并且保证以后不会再接受序号小于 2 的提议。其它的 Acceptor 类似。\n\n\n\n如果 Acceptor 接收到一个提议请求，包含的提议为 [n2, v2]，并且之前已经接收过提议 [n1, v1]。如果 n1 > n2，那么就丢弃该提议请求；否则，发送提议响应，该提议响应包含之前已经接收过的提议 [n1, v1]，设置当前接收到的提议为 [n2, v2]，并且保证以后不会再接受序号小于 n2 的提议。\n\n如下图，Acceptor Z 收到 Proposer A 发来的 [n=2, v=8] 的提议请求，由于之前已经接收过 [n=4, v=5] 的提议，并且 n > 2，因此就抛弃该提议请求；Acceptor X 收到 Proposer B 发来的 [n=4, v=5] 的提议请求，因为之前接收到的提议为 [n=2, v=8]，并且 2 <= 4，因此就发送 [n=2, v=8] 的提议响应，设置当前接收到的提议为 [n=4, v=5]，并且保证以后不会再接受序号小于 4 的提议。Acceptor Y 类似。\n\n\n\n当一个 Proposer 接收到超过一半 Acceptor 的提议响应时，就可以发送接受请求。\n\nProposer A 接收到两个提议响应之后，就发送 [n=2, v=8] 接受请求。该接受请求会被所有 Acceptor 丢弃，因为此时所有 Acceptor 都保证不接受序号小于 4 的提议。\n\nProposer B 过后也收到了两个提议响应，因此也开始发送接受请求。需要注意的是，接受请求的 v 需要取它收到的最大 v 值，也就是 8。因此它发送 [n=4, v=8] 的接受请求。\n\n\n\nAcceptor 接收到接受请求时，如果序号大于等于该 Acceptor 承诺的最小序号，那么就发送通知给所有的 Learner。当 Learner 发现有大多数的 Acceptor 接收了某个提议，那么该提议的提议值就被 Paxos 选择出来。\n\n\n\n约束条件\n\n1. 正确性\n   指只有一个提议值会生效。\n\n因为 Paxos 协议要求每个生效的提议被多数 Acceptor 接收，并且 Acceptor 不会接受两个不同的提议，因此可以保证正确性。\n\n2. 可终止性\n   指最后总会有一个提议生效。\n\nPaxos 协议能够让 Proposer 发送的提议朝着能被大多数 Acceptor 接受的那个提议靠拢，因此能够保证可终止性。\n\n\n\n## BasicPaxos与FastPaxos\n\n1Paxos算法\n1.1基本定义\n算法中的参与者主要分为三个角色，同时每个参与者又可兼领多个角色:\n\n⑴proposer提出提案，提案信息包括提案编号和提议的value;\n\n⑵acceptor收到提案后可以接受(accept)提案;\n\n⑶learner只能\"学习\"被批准的提案;\n\n算法保重一致性的基本语义:\n\n⑴决议(value)只有在被proposers提出后才能被批准(未经批准的决议称为\"提案(proposal)\");\n\n⑵在一次Paxos算法的执行实例中，只批准(chosen)一个value;\n\n⑶learners只能获得被批准(chosen)的value;\n\n有上面的三个语义可演化为四个约束:\n\n⑴P1:一个acceptor必须接受(accept)第一次收到的提案;\n\n⑵P2a:一旦一个具有valuev的提案被批准(chosen)，那么之后任何acceptor再次接受(accept)的提案必须具有valuev;\n\n⑶P2b:一旦一个具有valuev的提案被批准(chosen)，那么以后任何proposer提出的提案必须具有valuev;\n\n⑷P2c:如果一个编号为n的提案具有valuev，那么存在一个多数派，要么他们中所有人都没有接受(accept)编号小于n的任何提案，要么他们已经接受(accpet)的所有编号小于n的提案中编号最大的那个提案具有valuev;\n\n1.2基本算法(basicpaxos)\n算法(决议的提出与批准)主要分为两个阶段:\n\n1.prepare阶段：\n\n(1).当Porposer希望提出方案V1，首先发出prepare请求至大多数Acceptor。Prepare请求内容为序列号<SN1>;\n\n(2).当Acceptor接收到prepare请求<SN1>时，检查自身上次回复过的prepare请求<SN2>\n\na).如果SN2>SN1，则忽略此请求，直接结束本次批准过程;\n\nb).否则检查上次批准的accept请求<SNx，Vx>，并且回复<SNx，Vx>；如果之前没有进行过批准，则简单回复<OK>;\n\n2.accept批准阶段：\n\n(1a).经过一段时间，收到一些Acceptor回复，回复可分为以下几种:\n\na).回复数量满足多数派，并且所有的回复都是<OK>，则Porposer发出accept请求，请求内容为议案<SN1，V1>;\n\nb).回复数量满足多数派，但有的回复为：<SN2，V2>，<SN3，V3>……则Porposer找到所有回复中超过半数的那个，假设为<SNx，Vx>，则发出accept请求，请求内容为议案<SN1，Vx>;\n\nc).回复数量不满足多数派，Proposer尝试增加序列号为SN1+，转1继续执行;\n\n(1b).经过一段时间，收到一些Acceptor回复，回复可分为以下几种:\n\na).回复数量满足多数派，则确认V1被接受;\n\nb).回复数量不满足多数派，V1未被接受，Proposer增加序列号为SN1+，转1继续执行;\n\n(2).在不违背自己向其他proposer的承诺的前提下，acceptor收到accept请求后即接受并回复这个请求。\n\n1.3算法优化(fastpaxos)\nPaxos算法在出现竞争的情况下，其收敛速度很慢，甚至可能出现活锁的情况，例如当有三个及三个以上的proposer在发送prepare请求后，很难有一个proposer收到半数以上的回复而不断地执行第一阶段的协议。因此，为了避免竞争，加快收敛的速度，在算法中引入了一个Leader这个角色，在正常情况下同时应该最多只能有一个参与者扮演Leader角色，而其它的参与者则扮演Acceptor的角色，同时所有的人又都扮演Learner的角色。\n\n在这种优化算法中，只有Leader可以提出议案，从而避免了竞争使得算法能够快速地收敛而趋于一致，此时的paxos算法在本质上就退变为两阶段提交协议。但在异常情况下，系统可能会出现多Leader的情况，但这并不会破坏算法对一致性的保证，此时多个Leader都可以提出自己的提案，优化的算法就退化成了原始的paxos算法。\n\n一个Leader的工作流程主要有分为三个阶段：\n\n(1).学习阶段向其它的参与者学习自己不知道的数据(决议);\n\n(2).同步阶段让绝大多数参与者保持数据(决议)的一致性;\n\n(3).服务阶段为客户端服务，提议案;\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407205423.png)\n\n1.3.1学习阶段\n当一个参与者成为了Leader之后，它应该需要知道绝大多数的paxos实例，因此就会马上启动一个主动学习的过程。假设当前的新Leader早就知道了1-134、138和139的paxos实例，那么它会执行135-137和大于139的paxos实例的第一阶段。如果只检测到135和140的paxos实例有确定的值，那它最后就会知道1-135以及138-140的paxos实例。\n\n1.3.2同步阶段\n此时的Leader已经知道了1-135、138-140的paxos实例，那么它就会重新执行1-135的paxos实例，以保证绝大多数参与者在1-135的paxos实例上是保持一致的。至于139-140的paxos实例，它并不马上执行138-140的paxos实例，而是等到在服务阶段填充了136、137的paxos实例之后再执行。这里之所以要填充间隔，是为了避免以后的Leader总是要学习这些间隔中的paxos实例，而这些paxos实例又没有对应的确定值。\n\n1.3.4服务阶段\nLeader将用户的请求转化为对应的paxos实例，当然，它可以并发的执行多个paxos实例，当这个Leader出现异常之后，就很有可能造成paxos实例出现间断。\n\n1.3.5问题\n(1).Leader的选举原则\n\n(2).Acceptor如何感知当前Leader的失败，客户如何知道当前的Leader\n\n(3).当出现多Leader之后，如何kill掉多余的Leader\n\n(4).如何动态的扩展Acceptor\n\n\n## 五、Raft\n\nRaft 和 Paxos 类似，但是更容易理解，也更容易实现。\n\nRaft 主要是用来竞选主节点。\n\n单个 Candidate 的竞选\n有三种节点：Follower、Candidate 和 Leader。Leader 会周期性的发送心跳包给 Follower。每个 Follower 都设置了一个随机的竞选超时时间，一般为 150ms~300ms，如果在这个时间内没有收到 Leader 的心跳包，就会变成 Candidate，进入竞选阶段。\n\n下图表示一个分布式系统的最初阶段，此时只有 Follower，没有 Leader。Follower A 等待一个随机的竞选超时时间之后，没收到 Leader 发来的心跳包，因此进入竞选阶段。\n\n\n此时 A 发送投票请求给其它所有节点。\n\n\n其它节点会对请求进行回复，如果超过一半的节点回复了，那么该 Candidate 就会变成 Leader。\n\n\n之后 Leader 会周期性地发送心跳包给 Follower，Follower 接收到心跳包，会重新开始计时。\n\n\n多个 Candidate 竞选\n如果有多个 Follower 成为 Candidate，并且所获得票数相同，那么就需要重新开始投票，例如下图中 Candidate B 和 Candidate D 都获得两票，因此需要重新开始投票。\n\n\n当重新开始投票时，由于每个节点设置的随机竞选超时时间不同，因此能下一次再次出现多个 Candidate 并获得同样票数的概率很低。\n\n\n日志复制\n来自客户端的修改都会被传入 Leader。注意该修改还未被提交，只是写入日志中。\n\n\nLeader 会把修改复制到所有 Follower。\n\n\nLeader 会等待大多数的 Follower 也进行了修改，然后才将修改提交。\n\n\n此时 Leader 会通知的所有 Follower 让它们也提交修改，此时所有节点的值达成一致。\n\n\n参考资料\n\n    倪超. 从 Paxos 到 ZooKeeper : 分布式一致性原理与实践 [M]. 电子工业出版社, 2015.\n    What is CAP theorem in distributed database system?\n    NEAT ALGORITHMS - PAXOS\n    Raft: Understandable Distributed Consensus\n    Paxos By Example\n"
  },
  {
    "path": "docs/distributed/practice/搞懂分布式技术：分布式事务常用解决方案.md",
    "content": "# 目录\n\n* [一、概述](#一、概述)\n* [二、理论](#二、理论)\n  * [2.1、二阶段提交](#21、二阶段提交)\n  * [2.2、三阶段提交](#22、三阶段提交)\n  * [2.3、TCC柔性事务](#23、tcc柔性事务)\n  * [2.4、Seata](#24、seata)\n* [三、应用场景](#三、应用场景)\n* [四、总结](#四、总结)\n* [五、参考源码](#五、参考源码)\n\n\n\n> 数据不会无缘无故丢失，也不会莫名其妙增加\n\n# 一、概述\n\n1、曾几何时，知了在一家小公司做项目的时候，都是一个服务打天下，所以涉及到数据一致性的问题，都是直接用本地事务处理。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407215926.png)\n\n2、随着时间的推移，用户量增大了，发现一个Java服务扛不住了，于是技术大佬决定对于系统进行升级。根据系统的业务对于单体的一个服务进行拆分，然后对于开发人员也进行划分，一个开发人员只开发和维护一个或几个服务中的问题，大家各司其职，分工合作。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407215943.png)\n\n3、当然服务拆分不是一蹴而就的，这是一个耗时耗力的庞大工程，大多数系统都是进行多轮拆分，而后慢慢形成一个稳定的系统。\n\n遵守一个核心思想：**先按总体业务进行一轮拆分，后面再根据拆分后的服务模块，进行一个细致的拆分。**\n\n4、随着服务拆分之后，用户量是抗住了，但是发现数据都在不同的服务中存取，这就引出了一个新的问题：**跨服务器，如何保证数据的一致性？**当然，跨服务的分布式系统中不仅仅这个问题，还有其他的一些列问题，如：服务可用性、服务容错性、服务间调用的网络问题等等，这里只讨论数据一致性问题。\n\n5、说到数据一致性，大致分为三种：强一致性、弱一致性、最终一致性。\n\n* <section>**强一致性**：数据一旦写入，在任一时刻都能读取到最新的值。</section>\n\n* <section>**弱一致性**：当写入一个数据的时候，其他地方去读这些数据，可能查到的数据不是最新的</section>\n\n* <section>**最终一致性**：它是弱一致性的一个变种，不追求系统任意时刻数据要达到一致，但是在一定时间后，数据最终要达到一致。</section>\n\n从这三种一致型的模型上来说，我们可以看到，弱一致性和最终一致性一般来说是异步冗余的，而强一致性是同步冗余的，异步处理带来了更好的性能，但也需要处理数据的补偿。同步意味着简单，但也必然会降低系统的性能。\n\n# 二、理论\n\n上述说的数据一致性问题，其实也就是在说分布式事务的问题，现在有一些解决方案，相信大家多多少少都看到过，这里带大家回顾下。\n\n## 2.1、二阶段提交\n\n2PC是一种强一致性设计方案，通过引入一个**事务协调器**来协调各个本地事务（也称为事务参与者）的提交和回滚。2PC主要分为2个阶段：\n\n**1、第一阶段**：事务协调器会向每个事务参与者发起一个开启事务的命令，每个事务参与者执行准备操作，然后再向事务协调器回复是否准备完成。但是**不会提交本地事务，**但是这个阶段资源是需要被锁住的。\n\n**2、第二阶段：**事务协调器收到每个事务参与者的回复后，统计每个参与者的回复，如果每个参与者都回复“可以提交”，那么事务协调器会发送提交命令，参与者正式提交本地事务，释放所有资源，结束全局事务。但是有一个参与者回复“拒绝提交”，那么事务协调器发送回滚命令，所有参与者都回滚本地事务，待全部回滚完成，释放资源，取消全局事务。事务提交流程\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407215950.png)\n\n事务回滚流程\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407220002.png)\n\n\n\n当然2PC存在的问题这里也提一下，一个是**同步阻塞**，这个会消耗性能。另一个是**协调器故障**问题，一旦协调器发生故障，那么所有的参与者处理资源锁定状态，那么所有参与者都会被阻塞。\n\n## 2.2、三阶段提交\n\n3PC主要是在2PC的基础上做了改进，主要为了解决2PC的阻塞问题。它主要是将2PC的第一阶段分为2个步骤，先准备，再锁定资源，并且引入了超时机制（这也意味着会造成数据不一致）。\n\n3PC的三个阶段包括：`CanCommit`、`PreCommit `和 `DoCommit` 具体细节就不展开赘述了，就一个核心观点：**在CanCommit的时候并不锁定资源，除非所有参与者都同意了，才开始锁资源**。\n\n## 2.3、TCC柔性事务\n\n相比较前面的2PC和3PC，TCC和那哥俩的本质区别就是它是业务层面的分布式事务，而2PC和3PC是数据库层面的。\n\nTCC是三个单词的缩写：`Try`、`Confirm`、`Cancel`，也分为这三个流程。\n\nTry：尝试，即尝试预留资源，锁定资源\n\nConfirm：确认，即执行预留的资源，如果执行失败会重试\n\nCancel：取消，撤销预留的资源，如果执行失败会重试\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407220009.png)\n\n从上图可知，TCC对于业务的侵入是很大的，而且紧紧的耦合在一起。TCC相比较2PC和3PC，试用范围更广，可实现跨库，跨不同系统去实现分布式事务。缺点是要在业务代码中去开发大量的逻辑实现这三个步骤，需要和代码耦合在一起，提高开发成本。\n\n**事务日志**：在TCC模式中，事务发起者和事务参与者都会去记录事务日志（事务状态、信息等）。这个事务日志是整个分布式事务出现意外情况（宕机、重启、网络中断等），实现提交和回滚的关键。\n\n**幂等性**：在TCC第二阶段，confirm或者cancel的时候，这两个操作都需要保证幂等性。一旦由于网络等原因导致执行失败，就会发起不断重试。\n\n**防悬挂**：由于网络的不可靠性，有异常情况的时候，try请求可能比cancel请求更晚到达。cancel可能会执行空回滚，但是try请求被执行的时候也不会预留资源。\n\n## 2.4、Seata\n\n关于seata这里就不多提了，用的最多的是AT模式，上回知了逐步分析过，配置完后只需要在事务发起的方法上添加`@GlobalTransactional`注解就可以开启全局事务，对于业务无侵入，低耦合。感兴趣的话请参考之前讨论Seata的内容。\n\n# 三、应用场景\n\n知了之前在一家公司遇到过这样的业务场景；用户通过页面投保，提交一笔订单过来，这个订单通过上游服务，处理保单相关的业务逻辑，最后流入下游服务，处理业绩、人员晋升、分润处理等等业务。对于这个场景，两边处理的业务逻辑不在同一个服务中，接入的是不同的数据库。涉及到数据一致性问题，需要用到分布式事务。\n\n对于上面介绍的几种方案，只是讨论了理论和思路，下面我来总结下这个业务场景中运用的一种实现方案。采用了本地消息表+MQ异步消息的方案实现了事务最终一致性，也符合当时的业务场景，相对强一致性，实现的性能较高。下面是该方案的思路图\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407220016.png)\n\n1. <section>真实业务处理的状态可能会有多种，因此需要明确哪种状态需要定时任务补偿</section>\n\n2. <section>假如某条单据一直无法处理结束，定时任务也不能无限制下发，所以本地消息表需要增加轮次的概念，重试多少次后告警，人工介入处理</section>\n\n3. <section>因为MQ和定时任务的存在，难免会出现重复请求，因此下游要做好幂等防重，否则会出现重复数据，导致数据不一致</section>\n\n对于落地实现，话不多说，直接上代码。先定义两张表tb_order和tb_notice_message，分别存订单信息和本地事务信息\n\n```\nCREATE TABLE `tb_order` (  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id',  `user_id` int(11) NOT NULL COMMENT '下单人id',  `order_no` varchar(255) CHARACTER SET latin1 NOT NULL COMMENT '订单编号',  `insurance_amount` decimal(16,2) NOT NULL COMMENT '保额',  `order_amount` decimal(16,2) DEFAULT NULL COMMENT '保费',  `create_time` datetime DEFAULT NULL COMMENT '创建时间',  `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',  `is_delete` tinyint(4) DEFAULT '0' COMMENT '删除标识：0-不删除；1-删除',  PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4;\n```\n\n```\nCREATE TABLE `tb_notice_message` (  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id',  `type` tinyint(4) NOT NULL COMMENT '业务类型：1-下单',  `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态：1-待处理，2-已处理，3-预警',  `data` varchar(255) NOT NULL COMMENT '信息',  `retry_count` tinyint(4) DEFAULT '0' COMMENT '重试次数',  `create_time` datetime NOT NULL COMMENT '创建时间',  `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',  `is_delete` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标识：0-不删除；1-删除',  PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4;\n```\n\n处理订单service，这里可以用到我们之前说过的**装饰器模式**，去装饰这个service。把保存本地事务，发送mq消息，交给装饰器类去做，而service只需要关心业务逻辑即可，也符合**开闭原则**。\n\n```\n/** * @author 往事如风 * @version 1.0 * @date 2022/12/13 10:58 * @description */@Service@Slf4j@AllArgsConstructorpublic class OrderService implements BaseHandler<Object, Order> {    private final OrderMapper orderMapper;    /**     * 订单处理方法：只处理订单关联逻辑     * @param o     * @return     */    @Override    public Order handle(Object o) {        // 订单信息        Order order = Order.builder()                .orderNo(\"2345678\")                .createTime(LocalDateTime.now())                .userId(1)                .insuranceAmount(new BigDecimal(2000000))                .orderAmount(new BigDecimal(5000))                .build();        orderMapper.insert(order);        return order;    }}\n```\n\n新增`OrderService`的装饰类`OrderServiceDecorate`，负责对订单逻辑的扩展，这里是添加本地事务消息，以及发送MQ信息，扩展方法添加了`Transactional`注解，确保订单逻辑和本地事务消息的数据在同一个事务中进行，确保原子性。其中事务消息标记处理中，待下游服务处理完业务逻辑，再更新处理完成。\n\n```\n/** * @author 往事如风 * @version 1.0 * @date 2022/12/14 18:48 * @description */@Slf4j@AllArgsConstructor@Decorate(scene = SceneConstants.ORDER, type = DecorateConstants.CREATE_ORDER)public class OrderServiceDecorate extends AbstractHandler {    private final NoticeMessageMapper noticeMessageMapper;    private final RabbitTemplate rabbitTemplate;    /**     * 装饰方法：对订单处理逻辑进行扩展     * @param o     * @return     */    @Override    @Transactional    public Object handle(Object o) {        // 调用service方法，实现保单逻辑        Order order = (Order) service.handle(o);        // 扩展：1、保存事务消息，2、发送MQ消息        // 本地事务消息        String data = \"{\\\"orderNo\\\":\\\"2345678\\\", \\\"userId\\\":1, \\\"insuranceAmount\\\":2000000, \\\"orderAmount\\\":5000}\";        NoticeMessage noticeMessage = NoticeMessage.builder()                .retryCount(0)                .data(data)                .status(1)                .type(1)                .createTime(LocalDateTime.now())                .build();        noticeMessageMapper.insert(noticeMessage);        // 发送mq消息        log.info(\"发送mq消息....\");        rabbitTemplate.convertAndSend(\"trans\", \"trans.queue.key\", JSONUtil.toJsonStr(noticeMessage));        return null;    }}\n```\n\n关于这个装饰者模式，之前有讲到过，可以看下之前发布的内容。\n\n下游服务监听消息，处理完自己的业务逻辑后（如：业绩、分润、晋升等），需要发送MQ，上游服务监听消息，更新本地事务状态为已处理。这需要注意的是下游服务需要做幂等处理，防止异常情况下，上游服务数据的重试。\n\n```\n/** * @author 往事如风 * @version 1.0 * @date 2022/12/13 18:07 * @description */@Component@Slf4j@RabbitListener(queues = \"trans.queue\")public class FenRunListener {    @Autowired    private RabbitTemplate rabbitTemplate;    @RabbitHandler    public void orderHandler(String msg) {        log.info(\"监听到订单消息:{}\", msg);        // 需要注意幂等，幂等逻辑        log.info(\"下游服务业务逻辑。。。。。\");        JSONObject json = JSONUtil.parseObj(msg);        rabbitTemplate.convertAndSend(\"trans\", \"trans.update.order.queue.key\", json.getInt(\"id\"));    }}\n```\n\n这里插个题外话，关于幂等的处理，我这里大致有两种思路 1、比如根据订单号查一下记录是否存在，存在就直接返回成功。2、redis存一个唯一的请求号，处理完再删除，不存在请求号的直接返回成功，可以写个AOP去处理，与业务隔离。言归正传，上游服务消息监听，下游发送MQ消息，更新本地事务消息为已处理，分布式事务流程结束。\n\n```\n/** * @author 往事如风 * @version 1.0 * @date 2022/12/13 18:29 * @description */@Component@Slf4j@RabbitListener(queues = \"trans.update.order.queue\")public class OrderListener {    @Autowired    private NoticeMessageMapper noticeMessageMapper;    @RabbitHandler    public void updateOrder(Integer msgId) {        log.info(\"监听消息，更新本地事务消息，消息id:{}\", msgId);        NoticeMessage msg = NoticeMessage.builder().status(2).id(msgId).updateTime(LocalDateTime.now()).build();        noticeMessageMapper.updateById(msg);    }}\n```\n\n存在异常情况时，会通过定时任务，轮询的往MQ中发送消息，尽最大努力去让下游服务达到数据一致，当然重试也要设置上限；若达到上限以后还一直是失败，那不得不考虑是下游服务自身存在问题了（有可能就是代码逻辑存在问题）。\n\n```\n/** * @author 往事如风 * @version 1.0 * @date 2022/12/14 10:25 * @description */@Configuration@EnableScheduling@AllArgsConstructor@Slf4jpublic class RetryOrderJob {    private final RabbitTemplate rabbitTemplate;    private final NoticeMessageMapper noticeMessageMapper;    /**     * 最大自动重试次数     */    private final Integer MAX_RETRY_COUNT = 5;    @Scheduled(cron = \"0/20 * * * * ? \")    public void retry() {        log.info(\"定时任务，重试异常订单\");        LambdaQueryWrapper<NoticeMessage> wrapper = Wrappers.lambdaQuery(NoticeMessage.class);        wrapper.eq(NoticeMessage::getStatus, 1);        List<NoticeMessage> noticeMessages = noticeMessageMapper.selectList(wrapper);        for (NoticeMessage noticeMessage : noticeMessages) {            // 重新发送mq消息            rabbitTemplate.convertAndSend(\"trans\", \"trans.queue.key\", JSONUtil.toJsonStr(noticeMessage));            // 重试次数+1            noticeMessage.setRetryCount(noticeMessage.getRetryCount() + 1);            noticeMessageMapper.updateById(noticeMessage);            // 判断重试次数，等于最长限制次数，直接更新为报警状态            if (MAX_RETRY_COUNT.equals(noticeMessage.getRetryCount())) {                noticeMessage.setStatus(3);                noticeMessageMapper.updateById(noticeMessage);                // 发送告警，通知对应人员                // 告警逻辑（短信、邮件、企微群，等等）....            }        }    }}\n```\n\n其实这里有个问题，一个上游服务对应多个下游服务的时候。这个时候往往不能存一条本地消息记录。\n\n1. <section>这里可以在消息表多加个字段next_server_count，表示一个订单发起方，需要调用的下游服务数量。上游服务监听的时候，每次会与下游的回调都减去1，直到数值是0的时候，再更新状态是已处理。但是要控制并发，这个字段是被多个下游服务共享的。</section>\n\n2. <section>还有一种处理方案是为每个下游服务，都记录一条事务消息，用type字段去区分，标记类型。实现上游和下游对于事务消息的一对一关系。</section>\n\n3. <section>最后，达到最大重试次数以后，可以将消息加入到一个告警列表，这个告警列表可以展示在管理后台或其他监控系统中，展示一些必要的信息，去供公司内部人员去人工介入，处理这种异常的数据，使得数据达到最终一致性。</section>\n\n# 四、总结\n\n其实分布式事务没有一个完美的处理方案，只能说是尽量去满足业务需求，满足数据一致。如果程序不能处理了，最后由人工去兜底，做数据的补偿方案。\n\n# 五、参考源码\n\n```\n编程文档：https://gitee.com/cicadasmile/butte-java-note应用仓库：https://gitee.com/cicadasmile/butte-flyer-parent\n```\n\n本文转载自 https://mp.weixin.qq.com/s/xa9Giv9xdSM3AahwN3WckQ\n"
  },
  {
    "path": "docs/distributed/practice/搞懂分布式技术：分布式系统的一些基本概念.md",
    "content": "\n# 目录\n  * [1、分布式](#1、分布式)\n  * [2、集群（Cluster）](#2、集群（cluster）)\n  * [3、负载均衡（Load Balancer）](#3、负载均衡（load-balancer）)\n  * [4、弹性](#4、弹性)\n  * [怎么办？](#怎么办？)\n  * [认识分布式架构](#认识分布式架构)\n  * [集中式与分布式](#集中式与分布式)\n    * [分布性](#分布性)\n    * [对等性](#对等性)\n    * [并发性](#并发性)\n    * [缺乏全局时钟](#缺乏全局时钟)\n    * [故障总是会发生](#故障总是会发生)\n  * [分布式系统面临的问题](#分布式系统面临的问题)\n    * [通信异常](#通信异常)\n    * [网络分区](#网络分区)\n    * [三态](#三态)\n    * [节点故障](#节点故障)\n  * [分布式理论(一) - CAP定理](#分布式理论一---cap定理)\n    * [1. CAP原则简介](#1-cap原则简介)\n    * [2. CAP原则论证](#2-cap原则论证)\n    * [3. CAP原则权衡](#3-cap原则权衡)\n  * [小结](#小结)\n  * [分布式理论(二) - BASE理论](#分布式理论二---base理论)\n    * [1. CAP的3选2伪命题](#1-cap的3选2伪命题)\n    * [3. BASE理论的内容](#3-base理论的内容)\n      * [3.1. 基本可用](#31-基本可用)\n      * [3.2. 软状态](#32-软状态)\n      * [3.3. 最终一致性](#33-最终一致性)\n    * [更具体的分布式问题](#更具体的分布式问题)\n      * [分布式事务](#分布式事务)\n      * [二、分布式锁](#二、分布式锁)\n      * [三、分布式 Session](#三、分布式-session)\n      * [四、负载均衡](#四、负载均衡)\n      * [高可用之“脑裂”](#高可用之脑裂)\n\n\n本文内容参考网络，侵删\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本文也将同步到我的个人博客：\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n该系列博文会告诉你什么是分布式系统，这对后端工程师来说是很重要的一门学问，我们会逐步了解常见的分布式技术、以及一些较为常见的分布式系统概念，同时也需要进一步了解zookeeper、分布式事务、分布式锁、负载均衡等技术，以便让你更完整地了解分布式技术的具体实战方法，为真正应用分布式技术做好准备。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n\n本文较为粗略地讲述了CAP与BASE理论，以及分布式系统需要解决的一些问题，更加系统的理论可以参考后面的分布式系统理论专题文章。更加详细的实践内容也可以参考本专题的剩余文章\n\n\n\n\n## 1、分布式\n\n小明的公司又3个系统：系统A，系统B和系统C，这三个系统所做的业务不同，被部署在3个独立的机器上运行，他们之间互相调用（当然是跨域网络的），通力合作完成公司的业务流程。\n\n\n\n\n\n将不同的业务分部在不同的地方，就构成了一个分布式的系统，现在问题来了，系统A是整个分布式系统的脸面，用户直接访问，用户访问量大的时候要么是速度巨慢，要么直接挂掉，怎么办？\n\n由于系统A只有一份，所以会引起单点失败。。。\n\n\n\n## 2、集群（Cluster）\n\n小明的公司不差钱，就多买几台机器吧， 小明把系统A一下子部署了好几份（例如下图的3个服务器），每一份都是系统A的一个实例，对外提供同样的服务，这样，就不怕其中一个坏掉了，还有另外两个呢。\n\n这三个服务器的系统就组成了一个集群。\n\n\n\n可是对用户来说，一下子出现这么多系统A，每个系统的IP地址都不一样，到底访问哪一个呢？\n\n如果所有人都访问服务器1.1，那服务器1.1会被累死，剩下两个闲死，成了浪费钱的摆设\n\n\n\n## 3、负载均衡（Load Balancer）\n\n小明要尽可能的让3个机器上的系统A工作均衡一些，比如有3万个请求，那就让3个服务器各处理1万个（理想情况），这叫负载均衡\n\n很明显，这个负载均衡的工作最好独立出来，放到独立的服务器上（例如nginx）：\n\n\n\n后来小明发现，这个负载均衡的服务器虽然工作内容简单，就是拿到请求，分发请求，但是它还是有可能挂掉，单点失败还是会出现。\n\n\n\n没办法，只好把负载均衡也搞成一个集群，bug和系统A的集群有两点不同：\n\n1.这个新的集群中虽然有两个机器，但是我们可以用某种办法，让这个机器对外只提供一个IP地址，也就是用户看到的好像只有一个机器。\n\n2.同一时刻，我们只让一个负载均衡的机器工作，另外一个原地待命，如果工作的那个拐到了，待命的那个就顶上去。\n\n\n\n\n\n## 4、弹性\n\n如果3个系统A的实例还是满足不了大量请求，例如双十一，可以申请增加服务器，双十一过后，新增的服务器闲置，成了摆设，于是小明决定尝试云计算，在云端可以轻松的创建，删除虚拟的服务器，那样就可以轻松的随着用户的请求动图的增减服务器了。\n\n\n\n## 5、失效转移\n\n上面的系统看起来很美好，但是做了一个不切实际的假设：\n\n所有的服务都是无状态的，换句话说，假设用户的两次请求直接是没有关联的。\n\n但是现实是，大部分服务都是有状态的，例如购物车。\n\n\n\n用户访问系统，在服务器上创建了一个购物车，并向其中加了几个商品，然后服务器1.1挂掉了，用户后续访问就找不到服务器1.1了，这时候就要做失效转移，让另外几个服务器去接管，去处理用户的请求。\n\n可是问题来了，在服务器1.2,1.3上有用户的购物车吗？如果没有，用户就会抱怨，我刚创建的购物车哪里去了？\n\n还有更严重的，假设用户登录过得信息保存到了该服务器1.1上登录的，用户登录过的信息保存到了该服务器的session中，现在这个服务器挂了，用的session就不见了，会把用户踢到了登录界面，让用户再次登录！\n\n\n\n处理不好状态的问题，集群的威力就大打折扣，无法完成真正的失效转移，甚至无法使用。\n\n\n\n## 怎么办？\n\n一种办法是把状态信息在集群的各个服务器之间复制，让集群的各个服务器达成一致，谁来干这个事情？只能像Webspher，Weblogic这样的应用服务器了。\n\n\n\n还有一种办法， 就是把状态信息几种存储在一个地方，让集群服务器的各个服务器都能访问到：\n\n\n\n\n\n小明听说Redis不错，那就用Redis来保存吧！\n\n\n\n## 认识分布式架构\n认识分布式架构\n随着计算机系统规模变得越来越大，将所有的业务单元集中部署在一个或若干个大型机上的体系结构，已经越来越不能满足当今计算机系统，尤其是大型互联网系统的快速发展，各种灵活多变的系统架构模型层出不穷。分布式的处理方式越来越受到业界的青睐——计算机系统正在经历一场前所未有的从集中式向分布式架构的变革。\n\n## 集中式与分布式\n集中式系统\n\n所谓的集中式系统就是指由一台或多台主计算机组成中心节点，数据集中存储于这个中心节点中，并且整个系统的所有业务单元都集中部署在这个中心节点上，系统的所有功能均由其集中处理。\n\n集中式系统的最大的特点就是部署结构非常简单，底层一般采用从IBM、HP等厂商购买到的昂贵的大型主机。因此无需考虑如何对服务进行多节点的部署，也就不用考虑各节点之间的分布式协作问题。但是，由于采用单机部署，很可能带来系统大而复杂、难于维护、发生单点故障（单个点发生故障的时候会波及到整个系统或者网络，从而导致整个系统或者网络的瘫痪）、扩展性差等问题。\n\n分布式系统\n\n分布式系统是一个硬件或软件组件分布在不同的网络计算机上，彼此之间仅仅通过消息传递进行通信和协调的系统。简单来说就是一群独立计算机集合共同对外提供服务，但是对于系统的用户来说，就像是一台计算机在提供服务一样。分布式意味着可以采用更多的普通计算机（相对于昂贵的大型机）组成分布式集群对外提供服务。计算机越多，CPU、内存、存储资源等也就越多，能够处理的并发访问量也就越大。\n\n从分布式系统的概念中我们知道，各个主机之间通信和协调主要通过网络进行，所以分布式系统中的计算机在空间上几乎没有任何限制，这些计算机可能被放在不同的机柜上，也可能被部署在不同的机房中，还可能在不同的城市中，对于大型的网站甚至可能分布在不同的国家和地区。但是，无论空间上如何分布，一个标准的分布式系统应该具有以下几个主要特征：\n\n### 分布性\n\n分布式系统中的多台计算机之间在空间位置上可以随意分布，同时，机器的分布情况也会随时变动。\n\n### 对等性\n\n分布式系统中的计算机没有主／从之分，即没有控制整个系统的主机，也没有被控制的从机，组成分布式系统的所有计算机节点都是对等的。副本（Replica）是分布式系统最常见的概念之一，指的是分布式系统对数据和服务提供的一种冗余方式。在常见的分布式系统中，为了对外提供高可用的服务，我们往往会对数据和服务进行副本处理。数据副本是指在不同节点上持久化同一份数据，当某一个节点上存储的数据丢失时，可以从副本上读取该数据，这是解决分布式系统数据丢失问题最为有效的手段。另一类副本是服务副本，指多个节点提供同样的服务，每个节点都有能力接收来自外部的请求并进行相应的处理。\n\n### 并发性\n\n在一个计算机网络中，程序运行过程的并发性操作是非常常见的行为。例如同一个分布式系统中的多个节点，可能会并发地操作一些共享的资源，如何准确并高效地协调分布式并发操作也成为了分布式系统架构与设计中最大的挑战之一。\n\n### 缺乏全局时钟\n\n在分布式系统中，很难定义两个事件究竟谁先谁后，原因就是因为分布式系统缺乏一个全局的时钟序列控制。\n\n### 故障总是会发生\n\n组成分布式系统的所有计算机，都有可能发生任何形式的故障。除非需求指标允许，在系统设计时不能放过任何异常情况。\n\n## 分布式系统面临的问题\n### 通信异常\n\n分布式系统需要在各个节点之间进行网络通信，因此网络通信都会伴随着网络不可用的风险或是系统不可用都会导致最终分布式系统无法顺利完成一次网络通信。另外，即使分布式系统各节点之间的网络通信能够正常进行，其延时也会远大于单机操作，会影响消息的收发的过程，因此消息丢失和消息延迟变得非常普遍。\n\n### 网络分区\n\n当网络由于发生异常情况，导致分布式系统中部分节点之间的网络延时不断增大，最终导致组成分布式系统的所有节点中，只有部分节点之间能够进行正常通信，而另一些节点则不能——我们将这个现象称为网络分区，就是俗称的“脑裂”。当网络分区出现时，分布式系统会出现局部小集群，在极端情况下，这些局部小集群会独立完成原本需要整个分布式才能完成的功能，这就对分布式一致性提出类非常大的挑战。\n\n### 三态\n\n分布式系统的每一次请求与响应，存在特有的“三态”概念，即成功、失败与超时。当出现超时现象时，网络通信的发起方是无法确定当前请求是否被成功处理的。\n\n### 节点故障\n\n节点故障则是分布式环境下另一个比较常见的问题，指的是组成分布式系统的服务器节点出现的宕机或“僵死”现象。\n\n\n\n\n\n## 分布式理论(一) - CAP定理\n前言\nCAP原则又称CAP定理，指的是在一个分布式系统中，Consistency（一致性）、 Availability（可用性）、Partition tolerance（分区容错性）这三个基本需求，最多只能同时满足其中的2个。\n\n\n\n\n\n初探分布式理论\n正文\n### 1. CAP原则简介\n选项\t描述\nConsistency（一致性）\t指数据在多个副本之间能够保持一致的特性（严格的一致性）\nAvailability（可用性）\t指系统提供的服务必须一直处于可用的状态，每次请求都能获取到非错的响应（不保证获取的数据为最新数据）\nPartition tolerance（分区容错性）\t分布式系统在遇到任何网络分区故障的时候，仍然能够对外提供满足一致性和可用性的服务，除非整个网络环境都发生了故障\n什么是分区？\n\n在分布式系统中，不同的节点分布在不同的子网络中，由于一些特殊的原因，这些子节点之间出现了网络不通的状态，但他们的内部子网络是正常的。从而导致了整个系统的环境被切分成了若干个孤立的区域，这就是分区。\n\n### 2. CAP原则论证\nCAP的基本场景，网络中有两个节点N1和N2，可以简单的理解N1和N2分别是两台计算机，他们之间网络可以连通，N1中有一个应用程序A，和一个数据库V，N2也有一个应用程序B和一个数据库V。现在，A和B是分布式系统的两个部分，V是分布式系统的数据存储的两个子数据库。\n\n\n\n\n\n\n\n在满足一致性的时候，N1和N2中的数据是一样的，V0=V0。\n在满足可用性的时候，用户不管是请求N1或者N2，都会得到立即响应。\n在满足分区容错性的情况下，N1和N2有任何一方宕机，或者网络不通的时候，都不会影响N1和N2彼此之间的正常运作。\n\n这是分布式系统正常运转的流程，用户向N1机器请求数据更新，程序A更新数据库V0为V1。分布式系统将数据进行同步操作M，将V1同步的N2中V0，使得N2中的数据V0也更新为V1，N2中的数据再响应N2的请求。\n\n\n\n\n\n\n\n根据CAP原则定义，系统的一致性、可用性和分区容错性细分如下：\n\n一致性：N1和N2的数据库V之间的数据是否完全一样。\n可用性：N1和N2的对外部的请求能否做出正常的响应。\n分区容错性：N1和N2之间的网络是否互通。\n这是正常运作的场景，也是理想的场景。作为一个分布式系统，它和单机系统的最大区别，就在于网络。现在假设一种极端情况，N1和N2之间的网络断开了，我们要支持这种网络异常。相当于要满足分区容错性，能不能同时满足一致性和可用性呢？还是说要对他们进行取舍？\n\n\n\n假设在N1和N2之间网络断开的时候，有用户向N1发送数据更新请求，那N1中的数据V0将被更新为V1。由于网络是断开的，所以分布式系统同步操作M，所以N2中的数据依旧是V0。这个时候，有用户向N2发送数据读取请求，由于数据还没有进行同步，应用程序没办法立即给用户返回最新的数据V1，怎么办呢？\n\n这里有两种选择：\n\n第一：牺牲数据一致性，保证可用性。响应旧的数据V0给用户。\n第二：牺牲可用性，保证数据一致性。阻塞等待，直到网络连接恢复，数据更新操作M完成之后，再给用户响应最新的数据V1。\n这个过程，证明了要满足分区容错性的分布式系统，只能在一致性和可用性两者中，选择其中一个。\n\n### 3. CAP原则权衡\n通过CAP理论，我们知道无法同时满足一致性、可用性和分区容错性这三个特性，那要舍弃哪个呢？\n\n3.1. CA without P\n如果不要求P（不允许分区），则C（强一致性）和A（可用性）是可以保证的。但其实分区不是你想不想的问题，而是始终会存在，因此CA的系统更多的是允许分区后各子系统依然保持CA。\n\n3.2. CP without A\n如果不要求A（可用），相当于每个请求都需要在Server之间强一致，而P（分区）会导致同步时间无限延长，如此CP也是可以保证的。很多传统的数据库分布式事务都属于这种模式。\n\n3.3. AP wihtout C\n要高可用并允许分区，则需放弃一致性。一旦分区发生，节点之间可能会失去联系，为了高可用，每个节点只能用本地数据提供服务，而这样会导致全局数据的不一致性。现在众多的NoSQL都属于此类。\n\n## 小结\n对于多数大型互联网应用的场景，主机众多、部署分散。而且现在的集群规模越来越大，所以节点故障、网络故障是常态。这种应用一般要保证服务可用性达到N个9，即保证P和A，只有舍弃C（退而求其次保证最终一致性）。虽然某些地方会影响客户体验，但没达到造成用户流程的严重程度。\n\n对于涉及到钱财这样不能有一丝让步的场景，C必须保证。网络发生故障宁可停止服务，这是保证CA，舍弃P。貌似这几年国内银行业发生了不下10起事故，但影响面不大，报到也不多，广大群众知道的少。还有一种是保证CP，舍弃A，例如网络故障时只读不写。\n\n孰优孰劣，没有定论，只能根据场景定夺，适合的才是最好的。\n\n## 分布式理论(二) - BASE理论\n前言\nBASE理论是由eBay架构师提出的。BASE是对CAP中一致性和可用性权衡的结果，其来源于对大规模互联网分布式系统实践的总结，是基于CAP定律逐步演化而来。其核心思想是即使无法做到强一致性，但每个应用都可以根据自身业务特点，才用适当的方式来使系统打到最终一致性。\n\n正文\n### 1. CAP的3选2伪命题\n实际上，不是为了P（分区容错性），必须在C（一致性）和A（可用性）之间任选其一。分区的情况很少出现，CAP在大多时间能够同时满足C和A。\n\n对于分区存在或者探知其影响的情况下，需要提供一种预备策略做出处理：\n\n探知分区的发生；\n进入显示的分区模式，限制某些操作；\n启动恢复过程，恢复数据一致性，补偿分区发生期间的错误。\n2. BASE理论简介\nBASE理论是Basically Available(基本可用)，Soft State（软状态）和Eventually Consistent（最终一致性）三个短语的缩写。\n\n其核心思想是：\n\n既是无法做到强一致性（Strong consistency），但每个应用都可以根据自身的业务特点，采用适当的方式来使系统达到最终一致性（Eventual consistency）。\n\n### 3. BASE理论的内容\n基本可用（Basically Available）\n软状态（Soft State）\n最终一致性（Eventually Consistent）\n下面展开讨论：\n\n#### 3.1. 基本可用\n什么是基本可用呢？假设系统，出现了不可预知的故障，但还是能用，相比较正常的系统而言：\n\n响应时间上的损失：正常情况下的搜索引擎0.5秒即返回给用户结果，而基本可用的搜索引擎可以在2秒作用返回结果。\n\n功能上的损失：在一个电商网站上，正常情况下，用户可以顺利完成每一笔订单。但是到了大促期间，为了保护购物系统的稳定性，部分消费者可能会被引导到一个降级页面。\n\n#### 3.2. 软状态\n什么是软状态呢？相对于原子性而言，要求多个节点的数据副本都是一致的，这是一种“硬状态”。\n\n软状态指的是：允许系统中的数据存在中间状态，并认为该状态不影响系统的整体可用性，即允许系统在多个不同节点的数据副本存在数据延时。\n\n#### 3.3. 最终一致性\n上面说软状态，然后不可能一直是软状态，必须有个时间期限。在期限过后，应当保证所有副本保持数据一致性，从而达到数据的最终一致性。这个时间期限取决于网络延时、系统负载、数据复制方案设计等等因素。\n\n而在实际工程实践中，最终一致性分为5种：\n\n3.3.1. 因果一致性（Causal consistency）\n\n因果一致性指的是：如果节点A在更新完某个数据后通知了节点B，那么节点B之后对该数据的访问和修改都是基于A更新后的值。于此同时，和节点A无因果关系的节点C的数据访问则没有这样的限制。\n\n3.3.2. 读己之所写（Read your writes）\n\n读己之所写指的是：节点A更新一个数据后，它自身总是能访问到自身更新过的最新值，而不会看到旧值。其实也算一种因果一致性。\n\n3.3.3. 会话一致性（Session consistency）\n\n会话一致性将对系统数据的访问过程框定在了一个会话当中：系统能保证在同一个有效的会话中实现 “读己之所写” 的一致性，也就是说，执行更新操作之后，客户端能够在同一个会话中始终读取到该数据项的最新值。\n\n3.3.4. 单调读一致性（Monotonic read consistency）\n\n单调读一致性指的是：如果一个节点从系统中读取出一个数据项的某个值后，那么系统对于该节点后续的任何数据访问都不应该返回更旧的值。\n\n3.3.5. 单调写一致性（Monotonic write consistency）\n\n单调写一致性指的是：一个系统要能够保证来自同一个节点的写操作被顺序的执行。\n\n在实际的实践中，这5种系统往往会结合使用，以构建一个具有最终一致性的分布式系统。\n\n实际上，不只是分布式系统使用最终一致性，关系型数据库在某个功能上，也是使用最终一致性的。比如备份，数据库的复制过程是需要时间的，这个复制过程中，业务读取到的值就是旧的。当然，最终还是达成了数据一致性。这也算是一个最终一致性的经典案例。\n\n### 更具体的分布式问题\n#### 分布式事务\n指事务的操作位于不同的节点上，需要保证事务的 AICD 特性。例如在下单场景下，库存和订单如果不在同一个节点上，就需要涉及分布式事务。\n\n本地消息\n1. 原理\n本地消息表与业务数据表处于同一个数据库中，这样就能利用本地事务来保证在对这两个表的操作满足事务特性。\n\n在分布式事务操作的一方，它完成写业务数据的操作之后向本地消息表发送一个消息，本地事务能保证这个消息一定会被写入本地消息表中。\n之后将本地消息表中的消息转发到 Kafka 等消息队列（MQ）中，如果转发成功则将消息从本地消息表中删除，否则继续重新转发。\n在分布式事务操作的另一方从消息队列中读取一个消息，并执行消息中的操作。\n\n\n\n\n2. 分析\n本地消息表利用了本地事务来实现分布式事务，并且使用了消息队列来保证最终一致性。\n\n两阶段提交协议\n2PC\n\n#### 二、分布式锁\n可以使用 Java 提供的内置锁来实现进程同步：由 JVM 实现的 synchronized 和 JDK 提供的 Lock。但是在分布式场景下，需要同步的进程可能位于不同的节点上，那么就需要使用分布式锁来同步。\n\n原理\n锁可以有阻塞锁和乐观锁两种实现方式，这里主要探讨阻塞锁实现。阻塞锁通常使用互斥量来实现，互斥量为 1 表示有其它进程在使用锁，此时处于锁定状态，互斥量为 0 表示未锁定状态。1 和 0 可以用一个整型值来存储，也可以用某个数据存在或者不存在来存储，某个数据存在表示互斥量为 1，也就是锁定状态。\n\n实现\n\n1. 数据库的唯一索引\n当想要获得锁时，就向表中插入一条记录，释放锁时就删除这条记录。唯一索引可以保证该记录只被插入一次，那么就可以用这个记录是否存在来判断是否存于锁定状态。\n\n这种方式存在以下几个问题：\n\n锁没有失效时间，解锁失败会导致死锁，其他线程无法再获得锁。\n只能是非阻塞锁，插入失败直接就报错了，无法重试。\n不可重入，同一线程在没有释放锁之前无法再获得锁。\n2. Redis 的 SETNX 指令\n使用 SETNX（set if not exist）指令插入一个键值对，如果 Key 已经存在，那么会返回 False，否则插入成功并返回 True。\n\nSETNX 指令和数据库的唯一索引类似，可以保证只存在一个 Key 的键值对，可以用一个 Key 的键值对是否存在来判断是否存于锁定状态。\n\nEXPIRE 指令可以为一个键值对设置一个过期时间，从而避免了死锁的发生。\n\n3. Redis 的 RedLock 算法\n使用了多个 Redis 实例来实现分布式锁，这是为了保证在发生单点故障时仍然可用。\n\n尝试从 N 个相互独立 Redis 实例获取锁，如果一个实例不可用，应该尽快尝试下一个。\n计算获取锁消耗的时间，只有当这个时间小于锁的过期时间，并且从大多数（N/2+1）实例上获取了锁，那么就认为锁获取成功了。\n如果锁获取失败，会到每个实例上释放锁。\n4. Zookeeper 的有序节点\nZookeeper 是一个为分布式应用提供一致性服务的软件，例如配置管理、分布式协同以及命名的中心化等，这些都是分布式系统中非常底层而且是必不可少的基本功能，但是如果自己实现这些功能而且要达到高吞吐、低延迟同时还要保持一致性和可用性，实际上非常困难。\n\n（一）抽象模型\n\nZookeeper 提供了一种树形结构级的命名空间，/app1/p_1 节点表示它的父节点为 /app1。\n\n\n\n\n\n（二）节点类型\n\n永久节点：不会因为会话结束或者超时而消失；\n临时节点：如果会话结束或者超时就会消失；\n有序节点：会在节点名的后面加一个数字后缀，并且是有序的，例如生成的有序节点为 /lock/node-0000000000，它的下一个有序节点则为 /lock/node-0000000001，依次类推。\n\n（三）监听器\n\n为一个节点注册监听器，在节点状态发生改变时，会给客户端发送消息。\n\n（四）分布式锁实现\n\n创建一个锁目录 /lock；\n在 /lock 下创建临时的且有序的子节点，第一个客户端对应的子节点为 /lock/lock-0000000000，第二个为 /lock/lock-0000000001，以此类推；\n客户端获取 /lock 下的子节点列表，判断自己创建的子节点是否为当前子节点列表中序号最小的子节点，如果是则认为获得锁；否则监听自己的前一个子节点，获得子节点的变更通知后重复此步骤直至获得锁；\n执行业务代码，完成后，删除对应的子节点。\n\n\n（五）会话超时\n\n如果一个已经获得锁的会话超时了，因为创建的是临时节点，所以该会话对应的临时节点会被删除，其它会话就可以获得锁了。可以看到，Zookeeper 分布式锁不会出现数据库的唯一索引实现分布式锁的死锁问题。\n\n（六）羊群效应\n\n一个节点未获得锁，需要监听自己的前一个子节点，这是因为如果监听所有的子节点，那么任意一个子节点状态改变，其它所有子节点都会收到通知（羊群效应），而我们只希望它的后一个子节点收到通知。\n\n#### 三、分布式 Session\n在分布式场景下，一个用户的 Session 如果只存储在一个服务器上，那么当负载均衡器把用户的下一个请求转发到另一个服务器上，该服务器没有用户的 Session，就可能导致用户需要重新进行登录等操作。\n\n\n\n\n\n1. Sticky Sessions\n需要配置负载均衡器，使得一个用户的所有请求都路由到一个服务器节点上，这样就可以把用户的 Session 存放在该服务器节点中。\n\n缺点：当服务器节点宕机时，将丢失该服务器节点上的所有 Session。\n\n\n\n\n\n2. Session Replication\n在服务器节点之间进行 Session 同步操作，这样的话用户可以访问任何一个服务器节点。\n\n缺点：需要更好的服务器硬件条件；需要对服务器进行配置。\n\n\n\n\n\n3. Persistent DataStore\n将 Session 信息持久化到一个数据库中。\n\n缺点：有可能需要去实现存取 Session 的代码。\n\n\n\n\n\n4. In-Memory DataStore\n可以使用 Redis 和 Memcached 这种内存型数据库对 Session 进行存储，可以大大提高 Session 的读写效率。内存型数据库同样可以持久化数据到磁盘中来保证数据的安全性。\n\n#### 四、负载均衡\n算法\n1. 轮询（Round Robin）\n轮询算法把每个请求轮流发送到每个服务器上。下图中，一共有 6 个客户端产生了 6 个请求，这 6 个请求按 (1, 2, 3, 4, 5, 6) 的顺序发送。最后，(1, 3, 5) 的请求会被发送到服务器 1，(2, 4, 6) 的请求会被发送到服务器 2。\n\n\n\n\n\n该算法比较适合每个服务器的性能差不多的场景，如果有性能存在差异的情况下，那么性能较差的服务器可能无法承担过大的负载（下图的 Server 2）。\n\n\n\n\n\n2. 加权轮询（Weighted Round Robbin）\n加权轮询是在轮询的基础上，根据服务器的性能差异，为服务器赋予一定的权值。例如下图中，服务器 1 被赋予的权值为 5，服务器 2 被赋予的权值为 1，那么 (1, 2, 3, 4, 5) 请求会被发送到服务器 1，(6) 请求会被发送到服务器 2。\n\n\n\n\n\n3. 最少连接（least Connections）\n由于每个请求的连接时间不一样，使用轮询或者加权轮询算法的话，可能会让一台服务器当前连接数过大，而另一台服务器的连接过小，造成负载不均衡。例如下图中，(1, 3, 5) 请求会被发送到服务器 1，但是 (1, 3) 很快就断开连接，此时只有 (5) 请求连接服务器 1；(2, 4, 6) 请求被发送到服务器 2，只有 (2) 的连接断开。该系统继续运行时，服务器 2 会承担过大的负载。\n\n\n\n\n\n最少连接算法就是将请求发送给当前最少连接数的服务器上。例如下图中，服务器 1 当前连接数最小，那么新到来的请求 6 就会被发送到服务器 1 上。\n\n\n\n\n\n4. 加权最少连接（Weighted Least Connection）\n在最少连接的基础上，根据服务器的性能为每台服务器分配权重，再根据权重计算出每台服务器能处理的连接数。\n\n\n\n\n\n5. 随机算法（Random）\n把请求随机发送到服务器上。和轮询算法类似，该算法比较适合服务器性能差不多的场景。\n\n\n\n\n\n6. 源地址哈希法 (IP Hash)\n源地址哈希通过对客户端 IP 哈希计算得到的一个数值，用该数值对服务器数量进行取模运算，取模结果便是目标服务器的序号。\n\n优点：保证同一 IP 的客户端都会被 hash 到同一台服务器上。\n缺点：不利于集群扩展，后台服务器数量变更都会影响 hash 结果。可以采用一致性 Hash 改进。\n\n\n\n\n实现\n1. HTTP 重定向\nHTTP 重定向负载均衡服务器收到 HTTP 请求之后会返回服务器的地址，并将该地址写入 HTTP 重定向响应中返回给浏览器，浏览器收到后需要再次发送请求。\n\n缺点：\n\n用户访问的延迟会增加；\n如果负载均衡器宕机，就无法访问该站点。\n\n\n\n\n2. DNS 重定向\n使用 DNS 作为负载均衡器，根据负载情况返回不同服务器的 IP 地址。大型网站基本使用了这种方式做为第一级负载均衡手段，然后在内部使用其它方式做第二级负载均衡。\n\n缺点：\n\nDNS 查找表可能会被客户端缓存起来，那么之后的所有请求都会被重定向到同一个服务器。\n\n\n\n\n3. 修改 MAC 地址\n使用 LVS（Linux Virtual Server）这种链路层负载均衡器，根据负载情况修改请求的 MAC 地址。\n\n\n\n\n\n4. 修改 IP 地址\n在网络层修改请求的目的 IP 地址。\n\n\n\n\n\n5. 代理自动配置\n正向代理与反向代理的区别：\n\n正向代理：发生在客户端，是由用户主动发起的。比如翻墙，客户端通过主动访问代理服务器，让代理服务器获得需要的外网数据，然后转发回客户端。\n反向代理：发生在服务器端，用户不知道代理的存在。\nPAC 服务器是用来判断一个请求是否要经过代理。\n\n\n\n\n\n#### 高可用之“脑裂”\n\n在涉及到高可用性时，经常会听到”脑裂“，到底啥是”脑裂“？\n\n一句话：当两（多）个节点同时认为自已是唯一处于活动状态的服务器从而出现争用资源的情况，这种争用资源的场景即是所谓的“脑裂”（split-brain）或”区间集群“（partitioned cluster）。\n\n--------------\n\nHeartBeat原理：\n\nHeartBeat运行于备用主机上的Heartbeat可以通过以太网连接检测主服务器的运行状态，一旦其无法检测到主服务器的\"心跳\"则自动接管主服务器的资源。通常情况下，主、备服务器间的心跳连接是一个独立的物理连接，这个连接可以是串行线缆、一个由\"交叉线\"实现的以太网连接。Heartbeat甚至可同时通过多个物理连接检测主服务器的工作状态，而其只要能通过其中一个连接收到主服务器处于活动状态的信息，就会认为主服务器处于正常状态。从实践经验的角度来说，建议为Heartbeat配置多条独立的物理连接，以避免Heartbeat通信线路本身存在单点故障。\n\n--------------\n\n 在“双机热备”高可用（HA）系统中，当联系2个节点的“心跳线”断开时，本来为一整体、动作协调的HA系统，就分裂成为2个独立的个体。由于相互失去了联系，都以为是对方出了故障，2个节点上的HA软件像“裂脑人”一样，“本能”地争抢“共享资源”、争起“应用服务”，就会发生严重后果：或者共享资源被瓜分、2边“服务”都起不来了；或者2边“服务”都起来了，但同时读写“共享存储”，导致数据损坏（常见如数据库轮询着的联机日志出错）。\n \n运行于备用主机上的Heartbeat可以通过以太网连接检测主服务器的运行状态，一旦其无法检测到主服务器的“心跳”则自动接管主服务器的资源。通常情况下，主、备服务器间的心跳连接是一个独立的物理连接，这个连接可以是串行线缆、一个由“交叉线”实现的以太网连接。Heartbeat甚至可同时通过多个物理连接检测主服务器的工作状态，而其只要能通过其中一个连接收到主服务器处于活动状态的信息，就会认为主服务器处于正常状态。从实践经验的角度来说，建议为Heartbeat配置多条独立的物理连接，以避免Heartbeat通信线路本身存在单点故障。\n\n1、串行电缆：被认为是比以太网连接安全性稍好些的连接方式，因为hacker无法通过串行连接运行诸如telnet、ssh或rsh类的程序，从而可以降低其通过已劫持的服务器再次侵入备份服务器的几率。但串行线缆受限于可用长度，因此主、备服务器的距离必须非常短。\n2、以太网连接：使用此方式可以消除串行线缆的在长度方面限制，并且可以通过此连接在主备服务器间同步文件系统，从而减少了从正常通信连接带宽的占用。\n\n 基于冗余的角度考虑，应该在主、备服务器使用两个物理连接传输heartbeat的控制信息；这样可以避免在一个网络或线缆故障时导致两个节点同时认为自已是唯一处于活动状态的服务器从而出现争用资源的情况，这种争用资源的场景即是所谓的“脑裂”（split-brain）或“partitioned cluster”。在两个节点共享同一个物理设备资源的情况下，脑裂会产生相当可怕的后果。\n\n 为了避免出现脑裂，可采用下面的预防措施：\n添加冗余的心跳线，例如双线条线。尽量减少“裂脑”发生机会。\n启用磁盘锁。正在服务一方锁住共享磁盘，“裂脑”发生时，让对方完全“抢不走”共享磁盘资源。但使用锁磁盘也会有一个不小的问题，如果占用共享盘的一方不主动“解锁”，另一方就永远得不到共享磁盘。现实中假如服务节点突然死机或崩溃，就不可能执行解锁命令。后备节点也就接管不了共享资源和应用服务。于是有人在HA中设计了“智能”锁。即，正在服务的一方只在发现心跳线全部断开（察觉不到对端）时才启用磁盘锁。平时就不上锁了。\n\n设置仲裁机制。例如设置参考IP（如网关IP），当心跳线完全断开时，2个节点都各自ping一下 参考IP，不通则表明断点就出在本端，不仅“心跳”、还兼对外“服务”的本端网络链路断了，即使启动（或继续）应用服务也没有用了，那就主动放弃竞争，让能够ping通参考IP的一端去起服务。更保险一些，ping不通参考IP的一方干脆就自我重启，以彻底释放有可能还占用着的那些共享资源。\n\n"
  },
  {
    "path": "docs/distributed/practice/搞懂分布式技术：初探分布式协调服务zookeeper.md",
    "content": "# 目录\n\n* [目录](#目录)\n  * [小梁的邮件](#小梁的邮件)\n  * [小王的Master选举](#小王的master选举)\n  * [小蔡的分布式锁](#小蔡的分布式锁)\n  * [Zookeeper](#zookeeper)\n\n\n# 目录\n\n本文转自：微信公众号【码农翻身】\n\n本文内容参考网络，侵删\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本文也将同步到我的个人博客：\n\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n该系列博文会告诉你什么是分布式系统，这对后端工程师来说是很重要的一门学问，我们会逐步了解常见的分布式技术、以及一些较为常见的分布式系统概念，同时也需要进一步了解zookeeper、分布式事务、分布式锁、负载均衡等技术，以便让你更完整地了解分布式技术的具体实战方法，为真正应用分布式技术做好准备。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n张大胖所在的公司这几年发展得相当不错，业务激增，人员也迅速扩展，转眼之间，张大胖已经成为公司的“资深”员工了，更重要的是，经过这些年的不懈努力，他终于坐上了架构师的宝座。\n\n但是大胖很快发现，这架构师真不是好当的，技术选型、架构设计，尤其是大家搞不定的技术难点，最终都得自己扛起来。沟通、说服、妥协、甚至争吵都是家常便饭，比自己之前单纯做开发的时候难多了。\n\n公司的IT系统早已经从单机转向了分布式，分布式系统带来了巨大的挑战。这周一刚上班，张大胖的邮箱里已经塞满了紧急邮件。\n\n## 小梁的邮件\n\n小梁的邮件里说了一个RPC调用的问题，本来公司的架构组开发了一个RPC框架让各个组去使用，但是各开发小组纷纷抱怨：这个RPC框架不支持动态的服务注册和发现。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/640.webp)\n\n张大胖一看这个图就明白怎么回事了，为了支持高并发，OrderService被部署了4份，每个客户端都保存了一份服务提供者的列表，但是这个列表是静态的（在配置文件中写死的），如果服务的提供者发生了变化，例如有些机器down了，或者又新增了OrderService的实例，客户端根本不知道，可能还在傻乎乎地尝试那些已经坏掉的实例呢！\n\n想要得到最新的服务提供者的URL列表，必须得手工更新配置文件才行，确实很不方便。\n\n对于这样的问题，大胖马上就意识到，这就是客户端和服务提供者的紧耦合啊。\n\n想解除这个耦合，非得增加一个中间层不可！\n\n张大胖想到，应该有个注册中心，首先给这些服务命名（例如orderService），其次那些OrderService 都可以在这里注册一下，客户端就到这里来查询，只需要给出名称orderService，注册中心就可以给出一个可以使用的url， 再也不怕服务提供者的动态增减了。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/640.webp)\n\n不知道是不是下意识的行为，**张大胖把这个注册中心的数据结构设计成为了一个树形结构**：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/640.webp)\n\n/orderService 表达了一个服务的概念， 下面的每个节点表示了一个服务的实例。 例如/orderService/node2表示的order service 的第二个实例， 每个节点上可以记录下该实例的url , 这样就可以查询了。\n\n当然这个注册中心必须得能和各个服务实例通信，如果某个服务实例不幸down掉了，那它在树结构中对于的节点也必须删除， 这样客户端就查询不到了。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407205952.png)\n\n嗯，可以在注册中心和各个服务实例直接建立Session, 让各个服务实例定期地发送心跳，如果过了特定时间收不到心跳，就认为这个服务实例挂掉了，Session 过期， 把它从树形结构中删除。\n\n张大胖把自己的想法回复了小梁，接着看小王的邮件。\n\n## 小王的Master选举\n\n小王邮件中说的是三个Batch Job的协调问题，这三个Batch Job 部署在三台机器上，但是这三个Batch Job同一个时刻只能有一个运行，如果其中某个不幸down掉，剩下的两个就需要做个选举，选出来的那个Batch Job 需要“继承遗志”，继续工作。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/640.webp)\n\n其实这就是一个Master的选举问题，张大胖一眼就看出了本质。\n\n只是为了选举出Master， 这三个Batch Job 需要互通有无，互相协调才行，这就麻烦了！\n\n要不弄个数据库表？ 利用数据库表主键不能冲突的特性，让这三个Batch Job 都向同一个表中插入同样的数据，谁先成功谁就是Master ！\n\n可是如果抢到Master的那个Batch Job挂掉了，别人永远就抢不到了！ 因为记录已经存在了， 别的Batch Job 没法插入数据了！\n\n嗯，还得加上定期更新的机制，如果一段时间内没有更新就认为Master死掉了，别的Batch Job可以继续抢..... 不过这么做好麻烦！\n\n换个思路，让他们也去一个注册中心去大吼一声：“我是master!”， 谁的声音大谁是Master 。\n\n其实不是吼一声，三个Batch Job启动以后，都去注册中心争抢着去创建一个树的节点（例如/master ），谁创建成功谁就是Master （**当然注册中心必须保证只能创建成功一次，其他请求就失败了**），其他两个Batch Job就对这个节点虎视眈眈地监控，如果这个节点被删除，就开始新一轮争抢，去创建那个/master节点。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407210038.png)\n\n什么时候节点会被删除呢？ 对，就是当前Master的机器down掉了 ！ 很明显，注册中心也需要和各个机器通信，看看他们是否活着。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407210049.png)\n\n等等，这里还有一个复杂的情况， **如果机器1并没有死掉，只是和注册中心长时间连接不上**，注册中心会发现Session超时，会把机器1创建的/master删除。 让机器2和机器3去抢，如果机器3成为了master, 开始运行Batch Job, 但是机器1并不知道自己被解除了Master的职务， 还在努力的运行Batch Job，这就冲突了！\n\n看来机器1必须得能感知到和注册中心的连接断开了，需要停止Batch Job才行，等到和注册中心再次连接上以后，才知道自己已经不是master了，老老实实地等下一次机会吧。\n\n无论哪种方案，实现起来都很麻烦，这该死的分布式！\n\n先把思路给小王回复一下吧。接着看小蔡的邮件。\n\n## 小蔡的分布式锁\n\n小蔡的邮件里说的问题更加麻烦，有多个不同的系统（当然是分布在不同的机器上！），要对同一个资源操作。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407210101.png)\n\n这要是在一个机器上，使用某个语言内置的锁就可以搞定，例如Java的synchronized ， 但是现在是分布式啊，程序都跑在不同机器的不同进程中， synchcronized一点用都没有了！\n\n这是个分布式锁的问题啊！\n\n能不能考虑下Master选举问题中的方式，让大家去抢？ 谁能抢先在注册中心创建一个**/distribute_lock**的节点就表示抢到这个锁了，然后读写资源，读写完以后就把**/distribute_lock**节点删除，大家再来抢。\n\n可是这样的话某个系统可能会多次抢到，不太公平。\n\n如果让这些系统在注册中心的/distribute_lock下都创建子节点， 然后给每个系统一个编号，会是这个样子：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407210111.png)\n\n然后各个系统去检查自己的编号，谁的编号小就认为谁持有了锁， 例如系统1。\n\n系统1持有了锁，就可以对共享资源进行操作了， 操作完成以后process_01这个节点删除， 再创建一个新的节点（编号变成process_04了）：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407210450.png)\n\n其他系统一看，编号为01的删除了，再看看谁是最小的吧，是process_02，那就认为系统2持有了锁，可以对共享资源操作了。 操作完成以后也要把process_02节点删除，创建新的节点。这时候process_03就是最小的了，可以持有锁了。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407210132.png)\n\n这样循环往复下去...... 分布式锁就可以实现了！\n\n看看，我设计的这个集中式的树形结构很不错吧，能解决各种各样的问题！张大胖不由得意起来。\n\n好，先把这个想法告诉小蔡，实现细节下午开个会讨论。\n\n## Zookeeper\n\n正准备回复小蔡的时候，大胖突然意识到，自己漏了一个重要的点，那就是**注册中心的高可用性**，如果注册中心只有那么一台机器，一旦挂掉，整个系统就玩完了。\n\n这个注册中心也得有多台机器来保证高可用性，那个自己颇为得意的树形结构也需要在多个机器之间同步啊，要是有机器挂掉怎么办？ 通信超时怎么办？ 树形结构的数据怎么在各个机器之间保证强一致性？\n\n小王、小梁、小蔡的原始问题没有解决，单单是这个注册中心就要了命了。 以自己公司的技术实力，搞出一套这样的注册中心简直是Mission Impossible !\n\n大胖赶紧上网搜索，看看有没有类似的解决方案，让大胖感到万分幸运的是，果然有一个，叫做**Zookeeper** ！\n\nZookeeper 所使用的树形结构和自己想象的非常类似，更重要的是，人家实现了树形结构数据在多台机器之间的可靠复制，达到了数据在多台机器之间的一致性。并且这多台机器中如果有部分挂掉了/或者由于网络原因无法连接上了， 整个系统还可以工作。\n\n大胖赶快去看Zookeeper的关键概念和API：\n\n1. **Session** ： 表示某个客户系统（例如Batch Job）和ZooKeeper之间的连接会话, Batch Job连上ZooKeeper以后会周期性地发送心跳信息， 如果Zookeepr在特定时间内收不到心跳，就会认为这个Batch Job已经死掉了, Session 就会结束。\n\n2\\. **znode** : 树形结构中的每个节点叫做znode， 按类型可以分为**永久的znode**（除非主动删除，否则一直存在），**临时的znode**（Session结束就会删除）和 **顺序znode**（就是小蔡的分布式锁中的process_01,process_02.....）。\n\n3. **Watch** ： 某个客户系统（例如Batch Job）可以监控znode， znode节点的变化（删除，修改数据等）都可以通知Batch Job， 这样Batch Job可以采取相应的动作，例如争抢着去创建节点。\n\n嗯，这些概念和接口应该可以满足我们的要求了， 就是它了，下午召集大家开会开始学习Zookeeper吧。\n\n_后记：本文从使用者的角度描述了Zookeeper有什么用处，至于它内部是如何工作，那是另外一个Big topic了，我们以后再讲。___\n\n（完）\n"
  },
  {
    "path": "docs/distributed/practice/搞懂分布式技术：浅析分布式事务.md",
    "content": "# 目录\n\n* [目录](#目录)\n  * [什么是事务？](#什么是事务？)\n  * [事务的四大特性 ACID](#事务的四大特性-acid)\n  * [事务的隔离级别](#事务的隔离级别)\n  * [事务并发执行会出现的问题](#事务并发执行会出现的问题)\n  * [数据库的四种隔离级别](#数据库的四种隔离级别)\n  * [什么是分布式事务？](#什么是分布式事务？)\n  * [CAP理论](#cap理论)\n  * [BASE理论](#base理论)\n  * [分布式事务协议](#分布式事务协议)\n    * [2PC](#2pc)\n    * [3PC](#3pc)\n\n\n# 目录\n\n本文转载自 linkedkeeper.com\n\n本文内容参考网络，侵删\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本文也将同步到我的个人博客：\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n该系列博文会告诉你什么是分布式系统，这对后端工程师来说是很重要的一门学问，我们会逐步了解常见的分布式技术、以及一些较为常见的分布式系统概念，同时也需要进一步了解zookeeper、分布式事务、分布式锁、负载均衡等技术，以便让你更完整地了解分布式技术的具体实战方法，为真正应用分布式技术做好准备。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n众所周知，数据库能实现本地事务，也就是在同一个数据库中，你可以允许一组操作要么全都正确执行，要么全都不执行。这里特别强调了本地事务，也就是目前的数据库只能支持同一个数据库中的事务。但现在的系统往往采用微服务架构，业务系统拥有独立的数据库，因此就出现了跨多个数据库的事务需求，这种事务即为“分布式事务”。那么在目前数据库不支持跨库事务的情况下，我们应该如何实现分布式事务呢？本文首先会为大家梳理分布式事务的基本概念和理论基础，然后介绍几种目前常用的分布式事务解决方案。废话不多说，那就开始吧～\n\n## 什么是事务？\n事务由一组操作构成，我们希望这组操作能够全部正确执行，如果这一组操作中的任意一个步骤发生错误，那么就需要回滚之前已经完成的操作。也就是同一个事务中的所有操作，要么全都正确执行，要么全都不要执行。\n\n## 事务的四大特性 ACID\n说到事务，就不得不提一下事务著名的四大特性。\n\n原子性原子性要求，事务是一个不可分割的执行单元，事务中的所有操作要么全都执行，要么全都不执行。\n\n一致性一致性要求，事务在开始前和结束后，数据库的完整性约束没有被破坏。\n\n隔离性事务的执行是相互独立的，它们不会相互干扰，一个事务不会看到另一个正在运行过程中的事务的数据。\n\n持久性持久性要求，一个事务完成之后，事务的执行结果必须是持久化保存的。即使数据库发生崩溃，在数据库恢复后事务提交的结果仍然不会丢失。\n\n注意：事务只能保证数据库的高可靠性，即数据库本身发生问题后，事务提交后的数据仍然能恢复；而如果不是数据库本身的故障，如硬盘损坏了，那么事务提交的数据可能就丢失了。这属于『高可用性』的范畴。因此，事务只能保证数据库的『高可靠性』，而『高可用性』需要整个系统共同配合实现。\n\n## 事务的隔离级别\n这里扩展一下，对事务的隔离性做一个详细的解释。\n\n在事务的四大特性ACID中，要求的隔离性是一种严格意义上的隔离，也就是多个事务是串行执行的，彼此之间不会受到任何干扰。这确实能够完全保证数据的安全性，但在实际业务系统中，这种方式性能不高。因此，数据库定义了四种隔离级别，隔离级别和数据库的性能是呈反比的，隔离级别越低，数据库性能越高，而隔离级别越高，数据库性能越差。\n\n## 事务并发执行会出现的问题\n我们先来看一下在不同的隔离级别下，数据库可能会出现的问题：\n\n更新丢失当有两个并发执行的事务，更新同一行数据，那么有可能一个事务会把另一个事务的更新覆盖掉。当数据库没有加任何锁操作的情况下会发生。\n\n脏读一个事务读到另一个尚未提交的事务中的数据。该数据可能会被回滚从而失效。如果第一个事务拿着失效的数据去处理那就发生错误了。\n\n不可重复读不可重复度的含义：一个事务对同一行数据读了两次，却得到了不同的结果。它具体分为如下两种情况：\n\n虚读：在事务1两次读取同一记录的过程中，事务2对该记录进行了修改，从而事务1第二次读到了不一样的记录。\n幻读：事务1在两次查询的过程中，事务2对该表进行了插入、删除操作，从而事务1第二次查询的结果发生了变化。\n不可重复读 与 脏读 的区别？脏读读到的是尚未提交的数据，而不可重复读读到的是已经提交的数据，只不过在两次读的过程中数据被另一个事务改过了。\n\n## 数据库的四种隔离级别\n数据库一共有如下四种隔离级别：\n\nRead uncommitted 读未提交在该级别下，一个事务对一行数据修改的过程中，不允许另一个事务对该行数据进行修改，但允许另一个事务对该行数据读。因此本级别下，不会出现更新丢失，但会出现脏读、不可重复读。\n\nRead committed 读提交在该级别下，未提交的写事务不允许其他事务访问该行，因此不会出现脏读；但是读取数据的事务允许其他事务的访问该行数据，因此会出现不可重复读的情况。\n\nRepeatable read 重复读在该级别下，读事务禁止写事务，但允许读事务，因此不会出现同一事务两次读到不同的数据的情况（不可重复读），且写事务禁止其他一切事务。\n\nSerializable 序列化该级别要求所有事务都必须串行执行，因此能避免一切因并发引起的问题，但效率很低。\n\n隔离级别越高，越能保证数据的完整性和一致性，但是对并发性能的影响也越大。对于多数应用程序，可以优先考虑把数据库系统的隔离级别设为Read Committed。它能够避免脏读取，而且具有较好的并发性能。尽管它会导致不可重复读、幻读和第二类丢失更新这些并发问题，在可能出现这类问题的个别场合，可以由应用程序采用悲观锁或乐观锁来控制。\n\n## 什么是分布式事务？\n到此为止，所介绍的事务都是基于单数据库的本地事务，目前的数据库仅支持单库事务，并不支持跨库事务。而随着微服务架构的普及，一个大型业务系统往往由若干个子系统构成，这些子系统又拥有各自独立的数据库。往往一个业务流程需要由多个子系统共同完成，而且这些操作可能需要在一个事务中完成。在微服务系统中，这些业务场景是普遍存在的。此时，我们就需要在数据库之上通过某种手段，实现支持跨数据库的事务支持，这也就是大家常说的“分布式事务”。\n\n这里举一个分布式事务的典型例子——用户下单过程。当我们的系统采用了微服务架构后，一个电商系统往往被拆分成如下几个子系统：商品系统、订单系统、支付系统、积分系统等。整个下单的过程如下：\n\n用户通过商品系统浏览商品，他看中了某一项商品，便点击下单\n此时订单系统会生成一条订单\n订单创建成功后，支付系统提供支付功能\n当支付完成后，由积分系统为该用户增加积分\n上述步骤2、3、4需要在一个事务中完成。对于传统单体应用而言，实现事务非常简单，只需将这三个步骤放在一个方法A中，再用Spring的@Transactional注解标识该方法即可。Spring通过数据库的事务支持，保证这些步骤要么全都执行完成，要么全都不执行。但在这个微服务架构中，这三个步骤涉及三个系统，涉及三个数据库，此时我们必须在数据库和应用系统之间，通过某项黑科技，实现分布式事务的支持。\n\n## CAP理论\nCAP理论说的是：在一个分布式系统中，最多只能满足C、A、P中的两个需求。\n\nCAP的含义：\n\nC：Consistency 一致性同一数据的多个副本是否实时相同。\nA：Availability 可用性可用性：一定时间内 & 系统返回一个明确的结果 则称为该系统可用。\nP：Partition tolerance 分区容错性将同一服务分布在多个系统中，从而保证某一个系统宕机，仍然有其他系统提供相同的服务。\nCAP理论告诉我们，在分布式系统中，C、A、P三个条件中我们最多只能选择两个。那么问题来了，究竟选择哪两个条件较为合适呢？\n\n对于一个业务系统来说，可用性和分区容错性是必须要满足的两个条件，并且这两者是相辅相成的。业务系统之所以使用分布式系统，主要原因有两个：\n\n提升整体性能当业务量猛增，单个服务器已经无法满足我们的业务需求的时候，就需要使用分布式系统，使用多个节点提供相同的功能，从而整体上提升系统的性能，这就是使用分布式系统的第一个原因。\n\n实现分区容错性单一节点 或 多个节点处于相同的网络环境下，那么会存在一定的风险，万一该机房断电、该地区发生自然灾害，那么业务系统就全面瘫痪了。为了防止这一问题，采用分布式系统，将多个子系统分布在不同的地域、不同的机房中，从而保证系统高可用性。\n\n这说明分区容错性是分布式系统的根本，如果分区容错性不能满足，那使用分布式系统将失去意义。\n\n此外，可用性对业务系统也尤为重要。在大谈用户体验的今天，如果业务系统时常出现“系统异常”、响应时间过长等情况，这使得用户对系统的好感度大打折扣，在互联网行业竞争激烈的今天，相同领域的竞争者不甚枚举，系统的间歇性不可用会立马导致用户流向竞争对手。因此，我们只能通过牺牲一致性来换取系统的可用性和分区容错性。这也就是下面要介绍的BASE理论。\n\n## BASE理论\nCAP理论告诉我们一个悲惨但不得不接受的事实——我们只能在C、A、P中选择两个条件。而对于业务系统而言，我们往往选择牺牲一致性来换取系统的可用性和分区容错性。不过这里要指出的是，所谓的“牺牲一致性”并不是完全放弃数据一致性，而是牺牲强一致性换取弱一致性。下面来介绍下BASE理论。\n\nBA：Basic Available 基本可用\n整个系统在某些不可抗力的情况下，仍然能够保证“可用性”，即一定时间内仍然能够返回一个明确的结果。只不过“基本可用”和“高可用”的区别是：\n“一定时间”可以适当延长当举行大促时，响应时间可以适当延长\n给部分用户返回一个降级页面给部分用户直接返回一个降级页面，从而缓解服务器压力。但要注意，返回降级页面仍然是返回明确结果。\nS：Soft State：柔性状态同一数据的不同副本的状态，可以不需要实时一致。\nE：Eventual Consisstency：最终一致性同一数据的不同副本的状态，可以不需要实时一致，但一定要保证经过一定时间后仍然是一致的。\n酸碱平衡\nACID能够保证事务的强一致性，即数据是实时一致的。这在本地事务中是没有问题的，在分布式事务中，强一致性会极大影响分布式系统的性能，因此分布式系统中遵循BASE理论即可。但分布式系统的不同业务场景对一致性的要求也不同。如交易场景下，就要求强一致性，此时就需要遵循ACID理论，而在注册成功后发送短信验证码等场景下，并不需要实时一致，因此遵循BASE理论即可。因此要根据具体业务场景，在ACID和BASE之间寻求平衡。\n\n## 分布式事务协议\n下面介绍几种实现分布式事务的协议。\n\n\n理解2PC和3PC协议\n\n为了解决分布式一致性问题，前人在性能和数据一致性的反反复复权衡过程中总结了许多典型的协议和算法。其中比较著名的有二阶提交协议（2 Phase Commitment Protocol），三阶提交协议（3 Phase Commitment Protocol）。\n\n### 2PC\n\n分布式事务最常用的解决方案就是二阶段提交。在分布式系统中，每个节点虽然可以知晓自己的操作时成功或者失败，却无法知道其他节点的操作的成功或失败。当一个事务跨越多个节点时，为了保持事务的ACID特性，需要引入一个作为协调者的组件来统一掌控所有参与者节点的操作结果并最终指示这些节点是否要把操作结果进行真正的提交。\n\n因此，二阶段提交的算法思路可以概括为：参与者将操作成败通知协调者，再由协调者根据所有参与者的反馈情报决定各参与者是否要提交操作还是中止操作。\n\n所谓的两个阶段是指：第一阶段：准备阶段（投票阶段）和第二阶段：提交阶段（执行阶段）。\n\n第一阶段：投票阶段\n\n该阶段的主要目的在于打探数据库集群中的各个参与者是否能够正常的执行事务，具体步骤如下：\n\n1. 协调者向所有的参与者发送事务执行请求，并等待参与者反馈事务执行结果。\n\n2. 事务参与者收到请求之后，执行事务，但不提交，并记录事务日志。\n\n3. 参与者将自己事务执行情况反馈给协调者，同时阻塞等待协调者的后续指令。\n\n第二阶段：事务提交阶段\n\n在第一阶段协调者的询盘之后，各个参与者会回复自己事务的执行情况，这时候存在三种可能：\n\n1. 所有的参与者回复能够正常执行事务。\n\n2. 一个或多个参与者回复事务执行失败。\n\n3. 协调者等待超时。\n\n对于第一种情况，协调者将向所有的参与者发出提交事务的通知，具体步骤如下：\n\n1. 协调者向各个参与者发送commit通知，请求提交事务。\n\n2. 参与者收到事务提交通知之后，执行commit操作，然后释放占有的资源。\n\n3. 参与者向协调者返回事务commit结果信息。\n\n\n\n对于第二、三种情况，协调者均认为参与者无法正常成功执行事务，为了整个集群数据的一致性，所以要向各个参与者发送事务回滚通知，具体步骤如下：\n\n1. 协调者向各个参与者发送事务rollback通知，请求回滚事务。\n\n2. 参与者收到事务回滚通知之后，执行rollback操作，然后释放占有的资源。\n\n3. 参与者向协调者返回事务rollback结果信息。\n\n\n\n两阶段提交协议解决的是分布式数据库数据强一致性问题，其原理简单，易于实现，但是缺点也是显而易见的，主要缺点如下：\n\n单点问题：协调者在整个两阶段提交过程中扮演着举足轻重的作用，一旦协调者所在服务器宕机，那么就会影响整个数据库集群的正常运行，比如在第二阶段中，如果协调者因为故障不能正常发送事务提交或回滚通知，那么参与者们将一直处于阻塞状态，整个数据库集群将无法提供服务。\n\n同步阻塞：两阶段提交执行过程中，所有的参与者都需要听从协调者的统一调度，期间处于阻塞状态而不能从事其他操作，这样效率及其低下。\n\n数据不一致性：两阶段提交协议虽然为分布式数据强一致性所设计，但仍然存在数据不一致性的可能，比如在第二阶段中，假设协调者发出了事务commit的通知，但是因为网络问题该通知仅被一部分参与者所收到并执行了commit操作，其余的参与者则因为没有收到通知一直处于阻塞状态，这时候就产生了数据的不一致性。\n\n### 3PC\n\n针对两阶段提交存在的问题，三阶段提交协议通过引入一个“预询盘”阶段，以及超时策略来减少整个集群的阻塞时间，提升系统性能。三阶段提交的三个阶段分别为：can_commit，pre_commit，do_commit。\n\n第一阶段：can_commit\n\n该阶段协调者会去询问各个参与者是否能够正常执行事务，参与者根据自身情况回复一个预估值，相对于真正的执行事务，这个过程是轻量的，具体步骤如下：\n\n1. 协调者向各个参与者发送事务询问通知，询问是否可以执行事务操作，并等待回复。\n\n2. 各个参与者依据自身状况回复一个预估值，如果预估自己能够正常执行事务就返回确定信息，并进入预备状态，否则返回否定信息。\n\n第二阶段：pre_commit\n\n本阶段协调者会根据第一阶段的询盘结果采取相应操作，询盘结果主要有三种：\n\n1. 所有的参与者都返回确定信息。\n\n2. 一个或多个参与者返回否定信息。\n\n3. 协调者等待超时。\n\n针对第一种情况，协调者会向所有参与者发送事务执行请求，具体步骤如下：\n\n1. 协调者向所有的事务参与者发送事务执行通知。\n\n2. 参与者收到通知后，执行事务，但不提交。\n\n3. 参与者将事务执行情况返回给客户端。\n\n在上面的步骤中，如果参与者等待超时，则会中断事务。 针对第二、三种情况，协调者认为事务无法正常执行，于是向各个参与者发出abort通知，请求退出预备状态，具体步骤如下：\n\n1. 协调者向所有事务参与者发送abort通知\n\n2. 参与者收到通知后，中断事务\n\n\n\n第三阶段：do_commit\n\n如果第二阶段事务未中断，那么本阶段协调者将会依据事务执行返回的结果来决定提交或回滚事务，分为三种情况：\n\n1. 所有的参与者都能正常执行事务。\n\n2. 一个或多个参与者执行事务失败。\n\n3. 协调者等待超时。\n\n针对第一种情况，协调者向各个参与者发起事务提交请求，具体步骤如下：\n\n1. 协调者向所有参与者发送事务commit通知。\n\n2. 所有参与者在收到通知之后执行commit操作，并释放占有的资源。\n\n3. 参与者向协调者反馈事务提交结果。\n\n\n\n针对第二、三种情况，协调者认为事务无法正常执行，于是向各个参与者发送事务回滚请求，具体步骤如下：\n\n1. 协调者向所有参与者发送事务rollback通知。\n\n2. 所有参与者在收到通知之后执行rollback操作，并释放占有的资源。\n\n3. 参与者向协调者反馈事务提交结果。\n\n\n\n在本阶段如果因为协调者或网络问题，导致参与者迟迟不能收到来自协调者的commit或rollback请求，那么参与者将不会如两阶段提交中那样陷入阻塞，而是等待超时后继续commit。相对于两阶段提交虽然降低了同步阻塞，但仍然无法避免数据的不一致性。\n\n\n\nReference\n\nhttps://zhuanlan.zhihu.com/p/25933039\n\nhttp://www.infoq.com/cn/articles/solution-of-distributed-system-transaction-consistency\n\nhttp://blog.csdn.net/jasonsungblog/article/details/49017955\n\nhttp://blog.csdn.net/suifeng3051/article/details/52691210\n\nhttps://my.oschina.net/wangzhenchao/blog/736909\n\n"
  },
  {
    "path": "docs/distributed/practice/搞懂分布式技术：浅谈分布式消息技术Kafka.md",
    "content": "# 目录\n\n* [目录](#目录)\n  * [**Kafka的基本介绍**](#kafka的基本介绍)\n  * [**Kafka的设计原理分析**](#kafka的设计原理分析)\n  * [**Kafka数据传输的事务特点**](#kafka数据传输的事务特点)\n  * [**Kafka消息存储格式**](#kafka消息存储格式)\n  * [**副本（replication）策略**](#副本（replication）策略)\n  * [**Kafka消息分组，消息消费原理**](#kafka消息分组，消息消费原理)\n  * [**Push vs. Pull**](#push-vs-pull)\n  * [**Kafak顺序写入与数据读取**](#kafak顺序写入与数据读取)\n  * [**消费者（读取数据）**](#消费者（读取数据）)\n  * [**Reference**](#reference)\n\n\n# 目录\n\n本文转载自 linkedkeeper.com  \n本文内容参考网络，侵删\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本文也将同步到我的个人博客：\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n该系列博文会告诉你什么是分布式系统，这对后端工程师来说是很重要的一门学问，我们会逐步了解常见的分布式技术、以及一些较为常见的分布式系统概念，同时也需要进一步了解zookeeper、分布式事务、分布式锁、负载均衡等技术，以便让你更完整地了解分布式技术的具体实战方法，为真正应用分布式技术做好准备。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->  \n\n本文主要介绍了这几部分内容：\n\n1基本介绍和架构概览\n\n2kafka事务传输的特点\n\n3kafka的消息存储格式：topic和parition\n\n4副本（replication）策略：主从broker部署和partition备份，以及选主机制\n\n5kafka消息分组，通过comsumergroup实现主体订阅\n\n6push和pull的区别，顺序写入和消息读取，零拷贝机制\n\n## **Kafka的基本介绍**\n\nKafka是最初由Linkedin公司开发，是一个分布式、分区的、多副本的、多订阅者，基于zookeeper协调的分布式日志系统（也可以当做MQ系统），常见可以用于web/nginx日志、访问日志，消息服务等等，Linkedin于2010年贡献给了Apache基金会并成为顶级开源项目。\n\n主要应用场景是：日志收集系统和消息系统。\n\nKafka主要设计目标如下：\n\n*   以时间复杂度为O(1)的方式提供消息持久化能力，即使对TB级以上数据也能保证常数时间的访问性能。\n\n*   高吞吐率。即使在非常廉价的商用机器上也能做到单机支持每秒100K条消息的传输。\n\n*   支持Kafka Server间的消息分区，及分布式消费，同时保证每个partition内的消息顺序传输。\n\n*   同时支持离线数据处理和实时数据处理。\n\n## **Kafka的设计原理分析**\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/linkedkeeper0_3573ea93-7e93-49b4-9779-6765b4fb0878.jpg)\n\n一个典型的kafka集群中包含若干producer，若干broker，若干consumer，以及一个Zookeeper集群。Kafka通过Zookeeper管理集群配置，选举leader，以及在consumer group发生变化时进行rebalance。producer使用push模式将消息发布到broker，consumer使用pull模式从broker订阅并消费消息。 　  \nKafka专用术语：\n\n*   Broker：消息中间件处理结点，一个Kafka节点就是一个broker，多个broker可以组成一个Kafka集群。\n\n*   Topic：一类消息，Kafka集群能够同时负责多个topic的分发。\n\n*   Partition：topic物理上的分组，一个topic可以分为多个partition，每个partition是一个有序的队列。\n\n*   Segment：partition物理上由多个segment组成。\n\n*   offset：每个partition都由一系列有序的、不可变的消息组成，这些消息被连续的追加到partition中。partition中的每个消息都有一个连续的序列号叫做offset，用于partition唯一标识一条消息。\n\n*   Producer：负责发布消息到Kafka broker。\n\n*   Consumer：消息消费者，向Kafka broker读取消息的客户端。\n\n*   Consumer Group：每个Consumer属于一个特定的Consumer Group。\n\n## **Kafka数据传输的事务特点**\n\n*   at most once：最多一次，这个和JMS中\"非持久化\"消息类似，发送一次，无论成败，将不会重发。消费者fetch消息，然后保存offset，然后处理消息；当client保存offset之后，但是在消息处理过程中出现了异常，导致部分消息未能继续处理。那么此后\"未处理\"的消息将不能被fetch到，这就是\"at most once\"。\n\n*   at least once：消息至少发送一次，如果消息未能接受成功，可能会重发，直到接收成功。消费者fetch消息，然后处理消息，然后保存offset。如果消息处理成功之后，但是在保存offset阶段zookeeper异常导致保存操作未能执行成功，这就导致接下来再次fetch时可能获得上次已经处理过的消息，这就是\"at least once\"，原因offset没有及时的提交给zookeeper，zookeeper恢复正常还是之前offset状态。\n\n*   exactly once：消息只会发送一次。kafka中并没有严格的去实现（基于2阶段提交），我们认为这种策略在kafka中是没有必要的。\n\n通常情况下\"at-least-once\"是我们首选。\n\n## **Kafka消息存储格式**\n\n**Topic & Partition**\n\n一个topic可以认为一个一类消息，每个topic将被分成多个partition，每个partition在存储层面是append log文件。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/linkedkeeper0_ab744f92-5ee7-4856-aa95-dea5978bc622.jpg)\n\n在Kafka文件存储中，同一个topic下有多个不同partition，每个partition为一个目录，partiton命名规则为topic名称+有序序号，第一个partiton序号从0开始，序号最大值为partitions数量减1。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/linkedkeeper0_db460b0e-81c1-432d-94c5-6113e24deb06.jpg)\n\n*   每个partion（目录）相当于一个巨型文件被平均分配到多个大小相等segment（段）数据文件中。但每个段segment file消息数量不一定相等，这种特性方便old segment file快速被删除。\n\n*   每个partiton只需要支持顺序读写就行了，segment文件生命周期由服务端配置参数决定。\n\n这样做的好处就是能快速删除无用文件，有效提高磁盘利用率。\n\n*   segment file组成：由2大部分组成，分别为index file和data file，此2个文件一一对应，成对出现，后缀\".index\"和“.log”分别表示为segment索引文件、数据文件.\n\n*   segment文件命名规则：partion全局的第一个segment从0开始，后续每个segment文件名为上一个segment文件最后一条消息的offset值。数值最大为64位long大小，19位数字字符长度，没有数字用0填充。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/linkedkeeper0_5c910b0a-7769-4b61-8ef7-f203c6cc6ebf.jpg)\n\nsegment中index与data file对应关系物理结构如下：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/linkedkeeper0_7a05abe1-0f8e-42e3-8967-d1b2c08df29d.jpg)\n\n上图中索引文件存储大量元数据，数据文件存储大量消息，索引文件中元数据指向对应数据文件中message的物理偏移地址。\n\n其中以索引文件中元数据3,497为例，依次在数据文件中表示第3个message（在全局partiton表示第368772个message），以及该消息的物理偏移地址为497。\n\n了解到segment data file由许多message组成，下面详细说明message物理结构如下：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/linkedkeeper0_ea153317-4c86-4880-bbf5-f6e3cc674da8.jpg)\n\n参数说明：\n\n\n\n| 关键字 | 解释说明 |  \n| --- | --- |  \n| 8 byte offset | 在parition(分区)内的每条消息都有一个有序的id号，这个id号被称为偏移(offset),它可以唯一确定每条消息在parition(分区)内的位置。即offset表示partiion的第多少message |  \n| 4 byte message size | message大小 |  \n| 4 byte CRC32 | 用crc32校验message |  \n| 1 byte “magic\" | 表示本次发布Kafka服务程序协议版本号 |  \n| 1 byte “attributes\" | 表示为独立版本、或标识压缩类型、或编码类型。 |  \n| 4 byte key length | 表示key的长度,当key为-1时，K byte key字段不填 |  \n| K byte key | 可选 |  \n| value bytes payload | 表示实际消息数据。 |  \n\n\n\n## **副本（replication）策略**\n\nKafka的高可靠性的保障来源于其健壮的副本（replication）策略。\n\n1) 数据同步\n\nkafka在0.8版本前没有提供Partition的Replication机制，一旦Broker宕机，其上的所有Partition就都无法提供服务，而Partition又没有备份数据，数据的可用性就大大降低了。所以0.8后提供了Replication机制来保证Broker的failover。\n\n引入Replication之后，同一个Partition可能会有多个Replica，而这时需要在这些Replication之间选出一个Leader，Producer和Consumer只与这个Leader交互，其它Replica作为Follower从Leader中复制数据。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/linkedkeeper0_977192fb-57c2-4b1f-a224-34a5225ec343.jpg)\n\n2) 副本放置策略\n\n为了更好的做负载均衡，Kafka尽量将所有的Partition均匀分配到整个集群上。\n\nKafka分配Replica的算法如下：\n\n*   将所有存活的N个Brokers和待分配的Partition排序\n\n*   将第i个Partition分配到第(i mod n)个Broker上，这个Partition的第一个Replica存在于这个分配的Broker上，并且会作为partition的优先副本\n\n*   将第i个Partition的第j个Replica分配到第((i + j) mod n)个Broker上\n\n假设集群一共有4个brokers，一个topic有4个partition，每个Partition有3个副本。下图是每个Broker上的副本分配情况。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/linkedkeeper0_e2fe9558-b7c4-4d40-bd0d-d58d594b01a2.jpg)\n\n3) 同步策略\n\nProducer在发布消息到某个Partition时，先通过ZooKeeper找到该Partition的Leader，然后无论该Topic的Replication Factor为多少，Producer只将该消息发送到该Partition的Leader。Leader会将该消息写入其本地Log。每个Follower都从Leader pull数据。这种方式上，Follower存储的数据顺序与Leader保持一致。Follower在收到该消息并写入其Log后，向Leader发送ACK。一旦Leader收到了ISR中的所有Replica的ACK，该消息就被认为已经commit了，Leader将增加HW并且向Producer发送ACK。\n\n为了提高性能，每个Follower在接收到数据后就立马向Leader发送ACK，而非等到数据写入Log中。因此，对于已经commit的消息，Kafka只能保证它被存于多个Replica的内存中，而不能保证它们被持久化到磁盘中，也就不能完全保证异常发生后该条消息一定能被Consumer消费。\n\nConsumer读消息也是从Leader读取，只有被commit过的消息才会暴露给Consumer。\n\nKafka Replication的数据流如下图所示：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/linkedkeeper0_040045e5-02e6-4086-819c-146dc37f2db2.jpg)\n\n对于Kafka而言，定义一个Broker是否“活着”包含两个条件：\n\n*   一是它必须维护与ZooKeeper的session（这个通过ZooKeeper的Heartbeat机制来实现）。\n\n*   二是Follower必须能够及时将Leader的消息复制过来，不能“落后太多”。\n\nLeader会跟踪与其保持同步的Replica列表，该列表称为ISR（即in-sync Replica）。如果一个Follower宕机，或者落后太多，Leader将把它从ISR中移除。这里所描述的“落后太多”指Follower复制的消息落后于Leader后的条数超过预定值或者Follower超过一定时间未向Leader发送fetch请求。\n\nKafka只解决fail/recover，一条消息只有被ISR里的所有Follower都从Leader复制过去才会被认为已提交。这样就避免了部分数据被写进了Leader，还没来得及被任何Follower复制就宕机了，而造成数据丢失（Consumer无法消费这些数据）。而对于Producer而言，它可以选择是否等待消息commit。这种机制确保了只要ISR有一个或以上的Follower，一条被commit的消息就不会丢失。\n\n4) leader选举\n\nLeader选举本质上是一个分布式锁，有两种方式实现基于ZooKeeper的分布式锁：\n\n*   节点名称唯一性：多个客户端创建一个节点，只有成功创建节点的客户端才能获得锁\n\n*   临时顺序节点：所有客户端在某个目录下创建自己的临时顺序节点，只有序号最小的才获得锁\n\nMajority Vote的选举策略和ZooKeeper中的Zab选举是类似的，实际上ZooKeeper内部本身就实现了少数服从多数的选举策略。kafka中对于Partition的leader副本的选举采用了第一种方法：为Partition分配副本，指定一个ZNode临时节点，第一个成功创建节点的副本就是Leader节点，其他副本会在这个ZNode节点上注册Watcher监听器，一旦Leader宕机，对应的临时节点就会被自动删除，这时注册在该节点上的所有Follower都会收到监听器事件，它们都会尝试创建该节点，只有创建成功的那个follower才会成为Leader（ZooKeeper保证对于一个节点只有一个客户端能创建成功），其他follower继续重新注册监听事件。\n\n## **Kafka消息分组，消息消费原理**\n\n同一Topic的一条消息只能被同一个Consumer Group内的一个Consumer消费，但多个Consumer Group可同时消费这一消息。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/linkedkeeper0_dfba985b-057b-4d5f-adbb-b2590a5af87d.jpg)\n\n这是Kafka用来实现一个Topic消息的广播（发给所有的Consumer）和单播（发给某一个Consumer）的手段。一个Topic可以对应多个Consumer Group。如果需要实现广播，只要每个Consumer有一个独立的Group就可以了。要实现单播只要所有的Consumer在同一个Group里。用Consumer Group还可以将Consumer进行自由的分组而不需要多次发送消息到不同的Topic。\n\n## **Push vs. Pull**\n\n作为一个消息系统，Kafka遵循了传统的方式，选择由Producer向broker push消息并由Consumer从broker pull消息。\n\npush模式很难适应消费速率不同的消费者，因为消息发送速率是由broker决定的。push模式的目标是尽可能以最快速度传递消息，但是这样很容易造成Consumer来不及处理消息，典型的表现就是拒绝服务以及网络拥塞。而pull模式则可以根据Consumer的消费能力以适当的速率消费消息。\n\n对于Kafka而言，pull模式更合适。pull模式可简化broker的设计，Consumer可自主控制消费消息的速率，同时Consumer可以自己控制消费方式——即可批量消费也可逐条消费，同时还能选择不同的提交方式从而实现不同的传输语义。\n\n## **Kafak顺序写入与数据读取**\n\n生产者（producer）是负责向Kafka提交数据的，Kafka会把收到的消息都写入到硬盘中，它绝对不会丢失数据。为了优化写入速度Kafak采用了两个技术，顺序写入和MMFile。\n\n**顺序写入**\n\n因为硬盘是机械结构，每次读写都会寻址，写入，其中寻址是一个“机械动作”，它是最耗时的。所以硬盘最“讨厌”随机I/O，最喜欢顺序I/O。为了提高读写硬盘的速度，Kafka就是使用顺序I/O。\n\n每条消息都被append到该Partition中，属于顺序写磁盘，因此效率非常高。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/linkedkeeper0_969b4966-9981-47c9-a743-4e4b882339bb.jpg)\n\n对于传统的message queue而言，一般会删除已经被消费的消息，而Kafka是不会删除数据的，它会把所有的数据都保留下来，每个消费者（Consumer）对每个Topic都有一个offset用来表示读取到了第几条数据。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/linkedkeeper0_4e1d6df1-c2c0-432c-8463-dc43f18444eb.jpg)\n\n即便是顺序写入硬盘，硬盘的访问速度还是不可能追上内存。所以Kafka的数据并不是实时的写入硬盘，它充分利用了现代操作系统分页存储来利用内存提高I/O效率。\n\n在Linux Kernal 2.2之后出现了一种叫做“零拷贝(zero-copy)”系统调用机制，就是跳过“用户缓冲区”的拷贝，建立一个磁盘空间和内存空间的直接映射，数据不再复制到“用户态缓冲区”系统上下文切换减少2次，可以提升一倍性能。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/linkedkeeper0_de0c3ea1-f84f-4ef9-b223-0ef531165b81.jpg)\n\n通过mmap，进程像读写硬盘一样读写内存（当然是虚拟机内存）。使用这种方式可以获取很大的I/O提升，省去了用户空间到内核空间复制的开销（调用文件的read会把数据先放到内核空间的内存中，然后再复制到用户空间的内存中。）\n\n## **消费者（读取数据）**\n\n试想一下，一个Web Server传送一个静态文件，如何优化？答案是zero copy。传统模式下我们从硬盘读取一个文件是这样的。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/linkedkeeper0_94a51a2c-afa2-47e4-975d-1857bd769487.jpg)\n\n先复制到内核空间（read是系统调用，放到了DMA，所以用内核空间），然后复制到用户空间（1、2）；从用户空间重新复制到内核空间（你用的socket是系统调用，所以它也有自己的内核空间），最后发送给网卡（3、4）。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/linkedkeeper0_e89b49e9-369c-4deb-b9b0-f58f02b33430.jpg)\n\nZero Copy中直接从内核空间（DMA的）到内核空间（Socket的），然后发送网卡。这个技术非常普遍，Nginx也是用的这种技术。\n\n实际上，Kafka把所有的消息都存放在一个一个的文件中，当消费者需要数据的时候Kafka直接把“文件”发送给消费者。当不需要把整个文件发出去的时候，Kafka通过调用Zero Copy的sendfile这个函数，这个函数包括：\n\n*   out_fd作为输出（一般及时socket的句柄）\n\n*   in_fd作为输入文件句柄\n\n*   off_t表示in_fd的偏移（从哪里开始读取）\n\n*   size_t表示读取多少个\n\n「 浅谈大规模分布式系统中那些技术点」系列文章：\n\n*   [浅谈分布式事务](http://www.linkedkeeper.com/detail/blog.action?bid=1013)\n\n*   [浅谈分布式服务协调技术 Zookeeper](http://www.linkedkeeper.com/detail/blog.action?bid=1014)\n\n## **Reference**\n\nhttp://www.cnblogs.com/liuming1992/p/6423007.html\n\nhttp://blog.csdn.net/lifuxiangcaohui/article/details/51374862\n\nhttp://www.jasongj.com/2015/01/02/Kafka深度解析\n\nhttp://www.infoq.com/cn/articles/kafka-analysis-part-2\n\nhttp://zqhxuyuan.github.io/2016/02/23/2016-02-23-Kafka-Controller\n\nhttps://tech.meituan.com/kafka-fs-design-theory.html\n\nhttps://my.oschina.net/silence88/blog/856195\n\nhttps://toutiao.io/posts/508935/app_preview\n\n转载请并标注: “本文转载自 linkedkeeper.com ”\n"
  },
  {
    "path": "docs/distributed/practice/搞懂分布式技术：浅谈分布式锁的几种方案.md",
    "content": "# 目录\n\n* [目录](#目录)\n  * [前言](#前言)\n  * [分布式一致性问题](#分布式一致性问题)\n  * [分布式锁需要具备哪些条件](#分布式锁需要具备哪些条件)\n  * [分布式锁实现方式](#分布式锁实现方式)\n  * [解决上述问题有两种方案](#解决上述问题有两种方案)\n  * [zookeeper分布式锁](#zookeeper分布式锁)\n  * [小结](#小结)\n\n\n# 目录\n\n本文内容参考网络，侵删\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本文也将同步到我的个人博客：\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n该系列博文会告诉你什么是分布式系统，这对后端工程师来说是很重要的一门学问，我们会逐步了解常见的分布式技术、以及一些较为常见的分布式系统概念，同时也需要进一步了解zookeeper、分布式事务、分布式锁、负载均衡等技术，以便让你更完整地了解分布式技术的具体实战方法，为真正应用分布式技术做好准备。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n本文转载自 linkedkeeper.com\n\n## 前言\n\n随着互联网技术的不断发展，数据量的不断增加，业务逻辑日趋复杂，在这种背景下，传统的集中式系统已经无法满足我们的业务需求，分布式系统被应用在更多的场景，而在分布式系统中访问共享资源就需要一种互斥机制，来防止彼此之间的互相干扰，以保证一致性，在这种情况下，我们就需要用到分布式锁。\n\n\n\n## 分布式一致性问题\n\n首先我们先来看一个小例子：\n\n假设某商城有一个商品库存剩10个，用户A想要买6个，用户B想要买5个，在理想状态下，用户A先买走了6了，库存减少6个还剩4个，此时用户B应该无法购买5个，给出数量不足的提示；而在真实情况下，用户A和B同时获取到商品剩10个，A买走6个，在A更新库存之前，B又买走了5个，此时B更新库存，商品还剩5个，这就是典型的电商“秒杀”活动。\n\n从上述例子不难看出，在高并发情况下，如果不做处理将会出现各种不可预知的后果。那么在这种高并发多线程的情况下，解决问题最有效最普遍的方法就是给共享资源或对共享资源的操作加一把锁，来保证对资源的访问互斥。在Java JDK已经为我们提供了这样的锁，利用ReentrantLcok或者synchronized，即可达到资源互斥访问的目的。但是在分布式系统中，由于分布式系统的分布性，即多线程和多进程并且分布在不同机器中，这两种锁将失去原有锁的效果，需要我们自己实现分布式锁——分布式锁。\n\n\n\n## 分布式锁需要具备哪些条件\n\n1. 获取锁和释放锁的性能要好\n\n2. 判断是否获得锁必须是原子性的，否则可能导致多个请求都获取到锁\n\n3. 网络中断或宕机无法释放锁时，锁必须被清楚，不然会发生死锁\n\n4. 可重入一个线程中可以多次获取同一把锁，比如一个线程在执行一个带锁的方法，该方法中又调用了另一个需要相同锁的方法，则该线程可以直接执行调用的方法，而无需重新获得锁；\n\n5.阻塞锁和非阻塞锁，阻塞锁即没有获取到锁，则继续等待获取锁；非阻塞锁即没有获取到锁后，不继续等待，直接返回锁失败。\n\n\n\n## 分布式锁实现方式\n\n一、数据库锁\n\n一般很少使用数据库锁，性能不好并且容易产生死锁。\n\n1. 基于MySQL锁表\n\n该实现方式完全依靠数据库唯一索引来实现，当想要获得锁时，即向数据库中插入一条记录，释放锁时就删除这条记录。这种方式存在以下几个问题：\n\n(1) 锁没有失效时间，解锁失败会导致死锁，其他线程无法再获取到锁，因为唯一索引insert都会返回失败。\n\n(2) 只能是非阻塞锁，insert失败直接就报错了，无法进入队列进行重试\n\n(3) 不可重入，同一线程在没有释放锁之前无法再获取到锁\n\n2. 采用乐观锁增加版本号\n\n根据版本号来判断更新之前有没有其他线程更新过，如果被更新过，则获取锁失败。\n\n二、缓存锁\n\n具体实例可以参考我讲述Redis的系列文章，里面有完整的Redis分布式锁实现方案\n\n这里我们主要介绍几种基于redis实现的分布式锁：\n\n1. 基于setnx、expire两个命令来实现\n\n基于setnx（set if not exist）的特点，当缓存里key不存在时，才会去set，否则直接返回false。如果返回true则获取到锁，否则获取锁失败，为了防止死锁，我们再用expire命令对这个key设置一个超时时间来避免。但是这里看似完美，实则有缺陷，当我们setnx成功后，线程发生异常中断，expire还没来的及设置，那么就会产生死锁。\n\n## 解决上述问题有两种方案\n\n1. 第一种是采用redis2.6.12版本以后的set，它提供了一系列选项\n\nEX seconds – 设置键key的过期时间，单位时秒\n\nPX milliseconds – 设置键key的过期时间，单位时毫秒\n\nNX – 只有键key不存在的时候才会设置key的值\n\nXX – 只有键key存在的时候才会设置key的值\n\n第二种采用setnx()，get()，getset()实现，大体的实现过程如下：\n\n(1) 线程Asetnx，值为超时的时间戳(t1)，如果返回true，获得锁。\n\n(2) 线程B用get 命令获取t1，与当前时间戳比较，判断是否超时，没超时false，如果已超时执行步骤3\n\n(3) 计算新的超时时间t2，使用getset命令返回t3(这个值可能其他线程已经修改过)，如果t1==t3,获得锁,如果t1!=t3说明锁被其他线程获取了\n\n(4) 获取锁后，处理完业务逻辑，再去判断锁是否超时，如果没超时删除锁，如果已超时，不用处理（防止删除其他线程的锁）\n\n2. RedLock算法\n\nredlock算法是redis作者推荐的一种分布式锁实现方式，算法的内容如下：\n\n(1) 获取当前时间；\n\n(2) 尝试从5个相互独立redis客户端获取锁；\n\n(3) 计算获取所有锁消耗的时间，当且仅当客户端从多数节点获取锁，并且获取锁的时间小于锁的有效时间，认为获得锁；\n\n(4) 重新计算有效期时间，原有效时间减去获取锁消耗的时间；\n\n(5) 删除所有实例的锁\n\nredlock算法相对于单节点redis锁可靠性要更高，但是实现起来条件也较为苛刻。\n\n(1) 必须部署5个节点才能让Redlock的可靠性更强。\n\n(2) 需要请求5个节点才能获取到锁，通过Future的方式，先并发向5个节点请求，再一起获得响应结果，能缩短响应时间，不过还是比单节点redis锁要耗费更多时间。\n\n然后由于必须获取到5个节点中的3个以上，所以可能出现获取锁冲突，即大家都获得了1-2把锁，结果谁也不能获取到锁，这个问题，redis作者借鉴了raft算法的精髓，通过冲突后在随机时间开始，可以大大降低冲突时间，但是这问题并不能很好的避免，特别是在第一次获取锁的时候，所以获取锁的时间成本增加了。\n\n如果5个节点有2个宕机，此时锁的可用性会极大降低，首先必须等待这两个宕机节点的结果超时才能返回，另外只有3个节点，客户端必须获取到这全部3个节点的锁才能拥有锁，难度也加大了。\n\n如果出现网络分区，那么可能出现客户端永远也无法获取锁的情况，介于这种情况，下面我们来看一种更可靠的分布式锁zookeeper锁。\n\n\n\n## zookeeper分布式锁\n\n关于zookeeper的分布式锁实现在之前讲述zookeeper的时候已经介绍了。这里不再赘述、\n\n首先我们来了解一下zookeeper的特性，看看它为什么适合做分布式锁，\n\nzookeeper是一个为分布式应用提供一致性服务的软件，它内部是一个分层的文件系统目录树结构，规定统一个目录下只能有一个唯一文件名。\n\n数据模型：\n\n永久节点：节点创建后，不会因为会话失效而消失\n\n临时节点：与永久节点相反，如果客户端连接失效，则立即删除节点\n\n顺序节点：与上述两个节点特性类似，如果指定创建这类节点时，zk会自动在节点名后加一个数字后缀，并且是有序的。\n\n监视器（watcher）：\n\n当创建一个节点时，可以注册一个该节点的监视器，当节点状态发生改变时，watch被触发时，ZooKeeper将会向客户端发送且仅发送一条通知，因为watch只能被触发一次。\n\n根据zookeeper的这些特性，我们来看看如何利用这些特性来实现分布式锁：\n\n1. 创建一个锁目录lock\n\n2. 希望获得锁的线程A就在lock目录下，创建临时顺序节点\n\n3. 获取锁目录下所有的子节点，然后获取比自己小的兄弟节点，如果不存在，则说明当前线程顺序号最小，获得锁\n\n4. 线程B获取所有节点，判断自己不是最小节点，设置监听(watcher)比自己次小的节点（只关注比自己次小的节点是为了防止发生“羊群效应”）\n\n5. 线程A处理完，删除自己的节点，线程B监听到变更事件，判断自己是最小的节点，获得锁。\n\n\n\n## 小结\n\n在分布式系统中，共享资源互斥访问问题非常普遍，而针对访问共享资源的互斥问题，常用的解决方案就是使用分布式锁，这里只介绍了几种常用的分布式锁，分布式锁的实现方式还有有很多种，根据业务选择合适的分布式锁，下面对上述几种锁进行一下比较：\n\n数据库锁：\n\n优点：直接使用数据库，使用简单。\n\n缺点：分布式系统大多数瓶颈都在数据库，使用数据库锁会增加数据库负担。\n\n缓存锁：\n\n优点：性能高，实现起来较为方便，在允许偶发的锁失效情况，不影响系统正常使用，建议采用缓存锁。\n\n缺点：通过锁超时机制不是十分可靠，当线程获得锁后，处理时间过长导致锁超时，就失效了锁的作用。\n\nzookeeper锁：\n\n优点：不依靠超时时间释放锁；可靠性高；系统要求高可靠性时，建议采用zookeeper锁。\n\n缺点：性能比不上缓存锁，因为要频繁的创建节点删除节点。\n"
  },
  {
    "path": "docs/distributed/practice/搞懂分布式技术：消息队列因何而生.md",
    "content": "\n# 目录\n* [何时需要消息队列](#何时需要消息队列)\n    * [解耦](#解耦)\n    * [最终一致性](#最终一致性)\n    * [广播](#广播)\n    * [错峰与流控](#错峰与流控)\n    * [消息队列的流派之争](#消息队列的流派之争)\n    * [MQ是什么](#mq是什么)\n    * [MQ的流派](#mq的流派)\n        * [有broker](#有broker)\n        * [无broker](#无broker)\n    * [MQ只能异步吗](#mq只能异步吗)\n    * [总结](#总结)\n* [如何设计一个消息队列](#如何设计一个消息队列)\n    * [综述](#综述)\n    * [实现队列基本功能](#实现队列基本功能)\n        * [RPC通信协议](#rpc通信协议)\n        * [高可用](#高可用)\n        * [服务端承载消息堆积的能力](#服务端承载消息堆积的能力)\n        * [存储子系统的选择](#存储子系统的选择)\n        * [消费关系解析](#消费关系解析)\n    * [队列高级特性设计](#队列高级特性设计)\n        * [可靠投递（最终一致性）](#可靠投递（最终一致性）)\n            * [消费确认](#消费确认)\n            * [重复消息和顺序消息](#重复消息和顺序消息)\n        * [事务](#事务)\n        * [性能相关](#性能相关)\n            * [异步/同步](#异步同步)\n    * [push还是pull](#push还是pull)\n        * [慢消费](#慢消费)\n        * [消息延迟与忙等](#消息延迟与忙等)\n* [总结](#总结-1)\n\n\n本文转载自 linkedkeeper.com\n\n本文内容参考网络，侵删\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本文也将同步到我的个人博客：\n\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n该系列博文会告诉你什么是分布式系统，这对后端工程师来说是很重要的一门学问，我们会逐步了解常见的分布式技术、以及一些较为常见的分布式系统概念，同时也需要进一步了解zookeeper、分布式事务、分布式锁、负载均衡等技术，以便让你更完整地了解分布式技术的具体实战方法，为真正应用分布式技术做好准备。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n消息队列已经逐渐成为企业IT系统内部通信的核心手段。它具有低耦合、可靠投递、广播、流量控制、最终一致性等一系列功能，成为异步RPC的主要手段之一。\n当今市面上有很多主流的消息中间件，如老牌的ActiveMQ、RabbitMQ，炙手可热的Kafka，阿里巴巴自主开发的Notify、MetaQ、RocketMQ等。\n本文不会一一介绍这些消息队列的所有特性，而是探讨一下自主开发设计一个消息队列时，你需要思考和设计的重要方面。过程中我们会参考这些成熟消息队列的很多重要思想。\n本文首先会阐述什么时候你需要一个消息队列，然后以Push模型为主，从零开始分析设计一个消息队列时需要考虑到的问题，如RPC、高可用、顺序和重复消息、可靠投递、消费关系解析等。\n也会分析以Kafka为代表的pull模型所具备的优点。最后是一些高级主题，如用批量/异步提高性能、pull模型的系统设计理念、存储子系统的设计、流量控制的设计、公平调度的实现等。其中最后四个方面会放在下篇讲解。\n\n# 何时需要消息队列\n\n当你需要使用消息队列时，首先需要考虑它的必要性。可以使用mq的场景有很多，最常用的几种，是做业务解耦/最终一致性/广播/错峰流控等。反之，如果需要强一致性，关注业务逻辑的处理结果，则RPC显得更为合适。\n\n## 解耦\n\n解耦是消息队列要解决的最本质问题。所谓解耦，简单点讲就是一个事务，只关心核心的流程。而需要依赖其他系统但不那么重要的事情，有通知即可，无需等待结果。换句话说，基于消息的模型，关心的是“通知”，而非“处理”。\n比如在美团旅游，我们有一个产品中心，产品中心上游对接的是主站、移动后台、旅游供应链等各个数据源；下游对接的是筛选系统、API系统等展示系统。当上游的数据发生变更的时候，如果不使用消息系统，势必要调用我们的接口来更新数据，就特别依赖产品中心接口的稳定性和处理能力。但其实，作为旅游的产品中心，也许只有对于旅游自建供应链，产品中心更新成功才是他们关心的事情。而对于团购等外部系统，产品中心更新成功也好、失败也罢，并不是他们的职责所在。他们只需要保证在信息变更的时候通知到我们就好了。\n而我们的下游，可能有更新索引、刷新缓存等一系列需求。对于产品中心来说，这也不是我们的职责所在。说白了，如果他们定时来拉取数据，也能保证数据的更新，只是实时性没有那么强。但使用接口方式去更新他们的数据，显然对于产品中心来说太过于“重量级”了，只需要发布一个产品ID变更的通知，由下游系统来处理，可能更为合理。\n再举一个例子，对于我们的订单系统，订单最终支付成功之后可能需要给用户发送短信积分什么的，但其实这已经不是我们系统的核心流程了。如果外部系统速度偏慢（比如短信网关速度不好），那么主流程的时间会加长很多，用户肯定不希望点击支付过好几分钟才看到结果。那么我们只需要通知短信系统“我们支付成功了”，不一定非要等待它处理完成。\n\n## 最终一致性\n\n最终一致性指的是两个系统的状态保持一致，要么都成功，要么都失败。当然有个时间限制，理论上越快越好，但实际上在各种异常的情况下，可能会有一定延迟达到最终一致状态，但最后两个系统的状态是一样的。\n业界有一些为“最终一致性”而生的消息队列，如Notify（阿里）、QMQ（去哪儿）等，其设计初衷，就是为了交易系统中的高可靠通知。\n以一个银行的转账过程来理解最终一致性，转账的需求很简单，如果A系统扣钱成功，则B系统加钱一定成功。反之则一起回滚，像什么都没发生一样。\n然而，这个过程中存在很多可能的意外：\n\n1.  A扣钱成功，调用B加钱接口失败。\n2.  A扣钱成功，调用B加钱接口虽然成功，但获取最终结果时网络异常引起超时。\n3.  A扣钱成功，B加钱失败，A想回滚扣的钱，但A机器down机。\n\n可见，想把这件看似简单的事真正做成，真的不那么容易。所有跨VM的一致性问题，从技术的角度讲通用的解决方案是：\n\n1.  强一致性，分布式事务，但落地太难且成本太高，后文会具体提到。\n2.  最终一致性，主要是用“记录”和“补偿”的方式。在做所有的不确定的事情之前，先把事情记录下来，然后去做不确定的事情，结果可能是：成功、失败或是不确定，“不确定”（例如超时等）可以等价为失败。成功就可以把记录的东西清理掉了，对于失败和不确定，可以依靠定时任务等方式把所有失败的事情重新搞一遍，直到成功为止。\n    回到刚才的例子，系统在A扣钱成功的情况下，把要给B“通知”这件事记录在库里（为了保证最高的可靠性可以把通知B系统加钱和扣钱成功这两件事维护在一个本地事务里），通知成功则删除这条记录，通知失败或不确定则依靠定时任务补偿性地通知我们，直到我们把状态更新成正确的为止。\n    整个这个模型依然可以基于RPC来做，但可以抽象成一个统一的模型，基于消息队列来做一个“企业总线”。\n    具体来说，本地事务维护业务变化和通知消息，一起落地（失败则一起回滚），然后RPC到达broker，在broker成功落地后，RPC返回成功，本地消息可以删除。否则本地消息一直靠定时任务轮询不断重发，这样就保证了消息可靠落地broker。\n    broker往consumer发送消息的过程类似，一直发送消息，直到consumer发送消费成功确认。\n    我们先不理会重复消息的问题，通过两次消息落地加补偿，下游是一定可以收到消息的。然后依赖状态机版本号等方式做判重，更新自己的业务，就实现了最终一致性。\n\n最终一致性不是消息队列的必备特性，但确实可以依靠消息队列来做最终一致性的事情。另外，所有不保证100%不丢消息的消息队列，理论上无法实现最终一致性。好吧，应该说理论上的100%，排除系统严重故障和bug。\n像Kafka一类的设计，在设计层面上就有丢消息的可能（比如定时刷盘，如果掉电就会丢消息）。哪怕只丢千分之一的消息，业务也必须用其他的手段来保证结果正确。\n\n## 广播\n\n消息队列的基本功能之一是进行广播。如果没有消息队列，每当一个新的业务方接入，我们都要联调一次新接口。有了消息队列，我们只需要关心消息是否送达了队列，至于谁希望订阅，是下游的事情，无疑极大地减少了开发和联调的工作量。\n比如本文开始提到的产品中心发布产品变更的消息，以及景点库很多去重更新的消息，可能“关心”方有很多个，但产品中心和景点库只需要发布变更消息即可，谁关心谁接入。\n\n## 错峰与流控\n\n试想上下游对于事情的处理能力是不同的。比如，Web前端每秒承受上千万的请求，并不是什么神奇的事情，只需要加多一点机器，再搭建一些LVS负载均衡设备和Nginx等即可。但数据库的处理能力却十分有限，即使使用SSD加分库分表，单机的处理能力仍然在万级。由于成本的考虑，我们不能奢求数据库的机器数量追上前端。\n这种问题同样存在于系统和系统之间，如短信系统可能由于短板效应，速度卡在网关上（每秒几百次请求），跟前端的并发量不是一个数量级。但用户晚上个半分钟左右收到短信，一般是不会有太大问题的。如果没有消息队列，两个系统之间通过协商、滑动窗口等复杂的方案也不是说不能实现。但系统复杂性指数级增长，势必在上游或者下游做存储，并且要处理定时、拥塞等一系列问题。而且每当有处理能力有差距的时候，都需要单独开发一套逻辑来维护这套逻辑。所以，利用中间系统转储两个系统的通信内容，并在下游系统有能力处理这些消息的时候，再处理这些消息，是一套相对较通用的方式。\n\n总而言之，消息队列不是万能的。对于需要强事务保证而且延迟敏感的，RPC是优于消息队列的。\n对于一些无关痛痒，或者对于别人非常重要但是对于自己不是那么关心的事情，可以利用消息队列去做。\n支持最终一致性的消息队列，能够用来处理延迟不那么敏感的“分布式事务”场景，而且相对于笨重的分布式事务，可能是更优的处理方式。\n当上下游系统处理能力存在差距的时候，利用消息队列做一个通用的“漏斗”。在下游有能力处理的时候，再进行分发。\n\n如果下游有很多系统关心你的系统发出的通知的时候，果断地使用消息队列吧。\n\n## 消息队列的流派之争\n\n\n\n这篇文章的标题很难起，网上一翻全是各种MQ的性能比较，很容易让人以为我也是这么“粗俗”的人（o(╯□╰)o）。我这篇文章想要表达的是——它们根本不是一个东西，有毛的性能好比较？\n\n## MQ是什么\n\n\n\nMessage Queue（MQ），消息队列中间件。很多人都说：MQ通过将消息的发送和接收分离来实现应用程序的异步和解偶，这个给人的直觉是——MQ是异步的，用来解耦的，但是这个只是MQ的效果而不是目的。MQ真正的目的是为了通讯，屏蔽底层复杂的通讯协议，定义了一套应用层的、更加简单的通讯协议。一个分布式系统中两个模块之间通讯要么是HTTP，要么是自己开发的TCP，但是这两种协议其实都是原始的协议。HTTP协议很难实现两端通讯——模块A可以调用B，B也可以主动调用A，如果要做到这个两端都要背上WebServer，而且还不支持长连接（HTTP 2.0的库根本找不到）。TCP就更加原始了，粘包、心跳、私有的协议，想一想头皮就发麻。MQ所要做的就是在这些协议之上构建一个简单的“协议”——生产者/消费者模型。MQ带给我的“协议”不是具体的通讯协议，而是更高层次通讯模型。它定义了两个对象——发送数据的叫生产者；消费数据的叫消费者， 提供一个SDK让我们可以定义自己的生产者和消费者实现消息通讯而无视底层通讯协议。\n\n## MQ的流派\n\n\n\n列出功能表来比较MQ差异或者来一场“MQ性能大比武”的做法都是比较扯的，首先要做的事情应该是分类。我理解的MQ分为两个流派\n\n### 有broker\n\n\n\n这个流派通常有一台服务器作为Broker，所有的消息都通过它中转。生产者把消息发送给它就结束自己的任务了，Broker则把消息主动推送给消费者（或者消费者主动轮询）。\n\n* 重Topic流\n\n  kafka、JMS就属于这个流派，生产者会发送key和数据到Broker，由Broker比较key之后决定给那个消费者。这种模式是我们最常见的模式，是我们对MQ最多的印象。在这种模式下一个topic往往是一个比较大的概念，甚至一个系统中就可能只有一个topic，topic某种意义上就是queue，生产者发送key相当于说：“hi，把数据放到`key`的队列中”。\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407220345.png)\n\n如上图所示，Broker定义了三个队列，key1，key2，key3，生产者发送数据的时候会发送key1和data，Broker在推送数据的时候则推送data（也可能把key带上）。虽然架构一样但是kafka的性能要比jms的性能不知道高到多少倍，所以基本这种类型的MQ只有kafka一种备选方案。如果你需要一条暴力的数据流（在乎性能而非灵活性)那么kafka是最好的选择。\n\n*   轻Topic流\n\n这种的代表是RabbitMQ（或者说是AMQP）。生产者发送key和数据，消费者定义订阅的队列，Broker收到数据之后会通过一定的逻辑计算出key对应的队列，然后把数据交给队列。\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407220359.png)\n\n\n\n注意到了吗？这种模式下解耦了key和queue，在这种架构中queue是非常轻量级的（在RabbitMQ中它的上限取决于你的内存），消费者关心的只是自己的queue；生产者不必关心数据最终给谁只要指定key就行了，中间的那层映射在AMQP中叫exchange（交换机）。AMQP中有四种种exchange——Direct exchange：key就等于queue；Fanout exchange：无视key，给所有的queue都来一份；Topic exchange：key可以用“宽字符”模糊匹配queue；最后一个厉害了Headers exchange：无视key，通过查看消息的头部元数据来决定发给那个queue（AMQP头部元数据非常丰富而且可以自定义）。这种结构的架构给通讯带来了很大的灵活性，我们能想到的通讯方式都可以用这四种exchange表达出来。如果你需要一个企业数据总线（在乎灵活性）那么RabbitMQ绝对的值得一用。\n\n### 无broker\n\n\n\n此门派是AMQP的“叛徒”，某位道友嫌弃AMQP太“重”（那是他没看到用Erlang实现的时候是多么的行云流水） 所以设计了zeromq。这位道友非常睿智，他非常敏锐的意识到——MQ是更高级的Socket，它是解决通讯问题的。所以ZeroMQ被设计成了一个“库”而不是一个中间件，这种实现也可以达到——没有broker的目的。\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407220411.png)\n\n\n\n节点之间通讯的消息都是发送到彼此的队列中，每个节点都既是生产者又是消费者。ZeroMQ做的事情就是封装出一套类似于scoket的API可以完成发送数据，读取数据。如果你仔细想一下其实ZeroMQ是这样的\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407220426.png)\n\n\n\n\n\n顿悟了吗？Actor模型，ZeroMQ其实就是一个跨语言的、重量级的Actor模型邮箱库。你可以把自己的程序想象成一个actor，zeromq就是提供邮箱功能的库；zeromq可以实现同一台机器的IPC通讯也可以实现不同机器的TCP、UDP通讯。如果你需要一个强大的、灵活、野蛮的通讯能力，别犹豫zeromq。\n\n## MQ只能异步吗\n\n\n\n答案是否定了，首先ZeroMQ支持请求->应答模式；其次RabbitMQ提供了RPC是地地道道的同步通讯，只有JMS、kafka这种架构才只能做异步。我们很多人第一次接触MQ都是JMS之类的这种所以才会产生这种错觉。\n\n## 总结\n\n\n\nkafka，zeromq，rabbitmq代表了三种完全不同风格的MQ架构；关注点完全不同：\n\n*   kafka在乎的是性能，速度\n\n*   rabbitmq追求的是灵活\n\n*   zeromq追求的是轻量级、分布式\n\n如果你拿zeromq来做大数据量的传输功能，不是生产者的内存“爆掉”就是消费者被“压死”；如果你用kafka做通讯总线那绝对的不会快只能更慢；你想要rabbitmq实现分布式，那真的是难为它。\n\n# 如何设计一个消息队列\n\n## 综述\n\n我们现在明确了消息队列的使用场景，下一步就是如何设计实现一个消息队列了。\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407220539.png)\n基于消息的系统模型，不一定需要broker(消息队列服务端)。市面上的的Akka（actor模型)、ZeroMQ等，其实都是基于消息的系统设计范式，但是没有broker。\n我们之所以要设计一个消息队列，并且配备broker，无外乎要做两件事情：\n\n1.  消息的转储，在更合适的时间点投递，或者通过一系列手段辅助消息最终能送达消费机。\n2.  规范一种范式和通用的模式，以满足解耦、最终一致性、错峰等需求。\n    掰开了揉碎了看，最简单的消息队列可以做成一个消息转发器，把一次RPC做成两次RPC。发送者把消息投递到服务端（以下简称broker），服务端再将消息转发一手到接收端，就是这么简单。\n\n一般来讲，设计消息队列的整体思路是先build一个整体的数据流,例如producer发送给broker,broker发送给consumer,consumer回复消费确认，broker删除/备份消息等。\n利用RPC将数据流串起来。然后考虑RPC的高可用性，尽量做到无状态，方便水平扩展。\n之后考虑如何承载消息堆积，然后在合适的时机投递消息，而处理堆积的最佳方式，就是存储，存储的选型需要综合考虑性能/可靠性和开发维护成本等诸多因素。\n为了实现广播功能，我们必须要维护消费关系，可以利用zk/config server等保存消费关系。\n在完成了上述几个功能后，消息队列基本就实现了。然后我们可以考虑一些高级特性，如可靠投递，事务特性，性能优化等。\n下面我们会以设计消息队列时重点考虑的模块为主线，穿插灌输一些消息队列的特性实现方法，来具体分析设计实现一个消息队列时的方方面面。\n\n## 实现队列基本功能\n\n### RPC通信协议\n\n刚才讲到，所谓消息队列，无外乎两次RPC加一次转储，当然需要消费端最终做消费确认的情况是三次RPC。既然是RPC，就必然牵扯出一系列话题，什么负载均衡啊、服务发现啊、通信协议啊、序列化协议啊，等等。在这一块，我的强烈建议是不要重复造轮子。利用公司现有的RPC框架：Thrift也好，Dubbo也好，或者是其他自定义的框架也好。因为消息队列的RPC，和普通的RPC没有本质区别。当然了，自主利用Memchached或者Redis协议重新写一套RPC框架并非不可（如MetaQ使用了自己封装的Gecko NIO框架，卡夫卡也用了类似的协议）。但实现成本和难度无疑倍增。排除对效率的极端要求，都可以使用现成的RPC框架。\n简单来讲，服务端提供两个RPC服务，一个用来接收消息，一个用来确认消息收到。并且做到不管哪个server收到消息和确认消息，结果一致即可。当然这中间可能还涉及跨IDC的服务的问题。这里和RPC的原则是一致的，尽量优先选择本机房投递。你可能会问，如果producer和consumer本身就在两个机房了，怎么办？首先，broker必须保证感知的到所有consumer的存在。其次，producer尽量选择就近的机房就好了。\n\n### 高可用\n\n其实所有的高可用，是依赖于RPC和存储的高可用来做的。先来看RPC的高可用，美团的基于MTThrift的RPC框架，阿里的Dubbo等，其本身就具有服务自动发现，负载均衡等功能。而消息队列的高可用，只要保证broker接受消息和确认消息的接口是幂等的，并且consumer的几台机器处理消息是幂等的，这样就把消息队列的可用性，转交给RPC框架来处理了。\n那么怎么保证幂等呢？最简单的方式莫过于共享存储。broker多机器共享一个DB或者一个分布式文件/kv系统，则处理消息自然是幂等的。就算有单点故障，其他节点可以立刻顶上。另外failover可以依赖定时任务的补偿，这是消息队列本身天然就可以支持的功能。存储系统本身的可用性我们不需要操太多心，放心大胆的交给DBA们吧！\n对于不共享存储的队列，如Kafka使用分区加主备模式，就略微麻烦一些。需要保证每一个分区内的高可用性，也就是每一个分区至少要有一个主备且需要做数据的同步，关于这块HA的细节，可以参考下篇pull模型消息系统设计。\n\n### 服务端承载消息堆积的能力\n\n消息到达服务端如果不经过任何处理就到接收者了，broker就失去了它的意义。为了满足我们错峰/流控/最终可达等一系列需求，把消息存储下来，然后选择时机投递就显得是顺理成章的了。\n只是这个存储可以做成很多方式。比如存储在内存里，存储在分布式KV里，存储在磁盘里，存储在数据库里等等。但归结起来，主要有持久化和非持久化两种。\n持久化的形式能更大程度地保证消息的可靠性（如断电等不可抗外力），并且理论上能承载更大限度的消息堆积（外存的空间远大于内存）。\n但并不是每种消息都需要持久化存储。很多消息对于投递性能的要求大于可靠性的要求，且数量极大（如日志）。这时候，消息不落地直接暂存内存，尝试几次failover，最终投递出去也未尝不可。\n市面上的消息队列普遍两种形式都支持。当然具体的场景还要具体结合公司的业务来看。\n\n### 存储子系统的选择\n\n我们来看看如果需要数据落地的情况下各种存储子系统的选择。理论上，从速度来看，文件系统>分布式KV（持久化）>分布式文件系统>数据库，而可靠性却截然相反。还是要从支持的业务场景出发作出最合理的选择，如果你们的消息队列是用来支持支付/交易等对可靠性要求非常高，但对性能和量的要求没有这么高，而且没有时间精力专门做文件存储系统的研究，DB是最好的选择。\n但是DB受制于IOPS，如果要求单broker 5位数以上的QPS性能，基于文件的存储是比较好的解决方案。整体上可以采用数据文件+索引文件的方式处理，具体这块的设计比较复杂，可以参考下篇的存储子系统设计。\n分布式KV（如MongoDB，HBase）等，或者持久化的Redis，由于其编程接口较友好，性能也比较可观，如果在可靠性要求不是那么高的场景，也不失为一个不错的选择。\n\n### 消费关系解析\n\n现在我们的消息队列初步具备了转储消息的能力。下面一个重要的事情就是解析发送接收关系，进行正确的消息投递了。\n市面上的消息队列定义了一堆让人晕头转向的名词，如JMS 规范中的Topic/Queue，Kafka里面的Topic/Partition/ConsumerGroup，RabbitMQ里面的Exchange等等。抛开现象看本质，无外乎是单播与广播的区别。所谓单播，就是点到点；而广播，是一点对多点。当然，对于互联网的大部分应用来说，组间广播、组内单播是最常见的情形。\n消息需要通知到多个业务集群，而一个业务集群内有很多台机器，只要一台机器消费这个消息就可以了。\n当然这不是绝对的，很多时候组内的广播也是有适用场景的，如本地缓存的更新等等。另外，消费关系除了组内组间，可能会有多级树状关系。这种情况太过于复杂，一般不列入考虑范围。所以，一般比较通用的设计是支持组间广播，不同的组注册不同的订阅。组内的不同机器，如果注册一个相同的ID，则单播；如果注册不同的ID(如IP地址+端口)，则广播。\n至于广播关系的维护，一般由于消息队列本身都是集群，所以都维护在公共存储上，如config server、zookeeper等。维护广播关系所要做的事情基本是一致的:\n\n1.  发送关系的维护。\n2.  发送关系变更时的通知。\n\n\n\n## 队列高级特性设计\n\n上面都是些消息队列基本功能的实现，下面来看一些关于消息队列特性相关的内容，不管可靠投递/消息丢失与重复以及事务乃至于性能，不是每个消息队列都会照顾到，所以要依照业务的需求，来仔细衡量各种特性实现的成本，利弊，最终做出最为合理的设计。\n\n### 可靠投递（最终一致性）\n\n这是个激动人心的话题，完全不丢消息，究竟可不可能？答案是，完全可能，前提是消息可能会重复，并且，在异常情况下，要接受消息的延迟。\n方案说简单也简单，就是每当要发生不可靠的事情（RPC等）之前，先将消息落地，然后发送。当失败或者不知道成功失败（比如超时）时，消息状态是待发送，定时任务不停轮询所有待发送消息，最终一定可以送达。\n具体来说：\n\n1.  producer往broker发送消息之前，需要做一次落地。\n2.  请求到server后，server确保数据落地后再告诉客户端发送成功。\n3.  支持广播的消息队列需要对每个待发送的endpoint，持久化一个发送状态，直到所有endpoint状态都OK才可删除消息。\n\n对于各种不确定（超时、down机、消息没有送达、送达后数据没落地、数据落地了回复没收到），其实对于发送方来说，都是一件事情，就是消息没有送达。\n重推消息所面临的问题就是消息重复。重复和丢失就像两个噩梦，你必须要面对一个。好在消息重复还有处理的机会，消息丢失再想找回就难了。\nAnyway，作为一个成熟的消息队列，应该尽量在各个环节减少重复投递的可能性，不能因为重复有解决方案就放纵的乱投递。\n最后说一句，不是所有的系统都要求最终一致性或者可靠投递，比如一个论坛系统、一个招聘系统。一个重复的简历或话题被发布，可能比丢失了一个发布显得更让用户无法接受。不断重复一句话，任何基础组件要服务于业务场景。\n\n#### 消费确认\n\n当broker把消息投递给消费者后，消费者可以立即响应我收到了这个消息。但收到了这个消息只是第一步，我能不能处理这个消息却不一定。或许因为消费能力的问题，系统的负荷已经不能处理这个消息；或者是刚才状态机里面提到的消息不是我想要接收的消息，主动要求重发。\n把消息的送达和消息的处理分开，这样才真正的实现了消息队列的本质-解耦。所以，允许消费者主动进行消费确认是必要的。当然，对于没有特殊逻辑的消息，默认Auto Ack也是可以的，但一定要允许消费方主动ack。\n对于正确消费ack的，没什么特殊的。但是对于reject和error，需要特别说明。reject这件事情，往往业务方是无法感知到的，系统的流量和健康状况的评估，以及处理能力的评估是一件非常复杂的事情。举个极端的例子，收到一个消息开始build索引，可能这个消息要处理半个小时，但消息量却是非常的小。所以reject这块建议做成滑动窗口/线程池类似的模型来控制，\n消费能力不匹配的时候，直接拒绝，过一段时间重发，减少业务的负担。\n但业务出错这件事情是只有业务方自己知道的，就像上文提到的状态机等等。这时应该允许业务方主动ack error，并可以与broker约定下次投递的时间。\n\n#### 重复消息和顺序消息\n\n上文谈到重复消息是不可能100%避免的，除非可以允许丢失，那么，顺序消息能否100%满足呢? 答案是可以，但条件更为苛刻：\n\n1.  允许消息丢失。\n2.  从发送方到服务方到接受者都是单点单线程。\n\n所以绝对的顺序消息基本上是不能实现的，当然在METAQ/Kafka等pull模型的消息队列中，单线程生产/消费，排除消息丢失，也是一种顺序消息的解决方案。\n一般来讲，一个主流消息队列的设计范式里，应该是不丢消息的前提下，尽量减少重复消息，不保证消息的投递顺序。\n谈到重复消息，主要是两个话题：\n\n1.  如何鉴别消息重复，并幂等的处理重复消息。\n2.  一个消息队列如何尽量减少重复消息的投递。\n\n先来看看第一个话题，每一个消息应该有它的唯一身份。不管是业务方自定义的，还是根据IP/PID/时间戳生成的MessageId，如果有地方记录这个MessageId，消息到来是能够进行比对就\n能完成重复的鉴定。数据库的唯一键/bloom filter/分布式KV中的key，都是不错的选择。由于消息不能被永久存储，所以理论上都存在消息从持久化存储移除的瞬间上游还在投递的可能（上游因种种原因投递失败，不停重试，都到了下游清理消息的时间）。这种事情都是异常情况下才会发生的，毕竟是小众情况。两分钟消息都还没送达，多送一次又能怎样呢？幂等的处理消息是一门艺术，因为种种原因重复消息或者错乱的消息还是来到了，说两种通用的解决方案：\n\n1.  版本号。\n\n1.  状态机。\n\n### 事务\n\n持久性是事务的一个特性，然而只满足持久性却不一定能满足事务的特性。还是拿扣钱/加钱的例子讲。满足事务的一致性特征，则必须要么都不进行，要么都能成功。\n解决方案从大方向上有两种：\n\n1.  两阶段提交，分布式事务。\n2.  本地事务，本地落地，补偿发送。\n\n分布式事务存在的最大问题是成本太高，两阶段提交协议，对于仲裁down机或者单点故障，几乎是一个无解的黑洞。对于交易密集型或者I/O密集型的应用，没有办法承受这么高的网络延迟，系统复杂性。\n并且成熟的分布式事务一定构建与比较靠谱的商用DB和商用中间件上，成本也太高。\n那如何使用本地事务解决分布式事务的问题呢？以本地和业务在一个数据库实例中建表为例子，与扣钱的业务操作同一个事务里，将消息插入本地数据库。如果消息入库失败，则业务回滚；如果消息入库成功，事务提交。\n然后发送消息（注意这里可以实时发送，不需要等定时任务检出，以提高消息实时性）。以后的问题就是前文的最终一致性问题所提到的了，只要消息没有发送成功，就一直靠定时任务重试。\n这里有一个关键的点，本地事务做的，是业务落地和消息落地的事务，而不是业务落地和RPC成功的事务。这里很多人容易混淆，如果是后者，无疑是事务嵌套RPC，是大忌，会有长事务死锁等各种风险。\n而消息只要成功落地，很大程度上就没有丢失的风险（磁盘物理损坏除外）。而消息只要投递到服务端确认后本地才做删除，就完成了producer->broker的可靠投递，并且当消息存储异常时，业务也是可以回滚的。\n本地事务存在两个最大的使用障碍：\n\n1.  配置较为复杂，“绑架”业务方，必须本地数据库实例提供一个库表。\n2.  对于消息延迟高敏感的业务不适用。\n\n话说回来，不是每个业务都需要强事务的。扣钱和加钱需要事务保证，但下单和生成短信却不需要事务，不能因为要求发短信的消息存储投递失败而要求下单业务回滚。所以，一个完整的消息队列应该定义清楚自己可以投递的消息类型，如事务型消息，本地非持久型消息，以及服务端不落地的非可靠消息等。对不同的业务场景做不同的选择。另外事务的使用应该尽量低成本、透明化，可以依托于现有的成熟框架，如Spring的声明式事务做扩展。业务方只需要使用@Transactional标签即可。\n\n### 性能相关\n\n#### 异步/同步\n\n首先澄清一个概念，异步，同步和oneway是三件事。异步，归根结底你还是需要关心结果的，但可能不是当时的时间点关心，可以用轮询或者回调等方式处理结果；同步是需要当时关心\n的结果的；而oneway是发出去就不管死活的方式，这种对于某些完全对可靠性没有要求的场景还是适用的，但不是我们重点讨论的范畴。\n回归来看，任何的RPC都是存在客户端异步与服务端异步的，而且是可以任意组合的：客户端同步对服务端异步，客户端异步对服务端异步，客户端同步对服务端同步，客户端异步对服务端同步。\n对于客户端来说，同步与异步主要是拿到一个Result，还是Future(Listenable)的区别。实现方式可以是线程池，NIO或者其他事件机制，这里先不展开讲。\n服务端异步可能稍微难理解一点，这个是需要RPC协议支持的。参考servlet 3.0规范，服务端可以吐一个future给客户端，并且在future done的时候通知客户端。\n整个过程可以参考下面的代码：\n\n客户端同步服务端异步。\n\n```\nFuture<Result> future = request(server);//server立刻返回futuresynchronized(future){while(!future.isDone()){   future.wait();//server处理结束后会notify这个future，并修改isdone标志}}return future.get();\n```\n\n客户端同步服务端同步。\n\n```\nResult result = request(server);\n\n```\n\n客户端异步服务端同步(这里用线程池的方式)。\n\n```\nFuture<Result> future = executor.submit(new Callable(){public void call<Result>(){    result = request(server);}})return future;\n```\n\n客户端异步服务端异步。\n\n```\nFuture<Result> future = request(server);//server立刻返回future return future\n```\n\n上面说了这么多，其实是想让大家脱离两个误区：\n\n1.  RPC只有客户端能做异步，服务端不能。\n2.  异步只能通过线程池。\n\n那么，服务端使用异步最大的好处是什么呢？说到底，是解放了线程和I/O。试想服务端有一堆I/O等待处理，如果每个请求都需要同步响应，每条消息都需要结果立刻返回，那么就几乎没法做I/O合并\n（当然接口可以设计成batch的，但可能batch发过来的仍然数量较少）。而如果用异步的方式返回给客户端future，就可以有机会进行I/O的合并，把几个批次发过来的消息一起落地（这种合并对于MySQL等允许batch insert的数据库效果尤其明显），并且彻底释放了线程。不至于说来多少请求开多少线程，能够支持的并发量直线提高。\n来看第二个误区，返回future的方式不一定只有线程池。换句话说，可以在线程池里面进行同步操作，也可以进行异步操作，也可以不使用线程池使用异步操作（NIO、事件）。\n回到消息队列的议题上，我们当然不希望消息的发送阻塞主流程（前面提到了，server端如果使用异步模型，则可能因消息合并带来一定程度上的消息延迟），所以可以先使用线程池提交一个发送请求，主流程继续往下走。\n但是线程池中的请求关心结果吗？Of course，必须等待服务端消息成功落地，才算是消息发送成功。所以这里的模型，准确地说事客户端半同步半异步（使用线程池不阻塞主流程，但线程池中的任务需要等待server端的返回），server端是纯异步。客户端的线程池wait在server端吐回的future上，直到server端处理完毕，才解除阻塞继续进行。\n\n总结一句，同步能够保证结果，异步能够保证效率，要合理的结合才能做到最好的效率。\n\n## push还是pull\n\n上文提到的消息队列，大多是针对push模型的设计。现在市面上有很多经典的也比较成熟的pull模型的消息队列，如Kafka、MetaQ等。这跟JMS中传统的push方式有很大的区别，可谓另辟蹊径。\n我们简要分析下push和pull模型各自存在的利弊。\n\n### 慢消费\n\n慢消费无疑是push模型最大的致命伤，穿成流水线来看，如果消费者的速度比发送者的速度慢很多，势必造成消息在broker的堆积。假设这些消息都是有用的无法丢弃的，消息就要一直在broker端保存。当然这还不是最致命的，最致命的是broker给consumer推送一堆consumer无法处理的消息，consumer不是reject就是error，然后来回踢皮球。\n反观pull模式，consumer可以按需消费，不用担心自己处理不了的消息来骚扰自己，而broker堆积消息也会相对简单，无需记录每一个要发送消息的状态，只需要维护所有消息的队列和偏移量就可以了。所以对于建立索引等慢消费，消息量有限且到来的速度不均匀的情况，pull模式比较合适。\n\n### 消息延迟与忙等\n\n这是pull模式最大的短板。由于主动权在消费方，消费方无法准确地决定何时去拉取最新的消息。如果一次pull取到消息了还可以继续去pull，如果没有pull取到则需要等待一段时间重新pull。\n但等待多久就很难判定了。你可能会说，我可以有xx动态pull取时间调整算法，但问题的本质在于，有没有消息到来这件事情决定权不在消费方。也许1分钟内连续来了1000条消息，然后半个小时没有新消息产生，\n可能你的算法算出下次最有可能到来的时间点是31分钟之后，或者60分钟之后，结果下条消息10分钟后到了，是不是很让人沮丧？\n当然也不是说延迟就没有解决方案了，业界较成熟的做法是从短时间开始（不会对broker有太大负担），然后指数级增长等待。比如开始等5ms，然后10ms，然后20ms，然后40ms……直到有消息到来，然后再回到5ms。\n即使这样，依然存在延迟问题：假设40ms到80ms之间的50ms消息到来，消息就延迟了30ms，而且对于半个小时来一次的消息，这些开销就是白白浪费的。\n在阿里的RocketMq里，有一种优化的做法-长轮询，来平衡推拉模型各自的缺点。基本思路是:消费者如果尝试拉取失败，不是直接return,而是把连接挂在那里wait,服务端如果有新的消息到来，把连接notify起来，这也是不错的思路。但海量的长连接block对系统的开销还是不容小觑的，还是要合理的评估时间间隔，给wait加一个时间上限比较好~\n\n# 总结\n\n本文从为何使用消息队列开始讲起，然后主要介绍了如何从零开始设计一个消息队列，包括RPC、事务、最终一致性、广播、消息确认等关键问题。并对消息队列的push、pull模型做了简要分析，最后从批量和异步角度，分析了消息队列性能优化的思路。下篇会着重介绍一些高级话题，如存储系统的设计、流控和错峰的设计、公平调度等。希望通过这些，让大家对消息队列有个提纲挈领的整体认识，并给自主开发消息队列提供思路。另外，本文主要是源自自己在开发消息队列中的思考和读源码时的体会，比较不\"官方\"，也难免会存在一些漏洞，欢迎大家多多交流。\n"
  },
  {
    "path": "docs/distributed/practice/搞懂分布式技术：缓存更新的套路.md",
    "content": "\n# 目录\n\n* [缓存更新的套路](#缓存更新的套路)  \n  * [Cache Aside Pattern](#cache-aside-pattern)  \n  * [Read/Write Through Pattern](#readwrite-through-pattern)  \n  * [Read Through](#read-through)  \n  * [Write Through](#write-through)  \n  * [Write Behind Caching Pattern](#write-behind-caching-pattern)  \n  * [再多唠叨一些](#再多唠叨一些)\n\n本文内容参考网络，侵删\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本文也将同步到我的个人博客：\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n该系列博文会告诉你什么是分布式系统，这对后端工程师来说是很重要的一门学问，我们会逐步了解常见的分布式技术、以及一些较为常见的分布式系统概念，同时也需要进一步了解zookeeper、分布式事务、分布式锁、负载均衡等技术，以便让你更完整地了解分布式技术的具体实战方法，为真正应用分布式技术做好准备。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->  \n\n本文转载自 linkedkeeper.com\n\n# 缓存更新的套路\n\n看到好些人在写更新缓存数据代码时，**先删除缓存，然后再更新数据库**，而后续的操作会把数据再装载的缓存中。**然而，这个是逻辑是错误的**。试想，两个并发操作，一个是更新操作，另一个是查询操作，更新操作删除缓存后，查询操作没有命中缓存，先把老数据读出来后放到缓存中，然后更新操作更新了数据库。于是，在缓存中的数据还是老的数据，导致缓存中的数据是脏的，而且还一直这样脏下去了。\n\n我不知道为什么这么多人用的都是这个逻辑，当我在微博上发了这个贴以后，我发现好些人给了好多非常复杂和诡异的方案，所以，我想写这篇文章说一下几个缓存更新的Design Pattern（让我们多一些套路吧）。\n\n这里，我们先不讨论更新缓存和更新数据这两个事是一个事务的事，或是会有失败的可能，我们先假设更新数据库和更新缓存都可以成功的情况（我们先把成功的代码逻辑先写对）。\n\n更新缓存的的Design Pattern有四种：Cache aside, Read through, Write through, Write behind caching，我们下面一一来看一下这四种Pattern。\n\n\n\n#### Cache Aside Pattern\n\n这是最常用最常用的pattern了。其具体逻辑如下：\n\n*   **失效**：应用程序先从cache取数据，没有得到，则从数据库中取数据，成功后，放到缓存中。\n\n*   **命中**：应用程序从cache中取数据，取到后返回。\n\n*   **更新**：先把数据存到数据库中，成功后，再让缓存失效。\n\n![Cache-Aside-Design-Pattern-Flow-Diagram](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/Cache-Aside-Design-Pattern-Flow-Diagram-e1470471723210.png)\n\n![Updating-Data-using-the-Cache-Aside-Pattern-Flow-Diagram-1](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/Updating-Data-using-the-Cache-Aside-Pattern-Flow-Diagram-1-e1470471761402.png)\n\n注意，我们的更新是先更新数据库，成功后，让缓存失效。那么，这种方式是否可以没有文章前面提到过的那个问题呢？我们可以脑补一下。\n\n一个是查询操作，一个是更新操作的并发，首先，没有了删除cache数据的操作了，而是先更新了数据库中的数据，此时，缓存依然有效，所以，并发的查询操作拿的是没有更新的数据，但是，更新操作马上让缓存的失效了，后续的查询操作再把数据从数据库中拉出来。而不会像文章开头的那个逻辑产生的问题，后续的查询操作一直都在取老的数据。\n\n这是标准的design pattern，包括Facebook的论文《[Scaling Memcache at Facebook](https://www.usenix.org/system/files/conference/nsdi13/nsdi13-final170_update.pdf)》也使用了这个策略。为什么不是写完数据库后更新缓存？你可以看一下Quora上的这个问答《[Why does Facebook use delete to remove the key-value pair in Memcached instead of updating the Memcached during write request to the backend?](https://www.quora.com/Why-does-Facebook-use-delete-to-remove-the-key-value-pair-in-Memcached-instead-of-updating-the-Memcached-during-write-request-to-the-backend)》，主要是怕两个并发的写操作导致脏数据。\n\n那么，是不是Cache Aside这个就不会有并发问题了？不是的，比如，一个是读操作，但是没有命中缓存，然后就到数据库中取数据，此时来了一个写操作，写完数据库后，让缓存失效，然后，之前的那个读操作再把老的数据放进去，所以，会造成脏数据。\n\n但，这个case理论上会出现，不过，实际上出现的概率可能非常低，因为这个条件需要发生在读缓存时缓存失效，而且并发着有一个写操作。而实际上数据库的写操作会比读操作慢得多，而且还要锁表，而读操作必需在写操作前进入数据库操作，而又要晚于写操作更新缓存，所有的这些条件都具备的概率基本并不大。\n\n**所以，这也就是Quora上的那个答案里说的，要么通过2PC或是Paxos协议保证一致性，要么就是拼命的降低并发时脏数据的概率，而Facebook使用了这个降低概率的玩法，因为2PC太慢，而Paxos太复杂。当然，最好还是为缓存设置上过期时间。**\n\n#### Read/Write Through Pattern\n\n我们可以看到，在上面的Cache Aside套路中，我们的应用代码需要维护两个数据存储，一个是缓存（Cache），一个是数据库（Repository）。所以，应用程序比较啰嗦。而Read/Write Through套路是把更新数据库（Repository）的操作由缓存自己代理了，所以，对于应用层来说，就简单很多了。**可以理解为，应用认为后端就是一个单一的存储，而存储自己维护自己的Cache。**\n\n##### Read Through\n\nRead Through 套路就是在查询操作中更新缓存，也就是说，当缓存失效的时候（过期或LRU换出），Cache Aside是由调用方负责把数据加载入缓存，而Read Through则用缓存服务自己来加载，从而对应用方是透明的。\n\n##### Write Through\n\nWrite Through 套路和Read Through相仿，不过是在更新数据时发生。当有数据更新的时候，如果没有命中缓存，直接更新数据库，然后返回。如果命中了缓存，则更新缓存，然后再由Cache自己更新数据库（这是一个同步操作）\n\n下图自来Wikipedia的[Cache词条](https://en.wikipedia.org/wiki/Cache_(computing))。其中的Memory你可以理解为就是我们例子里的数据库。\n\n![Write-through_with_no-write-allocation](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/460px-Write-through_with_no-write-allocation.svg_.png)\n\n#### Write Behind Caching Pattern\n\nWrite Behind 又叫 Write Back。**一些了解Linux操作系统内核的同学对write back应该非常熟悉，这不就是Linux文件系统的Page Cache的算法吗？是的，你看基础这玩意全都是相通的。**所以，基础很重要，我已经不是一次说过基础很重要这事了。\n\nWrite Back套路，一句说就是，在更新数据的时候，只更新缓存，不更新数据库，而我们的缓存会异步地批量更新数据库。这个设计的好处就是让数据的I/O操作飞快无比（因为直接操作内存嘛 ），因为异步，write backg还可以合并对同一个数据的多次操作，所以性能的提高是相当可观的。\n\n但是，其带来的问题是，数据不是强一致性的，而且可能会丢失（我们知道Unix/Linux非正常关机会导致数据丢失，就是因为这个事）。在软件设计上，我们基本上不可能做出一个没有缺陷的设计，就像算法设计中的时间换空间，空间换时间一个道理，有时候，强一致性和高性能，高可用和高性性是有冲突的。软件设计从来都是取舍Trade-Off。\n\n另外，Write Back实现逻辑比较复杂，因为他需要track有哪数据是被更新了的，需要刷到持久层上。操作系统的write back会在仅当这个cache需要失效的时候，才会被真正持久起来，比如，内存不够了，或是进程退出了等情况，这又叫lazy write。\n\n在wikipedia上有一张write back的流程图，基本逻辑如下：\n\n![Write-back_with_write-allocation](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/Write-back_with_write-allocation.png)\n\n#### 再多唠叨一些\n\n1）上面讲的这些Design Pattern，其实并不是软件架构里的mysql数据库和memcache/redis的更新策略，这些东西都是计算机体系结构里的设计，比如CPU的缓存，硬盘文件系统中的缓存，硬盘上的缓存，数据库中的缓存。**基本上来说，这些缓存更新的设计模式都是非常老古董的，而且历经长时间考验的策略**，所以这也就是，工程学上所谓的Best Practice，遵从就好了。\n\n2）有时候，我们觉得能做宏观的系统架构的人一定是很有经验的，其实，宏观系统架构中的很多设计都来源于这些微观的东西。比如，云计算中的很多虚拟化技术的原理，和传统的虚拟内存不是很像么？Unix下的那些I/O模型，也放大到了架构里的同步异步的模型，还有Unix发明的管道不就是数据流式计算架构吗？TCP的好些设计也用在不同系统间的通讯中，仔细看看这些微观层面，你会发现有很多设计都非常精妙……所以，**请允许我在这里放句观点鲜明的话——如果你要做好架构，首先你得把计算机体系结构以及很多老古董的基础技术吃透了**。\n\n3）在软件开发或设计中，我非常建议在之前先去参考一下已有的设计和思路，**看看相应的guideline，best practice或design pattern，吃透了已有的这些东西，再决定是否要重新发明轮子**。千万不要似是而非地，想当然的做软件设计。\n\n4）上面，我们没有考虑缓存（Cache）和持久层（Repository）的整体事务的问题。比如，更新Cache成功，更新数据库失败了怎么吗？或是反过来。关于这个事，如果你需要强一致性，你需要使用“两阶段提交协议”——prepare, commit/rollback，比如Java 7 的[XAResource](http://docs.oracle.com/javaee/7/api/javax/transaction/xa/XAResource.html)，还有MySQL 5.7的[XA Transaction](http://dev.mysql.com/doc/refman/5.7/en/xa.html)，有些cache也支持XA，比如[EhCache](http://www.ehcache.org/documentation/3.0/xa.html)。当然，XA这样的强一致性的玩法会导致性能下降，关于分布式的事务的相关话题，你可以看看《[分布式系统的事务处理](https://coolshell.cn/articles/10910.html)》一文。\n\n（全文完）\n"
  },
  {
    "path": "docs/distributed/practice/搞懂分布式技术：缓存的那些事.md",
    "content": "# 目录\n\n* [缓存和它的那些淘汰算法们](#缓存和它的那些淘汰算法们)\n* [缓存技术杂谈](#缓存技术杂谈)\n* [缓存特征](#缓存特征)\n  * [命中率](#命中率)\n  * [最大元素（或最大空间）](#最大元素（或最大空间）)\n  * [清空策略](#清空策略)\n* [缓存介质](#缓存介质)\n* [缓存分类和应用场景](#缓存分类和应用场景)\n    * [本地缓存](#本地缓存)\n        * [编程直接实现缓存](#编程直接实现缓存)\n        * [Ehcache](#ehcache)\n    * [分布式缓存](#分布式缓存)\n        * [memcached缓存](#memcached缓存)\n        * [Redis缓存](#redis缓存)\n* [缓存常见问题](#缓存常见问题)\n    * [一、缓存雪崩](#一、缓存雪崩)\n    * [二、缓存穿透](#二、缓存穿透)\n    * [三、缓存预热](#三、缓存预热)\n    * [四、缓存更新](#四、缓存更新)\n    * [五、缓存降级](#五、缓存降级)\n    * [六、总结](#六、总结)\n\n本文内容参考网络，侵删\n\n本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star哈\n\n本文也将同步到我的个人博客：\n\n> www.how2playlife.com\n\n更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。\n\n该系列博文会告诉你什么是分布式系统，这对后端工程师来说是很重要的一门学问，我们会逐步了解常见的分布式技术、以及一些较为常见的分布式系统概念，同时也需要进一步了解zookeeper、分布式事务、分布式锁、负载均衡等技术，以便让你更完整地了解分布式技术的具体实战方法，为真正应用分布式技术做好准备。\n\n如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。\n\n<!-- more -->\n\n\n# 缓存和它的那些淘汰算法们\n\n为什么我们需要缓存？\n\n很久很久以前，在还没有缓存的时候……用户经常是去请求一个对象，而这个对象是从数据库去取，然后，这个对象变得越来越大，这个用户每次的请求时间也越来越长了，这也把数据库弄得很痛苦，他无时不刻不在工作。所以，这个事情就把用户和数据库弄得很生气，接着就有可能发生下面两件事情：\n\n１.用户很烦，在抱怨，甚至不去用这个应用了（这是大多数情况下都会发生的）\n\n２.数据库为打包回家，离开这个应用，然后，就出现了大麻烦（没地方去存储数据了）（发生在极少数情况下）\n\n上帝派来了缓存\n\n在几年之后，IBM（60年代）的研究人员引进了一个新概念，它叫“缓存”。\n\n什么是缓存？\n\n正如开篇所讲，缓存是“存贮数据（使用频繁的数据）的临时地方，因为取原始数据的代价太大了，所以我可以取得快一些。”\n\n缓存可以认为是数据的池，这些数据是从数据库里的真实数据复制出来的，并且为了能正确取回，被标上了标签（键 ID）。太棒了\n\nprogrammer one 已经知道这点了，但是他还不知道下面的缓存术语。\n\n![cache访问流程](https://user-gold-cdn.xitu.io/2017/10/25/49e480c480199d7da14f7f3853858292?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)\n\n命中：\n\n当客户发起一个请求（我们说他想要查看一个产品信息），我们的应用接受这个请求，并且如果是在第一次检查缓存的时候，需要去数据库读取产品信息。\n\n如果在缓存中，一个条目通过一个标记被找到了，这个条目就会被使用、我们就叫它缓存命中。所以，命中率也就不难理解了。\n\nCache Miss：\n\n但是这里需要注意两点：\n\n１.　如果还有缓存的空间，那么，没有命中的对象会被存储到缓存中来。\n\n２.　如果缓存满了，而又没有命中缓存，那么就会按照某一种策略，把缓存中的旧对象踢出，而把新的对象加入缓存池。而这些策略统称为替代策略（缓存算法），这些策略会决定到底应该提出哪些对象。\n\n存储成本：\n\n当没有命中时，我们会从数据库取出数据，然后放入缓存。而把这个数据放入缓存所需要的时间和空间，就是存储成本。\n\n索引成本：\n\n和存储成本相仿。\n\n失效：\n\n当存在缓存中的数据需要更新时，就意味着缓存中的这个数据失效了。\n\n替代策略：\n\n当缓存没有命中时，并且缓存容量已经满了，就需要在缓存中踢出一个老的条目，加入一条新的条目，而到底应该踢出什么条目，就由替代策略决定。\n\n最优替代策略：\n\n最优的替代策略就是想把缓存中最没用的条目给踢出去，但是未来是不能够被预知的，所以这种策略是不可能实现的。但是有很多策略，都是朝着这个目前去努力。\n\n缓存算法\n\n没有人能说清哪种缓存算法优于其他的缓存算法\n\nLeast Frequently Used（LFU）：\n\n大家好，我是 LFU，我会计算为每个缓存对象计算他们被使用的频率。我会把最不常用的缓存对象踢走。\n\nLeast Recently User（LRU）：\n\n我是 LRU 缓存算法，我把最近最少使用的缓存对象给踢走。\n\n我总是需要去了解在什么时候，用了哪个缓存对象。如果有人想要了解我为什么总能把最近最少使用的对象踢掉，是非常困难的。\n\n浏览器就是使用了我（LRU）作为缓存算法。新的对象会被放在缓存的顶部，当缓存达到了容量极限，我会把底部的对象踢走，而技巧就是：我会把最新被访问的缓存对象，放到缓存池的顶部。\n\n所以，经常被读取的缓存对象就会一直呆在缓存池中。有两种方法可以实现我，array 或者是 linked list。\n\n我的速度很快，我也可以被数据访问模式适配。我有一个大家庭，他们都可以完善我，甚至做的比我更好（我确实有时会嫉妒，但是没关系）。我家庭的一些成员包括 LRU2 和 2Q，他们就是为了完善 LRU 而存在的。\n\nFirst in First out（FIFO）：\n\n我是先进先出，我是一个低负载的算法，并且对缓存对象的管理要求不高。我通过一个队列去跟踪所有的缓存对象，最近最常用的缓存对象放在后面，而更早的缓存对象放在前面，当缓存容量满时，排在前面的缓存对象会被踢走，然后把新的缓存对象加进去。我很快，但是我并不适用。\n\nSecond Chance：\n\n大家好，我是 second chance，我是通过 FIFO 修改而来的，被大家叫做 second chance 缓存算法，我比 FIFO 好的地方是我改善了 FIFO 的成本。我是 FIFO 一样也是在观察队列的前端，但是很FIFO的立刻踢出不同，我会检查即将要被踢出的对象有没有之前被使用过的标志（1一个 bit 表示），没有被使用过，我就把他踢出；否则，我会把这个标志位清除，然后把这个缓存对象当做新增缓存对象加入队列。你可以想象就这就像一个环队列。当我再一次在队头碰到这个对象时，由于他已经没有这个标志位了，所以我立刻就把他踢开了。我在速度上比 FIFO 快。\n\n其他的缓存算法还考虑到了下面几点：\n\n成本：如果缓存对象有不同的成本，应该把那些难以获得的对象保存下来。\n\n容量：如果缓存对象有不同的大小，应该把那些大的缓存对象清除，这样就可以让更多的小缓存对象进来了。\n\n时间：一些缓存还保存着缓存的过期时间。电脑会失效他们，因为他们已经过期了。\n\n根据缓存对象的大小而不管其他的缓存算法可能是有必要的。\n\n看看缓存元素（缓存实体）\n\n    public class CacheElement\n     {\n         private Object objectValue;\n         private Object objectKey;\n         private int index;\n         private int hitCount;　// getters and setters\n     }\n\n这个缓存实体拥有缓存的key和value，这个实体的数据结构会被以下所有缓存算法用到。\n\n缓存算法的公用代码\n\n     public final synchronized void addElement(Object key, Object value)\n     {\n         int index;\n         Object obj;\n         // get the entry from the table\n         obj = table.get(key);\n         // If we have the entry already in our table\n         // then get it and replace only its value.\n         obj = table.get(key);\n     \n         if (obj != null)\n         {\n             CacheElement element;\n             element = (CacheElement) obj;\n             element.setObjectValue(value);\n             element.setObjectKey(key);\n             return;\n         }\n     ｝\n\n上面的代码会被所有的缓存算法实现用到。这段代码是用来检查缓存元素是否在缓存中了，如果是，我们就替换它，但是如果我们找不到这个 key 对应的缓存，我们会怎么做呢？那我们就来深入的看看会发生什么吧！\n\n现场访问\n\n今天的专题很特殊，因为我们有特殊的客人，事实上他们是我们想要听的与会者，但是首先，先介绍一下我们的客人：Random Cache，FIFO Cache。让我们从 Random Cache开始。\n\n看看随机缓存的实现\n\n     public final synchronized void addElement(Object key, Object value)\n     {\n         int index;\n         Object obj;\n         obj = table.get(key);\n         if (obj != null)\n         {\n             CacheElement element;// Just replace the value.\n             element = (CacheElement) obj;\n             element.setObjectValue(value);\n             element.setObjectKey(key);\n             return;\n         }// If we haven't filled the cache yet, put it at the end.\n         if (!isFull())\n         {\n             index = numEntries;\n             ++numEntries;\n         }\n         else { // Otherwise, replace a random entry.\n             index = (int) (cache.length * random.nextFloat());\n             table.remove(cache[index].getObjectKey());\n         }\n         cache[index].setObjectValue(value);\n         cache[index].setObjectKey(key);\n         table.put(key, cache[index]);\n     }\n\n看看FIFO缓算法的实现\n\n     public final synchronized void addElement(Objectkey, Object value)\n     {\n         int index;\n         Object obj;\n         obj = table.get(key);\n         if (obj != null)\n         {\n             CacheElement element; // Just replace the value.\n             element = (CacheElement) obj;\n             element.setObjectValue(value);\n             element.setObjectKey(key);\n             return;\n         }\n         // If we haven't filled the cache yet, put it at the end.\n         if (!isFull())\n         {\n             index = numEntries;\n             ++numEntries;\n         }\n         else { // Otherwise, replace the current pointer,\n                // entry with the new one.\n             index = current;\n             // in order to make Circular FIFO\n             if (++current >= cache.length)\n                 current = 0;\n             table.remove(cache[index].getObjectKey());\n         }\n         cache[index].setObjectValue(value);\n         cache[index].setObjectKey(key);\n         table.put(key, cache[index]);\n     }\n\n看看LFU缓存算法的实现\n\n     public synchronized Object getElement(Object key)\n     {\n         Object obj;\n         obj = table.get(key);\n         if (obj != null)\n         {\n             CacheElement element = (CacheElement) obj;\n             element.setHitCount(element.getHitCount() + 1);\n             return element.getObjectValue();\n         }\n         return null;\n     }\n     public final synchronized void addElement(Object key, Object value)\n     {\n         Object obj;\n         obj = table.get(key);\n         if (obj != null)\n         {\n             CacheElement element; // Just replace the value.\n             element = (CacheElement) obj;\n             element.setObjectValue(value);\n             element.setObjectKey(key);\n             return;\n         }\n         if (!isFull())\n         {\n             index = numEntries;\n             ++numEntries;\n         }\n         else\n         {\n             CacheElement element = removeLfuElement();\n             index = element.getIndex();\n             table.remove(element.getObjectKey());\n         }\n         cache[index].setObjectValue(value);\n         cache[index].setObjectKey(key);\n         cache[index].setIndex(index);\n         table.put(key, cache[index]);\n     }\n     public CacheElement removeLfuElement()\n     {\n         CacheElement[] elements = getElementsFromTable();\n         CacheElement leastElement = leastHit(elements);\n         return leastElement;\n     }\n     public static CacheElement leastHit(CacheElement[] elements)\n     {\n         CacheElement lowestElement = null;\n         for (int i = 0; i < elements.length; i++)\n         {\n             CacheElement element = elements[i];\n             if (lowestElement == null)\n             {\n                 lowestElement = element;\n             }\n             else {\n                 if (element.getHitCount() < lowestElement.getHitCount())\n                 {\n                     lowestElement = element;\n                 }\n             }\n         }\n         return lowestElement;\n     }\n\n最重点的代码，就应该是 leastHit 这个方法，这段代码就是把hitCount 最低的元素找出来，然后删除，给新进的缓存元素留位置\n\n看看LRU缓存算法实现\n\n     private void moveToFront(int index)\n     {\n         int nextIndex, prevIndex;\n         if(head != index)\n         {\n             nextIndex = next[index];\n             prevIndex = prev[index];\n             // Only the head has a prev entry that is an invalid index\n             // so we don't check.\n             next[prevIndex] = nextIndex;\n             // Make sure index is valid. If it isn't, we're at the tail\n             // and don't set prev[next].\n             if(nextIndex >= 0)\n                 prev[nextIndex] = prevIndex;\n             else\n                 tail = prevIndex;\n             prev[index] = -1;\n             next[index] = head;\n             prev[head] = index;\n             head = index;\n         }\n     }\n     public final synchronized void addElement(Object key, Object value)\n     {\n         int index;Object obj;\n         obj = table.get(key);\n         if(obj != null)\n         {\n             CacheElement entry;\n             // Just replace the value, but move it to the front.\n             entry = (CacheElement)obj;\n             entry.setObjectValue(value);\n             entry.setObjectKey(key);\n             moveToFront(entry.getIndex());\n             return;\n         }\n         // If we haven't filled the cache yet, place in next available\n         // spot and move to front.\n         if(!isFull())\n         {\n             if(_numEntries > 0)\n             {\n                 prev[_numEntries] = tail;\n                 next[_numEntries] = -1;\n                 moveToFront(numEntries);\n             }\n             ++numEntries;\n         }\n         else { // We replace the tail of the list.\n             table.remove(cache[tail].getObjectKey());\n             moveToFront(tail);\n         }\n         cache[head].setObjectValue(value);\n         cache[head].setObjectKey(key);\n         table.put(key, cache[head]);\n     }\n\n这段代码的逻辑如 LRU算法 的描述一样，把再次用到的缓存提取到最前面，而每次删除的都是最后面的元素。\n\n# 缓存技术杂谈\n\n一般而言，现在互联网应用（网站或App）的整体流程，可以概括如图1所示，用户请求从界面（浏览器或App界面）到网络转发、应用服务再到存储（数据库或文件系统），然后返回到界面呈现内容。\n\n随着互联网的普及，内容信息越来越复杂，用户数和访问量越来越大，我们的应用需要支撑更多的并发量，同时我们的应用服务器和数据库服务器所做的计算也越来越多。但是往往我们的应用服务器资源是有限的，且技术变革是缓慢的，数据库每秒能接受的请求次数也是有限的（或者文件的读写也是有限的），如何能够有效利用有限的资源来提供尽可能大的吞吐量？一个有效的办法就是引入缓存，打破标准流程，每个环节中请求可以从缓存中直接获取目标数据并返回，从而减少计算量，有效提升响应速度，让有限的资源服务更多的用户。\n\n\n# 缓存特征\n\n缓存也是一个数据模型对象，那么必然有它的一些特征：\n\n#### 命中率\n\n命中率=返回正确结果数/请求缓存次数，命中率问题是缓存中的一个非常重要的问题，它是衡量缓存有效性的重要指标。命中率越高，表明缓存的使用率越高。\n\n#### 最大元素（或最大空间）\n\n缓存中可以存放的最大元素的数量，一旦缓存中元素数量超过这个值（或者缓存数据所占空间超过其最大支持空间），那么将会触发缓存启动清空策略根据不同的场景合理的设置最大元素值往往可以一定程度上提高缓存的命中率，从而更有效的时候缓存。\n\n#### 清空策略\n\n如上描述，缓存的存储空间有限制，当缓存空间被用满时，如何保证在稳定服务的同时有效提升命中率？这就由缓存清空策略来处理，设计适合自身数据特征的清空策略能有效提升命中率。常见的一般策略有：\n\n* FIFO(first in first out)\n\n  先进先出策略，最先进入缓存的数据在缓存空间不够的情况下（超出最大元素限制）会被优先被清除掉，以腾出新的空间接受新的数据。策略算法主要比较缓存元素的创建时间。在数据实效性要求场景下可选择该类策略，优先保障最新数据可用。\n\n* LFU(less frequently used)\n\n  最少使用策略，无论是否过期，根据元素的被使用次数判断，清除使用次数较少的元素释放空间。策略算法主要比较元素的hitCount（命中次数）。在保证高频数据有效性场景下，可选择这类策略。\n\n* LRU(least recently used)\n\n  最近最少使用策略，无论是否过期，根据元素最后一次被使用的时间戳，清除最远使用时间戳的元素释放空间。策略算法主要比较元素最近一次被get使用时间。在热点数据场景下较适用，优先保证热点数据的有效性。\n\n除此之外，还有一些简单策略比如：\n\n*   根据过期时间判断，清理过期时间最长的元素；\n*   根据过期时间判断，清理最近要过期的元素；\n*   随机清理；\n*   根据关键字（或元素内容）长短清理等。\n\n# 缓存介质\n\n虽然从硬件介质上来看，无非就是内存和硬盘两种，但从技术上，可以分成内存、硬盘文件、数据库。\n\n*   内存：将缓存存储于内存中是最快的选择，无需额外的I/O开销，但是内存的缺点是没有持久化落地物理磁盘，一旦应用异常break down而重新启动，数据很难或者无法复原。\n*   硬盘：一般来说，很多缓存框架会结合使用内存和硬盘，在内存分配空间满了或是在异常的情况下，可以被动或主动的将内存空间数据持久化到硬盘中，达到释放空间或备份数据的目的。\n*   数据库：前面有提到，增加缓存的策略的目的之一就是为了减少数据库的I/O压力。现在使用数据库做缓存介质是不是又回到了老问题上了？其实，数据库也有很多种类型，像那些不支持SQL，只是简单的key-value存储结构的特殊数据库（如BerkeleyDB和Redis），响应速度和吞吐量都远远高于我们常用的关系型数据库等。\n\n# 缓存分类和应用场景\n\n缓存有各类特征，而且有不同介质的区别，那么实际工程中我们怎么去对缓存分类呢？在目前的应用服务框架中，比较常见的，时根据缓存雨应用的藕合度，分为local cache（本地缓存）和remote cache（分布式缓存）：\n\n本地缓存：指的是在应用中的缓存组件，其最大的优点是应用和cache是在同一个进程内部，请求缓存非常快速，没有过多的网络开销等，在单应用不需要集群支持或者集群情况下各节点无需互相通知的场景下使用本地缓存较合适；同时，它的缺点也是应为缓存跟应用程序耦合，多个应用程序无法直接的共享缓存，各应用或集群的各节点都需要维护自己的单独缓存，对内存是一种浪费。\n\n分布式缓存：指的是与应用分离的缓存组件或服务，其最大的优点是自身就是一个独立的应用，与本地应用隔离，多个应用可直接的共享缓存。\n\n目前各种类型的缓存都活跃在成千上万的应用服务中，还没有一种缓存方案可以解决一切的业务场景或数据类型，我们需要根据自身的特殊场景和背景，选择最适合的缓存方案。缓存的使用是程序员、架构师的必备技能，好的程序员能根据数据类型、业务场景来准确判断使用何种类型的缓存，如何使用这种缓存，以最小的成本最快的效率达到最优的目的。\n\n## 本地缓存\n\n### 编程直接实现缓存\n\n个别场景下，我们只需要简单的缓存数据的功能，而无需关注更多存取、清空策略等深入的特性时，直接编程实现缓存则是最便捷和高效的。\n\na. 成员变量或局部变量实现\n\n简单代码示例如下：\n\n        public void UseLocalCache(){\n         //一个本地的缓存变量\n         Map<String, Object> localCacheStoreMap = new HashMap<String, Object>();\n     \n        List<Object> infosList = this.getInfoList();\n        for(Object item:infosList){\n            if(localCacheStoreMap.containsKey(item)){ //缓存命中 使用缓存数据\n                // todo\n            } else { // 缓存未命中  IO获取数据，结果存入缓存\n                Object valueObject = this.getInfoFromDB();\n                localCacheStoreMap.put(valueObject.toString(), valueObject);\n     \n            }\n        }\n    }\n    //示例\n    private List<Object> getInfoList(){\n        return new ArrayList<Object>();\n    }\n    //示例数据库IO获取\n    private Object getInfoFromDB(){\n        return new Object();\n    }\n\n以局部变量map结构缓存部分业务数据，减少频繁的重复数据库I/O操作。缺点仅限于类的自身作用域内，类间无法共享缓存。\n\nb. 静态变量实现\n\n最常用的单例实现静态资源缓存，代码示例如下：\n\n\n          public class CityUtils {\n          private static final HttpClient httpClient = ServerHolder.createClientWithPool(); \n          private static Map<Integer, String> cityIdNameMap = new HashMap<Integer, String>();\n          private static Map<Integer, String> districtIdNameMap = new HashMap<Integer, String>();\n     \n      static {\n        HttpGet get = new HttpGet(\"http://gis-in.sankuai.com/api/location/city/all\");\n        BaseAuthorizationUtils.generateAuthAndDateHeader(get,\n                BaseAuthorizationUtils.CLIENT_TO_REQUEST_MDC,\n                BaseAuthorizationUtils.SECRET_TO_REQUEST_MDC);\n        try {\n            String resultStr = httpClient.execute(get, new BasicResponseHandler());\n            JSONObject resultJo = new JSONObject(resultStr);\n            JSONArray dataJa = resultJo.getJSONArray(\"data\");\n            for (int i = 0; i < dataJa.length(); i++) {\n                JSONObject itemJo = dataJa.getJSONObject(i);\n                cityIdNameMap.put(itemJo.getInt(\"id\"), itemJo.getString(\"name\"));\n            }\n        } catch (Exception e) {\n            throw new RuntimeException(\"Init City List Error!\", e);\n        }\n    }\n        static {\n        HttpGet get = new HttpGet(\"http://gis-in.sankuai.com/api/location/district/all\");\n        BaseAuthorizationUtils.generateAuthAndDateHeader(get,\n                BaseAuthorizationUtils.CLIENT_TO_REQUEST_MDC,\n                BaseAuthorizationUtils.SECRET_TO_REQUEST_MDC);\n        try {\n            String resultStr = httpClient.execute(get, new BasicResponseHandler());\n            JSONObject resultJo = new JSONObject(resultStr);\n            JSONArray dataJa = resultJo.getJSONArray(\"data\");\n            for (int i = 0; i < dataJa.length(); i++) {\n                JSONObject itemJo = dataJa.getJSONObject(i);\n                districtIdNameMap.put(itemJo.getInt(\"id\"), itemJo.getString(\"name\"));\n            }\n        } catch (Exception e) {\n            throw new RuntimeException(\"Init District List Error!\", e);\n        }\n    }\n     \n        public static String getCityName(int cityId) {\n          String name = cityIdNameMap.get(cityId);\n          if (name == null) {\n            name = \"未知\";\n          }\n           return name;\n         }\n     \n        public static String getDistrictName(int districtId) {\n          String name = districtIdNameMap.get(districtId);\n           if (name == null) {\n             name = \"未知\";\n            }\n           return name;\n         }\n       }\n\nO2O业务中常用的城市基础基本信息判断，通过静态变量一次获取缓存内存中，减少频繁的I/O读取，静态变量实现类间可共享，进程内可共享，缓存的实时性稍差。\n\n> 这类缓存实现，优点是能直接在heap区内读写，最快也最方便；缺点同样是受heap区域影响，缓存的数据量非常有限，同时缓存时间受GC影响。主要满足单机场景下的小数据量缓存需求，同时对缓存数据的变更无需太敏感感知，如上一般配置管理、基础静态数据等场景。\n\n### Ehcache\n\nEhcache是现在最流行的纯Java开源缓存框架，配置简单、结构清晰、功能强大，是一个非常轻量级的缓存实现，我们常用的Hibernate里面就集成了相关缓存功能。\n\n![](https://tech.meituan.com/img/cache_about/ehcach%E6%A1%86%E6%9E%B6%E5%9B%BE.png)\n\n主要特性：\n\n*   快速，针对大型高并发系统场景，Ehcache的多线程机制有相应的优化改善。\n*   简单，很小的jar包，简单配置就可直接使用，单机场景下无需过多的其他服务依赖。\n*   支持多种的缓存策略，灵活。\n*   缓存数据有两级：内存和磁盘，与一般的本地内存缓存相比，有了磁盘的存储空间，将可以支持更大量的数据缓存需求。\n*   具有缓存和缓存管理器的侦听接口，能更简单方便的进行缓存实例的监控管理。\n*   支持多缓存管理器实例，以及一个实例的多个缓存区域。\n\n> 注意：Ehcache的超时设置主要是针对整个cache实例设置整体的超时策略，而没有较好的处理针对单独的key的个性的超时设置（有策略设置，但是比较复杂，就不描述了），因此，在使用中要注意过期失效的缓存元素无法被GC回收，时间越长缓存越多，内存占用也就越大，内存泄露的概率也越大。\n\n## 分布式缓存\n\n### memcached缓存\n\nmemcached是应用较广的开源分布式缓存产品之一，它本身其实不提供分布式解决方案。在服务端，memcached集群环境实际就是一个个memcached服务器的堆积，环境搭建较为简单；cache的分布式主要是在客户端实现，通过客户端的路由处理来达到分布式解决方案的目的。客户端做路由的原理非常简单，应用服务器在每次存取某key的value时，通过某种算法把key映射到某台memcached服务器nodeA上。\n\n无特殊场景下，key-value能满足需求的前提下，使用memcached分布式集群是较好的选择，搭建与操作使用都比较简单；分布式集群在单点故障时，只影响小部分数据异常，目前还可以通过Magent缓存代理模式，做单点备份，提升高可用；整个缓存都是基于内存的，因此响应时间是很快，不需要额外的序列化、反序列化的程序，但同时由于基于内存，数据没有持久化，集群故障重启数据无法恢复。高版本的memcached已经支持CAS模式的原子操作，可以低成本的解决并发控制问题。\n\n### Redis缓存\n\nRedis是一个远程内存数据库（非关系型数据库），性能强劲，具有复制特性以及解决问题而生的独一无二的数据模型。它可以存储键值对与5种不同类型的值之间的映射，可以将存储在内存的键值对数据持久化到硬盘，可以使用复制特性来扩展读性能，还可以使用客户端分片来扩展写性能。\n\n\n个人总结了以下多种Web应用场景，在这些场景下可以充分的利用Redis的特性，大大提高效率。\n\n*   在主页中显示最新的项目列表：Redis使用的是常驻内存的缓存，速度非常快。LPUSH用来插入一个内容ID，作为关键字存储在列表头部。LTRIM用来限制列表中的项目数最多为5000。如果用户需要的检索的数据量超越这个缓存容量，这时才需要把请求发送到数据库。\n*   删除和过滤：如果一篇文章被删除，可以使用LREM从缓存中彻底清除掉。\n*   排行榜及相关问题：排行榜（leader board）按照得分进行排序。ZADD命令可以直接实现这个功能，而ZREVRANGE命令可以用来按照得分来获取前100名的用户，ZRANK可以用来获取用户排名，非常直接而且操作容易。\n*   按照用户投票和时间排序：排行榜，得分会随着时间变化。LPUSH和LTRIM命令结合运用，把文章添加到一个列表中。一项后台任务用来获取列表，并重新计算列表的排序，ZADD命令用来按照新的顺序填充生成列表。列表可以实现非常快速的检索，即使是负载很重的站点。\n*   过期项目处理：使用Unix时间作为关键字，用来保持列表能够按时间排序。对current_time和time_to_live进行检索，完成查找过期项目的艰巨任务。另一项后台任务使用ZRANGE…WITHSCORES进行查询，删除过期的条目。\n*   计数：进行各种数据统计的用途是非常广泛的，比如想知道什么时候封锁一个IP地址。INCRBY命令让这些变得很容易，通过原子递增保持计数；GETSET用来重置计数器；过期属性用来确认一个关键字什么时候应该删除。\n*   特定时间内的特定项目：这是特定访问者的问题，可以通过给每次页面浏览使用SADD命令来解决。SADD不会将已经存在的成员添加到一个集合。\n*   Pub/Sub：在更新中保持用户对数据的映射是系统中的一个普遍任务。Redis的pub/sub功能使用了SUBSCRIBE、UNSUBSCRIBE和PUBLISH命令，让这个变得更加容易。\n*   队列：在当前的编程中队列随处可见。除了push和pop类型的命令之外，Redis还有阻塞队列的命令，能够让一个程序在执行时被另一个程序添加到队列。\n\n# 缓存常见问题\n\n前面一节说到了《[为什么说Redis是单线程的以及Redis为什么这么快！](http://blog.csdn.net/u010870518/article/details/79470556)》，今天给大家整理一篇关于Redis经常被问到的问题：缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级等概念的入门及简单解决方案。\n\n## 一、缓存雪崩\n\n缓存雪崩我们可以简单的理解为：由于原有缓存失效，新缓存未到期间(例如：我们设置缓存时采用了相同的过期时间，在同一时刻出现大面积的缓存过期)，所有原本应该访问缓存的请求都去查询数据库了，而对数据库CPU和内存造成巨大压力，严重的会造成数据库宕机。从而形成一系列连锁反应，造成整个系统崩溃。\n\n缓存正常从Redis中获取，示意图如下：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407214616.png)\n\n缓存失效瞬间示意图如下：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230407214625.png)\n\n缓存失效时的雪崩效应对底层系统的冲击非常可怕！大多数系统设计者考虑用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写，从而避免失效时大量的并发请求落到底层存储系统上。还有一个简单方案就时讲缓存失效时间分散开，比如我们可以在原有的失效时间基础上增加一个随机值，比如1-5分钟随机，这样每一个缓存的过期时间的重复率就会降低，就很难引发集体失效的事件。\n\n以下简单介绍两种实现方式的伪代码：\n\n（1）碰到这种情况，一般并发量不是特别多的时候，使用最多的解决方案是加锁排队，伪代码如下：\n\n    //伪代码\n    public object GetProductListNew() {\n        int cacheTime = 30;\n        String cacheKey = \"product_list\";\n        String lockKey = cacheKey;\n     \n        String cacheValue = CacheHelper.get(cacheKey);\n        if (cacheValue != null) {\n            return cacheValue;\n        } else {\n            synchronized(lockKey) {\n                cacheValue = CacheHelper.get(cacheKey);\n                if (cacheValue != null) {\n                    return cacheValue;\n                } else {\n                    //这里一般是sql查询数据\n                    cacheValue = GetProductListFromDB(); \n                    CacheHelper.Add(cacheKey, cacheValue, cacheTime);\n                }\n            }\n            return cacheValue;\n        }\n    }\n\n加锁排队只是为了减轻数据库的压力，并没有提高系统吞吐量。假设在高并发下，缓存重建期间key是锁着的，这是过来1000个请求999个都在阻塞的。同样会导致用户等待超时，这是个治标不治本的方法！\n\n注意：加锁排队的解决方式分布式环境的并发问题，有可能还要解决分布式锁的问题；线程还会被阻塞，用户体验很差！因此，在真正的高并发场景下很少使用！\n\n（2）还有一个解决办法解决方案是：给每一个缓存数据增加相应的缓存标记，记录缓存的是否失效，如果缓存标记失效，则更新数据缓存，实例伪代码如下：\n\n    //伪代码\n    public object GetProductListNew() {\n        int cacheTime = 30;\n        String cacheKey = \"product_list\";\n        //缓存标记\n        String cacheSign = cacheKey + \"_sign\";\n     \n        String sign = CacheHelper.Get(cacheSign);\n        //获取缓存值\n        String cacheValue = CacheHelper.Get(cacheKey);\n        if (sign != null) {\n            return cacheValue; //未过期，直接返回\n        } else {\n            CacheHelper.Add(cacheSign, \"1\", cacheTime);\n            ThreadPool.QueueUserWorkItem((arg) -> {\n                //这里一般是 sql查询数据\n                cacheValue = GetProductListFromDB(); \n                //日期设缓存时间的2倍，用于脏读\n                CacheHelper.Add(cacheKey, cacheValue, cacheTime * 2);                 \n            });\n            return cacheValue;\n        }\n    } \n\n解释说明：\n\n1、缓存标记：记录缓存数据是否过期，如果过期会触发通知另外的线程在后台去更新实际key的缓存；\n\n2、缓存数据：它的过期时间比缓存标记的时间延长1倍，例：标记缓存时间30分钟，数据缓存设置为60分钟。 这样，当缓存标记key过期后，实际缓存还能把旧数据返回给调用端，直到另外的线程在后台更新完成后，才会返回新缓存。\n\n关于缓存崩溃的解决方法，这里提出了三种方案：使用锁或队列、设置过期标志更新缓存、为key设置不同的缓存失效时间，还有一各被称为“二级缓存”的解决方法，有兴趣的读者可以自行研究。\n\n## 二、缓存穿透\n\n缓存穿透是指用户查询数据，在数据库没有，自然在缓存中也不会有。这样就导致用户查询的时候，在缓存中找不到，每次都要去数据库再查询一遍，然后返回空（相当于进行了两次无用的查询）。这样请求就绕过缓存直接查数据库，这也是经常提的缓存命中率问题。\n\n有很多种方法可以有效地解决缓存穿透问题，最常见的则是采用布隆过滤器，将所有可能存在的数据哈希到一个足够大的bitmap中，一个一定不存在的数据会被这个bitmap拦截掉，从而避免了对底层存储系统的查询压力。\n\n另外也有一个更为简单粗暴的方法，如果一个查询返回的数据为空（不管是数据不存在，还是系统故障），我们仍然把这个空结果进行缓存，但它的过期时间会很短，最长不超过五分钟。通过这个直接设置的默认值存放到缓存，这样第二次到缓冲中获取就有值了，而不会继续访问数据库，这种办法最简单粗暴！\n\n    //伪代码\n    public object GetProductListNew() {\n        int cacheTime = 30;\n        String cacheKey = \"product_list\";\n     \n        String cacheValue = CacheHelper.Get(cacheKey);\n        if (cacheValue != null) {\n            return cacheValue;\n        }\n     \n        cacheValue = CacheHelper.Get(cacheKey);\n        if (cacheValue != null) {\n            return cacheValue;\n        } else {\n            //数据库查询不到，为空\n            cacheValue = GetProductListFromDB();\n            if (cacheValue == null) {\n                //如果发现为空，设置个默认值，也缓存起来\n                cacheValue = string.Empty;\n            }\n            CacheHelper.Add(cacheKey, cacheValue, cacheTime);\n            return cacheValue;\n        }\n    }\n\n把空结果，也给缓存起来，这样下次同样的请求就可以直接返回空了，即可以避免当查询的值为空时引起的缓存穿透。同时也可以单独设置个缓存区域存储空值，对要查询的key进行预先校验，然后再放行给后面的正常缓存处理逻辑。\n\n## 三、缓存预热\n\n缓存预热这个应该是一个比较常见的概念，相信很多小伙伴都应该可以很容易的理解，缓存预热就是系统上线后，将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候，先查询数据库，然后再将数据缓存的问题！用户直接查询事先被预热的缓存数据！\n\n解决思路：\n\n1、直接写个缓存刷新页面，上线时手工操作下；\n\n2、数据量不大，可以在项目启动的时候自动进行加载；\n\n3、定时刷新缓存；\n\n## 四、缓存更新\n\n除了缓存服务器自带的缓存失效策略之外（Redis默认的有6中策略可供选择），我们还可以根据具体的业务需求进行自定义的缓存淘汰，常见的策略有两种：\n\n（1）定时去清理过期的缓存；\n\n（2）当有用户请求过来时，再判断这个请求所用到的缓存是否过期，过期的话就去底层系统得到新数据并更新缓存。\n\n两者各有优劣，第一种的缺点是维护大量缓存的key是比较麻烦的，第二种的缺点就是每次用户请求过来都要判断缓存失效，逻辑相对比较复杂！具体用哪种方案，大家可以根据自己的应用场景来权衡。\n\n## 五、缓存降级\n\n当访问量剧增、服务出现问题（如响应时间慢或不响应）或非核心服务影响到核心流程的性能时，仍然需要保证服务还是可用的，即使是有损服务。系统可以根据一些关键数据进行自动降级，也可以配置开关实现人工降级。\n\n降级的最终目的是保证核心服务可用，即使是有损的。而且有些服务是无法降级的（如加入购物车、结算）。\n\n在进行降级之前要对系统进行梳理，看看系统是不是可以丢卒保帅；从而梳理出哪些必须誓死保护，哪些可降级；比如可以参考日志级别设置预案：\n\n（1）一般：比如有些服务偶尔因为网络抖动或者服务正在上线而超时，可以自动降级；\n\n（2）警告：有些服务在一段时间内成功率有波动（如在95~100%之间），可以自动降级或人工降级，并发送告警；\n\n（3）错误：比如可用率低于90%，或者数据库连接池被打爆了，或者访问量突然猛增到系统能承受的最大阀值，此时可以根据情况自动降级或者人工降级；\n\n（4）严重错误：比如因为特殊原因数据错误了，此时需要紧急人工降级。\n\n## 六、总结\n\n这些都是实际项目中，可能碰到的一些问题，也是面试的时候经常会被问到的知识点，实际上还有很多很多各种各样的问题，文中的解决方案，也不可能满足所有的场景，相对来说只是对该问题的入门解决方法。一般正式的业务场景往往要复杂的多，应用场景不同，方法和解决方案也不同，由于上述方案，考虑的问题并不是很全面，因此并不适用于正式的项目开发，但是可以作为概念理解入门，具体解决方案要根据实际情况来确定！\n\n\n\n\n\n"
  },
  {
    "path": "docs/distributed/分布式技术实践总结.md",
    "content": "# 目录\n* [分布式技术](#分布式技术)\n  * [分布式数据和nosql](#分布式数据和nosql)\n  * [缓存 分布式缓存](#缓存-分布式缓存)\n    * [redis的部署方案：](#redis的部署方案：)\n    * [缓存需要解决的问题：](#缓存需要解决的问题：)\n    * [缓存更新的方法](#缓存更新的方法)\n    * [缓存在springboot中的使用](#缓存在springboot中的使用)\n    * [一致性哈希](#一致性哈希)\n  * [session和分布式session](#session和分布式session)\n  * [负载均衡](#负载均衡)\n  * [zookeeper](#zookeeper)\n  * [数据库的分布式事务](#数据库的分布式事务)\n  * [分布式锁问题](#分布式锁问题)\n    * [MySQL实现分布式锁](#mysql实现分布式锁)\n    * [redis实现分布式锁](#redis实现分布式锁)\n    * [zookeeper实现分布式锁](#zookeeper实现分布式锁)\n  * [消息队列](#消息队列)\n  * [微服务和Dubbo](#微服务和dubbo)\n    * [全局id](#全局id)\n  * [秒杀系统](#秒杀系统)\n    * [第一层，客户端怎么优化（浏览器层，APP层）](#第一层，客户端怎么优化（浏览器层，app层）)\n    * [第二层，站点层面的请求拦截](#第二层，站点层面的请求拦截)\n    * [第三层 服务层来拦截（反正就是不要让请求落到数据库上去）消息队列+缓存](#第三层-服务层来拦截（反正就是不要让请求落到数据库上去）消息队列缓存)\n    * [好了，最后是数据库层](#好了，最后是数据库层)\n    * [总结](#总结)\n\n本文基于之前的分布式系统理论系列文章总结而成，本部分主要是实践内容，详细内容可见我的专栏：分布式系统理论与实践\n\nhttps://blog.csdn.net/column/details/24090.html\n\n本文主要是按照我自己的理解以及参考之前文章综合而成的，其中可能会有一些错误，还请见谅，也请指出。\n<!-- more -->  \n\n# 分布式技术\n\n## 分布式数据和nosql\n\n分布式一般是指分布式部署的数据库。\n\n比如Hbase基于HDFS分布式部署，所以他是一个分布式数据库。\n\n当然MySQL也可以分布式部署，比如按照不同业务部署，或者把单表内容拆成多个表乃至多个库进行部署。\n\n一般MySQL的扩展方式有：\n\n1 主从复制 使用冗余保证可用\n\n2 读写分离 主库负责写从库负责读，分担压力，并且保证数据一致性和备份。\n\n3 分表分库，横向拆分数据表放到多个表中或者多个库中，一般多个表或者多个库会使用不同节点部署，也就是一种分布式方案，提高并发的读写量。\n\nNosql的话就比较多了，redis，memcache等。  \n当然hbase也是，hbase按照region将数据文件分布在hdfs上，并且hdfs提供高可用和备份，同时hbase的regionserver也保证高可用，于是hbase的分布式方案也是比较成熟的。\n\n## 缓存 分布式缓存\n\n一般作为缓存的软件有redis，memcache等。当然我本地写一个hashmap也可以作为缓存。\n\n\nmemcache提出了一致性哈希的算法，但是本身不支持数据持久化，也没有提供分布式方案，需要自己完成持久化以及分布式部署并且保证其可用性。\n\nredis作为新兴的内存数据库，提供了比memcache更多的数据结构，以及各种分布式方案。当然它也支持持久化。\n\n### redis的部署方案：\n\n> 1 redis的主从复制结构，和MySQL类似，使用日志aof或者持久化文件rdb进行主从同步。\n>\n> 2 读写分离，也可以做，但一般不需要。因为redis够快。\n>\n> 3 redis的哨兵方案，主节点配置哨兵，每当宕机时自动完成主从切换。\n>\n> 4 redis的集群方案，p2p的Redis Cluster部署了多台Redis服务器，每台Redis拥有全局的分片信息，所以任意节点都可以对外提供服务，当然每个节点只保存一部分分片，所以某台机器宕机时不会影响整个集群，当然每个节点也有slave，哨兵自动进行故障切换。\n>\n> 5 codis方案，codis屏蔽了集群的内部实现，可以不更改redis api的情况下使用代理的方式提供集群访问。并且使用 group的概念封装一组节点。\n\n### 缓存需要解决的问题：\n\n    命中：缓存有数据  \n    不命中：去数据库读取    失效：过期    替换：缓存淘汰算法。        一般有lru，fifo，随机缓存等。  \n\n### 缓存更新的方法\n\n缓存更新可以先更新数据库再更新缓存，也可以先更新缓存再更新数据库。\n\n一般推荐先更新数据库，否则写一条数据时刚好有人读到缓存，把旧数据读到缓存中，此时新数据在数据库确不在缓存中。\n\n\n还有一种方法，就是让缓存自己去完成数据库更新，而不是让应用去选择如何更新数据库，这样的话缓存和数据库的更新操作就是透明的了，我们只需要操作缓存即可。\n\n### 缓存在springboot中的使用\n\n    springboot支持将缓存的curd操作配置在注解中，只需要在对应方法上配置好键和更新策略。  \n        则redis会根据该方法的操作类型执行对应操作，非常方便。  \n### 一致性哈希\n\n分布式部署时，经常要面对的问题是，一个服务集群由谁来提供给这个客户度服务，需要一种算法来完成这一步映射。\n\n如果直接使用hash显然分布非常不均匀。那如果使用余数法呢，一共有N台机器，我对N取余可以映射到任意一台机器上。\n\n这种方法的缺点在于，当取余的值集中在某一范围时，就容易集中访问某些机器，导致热点问题。\n\n> 于是memcache推出了一个叫做一致性哈希的算法，一个哈希环，环上支持2^32次方个节点，也就是包含了所有的ip。\n>\n> 然后我们把主机通过hash值分布到这个环上，请求到来时会映射到某一个节点，如果该节点没有主机，则顺时针寻找真正主机。\n>\n> 当节点加入或者节点删除时，并不会影响服务的可用性，只是某些请求会被映射到别的节点。\n\n但是当请求集中到某个区域时，会产生倾斜，我们引入了虚拟节点来改善这个问题，虚拟节点对应到真实节点，所以加入虚拟节点可以更好地转移请求。\n\n\n## session和分布式session\n\nsession是web应用必备的一个结构。  \n一般有几种方案来管理session。\n\n1 web应用保存session到内存中，但是宕机会丢失\n\n2 web应用持久化到数据库或者redis，增加数据库负担。\n\n3 使用cookie保存加密后的session，浏览器压力大，可能被破解\n\n4 使用单独的session服务集群提供session服务，并且本身也可以采用分布式部署，部署的时候可以主从。\n\n保证session一致性的解决方法（客户端可以访问到自己的session）：\n\n1 客户端cookie保存\n\n2 多个webserver进行同步，效率低\n\n3 反向代理绑定ip映射同一个服务器，但是宕机时出错\n\n4 后端统一存储，比如redis，或则部署session服务。\n\n## 负载均衡\n\n负载均衡一般可以分为七层，四层负载均衡。\n\nNginx\n\n七层的负载均衡也就是http负载均衡，主要使用Nginx完成。\n\n> 配置Nginx进行反向代理的url，然后转发请求到上游服务器，请求进来时自动转发到上游服务器，通过url进行负载均衡，所以是七层负载均衡。既然是七层负载，那么上游服务器提供了http服务，也可以解析该请求。\n>\n> 四层负载均衡主要是tcp请求的负载均衡，因为tcp请求是绑定到一个端口上的，所以我们根据端口进行请求转发到上游服务器的。既然是四层负载，上游服务器监听该端口的服务就可以处理该请求。\n\nLVS\n\nLVS术语定义：\n\n    DS：Director Server，前端负载均衡器节点（后文用Director称呼）；  \n        RS：Real Server，后端真实服务器；  \n        VIP：用户请求的目标的IP地址，一般是公网IP地址；  \n        DIP：Director Server IP，Director和Real Server通讯的内网IP地址；  \n        RIP：Real Server IP，Director和Real Server通讯的内网IP地址；  \n\nLVS有三种实现负载均衡的方式\n\nNAT 四层负载均衡\n\n> NAT支持四层负载均衡，NAT中只有DS提供公网ip，并且VIP绑定在DS的mac地址上，客户端只能访问DS。同时DS和RS通过内网ip进行网络连接。当TCP数据报到达DS时，DS修改数据报，指向RS的ip和port。进行转发即可。\n>\n> 同时，RS处理完请求后，由于网关时DS，所以仍然要返回给DS处理。\n>\n>\n> NAT模式中，RS返回数据包是返回给Director，Director再返回给客户端；事实上这跟NAT网络协议没什么关系。\n\nDR 二层负载均衡\n\n\n> DR模式中，DS负责接收请求。接收请求后把数据报的mac地址改成指向RS的mac地址，并且由于三台机器拥有同样的vip地址。  \n> 所以RS接收请求后认为该数据报应该由自己处理并相应。\n>\n> 同时为了避免RS再把相应转发会DS，我们禁用了对DS的arp，所以此时RS就会通过vip把响应通过vip网关返回给客户端。\n>\n> Director通过修改请求中目标地址MAC为选定的RS实现数据转发，这就要求Diretor和Real Server必须在同一个广播域内，也就是他们的mac地址是可达的。  \n> DR（Direct Routing）模式中，RS返回数据是直接返回给客户端（通过额外的路由）；\n>\n\nTUN\n\n> TUN中使用了IP隧道技术，客户端请求发给DS时，DS会通过隧道技术把数据报通过隧道发给实际的RS，然后RS解析数据以后可以直接响应给客户端，因为他有客户端的ip地址。这就不要求DS和RS在同一网段了，当然前提是RS有公网ip。\n>\n> TUN（IP Tunneling）模式中，RS返回的数据也是直接返回给客户端，这种模式通过Overlay协议（把一个IP数据包封装到另一个数据包内部叫Overlay）避免了DR的限制。\n\n## zookeeper\n\nzookeeper集群自身的特性：\n\n> 1 一个zookeeper服务器集群，一开始就会进行选主，主节点挂掉后也会进行选主。\n>\n> 使用zab协议中的选主机制进行选主，也就是每个节点进行一次提议，刚开始提议自己，如果有新的提议则覆盖自己原来的提议，不断重复，直到有节点获得过半的投票。完成一轮选主。\n>\n> 2 选主结束后，开始进行消息广播和数据同步，保证每一台服务器的数据都和leader同步。\n>\n> 3 开始提供服务，客户端向leader发送请求，leader首先发出提议，当有半数以上节点响应时，leader会发送commit信息，于是所有节点执行该操作。当有机器宕机时重启后会和leader同步。这是一个类似2pc的提交方式。\n\nzookeeper提供了分布式环境中常用的服务\n\n> 1 配置服务，多个机器可以通过文件节点共享配置。\n>\n> 2 选主服务，通过添加顺序节点，可以进行选主。\n>\n> 3 分布式锁，顺序节点和watcher\n>\n> 4 全局id，使用机器号+时间戳可以生成一个transactionid，是全局唯一的。\n\n\n## 数据库的分布式事务\n\n分布式事务的实现一般可以用2PC和3PC解决。\n\n成熟的方案有：\n\n> 1 TCC 补偿式事务，对每一个步骤都有一个补偿措施。\n>\n> 2 全局事务实现。\n>\n> 3 事务消息：rocketmq的事务实现，先发消息到队列中，然后本地执行事务并通知消息队列，若成功则消息主动推给另一个服务，直到服务二执行成功，消息从队列中删除。如果超时不成功，则消息要求事务A回滚。\n>\n> 如果过程中失败了，本地事务也会回滚。消息队列可以回调本地接口判断事务是否执行成功，防止超时。\n>\n> 4 本地实现消息表：  \n> 本地实现消息表并且和事务记录存在一起，自己实现消息的轮询发送。  \n> 首先把本地事务操作和消息增加放在一个事务里执行，然后轮询消息表进行发送，如果执行成功则消息达到服务B，通知其执行。执行成功后消息被删除，否则回滚事务删除消息。\n\n## 分布式锁问题\n\n分布式锁用于分布式环境中的资源互斥，因为单机可以通过共享内存实现，而分布式环境只能通过网络实现。\n\n### MySQL实现分布式锁\n\n    insert加锁，锁没有失效时间，容易产生死锁  \n\n### redis实现分布式锁\n\n    1. 基于setnx、expire两个命令来实现  \n        基于setnx（set if not exist）的特点，当缓存里key不存在时，才会去set，否则直接返回false。  \n        如果返回true则获取到锁，否则获取锁失败，为了防止死锁，我们再用expire命令对这个key设置一个超时时间来避免。  \n        但是这里看似完美，实则有缺陷，当我们setnx成功后，线程发生异常中断，expire还没来的及设置，那么就会产生死锁。  \n    2 使用getset实现，可以判断自己是否获得了锁，但是可能会出现并发的原子性问题。拆分成两个操作。  \n    3 避免原子性问题可以使用lua脚本保证事务的原子性。  \n    4 上述都是单点的redis，如果是分布式环境的redis集群，可以使用redlock，要求节点向半数以上redis机器请求锁。才算成功。  \n\n### zookeeper实现分布式锁\n\n创建有序节点，最小的抢到锁，其他的监听他的上一个节点即可。并且抢到锁的节点释放时只会通知下一个节点。\n\n小结\n\n在分布式系统中，共享资源互斥访问问题非常普遍，而针对访问共享资源的互斥问题，常用的解决方案就是使用分布式锁，这里只介绍了几种常用的分布式锁，分布式锁的实现方式还有有很多种，根据业务选择合适的分布式锁，下面对上述几种锁进行一下比较：  \n数据库锁：  \n优点：直接使用数据库，使用简单。  \n缺点：分布式系统大多数瓶颈都在数据库，使用数据库锁会增加数据库负担。  \n缓存锁：  \n优点：性能高，实现起来较为方便，在允许偶发的锁失效情况，不影响系统正常使用，建议采用缓存锁。  \n缺点：通过锁超时机制不是十分可靠，当线程获得锁后，处理时间过长导致锁超时，就失效了锁的作用。  \nzookeeper锁：  \n优点：不依靠超时时间释放锁；可靠性高；系统要求高可靠性时，建议采用zookeeper锁。  \n缺点：性能比不上缓存锁，因为要频繁的创建节点删除节点。并且zookeeper只能单点写入。而Redis可以并发写入。\n\n## 消息队列\n\n适合场景：\n\n1 服务之间解耦，比如淘宝的买家服务和物流服务，中间需要消息传递订单信息。但又不需要强耦合。便于服务的划分和独立部署\n\n2 控制流量，大流量访问某服务时，避免服务出现问题，将其先存入队列，均匀释放流量。\n\n3 削峰，当某一个服务如秒杀，如果直接集中访问，服务器可能会冲垮，所以先存到队列中，控制访问量，避免服务器冲击。\n\n4 事务，消息事务\n\n5 异步请求处理，比如一些不重要的服务可以延缓执行，比如卖家评价，站内信等。\n\n常用消息队列：\n\nrabbitmq：使用consumer和producer的模型，并且使用了broker，broker中包含路由功能的exchanger，每个key绑定一个queue，应用通过key进行队列消费和生产。\n\n一般是点对点的消息，也可以支持一对多的消息，当然也可以支持消息的订阅。还有就是主题模式，和key的区别就是主题模式是多级的key表示。\n\nkafka：\n\n\n\n## 微服务和Dubbo\n\n分布式架构意味着服务的拆分，最早的SOA架构已经进行了服务拆分，但是每个服务还是太过庞大，不适合扩展和修改。\n\n微服务的拆分粒度更加细，服务可以独立部署和快速迭代，通知支持扩展。\n\n服务之间一般使用rpc调用进行访问，可以使用自定义协议也可以使用http服务，当然通过netty 实现TCP服务并且搭配合理的序列化方案也可以完成rpc功能。rpc是微服务的基础。\n\n微服务一般需要配置中心来进行服务注册和发现，以便服务信息更新和配置，dubbo中使用的是zookeeper，用于配置服务信息提供给生产者使用。\n\n一般情况下微服务需要有监控中心，心跳检测每一台服务器，及时完成故障切换和通知。同时监控服务的性能和使用情况。\n\n序列化方式一般可以使用protobuf，http服务一般使用json。\n\n微服务还支持更多的包括权限控制，流量控制，灰度发布，服务降级等内容，这里就不再细谈。\n\n\n### 全局id\n\n方法一：使用数据库的 auto_increment 来生成全局唯一递增ID\n\n\n\n    优点：  \n        简单，使用数据库已有的功能  \n        能够保证唯一性  \n        能够保证递增性  \n        步长固定  \n  \n  \n          \n    缺点：  \n        可用性难以保证：数据库常见架构是一主多从+读写分离，生成自增ID是写请求，主库挂了就玩不转了  \n        扩展性差，性能有上限：因为写入是单点，数据库主库的写性能决定ID的生成性能上限，并且难以扩展  \n\n\n方法三：uuid/guid\n\n\n\n不管是通过数据库，还是通过服务来生成ID，业务方Application都需要进行一次远程调用，比较耗时。\n\n\n\n有没有一种本地生成ID的方法，即高性能，又时延低呢？\n\n\n\nuuid是一种常见的方案：\n\nstring ID =GenUUID();\n\n\n\n优点：\n\n本地生成ID，不需要进行远程调用，时延低\n\n扩展性好，基本可以认为没有性能上限\n\n\n\n缺点：\n\n无法保证趋势递增\n\nuuid过长，往往用字符串表示，作为主键建立索引查询效率低，常见优化方案为“转化为两个uint64整数存储”或者“折半存储”（折半后不能保证唯一性）\n\n\n\n\n方法四：取当前毫秒数\n\n\n\nuuid是一个本地算法，生成性能高，但无法保证趋势递增，且作为字符串ID检索效率低，有没有一种能保证递增的本地算法呢？\n\n\n\n取当前毫秒数是一种常见方案：\n\nuint64 ID = GenTimeMS();\n\n\n\n优点：\n\n本地生成ID，不需要进行远程调用，时延低\n\n生成的ID趋势递增\n\n生成的ID是整数，建立索引后查询效率高\n\n\n\n缺点：\n\n如果并发量超过1000，会生成重复的ID\n\n\n方法五：类snowflake算法\n\n\n\nsnowflake是twitter开源的分布式ID生成算法，其核心思想为，一个long型的ID：\n\n41bit作为毫秒数\n\n10bit作为机器编号\n\n12bit作为毫秒内序列号\n\n\n\n算法单机每秒内理论上最多可以生成1000*(2^12)，也就是400W的ID，完全能满足业务的需求。\n\n## 秒杀系统\n\n![image](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/9eda905930f00090d55b5ae3f6796d2b.png)\n\n\n### 第一层，客户端怎么优化（浏览器层，APP层）\n\n（a）产品层面，用户点击“查询”或者“购票”后，按钮置灰，禁止用户重复提交请求；\n\n（b）JS层面，限制用户在x秒之内只能提交一次请求；\n\n### 第二层，站点层面的请求拦截\n\n> 怎么拦截？怎么防止程序员写for循环调用，有去重依据么？ip？cookie-id？…想复杂了，这类业务都需要登录，用uid即可。在站点层面，对uid进行请求计数和去重，甚至不需要统一存储计数，直接站点层内存存储（这样计数会不准，但最简单）。一个uid，5秒只准透过1个请求，这样又能拦住99%的for循环请求。\n>\n> 5s只透过一个请求，其余的请求怎么办？缓存，页面缓存，同一个uid，限制访问频度，做页面缓存，x秒内到达站点层的请求，均返回同一页面。同一个item的查询，例如车次，做页面缓存，x秒内到达站点层的请求，均返回同一页面。如此限流，既能保证用户有良好的用户体验（没有返回404）又能保证系统的健壮性（利用页面缓存，把请求拦截在站点层了）。\n\n好，这个方式拦住了写for循环发http请求的程序员，有些高端程序员（黑客）控制了10w个肉鸡，手里有10w个uid，同时发请求（先不考虑实名制的问题，小米抢手机不需要实名制），这下怎么办，站点层按照uid限流拦不住了。\n\n\n### 第三层 服务层来拦截（反正就是不要让请求落到数据库上去）消息队列+缓存\n\n服务层怎么拦截？大哥，我是服务层，我清楚的知道小米只有1万部手机，我清楚的知道一列火车只有2000张车票，我透10w个请求去数据库有什么意义呢？没错，请求队列！\n\n对于写请求，做请求队列，每次只透有限的写请求去数据层（下订单，支付这样的写业务）\n\n1w部手机，只透1w个下单请求去db\n\n3k张火车票，只透3k个下单请求去db\n\n如果均成功再放下一批，如果库存不够则队列里的写请求全部返回“已售完”。\n\n\n对于读请求，怎么优化？cache抗，不管是memcached还是redis，单机抗个每秒10w应该都是没什么问题的。如此限流，只有非常少的写请求，和非常少的读缓存mis的请求会透到数据层去，又有99.9%的请求被拦住了。\n\n\n### 好了，最后是数据库层\n\n浏览器拦截了80%，站点层拦截了99.9%并做了页面缓存，服务层又做了写请求队列与数据缓存，每次透到数据库层的请求都是可控的。db基本就没什么压力了，闲庭信步，单机也能扛得住，还是那句话，库存是有限的，小米的产能有限，透这么多请求来数据库没有意义。\n\n全部透到数据库，100w个下单，0个成功，请求有效率0%。透3k个到数据，全部成功，请求有效率100%。\n\n\n![image](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/bf7107f82e635020a43f12aa4a8dc856.png)\n\n### 总结\n上文应该描述的非常清楚了，没什么总结了，对于秒杀系统，再次重复下我个人经验的两个架构优化思路：\n\n（1）尽量将请求拦截在系统上游（越上游越好）；\n\n（2）读多写少的常用多使用缓存（缓存抗读压力）；\n\n浏览器和APP：做限速\n\n站点层：按照uid做限速，做页面缓存\n\n\n\n服务层：按照业务做写请求队列控制流量，做数据缓存\n\n数据层：闲庭信步\n\n并且：结合业务做优化\n\n## 微信公众号\n\n### Java技术江湖\n\n如果大家想要实时关注我更新的文章以及分享的干货的话，可以关注我的公众号【Java技术江湖】一位阿里 Java 工程师的技术小站，作者黄小斜，专注 Java 相关技术：SSM、SpringBoot、MySQL、分布式、中间件、集群、Linux、网络、多线程，偶尔讲点Docker、ELK，同时也分享技术干货和学习经验，致力于Java全栈开发！\n\n**Java工程师必备学习资源:** 一些Java工程师常用学习资源，关注公众号后，后台回复关键字 **“Java”** 即可免费无套路获取。\n\n![我的公众号](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20190805090108984.jpg)\n\n### 个人公众号：黄小斜\n\n作者是 985 硕士，蚂蚁金服 JAVA 工程师，专注于 JAVA 后端技术栈：SpringBoot、MySQL、分布式、中间件、微服务，同时也懂点投资理财，偶尔讲点算法和计算机理论基础，坚持学习和写作，相信终身学习的力量！\n\n**程序员3T技术学习资源：** 一些程序员学习技术的资源大礼包，关注公众号后，后台回复关键字 **“资料”** 即可免费无套路获取。      \n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20190829222750556.jpg)"
  },
  {
    "path": "docs/distributed/分布式理论总结.md",
    "content": "# 目录\n\n  * [六、Raft](#六、raft)\n    * [单个 Candidate 的竞选](#单个-candidate-的竞选)\n    * [多个 Candidate 竞选](#多个-candidate-竞选)\n    * [数据同步](#数据同步)\n  * [参考](#参考)\n\n\n## 六、Raft\n\nRaft 也是分布式一致性协议，主要是用来竞选主节点。\n\n*   [Raft: Understandable Distributed Consensus](http://thesecretlivesofdata.com/raft)\n\n### 单个 Candidate 的竞选\n\n有三种节点：Follower、Candidate 和 Leader。Leader 会周期性的发送心跳包给 Follower。每个 Follower 都设置了一个随机的竞选超时时间，一般为 150ms~300ms，如果在这个时间内没有收到 Leader 的心跳包，就会变成 Candidate，进入竞选阶段。\n\n*   下图展示一个分布式系统的最初阶段，此时只有 Follower 没有 Leader。Node A 等待一个随机的竞选超时时间之后，没收到 Leader 发来的心跳包，因此进入竞选阶段。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/68747470733a2f2f63732d6e6f7465732d313235363130393739362e636f732e61702d6775616e677a686f752e6d7971636c6f75642e636f6d2f3131313532313131383031353839382e676966%20(1).gif)\n\n*   此时 Node A 发送投票请求给其它所有节点。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/68747470733a2f2f63732d6e6f7465732d313235363130393739362e636f732e61702d6775616e677a686f752e6d7971636c6f75642e636f6d2f3131313532313131383434353533382e676966.gif)\n\n*   其它节点会对请求进行回复，如果超过一半的节点回复了，那么该 Candidate 就会变成 Leader。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/68747470733a2f2f63732d6e6f7465732d313235363130393739362e636f732e61702d6775616e677a686f752e6d7971636c6f75642e636f6d2f3131313532313131383438333033392e676966.gif)\n\n*   之后 Leader 会周期性地发送心跳包给 Follower，Follower 接收到心跳包，会重新开始计时。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/68747470733a2f2f63732d6e6f7465732d313235363130393739362e636f732e61702d6775616e677a686f752e6d7971636c6f75642e636f6d2f3131313532313131383634303733382e676966.gif)\n\n### 多个 Candidate 竞选\n\n*   如果有多个 Follower 成为 Candidate，并且所获得票数相同，那么就需要重新开始投票。例如下图中 Node B 和 Node D 都获得两票，需要重新开始投票。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/68747470733a2f2f63732d6e6f7465732d313235363130393739362e636f732e61702d6775616e677a686f752e6d7971636c6f75642e636f6d2f3131313532313131393230333334372e676966.gif)\n\n*   由于每个节点设置的随机竞选超时时间不同，因此下一次再次出现多个 Candidate 并获得同样票数的概率很低。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/68747470733a2f2f63732d6e6f7465732d313235363130393739362e636f732e61702d6775616e677a686f752e6d7971636c6f75642e636f6d2f3131313532313131393336383731342e676966.gif)\n\n### 数据同步\n\n*   来自客户端的修改都会被传入 Leader。注意该修改还未被提交，只是写入日志中。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/68747470733a2f2f63732d6e6f7465732d313235363130393739362e636f732e61702d6775616e677a686f752e6d7971636c6f75642e636f6d2f37313535303431343130373537362e676966.gif)\n\n*   Leader 会把修改复制到所有 Follower。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/68747470733a2f2f63732d6e6f7465732d313235363130393739362e636f732e61702d6775616e677a686f752e6d7971636c6f75642e636f6d2f39313535303431343133313333312e676966.gif)\n\n*   Leader 会等待大多数的 Follower 也进行了修改，然后才将修改提交。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/68747470733a2f2f63732d6e6f7465732d313235363130393739362e636f732e61702d6775616e677a686f752e6d7971636c6f75642e636f6d2f39313535303431343133313333312e676966.gif)\n\n*   此时 Leader 会通知的所有 Follower 让它们也提交修改，此时所有节点的值达成一致。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/68747470733a2f2f63732d6e6f7465732d313235363130393739362e636f732e61702d6775616e677a686f752e6d7971636c6f75642e636f6d2f39313535303431343133313333312e676966.gif)\n\n## 参考\n\n- 倪超. 从 Paxos 到 ZooKeeper : 分布式一致性原理与实践 [M]. 电子工业出版社, 2015.\n- [Distributed locks with Redis](https://redis.io/topics/distlock)\n- [浅谈分布式锁](http://www.linkedkeeper.com/detail/blog.action?bid=1023)\n- [基于 Zookeeper 的分布式锁](http://www.dengshenyu.com/java/分布式系统/2017/10/23/zookeeper-distributed-lock.html)\n- [聊聊分布式事务，再说说解决方案](https://www.cnblogs.com/savorboard/p/distributed-system-transaction-consistency.html)\n- [分布式系统的事务处理](https://coolshell.cn/articles/10910.html)\n- [深入理解分布式事务](https://juejin.im/entry/577c6f220a2b5800573492be)\n- [What is CAP theorem in distributed database system?](http://www.colooshiki.com/index.php/2017/04/20/what-is-cap-theorem-in-distributed-database-system/)\n- [NEAT ALGORITHMS - PAXOS](http://harry.me/blog/2014/12/27/neat-algorithms-paxos/)\n- [Paxos By Example](https://angus.nyc/2012/paxos-by-example/)\n"
  },
  {
    "path": "docs/hxx/java/Java后端工程师必备书单（从Java基础到分布式）.md",
    "content": "# 目录\n\n* [网络](#网络)\n  * [1 TCP/IP卷一](#1-tcpip卷一)\n  * [2 计算机网络：自顶向下](#2-计算机网络：自顶向下)\n  * [3 图解HTTP和图解TCP/IP](#3-图解http和图解tcpip)\n  * [4 计算机网络](#4-计算机网络)\n* [操作系统](#操作系统)\n  * [1 深入理解计算机系统](#1-深入理解计算机系统)\n  * [2 现代操作系统](#2-现代操作系统)\n  * [3 Linux内核设计与实现](#3-linux内核设计与实现)\n  * [4 Unix网络编程](#4-unix网络编程)\n* [数据结构与算法](#数据结构与算法)\n  * [1 算法导论](#1-算法导论)\n  * [2 数据结构与算法（Java版）](#2-数据结构与算法（java版）)\n  * [3 算法图解，啊哈算法](#3-算法图解，啊哈算法)\n  * [4 剑指offer](#4-剑指offer)\n  * [5 LeetCode](#5-leetcode)\n* [Java基础](#java基础)\n* [1 Java编程思想](#1-java编程思想)\n* [2 Java核心技术卷一](#2-java核心技术卷一)\n* [Java进阶](#java进阶)\n  * [1 深入理解JVM虚拟机](#1-深入理解jvm虚拟机)\n  * [2 Java并发编程实战](#2-java并发编程实战)\n  * [3 Java并发编程艺术](#3-java并发编程艺术)\n  * [4 Effective Java](#4-effective-java)\n  * [5 Java性能调优指南](#5-java性能调优指南)\n  * [6 Netty权威指南](#6-netty权威指南)\n* [JavaWeb](#javaweb)\n  * [1 深入JavaWeb技术内幕](#1-深入javaweb技术内幕)\n  * [2 How Tomcat Works](#2-how-tomcat-works)\n  * [3 Tomcat架构解析](#3-tomcat架构解析)\n  * [4 Spring实战](#4-spring实战)\n  * [5 Spring源码深度解析](#5-spring源码深度解析)\n  * [6 Spring MVC学习指南](#6-spring-mvc学习指南)\n  * [6 Maven实战](#6-maven实战)\n* [数据库](#数据库)\n  * [1 数据库原理](#1-数据库原理)\n  * [1 sql必知必会](#1-sql必知必会)\n  * [2 深入浅出MySQL](#2-深入浅出mysql)\n  * [3 MySQL技术内幕：innodb存储引擎](#3-mysql技术内幕：innodb存储引擎)\n  * [4 高性能Mysql](#4-高性能mysql)\n  * [5 Redis实战](#5-redis实战)\n  * [6 Redis设计与实现](#6-redis设计与实现)\n* [分布式](#分布式)\n  * [1 分布式Java应用](#1-分布式java应用)\n  * [2 大型网站技术架构](#2-大型网站技术架构)\n  * [3 大型分布式网站架构设计与实践](#3-大型分布式网站架构设计与实践)\n  * [4 分布式服务框架原理与实践](#4-分布式服务框架原理与实践)\n  * [5 大型网站系统与Java中间件开发实践](#5-大型网站系统与java中间件开发实践)\n  * [6 从Paxos到Zookeeper分布式一致性原理与实践](#6-从paxos到zookeeper分布式一致性原理与实践)\n  * [7 大规模分布式存储系统](#7-大规模分布式存储系统)\n* [云计算](#云计算)\n  * [1 OpenStack设计与实现](#1-openstack设计与实现)\n  * [2 docker入门与实践](#2-docker入门与实践)\n  * [3 kubenetes权威指南](#3-kubenetes权威指南)\n* [大数据](#大数据)\n  * [1 大数据技术原理与应用](#1-大数据技术原理与应用)\n  * [2 Hadoop实战](#2-hadoop实战)\n  * [3 Hadoop权威指南](#3-hadoop权威指南)\n* [其他：](#其他：)\n  * [1 Git权威指南](#1-git权威指南)\n  * [2 重构](#2-重构)\n  * [3 - n](#3---n)\n* [微信公众号](#微信公众号)\n  * [个人公众号：黄小斜](#个人公众号：黄小斜)\n  * [技术公众号：Java技术江湖](#技术公众号：java技术江湖)\n\n\nJava开发工程师一般负责后端开发，当然也有专门做Java Web的工程师，但是随着前后端的分离，越来越多的Java工程师需要往大后端方向发展。\n\n今天我们就来介绍一下Java后端开发者的书单。\n\n首先要感谢一下江南白衣大大的后端书架，让我在初学阶段读到了很多好书，直到现在都印象深刻。\n\n我在两年的学习历程中看了很多的书，其中不乏XXX入门到精通，XXX王者归来，XXX指南什么的。\n\n虽然这类书确实毛病很多，但是作为非科班的我来说，当时还是看的津津有味。直到后来我看到一些优秀的书籍，以及白衣哥的书架，我才逐渐认识到看一些精品书籍的好处。\n\n所以我们今天就从这些精品书籍中挑选一些优秀书籍来推荐给大家，当然其中有一些书我自己也没有时间看完。\n\n接下来我们按照后端技术栈各个部分的内容来推荐书籍。\n\n# 网络\n\n  \n\n## 1 TCP/IP卷一\n\n  \n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPOTBB6VxkibFrAMBJ8ibbR5MsqoHcGCTTk8lwnicOzjrYsEPS0m4z5TptXoUlzlFfu7hsLfWk9gBz5hA/640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1)\n\n这本其实我刚开始没看太懂，可能是当时太水的原因，但是一般是大牛力荐的书。\n\n## 2 计算机网络：自顶向下\n\n  \n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPOTBB6VxkibFrAMBJ8ibbR5MsCYHvAMX255u3jPtfmknRsYt7JRS48VvzZSIiarQTNM96pv942AbmgIA/640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1)\n\n  \n\n这本从应用层讲到物理层，感觉这种方式学起来更轻松，我比较推荐小白看这本。\n\n## 3 图解HTTP和图解TCP/IP\n\n  \n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPOTBB6VxkibFrAMBJ8ibbR5MsLee9tvMJ90MGvsY1xTf696Nzs5reVs7he5Zxm6vs9Mia7tpCqFib9WnA/640?wx_fmt=jpeg)\n\n  \n\n相较于前两本大厚书，这两本书更加亲民，小白可以买来看看，还是很适合入门的。\n\n## 4 计算机网络\n\n  \n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPOTBB6VxkibFrAMBJ8ibbR5MspicVeFKBcEkU1GcE8YbqlKlaqZez3oA0ib6hohvOJ5nCp4ibYqm5LqibaQ/640?wx_fmt=jpeg)\n\n  \n\n没错，就是这本教材，作为非科班选手自己看这本书，那叫一个欲仙欲死啊，看完就忘记了。\n\n# 操作系统\n\n  \n\n## 1 深入理解计算机系统\n\n  \n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPOTBB6VxkibFrAMBJ8ibbR5MsShU8y0B9b6pJ4ExOIexD8LOolfhVvAeFicg0vQXzaZqmZoj9Uj178gA/640?wx_fmt=jpeg)\n\n  \n\n这本书不是严格意义上的操作系统书籍，而是对计算机基础和操作系统做了一个串联，可以解决你很多对于计算机的疑惑，并且对操作系统有一定理解。\n\n其实这本书还是很厚的，有一定难度，建议有一些基础后再看。\n\n## 2 现代操作系统\n\n  \n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPOTBB6VxkibFrAMBJ8ibbR5MsoRGA0QC4DLZy08ybPlaLVKrYS2ricexI06zbPtuHT1VrjLC2HsXAAdA/640?wx_fmt=jpeg)\n\n  \n\n这本书其实我没怎么看，比较详细地讲解了操作系，但是也是大家都比较推崇的书，还是那句话，很厚，慎看。\n\n## 3 Linux内核设计与实现\n\n  \n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPOTBB6VxkibFrAMBJ8ibbR5MsIsAElOVjXPZaPLbqRxBDHtcN7dLOVpTxicFfMCSlRvtibypntLxJbItg/640?wx_fmt=jpeg)\n\n  \n\n当你对操作系统有一定理解以后，这本书将为你打开学习Linux内核的大门，当然在此之前你得有一定的c语言开发能力，以及对Linux的了解。反正，我现在还没开始准备好看这本书。\n\n## 4 Unix网络编程\n\n  \n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPOTBB6VxkibFrAMBJ8ibbR5MstdxG8HczJnOCP6M5C06Zt4QgKn3koJ5qLJk6R9cT2p8ZJt0PRz9Yxg/640?wx_fmt=jpeg)\n\n  \n\n这两本书的厚度绝对让你感到绝望，其实就是讲解了Unix内核是如何实现网络通信的，其中涉及到很多网络，操作系统的知识，并且你要熟悉c语言。总之，这是一本奉为网络编程神书的存在，不过我等新手还是拿他压压泡面就好了。\n\n网上有很多博客会参照该书的内容，可以去看看它们。\n\n# 数据结构与算法\n\n  \n\n不瞒你说，由于我非科班，所以算法基础比较差，数据结构是考研时跟着天勤考研指南学的，学习算法也是一路坎坷，举步维艰。还是分享几本比较靠谱的书吧。\n\n## 1 算法导论\n\n  \n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPOTBB6VxkibFrAMBJ8ibbR5Msian7X4kGKPeU2lEOTN6bgTKbYCRFvGp3a38OaCanfMNaaH35vQEicEmQ/640?wx_fmt=jpeg)\n\n  \n\n你问我是不是认真的，我就是说说而已，这本书我买来没看过。\n## 2 数据结构与算法（Java版）\n\n  \n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPOTBB6VxkibFrAMBJ8ibbR5MsOZ46lb6ATwho5MxHyvCmia91uVp5rd5PTGJrGiacONVibgVU5whibxt0YA/640?wx_fmt=jpeg)\n\n  \n\n这本书对于初学者来说还是比较友好的，当然学Java的看这本。\n\n## 3 算法图解，啊哈算法\n\n  \n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPOTBB6VxkibFrAMBJ8ibbR5MsfZ0XycKZ0GYdtA9vD78ZGA4MHQfmDLMA2WWjmoU7VOnvM7cCHv7jKw/640?wx_fmt=jpeg)\n\n  \n\n这两部书籍非常适合学习算法的入门，前者主要用图解的形式覆盖了大部分常用算法，包括dp，贪心等等，可以作为入门书，后者则把很多常用算法都进行了实现，包括搜索，图，树等一些比较高级的常用算法。\n\n## 4 剑指offer\n\n  \n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPOTBB6VxkibFrAMBJ8ibbR5Msicrg4GrcBwcfVskpU2ER3ichxkZLmelObPkle2xZXQakpqJ72iazxubdQ/640?wx_fmt=jpeg)\n\n  \n\n这本书还是要强烈推荐的，毕竟是面试题经常参考的书籍，当然最好有前面基本的铺垫再看，可能收获更大，这本书在面试之前一般都要嚼烂。\n\n## 5 LeetCode\n\n  \n\n这不是一本书，是一个题库，算法么，终究还是要靠刷题来提升熟练度的。\n\n# Java基础\n\n  \n\n# 1 Java编程思想\n\n  \n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPOTBB6VxkibFrAMBJ8ibbR5Msxv333llv2svDSzJhMehe7MGe0dmUWMayDFkEJthXjTV5IzOgzGHUDw/640?wx_fmt=jpeg)\n\n  \n\n这本书也是被誉为Java神书的存在了，但是对新手不友好，适合有些基础再看，当然要选择性地看。我当时大概只看了1/3\n\n# 2 Java核心技术卷一\n\n  \n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPOTBB6VxkibFrAMBJ8ibbR5Ms4k0NHxprIMXNFOPQPIgsL3JQMbwZlmTTYicmYFZaGSRwmw0VNXLtxrA/640?wx_fmt=jpeg)\n\n  \n\n这本书还是比较适合入门的，当然，这种厚皮书要看完还是很有难度的，不过比起上面那本要简单一些\n\n# Java进阶\n\n  \n\n## 1 深入理解JVM虚拟机\n\n  \n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPOTBB6VxkibFrAMBJ8ibbR5Ms3a2wib6W7zxWmMRGeeaj7qSDgDbcSSRg4JiafJ6veibia1huSMYiaIApeOA/640?wx_fmt=jpeg)\n\n  \n\n这本书是Java开发者必须看的书，很多jvm的文章都是提取这本书的内容。JVM是Java虚拟机，赋予了Java程序生命，所以好好看看把，我自己就已经看了三遍了。\n\n## 2 Java并发编程实战\n\n  \n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPOTBB6VxkibFrAMBJ8ibbR5MsnJM4AZySogwnc18edVYiaibWbtZZlJt87zQ79mQxBdib1td82ceibdxQjg/640?wx_fmt=jpeg)\n\n  \n\n这本书是Java 并发包作者写的书，所以非常权威，但是比较晦涩难懂，我看的云里雾里的，大家可以按需选择。\n\n## 3 Java并发编程艺术\n\n  \n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPOTBB6VxkibFrAMBJ8ibbR5Ms5dloj3RticzOPib4sLQ198ibd6uukhh9mQAkylibNxZcQVgNmdgpJuuYibg/640?wx_fmt=jpeg)\n\n  \n\n这本书是国内作者写的Java并发书籍，比上面那一本更简单易懂，适合作为并发编程的入门书籍，当然，学习并发原理之前，还是先把Java的多线程搞懂吧。\n\n## 4 Effective Java\n\n  \n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPOTBB6VxkibFrAMBJ8ibbR5MsqHAgLGrtTqQC6EXb9UnIw1A7xXZwzPvNzLVBcMovlicXs6GdhapZQ6w/640?wx_fmt=jpeg)\n\n  \n\n这本书和Java编程思想一样被称为神书，主要讲的是Java的一些优化技巧和规范，没有一定开发经验的人看这本书会觉得索然无味，不知所云，所以，先搁着吧。\n\n## 5 Java性能调优指南\n\n  \n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPOTBB6VxkibFrAMBJ8ibbR5MswO0s5EYF4ETZ75h4mIwDnGWPDdkBSZLCypMFEH6bx3fFsYN6HFhH2w/640?wx_fmt=jpeg)\n\n  \n\n说到JVM调优，可能会有很多的面试题浮现在你的脑海里，这本书比较权威地讲解了Java的性能调优方法，不过我还没怎么看，有空好好看看。\n\n## 6 Netty权威指南\n\n  \n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPOTBB6VxkibFrAMBJ8ibbR5MsVAiaXRCibH0niccx3lI8eQJEcewVbzymYIfSiafytfibqg6uY4OWjN8HXbg/640?wx_fmt=jpeg)\n\n  \n\nNetty是基于NIO开发的网络编程框架，使用Java代码编程，其实这本书也可以放在网络或者Java Web部分。不过NIO属于JDK自带的一部分，是必须要掌握的，而对于Netty，大家如果学有余力的话也可以看看。\n\n# JavaWeb\n\n  \n\n## 1 深入JavaWeb技术内幕\n\n  \n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPOTBB6VxkibFrAMBJ8ibbR5MsXQCMLd9PwB74EAoWDZWKslQaicJn0lkfpS8vUiba9n4rQbnN4jsJXibRQ/640?wx_fmt=jpeg)\n\n  \n\n这本书是Java Web的集大成之作，涵盖了大部分Java Web开发的知识点，不过一本书显然无法把所有细节都讲完，但是作为Java Web的入门或者进阶书籍来看的话还是很不错的。\n\n## 2 How Tomcat Works\n\n  \n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPOTBB6VxkibFrAMBJ8ibbR5MsvItQbtqibRdPJvHrPu5WJlJLPyP9JS7jIsM8PwvQarn2bndmoF4ricKg/640?wx_fmt=jpeg)\n\n  \n\nJava Web很重要的一部分内容就是Tomcat，作为应用服务器，Tomcat使用Java开发，其源代码和架构设计都是经典之作。\n\n这是一本讲解Tomcat基本原理的书籍，很好地通过剖析源码来讲解Tomcat的内部结构和运行机制，但是需要一定的基础才能够看懂，我还没看这本书，日后再拜读。\n\n## 3 Tomcat架构解析\n\n  \n\n![](https://mmbiz.qpic.cn/mmbiz_png/XHh0SksQZPOTBB6VxkibFrAMBJ8ibbR5MsR7yXam5xjrMJc5FajShzqK8ib5FcIBUqGdzcvcJe6XEuW8Vyc5PKRqA/640?wx_fmt=png)\n\n和上面这本书类似，主要讲解Tomcat原理和架构，，要看懂这本书的话，前提是你要对Java基础，NIO以及设计模式有所了解。这本书我也还没看。\n\n## 4 Spring实战\n\n  \n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPOTBB6VxkibFrAMBJ8ibbR5MsE6AxZgMjGh3HYb1tblQVUGk6FjOQ74omphiaqrE2VZgqY8aianHaA9VQ/640?wx_fmt=jpeg)\n\n  \n\n这本书适合作为Spring的入门书籍，把Spring的概念，使用方式等内容都讲的比较清楚。并且也介绍了Spring MVC的部分内容，Spring框架还是更注重实践的，所以跟着书上的内容去做吧。\n\n## 5 Spring源码深度解析\n\n  \n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPOTBB6VxkibFrAMBJ8ibbR5Msia1wMFrvWicOC3kEiazfqS4cz9eXmVdwmu1G7ucIOUUtx914KlicxIwuOA/640?wx_fmt=jpeg)\n\n  \n\n学会Spring基础后，可以花点时间看看这本讲源码的书了，这本书对于新手来说不太友好，主要也是因为Spring的代码结构比较复杂，大家也可以看一些博客来完成对源码的学习。\n\n## 6 Spring MVC学习指南\n\n  \n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPOTBB6VxkibFrAMBJ8ibbR5Msibbox9gJ2wV8EZofhrs4RXLcZpXCUboGCChzf2NNScibtVCytt1iamwdw/640?wx_fmt=jpeg)\n\n  \n\n本书是一本Spring MVC的教程，内容细致、讲解清晰，非常适合Web开发者和想要使用Spring MVC开发基于Java的Web应用的读者阅读。但是由于出的比较早，所以不太适合现在版本。\n\n## 6 Maven实战\n\n  \n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPOTBB6VxkibFrAMBJ8ibbR5Ms9k4687xRSru4FicK1Mia6xO0Z6cBQVuYibfzUHlq2Kh9XpN5Oel37tbibA/640?wx_fmt=jpeg)\n\n  \n\nMaven是Java Web开发中不可缺少的一部分，如果想要全面了解其实现原理的话，可以看看这本书。\n\n# 数据库\n\n  \n\n## 1 数据库原理\n\n  \n\n数据库原理应该是教材吧，这本书作为数据库入门来说还是可以的，毕竟不是专门做DB的，看大厚书用处不大，这本书把数据库的基本概念都讲完了。\n\n## 1 sql必知必会\n\n  \n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPOTBB6VxkibFrAMBJ8ibbR5MsDz6nCG9LhHxjVVEIrB5n03443oFENiak2kwJ1Eicj0diacfp67icUAFjYg/640?wx_fmt=jpeg)\n\n  \n\n这本书主要是讲解sql语句怎么写，毕竟数据库最重要的一点就是要熟练地使用sql语句，当然这本书也可以当做工具书来使用。\n\n## 2 深入浅出MySQL\n\n  \n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPOTBB6VxkibFrAMBJ8ibbR5Msgwk2uEmyhyHlTAbEL2GHbW0BQaXSUm5myDq9XbJWEa6eQBXpcyo2vg/640?wx_fmt=jpeg)\n\n  \n\n这本书适合作为MySQL的学习书籍，当你有了一定的MySQL使用经验后，可以看看它，该书从数据库的基础、开发、优化、管理维护和架构5个方面对MySQL进行了详细的介绍，讲的不算特别深，但是足够我们使用了。这本书我也只看了一部分。\n\n## 3 MySQL技术内幕：innodb存储引擎\n\n  \n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPOTBB6VxkibFrAMBJ8ibbR5MsvicPSEMHB2AyGQxBNic3r3LBEukSXlTTxKExRazE2HEDLtXEqVNkq6hA/640?wx_fmt=jpeg)\n\n  \n\n看完上面那本书以后，对MySQL算是比较熟悉了，不过对于面试中常考的innodb引擎，还是推荐一下这本书把，专门讲解了innodb存储引擎的相关内容。我还没有细看，但是内容足够你学好innodb了。\n\n## 4 高性能Mysql\n\n  \n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPOTBB6VxkibFrAMBJ8ibbR5MsfLtH0gzib1kXiab1iaB7T7W4DqJhEIY7DaY6XMskos6OSS8MwblfexERg/640?wx_fmt=jpeg)\n\n  \n\n这本书可以说是很厚了，更适合DBA拜读，讲的太详细了，打扰了。\n\n## 5 Redis实战\n\n  \n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPOTBB6VxkibFrAMBJ8ibbR5MscrpdBT6nbJ32zd46Q0cjkH7ReTjuE7OibZpMZPbhNe74t6iagU03mO5A/640?wx_fmt=jpeg)\n\n  \n\n和MySQL一样，学习Redis的第一步最好也是先实战一下，通过这本书就可以较好地掌握Redis的使用方法，以及相关数据结构了。\n\n## 6 Redis设计与实现\n\n  \n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPOTBB6VxkibFrAMBJ8ibbR5Msre7k7xFaH32lu2ibDibNbkIpnRvK4XP5KJicLfZicteOvkFbttIZ8bZKcg/640?wx_fmt=jpeg)\n\n  \n\n该书全面而完整地讲解了 Redis 的内部运行机制,对 Redis 的大多数单机功能以及所有多机功能的实现原理进行了介绍。这本书把Redis的基本原理讲的一清二楚，包括数据结构，持久化，集群等内容，有空应该看看。\n\n# 分布式\n\n  \n\n## 1 分布式Java应用\n\n  \n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPOTBB6VxkibFrAMBJ8ibbR5MsDM0fL3YQ81tyskxJRdW4raFbwCUIMVCXw3aQ594DR9ib6OdTVLm7DZg/640?wx_fmt=jpeg)\n\n  \n\n这本书是淘宝大牛写的书，主要讲的就是使用rpc来构建分布式的Java应用，讲了很多基础的东西，可以作为入门书籍，不过这本书我之前没有遇到，所以没看过。\n\n## 2 大型网站技术架构\n\n  \n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPOTBB6VxkibFrAMBJ8ibbR5Ms54ygz1Uicdj9SkbbewcyA78SsESGDNn3VNjTDTDhW18G406UaOlCNXA/640?wx_fmt=jpeg)\n\n  \n\n这本淘宝系技术指南还是非常值得推崇的，可以说是把大型网站的现代架构进行了一次简单的总结，内容涵盖了各方面，主要讲的是概念，很适合没接触过架构的同学入门。看完以后你会觉得后端技术原来这么博大精深。\n## 3 大型分布式网站架构设计与实践\n\n  \n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPOTBB6VxkibFrAMBJ8ibbR5MsicmqxK5NtFkiaEEMuOicZUGWQlloNzIhsKvrLibZYFOic0MtTo2iciaxrUH3w/640?wx_fmt=jpeg)\n\n  \n\n这本书与上面一书相比更倾向于实践，主要讲的是分布式架构的一些解决方案，但是如果你没有接触过相关的场景，可能会看的云里雾里。\n\n## 4 分布式服务框架原理与实践\n\n  \n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPOTBB6VxkibFrAMBJ8ibbR5MsnYBbshOibs9eMXMpsb4BAMNe2Gl0zfbdoEnwVkgYYZqnHE8jTNI5x7Q/640?wx_fmt=jpeg)\n\n  \n\n上面那本书讲的是分布式架构的实践，而这本书更专注于分布式服务的原理讲解和对应实践，很好地讲述了分布式服务的基本概念，相关技术，以及解决方案等，对于想要学习分布式服务框架的同学来说是本好书。\n\n## 5 大型网站系统与Java中间件开发实践\n\n  \n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPOTBB6VxkibFrAMBJ8ibbR5MsA0cLZgXBkHU0FtBGaD3WusGHu9VGgAeFGy2YaK7dSlOibLnVtxMZHDA/640?wx_fmt=jpeg)\n\n  \n\n话说这些书的名字真实够长的。这本书也是阿里系出品，主要讲的是大型网站系统以及使用的相关中间件，毕竟阿里是中间件大户，所以很多中间件对应用再网站系统中，对于想学习这方面技术的同学来说可以一看。\n\n## 6 从Paxos到Zookeeper分布式一致性原理与实践\n\n  \n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPOTBB6VxkibFrAMBJ8ibbR5Msl8vtjticonEQopz007VDV1ZqA024AaSWicLpW12ib0bqAc5ibNK5XpKGGg/640?wx_fmt=jpeg)\n\n说起分布式系统，我们需要了解它的原理，相关理论及技术，这本书也是从这个角度出发，讲解了分布式系统的一些常用概念，并且带出了分布式一哥zookeeper，可以说是想学分布式技术的同学必看的书籍。\n\n## 7 大规模分布式存储系统\n\n  \n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPOTBB6VxkibFrAMBJ8ibbR5MsSOY9dKWKloIqmyiarqrJr3yZDchPndlWFV6k5QyvdLoVA8mWtcfpT4A/640?wx_fmt=jpeg)\n\n  \n\n这本书是阿里巴巴oceanbase核心开发大佬写的书，讲的是分布式存储相关的原理和解决方案，该书不是很厚，如果想做存储方向的同学可以看看。\n\n# 云计算\n\n  \n\n云计算方面的内容主要是我在实习阶段接触的，如果只是应用开发方向的话这块不懂也罢。主要还是看个人兴趣。\n\n## 1 OpenStack设计与实现\n\n  \n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPOTBB6VxkibFrAMBJ8ibbR5MsHhNAAvX06XgaWh8iaxT0Kqj8fdERs48qkA8wSxpnva9WWrMx9O7ibEBQ/640?wx_fmt=jpeg)\n\n  \n\nOpenStack是基于KVM技术的一套私有云生态。这本书很好地讲解了OpenStack的一些基本原理，包括各个组件的设计与实现，比起另一本《OpenStack王者归来》简单易懂的多。当然，前提最好是你对Linux内核和网络有所了解。\n\n## 2 docker入门与实践\n\n  \n\n\n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPOTBB6VxkibFrAMBJ8ibbR5MsHR3BrVSXFjib3REYKciasfa8VQ5iaPKdwEp6I73ONWZDbroKSicYib4QvsQ/640?wx_fmt=jpeg)\n\n  \n\ndocker是现在应用部署的主流方案了，所以了解一下还是挺有必要的，这本书作为入门书籍足够让你会使用docker了。\n\n## 3 kubenetes权威指南\n\n  \n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPOTBB6VxkibFrAMBJ8ibbR5MspWgNeRfHAzCxNicg1BOFIYL2jUgGB7CgCXxUqfVsPRicTppk49HNSNlQ/640?wx_fmt=jpeg)\n\n  \n\nkubenetes是docker的集群解决方案，也是一个微服务的解决方案，所以这本书涉及的内容非常多，需要有网络，操作系统以及docker相关的基础。我看这本书的时候可以说是非常晕的。\n\n# 大数据\n\n  \n\n和云计算一样，大数据方面的内容也不算是Java后端技术栈所需要的，但是这也能为你加分，并且让你跟大数据开发的岗位沾点边，何乐而不为。\n\n## 1 大数据技术原理与应用\n\n  \n\n![](https://mmbiz.qpic.cn/mmbiz_png/XHh0SksQZPOTBB6VxkibFrAMBJ8ibbR5MscJ1Mvq3qTe5fVOXUvyqPHzPZvEvDjhsQVoicfjCibkxRDVWOjyj0DmFA/640?wx_fmt=png)\n\n  \n\n作为大数据方面的一本教材，厦大教授写的这本书还是非常赞的，从最基础的原理方面讲解了Hadoop的生态系统，并且把每个组件的原理都讲得比较清楚，另外也加入了spark，storm等内容，可以说是大数据入门非常好的一本书了。\n\n## 2 Hadoop实战\n\n  \n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPOTBB6VxkibFrAMBJ8ibbR5MsbqrsXhbiclTXRA3gI8wibEV5pNJ11XAN3SEVu0zXh4vMEN942MqblpOw/640?wx_fmt=jpeg)\n\n  \n\n这本书很厚，我买的时候大概看了一遍，一头雾水。所以建议先看上面那本书，再来看更加进阶的书籍，否则可能就是浪费时间了。\n\n## 3 Hadoop权威指南\n\n  \n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPOTBB6VxkibFrAMBJ8ibbR5Ms1LB8BJFyw8npMNjUajzicmZRO7QibDubblqAl88OuN6WAVtAUqILGeXA/640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1)\n\n  \n\n这本书主要对Hadoop生态中组件进行详细讲解，有点太详细了，如果不是做大数据方向的话，可以不看。\n\n# 其他：\n\n  \n\n## 1 Git权威指南\n\n  \n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPOTBB6VxkibFrAMBJ8ibbR5Msz3J8ciah5m58wtiaLSnT7dHIpcVqBVggn2wmS6SVsGjvSeB5OVc3rZ0w/640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1)\n\n  \n\nGit是现在大公司主流的代码协同工具，如果你想要了解其底层原理，可以看看这本书。\n\n## 2 重构\n\n  \n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPOTBB6VxkibFrAMBJ8ibbR5MssH1SXJian4O6D85sZF2OseyGmKJjxE0aJ2GFlNlPdPmZMibwJahTIjTw/640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1)\n\n  \n\n这本书主要介绍的是代码重构的一些指导思想和最佳实践。有重构需求的同学可以看看。\n\n## 3 - n\n\n  \n\n\n\n其他方面的书籍就太多了，比如软件工程方面的，测试方面，Linux方面，以及讲一些程序员自我提升的书籍，就不一一列举了，因为这部分的内容可以不归入Java后端的技术栈。\n\n# 微信公众号\n\n## 个人公众号：黄小斜\n\n黄小斜是跨考软件工程的 985 硕士，自学 Java 两年，拿到了 BAT 等近十家大厂 offer，从技术小白成长为阿里工程师。\n\n作者专注于 JAVA 后端技术栈，热衷于分享程序员干货、学习经验、求职心得和程序人生，目前黄小斜的CSDN博客有百万+访问量，知乎粉丝2W+，全网已有10W+读者。\n\n黄小斜是一个斜杠青年，坚持学习和写作，相信终身学习的力量，希望和更多的程序员交朋友，一起进步和成长！\n\n**原创电子书:**\n关注公众号【黄小斜】后回复【原创电子书】即可领取我原创的电子书《菜鸟程序员修炼手册：从技术小白到阿里巴巴Java工程师》\n\n**程序员3T技术学习资源：** 一些程序员学习技术的资源大礼包，关注公众号后，后台回复关键字 **“资料”** 即可免费无套路获取。\t\n\n**考研复习资料：** \n计算机考研大礼包，都是我自己考研复习时用的一些复习资料,包括公共课和专业的复习视频，这里也推荐给大家，关注公众号后，后台回复关键字 **“考研”** 即可免费获取。\t\n\n![](https://img-blog.csdnimg.cn/20190829222750556.jpg)\n\n\n## 技术公众号：Java技术江湖\n\n如果大家想要实时关注我更新的文章以及分享的干货的话，可以关注我的公众号【Java技术江湖】一位阿里 Java 工程师的技术小站，作者黄小斜，专注 Java 相关技术：SSM、SpringBoot、MySQL、分布式、中间件、集群、Linux、网络、多线程，偶尔讲点Docker、ELK，同时也分享技术干货和学习经验，致力于Java全栈开发！\n\n**Java工程师必备学习资源:** 一些Java工程师常用学习资源，关注公众号后，后台回复关键字 **“Java”** 即可免费无套路获取。\n\n![我的公众号](https://img-blog.csdnimg.cn/20190805090108984.jpg)\n\n​                     \n"
  },
  {
    "path": "docs/hxx/java/Java工程师修炼之路（校招总结）.md",
    "content": "# 目录\n\n* [前言](#前言)\n* [大学时期的迷茫与坚定](#大学时期的迷茫与坚定)\n* [研究生时期的探索和规划](#研究生时期的探索和规划)\n* [我的Java入门之路](#我的java入门之路)\n* [我的Java进阶之路](#我的java进阶之路)\n* [我的Java实习之路](#我的java实习之路)\n* [抉择时刻：实习转正还是秋招](#抉择时刻：实习转正还是秋招)\n* [Java修仙之路](#java修仙之路)\n* [秋招回忆录](#秋招回忆录)\n* [结束也是开始](#结束也是开始)\n* [微信公众号](#微信公众号)\n  * [个人公众号：黄小斜](#个人公众号：黄小斜)\n  * [技术公众号：Java技术江湖](#技术公众号：java技术江湖)\n\n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPNibhTicDNtDkY4OicCvkS2Kz25mxuU09hVfia31PnXnTENnN7TE9TtlOic2vrDrGtIswWuouiaToHn3yLQ/640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1)\n\n# 前言\n\n**在下本是跨专业渣考研的985渣硕一枚，经历研究生两年的学习积累，有幸于2019秋季招聘中拿到几个公司的研发岗offer，包括百度，阿里，腾讯，今日头条，网易，华为等。**\n\n一路走来也遇到很多困难，也踩了很多坑，同时我自己也探索了很多的学习方法，总结了很多心得体会，并且，我对校园招聘也做了一些研究和相应的准备。\n\n在今年的秋季招聘结束以后，我也决定把这些东西全部都写成文字，做成专题，以便分享给更多未来将要参加校招的同学。\n\n**更多内容后续都发布于微信公众号：黄小斜**\n\n# 大学时期的迷茫与坚定\n\n我的本科专业是电子信息工程，基本没有接触过计算机专业的课程，只学过c语言，然后在大三的时候接触过java，Android，以及前端开发。\n\n那时候我还不知道软件开发的水有多深，抱着试一试的态度去应聘了很多公司。结果可想而知。\n\n当年我对游戏开发很有兴趣，特别是对网易游戏情有独钟，但是当我看到网易游戏研发工程师的招聘要求时，我只能望而却步，因为它要求学历至少是985的硕士。\n\n**也因为这个契机，我在大三的暑假开始准备考研，花了一个月的时间深思熟虑之后，选择了我大华科。**\n\n毕竟是跨专业，在复习过程还是有点吃力的，但是就靠着一股毅力和执着，半年之后，顺利地考上了，成绩还意外地非常好。\n\n# 研究生时期的探索和规划\n\n**对于即将读研的同学来说，一般有两件事很重要，一件事是选择导师，一件事是选择方向。**\n\n我当时本着想要找实习的想法选择了我现在的导师，事实证明我的选择还是很正确的。\n\n而选择方向这件事，我倒是折腾了好久。研一期间我做的最多的事情就是看书了，当时自己的方向还不明确，所以找了很多书来看。当别人都在专研数据挖掘和机器学习时，我还在各种方向之间摇摆不定。\n\n我在读研之前想做游戏开发和Android开发，但我以前也学过Java Web开发。于是我在网上了解对应方向的资讯，发现游戏研发的就业面比较窄，并且基于我之前的学习经历，java开发可能更加适合我。最终在学校的实训项目中我选择了Java Web项目，从此也真正意义上地踏上了Java的学习之路。\n\n# 我的Java入门之路\n\n之前说过，在研一期间看了很多计算机专业的书籍，比如计算机网络，操作系统，数据库等等，虽然吸收得都不太好，但也算是看过了。\n\n于是我开始踏上学习Java的道路。最开始我找了一些Java的书单，然后买了一些比较基础的书籍，先啃为敬。那时候我看过《Java从入门到精通》这种烂大街的书，也看过《Java编程思想》这种很难懂的书。\n\n**一段时间后我感觉吸收效果不好，于是开始把目光转向视频课程了。那时候听舍友力神的建议，到极客学院上看一些视频课程，我当时就觉得这个讲的比书上要好懂一些。后来我又接触到了慕课网，中国MOOC等网站，逐渐地把相关的技术课程都看完了。**\n\n那时候正好我们的项目实训还在进行，于是我就把趁热打铁把这些东西用在了项目当中，并且第一次用博客记录下我的实践经验。\n\n现在回头想想，此时的我也只不过是刚刚入门了Java以及web开发。然而那时候不知道天高地厚的我，就开始xjb投各大公司的Java实习岗位了。结果可想而知，那叫一个惨啊。\n\n# 我的Java进阶之路\n\n上文说到我刚刚开始投递实习岗位，是在研一的下学期。当时整天躲在实验室，一边看书一边看视频，接到面试时赶紧刷面经，忙的不亦乐乎。那段时间感觉自己的复习状态和考研差不多。\n\n然而，由于水平确实不咋地，当时我被各大公司的面试官吊打。比如我第一家面的就是百度，三个很简单的问题一个都不会，人家面试官都不好意思打击我了。后来我又面了一些大大小小的互联网公司，虽然情况有所好转，但是总的来说，我要学习的东西还很多。\n\n在准备面试的过程中，我看了很多面经，也看了很多技术博客，发现自己的基础很薄弱，需要系统性的学习。并且这些东西是视频和入门书籍给不了我的。于是我又踏上了找书的道路。\n\n**那时候Java书单泛滥，有的书单质量低下，买来的书看两眼就看不下去了。直到我看到了“江南白衣的后端书架”这一文章，才发现Java后端书架原来应该是这样的。于是我照葫芦画瓢把相关书籍都买了，这个阶段，也算是刚刚踏上Java进阶之路吧。**\n\n**这里面不得不提几本书，对学习Java的同学非常重要，一本是《深入理解JVM虚拟机》，一本是《深入分析Java技术内幕》，以及《Java并发编程艺术》。**\n\n再后来，凭着一股不到黄河心不死的精神，终于拿到了网易游戏的实习offer。于是，第一次在大厂实习的机会终于来了，我怀着即期待又忧虑的心情来到了杭州。\n\n# 我的Java实习之路\n\n在猪场实习的时间并不长，也就持续了三个月不到，当时我们部门在做数据仓库，于是我这边主要负责Java Web应用的开发，其实也就是写一些简单的后台接口。\n\n在熟悉了工作流程以后，我很快就适应了工作的节奏，毕竟做的东西也不难，导师也会经常指导，就这样我完成了一个又一个需求，直到后来家里有事，我才临时选择辞职回家。\n\n由于在网易实习的时间比较短，我也留下了一些遗憾，比如对整个项目的架构不够熟悉，并且很多相关技术栈也来不及学习。后来我去熊厂实习的时候，尽量避免了这些问题。\n\n熊厂实习的时间长达半年，部门当时做的是私有云，emmm完全是全新的技术栈啊，于是我基本上又是从零开始学习云计算，但是由于之前的操作系统和网络基础不扎实，在学习相关技术时，基本是两眼一抹黑，学啥啥不会。\n\n**这也导致我在上班期间看了很多计算机基础方面的书籍，包括《计算机网络：自顶向下》，《深入理解计算机系统》等等。当然，这也是因为我的工作内容很简单。CRUD你懂的。**\n\n于是花时间自学成为了我那时候的主旋律，看书，看内网资源，参加技术分享，倒也非常充实。当然，有空我也会看看项目代码，了解一下技术架构，以便让自己对整个项目有一个更好的理解。\n\n再后来，2018年来了。\n\n# 抉择时刻：实习转正还是秋招\n\n我是2018年1月份离开北京的。当时面临几个问题，是否续租房子，是否继续实习。还有一个小插曲就是当时养的一只猫也得带回去。再三思考后我决定回学校准备秋招。\n\n过年后我就回到学校了，当时我本不打算参加春招，想要潜心修炼准备秋招，但是架不住春招宣传力度大，并且几个大厂都标榜着“转正容易，hc多多”等口号。于是我没忍住，上牛客投了几次简历，打算面几家大厂，心想万一过了就去吧。\n\n简历都投出去了，那也只好复习了啊，当时我们宿舍跟打了鸡血一样，一整天都在刷题，从早到晚泡着刷LeetCode，一个月后终于刷到100多题，也算是能应付一下笔试了吧。\n\n春招我投的公司不多，也就at，网易游戏和京东。最后阿里和京东都给了offer。但是当时阿里的流程走得特别慢，直到内推截止前一天才给我发offer，并且自己也感觉之前面试表现一般，心想我要是去了也是B+，很有可能成为拥抱变化的牺牲品，于是我咬咬牙放弃了，大不了秋招再来。\n\n**塞翁失马，焉知非福，春招的颗粒无收反而让我可以安心准备秋招，于是我有大把的时间做我想做的事，制定自己的学习计划，安排自己的生活，不需要去考虑转正这种麻烦事了。**\n\n至此，四月终了，春招告一段落。\n\n# Java修仙之路\n\n平时经常逛牛客，我也经常发些面经啥的，于是很多牛油喜欢调侃说“看神仙”。这时候我只能尴尬又不失礼貌的微笑了0。0\n\n在下不才，成不了神仙，最多就是打游戏的时候能修修仙罢了。\n\n不过你还真别说，网上还真有“Java成神之路”这样的文章，真的打扰了哈哈。\n\n> 科普一下修仙梗： 修仙梗的意思是喜欢熬夜的人不睡觉不猝死反而要修仙，然后就被广大的网友们互相调侃玩坏了，现在熬夜都不叫熬夜了，新潮的说法就是修仙，熬夜不会猝死啊，会增强法力。\n\n不逗你们了，咱们还是进入正题吧。我在五月份的时候做了一个计划，打算在七月底之前完成复习工作，原因是七月底有阿里的提前批招聘，是我最最重视的一个招聘。这个计划简称三个月计划，我主要做了三个方面的学习规划。\n\n**一：首先，便是对Java后端技术栈的复习，这也是最重要的一部分，因为面试永远都是考基础考得最多。**\n\n这部分内容又可以细分为多个方面：\n\n**1 Java知识体系：包括了Java基础，集合类，设计模式，Java并发技术，Java网络编程，JVM，JavaWeb，Spring框架等等。**\n\n**2 计算机基础：包括了操作系统，计算机网络，数据结构，数据库，缓存等内容。**\n\n3 后端进阶：包括了分布式理论，以及常见分布式技术比如负载均衡，zookeeper，消息队列，分布式缓存等等。当然，这里面也包括系统架构方面的知识，以及RPC，微服务等内容。\n\n4 额外内容：这部分内容因人而异，我主要是因为实习的时候项目涉及了hadoop以及私有云技术栈，所以自己看了很多这方面的东西，譬如Hadoop生态，OpenStack生态，以及docker生态。\n\n我在复习这部分内容的时候，一般先看优质博客，然后自己整理总结对应写一些博客，最后把能够实现的东西实现一下，这样一来一个知识点就搞定了。剩下的事情就是重复这个步骤。\n\n**下面放上我的博客：https://blog.csdn.net/a724888**\n\n**二：其次，便是对算法的学习了。我也把算法的学习分为了几个部分。**\n\n1 基础数据结构与算法：主要是复习之前学过的数据结构和算法，额外再看一些算法书籍，譬如《图解算法》，以了解常见算法。\n\n2 剑指offer：剑指offer基本上是面试必考的，所以把它刷个两三遍是很有必要的。\n\n**3 LeetCode：搞定前面两项之后，刷LeetCode也会有些底气了，我当时就刷了150题左右，主要也是刷经典的题目。**\n\n**4 笔试真题：这个就不用多说了，真题是一定要刷的。毕竟各个公司出题的路子都花里胡哨。**\n\n刷题多了，就会遇到很多原题和类似题目，所以，尽早开刷，做好准备吧。\n\n**三、最后一部分，则是做项目。大概说下我做项目的几个要点吧**\n\n1 为什么这时候我还要做项目呢：一来是我觉得实习过程自己接触的东西太细碎，没有对全局做把控，二来是因为想给GitHub加点东西，顺便学点新的技术。于是我选择了当时牛客网上的两个项目来自己做做看。\n\n2 关于项目选择：叶神这两个项目还是讲的非常棒棒的，用的东西也很新，代码也有提供，避免了自己要写前端的尴尬，另外，这两个项目模仿了知乎和头条，更加接地气。\n\n3 把项目做到GitHub上：之前对git也比较熟了，所以想把这个项目按照正常开发的流程走一遍，于是我每天都会做一个模块，然后发布几个版本，记录一下版本更新的细节，写这些东西的时候，自己其实就已经做了思考和总结，感觉还是不错的。\n\n**下面放上我的GitHub：https://github.com/h2pl**\n\n就这么过了三个月，提前批拉开序幕。\n\n# 秋招回忆录\n\n从七月初第一次投递简历，到九月初，整整两个月的时间，大大小小投了几十家公司，其中很多都是提前批，内推，也经历了许多的笔试，面试。\n\n  \n\n**期间也拿了几个offer，包括百度，腾讯，蚂蚁金服，头条，华为，网易（网易云音乐没给offer，调到了其他部门）。有几家直接收到拒信的，包括拼多多，深信服。还有几家在等待结果。包括快手，斗鱼等。**\n\n当然也有一些还没面试完的公司以及待安排面试的公司，这里就不展开说了。\n\n八月底基本上提前批就已经结束了，所以一般这段时间正式校招也开始了，各种大规模的笔试也很多，所以大家即使没有拿到offer也不要灰心，毕竟校招是一场持久战，基本上要到九月十月才能下结论。我之前分享了很多公司的面经，其实大部分都是提前批的，很多都是直接免笔试的，因为我对算法并不是很在行，所以感觉还是比较幸运的。\n\n**从七月底第一次面试到9月基本佛系，中间经历了大大小小的面试，这里只进行简单的记录哈，要看面经的话请到我的公众号：程序员江湖。**\n\n具体的面经都比较长，这里大概介绍一下面试的情况，然后我会放上面经的链接供大家查阅。\n\n**1 阿里面经**\n\n[阿里中间件研发面经](http://mp.weixin.qq.com/s?__biz=MzUyMDc5MTYxNA==&mid=2247483852&idx=1&sn=9ec90620478b35d63a4a971a2222095d&chksm=f9e5b29dce923b8b2b080151e4b6b78373ee3f3e6f75b69252fc00b1a009d85c6f96944ed6e6&scene=21#wechat_redirect)\n\n[蚂蚁金服研发面经](http://mp.weixin.qq.com/s?__biz=MzUyMDc5MTYxNA==&mid=2247483861&idx=1&sn=34317917908fdb778f16fa9dd557908b&chksm=f9e5b284ce923b922446fb5431b84094dec03ee4688da9c8de1f025d80eac715cbd9740e8464&scene=21#wechat_redirect)\n\n岗位是研发工程师，直接找蚂蚁金服的大佬进行内推。\n\n我参与了阿里巴巴中间件部门的提前批面试，一共经历了四次面试，拿到了口头offer。\n\n然后我也参加了蚂蚁金服中间件部门的面试，经历了三次面试，但是没有走流程，所以面试中止了。\n\n最后我走的是蚂蚁金服财富事业群的流程，经历了四次面试，包括一次交叉面，最终拿到了蚂蚁金服的意向书，评级为A。\n\n阿里的面试体验还是比较好的，至少不要求手写算法，但是非常注重Java基础，中间件部门还会特别安排Java基础笔试。\n\n**2 腾讯面经**\n\n[腾讯研发面经](http://mp.weixin.qq.com/s?__biz=MzUyMDc5MTYxNA==&mid=2247483849&idx=1&sn=f81fd42954589fb2deaf128026ddd856&chksm=f9e5b298ce923b8ef02ae36f7e9029fef0ddb7d5ae456dfa9d64c0073bebaacfb78fac4c8035&scene=21#wechat_redirect)\n\n岗位是后台开发工程师，我没有选择意向事业群。\n\nSNG的部门捞了我的简历，开始了面试，他们的技术栈主要是Java，所以比较有的聊。\n\n一共经历了四次技术面试和一次HR面试，目前正在等待结果。\n\n腾讯的面试一如既往地注重考查网络和操作系统，并且喜欢问Linux底层的一些知识，在这方面我还是有很多不足的。\n\n**3 百度面经**\n\n[百度研发面经](http://mp.weixin.qq.com/s?__biz=MzUyMDc5MTYxNA==&mid=2247483857&idx=1&sn=214b0f93db93407a7ac5a5149778cbad&chksm=f9e5b280ce923b96fcd535b2ef639fee2de78f12aa961d525b21760b11a3b95c0879113c2944&scene=21#wechat_redirect)\n\n[百度研发面经整合版](http://mp.weixin.qq.com/s?__biz=MzUyMDc5MTYxNA==&mid=2247483866&idx=1&sn=88dc80fef6ad6aa3a4221862f3630a90&chksm=f9e5b28bce923b9d4defc91ed30d8bbe650d7215966f9fb776fe5b244eae527bcd24b1941204&scene=21#wechat_redirect)\n\n岗位是研发工程师岗位，部门包括百度智能云的三个分部门以及大搜索部门。\n\n百度的提前批面试不走流程，所以可以同时面试好多个部门，所以我参加百度面试的次数大概有12次左右，最终应该是拿了两个部门的offer。\n\n百度的面试风格非常统一，每次面试基本都要到电脑上写算法，所以那段时间写算法写的头皮发麻。\n\n**4 网易面经**\n\n[网易研发面经](http://mp.weixin.qq.com/s?__biz=MzUyMDc5MTYxNA==&mid=2247483875&idx=1&sn=fa9eaedd9cc3da172ad71d360c46a054&chksm=f9e5b2b2ce923ba443b91d56b24486d22b15bea16a4788e5ed3421906e84f8edd9ee10b2b306&scene=21#wechat_redirect)\n\n面试部门是网易云音乐，岗位是Java开发工程师。\n\n网易是唯一一家我去外地面试的公司，也是我最早去实习的老东家。\n\n一共三轮面试，耗时一个下午。\n\n网易的面试比我想象中的要难，面试官会问的问题都比较深，并且会让你写一些结合实践的代码。\n\n**5 头条面经**\n\n[今日头条研发面经](http://mp.weixin.qq.com/s?__biz=MzUyMDc5MTYxNA==&mid=2247483869&idx=1&sn=eedb7aebcb90cf3d4fe2450ef2d99947&chksm=f9e5b28cce923b9a9b0852a1c998ec014eb1aeb0442916c9028da276be8db0b2934c92292961&scene=21#wechat_redirect)\n\n岗位是后台研发工程师，地点选择了上海。\n\n我参加的是字节跳动的内推面试，当时找了一个牛友要到了白金码，再次感谢这位头条大佬。\n\n然后就开始了一下午的视频面试，一共三轮技术面试，每一轮都要写代码，问问题的风格有点像腾讯，也喜欢问一些底层知识，让我有点懵逼。\n\n**如果想看更多公司的面经，也请移步微信公众号：程序员江湖。**\n\n* * *\n\n另外，我上周还面试了一次亚马逊，因为很多知名外企到十月才开始招人，所以闲了很久之后我又重操旧业了，可能在面完大摩和微软之后，秋招才能正式结束吧\n\n# 结束也是开始\n\n中秋节刚过，国庆节又要到来了。正如每一年的秋招一样，年复一年，在时间面前我们也是渺小的尘埃。\n\n秋招结束不代表着结局，而是新的旅程开始，马上，毕业论文，offer选择，入职准备，毕业旅行等事项也要提上日程了。\n\n不知道明年我们看待学弟学妹的秋招时，会是怎样的一种心境呢。\n\n\n# 微信公众号\n\n## 个人公众号：黄小斜\n\n黄小斜是跨考软件工程的 985 硕士，自学 Java 两年，拿到了 BAT 等近十家大厂 offer，从技术小白成长为阿里工程师。\n\n作者专注于 JAVA 后端技术栈，热衷于分享程序员干货、学习经验、求职心得和程序人生，目前黄小斜的CSDN博客有百万+访问量，知乎粉丝2W+，全网已有10W+读者。\n\n黄小斜是一个斜杠青年，坚持学习和写作，相信终身学习的力量，希望和更多的程序员交朋友，一起进步和成长！\n\n**原创电子书:**\n关注公众号【黄小斜】后回复【原创电子书】即可领取我原创的电子书《菜鸟程序员修炼手册：从技术小白到阿里巴巴Java工程师》\n\n**程序员3T技术学习资源：** 一些程序员学习技术的资源大礼包，关注公众号后，后台回复关键字 **“资料”** 即可免费无套路获取。\t\n\n**考研复习资料：** \n计算机考研大礼包，都是我自己考研复习时用的一些复习资料,包括公共课和专业的复习视频，这里也推荐给大家，关注公众号后，后台回复关键字 **“考研”** 即可免费获取。\t\n\n![](https://img-blog.csdnimg.cn/20190829222750556.jpg)\n\n\n## 技术公众号：Java技术江湖\n\n如果大家想要实时关注我更新的文章以及分享的干货的话，可以关注我的公众号【Java技术江湖】一位阿里 Java 工程师的技术小站，作者黄小斜，专注 Java 相关技术：SSM、SpringBoot、MySQL、分布式、中间件、集群、Linux、网络、多线程，偶尔讲点Docker、ELK，同时也分享技术干货和学习经验，致力于Java全栈开发！\n\n**Java工程师必备学习资源:** 一些Java工程师常用学习资源，关注公众号后，后台回复关键字 **“Java”** 即可免费无套路获取。\n\n![我的公众号](https://img-blog.csdnimg.cn/20190805090108984.jpg)\n"
  },
  {
    "path": "docs/hxx/java/为什么我会选择走 Java 这条路？.md",
    "content": "# 目录\n\n  * [谈谈我的技术方向选择](#谈谈我的技术方向选择)\n  * [谈谈各个技术方向的前景](#谈谈各个技术方向的前景)\n  * [研究生就应该做算法么？](#研究生就应该做算法么？)\n* [微信公众号](#微信公众号)\n  * [个人公众号：黄小斜](#个人公众号：黄小斜)\n  * [技术公众号：Java技术江湖](#技术公众号：java技术江湖)\n\n\n​\n\n阅读本文大概需要 2.8 分钟。\n\n最近有一些小伙伴问我，为什么当初选择走Java这条路，为什么不做C++、前端之类的方向呢，另外还有一些声音：研究生不是应该去做算法方向么，本科生不就可以做开发了吗，为什么还要读研呢。其实吧，这个问题搁在不同人身上，回答可能也是完全不一样的。我可能还是主要以我的角度出发，结合一些读者的问题，来说一说为什么要选择Java这条路。\n\n## 谈谈我的技术方向选择\n\n我最早接触的语言应该是c，再后来又接触了前端、php、C#等语言，对这些语言的了解也仅限于懂得基本语法，写过一些小demo而已，那时候觉得掌握一门语言就是掌握它的语法就行了，于是会在简历上写，了解各种语言，现在想想实在是太可笑了。\n\n不过真的很多初学者都会这么认为，觉得自己不管选哪个方向都可以，这是因为他们不知道自己的技术方向到底是什么，因为他们不管哪个方向都不精。\n\n后来慢慢接触了Android开发，能自己写一些app，觉得这个方向还挺有趣的，于是想着以后干脆做这个吧。不过我那时候也明白自己离进大厂还有很远的距离，于是打算考研。巧的是，读研前的那个假期，去了一家公司实习，做的是Java Web，那时候才觉得Android比Java Web简单多了呀，完全不需要学那么多框架、技术啊，于是更坚定了做Android的决心，只不过那时候也同样发现了，Java web是更加热门的方向，岗位需求量也更大。\n\n读研的时候，一开始也是打算做Android方向的，买了各类Android书籍开始啃，那时候刚好遇到了个实践课程，让我们选方向，有Java、Android、C++等等。当时妹子做前端，为了带上妹子一起做项目，干脆就选了Java Web这个方向，想着应该不影响我做Android啊，于是接下来的时间里就开始学Java Web了。\n\n时间一长，发现这个方向也没有想象的那么难，相反还挺有意思的，毕竟能用到各种各样的框架，技术栈的内容也很丰富，看起来好像比Android的技术含量高很多，加上自己之前学过Java，也有Java Web的学习经历，简历上也能写的好看一点，于是一不做二不休，就开始做Java方向了。就这样，我找到了第一份Java实习，看了一遍Java后端书单，慢慢地在这条路上越走越远，后来我才发现，Java Web远没有想象中那么简单，Java后端技术栈也远不止Java Web这点内容，特别是对于大厂来说，要掌握的东西实在太多了，比如分布式、网络编程、中间件等等。\n\n所以，选择方向这件事，有时候就是看兴趣，看机遇，看你能坚持多久，如果你对一个方向感兴趣，并且愿意持续学习，不断深挖，这个方向可能就适合你，当你在这个方向投入了一定时间之后，有了一定积累和经验，就不太容易再改变方向了。\n\n## 谈谈各个技术方向的前景\n\n之前也有很多读者问过这个问题，做哪个方向更有前景，更有钱景。虽然我只做过一个方向，但是对其他方向也有一些了解和涉猎，不敢说了解得非常多，但是结合自己身边的同学、朋友的情况，还是可以给出一些比较中肯的建议。\n\n其实我最早打算做的是游戏开发的，所以我们先聊聊游戏开发这个方向。\n\n网易游戏在前几年对游戏开发的招聘要求是985硕士以上学历，当时我就是冲着这个要求考了研，后来却没有去做游戏开发，主要原因是游戏开发主要用的是C++，并且主要的岗位要求是客户端方向的技术，比如图形学、引擎技术，以及对C++的掌握程度。\n\n当时自认为有一些Java基础，不愿意转C++，同时也感觉游戏行业大厂太少了，除了腾讯网易就没有什么大公司了，职业发展的空间可能也不大。自己虽然爱玩游戏，但是做开发和玩游戏毕竟是两码事。当然，近两年游戏开发的岗位需求其实还是很大的，因为现在做游戏开发的人太少了，导致网易游\n\n戏放宽了研发工程师的标准，只要求211以上即可，所以，想要从事游戏开发的朋友，其实现在进大厂的机会可能比之前更多了。\n\n说完游戏开发，说一说C++，C++方向和Java一样主要是做后端的，虽然游戏开发大部分也用的是C++，但是C++服务端的需求量确实没有Java大，加上C++的学习难度稍微搞一点，所以我没有选择这个方向。\n\n当然，现在做CV等算法方向的同学都会用到C++，所以相对Java来说，C++方向选择岗位的范围可能也更多一些。不过，正如Java也能做大数据开发一样，选择方向并不是选择语言，比如你做游戏开发或者算法方向，要学的远不止C++，做大数据方向，Java也只是很小的一部分而已。\n\n除此之外，前端、测试、移动端等方向也有很多机会，这些方向的学习难度可能要稍微简单那么一点，所以有很多女生会选择这些方向，如果你想进大厂却对自己不是很有信心，那么这些方向也是很不错的选择。\n\n## 研究生就应该做算法么？\n\n再聊聊现在很火的人工智能、机器学习方向，这个方向说实话最难的地方在于理论知识，也就是机器学习理论、算法模型、统计学知识等内容。很多人对这个方向趋之若鹜的原因，很大程度上是因为这个方向的薪资高，并且相对工程方向来说，工作强度要低一些。\n\n但是，这么热门的方向，竞争有多激烈就不用多说了，大厂的算法岗简历多到数不胜数，你没有论文、实习经历或者比赛为你背书，基本上连简历筛选都过不去，就算你的简历很漂亮，但是很多时候由于岗位需求量不多，只要你不是特别优秀，就可能被安排到研发岗位，这也是我身边很多同学亲身经历的。再有一点，就是有很多算法方向的博士毕业生也会和你竞争，这就有点吓人了，总之，算法方向还是比研发方向更加有难度的，不管是学习难度、面试难度，还竞争激烈程度，都更加明显。\n\n很多人觉得读研就应该做算法，本科生才做研发，我对此不敢苟同，因为主要还是还看个人实际情况，如果你想进大厂，那么至少本科的时候就要有很扎实的基础实力，这对大部分同学来说都是比较困难的，如果你不是名校出身，我觉得进大厂的难度还是比较大的。\n\n很多名校背景的本科生确实可以拿到大厂的研发offer，于是他们会觉得没必要读研，但是对于我这种跨专业的人来说，研究生才是我开始的第一步，跟他们没有什么可比性，所以对于从零开始的我来说，做研发比算法要靠谱的多，对于很多要转行做程序员的人来说，也是一样的道理，应该选择更加符合自己实力的岗位方向，不要好高骛远。\n\n以上内容纯属个人观点！\n\n\n# 微信公众号\n\n## 个人公众号：黄小斜\n\n黄小斜是跨考软件工程的 985 硕士，自学 Java 两年，拿到了 BAT 等近十家大厂 offer，从技术小白成长为阿里工程师。\n\n作者专注于 JAVA 后端技术栈，热衷于分享程序员干货、学习经验、求职心得和程序人生，目前黄小斜的CSDN博客有百万+访问量，知乎粉丝2W+，全网已有10W+读者。\n\n黄小斜是一个斜杠青年，坚持学习和写作，相信终身学习的力量，希望和更多的程序员交朋友，一起进步和成长！\n\n**原创电子书:**\n关注公众号【黄小斜】后回复【原创电子书】即可领取我原创的电子书《菜鸟程序员修炼手册：从技术小白到阿里巴巴Java工程师》\n\n**程序员3T技术学习资源：** 一些程序员学习技术的资源大礼包，关注公众号后，后台回复关键字 **“资料”** 即可免费无套路获取。\t\n\n**考研复习资料：** \n计算机考研大礼包，都是我自己考研复习时用的一些复习资料,包括公共课和专业的复习视频，这里也推荐给大家，关注公众号后，后台回复关键字 **“考研”** 即可免费获取。\t\n\n![](https://img-blog.csdnimg.cn/20190829222750556.jpg)\n\n\n## 技术公众号：Java技术江湖\n\n如果大家想要实时关注我更新的文章以及分享的干货的话，可以关注我的公众号【Java技术江湖】一位阿里 Java 工程师的技术小站，作者黄小斜，专注 Java 相关技术：SSM、SpringBoot、MySQL、分布式、中间件、集群、Linux、网络、多线程，偶尔讲点Docker、ELK，同时也分享技术干货和学习经验，致力于Java全栈开发！\n\n**Java工程师必备学习资源:** 一些Java工程师常用学习资源，关注公众号后，后台回复关键字 **“Java”** 即可免费无套路获取。\n\n![我的公众号](https://img-blog.csdnimg.cn/20190805090108984.jpg)\n"
  },
  {
    "path": "docs/hxx/java/你不可错过的Java学习资源清单.md",
    "content": "# 目录\n\n  * [Java入门学习资源](#java入门学习资源)\n  * [Java后端技术专栏](#java后端技术专栏)\n  * [Java工程师书单](#java工程师书单)\n  * [技术社区推荐](#技术社区推荐)\n  * [技术大牛推荐](#技术大牛推荐)\n* [微信公众号](#微信公众号)\n  * [个人公众号：黄小斜](#个人公众号：黄小斜)\n  * [技术公众号：Java技术江湖](#技术公众号：java技术江湖)\n\n\n![](https://picturecdn.8qwe5.com/18532be2f2284a96b0407e5b21cd525a.webp)\n\n学习Java和其他技术的资源其实非常多，但是我们需要取其精华去其糟粕，选择那些最好的，最适合我们的，同时也要由浅入深，先易后难。基于这样的一个标准，我在这里为大家提供一份Java的学习资源清单。\n\n## Java入门学习资源\n\n这里主要推荐的是几个技术学习网站，基本上都是视频学习资源。\n\n1 慕课网慕课网是做得比较好的程序员学习网站了。里面主要提供的是视频学习资源，主要适用于入门，当然其中也有一些进阶的内容，不过一般都是收费的。\n\n2 极客学院极客学院是我最早用于视频学习的网站，当时主要是听室友推荐，看了一些之后发现确实还不错。不过比起慕课网，极客学院的内容可能少一点。\n\n3 w3cSchool这个想必不说大家也知道，最适合入门的学习网站之一，有很多的学习资源，但是也只适合入门，你可以在一天内看完一门语言或技术，大概了解怎么使用。\n\n4 中国MOOC以前我以为这个和慕课网一样，后来发现这个网站主要是做教育资源的，更像是在学校里上课，里面的很多资源都是高校老师提供的，所以想学习一些计算机基础理论知识可以看看这个网站。\n\n5 网易云课堂&腾讯课堂这两个网站大家也都知道，只不过他们不是专门做IT学习资源的，相对来说这方面的学习资源也会比较少一点。\n\n## Java后端技术专栏\n\n对于校园招聘来说，最重要的还是基础知识。下面的博客专栏出自我的技术博客：\n\nhttps://blog.csdn.net/a724888\n\n这些专栏中有一些文章是我自己原创的，也有一些文章是转载自技术大牛的，基本都是是我在学习Java后端的两年时间内陆续完成的。\n\n总的来说算是比较全面了，做后端方向的同学可以参考一下。\n\n深入浅出Java核心技术\n\nhttps://blog.csdn.net/column/details/21930.html\n\n本专栏主要介绍Java基础，并且会结合实现原理以及具体实例来讲解。同时还介绍了Java集合类，设计模式以及Java8的相关知识。\n\n深入理解JVM虚拟机\n\nhttps://blog.csdn.net/column/details/21960.html\n\n带你走进JVM的世界，整合高质量文章以阐述虚拟机的原理及相关技术，让开发者更好地了解Java的底层运行原理以及相应的调优方法。\n\nJava并发指南\n\nhttps://blog.csdn.net/column/details/21961.html\n\n本专栏主要介绍Java并发编程相关的基本原理以及进阶知识。主要包括Java多线程基础，Java并发编程基本原理以及JUC并发包的使用和源码解析。\n\nJava网络编程与NIO\n\nhttps://blog.csdn.net/column/details/21963.html\n\nJava网络编程一直是很重要的一部分内容，其中涉及了socket的使用，以及Java网络编程的IO模型，譬如BIO,NIO,AIO，当然也包括Linux的网络编程模型。\n\n了解这部分知识对于理解网络编程有很多帮助。另外还补充了两个涉及NIO的重要技术：Tomcat和Netty。\n\nJavaWeb技术世界\n\nhttps://blog.csdn.net/column/details/21850.html\n\n从这里开始打开去往JavaWeb世界的大门。什么是J2EE，什么是JavaWeb，以及这个生态中常用的一些技术：Maven，Spring，Tomcat，Junit，log4j等等。\n\n我们不仅要了解怎么使用它们，更要去了解它们为什么出现，其中一些技术的实现原理是什么。\n\nSpring与SpringMVC源码解析\n\nhttps://blog.csdn.net/column/details/21851.html\n\n本专栏主要讲解Spring和SpringMVC的实现原理。 Spring是最流行的Java框架之一。\n\n本专栏文章主要包括IOC的实现原理分析，AOP的实现原理分析，事务的实现源码分析等，当然也有SpringMVC的源码解析文章。\n\n重新学习MySQL与Redis\n\nhttps://blog.csdn.net/column/details/21877.html\n\n本专栏介绍MySQL的基本知识，比如基本架构，存储引擎，索引原理，主从复制，事务等内容。当然也会讲解一些和sql语句优化有关的知识。\n\n同时本专栏里也介绍了Redis的基本实现原理，包括数据结构，主从复制，集群方案，分布式锁等实现。\n\n分布式系统理论与实践\n\nhttps://blog.csdn.net/column/details/24090.html\n\n本专栏介绍分布式的基本理论和相关技术，比如CAP和BASE理论，一致性算法，以及ZooKeeper这类的分布式协调服务。\n\n在分布式实践方面，我们会讲到负载均衡，缓存，分布式事务，分布式锁，以及Dubbo这样的微服务，也包括消息队列，数据库中间件等等。\n\n后端技术杂谈\n\nhttps://blog.csdn.net/column/details/25481.html\n\n本专栏涵盖了大后端的众多技术文章，当你在Java后端方面有一定基础以后，再多了解一些相关技术总是有好处的。\n\n除了Java后端的文章以外，还会涉及Hadoop生态，云计算技术，搜索引擎，甚至包括一些数据挖掘和AI的文章。\n\n总的来说选取了一些不错的基础类文章，能让你对大后端有一个更直观的认识。\n\n## Java工程师书单\n\n我之前专门写了一篇文章介绍了Java工程师的书单，可以这里重点列举一些好书，推荐给大家。\n\n完整内容可以参考这篇文章：\n\nJava工程师必备书单\n\n《计算机网络：自顶向下》这本从应用层讲到物理层，感觉这种方式学起来更轻松。\n\n《图解算法》《啊哈算法》\n\n这两部书籍非常适合学习算法的入门，前者主要用图解的形式覆盖了大部分常用算法，包括dp，贪心等等，可以作为入门书，后者则把很多常用算法都进行了实现，包括搜索，图，树等一些比较高级的常用算法。\n\n《剑指offer》这本书还是要强烈推荐的，毕竟是面试题经常参考的书籍，当然最好有前面基本的铺垫再看，可能收获更大，这本书在面试之前一般都要嚼烂。如果想看Java版本的代码，可以到我的Github仓库中查看。\n\n《Java编程思想》这本书也是被誉为Java神书的存在了，但是对新手不友好，适合有些基础再看，当然要选择性地看。我当时大概只看了1/3\n\n《Java核心技术卷一》 这本书还是比较适合入门的，当然，这种厚皮书要看完还是很有难度的，不过比起上面那本要简单一些\n\n《深入理解JVM虚拟机》 这本书是Java开发者必须看的书，很多jvm的文章都是提取这本书的内容。JVM是Java虚拟机，赋予了Java程序生命，所以好好看看把，我自己就已经看了三遍了。\n\n《Java并发编程艺术》 这本书是国内作者写的Java并发书籍，比上面那一本更简单易懂，适合作为并发编程的入门书籍，当然，学习并发原理之前，还是先把Java的多线程搞懂吧。\n\n《深入JavaWeb技术内幕》 这本书是Java Web的集大成之作，涵盖了大部分Java Web开发的知识点，不过一本书显然无法把所有细节都讲完，但是作为Java Web的入门或者进阶书籍来看的话还是很不错的。\n\n《Redis设计与实现》 该书全面而完整地讲解了 Redis 的内部运行机制,对 Redis 的大多数单机功能以及所有多机功能的实现原理进行了介绍。这本书把Redis的基本原理讲的一清二楚，包括数据结构，持久化，集群等内容，有空应该看看。\n\n《大型网站技术架构》 这本淘宝系技术指南还是非常值得推崇的，可以说是把大型网站的现代架构进行了一次简单的总结，内容涵盖了各方面，主要讲的是概念，很适合没接触过架构的同学入门。看完以后你会觉得后端技术原来这么博大精深。\n\n《分布式服务框架原理与实践》 上面那本书讲的是分布式架构的实践，而这本书更专注于分布式服务的原理讲解和对应实践，很好地讲述了分布式服务的基本概念，相关技术，以及解决方案等，对于想要学习分布式服务框架的同学来说是本好书。\n\n《从Paxos到Zookeeper分布式一致性原理与实践》 说起分布式系统，我们需要了解它的原理，相关理论及技术，这本书也是从这个角度出发，讲解了分布式系统的一些常用概念，并且带出了分布式一哥zookeeper，可以说是想学分布式技术的同学必看的书籍。\n\n《大数据技术原理与应用》 作为大数据方面的一本教材，厦大教授写的这本书还是非常赞的，从最基础的原理方面讲解了Hadoop的生态系统，并且把每个组件的原理都讲得比较清楚，另外也加入了spark，storm等内容，可以说是大数据入门非常好的一本书了。\n\n## 技术社区推荐\n\n学习Java后端两年的时间里，接触过很多的资料，网站和课程，也走了不少弯路，所以这里也总结一些比较好的资源推荐给大家。\n\n0 CSDN和博客园主流的技术交流平台，虽然广告越打越多了，但是还是有很多不错的博文的。\n\n1 importnew 专注Java学习资源分享，适合Java初学者。\n\n2 并发编程网主要分享Java相关进阶内容，适合Java提高。\n\n3 推酷 一个不错的技术分享社区。\n\n4 segmentfault有点像国内的Stack Overflow，适合交流代码问题的地方。\n\n5 掘金一个很有极客范的技术社区，强推，有很多技术大牛分享优质文章。\n\n6 开发者头条一个整合优质技术博客的社区，里面基本上都是精选的高质量博文，适合技术学习提升。\n\n7 v2ex一个极客社区，除了交流技术以外还会有很多和程序员生活相关的话题分享。\n\n8 知乎这个就不必多说了。我在知乎上也有Java技术和校招的专栏，有兴趣的同学可以看看：\n\nhttps://www.zhihu.com/people/h2pl\n\n9 简书简书上有些技术文章也很不错，有空大家也可以去看看。\n\n10 Github\n\n有一些GitHub的项目还是非常不错的，其中也有仓库会分享技术文章。\n\n我的GitHub：https://github.com/h2pl\n\n## 技术大牛推荐\n\n1 江南白衣这位大大绝对是我的Java启蒙导师，他推荐的Java后端书架让我受益匪浅。\n\n2 码农翻身刘欣，一位工作15年的IBM架构师，用最浅显易懂的文章讲解技术的那些事，力荐，他的文章帮我解决了很多困惑。\n\n3 CoolShell陈皓老师的博客相信大家都看过，干货很多，酷壳应该算是国内最有影响力的个人博客了。\n\n4 廖雪峰学习Git和Python，看它的博客就够了。\n\n5 HollisChuang阿里一位研发大佬的博客，主要分享Java技术文章，内容还不错。\n\n6 梁桂钊阿里另一位研发大佬，博客里的后端技术文章非常丰富。\n\n7 chenssy这位大佬分享的Java技术文章也很多，并且有很多基础方面的文章，新手可以多看看。\n\n8 Java Doop一位魔都Java开发者的技术博客，里面有一些不错的讲解源码的文章，数量不是很多，但是质量都挺不错的。\n\n\n# 微信公众号\n\n## 个人公众号：黄小斜\n\n黄小斜是跨考软件工程的 985 硕士，自学 Java 两年，拿到了 BAT 等近十家大厂 offer，从技术小白成长为阿里工程师。\n\n作者专注于 JAVA 后端技术栈，热衷于分享程序员干货、学习经验、求职心得和程序人生，目前黄小斜的CSDN博客有百万+访问量，知乎粉丝2W+，全网已有10W+读者。\n\n黄小斜是一个斜杠青年，坚持学习和写作，相信终身学习的力量，希望和更多的程序员交朋友，一起进步和成长！\n\n**原创电子书:**\n关注公众号【黄小斜】后回复【原创电子书】即可领取我原创的电子书《菜鸟程序员修炼手册：从技术小白到阿里巴巴Java工程师》\n\n**程序员3T技术学习资源：** 一些程序员学习技术的资源大礼包，关注公众号后，后台回复关键字 **“资料”** 即可免费无套路获取。\t\n\n**考研复习资料：** \n计算机考研大礼包，都是我自己考研复习时用的一些复习资料,包括公共课和专业的复习视频，这里也推荐给大家，关注公众号后，后台回复关键字 **“考研”** 即可免费获取。\t\n\n![](https://img-blog.csdnimg.cn/20190829222750556.jpg)\n\n\n## 技术公众号：Java技术江湖\n\n如果大家想要实时关注我更新的文章以及分享的干货的话，可以关注我的公众号【Java技术江湖】一位阿里 Java 工程师的技术小站，作者黄小斜，专注 Java 相关技术：SSM、SpringBoot、MySQL、分布式、中间件、集群、Linux、网络、多线程，偶尔讲点Docker、ELK，同时也分享技术干货和学习经验，致力于Java全栈开发！\n\n**Java工程师必备学习资源:** 一些Java工程师常用学习资源，关注公众号后，后台回复关键字 **“Java”** 即可免费无套路获取。\n\n![我的公众号](https://img-blog.csdnimg.cn/20190805090108984.jpg)\n"
  },
  {
    "path": "docs/hxx/java/想了解Java后端学习路线？你只需要这一张图！.md",
    "content": "# 目录\n\n* [![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPNiac1cDGFicIqDTr2PoAq2jmvD3JnkUOiaBzxgZDjh9pcicS0rCKcGaWvGVnm7ZH2LvlODKAtzANlDZg/640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1)](#[]httpsmmbizqpiccnmmbiz_jpgxhh0sksqzpniac1cdgficiqdtr2poaq2jmvd3jnkuoiabzxgzdjh9pcics0rckcgawvgvnm7zh2lvlodkatzanldzg640wx_fmtjpegtpwebpwxfrom5wx_lazy1wx_co1)\n* [前言](#前言)\n* [1 计算机基础](#1-计算机基础)\n* [2 Java编程](#2-java编程)\n  * [Java基础](#java基础)\n  * [设计模式](#设计模式)\n  * [Java Web技术](#java-web技术)\n  * [Java并发技术](#java并发技术)\n  * [Java网络编程和服务器](#java网络编程和服务器)\n  * [Jvm基础与调优](#jvm基础与调优)\n* [3 Linux](#3-linux)\n* [4 数据相关](#4-数据相关)\n  * [关系数据库Mysql](#关系数据库mysql)\n  * [缓存](#缓存)\n  * [搜索引擎](#搜索引擎)\n  * [大数据](#大数据)\n* [6 分布式](#6-分布式)\n  * [web架构](#web架构)\n  * [分布式理论](#分布式理论)\n  * [一致性问题](#一致性问题)\n  * [分布式session](#分布式session)\n  * [分布式缓存](#分布式缓存)\n  * [分布式数据库](#分布式数据库)\n  * [负载均衡](#负载均衡)\n  * [消息队列](#消息队列)\n  * [服务化](#服务化)\n  * [虚拟化](#虚拟化)\n* [微信公众号](#微信公众号)\n  * [个人公众号：黄小斜](#个人公众号：黄小斜)\n  * [技术公众号：Java技术江湖](#技术公众号：java技术江湖)\n\n\n# ![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPNiac1cDGFicIqDTr2PoAq2jmvD3JnkUOiaBzxgZDjh9pcicS0rCKcGaWvGVnm7ZH2LvlODKAtzANlDZg/640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1)\n\n# 前言\n\n学习路线图往往是学习一样技术的入门指南。网上搜到的Java学习路线图也是一抓一大把。\n\n今天我只选一张图，仅此一图，足以包罗Java后端技术的知识点。所谓不求最好，但求最全，学习Java后端的同学完全可以参考这张图进行学习路线安排。\n\n当然，有一些知识点是可选的，并不是说上面有的你都要会啦。我在复习秋招的过程中就是基于此图进行复习的，感觉效果还是不错的。\n\n**闲言少叙，我们还是看看这张图上都包含哪些有价值的信息吧。再次说明，本文只对路线图做一个简单地解读，仅供参考。大家可以根据自身情况来指定合理的学习计划，相信也会大有裨益。**\n\n**由于图片比较大，如果觉得看不清楚，可以点击原文链接查看原图。**\n\n![](https://mmbiz.qpic.cn/mmbiz_png/XHh0SksQZPNiac1cDGFicIqDTr2PoAq2jmyib9OqSqW30WicXhC1xmMM8YIzUheD2icN0TAyaxBt5pUfyOzk8ZIiahog/640?wx_fmt=png)\n\n\n\n# 1 计算机基础\n\n这部分内容是计算机相关专业同学的课程，但是非科班的小伙伴（譬如在下）就需要花时间恶补了。特别是计算机网络，操作系统，数据结构这三门课程。\n\n至于编译原理，个人大概懂一点就行了，我也只看过简单的概念和状态机相关的内容，并不是特别重要。\n\n# 2 Java编程\n\n这里的Java编程部分包含了很多内容。我们可以分别看看，大概归纳一下就是这几个部分。\n\n## Java基础\n\n这里的Java基础包括基本语法，集合类框架，以及一些高级特性，比如反射，注解等等。\n\nJava基础的知识点非常多，所以要真正搞懂也没有那么简单，另外，随着时间推移，一些新特性也需要得到我们的重视，比如时下流行的JDK8。\n\n## 设计模式\n\n我一直觉得设计模式可以和Java基础一块学，所以我也把它放在这里。当然，一些真正使用到设计模式的地方，譬如JDK的集合类，IO流等等，也需要你足够重视。\n\n## Java Web技术\n\nJava Web技术包括J2EE，以及web框架，乃至一系列常用的组件。\n\n1 J2EE主要包括的就是servlet，jsp这些比较复古的web开发组件了。虽然现在直接用它们的情况比较少，但是我们还是需要花一些时间去掌握它们的。\n\n2 web框架常用的就是Spring了，相应的，hibernate和mybatis也需要了解一下。\n\n3 同时，JavaWeb开发时的常用类库，比如jnuit单元测试，log4j日志工具，以及构建工具maven，都属于我们要掌握的范畴。\n\n4 最后，要注意的是，Web相关的一些基本知识，比如HTTP协议，网络安全基础，也是我们要考虑的部分。\n\n## Java并发技术\n\nJava的并发技术泛指Java的多线程技术，以及JUC包里的并发类，比如线程池，并发工具类，阻塞队列等等。\n\nJava并发技术完全可以独立出来学习，是Java进阶知识的一大重点。\n\n## Java网络编程和服务器\n\n这一块内容是Java中比较复杂但也很重要的一块内容。比如BIO,NIO,AIO的一些使用和原理，以及tomcat这类web服务器，甚至是netty这种网络编程框架，都是可以去了解和学习的内容。\n\n## Jvm基础与调优\n\nJVM是提供Java程序运行的一个进程，学习JVM知识，也是我们的必经之路。除了看懂《深入理解jvm虚拟机》以外，我们还要学习的内容就是JVM调优，使用合适的工具诊断问题，最后解决问题。\n\n这部分内容在面试中呈现的不仅仅是GC,内存分区，以及类加载器，也包括了我所说的JVM调优问题。\n\n# 3 Linux\n\n作为后台同学，常年被面试官问linux相关的问题，所以觉得学好linux还是蛮重要的，除了基本命令以外，最好还能了解一些shell脚本，甚至是内核相关的知识，这方面是我的一个弱项。\n\n# 4 数据相关\n\n在这个路线图里，数据部分囊括了非常多的数据源，我们可以来看看都有哪些是我们需要掌握的。\n\n## 关系数据库Mysql\n\n这个不必多说，人手都要会，不管是基础的crud，索引，抑或是进阶的存储引擎，分布式方案等，我们都需要对应掌握。\n\n## 缓存\n\n如Redis，memcache一类的缓存，作为后端开发者的我们也需要对应掌握，当然，它们的高级特性，以及分布式方案，也是必备的知识点。\n\n## 搜索引擎\n\n基于Lucene的solr，elasticsearch这类搜索引擎，本质上也是数据源，但是并不是后端必备的内容，不过学一学也没有坏处啦。\n\n## 大数据\n\n海量数据处理的场景越来越多，大数据技术如hadoop，storm等也越来越火，但是大数据应用一般会由专业的大数据工程师来做，所以我们学一些基本内容也就足够了。\n\n  \n\n5 算法和数据结构\n\n算法一直是校招同学面前的一座大山，作为后端同学来讲，除了基本的数据结构算法以外，也要会一些高级的算法，譬如dp，搜索，贪心等等。\n\n另外，通过LeetCode等题库来刷题的方式来巩固算法也是公认的好办法了。\n\n# 6 分布式\n\n最后一个部分，也是内容最多，覆盖面最广泛的部分了。分布式相关的技术实在太多了，我们这里也会做一下简单的归纳。\n\n## web架构\n\n先了解web架构的发展会对分布式的学习有更好的理解，毕竟架构的发展也对应着分布式技术的发展。\n\n## 分布式理论\n\n这部分内容包括分布式的发展演化，base理论和cap理论等等，学习分布式技术之前，最好能对这部分概念有一定了解。\n\n## 一致性问题\n\n强一致性的解决方案：事务和锁，弱一致性的方案：消息队列。\n\n## 分布式session\n\n一个常见的问题，也有多种解决方案\n\n## 分布式缓存\n\n和上面说的缓存一样，只不过这里侧重缓存的分布式方案\n\n## 分布式数据库\n\n这里指的数据库的分布式方案，也包括hbase这种分布式数据库。\n\n## 负载均衡\n\n负载均衡也是一个值得探讨的话题，一般我们讨论的是七层和四层负载均衡。\n\n## 消息队列\n\n消息队列是一个比较复杂的分布式组件，我们可以了解常用消息队列比如amq，kafka等的实现。\n\n## 服务化\n\n服务化的核心包括rpc，服务注册中心等等。分布式服务相关技术也是后端同学必须掌握的内容。\n\n## 虚拟化\n\n虚拟化同样不是后端同学必须掌握的内容，只不过现在越来越多的服务部署方式使用的是docker和云服务的方式。所以了解一下也没有什么不好的。\n\n\n# 微信公众号\n\n## 个人公众号：黄小斜\n\n黄小斜是跨考软件工程的 985 硕士，自学 Java 两年，拿到了 BAT 等近十家大厂 offer，从技术小白成长为阿里工程师。\n\n作者专注于 JAVA 后端技术栈，热衷于分享程序员干货、学习经验、求职心得和程序人生，目前黄小斜的CSDN博客有百万+访问量，知乎粉丝2W+，全网已有10W+读者。\n\n黄小斜是一个斜杠青年，坚持学习和写作，相信终身学习的力量，希望和更多的程序员交朋友，一起进步和成长！\n\n**原创电子书:**\n关注公众号【黄小斜】后回复【原创电子书】即可领取我原创的电子书《菜鸟程序员修炼手册：从技术小白到阿里巴巴Java工程师》\n\n**程序员3T技术学习资源：** 一些程序员学习技术的资源大礼包，关注公众号后，后台回复关键字 **“资料”** 即可免费无套路获取。\t\n\n**考研复习资料：** \n计算机考研大礼包，都是我自己考研复习时用的一些复习资料,包括公共课和专业的复习视频，这里也推荐给大家，关注公众号后，后台回复关键字 **“考研”** 即可免费获取。\t\n\n![](https://img-blog.csdnimg.cn/20190829222750556.jpg)\n\n\n## 技术公众号：Java技术江湖\n\n如果大家想要实时关注我更新的文章以及分享的干货的话，可以关注我的公众号【Java技术江湖】一位阿里 Java 工程师的技术小站，作者黄小斜，专注 Java 相关技术：SSM、SpringBoot、MySQL、分布式、中间件、集群、Linux、网络、多线程，偶尔讲点Docker、ELK，同时也分享技术干货和学习经验，致力于Java全栈开发！\n\n**Java工程师必备学习资源:** 一些Java工程师常用学习资源，关注公众号后，后台回复关键字 **“Java”** 即可免费无套路获取。\n\n![我的公众号](https://img-blog.csdnimg.cn/20190805090108984.jpg)\n"
  },
  {
    "path": "docs/hxx/java/我的Java秋招面经大合集.md",
    "content": "# 目录\n\n* [阿里面经](#阿里面经)\n* [腾讯面经](#腾讯面经)\n* [百度面经](#百度面经)\n* [网易面经](#网易面经)\n* [头条面经](#头条面经)\n* [快手&拼多多面经](#快手拼多多面经)\n* [京东&美团面经](#京东美团面经)\n* [斗鱼面经](#斗鱼面经)\n* [有赞面经](#有赞面经)\n* [华为&深信服等面经](#华为深信服等面经)\n* [海康&商汤等面经](#海康商汤等面经)\n* [携程&拼多多面经](#携程拼多多面经)\n* [微信公众号](#微信公众号)\n  * [个人公众号：黄小斜](#个人公众号：黄小斜)\n  * [技术公众号：Java技术江湖](#技术公众号：java技术江湖)\n\n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/XHh0SksQZPPAdahlOcBvxxLrx0ibYleGQvVONWia2JjwyBDoUibkYrcm3viapMOCBPehbX6eOxWSHNAa8TYTqj2ibYQ/640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1)\n\n\n# 阿里面经\n\n[阿里中间件研发面经](http://mp.weixin.qq.com/s?__biz=MzUyMDc5MTYxNA==&mid=2247483852&idx=1&sn=9ec90620478b35d63a4a971a2222095d&chksm=f9e5b29dce923b8b2b080151e4b6b78373ee3f3e6f75b69252fc00b1a009d85c6f96944ed6e6&scene=21#wechat_redirect)\n\n[蚂蚁金服研发面经](http://mp.weixin.qq.com/s?__biz=MzUyMDc5MTYxNA==&mid=2247483861&idx=1&sn=34317917908fdb778f16fa9dd557908b&chksm=f9e5b284ce923b922446fb5431b84094dec03ee4688da9c8de1f025d80eac715cbd9740e8464&scene=21#wechat_redirect)\n\n岗位是研发工程师，直接找蚂蚁金服的大佬进行内推。\n\n我参与了阿里巴巴中间件部门的提前批面试，一共经历了四次面试，拿到了口头offer。\n\n然后我也参加了蚂蚁金服中间件部门的面试，经历了三次面试，但是没有走流程，所以面试中止了。\n\n最后我走的是蚂蚁金服财富事业群的流程，经历了四次面试，包括一次交叉面，最终拿到了蚂蚁金服的意向书，评级为A。\n\n阿里的面试体验还是比较好的，至少不要求手写算法，但是非常注重Java基础，中间件部门还会特别安排Java基础笔试。\n\n# 腾讯面经\n\n[腾讯研发面经](http://mp.weixin.qq.com/s?__biz=MzUyMDc5MTYxNA==&mid=2247483849&idx=1&sn=f81fd42954589fb2deaf128026ddd856&chksm=f9e5b298ce923b8ef02ae36f7e9029fef0ddb7d5ae456dfa9d64c0073bebaacfb78fac4c8035&scene=21#wechat_redirect)\n\n岗位是后台开发工程师，我没有选择意向事业群。\n\nSNG的部门捞了我的简历，开始了面试，他们的技术栈主要是Java，所以比较有的聊。\n\n一共经历了四次技术面试和一次HR面试，目前正在等待结果。\n\n腾讯的面试一如既往地注重考查网络和操作系统，并且喜欢问Linux底层的一些知识，在这方面我还是有很多不足的。\n\n# 百度面经\n\n[百度研发面经](http://mp.weixin.qq.com/s?__biz=MzUyMDc5MTYxNA==&mid=2247483857&idx=1&sn=214b0f93db93407a7ac5a5149778cbad&chksm=f9e5b280ce923b96fcd535b2ef639fee2de78f12aa961d525b21760b11a3b95c0879113c2944&scene=21#wechat_redirect)\n\n[百度研发面经整合版](http://mp.weixin.qq.com/s?__biz=MzUyMDc5MTYxNA==&mid=2247483866&idx=1&sn=88dc80fef6ad6aa3a4221862f3630a90&chksm=f9e5b28bce923b9d4defc91ed30d8bbe650d7215966f9fb776fe5b244eae527bcd24b1941204&scene=21#wechat_redirect)\n\n岗位是研发工程师岗位，部门包括百度智能云的三个分部门以及大搜索部门。\n\n百度的提前批面试不走流程，所以可以同时面试好多个部门，所以我参加百度面试的次数大概有12次左右，最终应该是拿了两个部门的offer。\n\n百度的面试风格非常统一，每次面试基本都要到电脑上写算法，所以那段时间写算法写的头皮发麻。\n\n# 网易面经\n\n[网易研发面经](http://mp.weixin.qq.com/s?__biz=MzUyMDc5MTYxNA==&mid=2247483875&idx=1&sn=fa9eaedd9cc3da172ad71d360c46a054&chksm=f9e5b2b2ce923ba443b91d56b24486d22b15bea16a4788e5ed3421906e84f8edd9ee10b2b306&scene=21#wechat_redirect)\n\n面试部门是网易云音乐，岗位是Java开发工程师。\n\n网易是唯一一家我去外地面试的公司，也是我最早去实习的老东家。\n\n一共三轮面试，耗时一个下午。\n\n网易的面试比我想象中的要难，面试官会问的问题都比较深，并且会让你写一些结合实践的代码。\n\n# 头条面经\n\n[今日头条研发面经](http://mp.weixin.qq.com/s?__biz=MzUyMDc5MTYxNA==&mid=2247483869&idx=1&sn=eedb7aebcb90cf3d4fe2450ef2d99947&chksm=f9e5b28cce923b9a9b0852a1c998ec014eb1aeb0442916c9028da276be8db0b2934c92292961&scene=21#wechat_redirect)\n\n岗位是后台研发工程师，地点选择了上海。\n\n我参加的是字节跳动的内推面试，当时找了一个牛友要到了白金码，再次感谢这位头条大佬。\n\n然后就开始了一下午的视频面试，一共三轮技术面试，每一轮都要写代码，问问题的风格有点像腾讯，也喜欢问一些底层知识，让我有点懵逼。\n\n# 快手&拼多多面经\n\n[拼多多&快手研发面经](http://mp.weixin.qq.com/s?__biz=MzUyMDc5MTYxNA==&mid=2247483878&idx=1&sn=aaafff4b1171361ccd1d94708d2beaa0&chksm=f9e5b2b7ce923ba10140dff71dd3a0f9a20593434a32cec66929116bba196dc7496abf502884&scene=21#wechat_redirect)\n\n岗位是Java开发工程师，面试我的部门好像是基础架构部门。\n\n快手是两轮视频面试加上一轮hr面试。然后没下文了，ben\n\n拼多多的岗位是业务平台研发工程师。\n\n当时在学校里参加了面试，过程是比较顺利的，问的问题也都比较有难度。\n\n自我感觉良好，但是最后却收到了拒信，还是挺可惜的。\n\n# 京东&美团面经\n\n[京东&美团研发面经](http://mp.weixin.qq.com/s?__biz=MzUyMDc5MTYxNA==&mid=2247483881&idx=1&sn=62981cc5305e43ae83c5fe654bb657a8&chksm=f9e5b2b8ce923bae18685601fd2e065d49333a701b40c6b21e3bf176a5651031be16e1469649&scene=21#wechat_redirect)\n\n岗位是Java开发工程师\n\n京东和美团都是电话面试，京东是提前批，聊了两次，问我能不能去实习，我说不能，然后就没有下文了。\n\n美团也是提前批的电话面试，直接一面问了一个多小时，有几个问题没答好，直接挂了。后来正式批也没让我参加，可以说是非常真实了。\n\n# 斗鱼面经\n\n[斗鱼研发面经](http://mp.weixin.qq.com/s?__biz=MzUyMDc5MTYxNA==&mid=2247483884&idx=1&sn=b3a062e8396da16c0d0bc9bc336e819c&chksm=f9e5b2bdce923babbd5d1324a9fdf48c750c7fb7cddb872c788cb8c97e0afe9f4d7b8669d4cd&scene=21#wechat_redirect)\n\n岗位是Java开发工程师（大数据方向）\n\n刚好我人在武汉，于是斗鱼让我去想场面。\n\n大概花了一下午的时间结束所有流程，首先做了一个笔试，还算简单，然后是三轮面试，前两轮主要是技术，最后一轮总监面。\n\n总体来说面的还是不错的，但是没有回应，不太清楚啥原因。\n\n# 有赞面经\n\n[有赞研发面经（Java细节）](http://mp.weixin.qq.com/s?__biz=MzUyMDc5MTYxNA==&mid=2247483894&idx=1&sn=518424518c4f8669720991f3aad263cb&chksm=f9e5b2a7ce923bb1cf6512090fd13b0e86a6f01589bc21236448d22853bcb26f1901585570a6&scene=21#wechat_redirect)\n\n岗位依然是Java开发工程师\n\n当时是电话面试。\n\n有赞的面试出人意料地很有挑战性，问的都是Java细节，死抠知识点，没有一定准备要回答好还真是很有难度的。\n\n断断续续大概面了三面，后来我不想去现场面，就没了下文。\n\n# 华为&深信服等面经\n\n[华为 深信服等研发面经](http://mp.weixin.qq.com/s?__biz=MzUyMDc5MTYxNA==&mid=2247483898&idx=1&sn=cfa719f19a93daf778208cf10890d4b2&chksm=f9e5b2abce923bbd8a7f42a95d95a657a3de6bec7505036aa93112f046adaef28d5efee01934&scene=21#wechat_redirect)\n\n除了华为和深信服，里面还包含了美图，迅雷，猿辅导等小公司的面经。\n\n华为和深信服是大数据研发岗。其他是后端工程师的岗位。\n\n华为和深信服差不多，技术面试都比较水，所以放一起说。\n\n另外三家小公司的面试难度也差不多，不过最后都没有下文了，感觉也是挺玄学的哈哈。\n\n# 海康&商汤等面经\n\n[海康，商汤，顺丰等研发面经](http://mp.weixin.qq.com/s?__biz=MzUyMDc5MTYxNA==&mid=2247483901&idx=1&sn=63bf0b935ef71f15f87c04443dd7df58&chksm=f9e5b2acce923bba564094c6424ebb4b754fdb0fd238a5063222f03bb294ee8b886cd7277aa9&scene=21#wechat_redirect)\n\n岗位都是后台开发工程师。\n\n海康只经历了简历面，现场面没有去。 商汤也只是稍微聊了一下，就没有后续了。\n\n顺丰经过两面直接给offer。\n\n其中还包括亚马逊的实习生面经。\n\n另外最近面了亚马逊的秋招，感觉难度和实习的面试差不多。面经就不贴了，有兴趣的同学可以和我聊聊。\n\n# 携程&拼多多面经\n\n[携程&拼多多研发面经](http://mp.weixin.qq.com/s?__biz=MzUyMDc5MTYxNA==&mid=2247483904&idx=1&sn=426c39db6f7d86e7c23b6357bf42ee9b&chksm=f9e5b151ce9238475630a2ed9e865f685117205fb26301297c2954fc3a61f040dd60e7985818&scene=21#wechat_redirect)\n\n岗位是Java开发工程师。\n\n携程是武汉现场面，很搞笑的是，携程的面试题是不换的，我同学第二天去面题目一模一样。\n\n并且，携程总共只有一轮面试，真是勤俭节约。\n\n之前拼多多提前批折戟，这次又来参加正式批了。\n\n没想到这次面的更差了2333 有个算法题想半天了不会。于是面完三面草草收场。不得不吐槽一下，负责我们学校面试接待的hr，脾气真是有点大，搞得我都不敢问她问题了。\n\n以上就是本次秋招我整理的面经合集啦，喜欢的朋友可以点赞收藏哈。\n\n\n# 微信公众号\n\n## 个人公众号：黄小斜\n\n黄小斜是跨考软件工程的 985 硕士，自学 Java 两年，拿到了 BAT 等近十家大厂 offer，从技术小白成长为阿里工程师。\n\n作者专注于 JAVA 后端技术栈，热衷于分享程序员干货、学习经验、求职心得和程序人生，目前黄小斜的CSDN博客有百万+访问量，知乎粉丝2W+，全网已有10W+读者。\n\n黄小斜是一个斜杠青年，坚持学习和写作，相信终身学习的力量，希望和更多的程序员交朋友，一起进步和成长！\n\n**原创电子书:**\n关注公众号【黄小斜】后回复【原创电子书】即可领取我原创的电子书《菜鸟程序员修炼手册：从技术小白到阿里巴巴Java工程师》\n\n**程序员3T技术学习资源：** 一些程序员学习技术的资源大礼包，关注公众号后，后台回复关键字 **“资料”** 即可免费无套路获取。\t\n\n**考研复习资料：** \n计算机考研大礼包，都是我自己考研复习时用的一些复习资料,包括公共课和专业的复习视频，这里也推荐给大家，关注公众号后，后台回复关键字 **“考研”** 即可免费获取。\t\n\n![](https://img-blog.csdnimg.cn/20190829222750556.jpg)\n\n\n## 技术公众号：Java技术江湖\n\n如果大家想要实时关注我更新的文章以及分享的干货的话，可以关注我的公众号【Java技术江湖】一位阿里 Java 工程师的技术小站，作者黄小斜，专注 Java 相关技术：SSM、SpringBoot、MySQL、分布式、中间件、集群、Linux、网络、多线程，偶尔讲点Docker、ELK，同时也分享技术干货和学习经验，致力于Java全栈开发！\n\n**Java工程师必备学习资源:** 一些Java工程师常用学习资源，关注公众号后，后台回复关键字 **“Java”** 即可免费无套路获取。\n\n![我的公众号](https://img-blog.csdnimg.cn/20190805090108984.jpg)\n"
  },
  {
    "path": "docs/hxx/think/copy.md",
    "content": "# 目录\n\n* [微信公众号](#微信公众号)\n  * [个人公众号：黄小斜](#个人公众号：黄小斜)\n  * [技术公众号：Java技术江湖](#技术公众号：java技术江湖)\n\n\n\n新手程序员通常会走入一个误区，就是认为学习了一门语言，就可以称为是某某语言工程师了。但事实上真的是这样吗？其实并非如此。\n\n今天我们就来聊一聊，Java 开发工程师到底开发的是什么东西。准确点来说，Java后端到底在做什么？\n\n大家都知道 Java 是一门后端语言，后端指的就是服务端，服务端代码一般运行在服务器上，通常我们运行Java 程序的服务器都是 Linux 服务器。\n\n这些服务器在互联网公司中一般放在一个叫做机房的地方里，于是像我们这类 Java 程序员的代码一般也运行在这些机房里的服务器中。\n\n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/hbTNOSuicwludicNDAe8NpGXFQQicqI58NAueCNFVHoJfpGFS6DYuduPsqD3qOwJXyDW54NrIFrgOWW0tLXiaia74pw/640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1 \"标志，商店橱窗，商店橱窗，店面，模特，购物，衣服，服装店，透过窗户，帽子\")\n\n\n\nJava 里有一个概念叫做虚拟机，你可以把它理解为一个安卓的模拟器，比如你在电脑上装了一个安卓模拟器，就可以通过它来运行安卓应用程序，比如装个 APP，手机游戏什么的。\n\n所以当你在电脑上安装了一个叫做 JDK 的东西时，电脑里就有了 JRE 也就是 Java 运行环境，有了这个运行环境，你就可以运行 Java 应用程序了。\n\n知道 Java 程序如何运行在计算机上之后，我们再来讲一讲平时学的一些 Java 基础知识，它们到底有什么用？\n\n其实平时这一些 Java 基础语法都仅仅是你写代码的一些基础知识，就相当于英语中的 26 个字母，常见的有基本类型变量、for 循环、if else 等等基本语法，掌握了这些基础知识之后，你就可以上手写一些很简单的代码了。\n\n除此之外，Java 还有一些比较特别的概念，比如面向对象的特性，其中有类、接口等概念。为什么 Java 要引入这些东西呢，其实就是想让使用者更好地进行设计、抽象和编程。\n\n对于新手来说，你不需要理解得特别的深刻，因为这些东西只有你在你真正写代码之后才能逐步去理解。\n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/hbTNOSuicwlv4siawNIaW61ib1Hgcl0QUAhyuibdlVKPVSv1vwTicOPE5RfPnwVmYXraBkhh62nhGJpKDlibBrnrxEwg/640?wx_fmt=jpeg)\n\n说完基本知识之后，我想你也会好奇，Java里经常提到的一些集合类是干嘛的呢，因为在现实生活中有很多场景，需要用到集合类，比如说一个用户名列表，你要怎么存呢？\n\n你会用一个 List 来做对不对，所以集合类的作用就是让你在编程中更好的存储数据。\n\n事实上，集合类的概念最早是来源于数据结构的，因为计算机里有很多特殊的数据存储结构，比如文件树，比如链表和数组等结构，因此计算机理论把这些存储数据的模型抽象成一些常见的结构，统称为数据结构。\n\n那么，Java 中的并发编程又是做什么的呢，Java 中的多线程是为了更好地利用电脑中的CPU核心，通过并发编程，就可以提高程序并发的效率。\n\n但是并发编程的背后需要操作系统的支持，以及计算机硬件的支持，所以，如果你要完全地理解多线程，绝不仅仅是理解 Java 里的 Thread 或者是线程池就足够了，你还需要去理解操作系统，以及计算机组成原理。\n\n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/hbTNOSuicwludicNDAe8NpGXFQQicqI58NA3ickdvv1gpvXcfNrg54pP1eKyL7B2ia0JNTWMTcgeS624Zng0JXwXQGA/640?wx_fmt=jpeg \"辣椒，红辣椒，黄辣椒，蔬菜，杂货，戒指，农产品，红色，黄色，颜色，食物，手臂，手腕，手，烹饪，购物，健康，新鲜，饮食，食谱\")\n\n\n\n和并发编程类似，Java 里也有网络编程的概念，Java 里的网络编程和其他语言大同小异，其实也是基于 TCP/IP 协议实现的一套 API，通过网络编程，你就可以在程序中把你想传输的数据传输到网络的另一端，有了网络编程和并发编程之后，Java 程序员的能量已经很大了\n\n讲完这几点之后接下来再谈谈，我们通常说的 Java 后端技术到底是什么，就拿支付宝来举例吧，曾经的支付宝用户数并不多，一台服务器，一个数据库就可以支持所有的业务了。\n\n当支付宝的用户越来越多的时候，一台服务器无法同时满足海量用户的需求，于是开始出现了多台服务器，多台服务器组成了一个集群，用户可以通过负载均衡的方式访问这些服务器，每个用户可能会访问到不同的机器上，这样子就达到了分流的效果，服务器的压力就会减小。\n\n由于数据库需要保证数据的可靠性，万一某一台数据库挂了，并且没有备份的话，那么这个数据就无法访问了，这在大型系统中是不允许出现的，于是乎，就有了数据库的主从部署。\n\n但事实上，随着业务发展，数据库的压力也越来越大，主备部署并不能解决数据库访问性能的问题，于是乎我们需要进行分库分表，在数据库主备的基础上，我们会把一个数据量很大的表拆成多个表，并且把数据库请求分流到不同的数据上，比如说100个分库，100个分表，就相当于把一个数据表划分成10000个数据表。\n\n此时又出现一个问题，如果一个数据库有多个备库，并且当主库挂掉的时候需要进行主从切换时，主备数据库之间的数据就可能发生不一致，而这也是分布式理论研究的问题之一，因为比较复杂，我们这里就略过不讲。\n\n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/hbTNOSuicwludicNDAe8NpGXFQQicqI58NAtxStKDiaZCBGKia8QQOe6vaXpDdYFiaB57QpibAGod08UiaTna20OAGl7XQ/640?wx_fmt=jpeg \"肖像、女人、微笑、购物、口香糖、糖果、糖果、快乐、欢乐、笑声、微笑、高加索人、眼镜、手、携带、乐趣\")\n\n\n\n刚才说到了分布式技术，其实负载均衡、分库分表都是分布式技术的一种实现，如果你不想做分库分表，那还有什么办法能够减轻数据库访问的压力呢？于是缓存就出现了，缓存可以让服务器先把请求打到缓存上，由于缓存的数据一般在内存中，所以访问速度会非常快，这些请求无需经过数据库。\n\n随着业务发展，缓存的单点压力也会比较大，于是乎分布式缓存就出现了，通常来说，缓存难以保证数据的可靠性，因为它们的数据可能会丢失，同时缓存只能存储一部分的数据，并不能解决所有问题。\n\n所以当某些业务的请求量非常大的时候，光靠缓存也解决不了问题，此时我们还可以通过消息队列来帮我们解决大流量并发请求的问题。\n\n我们可以通过消息队列来存储一部分的请求消息，然后根据我们服务器处理请求的能力，把消息再逐步取出来，接着去把这些消息逐渐地进行处理，这样就可以很好的解决高并发的问题。当然，前提是消息队列要保证消息存储的可靠性，这也是大部分消息队列都会保证的能力。\n\n\n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/hbTNOSuicwludicNDAe8NpGXFQQicqI58NAjHeib3k5b9gY71NZO9SUUMDDmDhCXENX1b7NxG6WXBnTSnnnyaCMHFg/640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1 \"杂志，书堆，商店，商店，摊位，货架，纸张，杂志商店，杂志商店\")\n\n\n\n\n\n一口气讲了这么多，算是把 Java 后端的大概面貌介绍清楚了，除此之外还有很多东西没讲到，真要讲完的话一晚上也说不完。\n\n总体来说，Java 后端技术，说难不难说简单也不简单，我尽量把这些内容都讲的比较通俗易懂，事实上每项技术的背后都有特别多复杂的实现原理，当然，在你理解了 Java 后端技术的整体概念以后，相信对于你之后的学习会更有帮助。\n\n\n# 微信公众号\n\n## 个人公众号：黄小斜\n\n黄小斜是跨考软件工程的 985 硕士，自学 Java 两年，拿到了 BAT 等近十家大厂 offer，从技术小白成长为阿里工程师。\n\n作者专注于 JAVA 后端技术栈，热衷于分享程序员干货、学习经验、求职心得和程序人生，目前黄小斜的CSDN博客有百万+访问量，知乎粉丝2W+，全网已有10W+读者。\n\n黄小斜是一个斜杠青年，坚持学习和写作，相信终身学习的力量，希望和更多的程序员交朋友，一起进步和成长！\n\n**原创电子书:**\n关注公众号【黄小斜】后回复【原创电子书】即可领取我原创的电子书《菜鸟程序员修炼手册：从技术小白到阿里巴巴Java工程师》\n\n**程序员3T技术学习资源：** 一些程序员学习技术的资源大礼包，关注公众号后，后台回复关键字 **“资料”** 即可免费无套路获取。\t\n\n**考研复习资料：** \n计算机考研大礼包，都是我自己考研复习时用的一些复习资料,包括公共课和专业的复习视频，这里也推荐给大家，关注公众号后，后台回复关键字 **“考研”** 即可免费获取。\t\n\n![](https://img-blog.csdnimg.cn/20190829222750556.jpg)\n\n\n## 技术公众号：Java技术江湖\n\n如果大家想要实时关注我更新的文章以及分享的干货的话，可以关注我的公众号【Java技术江湖】一位阿里 Java 工程师的技术小站，作者黄小斜，专注 Java 相关技术：SSM、SpringBoot、MySQL、分布式、中间件、集群、Linux、网络、多线程，偶尔讲点Docker、ELK，同时也分享技术干货和学习经验，致力于Java全栈开发！\n\n**Java工程师必备学习资源:** 一些Java工程师常用学习资源，关注公众号后，后台回复关键字 **“Java”** 即可免费无套路获取。\n\n![我的公众号](https://img-blog.csdnimg.cn/20190805090108984.jpg)\n"
  },
  {
    "path": "docs/hxx/电子书.md",
    "content": "# 目录\n\n* [《菜鸟程序员成长之路：从技术小白到阿里巴巴Java工程师》](#《菜鸟程序员成长之路：从技术小白到阿里巴巴java工程师》)\n  * [Java学习](#java学习)\n    * [新手上路](#新手上路)\n    * [Java面试指南](#java面试指南)\n    * [Java干货资源](#java干货资源)\n  * [求职面试心得](#求职面试心得)\n    * [校招指南](#校招指南)\n    * [笔面试攻略](#笔面试攻略)\n    * [面经大全](#面经大全)\n  * [成长心得](#成长心得)\n    * [关于考研](#关于考研)\n    * [成长感悟和思考](#成长感悟和思考)\n  * [关于我](#关于我)\n  * [程序人生](#程序人生)\n    * [一些思考](#一些思考)\n    * [互联网行业思考](#互联网行业思考)\n    * [职场心得：](#职场心得：)\n\n  * [知识星球](#知识星球)\n\n\n# 《菜鸟程序员成长之路：从技术小白到阿里巴巴Java工程师》\n\n这本电子书整理了我过去一年时间里在微信公众号【黄小斜】里创作的文章，包括Java学习、求职面试、成长心得、感悟思考、程序人生等多个主题的内容，这些内容的主线，就是告诉读者，我是如何从一个技术小白一步步自学成为阿里巴巴工程师的，我把我一路的学习历程、成长经历、求职和工作的心得都记录了下来，并且通过写作的方式分享给更多走在这条路上的人们，希望对你们有所帮助。\n\n我于2016年本科毕业，跨专业考上985软件工程，而后经过两年时间的自学，在2019年秋招期间拿到了BAT等10家互联网大厂的研发offer，顺利成为阿里巴巴的一名Java工程师，谨以本书把我的所学，所思，所得所想分享给有需要的人。\n\n如果想看及时看到我的最新文章，可以关注我的微信公众号【黄小斜】如果你想要看更多Java技术干货，也可以关注我们的Java技术公众号【Java技术江湖】\n\n这些文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看\n\n> https://github.com/h2pl/Java-Tutorial\n\n喜欢的话麻烦点下Star、fork哈\n\n文章首发于我的个人博客：\n\n> www.how2playlife.com\n\n\n**本书目前仅为初稿，有什么建议和意见也欢迎提出。**\n## Java学习\n\n### 新手上路\n\n[用大白话告诉你 ：Java 后端到底是在做什么？](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247485885&idx=1&sn=0b3e02d09559ad0fe371ce915a77d7e0&chksm=fa59ce7acd2e476c501ca68b311de5abaf8021c5baa96fa24e0ca621acaaf4b1c64e2e16f3cb&scene=21#wechat_redirect)\n\n[Java工程师学习指南（入门篇）](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247484573&idx=2&sn=7baf8dfe36357264ab25010429644792&chksm=fa59c35acd2e4a4c24c5b55003136b6b8c5e778273714093b2df7a016d3bb0ed0c774573a7a4&scene=21#wechat_redirect)\n\n[Java工程师学习指南（初级篇）](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247484573&idx=3&sn=290094a3a72f77aab0ebf67000fb1078&chksm=fa59c35acd2e4a4c23b5b53c1e81e870acfaa49c0b7d21da48dad55e39918f4bf174e2c45b8f&scene=21#wechat_redirect)\n\n[Java工程师学习指南（中级篇）](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247484574&idx=1&sn=912cad880c9b63d08d054baeb8a18061&chksm=fa59c359cd2e4a4f5ac6f8649e5cbf0b3b29536ae60c2a6bc9337035f3b786ea504e1c150e38&scene=21#wechat_redirect)\n\n[Java工程师学习指南（完结篇）](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247484573&idx=1&sn=d8f8d6d6913a3cf83ac4eb38ea9eb43c&chksm=fa59c35acd2e4a4c4858f0d176b461b526f96185b3cb32ead2c9d9f90d4cf856511661ca5877&scene=21#wechat_redirect)\n\n### Java面试指南\n\n[这些喜闻乐见的Java面试知识点，你都掌握了吗？](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247484562&idx=1&sn=18eb89447817653475d370ce69e9f235&chksm=fa59c355cd2e4a439fb3b844462e7d92f2f40aff89eff5addab2f5145c20627f682b758964e5&scene=21#wechat_redirect)\n\n[Java集合类常见面试知识点总结](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247484562&idx=2&sn=362774b01cdf8a3914d42c0a1a0911a5&chksm=fa59c355cd2e4a43b129c7940ae29a5255da22d6585ca83bd189ddd5f5cdf67ffa287b59e334&scene=21#wechat_redirect)\n\n[设计模式常见面试知识点总结](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247484562&idx=3&sn=8d17562c1a4552a9c18ca89a301f114a&chksm=fa59c355cd2e4a4323e63a6afce448848a6db27c42f138a7c2cdec08323e8645251d8773a3dd&scene=21#wechat_redirect)\n\n[如何才能够系统地学习Java并发技术？](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247484561&idx=2&sn=14b9475c0f2cec3df72f5691edb068de&chksm=fa59c356cd2e4a40acee42a64d2ac0115d1fd123632813906ff90964d41982e25c43ed393d6f&scene=21#wechat_redirect)\n\n### Java干货资源\n\n[想了解Java后端学习路线？你只需要这一张图！](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247484589&idx=1&sn=fbe8998c9f22ed13a67c8be93bc5fb11&chksm=fa59c36acd2e4a7cf1acbec1cbef2658c7d730dbee8e40952706c6bee9982a132a58e7cad53a&scene=21#wechat_redirect)\n\n[Java秋招面经大合集](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247484595&idx=1&sn=dfcdbc67340529f154cff71028479701&chksm=fa59c374cd2e4a622a7f74cbc5900231f969c8e3a13028271585edb4a1eddf06f09e248a4c02&scene=21#wechat_redirect)\n\n[你不可错过的Java学习资源清单](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247484594&idx=1&sn=9fd9a0e2dd61f5973eb81349066f8e99&chksm=fa59c375cd2e4a632c5e5300f83c2f17106ef0e8fde9d612b6d21804d96383119e8fa4a882ee&scene=21#wechat_redirect)\n\n[Java工程师修炼之路（校招总结）](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247484596&idx=1&sn=94468a925143a66562de44b328836fa1&chksm=fa59c373cd2e4a65cb3d02974ad86c82306e64611dbf4cc8cbadaed96317dfab1e54af9ae98c&scene=21#wechat_redirect)\n\n[Java工程师必备书单](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247484623&idx=1&sn=9c2ee2dbbebdc305affd4c42aa9f7270&chksm=fa59c308cd2e4a1e842b3b4de4354b509d24d448587f2131d75650f4b0b518ec2bd632feb0b4&scene=21#wechat_redirect)\n\n\n## 求职面试心得\n\n### 校招指南\n\n\n[简历写成这样，才能得到BAT的青睐](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247484629&idx=1&sn=421235100ba5d9a08921f7073c5895dd&chksm=fa59c312cd2e4a0498b6e036094c74a83c6d8716d1831803211a90e9715d33e2d27b85541f8a&scene=21#wechat_redirect)\n\n[关于校园招聘你必须了解的五件事](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247484627&idx=1&sn=d87168c60556f575bf5954c4d586f1a2&chksm=fa59c314cd2e4a029193ae409015b5745b2b4c5ab71031f45bce2b86daacf823be7751f13765&scene=21#wechat_redirect)\n\n[应届生如何获取招聘信息](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247484622&idx=1&sn=e217537ca6d98f27e016330c2295c72f&chksm=fa59c309cd2e4a1fdecb81c372c792cc479f6609d48eedc8bdab2f453d5dd658c8b9e453135c&scene=21#wechat_redirect)\n\n[关于秋招的一些真相](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247484602&idx=1&sn=9b15596499930e8b11040702c5f91b32&chksm=fa59c37dcd2e4a6b2d4ab4d0c221c78291c190259535073a385bf704cb7790443ccbab7f1aa3&scene=21#wechat_redirect)\n\n[有关秋招面试的一些小技巧](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247484599&idx=1&sn=7925d1a83a6e6d2b5825f85d1623d3ae&chksm=fa59c370cd2e4a66094fe7a91936dc042786059b616d088b493f4e752c9678b9d7d9ad7b1f81&scene=21#wechat_redirect)\n\n[笔试经验小分享](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247484600&idx=1&sn=238a47c8b93c3551eba1fc9f9d8605be&chksm=fa59c37fcd2e4a694fa2db46c7f5288259e5243a978616657124f68ed66c44b0a8c02e196409&scene=21#wechat_redirect)\n\n[实习转正和校招如何权衡？](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247484593&idx=1&sn=d3c88c3d4d6d40ca802b361e890e087e&chksm=fa59c376cd2e4a60c012950521ba2f0b49b195e43bf6eeef2201bf87fc43f4762c007f0c16fc&scene=21#wechat_redirect)\n\n[校招前几个月，如何高效地进行复习？](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247484533&idx=1&sn=e1a1dd3909556ea9b35ac21e0a544d31&chksm=fa59c3b2cd2e4aa41428646d3efe8b87b4f336c08b586caf11429558b462257e6211fd67479b&scene=21#wechat_redirect)\n\n[刷完500道BAT面试题，我能去面试大厂了吗？](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247484530&idx=1&sn=9e4381e12f5959cdee90ec68d14e3832&chksm=fa59c3b5cd2e4aa3c0be1a9632e5ce3864ba5f8f3347b3e4e0016a5390afcfe2c2ff3b53c177&scene=21#wechat_redirect)\n\n### 笔面试攻略\n[校招季到来，你可能需要这一份求职作战计划！](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247485785&idx=1&sn=6804d35f7def3dfe77e607088e3b2421&chksm=fa59ce9ecd2e47886995ad700b020d2f91e40be65d5d9afc62a9667ee0fce466341f7a60f0f0&scene=21#wechat_redirect)\n\n[记一次面试腾讯的奇葩经历](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247485147&idx=1&sn=321b126865f14f902dd92eef988e61b8&chksm=fa59c11ccd2e480ab804e074a31fca61fd5753d00c85b909fe7a1d91d19df8661b4aa8f04356&scene=21#wechat_redirect)\n\n[阿里巴巴实习生招聘 不完全指南](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247484914&idx=1&sn=015c6c0ccdd8641f2b23bc64de64529c&chksm=fa59c235cd2e4b2305b4bc767d74bd1983b4aa868e497725052fb173b59233d7922a8de9e306&scene=21#wechat_redirect)\n\n[应聘腾讯，面试官和我聊了一个小时的人生](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247484994&idx=1&sn=48182e895dec69f2e29f07bdbe51a77d&chksm=fa59c185cd2e4893db3bdfe9b0a653f25557ef31404977c523c4cd9ccf8d168843b7c1e8e6dc&scene=21#wechat_redirect)\n\n[看过太多大厂面试题，其实考的无非是这 3 点能力](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247485029&idx=1&sn=c1d10b13bb7fc3009560fba7f5700bbc&chksm=fa59c1a2cd2e48b4b3166381102811bc1679221f7a50eadd32f7bed141323896e4d4ab20f8bb&scene=21#wechat_redirect)\n\n[简历上的项目经历怎么写 ？这 3 条原则不可忽视 ！](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247485121&idx=1&sn=d0ebd48d2f86b1a137cbb71e81acfa57&chksm=fa59c106cd2e481020cbff1fa36f60116a49bff87af7e160a039373efa3b1a1cec9682ac94e5&scene=21#wechat_redirect)\n\n[改了 3 年的技术简历，终于能让面试官看顺眼了](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247485134&idx=1&sn=2c8506c048908e7bd679991c77ac8083&chksm=fa59c109cd2e481fa2b75cd930b2681dea20f5dcbfa1ee112433dffcd00fbf9c56921a0e9fbe&scene=21#wechat_redirect)\n\n[当面试官说 “你还有什么问题想问的” ，你该如何回答？](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247485141&idx=1&sn=b05c1a042199e7eaf637dae365209524&chksm=fa59c112cd2e4804f6a75e2ec3a6a8985e5b94f6d3ce8a8571512dabbc1d8681bafc3d3bb27b&scene=21#wechat_redirect)\n\n[百度面试两板斧：手写算法问基础](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247485156&idx=1&sn=dd5d31cf7bffb897cd5d007b0f0addae&chksm=fa59c123cd2e48354455f8b92c2926f0d4ee2d88571e3b0b9c16c46cf09e7d0bfa708df04a78&scene=21#wechat_redirect)\n\n[校招季到来，你可能需要这一份求职作战计划！](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247485785&idx=1&sn=6804d35f7def3dfe77e607088e3b2421&chksm=fa59ce9ecd2e47886995ad700b020d2f91e40be65d5d9afc62a9667ee0fce466341f7a60f0f0&scene=21#wechat_redirect)\n\n\n[20位程序员关于求职的疑问，以及我给出的参考答案](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247484825&idx=1&sn=218c55fdafb9d11b171add3d1107e2cb&chksm=fa59c25ecd2e4b48d031687e7b1cf4e972cf52ec92ec08101c4ef7302cfedd4ac94d1591bbd5&scene=21#wechat_redirect)\n\n\n### 面经大全\n\n[从Java小白到收获BAT等offer，分享我这两年的经验和感悟](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247484522&idx=1&sn=bbba28575a99aa5bf48aebf5e2e00f17&chksm=fa59c3adcd2e4abb9381cb89d49bc7aeb5619e9aa226c67de63cb7dc454dc9fda43c83c35b80&scene=21#wechat_redirect)\n\n[2017春招实习面试回顾：从面试连跪到收割offer](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247484558&idx=1&sn=325c3017a490b38188790a6fc8106dd5&chksm=fa59c349cd2e4a5fce8e57364a8e6056ecd83ae1925e8834c29276cdff81279a5af0c648afb3&scene=21#wechat_redirect)\n\n[从零基础到拿到网易Java实习offer，我做对了哪些事](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247484563&idx=1&sn=4f5cc0f952c87aa6a4d201d35111b97d&chksm=fa59c354cd2e4a424c2a6f82a66d957055d4ca21dfb46028fd89a4a1cfdc1d2e23cf8ce61b0b&scene=21#wechat_redirect)\n\n[腾讯研发面经](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247484618&idx=1&sn=6f7f2bb144587af4b669866753acea11&chksm=fa59c30dcd2e4a1b0275df163b135c81d60b0ddd65d8232683682226b989eddee845957d5c07&scene=21#wechat_redirect)\n\n[阿里中间件研发面经](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247484617&idx=1&sn=1907a48cf44cc541880e7dba533e91d2&chksm=fa59c30ecd2e4a182da5b68724a55bd2cd2ea142353db185a7b89835e78cf0ed7ab1d4301f4c&scene=21#wechat_redirect)\n\n[百度研发面经](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247484616&idx=1&sn=a15ad79f544514c2dbf4d93a9bf3045f&chksm=fa59c30fcd2e4a190daa700f65cde5e407135b1c1be2bed0da1c41aa415600c97427bd0521e2&scene=21#wechat_redirect)\n\n[蚂蚁金服研发面经](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247484615&idx=1&sn=3748aeb6ba863471dbb6a1a30ad42f29&chksm=fa59c300cd2e4a16d96b378eca1fc2c0c2c9592d2b7fa298d6e10161edd6bcacc096ed71d3e9&scene=21#wechat_redirect)\n\n[百度研发面经整合版](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247484614&idx=1&sn=3e8c6d7b9631f9a670578d09874f91a1&chksm=fa59c301cd2e4a17c7be6598ca51f91a6aa787f178260c230920f6d012a2ea55becbcba26205&scene=21#wechat_redirect)\n\n[今日头条研发面经](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247484613&idx=1&sn=af1e4b2a1117957e9a594f744ff53f49&chksm=fa59c302cd2e4a141797a8778876270b7e9a10f2918f206cc1e2648cb5267733ebb50b7a0342&scene=21#wechat_redirect)\n\n## 成长心得\n\n### 关于考研\n\n[考完研，才知道考研路上的这三个大坑！](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247484890&idx=1&sn=00e417afe5679f959686484883a658c8&chksm=fa59c21dcd2e4b0b1a862af760504eff5fd8dc78926d18c30405d5d9defb3e5e8b03a956f25f&scene=21#wechat_redirect)\n\n[今天，我想和你聊聊读研这件事](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247484539&idx=1&sn=a5d05f5a7aebb4be16f7eb2a1bb74d59&chksm=fa59c3bccd2e4aaa7c8be99bf55b3725ac386ec07736e30abc6b97e0d65f6dbab9491bdc0e8f&scene=21#wechat_redirect)\n\n[非科班小白如何逆袭，成功跨考985软件工程？！](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247484581&idx=1&sn=e828746361c0f92e39156aa65464ed12&chksm=fa59c362cd2e4a7499392dfaaa41d6369314f44682d2fb69a12f7dbf43916c521cdd35be5093&scene=21#wechat_redirect)\n\n### 成长感悟和思考\n\n[在网易和百度实习之后，我才明白了这些事](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247484536&idx=1&sn=e628f5c0e3ccb6a0cdfcc49cda673f5d&chksm=fa59c3bfcd2e4aa9879a4195d6cd6c25977d9f1e404dde9de371e1c5a1512e7768f1b9c33a4d&scene=21#wechat_redirect)\n\n[非科班程序员和科班程序员的差距到底在哪里？](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247486254&idx=1&sn=d0efe5692d199ad02aac318be57e53c4&chksm=fa59cce9cd2e45ffeb2b1c1e9264c725a9b8446fb24f6cf9d1d866a33541b4741d544af0550d&scene=21#wechat_redirect)\n\n[细数研究生和导师的那些恩怨情仇](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247486214&idx=1&sn=861e81c692f9d15a743325b746afec7a&chksm=fa59ccc1cd2e45d7a85f17be3889615a2c2d9b636f0fcf44dfb823632af93c65e9fbfc628522&scene=21#wechat_redirect)\n\n[年轻人应该要明白，职场里不只有晋升](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247486102&idx=2&sn=14e6a53b98b23fc4dbd64c605b9b45b1&chksm=fa59cd51cd2e44473ba101828e08e1f779b68f2335cd1871469b28870fcad66e74afd5f1e1de&scene=21#wechat_redirect)\n\n[在大公司做凤尾，还是在小公司做鸡头？](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247485997&idx=1&sn=030f5a626b68615f0b8a3b3cad9201c2&chksm=fa59cdeacd2e44fc9dda75aee4ed3e9cc1d4695134a265eadfd01c974b0dba6b233d38906538&scene=21#wechat_redirect)\n\n[如何在互联网上虚度人生？](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247485957&idx=1&sn=1ed13e969c210cb609011798db229c75&chksm=fa59cdc2cd2e44d466e14cabd73d8f987fd1fed3ae451ef4f111d376966b3953f8478c89ce7e&scene=21#wechat_redirect)\n\n[在阿里工作的日子里，我都学到了哪些东西？](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247486006&idx=1&sn=e18df4e69f8cd522c3dc0d79c5b65d6a&chksm=fa59cdf1cd2e44e75a31fa74a40b15eb338eabd1866c21ceceb9e837b5767fc5f817b5edd556&scene=21#wechat_redirect)\n\n\n## 关于我\n\n[关于我](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247485919&idx=1&sn=78b00c972818925bb5343fb18e2a8a35&chksm=fa59ce18cd2e470ee493e1ba0e992c0ffccbd2fcfaf8c95733b6fbdac0f7c932d2dd6eca681b&scene=21#wechat_redirect)\n\n[程序员江湖一周年，聊聊这一年的所得，所思，所感](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247485736&idx=1&sn=f9ab31189aeb8f26c69e64c3592a69f0&chksm=fa59ceefcd2e47f920d9c97bbff769cff6c164c4b9e9d53008a6d380dbbb5875f4af7ac9bf02&scene=21#wechat_redirect)\n\n\n## 程序人生\n### 一些思考\n\n\n[宁可多花1000元租房，也绝不要去挤半小时地铁](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247485017&idx=1&sn=9d11089a944e4bfd72f3902693b257dc&chksm=fa59c19ecd2e48888c5832f4007640013afb9d8b36cf70238f478ace8075798c16cb97ef8897&scene=21#wechat_redirect)\n\n\n[我很喜欢玩游戏，那么我就适合做游戏程序员吗？](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247485395&idx=1&sn=79e3525f7014c20e403321c50e55516a&chksm=fa59c014cd2e49022d027a5a322fabf22bb624c4375754457d257a68f31a9ec9bb66c7aef1dc&scene=21#wechat_redirect)\n\n[996 盛行的年代，互联网人如何平衡工作和生活 ？](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247485762&idx=1&sn=94a3c2d118e157218232fc33480c0483&chksm=fa59ce85cd2e4793297aec2243ba677f6118222adc1cfa7c54f63853421c0cdb5df58bc616bf&scene=21#wechat_redirect)\n### 互联网行业思考\n\n\n\n\n[马云退隐前，在年会上说了最重要的三件事](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247486268&idx=1&sn=dd13517634cd4f72f824278f93edebef&chksm=fa59ccfbcd2e45ed522c377ab3fa4554632f36c836e9b2e1490fb4748f4c0cdee9a4a99c91a6&scene=21#wechat_redirect)\n\n\n[那些拼命加班的程序员们，后来都怎么样了？](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247485861&idx=1&sn=e90ac488593b4b7adb4ac46303e715be&chksm=fa59ce62cd2e477410c26233ed6518e34fdd772e27ea7538bddfac36a549a50cab29b27881f4&scene=21#wechat_redirect)\n\n[互联网浪潮之下，聊聊 90 后所面临的困境](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247485843&idx=1&sn=492c9d3f7c9e32de9da7d833013cb9b9&chksm=fa59ce54cd2e4742939a5eb42a470bf282dbc79cb6f38ac9ec0112af5282091a9f2cd3615104&scene=21#wechat_redirect)\n\n[程序员的工资到底花到哪里去了？](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247485826&idx=1&sn=cc13da981115341666ae919dd2646cb4&chksm=fa59ce45cd2e475344776b1f7f52a6d281d96210e21229c5d2f300cfad585f712b636cf60427&scene=21#wechat_redirect)\n\n[互联网公司里都有哪些潜规则？](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247485819&idx=1&sn=816e20fa37a2abd99fa714c1c13e0402&chksm=fa59cebccd2e47aa8572071dadbb301008cd425cf0de4d71978af82b44bd3554faacfa8ad462&scene=21#wechat_redirect)\n\n[大厂程序员的一天是如何度过的？](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247485803&idx=1&sn=5814daaa71fb164d33d858311708f1c3&chksm=fa59ceaccd2e47baf573c73fcefbdc93e74becd8b9d4c533cf79404a3f90b2b6cdb46eee4e40&scene=21#wechat_redirect)\n\n\n### 职场心得：\n\n\n[从校园踏入职场之前，你最好先知道这五条建议](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247485876&idx=1&sn=bf0820fd15df7c5b20c6e29f9b2b2cb6&chksm=fa59ce73cd2e47650306f36e234c43f47cfc295f4801703c6c7281689bc68c0566cc74d399db&scene=21#wechat_redirect)\n\n[为什么我会选择走 Java 这条路？](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247485370&idx=1&sn=34899627ecd374d97cc7f076fdde2971&chksm=fa59c07dcd2e496b318937af86338c463b0f4e98c87a614c992b34f7c1c2c99490d6eab54952&scene=21#wechat_redirect)\n\n[Java 学到什么程度可以找到第一份工作 ？](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247485500&idx=1&sn=ce32645a453f64631469abe27ca90419&chksm=fa59cffbcd2e46ed54a47cbbca60a200b24eee7e6d48d82a8e9ceca925c9068def80e6157bbc&scene=21#wechat_redirect)\n\n[那些有实力进入 BAT 的本科生，都做对了什么事？](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247485745&idx=1&sn=86088bb1186806748193d7e4cfcac481&chksm=fa59cef6cd2e47e088c9e2b85955f0b4c355885f677ec0ee1cfb412fcde18922e84ae6b86121&scene=21#wechat_redirect)\n\n[从三流小公司到一线大厂，聊聊程序员的成长之道](http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA==&mid=2247485427&idx=1&sn=6004e6bed110c44f0d8ce77a2ffca1dc&chksm=fa59c034cd2e492253625d91492467b841c5f6de21d299cae01f05483ab363f856e8d97fe339&scene=21#wechat_redirect)\n\n\n\n\n\n## 知识星球\n\n国庆后将上调价格，想要加入的小伙伴要抓紧了！\n\n想了解更多关于我的故事，请点这个链接 https://w.url.cn/s/AIxCMVQ\n\n想要向我提问，进一步交流探讨，或者是想看我的独家经验分享，可以加入我们的知识星球。\n点击链接 https://w.url.cn/s/Ap8KBhZ 了解详情\n\n**我们星球内容不会设限，我会把你们最需要的内容分享给你们，星球第一期已经有六大主题内容，其中包括：技术学习、程序员求职，个人成长、认知提升、业余赚钱，以及星球用户的专属福利。**\n\n以上这些内容，我都会在星球和你分享。同时，我我也根据读者的需求创作更加有价值的文章。\n\n下图是星球第一个月内容的思维导图。后续还会有更多精彩内容陆续奉上！\n\n![](https://mmbiz.qpic.cn/mmbiz_png/hbTNOSuicwlszyNEnxqz4kmGEeEHUqj6mtsIk3nubiaSDFibtELv9CTiaKY5WyoB0NT8J1gFh67Pre0IvWBlOLcPzQ/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1)\n\n**另外，本期加入星球的粉丝还可以获得独家福利：**\n\n**知识星球【黄小斜和他的朋友们】一期精华电子书**\n\n**这是一期电子书的目录**\n\n![](https://mmbiz.qpic.cn/mmbiz_png/hbTNOSuicwlvZbmNuHp7ibP3oc5wAdZXt55aVYtbVUwbw7AALxvBaI8fx8AtIOHx0Uvt7liaQakrt9dB3WRqtfwWg/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1)\n\n![](https://mmbiz.qpic.cn/mmbiz_png/hbTNOSuicwlvZbmNuHp7ibP3oc5wAdZXt5awkibQ7icOBk1npw9RAniad7ddHJmlBKILoXs6Bs1edHYJKz8StoG1Jiag/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1)\n\n\n**扫码加入星球，看到人生中更多的可能性。**\n\n![](https://mmbiz.qpic.cn/mmbiz_png/hbTNOSuicwls6gWjHsicVr5nq3gtq0sYQOQZcxVV3pYtuic40863bxjLJ2zvVfkYqhzm4NYUY8AziarIA0FicJUicKFA/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1)\n\n​        \n\n"
  },
  {
    "path": "docs/interview/BATJ-Experience/alipay-pinduoduo-toutiao.md",
    "content": "# 目录\n\n* [2019年蚂蚁金服、头条、拼多多的面试总结](#2019年蚂蚁金服、头条、拼多多的面试总结)\n  * [准备过程](#准备过程)\n  * [蚂蚁金服](#蚂蚁金服)\n    * [一面](#一面)\n    * [二面](#二面)\n    * [三面](#三面)\n    * [四面](#四面)\n    * [五面](#五面)\n    * [小结](#小结)\n  * [拼多多](#拼多多)\n    * [面试前](#面试前)\n    * [一面](#一面-1)\n    * [二面](#二面-1)\n    * [三面](#三面-1)\n    * [小结](#小结-1)\n  * [字节跳动](#字节跳动)\n    * [面试前](#面试前-1)\n    * [一面](#一面-2)\n    * [二面](#二面-2)\n    * [小结](#小结-2)\n  * [总结](#总结)\n\n\n作者： rhwayfun,原文地址：https://mp.weixin.qq.com/s/msYty4vjjC0PvrwasRH5Bw ,JavaGuide 已经获得作者授权并对原文进行了重新排版。\n<!-- TOC -->\n\n- [写在2019年后的蚂蚁、头条、拼多多的面试总结](#写在2019年后的蚂蚁头条拼多多的面试总结)\n    - [准备过程](#准备过程)\n    - [蚂蚁金服](#蚂蚁金服)\n        - [一面](#一面)\n        - [二面](#二面)\n        - [三面](#三面)\n        - [四面](#四面)\n        - [五面](#五面)\n        - [小结](#小结)\n    - [拼多多](#拼多多)\n        - [面试前](#面试前)\n        - [一面](#一面-1)\n        - [二面](#二面-1)\n        - [三面](#三面-1)\n        - [小结](#小结-1)\n    - [字节跳动](#字节跳动)\n        - [面试前](#面试前-1)\n        - [一面](#一面-2)\n        - [二面](#二面-2)\n        - [小结](#小结-2)\n    - [总结](#总结)\n\n<!-- /TOC -->\n\n# 2019年蚂蚁金服、头条、拼多多的面试总结\n\n文章有点长，请耐心看完，绝对有收获！不想听我BB直接进入面试分享：\n\n- 准备过程\n- 蚂蚁金服面试分享\n- 拼多多面试分享\n- 字节跳动面试分享\n- 总结\n\n说起来开始进行面试是年前倒数第二周，上午9点，我还在去公司的公交上，突然收到蚂蚁的面试电话，其实算不上真正的面试。面试官只是和我聊了下他们在做的事情（主要是做双十一这里大促的稳定性保障，偏中间件吧），说的很详细，然后和我沟通了下是否有兴趣，我表示有兴趣，后面就收到正式面试的通知，最后没选择去蚂蚁表示抱歉。\n\n当时我自己也准备出去看看机会，顺便看看自己的实力。当时我其实挺纠结的，一方面现在部门也正需要我，还是可以有一番作为的，另一方面觉得近一年来进步缓慢，没有以前飞速进步的成就感了，而且业务和技术偏于稳定，加上自己也属于那种比较懒散的人，骨子里还是希望能够突破现状，持续在技术上有所精进。\n\n在开始正式的总结之前，还是希望各位同仁能否听我继续发泄一会，抱拳！\n\n我翻开自己2018年初立的flag，觉得甚是惭愧。其中就有一条是保持一周写一篇博客，奈何中间因为各种原因没能坚持下去。细细想来，主要是自己没能真正静下来心认真投入到技术的研究和学习，那么为什么会这样？说白了还是因为没有确定目标或者目标不明确，没有目标或者目标不明确都可能导致行动的失败。\n\n那么问题来了，目标是啥？就我而言，短期目标是深入研究某一项技术，比如最近在研究mysql，那么深入研究一定要动手实践并且有所产出，这就够了么？还需要我们能够举一反三，结合实际开发场景想一想日常开发要注意什么，这中间有没有什么坑？可以看出，要进步真的不是一件简单的事，这种反人类的行为需要我们克服自我的弱点，逐渐形成习惯。真正牛逼的人，从不觉得认真学习是一件多么难的事，因为这已经形成了他的习惯，就喝早上起床刷牙洗脸那么自然简单。\n\n扯了那么多，开始进入正题，先后进行了蚂蚁、拼多多和字节跳动的面试。\n\n## 准备过程\n\n先说说我自己的情况，我2016先在蚂蚁实习了将近三个月，然后去了我现在的老东家，2.5年工作经验，可以说毕业后就一直老老实实在老东家打怪升级，虽说有蚂蚁的实习经历，但是因为时间太短，还是有点虚的。所以面试官看到我简历第一个问题绝对是这样的。\n\n“哇，你在蚂蚁待过，不错啊”，面试官笑嘻嘻地问到。“是的，还好”，我说。“为啥才三个月？”，面试官脸色一沉问到。“哗啦啦解释一通。。。”，我解释道。“哦，原来如此，那我们开始面试吧”，面试官一本正经说到。\n\n尼玛，早知道不写蚂蚁的实习经历了，后面仔细一想，当初写上蚂蚁不就给简历加点料嘛。\n\n言归正传，准备过程其实很早开始了（当然这不是说我工作时老想着跳槽，因为我明白现在的老东家并不是终点，我还需要不断提升），具体可追溯到从蚂蚁离职的时候，当时出来也面了很多公司，没啥大公司，面了大概5家公司，都拿到offer了。\n\n工作之余常常会去额外研究自己感兴趣的技术以及工作用到的技术，力求把原理搞明白，并且会自己实践一把。此外，买了N多书，基本有时间就会去看，补补基础，什么操作系统、数据结构与算法、MySQL、JDK之类的源码，基本都好好温习了（文末会列一下自己看过的书和一些好的资料）。**我深知基础就像“木桶效应”的短板，决定了能装多少水。**\n\n此外，在正式决定看机会之前，我给自己列了一个提纲，主要包括Java要掌握的核心要点，有不懂的就查资料搞懂。我给自己定位还是Java工程师，所以Java体系是一定要做到心中有数的，很多东西没有常年的积累面试的时候很容易露馅，学习要对得起自己，不要骗人。\n\n剩下的就是找平台和内推了，除了蚂蚁，头条和拼多多都是找人内推的，感谢蚂蚁面试官对我的欣赏，以后说不定会去蚂蚁咯😄。\n\n平台：脉脉、GitHub、v2\n\n## 蚂蚁金服\n\n![img](https://mmbiz.qpic.cn/mmbiz_jpg/zsXjkGNcic53JMPc0FUw1lBXl5iaibrEXvt9qal7lJSgfGJ8mq00yE1J4UQ9H1oo9t6RAL4T3whhx17TYlj1mjlXA/?wx_fmt=jpeg)\n\n- 一面\n- 二面\n- 三面\n- 四面\n- 五面\n- 小结\n\n### 一面\n\n一面就做了一道算法题，要求两小时内完成，给了长度为N的有重复元素的数组，要求输出第10大的数。典型的TopK问题，快排算法搞定。\n\n算法题要注意的是合法性校验、边界条件以及异常的处理。另外，如果要写测试用例，一定要保证测试覆盖场景尽可能全。加上平时刷刷算法题，这种考核应该没问题的。\n\n### 二面\n\n- 自我介绍下呗\n- 开源项目贡献过代码么？（Dubbo提过一个打印accesslog的bug算么）\n- 目前在部门做什么，业务简单介绍下，内部有哪些系统，作用和交互过程说下\n- Dubbo踩过哪些坑，分别是怎么解决的？（说了异常处理时业务异常捕获的问题，自定义了一个异常拦截器）\n- 开始进入正题，说下你对线程安全的理解（多线程访问同一个对象，如果不需要考虑额外的同步，调用对象的行为就可以获得正确的结果就是线程安全）\n- 事务有哪些特性？（ACID）\n- 怎么理解原子性？（同一个事务下，多个操作要么成功要么失败，不存在部分成功或者部分失败的情况）\n- 乐观锁和悲观锁的区别？（悲观锁假定会发生冲突，访问的时候都要先获得锁，保证同一个时刻只有线程获得锁，读读也会阻塞；乐观锁假设不会发生冲突，只有在提交操作的时候检查是否有冲突）这两种锁在Java和MySQL分别是怎么实现的？（Java乐观锁通过CAS实现，悲观锁通过synchronize实现。mysql乐观锁通过MVCC，也就是版本实现，悲观锁可以通过select... for update加上排它锁） \n- HashMap为什么不是线程安全的？（多线程操作无并发控制，顺便说了在扩容的时候多线程访问时会造成死锁，会形成一个环，不过扩容时多线程操作形成环的问题再JDK1.8已经解决，但多线程下使用HashMap还会有一些其他问题比如数据丢失，所以多线程下不应该使用HashMap，而应该使用ConcurrentHashMap）怎么让HashMap变得线程安全？(Collections的synchronize方法包装一个线程安全的Map，或者直接用ConcurrentHashMap)两者的区别是什么？（前者直接在put和get方法加了synchronize同步，后者采用了分段锁以及CAS支持更高的并发）\n- jdk1.8对ConcurrentHashMap做了哪些优化？（插入的时候如果数组元素使用了红黑树，取消了分段锁设计，synchronize替代了Lock锁）为什么这样优化？（避免冲突严重时链表多长，提高查询效率，时间复杂度从O(N)提高到O(logN)）\n- redis主从机制了解么？怎么实现的？\n- 有过GC调优的经历么？（有点虚，答得不是很好）\n- 有什么想问的么？\n\n### 三面\n\n- 简单自我介绍下\n- 监控系统怎么做的，分为哪些模块，模块之间怎么交互的？用的什么数据库？（MySQL）使用什么存储引擎，为什么使用InnnoDB？(支持事务、聚簇索引、MVCC)\n- 订单表有做拆分么，怎么拆的？(垂直拆分和水平拆分)\n- 水平拆分后查询过程描述下\n- 如果落到某个分片的数据很大怎么办？(按照某种规则，比如哈希取模、range，将单张表拆分为多张表)\n- 哈希取模会有什么问题么？(有的，数据分布不均，扩容缩容相对复杂 )\n- 分库分表后怎么解决读写压力？(一主多从、多主多从)\n- 拆分后主键怎么保证惟一？(UUID、Snowflake算法)\n- Snowflake生成的ID是全局递增唯一么？(不是，只是全局唯一，单机递增)\n- 怎么实现全局递增的唯一ID？(讲了TDDL的一次取一批ID，然后再本地慢慢分配的做法)\n- Mysql的索引结构说下(说了B+树，B+树可以对叶子结点顺序查找，因为叶子结点存放了数据结点且有序)\n- 主键索引和普通索引的区别(主键索引的叶子结点存放了整行记录，普通索引的叶子结点存放了主键ID，查询的时候需要做一次回表查询)一定要回表查询么？(不一定，当查询的字段刚好是索引的字段或者索引的一部分，就可以不用回表，这也是索引覆盖的原理)\n- 你们系统目前的瓶颈在哪里？\n- 你打算怎么优化？简要说下你的优化思路\n- 有什么想问我么？\n\n### 四面\n\n- 介绍下自己\n- 为什么要做逆向？\n- 怎么理解微服务？\n- 服务治理怎么实现的？(说了限流、压测、监控等模块的实现)\n- 这个不是中间件做的事么，为什么你们部门做？(当时没有单独的中间件团队，微服务刚搞不久，需要进行监控和性能优化)\n- 说说Spring的生命周期吧\n- 说说GC的过程(说了young gc和full gc的触发条件和回收过程以及对象创建的过程)\n- CMS GC有什么问题？(并发清除算法，浮动垃圾，短暂停顿)\n- 怎么避免产生浮动垃圾？(记得有个VM参数设置可以让扫描新生代之前进行一次young gc，但是因为gc是虚拟机自动调度的，所以不保证一定执行。但是还有参数可以让虚拟机强制执行一次young gc)\n- 强制young gc会有什么问题？(STW停顿时间变长)\n- 知道G1么？(了解一点 )\n- 回收过程是怎么样的？(young gc、并发阶段、混合阶段、full gc，说了Remember Set)\n- 你提到的Remember Set底层是怎么实现的？\n- 有什么想问的么？\n\n### 五面\n\n五面是HRBP面的，和我提前预约了时间，主要聊了之前在蚂蚁的实习经历、部门在做的事情、职业发展、福利待遇等。阿里面试官确实是具有一票否决权的，很看重你的价值观是否match，一般都比较喜欢皮实的候选人。HR面一定要诚实，不要说谎，只要你说谎HR都会去证实，直接cut了。\n\n- 之前蚂蚁实习三个月怎么不留下来？\n- 实习的时候主管是谁？\n- 实习做了哪些事情？（尼玛这种也问？）\n- 你对技术怎么看？平时使用什么技术栈？（阿里HR真的是既当爹又当妈，😂）\n- 最近有在研究什么东西么\n- 你对SRE怎么看\n- 对待遇有什么预期么\n\n最后HR还对我说目前稳定性保障部挺缺人的，希望我尽快回复。\n\n### 小结\n\n蚂蚁面试比较重视基础，所以Java那些基本功一定要扎实。蚂蚁的工作环境还是挺赞的，因为我面的是稳定性保障部门，还有许多单独的小组，什么三年1班，很有青春的感觉。面试官基本水平都比较高，基本都P7以上，除了基础还问了不少架构设计方面的问题，收获还是挺大的。\n\n## 拼多多\n\n![img](https://mmbiz.qpic.cn/mmbiz_jpg/zsXjkGNcic53JMPc0FUw1lBXl5iaibrEXvtsmoh9TdJcV0hwnrjtbWPdOacyj2uYe2qaI5jvlGIQHwYtknwnGTibbQ/?wx_fmt=jpeg)\n\n- 面试前\n- 一面\n- 二面\n- 三面\n- 小结\n\n### 面试前\n\n面完蚂蚁后，早就听闻拼多多这个独角兽，决定也去面一把。首先我在脉脉找了一个拼多多的HR，加了微信聊了下，发了简历便开始我的拼多多面试之旅。这里要非常感谢拼多多HR小姐姐，从面试内推到offer确认一直都在帮我，人真的很nice。\n\n### 一面\n\n- 为啥蚂蚁只待了三个月？没转正？(转正了，解释了一通。。。)\n- Java中的HashMap、TreeMap解释下？(TreeMap红黑树，有序，HashMap无序，数组+链表)\n- TreeMap查询写入的时间复杂度多少？(O(logN))\n- HashMap多线程有什么问题？(线程安全，死锁)怎么解决？( jdk1.8用了synchronize + CAS，扩容的时候通过CAS检查是否有修改，是则重试)重试会有什么问题么？(CAS（Compare And Swap）是比较和交换，不会导致线程阻塞，但是因为重试是通过自旋实现的，所以仍然会占用CPU时间，还有ABA的问题)怎么解决？(超时，限定自旋的次数，ABA可以通过原理变量AtomicStampedReference解决，原理利用版本号进行比较)超过重试次数如果仍然失败怎么办？(synchronize互斥锁)\n- CAS和synchronize有什么区别？都用synchronize不行么？(CAS是乐观锁，不需要阻塞，硬件级别实现的原子性；synchronize会阻塞，JVM级别实现的原子性。使用场景不同，线程冲突严重时CAS会造成CPU压力过大，导致吞吐量下降，synchronize的原理是先自旋然后阻塞，线程冲突严重仍然有较高的吞吐量，因为线程都被阻塞了，不会占用CPU\n)\n- 如果要保证线程安全怎么办？(ConcurrentHashMap)\n- ConcurrentHashMap怎么实现线程安全的？(分段锁)\n- get需要加锁么，为什么？(不用，volatile关键字)\n- volatile的作用是什么？(保证内存可见性)\n- 底层怎么实现的？(说了主内存和工作内存，读写内存屏障，happen-before，并在纸上画了线程交互图)\n- 在多核CPU下，可见性怎么保证？(思考了一会，总线嗅探技术)\n- 聊项目，系统之间是怎么交互的？\n- 系统并发多少，怎么优化？\n- 给我一张纸，画了一个九方格，都填了数字，给一个M*N矩阵，从1开始逆时针打印这M*N个数，要求时间复杂度尽可能低（内心OS：之前貌似碰到过这题，最优解是怎么实现来着）思考中。。。\n- 可以先说下你的思路(想起来了，说了什么时候要变换方向的条件，向右、向下、向左、向上，依此循环)\n- 有什么想问我的？\n\n### 二面\n\n- 自我介绍下\n- 手上还有其他offer么？(拿了蚂蚁的offer)\n- 部门组织结构是怎样的？(这轮不是技术面么，不过还是老老实实说了)\n- 系统有哪些模块，每个模块用了哪些技术，数据怎么流转的？（面试官有点秃顶，一看级别就很高）给了我一张纸，我在上面简单画了下系统之间的流转情况\n- 链路追踪的信息是怎么传递的？(RpcContext的attachment，说了Span的结构:parentSpanId + curSpanId)\n- SpanId怎么保证唯一性？(UUID，说了下内部的定制改动)\n- RpcContext是在什么维度传递的？(线程)\n- Dubbo的远程调用怎么实现的？(讲了读取配置、拼装url、创建Invoker、服务导出、服务注册以及消费者通过动态代理、filter、获取Invoker列表、负载均衡等过程（哗啦啦讲了10多分钟），我可以喝口水么)\n- Spring的单例是怎么实现的？(单例注册表)\n- 为什么要单独实现一个服务治理框架？(说了下内部刚搞微服务不久，主要对服务进行一些监控和性能优化)\n- 谁主导的？内部还在使用么？ \n- 逆向有想过怎么做成通用么？\n- 有什么想问的么？\n\n### 三面\n\n二面老大面完后就直接HR面了，主要问了些职业发展、是否有其他offer、以及入职意向等问题，顺便说了下公司的福利待遇等，都比较常规啦。不过要说的是手上有其他offer或者大厂经历会有一定加分。\n\n### 小结\n\n拼多多的面试流程就简单许多，毕竟是一个成立三年多的公司。面试难度中规中矩，只要基础扎实应该不是问题。但不得不说工作强度很大，开始面试前HR就提前和我确认能否接受这样强度的工作，想来的老铁还是要做好准备\n\n## 字节跳动\n\n![img](https://mmbiz.qpic.cn/mmbiz_jpg/zsXjkGNcic53JMPc0FUw1lBXl5iaibrEXvtRoTSCMeUWramk7M4CekxE9ssH5DFGBxmDcw0x9hjzmbIGHVWenDK8w/?wx_fmt=jpeg)\n\n- 面试前\n- 一面\n- 二面\n- 小结\n\n### 面试前\n\n头条的面试是三家里最专业的，每次面试前有专门的HR和你约时间，确定OK后再进行面试。每次都是通过视频面试，因为都是之前都是电话面或现场面，所以视频面试还是有点不自然。也有人觉得视频面试体验很赞，当然萝卜青菜各有所爱。最坑的二面的时候对方面试官的网络老是掉线，最后很冤枉的挂了（当然有一些点答得不好也是原因之一）。所以还是有点遗憾的。\n\n### 一面\n\n- 先自我介绍下\n- 聊项目，逆向系统是什么意思\n- 聊项目，逆向系统用了哪些技术\n- 线程池的线程数怎么确定？\n- 如果是IO操作为主怎么确定？\n- 如果计算型操作又怎么确定？\n- Redis熟悉么，了解哪些数据结构?(说了zset) zset底层怎么实现的?(跳表)\n- 跳表的查询过程是怎么样的，查询和插入的时间复杂度?(说了先从第一层查找，不满足就下沉到第二层找，因为每一层都是有序的，写入和插入的时间复杂度都是O(logN))\n- 红黑树了解么，时间复杂度?(说了是N叉平衡树，O(logN))\n- 既然两个数据结构时间复杂度都是O(logN)，zset为什么不用红黑树(跳表实现简单，踩坑成本低，红黑树每次插入都要通过旋转以维持平衡，实现复杂)\n- 点了点头，说下Dubbo的原理?(说了服务注册与发布以及消费者调用的过程)踩过什么坑没有？（说了dubbo异常处理的和打印accesslog的问题）\n- CAS了解么？（说了CAS的实现）还了解其他同步机制么？（说了synchronize以及两者的区别，一个乐观锁，一个悲观锁）\n- 那我们做一道题吧，数组A，2*n个元素，n个奇数、n个偶数，设计一个算法，使得数组奇数下标位置放置的都是奇数，偶数下标位置放置的都是偶数\n- 先说下你的思路（从0下标开始遍历，如果是奇数下标判断该元素是否奇数，是则跳过，否则从该位置寻找下一个奇数）\n- 下一个奇数？怎么找？（有点懵逼，思考中。。）\n- 有思路么？（仍然是先遍历一次数组，并对下标进行判断，如果下标属性和该位置元素不匹配从当前下标的下一个遍历数组元素，然后替换）\n- 你这样时间复杂度有点高，如果要求O(N)要怎么做（思考一会，答道“定义两个指针，分别从下标0和1开始遍历，遇见奇数位是是偶数和偶数位是奇数就停下，交换内容”）\n- 时间差不多了，先到这吧。你有什么想问我的？\n\n### 二面\n\n- 面试官和蔼很多，你先介绍下自己吧\n- 你对服务治理怎么理解的？\n- 项目中的限流怎么实现的？（Guava ratelimiter，令牌桶算法）\n- 具体怎么实现的？（要点是固定速率且令牌数有限）\n- 如果突然很多线程同时请求令牌，有什么问题？（导致很多请求积压，线程阻塞）\n- 怎么解决呢？（可以把积压的请求放到消息队列，然后异步处理）\n- 如果不用消息队列怎么解决？（说了RateLimiter预消费的策略）\n- 分布式追踪的上下文是怎么存储和传递的？（ThreadLocal + spanId，当前节点的spanId作为下个节点的父spanId）\n- Dubbo的RpcContext是怎么传递的？（ThreadLocal）主线程的ThreadLocal怎么传递到线程池？（说了先在主线程通过ThreadLocal的get方法拿到上下文信息，在线程池创建新的ThreadLocal并把之前获取的上下文信息设置到ThreadLocal中。这里要注意的线程池创建的ThreadLocal要在finally中手动remove，不然会有内存泄漏的问题）\n- 你说的内存泄漏具体是怎么产生的？（说了ThreadLocal的结构，主要分两种场景：主线程仍然对ThreadLocal有引用和主线程不存在对ThreadLocal的引用。第一种场景因为主线程仍然在运行，所以还是有对ThreadLocal的引用，那么ThreadLocal变量的引用和value是不会被回收的。第二种场景虽然主线程不存在对ThreadLocal的引用，且该引用是弱引用，所以会在gc的时候被回收，但是对用的value不是弱引用，不会被内存回收，仍然会造成内存泄漏）\n- 线程池的线程是不是必须手动remove才可以回收value？（是的，因为线程池的核心线程是一直存在的，如果不清理，那么核心线程的threadLocals变量会一直持有ThreadLocal变量）\n- 那你说的内存泄漏是指主线程还是线程池？（主线程 ）\n- 可是主线程不是都退出了，引用的对象不应该会主动回收么？（面试官和内存泄漏杠上了），沉默了一会。。。\n- 那你说下SpringMVC不同用户登录的信息怎么保证线程安全的？（刚才解释的有点懵逼，一下没反应过来，居然回答成锁了。大脑有点晕了，此时已经一个小时过去了，感觉情况不妙。。。）\n- 这个直接用ThreadLocal不就可以么，你见过SpringMVC有锁实现的代码么？（有点晕菜。。。）\n- 我们聊聊mysql吧，说下索引结构（说了B+树）\n- 为什么使用B+树？（ 说了查询效率高，O(logN)，可以充分利用磁盘预读的特性，多叉树，深度小，叶子结点有序且存储数据）\n- 什么是索引覆盖？（忘记了。。。 ）\n- Java为什么要设计双亲委派模型？\n- 什么时候需要自定义类加载器？\n- 我们做一道题吧，手写一个对象池\n- 有什么想问我的么？（感觉我很多点都没答好，是不是挂了（结果真的是） ）\n\n### 小结\n\n头条的面试确实很专业，每次面试官会提前给你发一个视频链接，然后准点开始面试，而且考察的点都比较全。\n\n面试官都有一个特点，会抓住一个值得深入的点或者你没说清楚的点深入下去直到你把这个点讲清楚，不然面试官会觉得你并没有真正理解。二面面试官给了我一点建议，研究技术的时候一定要去研究产生的背景，弄明白在什么场景解决什么特定的问题，其实很多技术内部都是相通的。很诚恳，还是很感谢这位面试官大大。\n\n## 总结\n\n从年前开始面试到头条面完大概一个多月的时间，真的有点身心俱疲的感觉。最后拿到了拼多多、蚂蚁的offer，还是蛮幸运的。头条的面试对我帮助很大，再次感谢面试官对我的诚恳建议，以及拼多多的HR对我的啰嗦的问题详细解答。\n\n这里要说的是面试前要做好两件事：简历和自我介绍，简历要好好回顾下自己做的一些项目，然后挑几个亮点项目。自我介绍基本每轮面试都有，所以最好提前自己练习下，想好要讲哪些东西，分别怎么讲。此外，简历提到的技术一定是自己深入研究过的，没有深入研究也最好找点资料预热下，不打无准备的仗。\n\n**这些年看过的书**：\n\n《Effective Java》、《现代操作系统》、《TCP/IP详解：卷一》、《代码整洁之道》、《重构》、《Java程序性能优化》、《Spring实战》、《Zookeeper》、《高性能MySQL》、《亿级网站架构核心技术》、《可伸缩服务架构》、《Java编程思想》\n\n说实话这些书很多只看了一部分，我通常会带着问题看书，不然看着看着就睡着了，简直是催眠良药😅。\n\n\n最后，附一张自己面试前准备的脑图：\n\n链接:https://pan.baidu.com/s/1o2l1tuRakBEP0InKEh4Hzw 密码:300d\n\n全文完。\n"
  },
  {
    "path": "docs/interview/BATJ-Experience/蚂蚁金服实习生面经总结(已拿口头offer).md",
    "content": "# 目录\n\n    * [一面 (37 分钟左右)](#一面-37-分钟左右)\n    * [二面 (33 分钟左右)](#二面-33-分钟左右)\n    * [三面 (46 分钟)](#三面-46-分钟)\n    * [HR 面](#hr-面)\n\n\n本文来自 Anonymous 的投稿 ，JavaGuide 对原文进行了重新排版和一点完善。\n\n<!-- TOC -->\n\n- [一面 (37 分钟左右)](#一面-37-分钟左右)\n- [二面 (33 分钟左右)](#二面-33-分钟左右)\n- [三面 (46 分钟)](#三面-46-分钟)\n- [HR 面](#hr-面)\n\n<!-- /TOC -->\n\n### 一面 (37 分钟左右)\n\n一面是上海的小哥打来的，3.12 号中午确认的内推，下午就打来约时间了，也是唯一一个约时间的面试官。约的晚上八点。紧张的一比，人生第一次面试就献给了阿里。\n\n幸运的是一面的小哥特温柔。好像是个海归？口语中夹杂着英文。废话不多说，上干货：\n\n**面试官：** 先自我介绍下吧！\n\n**我：** 巴拉巴拉...。\n\n> 关于自我介绍：从 HR 面、技术面到高管面/部门主管面，面试官一般会让你先自我介绍一下，所以好好准备自己的自我介绍真的非常重要。网上一般建议的是准备好两份自我介绍：一份对 HR 说的，主要讲能突出自己的经历，会的编程技术一语带过；另一份对技术面试官说的，主要讲自己会的技术细节，项目经验，经历那些就一语带过。\n\n**面试官：** 我看你简历上写你做了个秒杀系统？我们就从这个项目开始吧，先介绍下你的项目。\n\n> 关于项目介绍：如果有项目的话，技术面试第一步，面试官一般都是让你自己介绍一下你的项目。你可以从下面几个方向来考虑：\n>\n> 1. 对项目整体设计的一个感受（面试官可能会让你画系统的架构图）\n> 2. 在这个项目中你负责了什么、做了什么、担任了什么角色\n> 3. 从这个项目中你学会了那些东西，使用到了那些技术，学会了那些新技术的使用\n> 4. 另外项目描述中，最好可以体现自己的综合素质，比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的又或者说你在这个项目用了什么技术实现了什么功能比如:用 redis 做缓存提高访问速度和并发量、使用消息队列削峰和降流等等。\n\n**我：** 我说了我是如何考虑它的需求（秒杀地址隐藏，记录订单，减库存），一开始简单的用 synchronized 锁住方法，出现了问题，后来乐观锁改进，又有瓶颈，再上缓存，出现了缓存雪崩，于是缓存预热，错开缓存失效时间。最后，发现先记录订单再减库存会减少行级锁等待时间。\n\n> 一面面试官很耐心地听，并给了我一些指导，问了我乐观锁是怎么实现的，我说是基于 sql 语句，在减库存操作的 where 条件里加剩余库存数>0，他说这应该不算是一种乐观锁，应该先查库存，在减库存的时候判断当前库存是否与读到的库存一样（可这样不是多一次查询操作吗？不是很理解，不过我没有反驳，只是说理解您的意思。事实证明千万别怼面试官，即使你觉得他说的不对）\n\n**面试官:** 我缓存雪崩什么情况下会发生？如何避免？\n\n**我：** 当多个商品缓存同时失效时会雪崩，导致大量查询数据库。还有就是秒杀刚开始的时候缓存里没有数据。解决方案：缓存预热，错开缓存失效时间\n\n**面试官：** 问我更新数据库的同时为什么不马上更新缓存，而是删除缓存？\n\n**我：** 因为考虑到更新数据库后更新缓存可能会因为多线程下导致写入脏数据（比如线程 A 先更新数据库成功，接下来要取更新缓存，接着线程 B 更新数据库，但 B 又更新了缓存，接着 B 的时间片用完了，线程 A 更新了缓存）\n\n逼逼了将近 30 分钟，面试官居然用周杰伦的语气对我说：\n\n![not bad](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3not-bad.jpg)\n\n我突然受宠若惊，连忙说谢谢，也正是因为第一次面试得到了面试官的肯定，才让我信心大增，二三面稳定发挥。\n\n**面试官又曰：** 我看你还懂数据库是吧，答：略懂略懂。。。那我问个简单的吧！\n\n**我：** 因为这个问题太简单了，所以我忘记它是什么了。\n\n**面试官：** 你还会啥数据库知识？\n\n**我：** 我一听，问的这么随意的吗。。。都让我选题了，我就说我了解索引，慢查询优化，巴拉巴拉\n\n**面试官：** 等等，你说索引是吧，那你能说下索引的存储数据结构吗？\n\n**我：** 我心想这简单啊，我就说 B+树，还说了为什么用 B+树\n\n**面试官：** 你简历上写的这个 J.U.C 包是什么啊？（他居然不知道 JUC）\n\n**我：** 就是 java 多线程的那个包啊。。。\n\n**面试官：** 那你都了解里面的哪些东西呢？\n\n**我：**  哈哈哈！这可是我的强项，从 ConcurrentHashMap，ConcurrentLinkedQueue 说到 CountDownLatch，CyclicBarrier，又说到线程池，分别说了底层实现和项目中的应用。\n\n**面试官:** 我觉得差不多了，那我再问个与技术无关的问题哈，虽然这个问题可能不应该我问，就是你是如何考虑你的项目架构的呢？\n\n**我：** 先用最简单的方式实现它，再去发掘系统的问题和瓶颈，于是查资料改进架构。。。\n\n**面试官：** 好，那我给你介绍下我这边的情况吧\n\n![chat-end](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3chat-end.jpg)\n\n**总结：** 一面可能是简历面吧，问的比较简单，我在讲项目中说出了我做项目时的学习历程和思考，赢得了面试官的好感，感觉他应该给我的评价很好。\n\n### 二面 (33 分钟左右)\n\n然而开心了没一会，内推人问我面的怎么样啊？看我流程已经到大大 boss 那了。我一听二面不是主管吗？？？怎么直接跳了一面。于是瞬间慌了，赶紧（下床）学习准备二面。\n\n隔了一天，3.14 的早上 10：56 分，杭州的大大 boss 给我打来了电话，卧槽我当时在上毛概课，万恶的毛概课每节课都点名，我还在最后一排不敢跑出去。于是接起电话来怂怂地说不好意思我在上课，晚上可以面试吗？大大 boss 看来很忙啊，跟我说晚上没时间啊，再说吧！\n\n于是又隔了一天，3.16 中午我收到了北京的电话，当时心里小失望，我的大大 boss 呢？？？接起电话来，就是一番狂轰乱炸。。。\n\n第一步还是先自我介绍，这个就不多说了，提前准备好要说的重点就没问题！\n\n**面试官:** 我们还是从你的项目开始吧，说说你的秒杀系统。\n\n**我:** 一面时的套路。。。我考虑到秒杀地址在开始前不应暴露给用户。。。\n\n**面试官:** 等下啊，为什么要这样呢？暴露给用户会怎么样？\n\n**我:** 用户提前知道秒杀地址就可以写脚本来抢购了，这样不公平\n\n**面试官:** 那比如说啊，我现在是个黑客，我在秒杀开始时写好了脚本，运行一万个线程获取秒杀地址，这样是不是也不公平呢？\n\n**我：** 我考虑到了这方面，于是我自己写了个 LRU 缓存（划重点，这么多好用的缓存我为啥不用偏要自己写？就是为了让面试官上钩问我是怎么写的，这样我就可以逼逼准备好的内容了！），用这个缓存存储请求的 ip 和用户名，一个 ip 和用户名只能同时透过 3 个请求。\n\n**面试官:** 那我可不可以创建一个 ip 代理池和很多用户来抢购呢？假设我有很多手机号的账户。\n\n**我：** 这就是在为难我胖虎啊，我说这种情况跟真实用户操作太像了。。。我没法区别，不过我觉得可以通过地理位置信息或者机器学习算法来做吧。。。\n\n**面试官:** 好的这个问题就到这吧，你接着说\n\n**我：** 我把生成订单和减库存两条 sql 语句放在一个事务里，都操作成功了则认为秒杀成功。\n\n**面试官:** 等等，你这个订单表和商品库存表是在一个数据库的吧，那如果在不同的数据库中呢？\n\n**我:**  这面试官好变态啊，我只是个本科生？！？！我觉得应该要用分布式锁来实现吧。。。\n\n**面试官:** 有没有更轻量级的做法？\n\n**我:**  不知道了。后来查资料发现可以用消息队列来实现。使用消息队列主要能带来两个好处：(1) 通过异步处理提高系统性能（削峰、减少响应所需时间）;(2) 降低系统耦合性。关于消息队列的更多内容可以查看这篇文章：<https://snailclimb.gitee.io/javaguide/#/./system-design/data-communication/message-queue>\n\n后来发现消息队列作用好大，于是现在在学手写一个消息队列。\n\n**面试官:** 好的你接着说项目吧。\n\n**我:**  我考虑到了缓存雪崩问题，于是。。。\n\n**面试官:** 等等，你有没有考虑到一种情况，假如说你的缓存刚刚失效，大量流量就来查缓存，你的数据库会不会炸？\n\n**我:** 我不知道数据库会不会炸，反正我快炸了。当时说没考虑这么高的并发量，后来发现也是可以用消息队列来解决，对流量削峰填谷。\n\n**面试官:** 好项目聊（怼）完了，我们来说说别的，操作系统了解吧，你能说说 NIO 吗？\n\n**我:** NIO 是。。。\n\n**面试官:** 那你知道 NIO 的系统调用有哪些吗，具体是怎么实现的？\n\n**我：** 当时复习 NIO 的时候就知道是咋回事，不知道咋实现。最近在补这方面的知识，可见 NIO 还是很重要的！\n\n**面试官:** 说说进程切换时操作系统都会发生什么？\n\n**我:** 不如杀了我，我最讨厌操作系统了。简单说了下，可能不对，需要答案自行百度。\n\n**面试官:** 说说线程池？\n\n**答：** 卧槽这我熟啊，把 Java 并发编程的艺术里讲的都说出来了，说了得有十分钟，自夸一波，毕竟这本书我看了五遍😂\n\n**面试官:** 好问问计网吧如果设计一个聊天系统，应该用 TCP 还是 UDP？为什么\n\n**我:** 当然是 TCP！原因如下：\n\n![TCP VS UDP](https://user-gold-cdn.xitu.io/2018/4/19/162db5e97e9a9e01?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)\n\n**面试官:** 好的，你有什么要问我的吗？\n\n**我:** 我还有下一次面试吗?\n\n**面试官：** 应该。应该有的，一周内吧。还告诉我居然转正前要实习三个月？wtf，一个大三满课的本科生让我如何在八月底前实习三个月？\n\n**我：** 面试官再见\n\n![saygoodbye-smile](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3saygoodbye-smile.jpg)\n\n### 三面 (46 分钟)\n\n3.18 号，三面来了，这次又是那个大大 boss！\n\n第一步还是先自我介绍，这个就不多说了，提前准备好要说的重点就没问题！\n\n**面试官:** 聊聊你的项目？\n\n**我:** 经过二面的教训，我迅速学习了一下分布式的理论知识，并应用到了我的项目（吹牛逼）中。\n\n**面试官:** 看你用到了 Spring 的事务机制，你能说下 Spring 的事务传播吗？\n\n**我：** 完了这个问题好像没准备，虽然之前刷知乎看到过。。。我就只说出来一条，面试官说其实这个有很多机制的，比如事务嵌套，内事务回滚外事务回滚都会有不同情况，你可以回去看看。\n\n**面试官:** 说说你的分布式事务解决方案？\n\n**我:** 我叭叭的照着资料查到的解决方案说了一通，面试官怎么好像没大听懂？？？\n\n> 阿里巴巴之前开源了一个分布式 Fescar（一种易于使用，高性能，基于 Java 的开源分布式事务解决方案），后来，Ant Financial 加入 Fescar，使其成为一个更加中立和开放的分布式交易社区，Fescar 重命名为 Seata。Github 地址：<https://github.com/seata/seata>\n\n**面试官:** 好，我们聊聊其他项目，说说你这个 MapReduce 项目？MapReduce 原理了解过吗？\n\n**我:** 我叭叭地说了一通，面试官好像觉得这个项目太简单了。要不是没项目，我会把我的实验写上吗？？？\n\n**面试官:** 你这个手写 BP 神经网络是干了啥？\n\n**我:** 这是我选修机器学习课程时的一个作业，我又对它进行了扩展。\n\n**面试官:** 你能说说为什么调整权值时要沿着梯度下降的方向？\n\n**我:** 老大，你太厉害了，怎么什么都懂。我压根没准备这个项目。。。没想到会问，做过去好几个月了，加上当时一紧张就忘了，后来想起来大概是....。\n\n**面试官:** 好我们问问基础知识吧，说说什么叫 xisuo？\n\n**我：**？？？xisuo，您说什么，不好意思我没听清。（这面试官有点口音。。。）就是 xisuo 啊！xisuo 你不知道吗？。。。尴尬了十几秒后我终于意识到，他在说死锁！！！\n\n**面试官:** 假如 A 账户给 B 账户转钱，会发生 xisuo 吗？能具体说说吗？\n\n**我:** 当时答的不好，后来发现面试官又是想问分布式，具体答案参考这个：<https://blog.csdn.net/taylorchan2016/article/details/51039362>\n\n**面试官:** 为什么不考研？\n\n**我：** 不喜欢学术氛围，巴拉巴拉。\n\n**面试官:** 你有什么问题吗？\n\n**我:**  我还有下一面吗。。。面试官说让我等，一周内答复。\n\n------\n\n等了十天，一度以为我凉了，内推人说我流程到 HR 了，让我等着吧可能 HR 太忙了，3.28 号 HR 打来了电话，当时在教室，我直接飞了出去。\n\n### HR 面\n\n**面试官:** 你好啊，先自我介绍下吧\n\n**我：** 巴拉巴拉....HR 面的技术面试和技术面的还是有所区别的！\n\n面试官人特别好，一听就是很会说话的小姐姐！说我这里给你悄悄透露下，你的评级是 A 哦！\n\n![panghu-knowledge](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3panghu-knowledge.jpg)\n\n接下来就是几个经典 HR 面挂人的问题，什么难给我来什么，我看别人的 HR 面怎么都是聊聊天。。。\n\n**面试官:** 你为什么选择支付宝呢，你怎么看待支付宝？\n\n**我:** 我从个人情怀，公司理念，环境氛围，市场价值，趋势导向分析了一波（说白了就是疯狂夸支付宝，不过说实话我说的那些一点都没撒谎，阿里确实做到了。比如我举了个雷军和格力打赌 5 年 2000 亿销售额，大部分企业家关注的是利益，而马云更关注的是真的为人类为世界做一些事情，利益不是第一位的。）\n\n**面试官:** 明白了解，那你的优点我们都很明了了，你能说说你的缺点吗？\n\n> 缺点肯定不能是目标岗位需要的关键能力！！！\n>\n> 总之，记住一点，面试官问你这个问题的话，你可以说一些不影响你这个职位工作需要的一些缺点。比如你面试后端工程师，面试官问你的缺点是什么的话，你可以这样说：自己比较内向，平时不太爱与人交流，但是考虑到以后可能要和客户沟通，自己正在努力改。\n\n**我:** 据说这是 HR 面最难的一个问题。。。我当时翻了好几天的知乎才找到一个合适的，也符合我的答案：我有时候会表现的不太自信，比如阿里的内推二月份就开始了，其实我当时已经复习了很久了，但是老是觉得自己还不行，不敢投简历，于是又把书看了一遍才投的，当时也是舍友怂恿一波才投的，面了之后发现其实自己也没有很差。（划重点，一定要把自己的缺点圆回来）。\n\n**面试官:** HR 好像不太满意我的答案，继续问我还有缺点吗？\n\n**我:** 我说比较容易紧张吧，举了自己大一面实验室因为紧张没进去的例子，后来不断调整心态，现在已经好很多了。\n\n接下来又是个好难的问题。\n\n**面试官：** BAT 都给你 offer 了，你怎么选？\n\n其实我当时好想说，BT 是什么？不好意思我只知道阿里。\n\n**我 :** 哈哈哈哈开玩笑，就说了阿里的文化，支付宝给我们带来很多便利，想加入支付宝为人类做贡献！\n\n最后 HR 问了我实习时间，现在大几之类的问题，说肯定会给我发 offer 的，让我等着就好了，希望过两天能收到好的结果。\n\n![mengbi](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3mengbi.jpg)\n"
  },
  {
    "path": "docs/interview/BATJ-Experience/面阿里,终获offer.md",
    "content": "# 目录\n\n    * [前言](#前言)\n    * [一面(技术面)](#一面技术面)\n    * [二面(技术面)](#二面技术面)\n    * [三面(技术面)](#三面技术面)\n    * [四面(半个技术面)](#四面半个技术面)\n  * [五面(HR面)](#五面hr面)\n    * [总结](#总结)\n\n\n> 作者：ppxyn。本文来自读者投稿，同时也欢迎各位投稿，**对于不错的原创文章我根据你的选择给予现金(50-200)、付费专栏或者任选书籍进行奖励！所以，快提 pr 或者邮件的方式（邮件地址在主页）给我投稿吧！** 当然，我觉得奖励是次要的，最重要的是你可以从自己整理知识点的过程中学习到很多知识。\n\n**目录**\n\n<!-- MarkdownTOC -->\n\n- [前言](#前言)\n- [一面\\(技术面\\)](#一面技术面)\n- [二面\\(技术面\\)](#二面技术面)\n- [三面\\(技术面\\)](#三面技术面)\n- [四面\\(半个技术面\\)](#四面半个技术面)\n- [五面\\(HR面\\)](#五面hr面)\n- [总结](#总结)\n\n<!-- /MarkdownTOC -->\n\n### 前言\n\n在接触 Java 之前我接触的比较多的是硬件方面，用的比较多的语言就是C和C++。到了大三我才正式选择 Java 方向，到目前为止使用Java到现在大概有一年多的时间，所以Java算不上很好。刚开始投递的时候，实习刚辞职，也没准备笔试面试，很多东西都忘记了。所以，刚开始我并没有直接就投递阿里，毕竟心里还是有一点点小害怕的。于是，我就先投递了几个不算大的公司来练手，就是想着刷刷经验而已或者说是练练手（ps：还是挺对不起那些公司的）。面了一个月其他公司后，我找了我实验室的学长内推我，后面就有了这5次面试。\n\n下面简单的说一下我的这5次面试：4次技术面+1次HR面，希望我的经历能对你有所帮助。\n\n### 一面(技术面)\n\n1. 自我介绍（主要讲自己会的技术细节，项目经验，经历那些就一语带过，后面面试官会问你的）。\n2. 聊聊项目（就是一个很普通的分布式商城，自己做了一些改进），让我画了整个项目的架构图，然后针对项目抛了一系列的提高性能的问题，还问了我做项目的过程中遇到了那些问题，如何解决的，差不读就这些吧。\n3. 可能是我前面说了我会数据库优化，然后面试官就开始问索引、事务隔离级别、悲观锁和乐观锁、索引、ACID、MVVC这些问题。\n4. 浏览器输入URL发生了什么? TCP和UDP区别? TCP如何保证传输可靠性?\n5. 讲下跳表怎么实现的?哈夫曼编码是怎么回事？非递归且不用额外空间（不用栈），如何遍历二叉树\n6. 后面又问了很多JVM方面的问题，比如Java内存模型、常见的垃圾回收器、双亲委派模型这些\n7. 你有什么问题要问吗？\n\n### 二面(技术面)\n\n1. 自我介绍（主要讲自己会的技术细节，项目经验，经历那些就一语带过，后面面试官会问你的）。\n2. 操作系统的内存管理机制\n3. 进程和线程的区别\n4. 说下你对线程安全的理解\n5. volatile 有什么作用 ，sychronized和lock有什么区别\n6. ReentrantLock实现原理\n7. 用过CountDownLatch么？什么场景下用的？\n8. AQS底层原理。\n9. 造成死锁的原因有哪些，如何预防？\n10.  加锁会带来哪些性能问题。如何解决？  \n11. HashMap、ConcurrentHashMap源码。HashMap是线程安全的吗？Hashtable呢？ConcurrentHashMap有了解吗？    \n12. 是否可以实习？ \n13. 你有什么问题要问吗？  \n\n### 三面(技术面)\n\n1. 有没有参加过 ACM 或者他竞赛，有没有拿过什么奖？（  我说我没参加过ACM，本科参加过数学建模竞赛，名次并不好，没拿过什么奖。面试官好像有点失望，然后我又赶紧补充说我和老师一起做过一个项目，目前已经投入使用。面试官还比较感兴趣，后面又和他聊了一下这个项目。）\n2. 研究生期间，做过什么项目，发过论文吗？有什么成果吗？\n3. 你觉得你有什么优点和缺点？你觉得你相比于那些比你更优秀的人欠缺什么？\n4. 有读过什么源码吗？（我说我读过 Java 集合框架和 Netty 的，面试官说 Java 集合前几面一定问的差不多，就不问了，然后就问我 Netty的，我当时很慌啊！）\n5. 介绍一下自己对 Netty 的认识，为什么要用。说说业务中，Netty 的使用场景。什么是TCP 粘包/拆包,解决办法。Netty线程模型。Dubbo 在使用 Netty 作为网络通讯时候是如何避免粘包与半包问题？讲讲Netty的零拷贝？巴拉巴拉问了好多，我记得有好几个我都没回答上来，心里想着凉凉了啊。\n6. 用到了那些开源技术、在开源领域做过贡献吗？\n7. 常见的排序算法及其复杂度，现场写了快排。\n8. 红黑树，B树的一些问题。\n9. 讲讲算法及数据结构在实习项目中的用处。\n10. 自己的未来规划（就简单描述了一下自己未来的设想啊，说的还挺诚恳，面试官好像还挺满意的）\n11. 你有什么问题要问吗？  \n\n### 四面(半个技术面)\n\n三面面完当天，晚上9点接到面试电话，感觉像是部门或者项目主管。 这个和之前的面试不大相同，感觉面试官主要考察的是你解决问题的能力、学习能力和团队协作能力。\n\n1.  让我讲一个自己觉得最不错的项目。然后就巴拉巴拉的聊，我记得主要是问了项目是如何进行协作的、遇到问题是如何解决的、与他人发生冲突是如何解决的这些。感觉聊了挺久。\n2.  出现 OOM 后你会怎么排查问题？\n3. 自己平时是如何学习新技术的？除了 Java 还回去了解其他技术吗?\n4. 上一段实习经历的收获。\n5. NginX如何做负载均衡、常见的负载均衡算法有哪些、一致性哈希的一致性是什么意思、一致性哈希是如何做哈希的  \n6. 你有什么问题问我吗？\n7. 还有一些其他的，想不起来了，感觉这一面不是偏向技术来问。\n\n## 五面(HR面)\n\n1. 自我介绍（主要讲能突出自己的经历，会的编程技术一语带过）。\n2. 你觉得你有什么优点和缺点？如何克服这些缺点？\n3. 说一件大学里你自己比较有成就感的一件事情，为此付出了那些努力。\n4. 你前面跟其他面试官讲过一些你做的项目吧？可以给我讲讲吗？你要考虑到我不是一个做技术的人，怎么让我也听得懂。项目中有什么问题，你怎么解决的？你最大的收获是什么？\n5. 你目前有面试过其他公司吗？如果让你选，这些公司和阿里，你选哪个？（送分题，回答不好可能送命）\n6. 你期望的工作地点是哪里？\n7. 你有什么问题吗？\n\n### 总结\n\n1. 可以看出面试官问我的很多问题都是比较常见的问题，所以记得一定要提前准备，还要深入准备，不要回答的太皮毛。很多时候一个问题可能会牵扯出很多问题，遇到不会的问题不要慌，冷静分析，如果你真的回答不上来，也不要担心自己是不是就要挂了，很可能这个问题本身就比较难。\n2. 表达能力和沟通能力太重要了，一定要提前练一下，我自身就是一个不太会说话的人，所以，面试前我对于自我介绍、项目介绍和一些常见问题都在脑子里练了好久，确保面试的时候能够很清晰和简洁的说出来。\n3. 等待面试的过程和面试的过程真的好熬人，那段时间我压力也比较大，好在我私下找到学长聊了很多，心情也好了很多。\n4. 面试之后及时总结，面的好的话，不要得意，尽快准备下一场面试吧！\n\n我觉得我还算是比较幸运的，最后也祝大家都能获得心仪的Offer。\n\n          \n\n        \n"
  },
  {
    "path": "docs/interview/InterviewQuestions/Java核心技术总结.md",
    "content": "# 目录\n\n  * [Java基础学习总结](#java基础学习总结)\n    * [面向对象三大特性](#面向对象三大特性)\n    * [基本数据类型](#基本数据类型)\n    * [String及包装类](#string及包装类)\n    * [final关键字](#final关键字)\n    * [抽象类和接口](#抽象类和接口)\n    * [代码块和加载顺序](#代码块和加载顺序)\n    * [包、内部类、外部类](#包、内部类、外部类)\n    * [异常](#异常)\n    * [泛型](#泛型)\n    * [Class类和Object类](#class类和object类)\n    * [javac和java](#javac和java)\n    * [反射](#反射)\n    * [枚举类](#枚举类)\n    * [序列化](#序列化)\n    * [动态代理](#动态代理)\n    * [多线程](#多线程)\n    * [IO流](#io流)\n    * [网络编程](#网络编程)\n    * [Java8](#java8)\n\n\n\n---\ntitle: Java核心技术学习总结\ndate: 2018-07-10 22:37:47\ntags:\n    - Java基础\ncategories:\n\t- 后端\n\t- Java基础\n---\n本文主要是我最近复习Java基础原理过程中写的Java基础学习总结。Java的知识点其实非常多，并且有些知识点比较难以理解，有时候我们自以为理解了某些内容，其实可能只是停留在表面上，没有理解其底层实现原理。\n\n纸上得来终觉浅，绝知此事要躬行。笔者之前对每部分的内容\n对做了比较深入的学习以及代码实现，基本上比较全面地讲述了每一个Java基础知识点，当然可能有些遗漏和错误，还请读者指正。\n\n<!-- more -->\n\n**这里先把整体的学习大纲列出来，让大家对知识框架有个基本轮廓，具体每个部分的内容，笔者都对应写了一篇博文来加以讲解和剖析，并且发表在我的个人博客和csdn技术专栏里，下面给出地址**\n\n\n专栏：深入理解Java原理\n\nhttps://blog.csdn.net/column/details/21930.html\n\n相关代码实现在我的GitHub里：\n\nhttps://github.com/h2pl/MyTech\n\n**喜欢的话麻烦star一下哈**\n\n本系列技术文章首发于我的个人博客：\n\nhttps://h2pl.github.io\n\n更多关于Java后端学习的内容请到我的CSDN博客上查看：\n\nhttps://blog.csdn.net/a724888\n\n## Java基础学习总结\n\n每部分内容会重点写一些常见知识点，方便复习和记忆，但是并不是全部内容，详细的内容请参见具体的文章地址。\n\n### 面向对象三大特性\n\n    继承：一般类只能单继承，内部类实现多继承，接口可以多继承\n    \n    封装：访问权限控制public > protected > 包 > private 内部类也是一种封装\n    \n    多态：编译时多态，体现在向上转型和向下转型，通过引用类型判断调用哪个方法（静态分派）。\n    \n    运行时多态，体现在同名函数通过不同参数实现多种方法（动态分派）。\n\n### 基本数据类型\n\n    基本类型位数，自动装箱，常量池\n    \n    例如byte类型是1byte也就是8位，可以表示的数字是-128到127，因为还有一个0，加起来一共是256，也就是2的八次方。\n    \n    32位和64位机器的int是4个字节也就是32位，char是1个字节就是8位，float是4个字节，double是8个字节，long是8个字节。\n    \n    所以它们占有字节数是相同的，这样的话两个版本才可以更好地兼容。（应该）\n    \n    基本数据类型的包装类只在数字范围-128到127中用到常量池，会自动拆箱装箱，其余数字范围的包装类则会新建实例\n\n### String及包装类\n\n    String类型是final类型，在堆中分配空间后内存地址不可变。\n    \n    底层是final修饰的char[]数组，数组的内存地址同样不可变。\n    \n    但实际上可以通过修改char[n] = 'a'来进行修改，不会改变String实例的内存值，不过在jdk中，用户无法直接获取char[]，也没有方法能操作该数组。\n    所以String类型的不可变实际上也是理论上的不可变。所以我们在分配String对象以后，如果将其 = \"abc\"，那也只是改变了引用的指向，实际上没有改变原来的对象。\n    \n    StringBuffer和StringBuilder底层是可变的char[]数组，继承父类AbstractStringBuilder的各种成员和方法，实际上的操作都是由父类方法来完成的。\n\n### final关键字\n\n    final修饰基本数据类型保证不可变\n    \n    final修饰引用保证引用不能指向别的对象，否则会报错。\n    \n    final修饰类，类的实例分配空间后地址不可变，子类不能重写所有父类方法。因此在cglib动态代理中，不能为一个类的final修饰的函数做代理，因为cglib要将被代理的类设置为父类，然后再生成字节码。\n    \n    final修饰方法，子类不能重写该方法。\n\n### 抽象类和接口\n\n    1 抽象类可以有方法实现。\n    抽象类可以有非final成员变量。\n    抽象方法要用abstract修饰。\n    抽象类可以有构造方法，但是只能由子类进行实例化。\n    \n    2 接口可以用extends加多个接口实现多继承。\n    接口只能有public final类型的成员变量。\n    接口只能有抽象方法，不能有方法体、\n    接口不能实例化，但是可以作为引用类型。\n\n### 代码块和加载顺序\n\n    假设该类是第一次进行实例化。那么有如下加载顺序\n    静态总是比非静态优先，从早到晚的顺序是：\n    1 静态代码块 和 静态成员变量的顺序根据代码位置前后来决定。\n    2 代码块和成员变量的顺序也根据代码位置来决定\n    3 最后才调用构造方法构造方法\n### 包、内部类、外部类\n    1 Java项目一般从src目录开始有com.*.*.A.java这样的目录结构。这就是包结构。所以一般编译后的结构是跟包结构一模一样的，这样的结构保证了import时能找到正确的class引用包访问权限就是指同包下的类可见。\n    \n    import 一般加上全路径，并且使用.*时只包含当前目录的所有类文件，不包括子目录。\n    \n    2 外部类只有public和default两种修饰，要么全局可访问，要么包内可访问。\n    \n    3 内部类可以有全部访问权限，因为它的概念就是一个成员变量，所以访问权限设置与一般的成员变量相同。\n    \n    非静态内部类是外部类的一个成员变量，只跟外部类的实例有关。\n    \n    静态内部类是独立于外部类存在的一个类，与外部类实例无关，可以通过外部类.内部类直接获取Class类型。\n\n### 异常\n\n    1 异常体系的最上层是Throwable类\n    子类有Error和Exception\n    Exception的子类又有RuntimeException和其他具体的可检查异常。\n    \n    2 Error是jvm完全无法处理的系统错误，只能终止运行。\n    \n    运行时异常指的是编译正确但运行错误的异常，如数组越界异常，一般是人为失误导致的，这种异常不用try catch，而是需要程序员自己检查。\n    \n    可检查异常一般是jvm处理不了的一些异常，但是又经常会发生，比如Ioexception，Sqlexception等，是外部实现带来的异常。\n    \n    3 多线程的异常流程是独立的，互不影响。\n    大型模块的子模块异常一般需要重新封装成外部异常再次抛出，否则只能看到最外层异常信息，难以进行调试。\n    \n    日志框架是异常报告的最好帮手，log4j，slf4j中，在工作中必不可少。\n\n### 泛型\n\n    Java中的泛型是伪泛型，只在编译期生效，运行期自动进行泛型擦除，将泛型替换为实际上传入的类型。\n    \n    泛型类用class <T> A {\n        \n    }这样的形式表示，里面的方法和成员变量都可以用T来表示类型。泛型接口也是类似的，不过泛型类实现泛型接口时可以选择注入实际类型或者是继续使用泛型。\n    \n    泛型方法可以自带泛型比如void <E> E go();\n    \n    泛型可以使用?通配符进行泛化 Object<?>可以接受任何类型\n    \n    也可以使用 <? extends Number> <? super Integer>这种方式进行上下边界的限制。\n\n### Class类和Object类\n\n    Java反射的基础是Class类，该类封装所有其他类的类型信息，并且在每个类加载后在堆区生成每个类的一个Class<类名>实例，用于该类的实例化。\n    \n    Java中可以通过多种方式获取Class类型，比如A.class,new A().getClass()方法以及Class.forName(\"com.?.?.A\")方法。\n    \n    Object是所有类的父类，有着自己的一些私有方法，以及被所有类继承的9大方法。\n    \n    有人讨论Object和Class类型谁先加载谁后加载，因为每个类都要继承Object，但是又得先被加载到堆区，事实上，这个问题在JVM初始化时就解决了，没必要多想。\n\n### javac和java\n\n    javac 是编译一个java文件的基本命令，通过不同参数可以完成各种配置，比如导入其他类，指定编译路径等。\n    \n    java是执行一个java文件的基本命令，通过参数配置可以以不同方式执行一个java程序或者是一个jar包。\n    \n    javap是一个class文件的反编译程序，可以获取class文件的反编译结果，甚至是jvm执行程序的每一步代码实现。\n\n\n​    \n\n### 反射\n\n    Java反射包reflection提供对Class，Method，field，constructor等信息的封装类型。\n    \n    通过这些api可以轻易获得一个类的各种信息并且可以进行实例化，方法调用等。\n    \n    类中的private参数可以通过setaccessible方法强制获取。\n    \n    反射的作用可谓是博大精深，JDK动态代理生成代理类的字节码后，首先把这个类通过defineclass定义成一个类，然后用class.for(name)会把该类加载到jvm，之后我们就可以通过，A.class.GetMethod()获取其方法，然后通过invoke调用其方法，在调用这个方法时，实际上会通过被代理类的引用再去调用原方法。\n\n### 枚举类\n\n    枚举类继承Enum并且每个枚举类的实例都是唯一的。\n    \n    枚举类可以用于封装一组常量，取值从这组常量中取，比如一周的七天，一年的十二个月。\n    \n    枚举类的底层实现其实是语法糖，每个实例可以被转化成内部类。并且使用静态代码块进行初始化，同时保证内部成员变量不可变。\n\n### 序列化\n\n    序列化的类要实现serializable接口\n    \n    transient修饰符可以保证某个成员变量不被序列化\n    \n    readObject和writeOject来实现实例的写入和读取。\n    \n    待更新。\n    \n    事实上，一些拥有数组变量的类都会把数组设为transient修饰，这样的话不会对整个数组进行序列化，而是利用专门的方法将有数据的数组范围进行序列化，以便节省空间。\n\n### 动态代理\n\n    jdk自带的动态代理可以代理一个已经实现接口的类。\n    \n    cglib代理可以代理一个普通的类。\n    \n    动态代理的基本实现原理都是通过字节码框架动态生成字节码，并且在用defineclass加载类后，获取代理类的实例。\n    \n    一般需要实现一个代理处理器，用来处理被代理类的前置操作和后置操作。在JDK动态代理中，这个类叫做invocationHandler。\n    \n    JDK动态代理首先获取被代理类的方法，并且只获取在接口中声明的方法，生成代理类的字节码后，首先把这个类通过defineclass定义成一个类，然后把该类加载到jvm，之后我们就可以通过，A.class.GetMethod()获取其方法，然后通过invoke调用其方法，在调用这个方法时，实际上会通过被代理类的引用再去调用原方法。\n    \n    而对于cglib动态代理，一般会把被代理类设为代理类的父类，然后获取被代理类中所有非final的方法，通过asm字节码框架生成代理类的字节码，这个代理类很神奇，他会保留原来的方法以及代理后的方法，通过方法数组的形式保存。\n    \n    cglib的动态代理需要实现一个enhancer和一个interceptor，在interceptor中配置我们需要的代理内容。如果没有配置interceptor，那么代理类会调用被代理类自己的方法，如果配置了interceptor，则会使用代理类修饰过的方法。\n\n\n### 多线程\n    这里先不讲juc包里的多线程类。juc相关内容会在Java并发专题讲解。\n    \n    线程的实现可以通过继承Thread类和实现Runable接口\n    也可以使用线程池。callable配合future可以实现线程中的数据获取。\n    \n    Java中的线程有7种状态，new runable running blocked waiting time_waiting terminate\n    blocked是线程等待其他线程锁释放。\n    waiting是wait以后线程无限等待其他线程使用notify唤醒\n    time_wating是有限时间地等待被唤醒，也可能是sleep固定时间。\n    \n    Thread的join是实例方法，比如a.join(b),则说明a线程要等b线程运行完才会运行。\n    \n    o.wait方法会让持有该对象o的线程释放锁并且进入阻塞状态，notify则是持有o锁对象的线程通知其他等待锁的线程获取锁。notify方法并不会释放锁。注意这两个方法都只能在synchronized同步方法或同步块里使用。\n    \n    synchronized方法底层使用系统调用的mutex锁，开销较大，jvm会为每个锁对象维护一个等待队列，让等待该对象锁的线程在这个队列中等待。当线程获取不到锁时则让线程阻塞，而其他检查notify以后则会通知任意一个线程，所以这个锁时非公平锁。\n    \n    Thread.sleep()，Thread.interrupt()等方法都是类方法，表示当前调用该方法的线程的操作。\n\n\n​    \n    一个线程实例连续start两次会抛异常,这是因为线程start后会设置标识，如果再次start则判断为错误。\n\n### IO流\n\n    IO流也是Java中比较重要的一块，Java中主要有字节流，字符流，文件等。其中文件也是通过流的方式打开，读取和写入的。\n    \n    IO流的很多接口都使用了装饰者模式，即将原类型通过传入装饰类构造函数的方式，增强原类型，以此获得像带有缓冲区的字节流，或者将字节流封装成字符流等等，其中需要注意的是编码问题，后者打印出来的结果可能是乱码哦。\n    \n    IO流与网络编程息息相关，一个socket接入后，我们可以获取它的输入流和输出流，以获取TCP数据包的内容，并且可以往数据报里写入内容，因为TCP协议也是按照流的方式进行传输的，实际上TCP会将这些数据进行分包处理，并且通过差错检验，超时重传，滑动窗口协议等方式，保证了TCP数据包的高效和可靠传输。\n\n### 网络编程\n\n    承接IO流的内容\n    \n    IO流与网络编程息息相关，一个socket接入后，我们可以获取它的输入流和输出流，以获取TCP数据包的内容，并且可以往数据报里写入内容，因为TCP协议也是按照流的方式进行传输的，实际上TCP会将这些数据进行分包处理，并且通过差错检验，超时重传，滑动窗口协议等方式，保证了TCP数据包的高效和可靠传输。\n    \n    除了使用socket来获取TCP数据包外，还可以使用UDP的DatagramPacket来封装UDP数据包，因为UDP数据包的大小是确定的，所以不是使用流方式处理，而是需要事先定义他的长度，源端口和目标端口等信息。\n    \n    为了方便网络编程，Java提供了一系列类型来支持网络编程的api，比如URL类，InetAddress类等。\n    \n    后续文章会带来NIO相关的内容，敬请期待。\n\n\n​    \n\n\n### Java8\n\n    接口中的默认方法，接口终于可以有方法实现了，使用注解即可标识出默认方法。\n    \n    lambda表达式实现了函数式编程，通过注解可以声明一个函数式接口，该接口中只能有一个方法，这个方法正是使用lambda表达式时会调用到的接口。\n    \n    Option类实现了非空检验\n    \n    新的日期API\n    \n    各种api的更新，包括chm，hashmap的实现等\n    \n    Stream流概念，实现了集合类的流式访问，可以基于此使用map和reduce并行计算。\n\n\n\n\n"
  },
  {
    "path": "docs/interview/PreparingForInterview/JavaInterviewLibrary.md",
    "content": "# 目录\n\n  * [知识点相关](#知识点相关)\n    * [1.JavaGuide](#1javaguide)\n    * [2.CS-Notes](#2cs-notes)\n    * [3. advanced-java](#3-advanced-java)\n    * [4.JCSprout](#4jcsprout)\n    * [5.toBeTopJavaer](#5tobetopjavaer)\n    * [6.architect-awesome](#6architect-awesome)\n    * [7.technology-talk](#7technology-talk)\n    * [8.fullstack-tutorial](#8fullstack-tutorial)\n    * [9.3y](#93y)\n    * [10.java-bible](#10java-bible)\n    * [11.interviews](#11interviews)\n  * [算法相关](#算法相关)\n    * [1.LeetCodeAnimation](#1leetcodeanimation)\n    * [2.awesome-java-leetcode](#2awesome-java-leetcode)\n    * [3.leetcode](#3leetcode)\n\n\n昨天我整理了公众号历史所有和面试相关的我觉得还不错的文章：[整理了一些有助于你拿Offer的文章](<https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485434&idx=1&sn=f6bdf19d2594bf719e149e48d1384340&chksm=cea24831f9d5c1278617d347238f65f0481f36291675f05fabb382b69ea0ff3adae7ee6e6524&token=1452779379&lang=zh_CN#rd>) 。今天分享一下最近逛Github看到了一些我觉得对于Java面试以及学习有帮助的仓库，这些仓库涉及Java核心知识点整理、Java常见面试题、算法、基础知识点比如网络和操作系统等等。\n\n## 知识点相关\n\n### 1.JavaGuide\n\n- Github地址： [https://github.com/Snailclimb/JavaGuide](https://github.com/Snailclimb/JavaGuide)\n- star: 64.0k\n- 介绍: 【Java学习+面试指南】 一份涵盖大部分Java程序员所需要掌握的核心知识。\n\n### 2.CS-Notes\n\n- Github 地址：<https://github.com/CyC2018/CS-Notes>\n- Star:  68.3k \n- 介绍: 技术面试必备基础知识、Leetcode 题解、后端面试、Java 面试、春招、秋招、操作系统、计算机网络、系统设计。\n\n### 3. advanced-java\n\n- Github地址：[https://github.com/doocs/advanced-java](https://github.com/doocs/advanced-java)\n- star: 23.4k\n- 介绍: 互联网 Java 工程师进阶知识完全扫盲：涵盖高并发、分布式、高可用、微服务等领域知识，后端同学必看，前端同学也可学习。\n\n### 4.JCSprout\n\n- Github地址：[https://github.com/crossoverJie/JCSprout](https://github.com/crossoverJie/JCSprout)\n- star: 21.2k\n- 介绍: Java Core Sprout：处于萌芽阶段的 Java 核心知识库。\n\n### 5.toBeTopJavaer\n\n- Github地址：[https://github.com/hollischuang/toBeTopJavaer](https://github.com/hollischuang/toBeTopJavaer)\n- star: 4.0 k\n- 介绍: Java工程师成神之路。\n\n### 6.architect-awesome\n\n- Github地址：[https://github.com/xingshaocheng/architect-awesome](https://github.com/xingshaocheng/architect-awesome)\n- star: 34.4 k\n- 介绍:后端架构师技术图谱。\n\n### 7.technology-talk\n\n- Github地址： [https://github.com/aalansehaiyang/technology-talk](https://github.com/aalansehaiyang/technology-talk)\n- star: 6.1k\n- 介绍: 汇总java生态圈常用技术框架、开源中间件，系统架构、项目管理、经典架构案例、数据库、常用三方库、线上运维等知识。\n\n### 8.fullstack-tutorial\n\n- Github地址： [https://github.com/frank-lam/fullstack-tutorial](https://github.com/frank-lam/fullstack-tutorial)\n- star: 4.0k\n- 介绍: fullstack tutorial 2019，后台技术栈/架构师之路/全栈开发社区，春招/秋招/校招/面试。\n\n### 9.3y\n\n- Github地址：[https://github.com/ZhongFuCheng3y/3y](https://github.com/ZhongFuCheng3y/3y)\n- star: 1.9 k\n- 介绍: Java 知识整合。\n\n### 10.java-bible\n\n- Github地址：[https://github.com/biezhi/java-bible](https://github.com/biezhi/java-bible)\n- star: 2.3k\n- 介绍:  这里记录了一些技术摘要，部分文章来自网络，本项目的目的力求分享精品技术干货，以Java为主。\n\n### 11.interviews\n\n- Github地址:  [https://github.com/kdn251/interviews/blob/master/README-zh-cn.md](https://github.com/kdn251/interviews/blob/master/README-zh-cn.md)\n- star: 35.3k\n- 介绍: 软件工程技术面试个人指南（国外的一个项目，虽然有翻译版，但是不太推荐，因为很多内容并不适用于国内）。\n\n## 算法相关\n\n### 1.LeetCodeAnimation\n\n- Github 地址： <https://github.com/MisterBooo/LeetCodeAnimation>\n- Star:  33.4k\n- 介绍: Demonstrate all the questions on LeetCode in the form of animation.（用动画的形式呈现解LeetCode题目的思路）。\n\n### 2.awesome-java-leetcode\n\n- Github地址：[https://github.com/Blankj/awesome-java-leetcode](https://github.com/Blankj/awesome-java-leetcode)\n- star: 6.1k\n- 介绍:  LeetCode 上 Facebook 的面试题目。\n\n### 3.leetcode\n\n- Github地址：[https://github.com/azl397985856/leetcode](https://github.com/azl397985856/leetcode)\n- star: 12.0k\n- 介绍:  LeetCode Solutions: A Record of My Problem Solving Journey.( leetcode题解，记录自己的leetcode解题之路。)\n"
  },
  {
    "path": "docs/interview/PreparingForInterview/JavaProgrammerNeedKnow.md",
    "content": "# 目录\n\n    * [Question1:我是双非/三本/专科学校的，我有机会进入大厂吗？](#question1我是双非三本专科学校的，我有机会进入大厂吗？)\n    * [Question2:非计算机专业的学生能学好Java后台吗？我能进大厂吗？](#question2非计算机专业的学生能学好java后台吗？我能进大厂吗？)\n    * [Question3: 我没有实习经历的话找工作是不是特别艰难？](#question3-我没有实习经历的话找工作是不是特别艰难？)\n    * [Question4: 我该如何准备面试呢？面试的注意事项有哪些呢？](#question4-我该如何准备面试呢？面试的注意事项有哪些呢？)\n    * [Question5: 我该自学还是报培训班呢？](#question5-我该自学还是报培训班呢？)\n    * [Question6: 没有项目经历/博客/Github开源项目怎么办？](#question6-没有项目经历博客github开源项目怎么办？)\n    * [Question7: 大厂到底青睐什么样的应届生？](#question7-大厂到底青睐什么样的应届生？)\n\n\n　　身边的朋友或者公众号的粉丝很多人都向我询问过：“我是双非/三本/专科学校的，我有机会进入大厂吗？”、“非计算机专业的学生能学好吗？”、“如何学习Java？”、“Java学习该学哪些东西？”、“我该如何准备Java面试？”......这些方面的问题。我会根据自己的一点经验对大部分人关心的这些问题进行答疑解惑。现在又刚好赶上考研结束，这篇文章也算是给考研结束准备往Java后端方向发展的朋友们指明一条学习之路。道理懂了如果没有实际行动，那这篇文章对你或许没有任何意义。\n\n### Question1:我是双非/三本/专科学校的，我有机会进入大厂吗？\n\n　　我自己也是非985非211学校的，结合自己的经历以及一些朋友的经历，我觉得让我回答这个问题再好不过。\n\n　　首先，我觉得学校歧视很正常，真的太正常了，如果要抱怨的话，你只能抱怨自己没有进入名校。但是，千万不要动不动说自己学校差，动不动拿自己学校当做自己进不了大厂的借口，学历只是筛选简历的很多标准中的一个而已，如果你够优秀，简历够丰富，你也一样可以和名校同学一起同台竞争。\n\n　　企业HR肯定是更喜欢高学历的人，毕竟985、211优秀人才比例肯定比普通学校高很多，HR团队肯定会优先在这些学校里选。这就好比相亲，你是愿意在很多优秀的人中选一个优秀的，还是愿意在很多普通的人中选一个优秀的呢？\n　　\n　　双非本科甚至是二本、三本甚至是专科的同学也有很多进入大厂的，不过比率相比于名校的低很多而已。从大厂招聘的结果上看，高学历人才的数量占据大头，那些成功进入BAT、美团，京东，网易等大厂的双非本科甚至是二本、三本甚至是专科的同学往往是因为具备丰富的项目经历或者在某个含金量比较高的竞赛比如ACM中取得了不错的成绩。**一部分学历不突出但能力出众的面试者能够进入大厂并不是说明学历不重要，而是学历的软肋能够通过其他的优势来弥补。** 所以，如果你的学校不够好而你自己又想去大厂的话，建议你可以从这几点来做：**①尽量在面试前最好有一个可以拿的出手的项目；②有实习条件的话，尽早出去实习，实习经历也会是你的简历的一个亮点（有能力在大厂实习最佳！）；③参加一些含金量比较高的比赛，拿不拿得到名次没关系，重在锻炼。**\n\n\n### Question2:非计算机专业的学生能学好Java后台吗？我能进大厂吗？\n\n　　当然可以！现在非科班的程序员很多，很大一部分原因是互联网行业的工资比较高。我们学校外面的培训班里面90%都是非科班，我觉得他们很多人学的都还不错。另外，我的一个朋友本科是机械专业，大一开始自学安卓，技术贼溜，在我看来他比大部分本科是计算机的同学学的还要好。参考Question1的回答，即使你是非科班程序员，如果你想进入大厂的话，你也可以通过自己的其他优势来弥补。\n\n　　我觉得我们不应该因为自己的专业给自己划界限或者贴标签，说实话，很多科班的同学可能并不如你，你以为科班的同学就会认真听讲吗？还不是几乎全靠自己课下自学！不过如果你是非科班的话，你想要学好，那么注定就要舍弃自己本专业的一些学习时间，这是无可厚非的。\n\n　　建议非科班的同学，首先要打好计算机基础知识基础：①计算机网络、②操作系统、③数据机构与算法，我个人觉得这3个对你最重要。这些东西就像是内功，对你以后的长远发展非常有用。当然，如果你想要进大厂的话，这些知识也是一定会被问到的。另外，“一定学好数据结构与算法！一定学好数据结构与算法！一定学好数据结构与算法！”，重要的东西说3遍。\n\n\n\n### Question3: 我没有实习经历的话找工作是不是特别艰难？\n\n　　没有实习经历没关系，只要你有拿得出手的项目或者大赛经历的话，你依然有可能拿到大厂的 offer 。笔主当时找工作的时候就没有实习经历以及大赛获奖经历，单纯就是凭借自己的项目经验撑起了整个面试。\n\n　　如果你既没有实习经历，又没有拿得出手的项目或者大赛经历的话，我觉得在简历关，除非你有其他特别的亮点，不然，你应该就会被刷。\n\n### Question4: 我该如何准备面试呢？面试的注意事项有哪些呢？\n\n下面是我总结的一些准备面试的Tips以及面试必备的注意事项：\n\n1. **准备一份自己的自我介绍，面试的时候根据面试对象适当进行修改**（突出重点，突出自己的优势在哪里，切忌流水账）；\n2. **注意随身带上自己的成绩单和简历复印件；** （有的公司在面试前都会让你交一份成绩单和简历当做面试中的参考。）\n3. **如果需要笔试就提前刷一些笔试题，大部分在线笔试的类型是选择题+编程题，有的还会有简答题。**（平时空闲时间多的可以刷一下笔试题目（牛客网上有很多），但是不要只刷面试题，不动手code，程序员不是为了考试而存在的。）另外，注意抓重点，因为题目太多了，但是有很多题目几乎次次遇到，像这样的题目一定要搞定。\n4. **提前准备技术面试。** 搞清楚自己面试中可能涉及哪些知识点、哪些知识点是重点。面试中哪些问题会被经常问到、自己该如何回答。(强烈不推荐背题，第一：通过背这种方式你能记住多少？能记住多久？第二：背题的方式的学习很难坚持下去！)\n5. **面试之前做好定向复习。** 也就是专门针对你要面试的公司来复习。比如你在面试之前可以在网上找找有没有你要面试的公司的面经。\n6. **准备好自己的项目介绍。** 如果有项目的话，技术面试第一步，面试官一般都是让你自己介绍一下你的项目。你可以从下面几个方向来考虑：①对项目整体设计的一个感受（面试官可能会让你画系统的架构图）；②在这个项目中你负责了什么、做了什么、担任了什么角色；③ 从这个项目中你学会了那些东西，使用到了那些技术，学会了那些新技术的使用；④项目描述中，最好可以体现自己的综合素质，比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的又或者说你在这个项目用了什么技术实现了什么功能比如：用 redis 做缓存提高访问速度和并发量、使用消息队列削峰和降流等等。\n7. **面试之后记得复盘。** 面试遭遇失败是很正常的事情，所以善于总结自己的失败原因才是最重要的。如果失败，不要灰心；如果通过，切勿狂喜。\n\n\n**一些还算不错的 Java面试/学习相关的仓库，相信对大家准备面试一定有帮助：**[盘点一下Github上开源的Java面试/学习相关的仓库，看完弄懂薪资至少增加10k](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484817&idx=1&sn=12f0c254a240c40c2ccab8314653216b&chksm=fd9853f0caefdae6d191e6bf085d44ab9c73f165e3323aa0362d830e420ccbfad93aa5901021&token=766994974&lang=zh_CN#rd)\n\n### Question5: 我该自学还是报培训班呢？\n\n　　我本人更加赞同自学（你要知道去了公司可没人手把手教你了，而且几乎所有的公司都对培训班出生的有偏见。为什么有偏见，你学个东西还要去培训班，说明什么，同等水平下，你的自学能力以及自律能力一定是比不上自学的人的）。但是如果，你连每天在寝室坚持学上8个小时以上都坚持不了，或者总是容易半途而废的话，我还是推荐你去培训班。观望身边同学去培训班的，大多是非计算机专业或者是没有自律能力以及自学能力非常差的人。\n\n　　另外，如果自律能力不行，你也可以通过结伴学习、参加老师的项目等方式来督促自己学习。\n\n　　总结：去不去培训班主要还是看自己，如果自己能坚持自学就自学，坚持不下来就去培训班。\n\n### Question6: 没有项目经历/博客/Github开源项目怎么办？\n\n　　从现在开始做！\n\n　　网上有很多非常不错的项目视频，你就跟着一步一步做，不光要做，还要改进，改善。另外，如果你的老师有相关 Java 后台项目的话，你也可以主动申请参与进来。\n\n　　如果有自己的博客，也算是简历上的一个亮点。建议可以在掘金、Segmentfault、CSDN等技术交流社区写博客，当然，你也可以自己搭建一个博客（采用 Hexo+Githu Pages 搭建非常简单）。写一些什么？学习笔记、实战内容、读书笔记等等都可以。\n\n　　多用 Github，用好 Github，上传自己不错的项目，写好 readme 文档，在其他技术社区做好宣传。相信你也会收获一个不错的开源项目！\n\n\n### Question7: 大厂到底青睐什么样的应届生？\n\n　　从阿里、腾讯等大厂招聘官网对于Java后端方向/后端方向的应届实习生的要求，我们大概可以总结归纳出下面这 4 点能给简历增加很多分数：\n\n- 参加过竞赛（含金量超高的是ACM）；\n- 对数据结构与算法非常熟练；\n- 参与过实际项目（比如学校网站）；\n- 参与过某个知名的开源项目或者自己的某个开源项目很不错；\n\n　　除了我上面说的这三点，在面试Java工程师的时候，下面几点也提升你的个人竞争力：\n\n- 熟悉Python、Shell、Perl等脚本语言；\n- 熟悉 Java 优化，JVM调优；\n- 熟悉 SOA 模式；\n- 熟悉自己所用框架的底层知识比如Spring；\n- 了解分布式一些常见的理论；\n- 具备高并发开发经验；大数据开发经验等等。\n\n"
  },
  {
    "path": "docs/interview/PreparingForInterview/interviewPrepare.md",
    "content": "# 目录\n\n  * [1 如何获取大厂面试机会？](#1-如何获取大厂面试机会？)\n  * [2  面试前的准备](#2--面试前的准备)\n    * [2.1 准备自己的自我介绍](#21-准备自己的自我介绍)\n    * [2.2 关于着装](#22-关于着装)\n    * [2.3 随身带上自己的成绩单和简历](#23-随身带上自己的成绩单和简历)\n    * [2.4 如果需要笔试就提前刷一些笔试题](#24-如果需要笔试就提前刷一些笔试题)\n    * [2.5 花时间一些逻辑题](#25-花时间一些逻辑题)\n    * [2.6 准备好自己的项目介绍](#26-准备好自己的项目介绍)\n    * [2.7 提前准备技术面试](#27-提前准备技术面试)\n    * [2.7 面试之前做好定向复习](#27-面试之前做好定向复习)\n  * [3 面试之后复盘](#3-面试之后复盘)\n\n\n不论是校招还是社招都避免不了各种面试、笔试，如何去准备这些东西就显得格外重要。不论是笔试还是面试都是有章可循的，我这个“有章可循”说的意思只是说应对技术面试是可以提前准备。 我其实特别不喜欢那种临近考试就提前背啊记啊各种题的行为，非常反对！我觉得这种方法特别极端，而且在稍有一点经验的面试官面前是根本没有用的。建议大家还是一步一个脚印踏踏实实地走。\n\n<!-- TOC -->\n\n- [1 如何获取大厂面试机会？](#1-如何获取大厂面试机会)\n- [2  面试前的准备](#2--面试前的准备)\n    - [2.1 准备自己的自我介绍](#21-准备自己的自我介绍)\n    - [2.2 关于着装](#22-关于着装)\n    - [2.3 随身带上自己的成绩单和简历](#23-随身带上自己的成绩单和简历)\n    - [2.4 如果需要笔试就提前刷一些笔试题](#24-如果需要笔试就提前刷一些笔试题)\n    - [2.5 花时间一些逻辑题](#25-花时间一些逻辑题)\n    - [2.6 准备好自己的项目介绍](#26-准备好自己的项目介绍)\n    - [2.7 提前准备技术面试](#27-提前准备技术面试)\n    - [2.7 面试之前做好定向复习](#27-面试之前做好定向复习)\n- [3 面试之后复盘](#3-面试之后复盘)\n\n<!-- /TOC -->\n\n## 1 如何获取大厂面试机会？\n\n**在讲如何获取大厂面试机会之前，先来给大家科普/对比一下两个校招非常常见的概念——春招和秋招。**\n\n1. **招聘人数** ：秋招多于春招 ；\n2. **招聘时间** ： 秋招一般7月左右开始，大概一直持续到10月底。<font color=\"red\">但是大厂（如BAT）都会早开始早结束，所以一定要把握好时间。</font>春招最佳时间为3月，次佳时间为4月，进入5月基本就不会再有春招了（金三银四）。  \n3. **应聘难度** ：秋招略大于春招；\n4. **招聘公司：**  秋招数量多，而春招数量较少，一般为秋招的补充。 \n\n**综上，一般来说，秋招的含金量明显是高于春招的。**\n\n**下面我就说一下我自己知道的一些方法，不过应该也涵盖了大部分获取面试机会的方法。**\n\n1. **关注大厂官网，随时投递简历（走流程的网申）；**\n2.  **线下参加宣讲会，直接投递简历；**\n3. **找到师兄师姐/认识的人，帮忙内推（能够让你避开网申简历筛选，笔试筛选，还是挺不错的，不过也还是需要你的简历够棒）；**\n4. **博客发文被看中/Github优秀开源项目作者，大厂内部人员邀请你面试；**\n5. **求职类网站投递简历（不是太推荐，适合海投）；**\n\n\n除了这些方法，我也遇到过这样的经历：有些大公司的一些部门可能暂时没招够人，然后如果你的亲戚或者朋友刚好在这个公司，而你正好又在寻求offer，那么面试机会基本上是有了，而且这种面试的难度好像一般还普遍比其他正规面试低很多。\n\n## 2  面试前的准备\n\n### 2.1 准备自己的自我介绍\n\n从HR面、技术面到高管面/部门主管面，面试官一般会让你先自我介绍一下，所以好好准备自己的自我介绍真的非常重要。网上一般建议的是准备好两份自我介绍：一份对hr说的，主要讲能突出自己的经历，会的编程技术一语带过；另一份对技术面试官说的，主要讲自己会的技术细节，项目经验，经历那些就一语带过。\n\n我这里简单分享一下我自己的自我介绍的一个简单的模板吧：\n\n> 面试官，您好！我叫某某。大学时间我主要利用课外时间学习某某。在校期间参与过一个某某系统的开发，另外，自己学习过程中也写过很多系统比如某某系统。在学习之余，我比较喜欢通过博客整理分享自己所学知识。我现在是某某社区的认证作者，写过某某很不错的文章。另外，我获得过某某奖,我的Github上开源的某个项目已经有多少Star了。\n\n### 2.2 关于着装\n\n穿西装、打领带、小皮鞋？NO！NO！NO！这是互联网公司面试又不是去走红毯，所以你只需要穿的简单大方就好，不需要太正式。\n\n### 2.3 随身带上自己的成绩单和简历\n\n有的公司在面试前都会让你交一份成绩单和简历当做面试中的参考。\n\n### 2.4 如果需要笔试就提前刷一些笔试题\n\n平时空闲时间多的可以刷一下笔试题目（牛客网上有很多）。但是不要只刷面试题，不动手code，程序员不是为了考试而存在的。\n\n### 2.5 花时间一些逻辑题\n\n面试中发现有些公司都有逻辑题测试环节，并且都把逻辑笔试成绩作为很重要的一个参考。\n\n### 2.6 准备好自己的项目介绍\n\n如果有项目的话，技术面试第一步，面试官一般都是让你自己介绍一下你的项目。你可以从下面几个方向来考虑：\n\n1. 对项目整体设计的一个感受（面试官可能会让你画系统的架构图）\n2. 在这个项目中你负责了什么、做了什么、担任了什么角色\n3. 从这个项目中你学会了那些东西，使用到了那些技术，学会了那些新技术的使用\n4. 另外项目描述中，最好可以体现自己的综合素质，比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的又或者说你在这个项目用了什么技术实现了什么功能比如：用redis做缓存提高访问速度和并发量、使用消息队列削峰和降流等等。\n\n### 2.7 提前准备技术面试\n\n搞清楚自己面试中可能涉及哪些知识点、哪些知识点是重点。面试中哪些问题会被经常问到、自己该如何回答。(强烈不推荐背题，第一：通过背这种方式你能记住多少？能记住多久？第二：背题的方式的学习很难坚持下去！)\n\n### 2.7 面试之前做好定向复习\n\n所谓定向复习就是专门针对你要面试的公司来复习。比如你在面试之前可以在网上找找有没有你要面试的公司的面经。\n\n举个栗子：在我面试 ThoughtWorks 的前几天我就在网上找了一些关于 ThoughtWorks 的技术面的一些文章。然后知道了 ThoughtWorks 的技术面会让我们在之前做的作业的基础上增加一个或两个功能，所以我提前一天就把我之前做的程序重新重构了一下。然后在技术面的时候，简单的改了几行代码之后写个测试就完事了。如果没有提前准备，我觉得 20 分钟我很大几率会完不成这项任务。\n\n## 3 面试之后复盘\n\n如果失败，不要灰心；如果通过，切勿狂喜。面试和工作实际上是两回事，可能很多面试未通过的人，工作能力比你强的多，反之亦然。我个人觉得面试也像是一场全新的征程，失败和胜利都是平常之事。所以，劝各位不要因为面试失败而灰心、丧失斗志。也不要因为面试通过而沾沾自喜，等待你的将是更美好的未来，继续加油！\n"
  },
  {
    "path": "docs/interview/PreparingForInterview/如果面试官问你“你有什么问题问我吗？”时，你该如何回答.md",
    "content": "# 目录\n\n    * [这个问题对最终面试结果的影响到底大不大?](#这个问题对最终面试结果的影响到底大不大)\n    * [真诚一点,不要问太 Low 的问题](#真诚一点不要问太-low-的问题)\n    * [有哪些有价值的问题值得问?](#有哪些有价值的问题值得问)\n      * [面对HR或者其他Level比较低的面试官时](#面对hr或者其他level比较低的面试官时)\n      * [面对部门领导](#面对部门领导)\n      * [面对Level比较高的(比如总裁,老板)](#面对level比较高的比如总裁老板)\n    * [来个补充,顺便送个祝福给大家](#来个补充顺便送个祝福给大家)\n\n\n我还记得当时我去参加面试的时候，几乎每一场面试，特别是HR面和高管面的时候，面试官总是会在结尾问我:“问了你这么多问题了，你有什么问题问我吗？”。这个时候很多人内心就会陷入短暂的纠结中：我该问吗？不问的话面试官会不会对我影响不好？问什么问题？问这个问题会不会让面试官对我的影响不好啊？ \n\n![无奈](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-2/无奈.jpg)\n\n### 这个问题对最终面试结果的影响到底大不大?\n\n就技术面试而言，回答这个问题的时候，只要你不是触碰到你所面试的公司的雷区，那么我觉得这对你能不能拿到最终offer来说影响确实是不大的。我说这些并不代表你就可以直接对面试官说：“我没问题了。”，笔主当时面试的时候确实也说过挺多次“没问题要问了。”，最终也没有导致笔主被pass掉（可能是前面表现比较好，哈哈，自恋一下）。我现在回想起来，觉得自己当时做法其实挺不对的。面试本身就是一个双向选择的过程，你对这个问题的回答也会侧面反映出你对这次面试的上心程度，你的问题是否有价值，也影响了你最终的选择与公司是否选择你。\n\n面试官在技术面试中主要考察的还是你这样个人到底有没有胜任这个工作的能力以及你是否适合公司未来的发展需要，很多公司还需要你认同它的文化，我觉得你只要不是太笨，应该不会栽在这里。除非你和另外一个人在能力上相同，但是只能在你们两个人中选一个，那么这个问题才对你能不能拿到offer至关重要。有准备总比没准备好，给面试官留一个好的影响总归是没错的。\n\n但是，就非技术面试来说，我觉得好好回答这个问题对你最终的结果还是比较重要的。\n\n总的来说不管是技术面试还是非技术面试，如果你想赢得公司的青睐和尊重，我觉得我们都应该重视这个问题。\n\n### 真诚一点,不要问太 Low 的问题\n\n回答这个问题很重要的一点就是你没有必要放低自己的姿态问一些很虚或者故意讨好面试官的问题，也不要把自己从面经上学到的东西照搬下来使用。面试官也不是傻子，特别是那种特别有经验的面试官，你是真心诚意的问问题，还是从别处照搬问题来讨好面试官，人家可能一听就听出来了。总的来说，还是要真诚。除此之外，不要问太 Low 的问题，会显得你整个人格局比较小或者说你根本没有准备（侧面反映你对这家公司不上心，既然你不上心，为什么要要你呢）。举例几个比较 Low 的问题，大家看看自己有没有问过其中的问题：\n\n- 贵公司的主要业务是什么？（面试之前自己不知道提前网上查一下吗？）\n- 贵公司的男女比例如何？（考虑脱单？记住你是来工作的！）\n- 贵公司一年搞几次外出旅游？（你是来工作的，这些娱乐活动先别放在心上！）\n- ......\n\n### 有哪些有价值的问题值得问?\n\n针对这个问题。笔主专门找了几个专门做HR工作的小哥哥小姐姐们询问并且查阅了挺多前辈们的回答，然后结合自己的实际经历，我概括了下面几个比较适合问的问题。\n\n#### 面对HR或者其他Level比较低的面试官时\n\n1. **能不能谈谈你作为一个公司老员工对公司的感受?** (这个问题比较容易回答，不会让面试官陷入无话可说的尴尬境地。另外，从面试官的回答中你可以加深对这个公司的了解，让你更加清楚这个公司到底是不是你想的那样或者说你是否能适应这个公司的文化。除此之外，这样的问题在某种程度上还可以拉进你与面试官的距离。)\n2. **能不能问一下，你当时因为什么原因选择加入这家公司的呢或者说这家公司有哪些地方吸引你？有什么地方你觉得还不太好或者可以继续完善吗？** （类似第一个问题，都是问面试官个人对于公司的看法。）\n3. **我觉得我这次表现的不是太好，你有什么建议或者评价给我吗？**(这个是我常问的。我觉得说自己表现不好只是这个语境需要这样来说，这样可以显的你比较谦虚好学上进。)\n4. **接下来我会有一段空档期，有什么值得注意或者建议学习的吗？** （体现出你对工作比较上心，自助学习意识比较强。）\n5. **这个岗位为什么还在招人？** (岗位真实性和价值咨询)\n6. **大概什么时候能给我回复呢？** (终面的时候，如果面试官没有说的话，可以问一下)\n7. ......\n\n\n\n#### 面对部门领导\n\n1. **部门的主要人员分配以及对应的主要工作能简单介绍一下吗？** \n2. **未来如果我要加入这个团队，你对我的期望是什么？** （部门领导一般情况下是你的直属上级了，你以后和他打交道的机会应该是最多的。你问这个问题，会让他感觉你是一个对他的部门比较上心，比较有团体意识，并且愿意倾听的候选人。）\n3. **公司对新入职的员工的培养机制是什么样的呢？** （正规的公司一般都有培养机制，提前问一下是对你自己的负责也会显的你比较上心）\n4. **以您来看，这个岗位未来在公司内部的发展如何？** (在我看来，问这个问题也是对你自己的负责吧，谁不想发展前景更好的岗位呢？)\n5. **团队现在面临的最大挑战是什么？** (这样的问题不会暴露你对公司的不了解，并且也能让你对未来工作的挑战或困难有一个提前的预期。)\n\n\n\n#### 面对Level比较高的(比如总裁,老板)\n\n1. **贵公司的发展目标和方向是什么？** （看下公司的发展是否满足自己的期望）\n2. **与同行业的竞争者相比，贵公司的核心竞争优势在什么地方？** （充分了解自己的优势和劣势）\n3. **公司现在面临的最大挑战是什么？**\n\n### 来个补充,顺便送个祝福给大家\n\n薪酬待遇和相关福利问题一般在终面的时候（最好不要在前面几面的时候就问到这个问题），面试官会提出来或者在面试完之后以邮件的形式告知你。一般来说，如果面试官很愿意为你回答问题，对你的问题也比较上心的话，那他肯定是觉得你就是他们要招的人。\n\n大家在面试的时候，可以根据自己对于公司或者岗位的了解程度，对上面提到的问题进行适当修饰或者修改。上面提到的一些问题只是给没有经验的朋友一个参考，如果你还有其他比较好的问题的话，那当然也更好啦！\n\n金三银四。过了二月就到了面试高峰期或者说是黄金期。几份惊喜几份愁，愿各位能始终不忘初心！每个人都有每个人的难处。引用一句《阿甘正传》里面的台词：“生活就像一盒巧克力，你永远不知道下一块是什么味道“。\n\n![加油！彩虹就要来了](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-2/生活就像一盒巧克力你永远不知道下一块是什么味道.JPEG)\n"
  },
  {
    "path": "docs/interview/PreparingForInterview/程序员的简历之道.md",
    "content": "# 目录\n\n* [程序员简历就该这样写](#程序员简历就该这样写)\n  * [为什么说简历很重要？](#为什么说简历很重要？)\n    * [先从面试前来说](#先从面试前来说)\n    * [再从面试中来说](#再从面试中来说)\n  * [下面这几点你必须知道](#下面这几点你必须知道)\n  * [必须了解的两大法则](#必须了解的两大法则)\n    * [STAR法则（Situation Task Action Result）](#star法则（situation-task-action-result）)\n    * [FAB 法则（Feature Advantage Benefit）](#fab-法则（feature-advantage-benefit）)\n  * [项目经历怎么写？](#项目经历怎么写？)\n  * [专业技能该怎么写？](#专业技能该怎么写？)\n  * [排版注意事项](#排版注意事项)\n  * [其他的一些小tips](#其他的一些小tips)\n  * [推荐的工具/网站](#推荐的工具网站)\n\n\n<!-- TOC -->\n\n- [程序员简历就该这样写](#程序员简历就该这样写)\n    - [为什么说简历很重要？](#为什么说简历很重要)\n        - [先从面试前来说](#先从面试前来说)\n        - [再从面试中来说](#再从面试中来说)\n    - [下面这几点你必须知道](#下面这几点你必须知道)\n    - [必须了解的两大法则](#必须了解的两大法则)\n        - [STAR法则（Situation Task Action Result）](#star法则situation-task-action-result)\n        - [FAB 法则（Feature Advantage Benefit）](#fab-法则feature-advantage-benefit)\n    - [项目经历怎么写？](#项目经历怎么写)\n    - [专业技能该怎么写？](#专业技能该怎么写)\n    - [排版注意事项](#排版注意事项)\n    - [其他的一些小tips](#其他的一些小tips)\n    - [推荐的工具/网站](#推荐的工具网站)\n\n<!-- /TOC -->\n\n# 程序员简历就该这样写\n\n本篇文章除了教大家用Markdown如何写一份程序员专属的简历，后面还会给大家推荐一些不错的用来写Markdown简历的软件或者网站，以及如何优雅的将Markdown格式转变为PDF格式或者其他格式。\n\n推荐大家使用Markdown语法写简历，然后再将Markdown格式转换为PDF格式后进行简历投递。\n\n如果你对Markdown语法不太了解的话，可以花半个小时简单看一下Markdown语法说明: http://www.markdown.cn 。\n\n## 为什么说简历很重要？\n\n一份好的简历可以在整个申请面试以及面试过程中起到非常好的作用。 在不夸大自己能力的情况下，写出一份好的简历也是一项很棒的能力。为什么说简历很重要呢?\n\n### 先从面试前来说\n\n- 假如你是网申，你的简历必然会经过HR的筛选，一张简历HR可能也就花费10秒钟看一下，然后HR就会决定你这一关是Fail还是Pass。\n- 假如你是内推，如果你的简历没有什么优势的话，就算是内推你的人再用心，也无能为力。\n\n另外，就算你通过了筛选，后面的面试中，面试官也会根据你的简历来判断你究竟是否值得他花费很多时间去面试。\n\n所以，简历就像是我们的一个门面一样，它在很大程度上决定了你能否进入到下一轮的面试中。\n\n###  再从面试中来说\n\n我发现大家比较喜欢看面经 ，这点无可厚非，但是大部分面经都没告诉你很多问题都是在特定条件下才问的。举个简单的例子：一般情况下你的简历上注明你会的东西才会被问到（Java、数据结构、网络、算法这些基础是每个人必问的），比如写了你会 redis,那面试官就很大概率会问你 redis 的一些问题。比如：redis的常见数据类型及应用场景、redis是单线程为什么还这么快、 redis 和 memcached 的区别、redis 内存淘汰机制等等。\n\n所以，首先，你要明确的一点是：**你不会的东西就不要写在简历上**。另外，**你要考虑你该如何才能让你的亮点在简历中凸显出来**，比如：你在某某项目做了什么事情解决了什么问题（只要有项目就一定有要解决的问题）、你的某一个项目里使用了什么技术后整体性能和并发量提升了很多等等。\n\n面试和工作是两回事，聪明的人会把面试官往自己擅长的领域领，其他人则被面试官牵着鼻子走。虽说面试和工作是两回事，但是你要想要获得自己满意的 offer ，你自身的实力必须要强。\n\n## 下面这几点你必须知道\n\n1. 大部分公司的HR都说我们不看重学历（骗你的！），但是如果你的学校不出众的话，很难在一堆简历中脱颖而出，除非你的简历上有特别的亮点，比如：某某大厂的实习经历、获得了某某大赛的奖等等。\n2. **大部分应届生找工作的硬伤是没有工作经验或实习经历，所以如果你是应届生就不要错过秋招和春招。一旦错过，你后面就极大可能会面临社招，这个时候没有工作经验的你可能就会面临各种碰壁，导致找不到一个好的工作**\n3. **写在简历上的东西一定要慎重，这是面试官大量提问的地方；**\n4. **将自己的项目经历完美的展示出来非常重要。**\n\n## 必须了解的两大法则\n\n### STAR法则（Situation Task Action Result）\n\n- **Situation：** 事情是在什么情况下发生；\n- **Task:：** 你是如何明确你的任务的；\n- **Action：** 针对这样的情况分析，你采用了什么行动方式；\n- **Result：** 结果怎样，在这样的情况下你学习到了什么。\n\n简而言之，STAR法则，就是一种讲述自己故事的方式，或者说，是一个清晰、条理的作文模板。不管是什么，合理熟练运用此法则，可以轻松的对面试官描述事物的逻辑方式，表现出自己分析阐述问题的清晰性、条理性和逻辑性。\n\n### FAB 法则（Feature Advantage Benefit）\n\n- **Feature：** 是什么；\n- **Advantage：** 比别人好在哪些地方；\n- **Benefit：** 如果雇佣你，招聘方会得到什么好处。\n\n简单来说，这个法则主要是让你的面试官知道你的优势、招了你之后对公司有什么帮助。\n\n## 项目经历怎么写？\n\n简历上有一两个项目经历很正常，但是真正能把项目经历很好的展示给面试官的非常少。对于项目经历大家可以考虑从如下几点来写：\n\n1. 对项目整体设计的一个感受\n2. 在这个项目中你负责了什么、做了什么、担任了什么角色\n3. 从这个项目中你学会了那些东西，使用到了那些技术，学会了那些新技术的使用\n4. 另外项目描述中，最好可以体现自己的综合素质，比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的又或者说你在这个项目用了什么技术实现了什么功能比如:用redis做缓存提高访问速度和并发量、使用消息队列削峰和降流等等。\n\n## 专业技能该怎么写？\n\n先问一下你自己会什么，然后看看你意向的公司需要什么。一般HR可能并不太懂技术，所以他在筛选简历的时候可能就盯着你专业技能的关键词来看。对于公司有要求而你不会的技能，你可以花几天时间学习一下，然后在简历上可以写上自己了解这个技能。比如你可以这样写(下面这部分内容摘自我的简历，大家可以根据自己的情况做一些修改和完善)：\n\n- 计算机网络、数据结构、算法、操作系统等课内基础知识：掌握\n- Java 基础知识：掌握\n- JVM 虚拟机（Java内存区域、虚拟机垃圾算法、虚拟垃圾收集器、JVM内存管理）：掌握\n- 高并发、高可用、高性能系统开发：掌握\n- Struts2、Spring、Hibernate、Ajax、Mybatis、JQuery ：掌握\n- SSH 整合、SSM 整合、 SOA 架构：掌握\n- Dubbo： 掌握\n- Zookeeper: 掌握\n- 常见消息队列: 掌握\n- Linux：掌握\n- MySQL常见优化手段：掌握\n- Spring Boot +Spring Cloud +Docker:了解\n- Hadoop 生态相关技术中的 HDFS、Storm、MapReduce、Hive、Hbase ：了解\n- Python 基础、一些常见第三方库比如OpenCV、wxpy、wordcloud、matplotlib：熟悉\n\n## 排版注意事项\n\n1. 尽量简洁，不要太花里胡哨；\n2. 一些技术名词不要弄错了大小写比如MySQL不要写成mysql，Java不要写成java。这个在我看来还是比较忌讳的，所以一定要注意这个细节；\n3. 中文和数字英文之间加上空格的话看起来会舒服一点；\n\n## 其他的一些小tips\n\n1. 尽量避免主观表述，少一点语义模糊的形容词，尽量要简洁明了，逻辑结构清晰。\n2. 如果自己有博客或者个人技术栈点的话，写上去会为你加分很多。\n3. 如果自己的Github比较活跃的话，写上去也会为你加分很多。\n4. 注意简历真实性，一定不要写自己不会的东西，或者带有欺骗性的内容\n5. 项目经历建议以时间倒序排序，另外项目经历不在于多，而在于有亮点。\n6. 如果内容过多的话，不需要非把内容压缩到一页，保持排版干净整洁就可以了。\n7. 简历最后最好能加上：“感谢您花时间阅读我的简历，期待能有机会和您共事。”这句话，显的你会很有礼貌。\n\n## 推荐的工具/网站\n\n- 冷熊简历(MarkDown在线简历工具，可在线预览、编辑和生成PDF):<http://cv.ftqq.com/>\n- Typora+[Java程序员简历模板](https://github.com/geekcompany/ResumeSample/blob/master/java.md)\n"
  },
  {
    "path": "docs/interview/PreparingForInterview/美团面试常见问题总结.md",
    "content": "# 目录\n\n* [一 基础篇](#一-基础篇)\n  * [1. `System.out.println(3|9)`输出什么?](#1-`systemoutprintln39`输出什么)\n  * [2. 说一下转发(Forward)和重定向(Redirect)的区别](#2-说一下转发forward和重定向redirect的区别)\n  * [3. 在浏览器中输入url地址到显示主页的过程,整个过程会使用哪些协议](#3-在浏览器中输入url地址到显示主页的过程整个过程会使用哪些协议)\n  * [4. TCP 三次握手和四次挥手](#4-tcp-三次握手和四次挥手)\n      * [为什么要三次握手](#为什么要三次握手)\n      * [为什么要传回 SYN](#为什么要传回-syn)\n      * [传了 SYN,为啥还要传 ACK](#传了-syn为啥还要传-ack)\n      * [为什么要四次挥手](#为什么要四次挥手)\n  * [5. IP地址与MAC地址的区别](#5-ip地址与mac地址的区别)\n  * [6. HTTP请求,响应报文格式](#6-http请求响应报文格式)\n  * [7. 为什么要使用索引?索引这么多优点,为什么不对表中的每一个列创建一个索引呢?索引是如何提高查询速度的?说一下使用索引的注意事项?Mysql索引主要使用的两种数据结构?什么是覆盖索引?](#7-为什么要使用索引索引这么多优点为什么不对表中的每一个列创建一个索引呢索引是如何提高查询速度的说一下使用索引的注意事项mysql索引主要使用的两种数据结构什么是覆盖索引)\n  * [8. 进程与线程的区别是什么?进程间的几种通信方式说一下?线程间的几种通信方式知道不?](#8-进程与线程的区别是什么进程间的几种通信方式说一下线程间的几种通信方式知道不)\n  * [9. 为什么要用单例模式?手写几种线程安全的单例模式?](#9-为什么要用单例模式手写几种线程安全的单例模式)\n  * [10. 简单介绍一下bean;知道Spring的bean的作用域与生命周期吗?](#10-简单介绍一下bean知道spring的bean的作用域与生命周期吗)\n  * [11. Spring 中的事务传播行为了解吗?TransactionDefinition 接口中哪五个表示隔离级别的常量?](#11-spring-中的事务传播行为了解吗transactiondefinition-接口中哪五个表示隔离级别的常量)\n      * [事务传播行为](#事务传播行为)\n      * [隔离级别](#隔离级别)\n  * [12. SpringMVC 原理了解吗?](#12-springmvc-原理了解吗)\n  * [13. Spring AOP IOC 实现原理](#13-spring-aop-ioc-实现原理)\n* [二 进阶篇](#二-进阶篇)\n  * [1 消息队列MQ的套路](#1-消息队列mq的套路)\n    * [1.1 介绍一下消息队列MQ的应用场景/使用消息队列的好处](#11-介绍一下消息队列mq的应用场景使用消息队列的好处)\n      * [1)通过异步处理提高系统性能](#1通过异步处理提高系统性能)\n      * [2)降低系统耦合性](#2降低系统耦合性)\n    * [1.2 那么使用消息队列会带来什么问题?考虑过这些问题吗?](#12-那么使用消息队列会带来什么问题考虑过这些问题吗)\n    * [1.3 介绍一下你知道哪几种消息队列,该如何选择呢?](#13-介绍一下你知道哪几种消息队列该如何选择呢)\n    * [1.4 关于消息队列其他一些常见的问题展望](#14-关于消息队列其他一些常见的问题展望)\n  * [2 谈谈 InnoDB 和 MyIsam 两者的区别](#2-谈谈-innodb-和-myisam-两者的区别)\n    * [2.1 两者的对比](#21-两者的对比)\n    * [2.2 关于两者的总结](#22-关于两者的总结)\n  * [3 聊聊 Java 中的集合吧!](#3-聊聊-java-中的集合吧)\n    * [3.1 Arraylist 与 LinkedList 有什么不同?(注意加上从数据结构分析的内容)](#31-arraylist-与-linkedlist-有什么不同注意加上从数据结构分析的内容)\n    * [3.2 HashMap的底层实现](#32-hashmap的底层实现)\n      * [1)JDK1.8之前](#1jdk18之前)\n      * [2)JDK1.8之后](#2jdk18之后)\n    * [3.3 既然谈到了红黑树,你给我手绘一个出来吧,然后简单讲一下自己对于红黑树的理解](#33-既然谈到了红黑树你给我手绘一个出来吧然后简单讲一下自己对于红黑树的理解)\n    * [3.4 红黑树这么优秀,为何不直接使用红黑树得了?](#34-红黑树这么优秀为何不直接使用红黑树得了)\n    * [3.5 HashMap 和 Hashtable 的区别/HashSet 和 HashMap 区别](#35-hashmap-和-hashtable-的区别hashset-和-hashmap-区别)\n* [三 终结篇](#三-终结篇)\n  * [1. Object类有哪些方法?](#1-object类有哪些方法)\n    * [1.1 Object类的常见方法总结](#11-object类的常见方法总结)\n    * [1.2 hashCode与equals](#12-hashcode与equals)\n      * [1.2.1 hashCode()介绍](#121-hashcode介绍)\n      * [1.2.2 为什么要有hashCode](#122-为什么要有hashcode)\n      * [1.2.3 hashCode()与equals()的相关规定](#123-hashcode与equals的相关规定)\n      * [1.2.4 为什么两个对象有相同的hashcode值,它们也不一定是相等的?](#124-为什么两个对象有相同的hashcode值它们也不一定是相等的)\n    * [1.3  ==与equals](#13--与equals)\n  * [2 ConcurrentHashMap 相关问题](#2-concurrenthashmap-相关问题)\n    * [2.1 ConcurrentHashMap 和 Hashtable 的区别](#21-concurrenthashmap-和-hashtable-的区别)\n    * [2.2 ConcurrentHashMap线程安全的具体实现方式/底层具体实现](#22-concurrenthashmap线程安全的具体实现方式底层具体实现)\n      * [JDK1.7(上面有示意图)](#jdk17上面有示意图)\n      * [JDK1.8(上面有示意图)](#jdk18上面有示意图)\n  * [3 谈谈 synchronized 和 ReentrantLock 的区别](#3-谈谈-synchronized-和-reentrantlock-的区别)\n  * [4 线程池了解吗?](#4-线程池了解吗)\n    * [4.1 为什么要用线程池?](#41-为什么要用线程池)\n    * [4.2 Java 提供了哪几种线程池?他们各自的使用场景是什么?](#42-java-提供了哪几种线程池他们各自的使用场景是什么)\n      * [Java 主要提供了下面4种线程池](#java-主要提供了下面4种线程池)\n      * [各种线程池的适用场景介绍](#各种线程池的适用场景介绍)\n    * [4.3 创建的线程池的方式](#43-创建的线程池的方式)\n  * [5 Nginx](#5-nginx)\n    * [5.1 简单介绍一下Nginx](#51-简单介绍一下nginx)\n      * [反向代理](#反向代理)\n      * [负载均衡](#负载均衡)\n      * [动静分离](#动静分离)\n    * [5.2 为什么要用 Nginx?](#52-为什么要用-nginx)\n    * [5.3 Nginx 的四个主要组成部分了解吗?](#53-nginx-的四个主要组成部分了解吗)\n\n\n<!-- MarkdownTOC -->\n\n- [一 基础篇](#一-基础篇)\n  - [1. `System.out.println(3|9)`输出什么?](#1-systemoutprintln39输出什么)\n  - [2. 说一下转发\\(Forward\\)和重定向\\(Redirect\\)的区别](#2-说一下转发forward和重定向redirect的区别)\n  - [3. 在浏览器中输入url地址到显示主页的过程,整个过程会使用哪些协议](#3-在浏览器中输入url地址到显示主页的过程整个过程会使用哪些协议)\n  - [4. TCP 三次握手和四次挥手](#4-tcp-三次握手和四次挥手)\n      - [为什么要三次握手](#为什么要三次握手)\n      - [为什么要传回 SYN](#为什么要传回-syn)\n      - [传了 SYN,为啥还要传 ACK](#传了-syn为啥还要传-ack)\n      - [为什么要四次挥手](#为什么要四次挥手)\n  - [5. IP地址与MAC地址的区别](#5-ip地址与mac地址的区别)\n  - [6. HTTP请求,响应报文格式](#6-http请求响应报文格式)\n  - [7. 为什么要使用索引?索引这么多优点,为什么不对表中的每一个列创建一个索引呢?索引是如何提高查询速度的?说一下使用索引的注意事项?Mysql索引主要使用的两种数据结构?什么是覆盖索引?](#7-为什么要使用索引索引这么多优点为什么不对表中的每一个列创建一个索引呢索引是如何提高查询速度的说一下使用索引的注意事项mysql索引主要使用的两种数据结构什么是覆盖索引)\n  - [8. 进程与线程的区别是什么?进程间的几种通信方式说一下?线程间的几种通信方式知道不?](#8-进程与线程的区别是什么进程间的几种通信方式说一下线程间的几种通信方式知道不)\n  - [9. 为什么要用单例模式?手写几种线程安全的单例模式?](#9-为什么要用单例模式手写几种线程安全的单例模式)\n  - [10. 简单介绍一下bean;知道Spring的bean的作用域与生命周期吗?](#10-简单介绍一下bean知道spring的bean的作用域与生命周期吗)\n  - [11. Spring 中的事务传播行为了解吗?TransactionDefinition 接口中哪五个表示隔离级别的常量?](#11-spring-中的事务传播行为了解吗transactiondefinition-接口中哪五个表示隔离级别的常量)\n      - [事务传播行为](#事务传播行为)\n      - [隔离级别](#隔离级别)\n  - [12. SpringMVC 原理了解吗?](#12-springmvc-原理了解吗)\n  - [13. Spring AOP IOC 实现原理](#13-spring-aop-ioc-实现原理)\n- [二 进阶篇](#二-进阶篇)\n  - [1 消息队列MQ的套路](#1-消息队列mq的套路)\n    - [1.1 介绍一下消息队列MQ的应用场景/使用消息队列的好处](#11-介绍一下消息队列mq的应用场景使用消息队列的好处)\n      - [1)通过异步处理提高系统性能](#1通过异步处理提高系统性能)\n      - [2)降低系统耦合性](#2降低系统耦合性)\n    - [1.2 那么使用消息队列会带来什么问题?考虑过这些问题吗?](#12-那么使用消息队列会带来什么问题考虑过这些问题吗)\n    - [1.3 介绍一下你知道哪几种消息队列,该如何选择呢?](#13-介绍一下你知道哪几种消息队列该如何选择呢)\n    - [1.4 关于消息队列其他一些常见的问题展望](#14-关于消息队列其他一些常见的问题展望)\n  - [2 谈谈 InnoDB 和 MyIsam 两者的区别](#2-谈谈-innodb-和-myisam-两者的区别)\n    - [2.1 两者的对比](#21-两者的对比)\n    - [2.2 关于两者的总结](#22-关于两者的总结)\n  - [3 聊聊 Java 中的集合吧！](#3-聊聊-java-中的集合吧)\n    - [3.1 ArrayList 与 LinkedList 有什么不同?\\(注意加上从数据结构分析的内容\\)](#31-arraylist-与-linkedlist-有什么不同注意加上从数据结构分析的内容)\n    - [3.2 HashMap的底层实现](#32-hashmap的底层实现)\n      - [1)JDK1.8之前](#1jdk18之前)\n      - [2)JDK1.8之后](#2jdk18之后)\n    - [3.3 既然谈到了红黑树,你给我手绘一个出来吧,然后简单讲一下自己对于红黑树的理解](#33-既然谈到了红黑树你给我手绘一个出来吧然后简单讲一下自己对于红黑树的理解)\n    - [3.4 红黑树这么优秀,为何不直接使用红黑树得了?](#34-红黑树这么优秀为何不直接使用红黑树得了)\n    - [3.5 HashMap 和 Hashtable 的区别/HashSet 和 HashMap 区别](#35-hashmap-和-hashtable-的区别hashset-和-hashmap-区别)\n- [三 终结篇](#三-终结篇)\n  - [1. Object类有哪些方法?](#1-object类有哪些方法)\n    - [1.1 Object类的常见方法总结](#11-object类的常见方法总结)\n    - [1.2 hashCode与equals](#12-hashcode与equals)\n      - [1.2.1 hashCode\\(\\)介绍](#121-hashcode介绍)\n      - [1.2.2 为什么要有hashCode](#122-为什么要有hashcode)\n      - [1.2.3 hashCode\\(\\)与equals\\(\\)的相关规定](#123-hashcode与equals的相关规定)\n      - [1.2.4 为什么两个对象有相同的hashcode值,它们也不一定是相等的?](#124-为什么两个对象有相同的hashcode值它们也不一定是相等的)\n    - [1.3  ==与equals](#13-与equals)\n  - [2 ConcurrentHashMap 相关问题](#2-concurrenthashmap-相关问题)\n    - [2.1 ConcurrentHashMap 和 Hashtable 的区别](#21-concurrenthashmap-和-hashtable-的区别)\n    - [2.2 ConcurrentHashMap线程安全的具体实现方式/底层具体实现](#22-concurrenthashmap线程安全的具体实现方式底层具体实现)\n      - [JDK1.7\\(上面有示意图\\)](#jdk17上面有示意图)\n      - [JDK1.8\\(上面有示意图\\)](#jdk18上面有示意图)\n  - [3 谈谈 synchronized 和 ReenTrantLock 的区别](#3-谈谈-synchronized-和-reentrantlock-的区别)\n  - [4 线程池了解吗?](#4-线程池了解吗)\n    - [4.1 为什么要用线程池?](#41-为什么要用线程池)\n    - [4.2 Java 提供了哪几种线程池?他们各自的使用场景是什么?](#42-java-提供了哪几种线程池他们各自的使用场景是什么)\n      - [Java 主要提供了下面4种线程池](#java-主要提供了下面4种线程池)\n      - [各种线程池的适用场景介绍](#各种线程池的适用场景介绍)\n    - [4.3 创建的线程池的方式](#43-创建的线程池的方式)\n  - [5 Nginx](#5-nginx)\n    - [5.1 简单介绍一下Nginx](#51-简单介绍一下nginx)\n      - [反向代理](#反向代理)\n      - [负载均衡](#负载均衡)\n      - [动静分离](#动静分离)\n    - [5.2 为什么要用 Nginx?](#52-为什么要用-nginx)\n    - [5.3  Nginx 的四个主要组成部分了解吗?](#53-nginx-的四个主要组成部分了解吗)\n\n<!-- /MarkdownTOC -->\n\n\n这些问题是2018年去美团面试的同学被问到的一些常见的问题，希望对你有帮助！\n\n# 一 基础篇\n\n\n## 1. `System.out.println(3|9)`输出什么?\n\n正确答案：11。\n\n**考察知识点：&和&&；|和||**\n\n**&和&&：**\n\n共同点：两者都可做逻辑运算符。它们都表示运算符的两边都是true时，结果为true；\n\n不同点: &也是位运算符。& 表示在运算时两边都会计算，然后再判断；&&表示先运算符号左边的东西，然后判断是否为true，是true就继续运算右边的然后判断并输出，是false就停下来直接输出不会再运行后面的东西。\n\n**|和||：**\n\n共同点：两者都可做逻辑运算符。它们都表示运算符的两边任意一边为true，结果为true，两边都不是true，结果就为false；\n\n不同点：|也是位运算符。| 表示两边都会运算，然后再判断结果；|| 表示先运算符号左边的东西，然后判断是否为true，是true就停下来直接输出不会再运行后面的东西，是false就继续运算右边的然后判断并输出。\n\n**回到本题：**\n\n3 | 9=0011（二进制） | 1001（二进制）=1011（二进制）=11（十进制）\n\n## 2. 说一下转发(Forward)和重定向(Redirect)的区别\n\n**转发是服务器行为，重定向是客户端行为。**\n\n**转发（Forword）** 通过RequestDispatcher对象的`forward（HttpServletRequest request,HttpServletResponse response）`方法实现的。`RequestDispatcher` 可以通过`HttpServletRequest` 的 `getRequestDispatcher()`方法获得。例如下面的代码就是跳转到 login_success.jsp 页面。\n\n```java\nrequest.getRequestDispatcher(\"login_success.jsp\").forward(request, response);\n```\n\n**重定向（Redirect）** 是利用服务器返回的状态码来实现的。客户端浏览器请求服务器的时候，服务器会返回一个状态码。服务器通过HttpServletRequestResponse的setStatus(int status)方法设置状态码。如果服务器返回301或者302，则浏览器会到新的网址重新请求该资源。\n\n1. **从地址栏显示来说**：forward是服务器请求资源，服务器直接访问目标地址的URL，把那个URL的响应内容读取过来，然后把这些内容再发给浏览器。浏览器根本不知道服务器发送的内容从哪里来的，所以它的地址栏还是原来的地址。redirect是服务端根据逻辑，发送一个状态码，告诉浏览器重新去请求那个地址。所以地址栏显示的是新的URL。\n2. **从数据共享来说**：forward：转发页面和转发到的页面可以共享request里面的数据。redirect：不能共享数据。\n3. **从运用地方来说**：forward：一般用于用户登陆的时候，根据角色转发到相应的模块。redirect：一般用于用户注销登陆时返回主页面和跳转到其它的网站等。\n4. **从效率来说**：forward：高。redirect：低。\n\n\n## 3. 在浏览器中输入url地址到显示主页的过程,整个过程会使用哪些协议\n\n图片来源：《图解HTTP》：\n\n![状态码](https://user-gold-cdn.xitu.io/2018/4/19/162db5e985aabdbe?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)\n\n总体来说分为以下几个过程:\n\n1. DNS解析\n2. TCP连接\n3. 发送HTTP请求\n4. 服务器处理请求并返回HTTP报文\n5. 浏览器解析渲染页面\n6. 连接结束\n\n具体可以参考下面这篇文章：\n\n- [https://segmentfault.com/a/1190000006879700](https://segmentfault.com/a/1190000006879700)\n\n## 4. TCP 三次握手和四次挥手\n\n为了准确无误地把数据送达目标处，TCP协议采用了三次握手策略。\n\n**漫画图解：**\n\n图片来源：《图解HTTP》\n![TCP三次握手](https://user-gold-cdn.xitu.io/2018/5/8/1633e127396541f1?w=864&h=439&f=png&s=226095)\n\n**简单示意图：**\n![TCP三次握手](https://user-gold-cdn.xitu.io/2018/5/8/1633e14233d95972?w=542&h=427&f=jpeg&s=15088)\n\n- 客户端–发送带有 SYN 标志的数据包–一次握手–服务端\n- 服务端–发送带有 SYN/ACK 标志的数据包–二次握手–客户端\n- 客户端–发送带有带有 ACK 标志的数据包–三次握手–服务端\n\n#### 为什么要三次握手\n\n**三次握手的目的是建立可靠的通信信道，说到通讯，简单来说就是数据的发送与接收，而三次握手最主要的目的就是双方确认自己与对方的发送与接收是正常的。**\n\n第一次握手：Client 什么都不能确认；Server 确认了对方发送正常，自己接收正常。\n\n第二次握手：Client 确认了：自己发送、接收正常，对方发送、接收正常；Server 确认了：自己接收正常，对方发送正常\n\n第三次握手：Client 确认了：自己发送、接收正常，对方发送、接收正常；Server 确认了：自己发送、接收正常，对方发送、接收正常\n\n所以三次握手就能确认双发收发功能都正常，缺一不可。\n\n#### 为什么要传回 SYN\n接收端传回发送端所发送的 SYN 是为了告诉发送端，我接收到的信息确实就是你所发送的信号了。\n\n> SYN 是 TCP/IP 建立连接时使用的握手信号。在客户机和服务器之间建立正常的 TCP 网络连接时，客户机首先发出一个 SYN 消息，服务器使用 SYN-ACK 应答表示接收到了这个消息，最后客户机再以 ACK(Acknowledgement[汉译：确认字符 ,在数据通信传输中，接收站发给发送站的一种传输控制字符。它表示确认发来的数据已经接受无误。 ]）消息响应。这样在客户机和服务器之间才能建立起可靠的TCP连接，数据才可以在客户机和服务器之间传递。\n\n\n#### 传了 SYN,为啥还要传 ACK\n\n双方通信无误必须是两者互相发送信息都无误。传了 SYN，证明发送方（主动关闭方）到接收方（被动关闭方）的通道没有问题，但是接收方到发送方的通道还需要 ACK 信号来进行验证。\n\n![TCP四次挥手](https://user-gold-cdn.xitu.io/2018/5/8/1633e1676e2ac0a3?w=500&h=340&f=jpeg&s=13406)\n\n断开一个 TCP 连接则需要“四次挥手”：\n\n- 客户端-发送一个 FIN，用来关闭客户端到服务器的数据传送\n- 服务器-收到这个 FIN，它发回一 个 ACK，确认序号为收到的序号加1 。和 SYN 一样，一个 FIN 将占用一个序号\n- 服务器-关闭与客户端的连接，发送一个FIN给客户端\n- 客户端-发回 ACK 报文确认，并将确认序号设置为收到序号加1\n\n\n####  为什么要四次挥手\n\n任何一方都可以在数据传送结束后发出连接释放的通知，待对方确认后进入半关闭状态。当另一方也没有数据再发送的时候，则发出连接释放通知，对方确认后就完全关闭了TCP连接。\n\n举个例子：A 和 B 打电话，通话即将结束后，A 说“我没啥要说的了”，B回答“我知道了”，但是 B 可能还会有要说的话，A 不能要求 B 跟着自己的节奏结束通话，于是 B 可能又巴拉巴拉说了一通，最后 B 说“我说完了”，A 回答“知道了”，这样通话才算结束。\n\n上面讲的比较概括，推荐一篇讲的比较细致的文章：[https://blog.csdn.net/qzcsu/article/details/72861891](https://blog.csdn.net/qzcsu/article/details/72861891)\n\n\n\n## 5. IP地址与MAC地址的区别\n\n参考：[https://blog.csdn.net/guoweimelon/article/details/50858597](https://blog.csdn.net/guoweimelon/article/details/50858597)\n\nIP地址是指互联网协议地址（Internet Protocol Address）IP Address的缩写。IP地址是IP协议提供的一种统一的地址格式，它为互联网上的每一个网络和每一台主机分配一个逻辑地址，以此来屏蔽物理地址的差异。\n\n\n\nMAC 地址又称为物理地址、硬件地址，用来定义网络设备的位置。网卡的物理地址通常是由网卡生产厂家写入网卡的，具有全球唯一性。MAC地址用于在网络中唯一标示一个网卡，一台电脑会有一或多个网卡，每个网卡都需要有一个唯一的MAC地址。\n\n## 6. HTTP请求,响应报文格式\n\n\n\nHTTP请求报文主要由请求行、请求头部、请求正文3部分组成\n\nHTTP响应报文主要由状态行、响应头部、响应正文3部分组成\n\n详细内容可以参考：[https://blog.csdn.net/a19881029/article/details/14002273](https://blog.csdn.net/a19881029/article/details/14002273)\n\n## 7. 为什么要使用索引?索引这么多优点,为什么不对表中的每一个列创建一个索引呢?索引是如何提高查询速度的?说一下使用索引的注意事项?Mysql索引主要使用的两种数据结构?什么是覆盖索引?\n\n**为什么要使用索引？**\n\n1. 通过创建唯一性索引，可以保证数据库表中每一行数据的唯一性。\n2. 可以大大加快 数据的检索速度（大大减少的检索的数据量）,  这也是创建索引的最主要的原因。 \n3. 帮助服务器避免排序和临时表\n4. 将随机IO变为顺序IO\n5. 可以加速表和表之间的连接，特别是在实现数据的参考完整性方面特别有意义。\n\n**索引这么多优点，为什么不对表中的每一个列创建一个索引呢？**\n\n1. 当对表中的数据进行增加、删除和修改的时候，索引也要动态的维护，这样就降低了数据的维护速度。 \n2. 索引需要占物理空间，除了数据表占数据空间之外，每一个索引还要占一定的物理空间，如果要建立聚簇索引，那么需要的空间就会更大。 \n3. 创建索引和维护索引要耗费时间，这种时间随着数据量的增加而增加。 \n\n**索引是如何提高查询速度的？**\n\n将无序的数据变成相对有序的数据（就像查目录一样）\n\n**说一下使用索引的注意事项**\n\n1. 避免 where 子句中对字段施加函数，这会造成无法命中索引。\n2. 在使用InnoDB时使用与业务无关的自增主键作为主键，即使用逻辑主键，而不要使用业务主键。\n3. 将打算加索引的列设置为 NOT NULL ，否则将导致引擎放弃使用索引而进行全表扫描\n4. 删除长期未使用的索引，不用的索引的存在会造成不必要的性能损耗 MySQL 5.7 可以通过查询 sys 库的 schema_unused_indexes 视图来查询哪些索引从未被使用\n5. 在使用 limit offset 查询缓慢时，可以借助索引来提高性能\n\n**Mysql索引主要使用的哪两种数据结构？**\n\n- 哈希索引：对于哈希索引来说，底层的数据结构就是哈希表，因此在绝大多数需求为单条记录查询的时候，可以选择哈希索引，查询性能最快；其余大部分场景，建议选择BTree索引。\n- BTree索引：Mysql的BTree索引使用的是B树中的B+Tree。但对于主要的两种存储引擎（MyISAM和InnoDB）的实现方式是不同的。\n\n更多关于索引的内容可以查看我的这篇文章：[【思维导图-索引篇】搞定数据库索引就是这么简单](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484486&idx=1&sn=215450f11e042bca8a58eac9f4a97686&chksm=fd985227caefdb3117b8375f150676f5824aa20d1ebfdbcfb93ff06e23e26efbafae6cf6b48e&token=1990180468&lang=zh_CN#rd)\n\n**什么是覆盖索引?**\n\n如果一个索引包含（或者说覆盖）所有需要查询的字段的值，我们就称\n之为“覆盖索引”。我们知道在InnoDB存储引擎中，如果不是主键索引，叶子节点存储的是主键+列值。最终还是要“回表”，也就是要通过主键再查找一次,这样就会比较慢。覆盖索引就是把要查询出的列和索引是对应的，不做回表操作！\n\n\n## 8. 进程与线程的区别是什么?进程间的几种通信方式说一下?线程间的几种通信方式知道不?\n **进程与线程的区别是什么？**\n\n线程与进程相似，但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源，所以系统在产生一个线程，或是在各个线程之间作切换工作时，负担要比进程小得多，也正因为如此，线程也被称为轻量级进程。另外，也正是因为共享资源，所以线程中执行时一般都要进行同步和互斥。总的来说，进程和线程的主要差别在于它们是不同的操作系统资源管理方式。\n\n**进程间的几种通信方式说一下？**\n\n\n1. **管道（pipe）**：管道是一种半双工的通信方式，数据只能单向流动，而且只能在具有血缘关系的进程间使用。进程的血缘关系通常指父子进程关系。管道分为pipe（无名管道）和fifo（命名管道）两种，有名管道也是半双工的通信方式，但是它允许无亲缘关系进程间通信。\n2. **信号量（semophore）**：信号量是一个计数器，可以用来控制多个进程对共享资源的访问。它通常作为一种锁机制，防止某进程正在访问共享资源时，其他进程也访问该资源。因此，主要作为进程间以及同一进程内不同线程之间的同步手段。\n3. **消息队列（message queue）**：消息队列是由消息组成的链表，存放在内核中 并由消息队列标识符标识。消息队列克服了信号传递信息少，管道只能承载无格式字节流以及缓冲区大小受限等缺点。消息队列与管道通信相比，其优势是对每个消息指定特定的消息类型，接收的时候不需要按照队列次序，而是可以根据自定义条件接收特定类型的消息。\n4. **信号（signal）**：信号是一种比较复杂的通信方式，用于通知接收进程某一事件已经发生。\n5. **共享内存（shared memory）**：共享内存就是映射一段能被其他进程所访问的内存，这段共享内存由一个进程创建，但多个进程都可以访问，共享内存是最快的IPC方式，它是针对其他进程间的通信方式运行效率低而专门设计的。它往往与其他通信机制，如信号量配合使用，来实现进程间的同步和通信。\n6. **套接字（socket）**：socket，即套接字是一种通信机制，凭借这种机制，客户/服务器（即要进行通信的进程）系统的开发工作既可以在本地单机上进行，也可以跨网络进行。也就是说它可以让不在同一台计算机但通过网络连接计算机上的进程进行通信。也因为这样，套接字明确地将客户端和服务器区分开来。\n\n**线程间的几种通信方式知道不？**\n\n1、锁机制\n\n- 互斥锁：提供了以排它方式阻止数据结构被并发修改的方法。\n- 读写锁：允许多个线程同时读共享数据，而对写操作互斥。\n- 条件变量：可以以原子的方式阻塞进程，直到某个特定条件为真为止。对条件测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。\n\n2、信号量机制：包括无名线程信号量与有名线程信号量\n\n3、信号机制：类似于进程间的信号处理。\n\n线程间通信的主要目的是用于线程同步，所以线程没有象进程通信中用于数据交换的通信机制。\n\n## 9. 为什么要用单例模式?手写几种线程安全的单例模式?\n\n**简单来说使用单例模式可以带来下面几个好处:**\n\n- 对于频繁使用的对象，可以省略创建对象所花费的时间，这对于那些重量级对象而言，是非常可观的一笔系统开销；\n- 由于 new 操作的次数减少，因而对系统内存的使用频率也会降低，这将减轻 GC 压力，缩短 GC 停顿时间。\n\n**懒汉式(双重检查加锁版本)**\n\n```java\npublic class Singleton {\n\n    //volatile保证，当uniqueInstance变量被初始化成Singleton实例时，多个线程可以正确处理uniqueInstance变量\n    private volatile static Singleton uniqueInstance;\n    private Singleton() {\n    }\n    public static Singleton getInstance() {\n       //检查实例，如果不存在，就进入同步代码块\n        if (uniqueInstance == null) {\n            //只有第一次才彻底执行这里的代码\n            synchronized(Singleton.class) {\n               //进入同步代码块后，再检查一次，如果仍是null，才创建实例\n                if (uniqueInstance == null) {\n                    uniqueInstance = new Singleton();\n                }\n            }\n        }\n        return uniqueInstance;\n    }\n}\n```\n\n**静态内部类方式**\n\n静态内部实现的单例是懒加载的且线程安全。\n\n只有通过显式调用 getInstance 方法时，才会显式装载 SingletonHolder 类，从而实例化 instance（只有第一次使用这个单例的实例的时候才加载，同时不会有线程安全问题）。\n\n```java\npublic class Singleton {  \n    private static class SingletonHolder {  \n    private static final Singleton INSTANCE = new Singleton();  \n    }  \n    private Singleton (){}  \n    public static final Singleton getInstance() {  \n    return SingletonHolder.INSTANCE;  \n    }  \n}   \n```\n\n## 10. 简单介绍一下bean;知道Spring的bean的作用域与生命周期吗?\n\n在 Spring 中，那些组成应用程序的主体及由 Spring IOC 容器所管理的对象，被称之为 bean。简单地讲，bean 就是由 IOC 容器初始化、装配及管理的对象，除此之外，bean 就与应用程序中的其他对象没有什么区别了。而 bean 的定义以及 bean 相互间的依赖关系将通过配置元数据来描述。\n\nSpring中的bean默认都是单例的，这些单例Bean在多线程程序下如何保证线程安全呢？ 例如对于Web应用来说，Web容器对于每个用户请求都创建一个单独的Sevlet线程来处理请求，引入Spring框架之后，每个Action都是单例的，那么对于Spring托管的单例Service Bean，如何保证其安全呢？ Spring的单例是基于BeanFactory也就是Spring容器的，单例Bean在此容器内只有一个，Java的单例是基于 JVM，每个 JVM 内只有一个实例。\n\n![pring的bean的作用域](https://user-gold-cdn.xitu.io/2018/11/10/166fd45773d5dd2e?w=563&h=299&f=webp&s=27930)\n\nSpring的bean的生命周期以及更多内容可以查看：[一文轻松搞懂Spring中bean的作用域与生命周期](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484400&idx=2&sn=7201eb365102fce017f89cb3527fb0bc&chksm=fd985591caefdc872a2fac897288119f94c345e4e12150774f960bf5f816b79e4b9b46be3d7f&token=1990180468&lang=zh_CN#rd)\n\n\n## 11. Spring 中的事务传播行为了解吗?TransactionDefinition 接口中哪五个表示隔离级别的常量?\n\n#### 事务传播行为\n\n事务传播行为（为了解决业务层方法之间互相调用的事务问题）：\n当事务方法被另一个事务方法调用时，必须指定事务应该如何传播。例如：方法可能继续在现有事务中运行，也可能开启一个新事务，并在自己的事务中运行。在TransactionDefinition定义中包括了如下几个表示传播行为的常量：\n\n**支持当前事务的情况：**\n\n- TransactionDefinition.PROPAGATION_REQUIRED： 如果当前存在事务，则加入该事务；如果当前没有事务，则创建一个新的事务。\n- TransactionDefinition.PROPAGATION_SUPPORTS： 如果当前存在事务，则加入该事务；如果当前没有事务，则以非事务的方式继续运行。\n- TransactionDefinition.PROPAGATION_MANDATORY： 如果当前存在事务，则加入该事务；如果当前没有事务，则抛出异常。（mandatory：强制性）\n\n**不支持当前事务的情况：**\n\n- TransactionDefinition.PROPAGATION_REQUIRES_NEW： 创建一个新的事务，如果当前存在事务，则把当前事务挂起。\n- TransactionDefinition.PROPAGATION_NOT_SUPPORTED： 以非事务方式运行，如果当前存在事务，则把当前事务挂起。\n- TransactionDefinition.PROPAGATION_NEVER： 以非事务方式运行，如果当前存在事务，则抛出异常。\n\n**其他情况：**\n\n- TransactionDefinition.PROPAGATION_NESTED： 如果当前存在事务，则创建一个事务作为当前事务的嵌套事务来运行；如果当前没有事务，则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。\n\n\n#### 隔离级别\n\nTransactionDefinition 接口中定义了五个表示隔离级别的常量：\n\n- **TransactionDefinition.ISOLATION_DEFAULT:**  使用后端数据库默认的隔离级别，Mysql 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别.\n- **TransactionDefinition.ISOLATION_READ_UNCOMMITTED:** 最低的隔离级别，允许读取尚未提交的数据变更，可能会导致脏读、幻读或不可重复读\n- **TransactionDefinition.ISOLATION_READ_COMMITTED:**   允许读取并发事务已经提交的数据，可以阻止脏读，但是幻读或不可重复读仍有可能发生\n- **TransactionDefinition.ISOLATION_REPEATABLE_READ:**  对同一字段的多次读取结果都是一致的，除非数据是被本身事务自己所修改，可以阻止脏读和不可重复读，但幻读仍有可能发生。\n- **TransactionDefinition.ISOLATION_SERIALIZABLE:**   最高的隔离级别，完全服从ACID的隔离级别。所有的事务依次逐个执行，这样事务之间就完全不可能产生干扰，也就是说，该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。\n\n## 12. SpringMVC 原理了解吗?\n\n![SpringMVC 原理](https://user-gold-cdn.xitu.io/2018/11/10/166fd45787394192?w=1015&h=466&f=webp&s=35352)\n\n客户端发送请求-> 前端控制器 DispatcherServlet 接受客户端请求 -> 找到处理器映射 HandlerMapping 解析请求对应的 Handler-> HandlerAdapter 会根据 Handler 来调用真正的处理器处理请求，并处理相应的业务逻辑 -> 处理器返回一个模型视图 ModelAndView -> 视图解析器进行解析 -> 返回一个视图对象->前端控制器 DispatcherServlet 渲染数据（Model）->将得到视图对象返回给用户\n\n关于 SpringMVC 原理更多内容可以查看我的这篇文章：[SpringMVC 工作原理详解](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484496&idx=1&sn=5472ffa687fe4a05f8900d8ee6726de4&chksm=fd985231caefdb27fc75b44ecf76b6f43e4617e0b01b3c040f8b8fab32e51dfa5118eed1d6ad&token=1990180468&lang=zh_CN#rd)\n\n## 13. Spring AOP IOC 实现原理\n\n过了秋招挺长一段时间了，说实话我自己也忘了如何简要概括 Spring AOP IOC 实现原理，就在网上找了一个较为简洁的答案，下面分享给各位。\n\n**IOC：** 控制反转也叫依赖注入。IOC利用java反射机制，AOP利用代理模式。IOC 概念看似很抽象，但是很容易理解。说简单点就是将对象交给容器管理，你只需要在spring配置文件中配置对应的bean以及设置相关的属性，让spring容器来生成类的实例对象以及管理对象。在spring容器启动的时候，spring会把你在配置文件中配置的bean都初始化好，然后在你需要调用的时候，就把它已经初始化好的那些bean分配给你需要调用这些bean的类。\n\n**AOP：** 面向切面编程。（Aspect-Oriented Programming） 。AOP可以说是对OOP的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构，用以模拟公共行为的一个集合。实现AOP的技术，主要分为两大类：一是采用动态代理技术，利用截取消息的方式，对该消息进行装饰，以取代原有对象行为的执行；二是采用静态织入的方式，引入特定的语法创建“方面”，从而使得编译器可以在编译期间织入有关“方面”的代码，属于静态代理。\n\n\n\n# 二 进阶篇\n\n## 1 消息队列MQ的套路\n\n消息队列/消息中间件应该是Java程序员必备的一个技能了，如果你之前没接触过消息队列的话，建议先去百度一下某某消息队列入门，然后花2个小时就差不多可以学会任何一种消息队列的使用了。如果说仅仅学会使用是万万不够的，在实际生产环境还要考虑消息丢失等等情况。关于消息队列面试相关的问题，推荐大家也可以看一下视频《Java工程师面试突击第1季-中华石杉老师》，如果大家没有资源的话，可以在我的公众号“Java面试通关手册”后台回复关键字“1”即可！\n\n### 1.1 介绍一下消息队列MQ的应用场景/使用消息队列的好处\n\n面试官一般会先问你这个问题，预热一下，看你知道消息队列不，一般在第一面的时候面试官可能只会问消息队列MQ的应用场景/使用消息队列的好处、使用消息队列会带来什么问题、消息队列的技术选型这几个问题，不会太深究下去，在后面的第二轮/第三轮技术面试中可能会深入问一下。\n\n**《大型网站技术架构》第四章和第七章均有提到消息队列对应用性能及扩展性的提升。**\n\n#### 1)通过异步处理提高系统性能\n![通过异步处理提高系统性能](https://user-gold-cdn.xitu.io/2018/4/21/162e63a8e34ba534?w=910&h=350&f=jpeg&s=29123)\n如上图，**在不使用消息队列服务器的时候，用户的请求数据直接写入数据库，在高并发的情况下数据库压力剧增，使得响应速度变慢。但是在使用消息队列之后，用户的请求数据发送给消息队列之后立即 返回，再由消息队列的消费者进程从消息队列中获取数据，异步写入数据库。由于消息队列服务器处理速度快于数据库（消息队列也比数据库有更好的伸缩性），因此响应速度得到大幅改善。**\n\n通过以上分析我们可以得出**消息队列具有很好的削峰作用的功能**——即**通过异步处理，将短时间高并发产生的事务消息存储在消息队列中，从而削平高峰期的并发事务。** 举例：在电子商务一些秒杀、促销活动中，合理使用消息队列可以有效抵御促销活动刚开始大量订单涌入对系统的冲击。如下图所示：\n![合理使用消息队列可以有效抵御促销活动刚开始大量订单涌入对系统的冲击](https://user-gold-cdn.xitu.io/2018/4/21/162e64583dd3ed01?w=780&h=384&f=jpeg&s=13550)\n因为**用户请求数据写入消息队列之后就立即返回给用户了，但是请求数据在后续的业务校验、写数据库等操作中可能失败**。因此使用消息队列进行异步处理之后，需要**适当修改业务流程进行配合**，比如**用户在提交订单之后，订单数据写入消息队列，不能立即返回用户订单提交成功，需要在消息队列的订单消费者进程真正处理完该订单之后，甚至出库后，再通过电子邮件或短信通知用户订单成功**，以免交易纠纷。这就类似我们平时手机订火车票和电影票。\n\n#### 2)降低系统耦合性\n我们知道模块分布式部署以后聚合方式通常有两种：1.**分布式消息队列**和2.**分布式服务**。\n\n> **先来简单说一下分布式服务：**\n\n目前使用比较多的用来构建**SOA（Service Oriented Architecture面向服务体系结构）**的**分布式服务框架**是阿里巴巴开源的**Dubbo**。如果想深入了解Dubbo的可以看我写的关于Dubbo的这一篇文章：**《高性能优秀的服务框架-dubbo介绍》**：[https://juejin.im/post/5acadeb1f265da2375072f9c](https://juejin.im/post/5acadeb1f265da2375072f9c)\n\n> **再来谈我们的分布式消息队列：**\n\n我们知道如果模块之间不存在直接调用，那么新增模块或者修改模块就对其他模块影响较小，这样系统的可扩展性无疑更好一些。\n\n我们最常见的**事件驱动架构**类似生产者消费者模式，在大型网站中通常用利用消息队列实现事件驱动结构。如下图所示：\n![利用消息队列实现事件驱动结构](https://user-gold-cdn.xitu.io/2018/4/21/162e6665fa394b3b?w=790&h=290&f=jpeg&s=14946)\n**消息队列使利用发布-订阅模式工作，消息发送者（生产者）发布消息，一个或多个消息接受者（消费者）订阅消息。** 从上图可以看到**消息发送者（生产者）和消息接受者（消费者）之间没有直接耦合**，消息发送者将消息发送至分布式消息队列即结束对消息的处理，消息接受者从分布式消息队列获取该消息后进行后续处理，并不需要知道该消息从何而来。**对新增业务，只要对该类消息感兴趣，即可订阅该消息，对原有系统和业务没有任何影响，从而实现网站业务的可扩展性设计**。\n\n消息接受者对消息进行过滤、处理、包装后，构造成一个新的消息类型，将消息继续发送出去，等待其他消息接受者订阅该消息。因此基于事件（消息对象）驱动的业务架构可以是一系列流程。\n\n**另外为了避免消息队列服务器宕机造成消息丢失，会将成功发送到消息队列的消息存储在消息生产者服务器上，等消息真正被消费者服务器处理后才删除消息。在消息队列服务器宕机后，生产者服务器会选择分布式消息队列服务器集群中的其他服务器发布消息。**   \n\n**备注：** 不要认为消息队列只能利用发布-订阅模式工作，只不过在解耦这个特定业务环境下是使用发布-订阅模式的，**比如在我们的ActiveMQ消息队列中还有点对点工作模式**，具体的会在后面的文章给大家详细介绍，这一篇文章主要还是让大家对消息队列有一个更透彻的了解。\n\n> 这个问题一般会在上一个问题问完之后，紧接着被问到。“使用消息队列会带来什么问题？”这个问题要引起重视，一般我们都会考虑使用消息队列会带来的好处而忽略它带来的问题！\n\n### 1.2 那么使用消息队列会带来什么问题?考虑过这些问题吗?\n\n- **系统可用性降低：** 系统可用性在某种程度上降低，为什么这样说呢？在加入MQ之前，你不用考虑消息丢失或者说MQ挂掉等等的情况，但是，引入MQ之后你就需要去考虑了！\n- **系统复杂性提高：** 加入MQ之后，你需要保证消息没有被重复消费、处理消息丢失的情况、保证消息传递的顺序性等等问题！\n- **一致性问题：** 我上面讲了消息队列可以实现异步，消息队列带来的异步确实可以提高系统响应速度。但是，万一消息的真正消费者并没有正确消费消息怎么办？这样就会导致数据不一致的情况了!\n\n> 了解下面这个问题是为了我们更好的进行技术选型！该部分摘自：《Java工程师面试突击第1季-中华石杉老师》，如果大家没有资源的话，可以在我的公众号“Java面试通关手册”后台回复关键字“1”即可！\n\n### 1.3 介绍一下你知道哪几种消息队列,该如何选择呢?\n\n\n| 特性                    |                                                     ActiveMQ |                                                     RabbitMQ |                                                     RocketMQ |                                                       Kafaka |\n| :---------------------- | -----------------------------------------------------------: | -----------------------------------------------------------: | -----------------------------------------------------------: | -----------------------------------------------------------: |\n| 单机吞吐量              |                万级，吞吐量比RocketMQ和Kafka要低了一个数量级 |                万级，吞吐量比RocketMQ和Kafka要低了一个数量级 |                   10万级，RocketMQ也是可以支撑高吞吐的一种MQ | 10万级别，这是kafka最大的优点，就是吞吐量高。一般配合大数据类的系统来进行实时数据计算、日志采集等场景 |\n| topic数量对吞吐量的影响 |                                                              |                                                              | topic可以达到几百，几千个的级别，吞吐量会有较小幅度的下降这是RocketMQ的一大优势，在同等机器下，可以支撑大量的topic | topic从几十个到几百个的时候，吞吐量会大幅度下降。所以在同等机器下，kafka尽量保证topic数量不要过多。如果要支撑大规模topic，需要增加更多的机器资源 |\n| 可用性                  |                                 高，基于主从架构实现高可用性 |                                 高，基于主从架构实现高可用性 |                                           非常高，分布式架构 | 非常高，kafka是分布式的，一个数据多个副本，少数机器宕机，不会丢失数据，不会导致不可用 |\n| 消息可靠性              |                                         有较低的概率丢失数据 |                                                              |                              经过参数优化配置，可以做到0丢失 |                          经过参数优化配置，消息可以做到0丢失 |\n| 时效性                  |                                                         ms级 |                 微秒级，这是rabbitmq的一大特点，延迟是最低的 |                                                         ms级 |                                               延迟在ms级以内 |\n| 功能支持                |                                         MQ领域的功能极其完备 |       基于erlang开发，所以并发能力很强，性能极其好，延时很低 |                       MQ功能较为完善，还是分布式的，扩展性好 | 功能较为简单，主要支持简单的MQ功能，在大数据领域的实时计算以及日志采集被大规模使用，是事实上的标准 |\n| 优劣势总结              | 非常成熟，功能强大，在业内大量的公司以及项目中都有应用。偶尔会有较低概率丢失消息，而且现在社区以及国内应用都越来越少，官方社区现在对ActiveMQ 5.x维护越来越少，几个月才发布一个版本而且确实主要是基于解耦和异步来用的，较少在大规模吞吐的场景中使用 | erlang语言开发，性能极其好，延时很低；吞吐量到万级，MQ功能比较完备而且开源提供的管理界面非常棒，用起来很好用。社区相对比较活跃，几乎每个月都发布几个版本分在国内一些互联网公司近几年用rabbitmq也比较多一些但是问题也是显而易见的，RabbitMQ确实吞吐量会低一些，这是因为他做的实现机制比较重。而且erlang开发，国内有几个公司有实力做erlang源码级别的研究和定制？如果说你没这个实力的话，确实偶尔会有一些问题，你很难去看懂源码，你公司对这个东西的掌控很弱，基本职能依赖于开源社区的快速维护和修复bug。而且rabbitmq集群动态扩展会很麻烦，不过这个我觉得还好。其实主要是erlang语言本身带来的问题。很难读源码，很难定制和掌控。 | 接口简单易用，而且毕竟在阿里大规模应用过，有阿里品牌保障。日处理消息上百亿之多，可以做到大规模吞吐，性能也非常好，分布式扩展也很方便，社区维护还可以，可靠性和可用性都是ok的，还可以支撑大规模的topic数量，支持复杂MQ业务场景。而且一个很大的优势在于，阿里出品都是java系的，我们可以自己阅读源码，定制自己公司的MQ，可以掌控。社区活跃度相对较为一般，不过也还可以，文档相对来说简单一些，然后接口这块不是按照标准JMS规范走的有些系统要迁移需要修改大量代码。还有就是阿里出台的技术，你得做好这个技术万一被抛弃，社区黄掉的风险，那如果你们公司有技术实力我觉得用RocketMQ挺好的 | kafka的特点其实很明显，就是仅仅提供较少的核心功能，但是提供超高的吞吐量，ms级的延迟，极高的可用性以及可靠性，而且分布式可以任意扩展。同时kafka最好是支撑较少的topic数量即可，保证其超高吞吐量。而且kafka唯一的一点劣势是有可能消息重复消费，那么对数据准确性会造成极其轻微的影响，在大数据领域中以及日志采集中，这点轻微影响可以忽略这个特性天然适合大数据实时计算以及日志收集。 |\n\n> 这部分内容，我这里不给出答案，大家可以自行根据自己学习的消息队列查阅相关内容，我可能会在后面的文章中介绍到这部分内容。另外，下面这些问题在视频《Java工程师面试突击第1季-中华石杉老师》中都有提到，如果大家没有资源的话，可以在我的公众号“Java面试通关手册”后台回复关键字“1”即可！\n\n### 1.4 关于消息队列其他一些常见的问题展望\n\n1.  引入消息队列之后如何保证高可用性？\n2.  如何保证消息不被重复消费呢？\n3.  如何保证消息的可靠性传输（如何处理消息丢失的问题）？\n4.  我该怎么保证从消息队列里拿到的数据按顺序执行？\n5.  如何解决消息队列的延时以及过期失效问题？消息队列满了以后该怎么处理？有几百万消息持续积压几小时，说说怎么解决？\n6.  如果让你来开发一个消息队列中间件，你会怎么设计架构？\n\n\n\n## 2 谈谈 InnoDB 和 MyIsam 两者的区别\n\n### 2.1 两者的对比\n\n1.  **count运算上的区别：** 因为MyISAM缓存有表meta-data（行数等），因此在做COUNT(*)时对于一个结构很好的查询是不需要消耗多少资源的。而对于InnoDB来说，则没有这种缓存\n2. **是否支持事务和崩溃后的安全恢复：** MyISAM 强调的是性能，每次查询具有原子性，其执行速度比InnoDB类型更快，但是不提供事务支持。但是 InnoDB 提供事务支持，外部键等高级数据库功能。 具有事务(commit)、回滚(rollback)和崩溃修复能力(crash recovery capabilities)的事务安全(transaction-safe (ACID compliant))型表。\n3. **是否支持外键：** MyISAM不支持，而InnoDB支持。\n\n\n### 2.2 关于两者的总结\n\nMyISAM更适合读密集的表，而InnoDB更适合写密集的表。 在数据库做主从分离的情况下，经常选择MyISAM作为主库的存储引擎。\n\n一般来说，如果需要事务支持，并且有较高的并发读取频率(MyISAM的表锁的粒度太大，所以当该表写并发量较高时，要等待的查询就会很多了)，InnoDB是不错的选择。如果你的数据量很大（MyISAM支持压缩特性可以减少磁盘的空间占用），而且不需要支持事务时，MyISAM是最好的选择。\n\n\n## 3 聊聊 Java 中的集合吧!\n\n### 3.1 Arraylist 与 LinkedList 有什么不同?(注意加上从数据结构分析的内容)\n\n- **1. 是否保证线程安全：** ArrayList 和 LinkedList 都是不同步的，也就是不保证线程安全；\n- **2. 底层数据结构：** Arraylist 底层使用的是Object数组；LinkedList 底层使用的是双向链表数据结构（注意双向链表和双向循环链表的区别：）；\n- **3. 插入和删除是否受元素位置的影响：** ① **ArrayList 采用数组存储，所以插入和删除元素的时间复杂度受元素位置的影响。** 比如：执行`add(E e)  `方法的时候， ArrayList 会默认在将指定的元素追加到此列表的末尾，这种情况时间复杂度就是O(1)。但是如果要在指定位置 i 插入和删除元素的话（`add(int index, E element) `）时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。 ② **LinkedList 采用链表存储，所以插入，删除元素时间复杂度不受元素位置的影响，都是近似 O(1) 而数组为近似 O(n) 。**\n- **4. 是否支持快速随机访问：** LinkedList 不支持高效的随机元素访问，而 ArrayList 支持。快速随机访问就是通过元素的序号快速获取元素对象（对应于`get(int index) `方法）。\n- **5. 内存空间占用：** ArrayList的空 间浪费主要体现在在list列表的结尾会预留一定的容量空间，而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间（因为要存放直接后继和直接前驱以及数据）。 \n\n**补充内容:RandomAccess接口**\n\n```java\npublic interface RandomAccess {\n}\n```\n\n查看源码我们发现实际上 RandomAccess 接口中什么都没有定义。所以，在我看来 RandomAccess 接口不过是一个标识罢了。标识什么？ 标识实现这个接口的类具有随机访问功能。\n\n在 binarySearch() 方法中，它要判断传入的 list 是否RamdomAccess的实例，如果是，调用 indexedBinarySearch() 方法，如果不是，那么调用 iteratorBinarySearch() 方法\n\n```java\n    public static <T>\n    int binarySearch(List<? extends Comparable<? super T>> list, T key) {\n        if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)\n            return Collections.indexedBinarySearch(list, key);\n        else\n            return Collections.iteratorBinarySearch(list, key);\n    }\n\n```\nArraysList 实现了 RandomAccess 接口， 而 LinkedList 没有实现。为什么呢？我觉得还是和底层数据结构有关！ArraysList 底层是数组，而 LinkedList 底层是链表。数组天然支持随机访问，时间复杂度为 O(1) ，所以称为快速随机访问。链表需要遍历到特定位置才能访问特定位置的元素，时间复杂度为 O(n) ，所以不支持快速随机访问。，ArraysList 实现了 RandomAccess 接口，就表明了他具有快速随机访问功能。 RandomAccess 接口只是标识，并不是说 ArraysList 实现 RandomAccess 接口才具有快速随机访问功能的！\n\n\n\n**下面再总结一下 list 的遍历方式选择：**\n\n- 实现了RandomAccess接口的list，优先选择普通for循环 ，其次foreach,\n- 未实现RandomAccess接口的ist， 优先选择iterator遍历（foreach遍历底层也是通过iterator实现的），大size的数据，千万不要使用普通for循环\n\n> Java 中的集合这类问题几乎是面试必问的，问到这类问题的时候，HashMap 又是几乎必问的问题，所以大家一定要引起重视！\n\n###  3.2 HashMap的底层实现\n\n#### 1)JDK1.8之前\n\nJDK1.8 之前 HashMap 底层是 **数组和链表** 结合在一起使用也就是 **链表散列**。**HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash  值，然后通过 `(n - 1) & hash` 判断当前元素存放的位置（这里的 n 指的时数组的长度），如果当前位置存在元素的话，就判断该元素与要存入的元素的 hash 值以及 key 是否相同，如果相同的话，直接覆盖，不相同就通过拉链法解决冲突。**\n\n**所谓扰动函数指的就是 HashMap 的 hash 方法。使用 hash 方法也就是扰动函数是为了防止一些实现比较差的 hashCode() 方法 换句话说使用扰动函数之后可以减少碰撞。**\n\n**JDK 1.8 HashMap 的 hash 方法源码:**\n\nJDK 1.8 的 hash方法 相比于 JDK 1.7 hash 方法更加简化，但是原理不变。\n\n```java\n      static final int hash(Object key) {\n        int h;\n        // key.hashCode()：返回散列值也就是hashcode\n        // ^ ：按位异或\n        // >>>:无符号右移，忽略符号位，空位都以0补齐\n        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);\n    }\n```\n对比一下 JDK1.7的 HashMap 的 hash 方法源码.\n\n```java\nstatic int hash(int h) {\n    // This function ensures that hashCodes that differ only by\n    // constant multiples at each bit position have a bounded\n    // number of collisions (approximately 8 at default load factor).\n\n    h ^= (h >>> 20) ^ (h >>> 12);\n    return h ^ (h >>> 7) ^ (h >>> 4);\n}\n```\n\n相比于 JDK1.8 的 hash 方法 ，JDK 1.7 的 hash 方法的性能会稍差一点点，因为毕竟扰动了 4 次。\n\n所谓 **“拉链法”** 就是：将链表和数组相结合。也就是说创建一个链表数组，数组中每一格就是一个链表。若遇到哈希冲突，则将冲突的值加到链表中即可。\n\n\n\n![jdk1.8之前的内部结构](https://user-gold-cdn.xitu.io/2018/3/20/16240dbcc303d872?w=348&h=427&f=png&s=10991)\n\n\n#### 2)JDK1.8之后\n\n相比于之前的版本， JDK1.8之后在解决哈希冲突时有了较大的变化，当链表长度大于阈值（默认为8）时，将链表转化为红黑树，以减少搜索时间。\n\n![JDK1.8之后的HashMap底层数据结构](https://user-gold-cdn.xitu.io/2018/11/14/16711ac29c351da9?w=720&h=545&f=jpeg&s=23933)\n\nTreeMap、TreeSet以及JDK1.8之后的HashMap底层都用到了红黑树。红黑树就是为了解决二叉查找树的缺陷，因为二叉查找树在某些情况下会退化成一个线性结构。\n\n> 问完 HashMap 的底层原理之后，面试官可能就会紧接着问你 HashMap 底层数据结构相关的问题！\n\n###  3.3 既然谈到了红黑树,你给我手绘一个出来吧,然后简单讲一下自己对于红黑树的理解\n\n![红黑树](https://user-gold-cdn.xitu.io/2018/11/14/16711ac29c138cba?w=851&h=614&f=jpeg&s=34458)\n\n**红黑树特点:**\n\n1.  每个节点非红即黑；\n2.  根节点总是黑色的；\n3.  每个叶子节点都是黑色的空节点（NIL节点）；\n4.  如果节点是红色的，则它的子节点必须是黑色的（反之不一定）；\n5.  从根节点到叶节点或空子节点的每条路径，必须包含相同数目的黑色节点（即相同的黑色高度）\n\n\n**红黑树的应用：**\n\nTreeMap、TreeSet以及JDK1.8之后的HashMap底层都用到了红黑树。\n    \n**为什么要用红黑树**\n     \n简单来说红黑树就是为了解决二叉查找树的缺陷，因为二叉查找树在某些情况下会退化成一个线性结构。\n\n\n###  3.4 红黑树这么优秀,为何不直接使用红黑树得了?\n\n说一下自己对于这个问题的看法：我们知道红黑树属于（自）平衡二叉树，但是为了保持“平衡”是需要付出代价的，红黑树在插入新数据后可能需要通过左旋，右旋、变色这些操作来保持平衡，这费事啊。你说说我们引入红黑树就是为了查找数据快，如果链表长度很短的话，根本不需要引入红黑树的，你引入之后还要付出代价维持它的平衡。但是链表过长就不一样了。至于为什么选 8 这个值呢？通过概率统计所得，这个值是综合查询成本和新增元素成本得出的最好的一个值。\n\n### 3.5 HashMap 和 Hashtable 的区别/HashSet 和 HashMap 区别\n\n**HashMap 和 Hashtable 的区别**\n\n1.  **线程是否安全：** HashMap 是非线程安全的，Hashtable 是线程安全的；Hashtable 内部的方法基本都经过  `synchronized`  修饰。（如果你要保证线程安全的话就使用 ConcurrentHashMap 吧！）；\n2.  **效率：** 因为线程安全的问题，HashMap 要比 Hashtable 效率高一点。另外，Hashtable 基本被淘汰，不要在代码中使用它；\n3.  **对Null key 和Null value的支持：** HashMap 中，null 可以作为键，这样的键只有一个，可以有一个或多个键所对应的值为 null。但是在 Hashtable 中 put 进的键值只要有一个 null，直接抛出 NullPointerException。\n4.  **初始容量大小和每次扩充容量大小的不同 ：**   ①创建时如果不指定容量初始值，Hashtable 默认的初始大小为11，之后每次扩充，容量变为原来的2n+1。HashMap 默认的初始化大小为16。之后每次扩充，容量变为原来的2倍。②创建时如果给定了容量初始值，那么 Hashtable 会直接使用你给定的大小，而 HashMap 会将其扩充为2的幂次方大小（HashMap 中的`tableSizeFor()`方法保证，下面给出了源代码）。也就是说 HashMap 总是使用2的幂作为哈希表的大小,后面会介绍到为什么是2的幂次方。\n5.  **底层数据结构：** JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化，当链表长度大于阈值（默认为8）时，将链表转化为红黑树，以减少搜索时间。Hashtable 没有这样的机制。\n\n**HashSet 和 HashMap 区别**\n\n如果你看过 HashSet 源码的话就应该知道：HashSet 底层就是基于 HashMap 实现的。（HashSet 的源码非常非常少，因为除了 clone() 方法、writeObject()方法、readObject()方法是 HashSet 自己不得不实现之外，其他方法都是直接调用 HashMap 中的方法。）\n\n![HashSet 和 HashMap 区别](https://user-gold-cdn.xitu.io/2018/3/2/161e717d734f3b23?w=896&h=363&f=jpeg&s=205536)\n\n# 三 终结篇\n\n## 1. Object类有哪些方法?\n\n这个问题，面试中经常出现。我觉得不论是出于应付面试还是说更好地掌握Java这门编程语言，大家都要掌握！\n\n### 1.1 Object类的常见方法总结\n\nObject类是一个特殊的类，是所有类的父类。它主要提供了以下11个方法：\n\n```java\n\npublic final native Class<?> getClass()//native方法，用于返回当前运行时对象的Class对象，使用了final关键字修饰，故不允许子类重写。\n\npublic native int hashCode() //native方法，用于返回对象的哈希码，主要使用在哈希表中，比如JDK中的HashMap。\npublic boolean equals(Object obj)//用于比较2个对象的内存地址是否相等，String类对该方法进行了重写用户比较字符串的值是否相等。\n\nprotected native Object clone() throws CloneNotSupportedException//naitive方法，用于创建并返回当前对象的一份拷贝。一般情况下，对于任何对象 x，表达式 x.clone() != x 为true，x.clone().getClass() == x.getClass() 为true。Object本身没有实现Cloneable接口，所以不重写clone方法并且进行调用的话会发生CloneNotSupportedException异常。\n\npublic String toString()//返回类的名字@实例的哈希码的16进制的字符串。建议Object所有的子类都重写这个方法。\n\npublic final native void notify()//native方法，并且不能重写。唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。如果有多个线程在等待只会任意唤醒一个。\n\npublic final native void notifyAll()//native方法，并且不能重写。跟notify一样，唯一的区别就是会唤醒在此对象监视器上等待的所有线程，而不是一个线程。\n\npublic final native void wait(long timeout) throws InterruptedException//native方法，并且不能重写。暂停线程的执行。注意：sleep方法没有释放锁，而wait方法释放了锁 。timeout是等待时间。\n\npublic final void wait(long timeout, int nanos) throws InterruptedException//多了nanos参数，这个参数表示额外时间（以毫微秒为单位，范围是 0-999999）。 所以超时的时间还需要加上nanos毫秒。\n\npublic final void wait() throws InterruptedException//跟之前的2个wait方法一样，只不过该方法一直等待，没有超时时间这个概念\n\nprotected void finalize() throws Throwable { }//实例被垃圾回收器回收的时候触发的操作\n\n```\n\n> 问完上面这个问题之后，面试官很可能紧接着就会问你“hashCode与equals”相关的问题。\n\n### 1.2 hashCode与equals\n\n面试官可能会问你：“你重写过 hashcode 和 equals 么，为什么重写equals时必须重写hashCode方法？”\n\n#### 1.2.1 hashCode()介绍\n\nhashCode() 的作用是获取哈希码，也称为散列码；它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中，这就意味着Java中的任何类都包含有hashCode() 函数。另外需要注意的是： Object 的 hashcode 方法是本地方法，也就是用 c 语言或 c++ 实现的，该方法通常用来将对象的 内存地址 转换为整数之后返回。\n\n```java\n    public native int hashCode();\n```\n\n散列表存储的是键值对(key-value)，它的特点是：能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码！（可以快速找到所需要的对象）\n\n#### 1.2.2 为什么要有hashCode\n\n\n**我们以“HashSet如何检查重复”为例子来说明为什么要有hashCode：**\n\n当你把对象加入HashSet时，HashSet会先计算对象的hashcode值来判断对象加入的位置，同时也会与其他已经加入的对象的hashcode值作比较，如果没有相符的hashcode，HashSet会假设对象没有重复出现。但是如果发现有相同hashcode值的对象，这时会调用equals（）方法来检查hashcode相等的对象是否真的相同。如果两者相同，HashSet就不会让其加入操作成功。如果不同的话，就会重新散列到其他位置。（摘自我的Java启蒙书《Head fist java》第二版）。这样我们就大大减少了equals的次数，相应就大大提高了执行速度。\n\n\n#### 1.2.3 hashCode()与equals()的相关规定\n\n1. 如果两个对象相等，则hashcode一定也是相同的\n2. 两个对象相等，对两个对象分别调用equals方法都返回true\n3. 两个对象有相同的hashcode值，它们也不一定是相等的\n4. **因此，equals方法被覆盖过，则hashCode方法也必须被覆盖**\n5. hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode()，则该class的两个对象无论如何都不会相等（即使这两个对象指向相同的数据）\n\n#### 1.2.4 为什么两个对象有相同的hashcode值,它们也不一定是相等的?\n\n在这里解释一位小伙伴的问题。以下内容摘自《Head Fisrt Java》。\n\n因为hashCode() 所使用的杂凑算法也许刚好会让多个对象传回相同的杂凑值。越糟糕的杂凑算法越容易碰撞，但这也与数据值域分布的特性有关（所谓碰撞也就是指的是不同的对象得到相同的 hashCode）。 \n\n我们刚刚也提到了 HashSet,如果 HashSet 在对比的时候，同样的 hashcode 有多个对象，它会使用 equals() 来判断是否真的相同。也就是说 hashcode 只是用来缩小查找成本。\n\n>  ==与equals 的对比也是比较常问的基础问题之一！\n\n### 1.3  ==与equals\n\n**==** : 它的作用是判断两个对象的地址是不是相等。即，判断两个对象是不是同一个对象。(基本数据类型==比较的是值，引用数据类型==比较的是内存地址)\n\n**equals()** : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况：\n\n-  情况1：类没有覆盖equals()方法。则通过equals()比较该类的两个对象时，等价于通过“==”比较这两个对象。\n-  情况2：类覆盖了equals()方法。一般，我们都覆盖equals()方法来两个对象的内容相等；若它们的内容相等，则返回true(即，认为这两个对象相等)。\n\n\n**举个例子：**\n\n```java\npublic class test1 {\n    public static void main(String[] args) {\n        String a = new String(\"ab\"); // a 为一个引用\n        String b = new String(\"ab\"); // b为另一个引用,对象的内容一样\n        String aa = \"ab\"; // 放在常量池中\n        String bb = \"ab\"; // 从常量池中查找\n        if (aa == bb) // true\n            System.out.println(\"aa==bb\");\n        if (a == b) // false，非同一对象\n            System.out.println(\"a==b\");\n        if (a.equals(b)) // true\n            System.out.println(\"aEQb\");\n        if (42 == 42.0) { // true\n            System.out.println(\"true\");\n        }\n    }\n}\n```\n\n**说明：**\n\n- String中的equals()方法是被重写过的，因为Object的equals()方法是比较的对象的内存地址，而String的equals()方法比较的是对象的值。\n- 当创建String类型的对象时，虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象，如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个String对象。\n\n> 在[【备战春招/秋招系列5】美团面经总结进阶篇 （附详解答案）](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484625&idx=1&sn=9c4fa1f7d4291a5fbd7daa44bac2b012&chksm=fd9852b0caefdba6edcf9a827aa4a17ddc97bf6ad2e5ee6f7e1aa1b443b54444d05d2b76732b&token=723699735&lang=zh_CN#rd) 这篇文章中，我们已经提到了一下关于 HashMap 在面试中常见的问题：HashMap 的底层实现、简单讲一下自己对于红黑树的理解、红黑树这么优秀，为何不直接使用红黑树得了、HashMap 和 Hashtable 的区别/HashSet 和 HashMap 区别。HashMap 和 ConcurrentHashMap 这俩兄弟在一般只要面试中问到集合相关的问题就一定会被问到，所以各位务必引起重视！\n\n## 2 ConcurrentHashMap 相关问题\n\n### 2.1 ConcurrentHashMap 和 Hashtable 的区别\n\nConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的方式上不同。\n\n- **底层数据结构：** JDK1.7的 ConcurrentHashMap 底层采用 **分段的数组+链表** 实现，JDK1.8 采用的数据结构跟HashMap1.8的结构一样，数组+链表/红黑二叉树。Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似都是采用 **数组+链表** 的形式，数组是 HashMap 的主体，链表则是主要为了解决哈希冲突而存在的；\n- **实现线程安全的方式（重要）：** ① **在JDK1.7的时候，ConcurrentHashMap（分段锁）** 对整个桶数组进行了分割分段(Segment)，每一把锁只锁容器其中一部分数据，多线程访问容器里不同数据段的数据，就不会存在锁竞争，提高并发访问率。（默认分配16个Segment，比Hashtable效率提高16倍。） **到了 JDK1.8 的时候已经摒弃了Segment的概念，而是直接用 Node 数组+链表+红黑树的数据结构来实现，并发控制使用 synchronized 和 CAS 来操作。（JDK1.6以后 对 synchronized锁做了很多优化）**  整个看起来就像是优化过且线程安全的 HashMap，虽然在JDK1.8中还能看到 Segment 的数据结构，但是已经简化了属性，只是为了兼容旧版本；② **Hashtable(同一把锁)**：使用 synchronized 来保证线程安全，效率非常低下。当一个线程访问同步方法时，其他线程也访问同步方法，可能会进入阻塞或轮询状态，如使用 put 添加元素，另一个线程不能使用 put 添加元素，也不能使用 get，竞争会越来越激烈效率越低。\n\n**两者的对比图：** \n\n图片来源：http://www.cnblogs.com/chengxiao/p/6842045.html\n\nHashtable：\n![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-22/50656681.jpg)\n\nJDK1.7的ConcurrentHashMap：\n![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-22/33120488.jpg)\nJDK1.8的ConcurrentHashMap（TreeBin: 红黑二叉树节点\nNode: 链表节点）：\n![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-22/97739220.jpg)\n\n###  2.2 ConcurrentHashMap线程安全的具体实现方式/底层具体实现\n\n#### JDK1.7(上面有示意图)\n\n首先将数据分为一段一段的存储，然后给每一段数据配一把锁，当一个线程占用锁访问其中一个段数据时，其他段的数据也能被其他线程访问。\n\n**ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成**。\n\nSegment 实现了 ReentrantLock，所以 Segment 是一种可重入锁，扮演锁的角色。HashEntry 用于存储键值对数据。\n\n```java\nstatic class Segment<K,V> extends ReentrantLock implements Serializable {\n}\n```\n\n一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和HashMap类似，是一种数组和链表结构，一个 Segment 包含一个 HashEntry  数组，每个 HashEntry 是一个链表结构的元素，每个 Segment 守护着一个HashEntry数组里的元素，当对 HashEntry 数组的数据进行修改时，必须首先获得对应的 Segment的锁。\n\n#### JDK1.8(上面有示意图)\n\nConcurrentHashMap取消了Segment分段锁，采用CAS和synchronized来保证并发安全。数据结构跟HashMap1.8的结构类似，数组+链表/红黑二叉树。\n\nsynchronized只锁定当前链表或红黑二叉树的首节点，这样只要hash不冲突，就不会产生并发，效率又提升N倍。\n\n## 3 谈谈 synchronized 和 ReentrantLock 的区别\n\n**① 两者都是可重入锁**\n\n两者都是可重入锁。“可重入锁”概念是：自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁，此时这个对象锁还没有释放，当其再次想要获取这个对象的锁的时候还是可以获取的，如果不可锁重入的话，就会造成死锁。同一个线程每次获取锁，锁的计数器都自增1，所以要等到锁的计数器下降为0时才能释放锁。\n\n**② synchronized 依赖于 JVM 而 ReentrantLock 依赖于 API**\n\nsynchronized 是依赖于 JVM 实现的，前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化，但是这些优化都是在虚拟机层面实现的，并没有直接暴露给我们。ReentrantLock 是 JDK 层面实现的（也就是 API 层面，需要 lock() 和 unlock() 方法配合 try/finally 语句块来完成），所以我们可以通过查看它的源代码，来看它是如何实现的。\n\n**③ ReentrantLock 比 synchronized 增加了一些高级功能**\n\n相比synchronized，ReentrantLock增加了一些高级功能。主要来说主要有三点：**①等待可中断；②可实现公平锁；③可实现选择性通知（锁可以绑定多个条件）**\n\n- **ReentrantLock提供了一种能够中断等待锁的线程的机制**，通过 lock.lockInterruptibly() 来实现这个机制。也就是说正在等待的线程可以选择放弃等待，改为处理其他事情。\n- **ReentrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。** ReentrantLock默认情况是非公平的，可以通过 ReentrantLock类的`ReentrantLock(boolean fair)`构造方法来制定是否是公平的。\n- synchronized关键字与wait()和notify/notifyAll()方法相结合可以实现等待/通知机制，ReentrantLock类当然也可以实现，但是需要借助于Condition接口与newCondition() 方法。Condition是JDK1.5之后才有的，它具有很好的灵活性，比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例（即对象监视器），**线程对象可以注册在指定的Condition中，从而可以有选择性的进行线程通知，在调度线程上更加灵活。 在使用notify/notifyAll()方法进行通知时，被通知的线程是由 JVM 选择的，用ReentrantLock类结合Condition实例可以实现“选择性通知”** ，这个功能非常重要，而且是Condition接口默认提供的。而synchronized关键字就相当于整个Lock对象中只有一个Condition实例，所有的线程都注册在它一个身上。如果执行notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题，而Condition实例的signalAll()方法 只会唤醒注册在该Condition实例中的所有等待线程。\n\n如果你想使用上述功能，那么选择ReentrantLock是一个不错的选择。\n\n**④ 两者的性能已经相差无几**\n\n在JDK1.6之前，synchronized 的性能是比 ReentrantLock 差很多。具体表示为：synchronized 关键字吞吐量岁线程数的增加，下降得非常严重。而ReentrantLock 基本保持一个比较稳定的水平。我觉得这也侧面反映了， synchronized 关键字还有非常大的优化余地。后续的技术发展也证明了这一点，我们上面也讲了在 JDK1.6 之后 JVM 团队对 synchronized 关键字做了很多优化。JDK1.6 之后，synchronized 和 ReentrantLock 的性能基本是持平了。所以网上那些说因为性能才选择 ReentrantLock 的文章都是错的！JDK1.6之后，性能已经不是选择synchronized和ReentrantLock的影响因素了！而且虚拟机在未来的性能改进中会更偏向于原生的synchronized，所以还是提倡在synchronized能满足你的需求的情况下，优先考虑使用synchronized关键字来进行同步！优化后的synchronized和ReentrantLock一样，在很多地方都是用到了CAS操作。\n\n\n## 4 线程池了解吗?\n\n\n### 4.1 为什么要用线程池?\n\n线程池提供了一种限制和管理资源（包括执行一个任务）。 每个线程池还维护一些基本统计信息，例如已完成任务的数量。 \n\n这里借用《Java并发编程的艺术》提到的来说一下使用线程池的好处：\n\n- **降低资源消耗。** 通过重复利用已创建的线程降低线程创建和销毁造成的消耗。\n- **提高响应速度。** 当任务到达时，任务可以不需要的等到线程创建就能立即执行。\n- **提高线程的可管理性。** 线程是稀缺资源，如果无限制的创建，不仅会消耗系统资源，还会降低系统的稳定性，使用线程池可以进行统一的分配，调优和监控。\n\n### 4.2 Java 提供了哪几种线程池?他们各自的使用场景是什么?\n\n#### Java 主要提供了下面4种线程池\n\n- **FixedThreadPool：** 该方法返回一个固定线程数量的线程池。该线程池中的线程数量始终不变。当有一个新的任务提交时，线程池中若有空闲线程，则立即执行。若没有，则新的任务会被暂存在一个任务队列中，待有线程空闲时，便处理在任务队列中的任务。\n- **SingleThreadExecutor：** 方法返回一个只有一个线程的线程池。若多余一个任务被提交到该线程池，任务会被保存在一个任务队列中，待线程空闲，按先入先出的顺序执行队列中的任务。\n- **CachedThreadPool：** 该方法返回一个可根据实际情况调整线程数量的线程池。线程池的线程数量不确定，但若有空闲线程可以复用，则会优先使用可复用的线程。若所有线程均在工作，又有新的任务提交，则会创建新的线程处理任务。所有线程在当前任务执行完毕后，将返回线程池进行复用。\n- **ScheduledThreadPoolExecutor：** 主要用来在给定的延迟后运行任务，或者定期执行任务。ScheduledThreadPoolExecutor又分为：ScheduledThreadPoolExecutor（包含多个线程）和SingleThreadScheduledExecutor （只包含一个线程）两种。\n\n#### 各种线程池的适用场景介绍\n\n- **FixedThreadPool：** 适用于为了满足资源管理需求，而需要限制当前线程数量的应用场景。它适用于负载比较重的服务器；\n- **SingleThreadExecutor：** 适用于需要保证顺序地执行各个任务并且在任意时间点，不会有多个线程是活动的应用场景；\n- **CachedThreadPool：** 适用于执行很多的短期异步任务的小程序，或者是负载较轻的服务器；\n- **ScheduledThreadPoolExecutor：** 适用于需要多个后台执行周期任务，同时为了满足资源管理需求而需要限制后台线程的数量的应用场景；\n- **SingleThreadScheduledExecutor：** 适用于需要单个后台线程执行周期任务，同时保证顺序地执行各个任务的应用场景。\n\n### 4.3 创建的线程池的方式\n\n**（1） 使用 Executors 创建**\n\n我们上面刚刚提到了 Java 提供的几种线程池，通过 Executors 工具类我们可以很轻松的创建我们上面说的几种线程池。但是实际上我们一般都不是直接使用Java提供好的线程池，另外在《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建，而是通过 ThreadPoolExecutor 构造函数 的方式，这样的处理方式让写的同学更加明确线程池的运行规则，规避资源耗尽的风险。\n\n```java\nExecutors 返回线程池对象的弊端如下：\n\nFixedThreadPool 和 SingleThreadExecutor ： 允许请求的队列长度为 Integer.MAX_VALUE,可能堆积大量的请求，从而导致OOM。\nCachedThreadPool 和 ScheduledThreadPool ： 允许创建的线程数量为 Integer.MAX_VALUE ，可能会创建大量线程，从而导致OOM。\n\n```\n**（2） ThreadPoolExecutor的构造函数创建**\n\n\n我们可以自己直接调用 ThreadPoolExecutor 的构造函数来自己创建线程池。在创建的同时，给 BlockQueue 指定容量就可以了。示例如下：\n\n```java\nprivate static ExecutorService executor = new ThreadPoolExecutor(13, 13,\n        60L, TimeUnit.SECONDS,\n        new ArrayBlockingQueue(13));\n```\n\n这种情况下，一旦提交的线程数超过当前可用线程数时，就会抛出java.util.concurrent.RejectedExecutionException，这是因为当前线程池使用的队列是有边界队列，队列已经满了便无法继续处理新的请求。但是异常（Exception）总比发生错误（Error）要好。\n\n**（3） 使用开源类库**\n\nHollis 大佬之前在他的文章中也提到了：“除了自己定义ThreadPoolExecutor外。还有其他方法。这个时候第一时间就应该想到开源类库，如apache和guava等。”他推荐使用guava提供的ThreadFactoryBuilder来创建线程池。下面是参考他的代码示例：\n\n```java\npublic class ExecutorsDemo {\n\n    private static ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()\n        .setNameFormat(\"demo-pool-%d\").build();\n\n    private static ExecutorService pool = new ThreadPoolExecutor(5, 200,\n        0L, TimeUnit.MILLISECONDS,\n        new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());\n\n    public static void main(String[] args) {\n\n        for (int i = 0; i < Integer.MAX_VALUE; i++) {\n            pool.execute(new SubThread());\n        }\n    }\n}\n```\n\n通过上述方式创建线程时，不仅可以避免OOM的问题，还可以自定义线程名称，更加方便的出错的时候溯源。\n\n## 5 Nginx \n\n### 5.1 简单介绍一下Nginx \n\nNginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件（IMAP/POP3）代理服务器。 Nginx  主要提供反向代理、负载均衡、动静分离(静态资源服务)等服务。下面我简单地介绍一下这些名词。\n\n#### 反向代理\n\n谈到反向代理，就不得不提一下正向代理。无论是正向代理，还是反向代理，说到底，就是代理模式的衍生版本罢了\n\n- **正向代理：**某些情况下，代理我们用户去访问服务器，需要用户手动的设置代理服务器的ip和端口号。正向代理比较常见的一个例子就是 VPN 了。\n- **反向代理：** 是用来代理服务器的，代理我们要访问的目标服务器。代理服务器接受请求，然后将请求转发给内部网络的服务器，并将从服务器上得到的结果返回给客户端，此时代理服务器对外就表现为一个服务器。\n\n通过下面两幅图，大家应该更好理解（图源：http://blog.720ui.com/2016/nginx_action_05_proxy/）：\n\n![正向代理](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-15/60925795.jpg)\n\n![反向代理](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-15/62563930.jpg)\n\n所以，简单的理解，就是正向代理是为客户端做代理，代替客户端去访问服务器，而反向代理是为服务器做代理，代替服务器接受客户端请求。\n\n#### 负载均衡\n\n在高并发情况下需要使用，其原理就是将并发请求分摊到多个服务器执行，减轻每台服务器的压力，多台服务器(集群)共同完成工作任务，从而提高了数据的吞吐量。\n\nNginx支持的weight轮询（默认）、ip_hash、fair、url_hash这四种负载均衡调度算法，感兴趣的可以自行查阅。\n\n负载均衡相比于反向代理更侧重的是将请求分担到多台服务器上去，所以谈论负载均衡只有在提供某服务的服务器大于两台时才有意义。\n\n#### 动静分离\n\n动静分离是让动态网站里的动态网页根据一定规则把不变的资源和经常变的资源区分开来，动静资源做好了拆分以后，我们就可以根据静态资源的特点将其做缓存操作，这就是网站静态化处理的核心思路。\n\n### 5.2 为什么要用 Nginx?\n\n> 这部分内容参考极客时间—[Nginx核心知识100讲的内容](https://time.geekbang.org/course/intro/138?code=AycjiiQk6uQRxnVJzBupFkrGkvZlmYELPRsZbWzaAHE=)。\n\n如果面试官问你这个问题，就一定想看你知道 Nginx 服务器的一些优点吗。\n\nNginx 有以下5个优点：\n\n1. 高并发、高性能（这是其他web服务器不具有的）\n2. 可扩展性好（模块化设计，第三方插件生态圈丰富）\n3. 高可靠性（可以在服务器行持续不间断的运行数年）\n4. 热部署（这个功能对于 Nginx 来说特别重要，热部署指可以在不停止 Nginx服务的情况下升级 Nginx）\n5. BSD许可证（意味着我们可以将源代码下载下来进行修改然后使用自己的版本）\n\n### 5.3 Nginx 的四个主要组成部分了解吗?\n\n> 这部分内容参考极客时间—[Nginx核心知识100讲的内容](https://time.geekbang.org/course/intro/138?code=AycjiiQk6uQRxnVJzBupFkrGkvZlmYELPRsZbWzaAHE=)。\n\n- Nginx 二进制可执行文件：由各模块源码编译出一个文件\n- nginx.conf 配置文件：控制Nginx 行为\n- acess.log 访问日志： 记录每一条HTTP请求信息\n- error.log 错误日志：定位问题\n"
  },
  {
    "path": "docs/monitor/Spring Actuator.md",
    "content": "## ǰ\n\n΢ϵͳܹУļǱزٵġĿǰ΢ӦǻSpring CloudϵУҲ˵ǻSpring BootϵеġʱʹSpring Boot Actuator΢ļأȫ棬ҷǳ㡣\n\nƪ¡[Spring Boot Actuatorɣѵã](https://link.juejin.cn?target=https%3A%2F%2Fmp.weixin.qq.com%2Fs%2FBaNQWygQb8UXxktrXetOcw \"https://mp.weixin.qq.com/s/BaNQWygQb8UXxktrXetOcw\")ѾνActuatorɵSpring BootĿУҽԶEndpoint˵㣩˵룬ôƪأǽActuatorԭ˵Ĺܼʹó\n\n## Endpoints \n\nActuatorν Endpoints Ϊ˵㣩ṩⲿӦóзʺͽĹܡ ˵/health˵ṩӦýϢmetrics ˵ṩӦóָ꣨JVM ڴʹáϵͳCPUʹõȣϢ\n\nActuatorԭĶ˵ɷΪࣺ\n\n*   ӦࣺȡӦóмصӦáԶñSpring BootӦصϢ\n*   ָࣺȡӦóйڼصĶָ꣬磺ڴϢ̳߳ϢHTTPͳƵȡ\n*   ࣺṩ˶ӦõĹرյȲ๦ܡ\n\nͬ汾Actuatorṩԭ˵룬ʹõĹʹð汾ĹٷĵΪ׼ͬʱÿԭĶ˵㶼ͨĽûá\n\nActuator 2.x Ĭ϶˵/actuatorǰ׺ͬʱĬֻ¶˵Ϊ/actuator/health /actuator/infoڶ˵㱩¶ãɲοǰһƪ¡Spring Boot 2.2.2.RELEASE汾ص㽲ÿ˵ĹܺӦó\n\n## actuator˵\n\nActuator 2.xĬ϶˵㣬չʾĿǰӦб¶Ķ˵ܣΪö˵Ŀ¼\n\nURL[http://localhost:8080/actuator](https://link.juejin.cn?target=http%3A%2F%2Flocalhost%3A8080%2Factuator \"http://localhost:8080/actuator\") Ӧչʾͼ\n\n![image-20230530233537559](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230530233537559.png)\n\nֻչʾһֵĶ˵㣬ؽΪĲ-Handler˸ʽͨactuatorֱ۵ĿĿǰЩ˵㣬ԼЩ˵ƺ·\n\nǾͰʾactuator˵չʾбһܡ\n\n## auditevents˵\n\nauditevents˵ʾӦñ¶¼ (֤롢ʧ)ʹǴж˵㣬ĬҲǿ˵ġΪʹǰҪSpringдһΪAuditEventRepositoryBeanġ\n\n鿴ϴ̳̣϶ǽauditevents˵㹦ܣδչʾʵ߾෽ԣڸдһ\n\n漰Ȩ֤Ҫspring-boot-starter-security\n\n````\n<dependency>\n <groupId>org.springframework.boot</groupId>\n <artifactId>spring-boot-starter-security</artifactId>\n</dependency>` \n````\n\nǲģҪsecurityãȻAuthorizationAuditListener,AuthenticationAuditListener ʲô¼? ,Ǽ´룺\n\n\n````\n@Configuration\npublic class WebSecurityConfig extends WebSecurityConfigurerAdapter {\n\n @Override\n protected void configure(AuthenticationManagerBuilder auth) throws Exception {\n\n auth.inMemoryAuthentication()\n .withUser(\"admin\")\n .password(bcryptPasswordEncoder().encode(\"admin\"))\n .roles(\"admin\");\n }\n````\n\n````\n @Bean\n public PasswordEncoder bcryptPasswordEncoder() {\n return new BCryptPasswordEncoder();\n }\n}\n````\n\nsecurityĬϵĵ¼Ȩ޿ƣҲ˵еķʶҪе¼¼ûΪadmin\n\n⣬ǰᵽҪõAuditEventRepositoryBeanʼһӦBean\n\n\n\n\n````\n@Configuration\npublic class AuditEventConfig {\n\n @Bean\n public InMemoryAuditEventRepository repository(){\n return new InMemoryAuditEventRepository();\n }\n}\n````\n\nInMemoryAuditEventRepositoryAuditEventRepositoryӿڵΨһʵࡣ\n\nĿauditevents˵ˡ[http://localhost:8080/actuator](https://link.juejin.cn?target=http%3A%2F%2Flocalhost%3A8080%2Factuator \"http://localhost:8080/actuator\") ,ʱתSecurityṩĵ¼ҳ棺\n\n![image-20230530233604253](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230530233604253.png)\n\nָû룬¼ɹת/actuatorҳ棺\n\n![image-20230530233625068](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230530233625068.png)\n\nԿauditevents˵Ѿɹʾˡ¿ҳ[http://localhost:8080/actuator/auditevents](https://link.juejin.cn?target=http%3A%2F%2Flocalhost%3A8080%2Factuator%2Fauditevents \"http://localhost:8080/actuator/auditevents\") չʾ£\n\n![image-20230530233716752](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230530233716752.png)\n\nԿѾ¼Ȩص¼еһ¼ֱӷactuator˵ʱ֮ǰΪȨ棬¼Ϊ\"AUTHORIZATION_FAILURE\"Ҳ֤ʧܡʱת¼ҳ棬Ȼڵ¼ҳû룬¼ɹӦ¼Ϊ\"AUTHENTICATION_SUCCESS\"\n\nҲ˵auditevents¼û֤¼ϵͳص¼Ϣʱ֤û¼͡ʵַsessionIdȡ\n\nʾԴַ[github.com/secbr/sprin](https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fsecbr%2Fspringboot-all%2Ftree%2Fmaster%2Fspringboot-actuator-auditevents \"https://github.com/secbr/springboot-all/tree/master/springboot-actuator-auditevents\") \n\n## beans˵\n\n/beans˵᷵Springbeanı͡ǷϢ\n\n·Ϊ[http://localhost:8080/actuator/beans](https://link.juejin.cn?target=http%3A%2F%2Flocalhost%3A8080%2Factuator%2Fbeans \"http://localhost:8080/actuator/beans\") Χ£\n\n![image-20230530233748286](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230530233748286.png)\n\n˵չʾĿǰSpringгʼBeanһ£һBeanȷǷɹʵǲǾͿͨ˿ڲѯһأ\n\nĿжһTestControllerעһUserService\n\n\n````\n@Controller\npublic class TestController {\n\n @Resource\n private UserService userService;\n}\n````\n\nʸö˵㣬ῴϢ\n\n![image-20230530233805161](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230530233805161.png)\n\nԿTestControllerʵˣUserService\n\n## caches˵\n\ncaches˵Ҫڱ¶ӦóеĻ塣Spring BootṩCacheչʾһʵ\n\nĿмspring-boot-starter-cache\n\n\n````\n<dependency>\n <groupId>org.springframework.boot</groupId>\n <artifactId>spring-boot-starter-cache</artifactId>\n</dependency>\n````\n\nȻ@EnableCaching湦ܡ\n\nһCacheController䷽queryAllʹûƣ\n\n\n````\n@RestController\npublic class CacheController {\n\n @RequestMapping(\"/queryAll\")\n @Cacheable(value = \"queryAll\")\n public Map<String, String> queryAll() {\n Map<String, String> map = new HashMap<>();\n map.put(\"1\", \"Tom\");\n map.put(\"2\", \"Steven\");\n return map;\n }\n}\n````\n\nʹ@Cacheableעʵֻ湦ܣkeyΪqueryAllʱ[http://localhost:8080/actuator/caches](https://link.juejin.cn?target=http%3A%2F%2Flocalhost%3A8080%2Factuator%2Fcaches \"http://localhost:8080/actuator/caches\") չʾĸݣ沢ûл档\n\nһ[http://localhost:8080/queryAll](https://link.juejin.cn?target=http%3A%2F%2Flocalhost%3A8080%2FqueryAll \"http://localhost:8080/queryAll\") ҲǴһ»ݵɡʱٷӣԿӦóеĻϢˣ\n\n![image-20230530233852486](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230530233852486.png)\n\nԿصݲչʾӦóĻͬʱҲչʾ˻Keyͻݴ洢Ϣ\n\n## caches-cache˵\n\ncaches-cache˵Ƕcaches˵չcaches˵չʾеĻϢֱӿһϢʹcaches-cache˵㡣\n\nʵURLΪ[http://localhost:8080/actuator/caches/{cache}](https://link.juejin.cn?target=http%3A%2F%2Flocalhost%3A8080%2Factuator%2Fcaches%2F%257Bcache%257D \"http://localhost:8080/actuator/caches/%7Bcache%7D\") дڵֵ滻Ϊkey\n\n\n\n\n`http://localhost:8080/actuator/caches/queryAll`\n\nռλqueryAllkeyִн£\n\n![image-20230530233906164](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230530233906164.png)\n\nԿֻѯָĻϢƣkeyĴ洢͡\n\n## health˵\n\nhealth˵Ӧõ״̬Ƶʹõһ˵㡣Ӧʵ״̬ԼӦòԭ򣬱ݿӡ̿ռ䲻ȡ\n\nʵַ[http://localhost:8080/actuator/health](https://link.juejin.cn?target=http%3A%2F%2Flocalhost%3A8080%2Factuator%2Fhealth \"http://localhost:8080/actuator/health\")\n\nչʾ\n\n`{\n\"status\": \"UP\"\n}`\n\nʵڼ򵥣Ŀаݿɽȥ\n\n`<!--ݿ-->\n\n````\n<dependency>\n <groupId>org.springframework.boot</groupId>\n <artifactId>spring-boot-starter-jdbc</artifactId>\n</dependency>\n<dependency>\n <groupId>mysql</groupId>\n <artifactId>mysql-connector-java</artifactId>\n</dependency>` \n````\n\nȻapplicationļнã\n\n\n\n```\nspring:\n datasource:\n url: jdbc:mysql://xxx:3333/xxx?characterEncoding=utf8&serverTimezone=Asia/Shanghai\n username: root\n password: root\n driver-class-name: com.mysql.cj.jdbc.Driver\n```\n\nͬʱҪapplicationļһmanagement.endpoint.health.show-detailsֵѡ\n\n*   never չʾϸϢup  down ״̬Ĭã\n*   when-authorizedϸϢչʾ֤ͨûȨĽɫͨmanagement.endpoint.health.roles ã\n*   alwaysû¶ϸϢ\n\nĬֵneverֱӷʿֻUPDOWNڼݿ⣬ͬʱѸֵΪalwaysһ飺\n\n![image-20230530233934501](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230530233934501.png)\n\nԿ״̬ΪUPΪUPݿMYSQLݿΪSELECT 1ͬʱչʾ˴Ϣping״̬\n\nǰݿûĴʿɵã\n\n![image-20230530233951145](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230530233951145.png)\n\n״̬ΪDOWNdb⣬״̬ΪDOWNerrorչʾԿǽʱˡʵУǿͨhealth˿ڼݿ⡢RedisMongoDB̵ȽActuatorԤĴΪDataSourceHealthIndicator, DiskSpaceHealthIndicator, MongoHealthIndicator, RedisHealthIndicatorȡ\n\nÿָ궼ԵĽп͹رգݿΪ\n\n\n````\nmanagement:\n health:\n db:\n enabled: true` \n````\n\n## info˵\n\n/info ˵鿴ļ applicationinfoͷϢĬ applicationвû info ڵãĬΪա\n\napplicationã\n\n\n\n````\ninfo:\n user:\n type: ں\n name: ӽ\n wechat: zhuan2quan\n````\n\n[http://localhost:8080/actuator/info](https://link.juejin.cn?target=http%3A%2F%2Flocalhost%3A8080%2Factuator%2Finfo \"http://localhost:8080/actuator/info\") չʾ£\n\n![image-20230530234019487](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230530234019487.png)\n\n## conditions˵\n\nSpring BootṩԶùܣʹǳ㡣ЩԶʲôЧģǷЧǱȽŲġʱʹ conditions Ӧʱ鿴ĳʲôЧΪʲôûЧ\n\nURL[http://localhost:8080/actuator/conditions](https://link.juejin.cn?target=http%3A%2F%2Flocalhost%3A8080%2Factuator%2Fconditions \"http://localhost:8080/actuator/conditions\") ַϢ£\n\n![image-20230530234053134](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230530234053134.png)\n\nԿĳԶӦЧʾϢ\n\n## shutdown˵\n\nshutdown˵ڲ˵㣬Źر Spring Boot ӦáҪļп\n\n\n````\nmanagement:\n endpoint:\n shutdown:\n enabled: true\n````\n\nö˵ֻ֧POSTִؽ£\n\n\n```\ncurl -X POST \"http://localhost:8080/actuator/shutdown\" \n{\n \"message\": \"Shutting down, bye...\"\n}\n```\n\nִ֮󣬻ᷢӦóѾرˡڸö˵رӦóʹҪСġ\n\n## configprops˵\n\nSpring BootĿУǾõ@ConfigurationPropertiesעעһЩԣconfigprops˵ʾЩעעࡣ\n\nǰinfoãǾͿԶһInfoProperties\n\n\n````\n@Component\n@ConfigurationProperties(prefix = \"info\")\npublic class InfoProperties {\n\n private String type;\n\n private String name;\n\n private String wechat;\n  \n // ʡgetter/setter \n}\n````\n\nURL[http://localhost:8080/actuator/configprops](https://link.juejin.cn?target=http%3A%2F%2Flocalhost%3A8080%2Factuator%2Fconfigprops \"http://localhost:8080/actuator/configprops\") Ϣ£\n\n![image-20230530234110515](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230530234110515.png)\n\nԿϵͳĬϼɵϢԿԶϢҪעǶӦҪʵ@Componentܹ\n\nԶзBeanơǰ׺ProjectInfoPropertiesϢ\n\n## env˵\n\nenv˵ڻȡȫԣapplicationļеݡϵͳȡ\n\nURL[http://localhost:8080/actuator/envزϢ](https://link.juejin.cn?target=http%3A%2F%2Flocalhost%3A8080%2Factuator%2Fenv%25EF%25BC%258C%25E8%25BF%2594%25E5%259B%259E%25E9%2583%25A8%25E5%2588%2586%25E4%25BF%25A1%25E6%2581%25AF%25EF%25BC%259A \"http://localhost:8080/actuator/env%EF%BC%8C%E8%BF%94%E5%9B%9E%E9%83%A8%E5%88%86%E4%BF%A1%E6%81%AF%EF%BC%9A\")\n\n![image-20230530234200949](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230530234200949.png)\n\n## env-toMatch˵\n\nenv-toMatch˵cachescaches-cacheƣһǻȡеģһǻȡָġenv-toMatch˵ǻȡָkeyĻԡ\n\nʽΪ[http://localhost:8080/actuator/env/{toMatch}](https://link.juejin.cn?target=http%3A%2F%2Flocalhost%3A8080%2Factuator%2Fenv%2F%257BtoMatch%257D%25E3%2580%2582 \"http://localhost:8080/actuator/env/%7BtoMatch%7D%E3%80%82\") ʵURL[http://localhost:8080/actuator/env/info.user.name](https://link.juejin.cn?target=http%3A%2F%2Flocalhost%3A8080%2Factuator%2Fenv%2Finfo.user.name \"http://localhost:8080/actuator/env/info.user.name\") ؽͼ\n\n![image-20230530234238073](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230530234238073.png)\n\nϢԵԴvalueֵϢ\n\n## loggers˵\n\n/loggers ˵㱩¶˳ڲõ logger Ϣͬpackageͬ־Ϣ\n\nURL[http://localhost:8080/actuator/loggers](https://link.juejin.cn?target=http%3A%2F%2Flocalhost%3A8080%2Factuator%2Floggers \"http://localhost:8080/actuator/loggers\") ַؽ\n\n![image-20230530234301625](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230530234301625.png)\n\n## loggers-name˵\n\nloggers-name˵Ҳlogger˵ϸ֣ͨnameĳһlogger\n\nʽ[http://localhost:8080/actuator/loggers/{name}](https://link.juejin.cn?target=http%3A%2F%2Flocalhost%3A8080%2Factuator%2Floggers%2F%257Bname%257D \"http://localhost:8080/actuator/loggers/%7Bname%7D\") ʾURL[http://localhost:8080/actuator/loggers/com.secbro2.SpringbootActuatorApplication](https://link.juejin.cn?target=http%3A%2F%2Flocalhost%3A8080%2Factuator%2Floggers%2Fcom.secbro2.SpringbootActuatorApplication \"http://localhost:8080/actuator/loggers/com.secbro2.SpringbootActuatorApplication\") ؽ£\n\n\n\n`{\n\"configuredLevel\": null,\n\"effectiveLevel\": \"INFO\"\n}`\n\nԿ־ΪINFO\n\n## heapdump˵\n\nheapdump˵᷵һJVM dumpͨJVMԴļعVisualVMɴ򿪴ļ鿴ڴաڴŻڶջŲ\n\nURL[http://localhost:8080/actuator/heapdump](https://link.juejin.cn?target=http%3A%2F%2Flocalhost%3A8080%2Factuator%2Fheapdump \"http://localhost:8080/actuator/heapdump\") MacϵͳʻһΪheapdumpļ޺׺30M\n\nִjvisualvmVisualVMεļװ롱ǵļҪѡ񡰶Dump(_.hprof,_.*)Ȼѡheapdump\n\n![image-20230530234346098](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230530234346098.png)\n\nʱͨжջϢķˡķṩ˼Ϊķʽ\n\n## threaddump˵\n\n/threaddump ˵ɵǰ̻߳Ŀաճλʱ鿴̵߳ǳãҪչʾ߳߳ID̵߳״̬ǷȴԴϢ\n\nURL[http://localhost:8080/actuator/threaddump](https://link.juejin.cn?target=http%3A%2F%2Flocalhost%3A8080%2Factuator%2Fthreaddump \"http://localhost:8080/actuator/threaddump\") ַؽ\n\n![image-20230530234405331](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230530234405331.png)\n\nǿͨ߳̿Ų⡣\n\n## metrics˵\n\n/metrics ˵¶ǰӦõĸҪָ꣬磺ڴϢ߳ϢϢtomcatݿӳصȡ2.x汾ֻʾһָб\n\nURL[http://localhost:8080/actuator/metrics](https://link.juejin.cn?target=http%3A%2F%2Flocalhost%3A8080%2Factuator%2Fmetrics \"http://localhost:8080/actuator/metrics\") \n\n````\n{\n \"names\": [\n \"jvm.memory.max\",\n \"jvm.threads.states\",\n \"jvm.gc.pause\",\n \"http.server.requests\",\n \"process.files.max\",\n \"jvm.gc.memory.promoted\",\n \"system.load.average.1m\",\n \"jvm.memory.used\",\n \"jvm.gc.max.data.size\",\n \"jvm.memory.committed\",\n \"system.cpu.count\",\n \"logback.events\",\n \"jvm.buffer.memory.used\",\n \"tomcat.sessions.created\",\n \"jvm.threads.daemon\",\n \"system.cpu.usage\",\n \"jvm.gc.memory.allocated\",\n \"tomcat.sessions.expired\",\n \"jvm.threads.live\",\n \"jvm.threads.peak\",\n \"process.uptime\",\n \"tomcat.sessions.rejected\",\n \"process.cpu.usage\",\n \"jvm.classes.loaded\",\n \"jvm.classes.unloaded\",\n \"tomcat.sessions.active.current\",\n \"tomcat.sessions.alive.max\",\n \"jvm.gc.live.data.size\",\n \"process.files.open\",\n \"jvm.buffer.count\",\n \"jvm.buffer.total.capacity\",\n \"tomcat.sessions.active.max\",\n \"process.start.time\"\n ]\n}\n````\n\n/metrics˵ṩӦ״ָ̬걨棬ܷǳʵãǶڼϵͳеĸعܣǵļݡռƵʶͬÿζͨȫȡķʽռԴֱٷҲǿǴڴ˷ĿǣSpring Boot 2.x֮/metrics˵ֻʾָб\n\nҪ鿴ĳָ꣬ͨ/metrics-requiredMetricName˵ʵ֡\n\n## metrics-requiredMetricName˵\n\nmetrics-requiredMetricName˵㣬ڷָָı棬һ/metrics˵ȲָбȻٲѯĳָꡣ\n\nʽ[http://localhost:8080/actuator/metrics/{requiredMetricName}](https://link.juejin.cn?target=http%3A%2F%2Flocalhost%3A8080%2Factuator%2Fmetrics%2F%257BrequiredMetricName%257D%25E3%2580%2582 \"http://localhost:8080/actuator/metrics/%7BrequiredMetricName%7D%E3%80%82\") ʵURL[http://localhost:8080/actuator/metrics/jvm.memory.max](https://link.juejin.cn?target=http%3A%2F%2Flocalhost%3A8080%2Factuator%2Fmetrics%2Fjvm.memory.max \"http://localhost:8080/actuator/metrics/jvm.memory.max\") ؽ£\n\n\n````\n{\n \"name\": \"jvm.memory.max\",\n \"description\": \"The maximum amount of memory in bytes that can be used for memory management\",\n \"baseUnit\": \"bytes\",\n \"measurements\": [\n {\n \"statistic\": \"VALUE\",\n \"value\": 5606211583\n }\n ],\n \"availableTags\": [\n {\n \"tag\": \"area\",\n \"values\": [\n \"heap\",\n \"nonheap\"\n ]\n },\n {\n \"tag\": \"id\",\n \"values\": [\n \"Compressed Class Space\",\n \"PS Survivor Space\",\n \"PS Old Gen\",\n \"Metaspace\",\n \"PS Eden Space\",\n \"Code Cache\"\n ]\n }\n ]\n}\n````\n\nչʾڴָչʾ滻Ӧֽв鿴ɡ\n\n## scheduledtasks˵\n\n/scheduledtasks˵չʾӦеĶʱϢ\n\nĿйʱ@EnableSchedulingʱܡȻ󴴽ʱࣺ\n\n````\n@Component\npublic class MyTask {\n\n @Scheduled(cron = \"0/10 * * * * *\")\n public void work() {\n System.out.println(\"I am a cron job.\");\n }\n\n @Scheduled(fixedDelay = 10000)\n public void work1() {\n System.out.println(\"I am a fixedDelay job.\");\n }\n}\n````\n\nж͵ĶʱworkǻcronʵֵĶʱwork1ǻfixedDelayʵֵĶʱ\n\nURL[http://localhost:8080/actuator/scheduledtasks](https://link.juejin.cn?target=http%3A%2F%2Flocalhost%3A8080%2Factuator%2Fscheduledtasks \"http://localhost:8080/actuator/scheduledtasks\") ؽϢ£\n\n```\n{\n \"cron\": [\n {\n \"runnable\": {\n \"target\": \"com.secbro2.job.MyTask.work\"\n },\n \"expression\": \"0/10 * * * * *\"\n }\n ],\n \"fixedDelay\": [\n {\n \"runnable\": {\n \"target\": \"com.secbro2.job.MyTask.work1\"\n },\n \"initialDelay\": 0,\n \"interval\": 10000\n }\n ],\n \"fixedRate\": [],\n \"custom\": []\n}\n```\n\nԿͨö˵ȷ֪ǰӦжĶʱԼִģʽƵΡ\n\n## mappings˵\n\n/mappings˵ȫ URI ·ԼͿӳϵǱȽϳõˣϵͳĲ鿴URLӦControllerʹô˶˵㡣\n\nURL[http://localhost:8080/actuator/mappings](https://link.juejin.cn?target=http%3A%2F%2Flocalhost%3A8080%2Factuator%2Fmappings \"http://localhost:8080/actuator/mappings\") ַؽ£\n\n![image-20230530234501440](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230530234501440.png)\n\n˹Spring Boot Actuatorṩж˵ϡ\n\n## С\n\nͨSpring Boot Actuatorṩж˵㹹ʵʾĴݺʵȫһϡÿܶŲ⣬ŻȶмİдĵĹҲԽԽ̾ActuatorĹ֮ǿǿƼ\n\n## ο\n\nߣӽ\nӣhttps://juejin.cn/post/6984550846876876814\nԴϡ\nȨСҵתϵ߻Ȩҵתע\n\n"
  },
  {
    "path": "docs/monitor/SpringBoot Admin.md",
    "content": "## ժҪ\n\nSpring Boot Admin ԶSpringBootӦõĸָмأΪ΢ܹеļʹãĽ÷ϸܡ\n\n## Spring Boot Admin \n\nSpringBootӦÿͨActuator¶Ӧйеĸָ꣬Spring Boot AdminͨЩָSpringBootӦãȻͨͼλֳSpring Boot AdminԼصӦãԺSpring Cloudע΢Ӧá\n\nSpring Boot Admin ṩӦõ¼Ϣ\n\n*   ӦйеĸϢ\n*   ָϢJVMTomcatϢ\n*   ϢϵͳԡϵͳԼӦϢ\n*   鿴дBeanϢ\n*   鿴ӦеϢ\n*   鿴Ӧ־Ϣ\n*   鿴JVMϢ\n*   鿴ԷʵWeb˵㣻\n*   鿴HTTPϢ\n\n## admin-serverģ\n\n> Ǵһadmin-serverģΪʾ书ܡ\n\n*   pom.xml\n\n\n\n\n\n````\n<dependency>\n <groupId>org.springframework.boot</groupId>\n <artifactId>spring-boot-starter-web</artifactId>\n</dependency>\n<dependency>\n <groupId>de.codecentric</groupId>\n <artifactId>spring-boot-admin-starter-server</artifactId>\n</dependency>\n\n````\n\n*   application.ymlнã\n\n\n\n\n\n````spring:\n application:\n name: admin-server\nserver:\n port: 9301\n````\n\n*   @EnableAdminServeradmin-serverܣ\n\n\n\n\n````\n@EnableAdminServer\n@SpringBootApplication\npublic class AdminServerApplication {\n\n public static void main(String[] args) {\n SpringApplication.run(AdminServerApplication.class, args);\n }\n\n}\n````\n\n## admin-clientģ\n\n> Ǵһadmin-clientģΪͻעᵽadmin-server\n\n*   pom.xml\n\n\n\n````\n<dependency>\n <groupId>org.springframework.boot</groupId>\n <artifactId>spring-boot-starter-web</artifactId>\n</dependency>\n<dependency>\n <groupId>de.codecentric</groupId>\n <artifactId>spring-boot-admin-starter-client</artifactId>\n</dependency>\n````\n\n*   application.ymlнã\n\n\n\n\n\n\n\n\n```\nspring:\n application:\n name: admin-client\n boot:\n admin:\n client:\n url: http://localhost:9301 #admin-serverַ\nserver:\n port: 9305\nmanagement:\n endpoints:\n web:\n exposure:\n include: '*'\n endpoint:\n health:\n show-details: always\nlogging:\n file: admin-client.log #ӿadmin־\n```\n\n*   admin-serveradmin-client\n\n## Ϣʾ\n\n*   µַSpring Boot Adminҳ[http://localhost:9301](https://link.juejin.cn?target=http%3A%2F%2Flocalhost%3A9301 \"http://localhost:9301\")\n\n![image-20230531001002163](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230531001002163.png)\n\n\n\n\n\n*   wallboardťѡadmin-client鿴Ϣ\n\n*   Ϣ\n\n![image-20230531001023644](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230531001023644.png)\n\n\n\n\n\n*   ָϢJVMTomcatϢ\n\n![image-20230531001053279](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230531001053279.png)\n\n\n\n\n\n*   ϢϵͳԡϵͳԼӦϢ\n\n![image-20230531001103093](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230531001103093.png)\n\n\n\n\n\n*   鿴дBeanϢ\n\n![image-20230531001111221](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230531001111221.png)\n\n\n\n\n\n*   鿴ӦеϢ\n\n![image-20230531001124678](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230531001124678.png)\n\n\n\n\n\n*   鿴־ϢҪòܿ\n\n\n\n`logging:\nfile: admin-client.log #ӿadmin־`\n\n![image-20230531001136184](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230531001136184.png)\n\n\n\n\n*   鿴JVMϢ\n\n![image-20230531001144614](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230531001144614.png)\n\n\n\n\n\n*   鿴ԷʵWeb˵㣻\n\n![image-20230531001156191](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230531001156191.png)\n\n\n\n\n\n*   鿴HTTPϢ\n\n![image-20230531001206364](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230531001206364.png)\n\n\n\n\n\n## עʹ\n\n> Spring Boot AdminSpring Cloud עʹãֻ轫admin-serverעϼɣadmin-server ԶעĻȡбȻ󰤸ȡϢEurekaעΪ¸ùܡ\n\n### ޸admin-server\n\n*   pom.xml\n\n\n\n\n````\n<dependency>\n <groupId>org.springframework.cloud</groupId>\n <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>\n</dependency>\n````\n\n*   application-eureka.ymlнãֻעüɣ\n\n\n\n\n````\nspring:\n application:\n name: admin-server\nserver:\n port: 9301\neureka:\n client:\n register-with-eureka: true\n fetch-registry: true\n service-url:\n defaultZone: http://localhost:8001/eureka/\n````\n\n*   @EnableDiscoveryClient÷עṦܣ\n\n\n\n\n\n````\n`@EnableDiscoveryClient\n@EnableAdminServer\n@SpringBootApplication\npublic class AdminServerApplication {\n\n public static void main(String[] args) {\n SpringApplication.run(AdminServerApplication.class, args);\n }\n\n}\n````\n\n### ޸admin-client\n\n*   pom.xml\n\n\n\n````\n<dependency>\n <groupId>org.springframework.cloud</groupId>\n <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>\n</dependency>\n````\n\n*   application-eureka.ymlнãɾԭadmin-serverַãעüɣ\n\n\n\n\n\n```\nspring:\n application:\n name: admin-client\nserver:\n port: 9305\nmanagement:\n endpoints:\n web:\n exposure:\n include: '*'\n endpoint:\n health:\n show-details: always\nlogging:\n file: admin-client.log #ӿadmin־\neureka:\n client:\n register-with-eureka: true\n fetch-registry: true\n service-url:\n defaultZone: http://localhost:8001/eureka/\n```\n\n*   @EnableDiscoveryClient÷עṦܣ\n\n\n\n\n\n```\n@EnableDiscoveryClient\n@SpringBootApplication\npublic class AdminClientApplication {\n\n public static void main(String[] args) {\n SpringApplication.run(AdminClientApplication.class, args);\n }\n\n}\n```\n\n### ʾ\n\n*   eureka-serverʹapplication-eureka.ymladmin-serveradmin-client\n\n*   鿴עķַע᣺[http://localhost:8001/](https://link.juejin.cn?target=http%3A%2F%2Flocalhost%3A8001%2F \"http://localhost:8001/\")\n\n![image-20230531001221519](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230531001221519.png)\n\n\n*   鿴Spring Boot Admin ҳֿԿϢ[http://localhost:9301](https://link.juejin.cn?target=http%3A%2F%2Flocalhost%3A9301 \"http://localhost:9301\")\n\n![image-20230531001232048](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230531001232048.png)\n\n\n## ӵ¼֤\n\n> ǿͨadmin-serverSpring Security֧õ¼֤ܡ\n\n### admin-security-serverģ\n\n*   pom.xml\n\n\n\n```\n<dependency>\n <groupId>org.springframework.cloud</groupId>\n <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>\n</dependency>\n<dependency>\n <groupId>de.codecentric</groupId>\n <artifactId>spring-boot-admin-starter-server</artifactId>\n <version>2.1.5</version>\n</dependency>\n<dependency>\n <groupId>org.springframework.boot</groupId>\n <artifactId>spring-boot-starter-security</artifactId>\n</dependency>\n<dependency>\n <groupId>org.springframework.boot</groupId>\n <artifactId>spring-boot-starter-web</artifactId>\n</dependency>\n```\n\n*   application.ymlнãõ¼û룬admin-security-serverļϢ\n\n\n\n\n\n```\nspring:\n application:\n name: admin-security-server\n security: # õ¼û\n user:\n name: macro\n password: 123456\n boot:  # ʾadmin-security-serverļϢ\n admin:\n discovery:\n ignored-services: ${spring.application.name}\nserver:\n port: 9301\neureka:\n client:\n register-with-eureka: true\n fetch-registry: true\n service-url:\n defaultZone: http://localhost:8001/eureka/\n```\n\n*   SpringSecurityãԱadmin-clientע᣺\n\n\n\n\n\nscss\n\nƴ\n\n\n\n\n\n```\n/**\n * Created by macro on 2019/9/30.\n */\n@Configuration\npublic class SecuritySecureConfig extends WebSecurityConfigurerAdapter {\n private final String adminContextPath;\n\n public SecuritySecureConfig(AdminServerProperties adminServerProperties) {\n this.adminContextPath = adminServerProperties.getContextPath();\n }\n\n @Override\n protected void configure(HttpSecurity http) throws Exception {\n SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();\n successHandler.setTargetUrlParameter(\"redirectTo\");\n successHandler.setDefaultTargetUrl(adminContextPath + \"/\");\n\n http.authorizeRequests()\n //1.о̬Դ͵¼ҳԹ\n .antMatchers(adminContextPath + \"/assets/**\").permitAll()\n .antMatchers(adminContextPath + \"/login\").permitAll()\n .anyRequest().authenticated()\n .and()\n //2.õ¼͵ǳ·\n .formLogin().loginPage(adminContextPath + \"/login\").successHandler(successHandler).and()\n .logout().logoutUrl(adminContextPath + \"/logout\").and()\n //3.http basic֧֣admin-clientעʱҪʹ\n .httpBasic().and()\n .csrf()\n //4.cookiecsrf\n .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())\n //5.Щ·csrfԱadmin-clientע\n .ignoringAntMatchers(\n adminContextPath + \"/instances\",\n adminContextPath + \"/actuator/**\"\n );\n }\n}\n```\n\n*   ޸࣬AdminServerעֹᷢܣ\n\n\n\n\n\n```\n@EnableDiscoveryClient\n@EnableAdminServer\n@SpringBootApplication\npublic class AdminSecurityServerApplication {\n\n public static void main(String[] args) {\n SpringApplication.run(AdminSecurityServerApplication.class, args);\n }\n}\n```\n\n*   eureka-serveradmin-security-serverSpring Boot Admin ҳҪ¼ܷʣ[http://localhost:9301](https://link.juejin.cn?target=http%3A%2F%2Flocalhost%3A9301 \"http://localhost:9301\")\n\n![image-20230531001242361](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230531001242361.png)\n\n\n\n\n\n## ʹõģ\n\n\n\n\n\n```\nspringcloud-learning\n eureka-server -- eurekaע\n admin-server -- adminķ\n admin-client -- adminļصӦ÷\n admin-security-server -- ¼֤adminķ` \n```"
  },
  {
    "path": "docs/mq/RocketMQ/RocketMQ系列：事务消息（最终一致性）.md",
    "content": "# [RocketMQϵУһ](https://www.cnblogs.com/boboooo/p/13038950.html)\n\n\n\n\n\nRocketMQǰƷһԴϢмľϢĹܡҵУϢмѡʹRocketMQĻͦģһϵе¶RocketMQģȴRocketMQһЩͻĴʼ\n\nRocketMQ4ɣֱǣƷName ServerϢУBrokersߣproducerߣconsumer4ֶԽˮƽչӶⵥϣͼ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1191201-20200603173058174-1551688390.png)\n\nRocketMQϵһͼǳг4֣ҶǼȺģʽǾͷֱ˵һ˵4֡\n\n## ƷNameServer\n\nName ServerݵĽɫһעģZookeeperòࡣҪ£\n\n*   brokerĹbrokerȺԼϢעᵽNameServerNameServerṩƼÿһbrokerǷ\n*   ·ɹÿһNameServerbrokerȺͶеϢԱͻˣߺߣѯ\n\nNameServerЭŷֲʽϵͳеÿһҹÿһTopic·Ϣ\n\n## Broker\n\nBrokerҪǴ洢ϢṩTopicĻơṩƺģʽһЩֵĴʩϢǿһBrokcerӻơ\n\nBrokerĽɫΪ첽ͬ͡ӡɫ㲻ϢĶʧһͬ͡ӡBrokerϢʧҲνֻҪпþokĻá첽͡ӡbrokerֻ򵥵Ĵֻһ첽áӡҲǿԵġ\n\nᵽbroker֮ıݣbrokerϢҲǿԱ浽̵ģ浽̵ķʽҲ֣Ƽķʽ첽̣ͬǷǳܵġ\n\n## \n\nּ֧ȺbrokerȺϢֶָ֧ؾķʽ\n\nbrokerϢʱõͽͽһ״̬ǵУϢ`isWaitStoreMsgOK = true`ĬҲ`true`Ϊ`false`ڷϢĹУֻҪ쳣ͽ`SEND_OK``isWaitStoreMsgOK = true`ͽ¼֣\n\n*   `FLUSH_DISK_TIMEOUT`̳ʱ̵ķʽΪSYNC_FLUSHͬsyncFlushTimeoutõʱڣĬ5sûɱ̵Ķõ״̬\n*   `FLUSH_SLAVE_TIMEOUT`ͬӡʱbrokerĽɫΪͬʱõͬʱڣĬΪ5sû֮ͬͻõ״̬\n*   `SLAVE_NOT_AVAILABLE`ӡãáͬûáӡbrokerʱ᷵״̬\n*   `SEND_OK`Ϣͳɹ\n\nϢظϢʧ㷢ϢʧʱͨѡһǶͶɣϢĶˣһѡϢ·ͣпϢظͨ£Ƽ·͵ģϢʱҪȥظϢ\n\nmessageĴСһ㲻512kĬϵķϢķʽͬģͷһֱֱȵصӦȽܣҲ`send(msg, callback)`첽ķʽϢ\n\n## \n\n߿**飨consumer group**ͬ****ԶͬTopicҲԶTopicÿһ鶼Լƫ\n\nϢѷʽһ֣˳ѺͲѡ\n\n*   ˳ѣ߽סϢУȷϢ˳һһıѵ˳ѻһʧϢʱ쳣ֱ׳Ӧ÷`SUSPEND_CURRENT_QUEUE_A_MOMENT`״̬߹һʱ󣬻Ϣ\n*   ѣ߽ϢַʽܷǳãҲƼѷʽѵĹУ쳣ֱ׳Ƿ`RECONSUME_LATER`״̬ڲȷһʱ󣬻ٴ\n\nڲʹ`ThreadPoolExecutor`Ϊ̳߳صģǿͨ`setConsumeThreadMin``setConsumeThreadMax`С̡̺߳߳\n\nһµ齨ԺҪǷ֮ǰʷϢ`CONSUME_FROM_LAST_OFFSET`ʷϢµϢ`CONSUME_FROM_FIRST_OFFSET`ѶеÿһϢ֮ǰʷϢҲһ顣`CONSUME_FROM_TIMESTAMP`ָϢʱ䣬ָʱԺϢᱻѡ\n\nӦòظѣôϢĹУҪϢУ顣\n\nˣ͵ɣһƪǽRocketMQĻ"
  },
  {
    "path": "docs/mq/RocketMQ/RocketMQ系列：基本概念.md",
    "content": "# [RocketMQϵУһ](https://www.cnblogs.com/boboooo/p/13038950.html)\n\n\n\n\n\nRocketMQǰƷһԴϢмľϢĹܡҵУϢмѡʹRocketMQĻͦģһϵе¶RocketMQģȴRocketMQһЩͻĴʼ\n\nRocketMQ4ɣֱǣƷName ServerϢУBrokersߣproducerߣconsumer4ֶԽˮƽչӶⵥϣͼ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1191201-20200603173058174-1551688390.png)\n\nRocketMQϵһͼǳг4֣ҶǼȺģʽǾͷֱ˵һ˵4֡\n\n## ƷNameServer\n\nName ServerݵĽɫһעģZookeeperòࡣҪ£\n\n*   brokerĹbrokerȺԼϢעᵽNameServerNameServerṩƼÿһbrokerǷ\n*   ·ɹÿһNameServerbrokerȺͶеϢԱͻˣߺߣѯ\n\nNameServerЭŷֲʽϵͳеÿһҹÿһTopic·Ϣ\n\n## Broker\n\nBrokerҪǴ洢ϢṩTopicĻơṩƺģʽһЩֵĴʩϢǿһBrokcerӻơ\n\nBrokerĽɫΪ첽ͬ͡ӡɫ㲻ϢĶʧһͬ͡ӡBrokerϢʧҲνֻҪпþokĻá첽͡ӡbrokerֻ򵥵Ĵֻһ첽áӡҲǿԵġ\n\nᵽbroker֮ıݣbrokerϢҲǿԱ浽̵ģ浽̵ķʽҲ֣Ƽķʽ첽̣ͬǷǳܵġ\n\n## \n\nּ֧ȺbrokerȺϢֶָ֧ؾķʽ\n\nbrokerϢʱõͽͽһ״̬ǵУϢ`isWaitStoreMsgOK = true`ĬҲ`true`Ϊ`false`ڷϢĹУֻҪ쳣ͽ`SEND_OK``isWaitStoreMsgOK = true`ͽ¼֣\n\n*   `FLUSH_DISK_TIMEOUT`̳ʱ̵ķʽΪSYNC_FLUSHͬsyncFlushTimeoutõʱڣĬ5sûɱ̵Ķõ״̬\n*   `FLUSH_SLAVE_TIMEOUT`ͬӡʱbrokerĽɫΪͬʱõͬʱڣĬΪ5sû֮ͬͻõ״̬\n*   `SLAVE_NOT_AVAILABLE`ӡãáͬûáӡbrokerʱ᷵״̬\n*   `SEND_OK`Ϣͳɹ\n\nϢظϢʧ㷢ϢʧʱͨѡһǶͶɣϢĶˣһѡϢ·ͣпϢظͨ£Ƽ·͵ģϢʱҪȥظϢ\n\nmessageĴСһ㲻512kĬϵķϢķʽͬģͷһֱֱȵصӦȽܣҲ`send(msg, callback)`첽ķʽϢ\n\n## \n\n߿**飨consumer group**ͬ****ԶͬTopicҲԶTopicÿһ鶼Լƫ\n\nϢѷʽһ֣˳ѺͲѡ\n\n*   ˳ѣ߽סϢУȷϢ˳һһıѵ˳ѻһʧϢʱ쳣ֱ׳Ӧ÷`SUSPEND_CURRENT_QUEUE_A_MOMENT`״̬߹һʱ󣬻Ϣ\n*   ѣ߽ϢַʽܷǳãҲƼѷʽѵĹУ쳣ֱ׳Ƿ`RECONSUME_LATER`״̬ڲȷһʱ󣬻ٴ\n\nڲʹ`ThreadPoolExecutor`Ϊ̳߳صģǿͨ`setConsumeThreadMin``setConsumeThreadMax`С̡̺߳߳\n\nһµ齨ԺҪǷ֮ǰʷϢ`CONSUME_FROM_LAST_OFFSET`ʷϢµϢ`CONSUME_FROM_FIRST_OFFSET`ѶеÿһϢ֮ǰʷϢҲһ顣`CONSUME_FROM_TIMESTAMP`ָϢʱ䣬ָʱԺϢᱻѡ\n\nӦòظѣôϢĹУҪϢУ顣\n\nˣ͵ɣһƪǽRocketMQĻ"
  },
  {
    "path": "docs/mq/RocketMQ/RocketMQ系列：广播与延迟消息.md",
    "content": "# [RocketMQϵУһ](https://www.cnblogs.com/boboooo/p/13038950.html)\n\n\n\n\n\nRocketMQǰƷһԴϢмľϢĹܡҵУϢмѡʹRocketMQĻͦģһϵе¶RocketMQģȴRocketMQһЩͻĴʼ\n\nRocketMQ4ɣֱǣƷName ServerϢУBrokersߣproducerߣconsumer4ֶԽˮƽչӶⵥϣͼ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1191201-20200603173058174-1551688390.png)\n\nRocketMQϵһͼǳг4֣ҶǼȺģʽǾͷֱ˵һ˵4֡\n\n## ƷNameServer\n\nName ServerݵĽɫһעģZookeeperòࡣҪ£\n\n*   brokerĹbrokerȺԼϢעᵽNameServerNameServerṩƼÿһbrokerǷ\n*   ·ɹÿһNameServerbrokerȺͶеϢԱͻˣߺߣѯ\n\nNameServerЭŷֲʽϵͳеÿһҹÿһTopic·Ϣ\n\n## Broker\n\nBrokerҪǴ洢ϢṩTopicĻơṩƺģʽһЩֵĴʩϢǿһBrokcerӻơ\n\nBrokerĽɫΪ첽ͬ͡ӡɫ㲻ϢĶʧһͬ͡ӡBrokerϢʧҲνֻҪпþokĻá첽͡ӡbrokerֻ򵥵Ĵֻһ첽áӡҲǿԵġ\n\nᵽbroker֮ıݣbrokerϢҲǿԱ浽̵ģ浽̵ķʽҲ֣Ƽķʽ첽̣ͬǷǳܵġ\n\n## \n\nּ֧ȺbrokerȺϢֶָ֧ؾķʽ\n\nbrokerϢʱõͽͽһ״̬ǵУϢ`isWaitStoreMsgOK = true`ĬҲ`true`Ϊ`false`ڷϢĹУֻҪ쳣ͽ`SEND_OK``isWaitStoreMsgOK = true`ͽ¼֣\n\n*   `FLUSH_DISK_TIMEOUT`̳ʱ̵ķʽΪSYNC_FLUSHͬsyncFlushTimeoutõʱڣĬ5sûɱ̵Ķõ״̬\n*   `FLUSH_SLAVE_TIMEOUT`ͬӡʱbrokerĽɫΪͬʱõͬʱڣĬΪ5sû֮ͬͻõ״̬\n*   `SLAVE_NOT_AVAILABLE`ӡãáͬûáӡbrokerʱ᷵״̬\n*   `SEND_OK`Ϣͳɹ\n\nϢظϢʧ㷢ϢʧʱͨѡһǶͶɣϢĶˣһѡϢ·ͣпϢظͨ£Ƽ·͵ģϢʱҪȥظϢ\n\nmessageĴСһ㲻512kĬϵķϢķʽͬģͷһֱֱȵصӦȽܣҲ`send(msg, callback)`첽ķʽϢ\n\n## \n\n߿**飨consumer group**ͬ****ԶͬTopicҲԶTopicÿһ鶼Լƫ\n\nϢѷʽһ֣˳ѺͲѡ\n\n*   ˳ѣ߽סϢУȷϢ˳һһıѵ˳ѻһʧϢʱ쳣ֱ׳Ӧ÷`SUSPEND_CURRENT_QUEUE_A_MOMENT`״̬߹һʱ󣬻Ϣ\n*   ѣ߽ϢַʽܷǳãҲƼѷʽѵĹУ쳣ֱ׳Ƿ`RECONSUME_LATER`״̬ڲȷһʱ󣬻ٴ\n\nڲʹ`ThreadPoolExecutor`Ϊ̳߳صģǿͨ`setConsumeThreadMin``setConsumeThreadMax`С̡̺߳߳\n\nһµ齨ԺҪǷ֮ǰʷϢ`CONSUME_FROM_LAST_OFFSET`ʷϢµϢ`CONSUME_FROM_FIRST_OFFSET`ѶеÿһϢ֮ǰʷϢҲһ顣`CONSUME_FROM_TIMESTAMP`ָϢʱ䣬ָʱԺϢᱻѡ\n\nӦòظѣôϢĹУҪϢУ顣\n\nˣ͵ɣһƪǽRocketMQĻ"
  },
  {
    "path": "docs/mq/RocketMQ/RocketMQ系列：批量发送与过滤.md",
    "content": "# [RocketMQϵУһ](https://www.cnblogs.com/boboooo/p/13038950.html)\n\n\n\n\n\nRocketMQǰƷһԴϢмľϢĹܡҵУϢмѡʹRocketMQĻͦģһϵе¶RocketMQģȴRocketMQһЩͻĴʼ\n\nRocketMQ4ɣֱǣƷName ServerϢУBrokersߣproducerߣconsumer4ֶԽˮƽչӶⵥϣͼ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1191201-20200603173058174-1551688390.png)\n\nRocketMQϵһͼǳг4֣ҶǼȺģʽǾͷֱ˵һ˵4֡\n\n## ƷNameServer\n\nName ServerݵĽɫһעģZookeeperòࡣҪ£\n\n*   brokerĹbrokerȺԼϢעᵽNameServerNameServerṩƼÿһbrokerǷ\n*   ·ɹÿһNameServerbrokerȺͶеϢԱͻˣߺߣѯ\n\nNameServerЭŷֲʽϵͳеÿһҹÿһTopic·Ϣ\n\n## Broker\n\nBrokerҪǴ洢ϢṩTopicĻơṩƺģʽһЩֵĴʩϢǿһBrokcerӻơ\n\nBrokerĽɫΪ첽ͬ͡ӡɫ㲻ϢĶʧһͬ͡ӡBrokerϢʧҲνֻҪпþokĻá첽͡ӡbrokerֻ򵥵Ĵֻһ첽áӡҲǿԵġ\n\nᵽbroker֮ıݣbrokerϢҲǿԱ浽̵ģ浽̵ķʽҲ֣Ƽķʽ첽̣ͬǷǳܵġ\n\n## \n\nּ֧ȺbrokerȺϢֶָ֧ؾķʽ\n\nbrokerϢʱõͽͽһ״̬ǵУϢ`isWaitStoreMsgOK = true`ĬҲ`true`Ϊ`false`ڷϢĹУֻҪ쳣ͽ`SEND_OK``isWaitStoreMsgOK = true`ͽ¼֣\n\n*   `FLUSH_DISK_TIMEOUT`̳ʱ̵ķʽΪSYNC_FLUSHͬsyncFlushTimeoutõʱڣĬ5sûɱ̵Ķõ״̬\n*   `FLUSH_SLAVE_TIMEOUT`ͬӡʱbrokerĽɫΪͬʱõͬʱڣĬΪ5sû֮ͬͻõ״̬\n*   `SLAVE_NOT_AVAILABLE`ӡãáͬûáӡbrokerʱ᷵״̬\n*   `SEND_OK`Ϣͳɹ\n\nϢظϢʧ㷢ϢʧʱͨѡһǶͶɣϢĶˣһѡϢ·ͣпϢظͨ£Ƽ·͵ģϢʱҪȥظϢ\n\nmessageĴСһ㲻512kĬϵķϢķʽͬģͷһֱֱȵصӦȽܣҲ`send(msg, callback)`첽ķʽϢ\n\n## \n\n߿**飨consumer group**ͬ****ԶͬTopicҲԶTopicÿһ鶼Լƫ\n\nϢѷʽһ֣˳ѺͲѡ\n\n*   ˳ѣ߽סϢУȷϢ˳һһıѵ˳ѻһʧϢʱ쳣ֱ׳Ӧ÷`SUSPEND_CURRENT_QUEUE_A_MOMENT`״̬߹һʱ󣬻Ϣ\n*   ѣ߽ϢַʽܷǳãҲƼѷʽѵĹУ쳣ֱ׳Ƿ`RECONSUME_LATER`״̬ڲȷһʱ󣬻ٴ\n\nڲʹ`ThreadPoolExecutor`Ϊ̳߳صģǿͨ`setConsumeThreadMin``setConsumeThreadMax`С̡̺߳߳\n\nһµ齨ԺҪǷ֮ǰʷϢ`CONSUME_FROM_LAST_OFFSET`ʷϢµϢ`CONSUME_FROM_FIRST_OFFSET`ѶеÿһϢ֮ǰʷϢҲһ顣`CONSUME_FROM_TIMESTAMP`ָϢʱ䣬ָʱԺϢᱻѡ\n\nӦòظѣôϢĹУҪϢУ顣\n\nˣ͵ɣһƪǽRocketMQĻ"
  },
  {
    "path": "docs/mq/RocketMQ/RocketMQ系列：消息的生产与消费.md",
    "content": "# [RocketMQϵУһ](https://www.cnblogs.com/boboooo/p/13038950.html)\n\n\n\n\n\nRocketMQǰƷһԴϢмľϢĹܡҵУϢмѡʹRocketMQĻͦģһϵе¶RocketMQģȴRocketMQһЩͻĴʼ\n\nRocketMQ4ɣֱǣƷName ServerϢУBrokersߣproducerߣconsumer4ֶԽˮƽչӶⵥϣͼ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1191201-20200603173058174-1551688390.png)\n\nRocketMQϵһͼǳг4֣ҶǼȺģʽǾͷֱ˵һ˵4֡\n\n## ƷNameServer\n\nName ServerݵĽɫһעģZookeeperòࡣҪ£\n\n*   brokerĹbrokerȺԼϢעᵽNameServerNameServerṩƼÿһbrokerǷ\n*   ·ɹÿһNameServerbrokerȺͶеϢԱͻˣߺߣѯ\n\nNameServerЭŷֲʽϵͳеÿһҹÿһTopic·Ϣ\n\n## Broker\n\nBrokerҪǴ洢ϢṩTopicĻơṩƺģʽһЩֵĴʩϢǿһBrokcerӻơ\n\nBrokerĽɫΪ첽ͬ͡ӡɫ㲻ϢĶʧһͬ͡ӡBrokerϢʧҲνֻҪпþokĻá첽͡ӡbrokerֻ򵥵Ĵֻһ첽áӡҲǿԵġ\n\nᵽbroker֮ıݣbrokerϢҲǿԱ浽̵ģ浽̵ķʽҲ֣Ƽķʽ첽̣ͬǷǳܵġ\n\n## \n\nּ֧ȺbrokerȺϢֶָ֧ؾķʽ\n\nbrokerϢʱõͽͽһ״̬ǵУϢ`isWaitStoreMsgOK = true`ĬҲ`true`Ϊ`false`ڷϢĹУֻҪ쳣ͽ`SEND_OK``isWaitStoreMsgOK = true`ͽ¼֣\n\n*   `FLUSH_DISK_TIMEOUT`̳ʱ̵ķʽΪSYNC_FLUSHͬsyncFlushTimeoutõʱڣĬ5sûɱ̵Ķõ״̬\n*   `FLUSH_SLAVE_TIMEOUT`ͬӡʱbrokerĽɫΪͬʱõͬʱڣĬΪ5sû֮ͬͻõ״̬\n*   `SLAVE_NOT_AVAILABLE`ӡãáͬûáӡbrokerʱ᷵״̬\n*   `SEND_OK`Ϣͳɹ\n\nϢظϢʧ㷢ϢʧʱͨѡһǶͶɣϢĶˣһѡϢ·ͣпϢظͨ£Ƽ·͵ģϢʱҪȥظϢ\n\nmessageĴСһ㲻512kĬϵķϢķʽͬģͷһֱֱȵصӦȽܣҲ`send(msg, callback)`첽ķʽϢ\n\n## \n\n߿**飨consumer group**ͬ****ԶͬTopicҲԶTopicÿһ鶼Լƫ\n\nϢѷʽһ֣˳ѺͲѡ\n\n*   ˳ѣ߽סϢУȷϢ˳һһıѵ˳ѻһʧϢʱ쳣ֱ׳Ӧ÷`SUSPEND_CURRENT_QUEUE_A_MOMENT`״̬߹һʱ󣬻Ϣ\n*   ѣ߽ϢַʽܷǳãҲƼѷʽѵĹУ쳣ֱ׳Ƿ`RECONSUME_LATER`״̬ڲȷһʱ󣬻ٴ\n\nڲʹ`ThreadPoolExecutor`Ϊ̳߳صģǿͨ`setConsumeThreadMin``setConsumeThreadMax`С̡̺߳߳\n\nһµ齨ԺҪǷ֮ǰʷϢ`CONSUME_FROM_LAST_OFFSET`ʷϢµϢ`CONSUME_FROM_FIRST_OFFSET`ѶеÿһϢ֮ǰʷϢҲһ顣`CONSUME_FROM_TIMESTAMP`ָϢʱ䣬ָʱԺϢᱻѡ\n\nӦòظѣôϢĹУҪϢУ顣\n\nˣ͵ɣһƪǽRocketMQĻ"
  },
  {
    "path": "docs/mq/RocketMQ/RocketMQ系列：环境搭建.md",
    "content": "# [RocketMQϵУһ](https://www.cnblogs.com/boboooo/p/13038950.html)\n\n\n\n\n\nRocketMQǰƷһԴϢмľϢĹܡҵУϢмѡʹRocketMQĻͦģһϵе¶RocketMQģȴRocketMQһЩͻĴʼ\n\nRocketMQ4ɣֱǣƷName ServerϢУBrokersߣproducerߣconsumer4ֶԽˮƽչӶⵥϣͼ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1191201-20200603173058174-1551688390.png)\n\nRocketMQϵһͼǳг4֣ҶǼȺģʽǾͷֱ˵һ˵4֡\n\n## ƷNameServer\n\nName ServerݵĽɫһעģZookeeperòࡣҪ£\n\n*   brokerĹbrokerȺԼϢעᵽNameServerNameServerṩƼÿһbrokerǷ\n*   ·ɹÿһNameServerbrokerȺͶеϢԱͻˣߺߣѯ\n\nNameServerЭŷֲʽϵͳеÿһҹÿһTopic·Ϣ\n\n## Broker\n\nBrokerҪǴ洢ϢṩTopicĻơṩƺģʽһЩֵĴʩϢǿһBrokcerӻơ\n\nBrokerĽɫΪ첽ͬ͡ӡɫ㲻ϢĶʧһͬ͡ӡBrokerϢʧҲνֻҪпþokĻá첽͡ӡbrokerֻ򵥵Ĵֻһ첽áӡҲǿԵġ\n\nᵽbroker֮ıݣbrokerϢҲǿԱ浽̵ģ浽̵ķʽҲ֣Ƽķʽ첽̣ͬǷǳܵġ\n\n## \n\nּ֧ȺbrokerȺϢֶָ֧ؾķʽ\n\nbrokerϢʱõͽͽһ״̬ǵУϢ`isWaitStoreMsgOK = true`ĬҲ`true`Ϊ`false`ڷϢĹУֻҪ쳣ͽ`SEND_OK``isWaitStoreMsgOK = true`ͽ¼֣\n\n*   `FLUSH_DISK_TIMEOUT`̳ʱ̵ķʽΪSYNC_FLUSHͬsyncFlushTimeoutõʱڣĬ5sûɱ̵Ķõ״̬\n*   `FLUSH_SLAVE_TIMEOUT`ͬӡʱbrokerĽɫΪͬʱõͬʱڣĬΪ5sû֮ͬͻõ״̬\n*   `SLAVE_NOT_AVAILABLE`ӡãáͬûáӡbrokerʱ᷵״̬\n*   `SEND_OK`Ϣͳɹ\n\nϢظϢʧ㷢ϢʧʱͨѡһǶͶɣϢĶˣһѡϢ·ͣпϢظͨ£Ƽ·͵ģϢʱҪȥظϢ\n\nmessageĴСһ㲻512kĬϵķϢķʽͬģͷһֱֱȵصӦȽܣҲ`send(msg, callback)`첽ķʽϢ\n\n## \n\n߿**飨consumer group**ͬ****ԶͬTopicҲԶTopicÿһ鶼Լƫ\n\nϢѷʽһ֣˳ѺͲѡ\n\n*   ˳ѣ߽סϢУȷϢ˳һһıѵ˳ѻһʧϢʱ쳣ֱ׳Ӧ÷`SUSPEND_CURRENT_QUEUE_A_MOMENT`״̬߹һʱ󣬻Ϣ\n*   ѣ߽ϢַʽܷǳãҲƼѷʽѵĹУ쳣ֱ׳Ƿ`RECONSUME_LATER`״̬ڲȷһʱ󣬻ٴ\n\nڲʹ`ThreadPoolExecutor`Ϊ̳߳صģǿͨ`setConsumeThreadMin``setConsumeThreadMax`С̡̺߳߳\n\nһµ齨ԺҪǷ֮ǰʷϢ`CONSUME_FROM_LAST_OFFSET`ʷϢµϢ`CONSUME_FROM_FIRST_OFFSET`ѶеÿһϢ֮ǰʷϢҲһ顣`CONSUME_FROM_TIMESTAMP`ָϢʱ䣬ָʱԺϢᱻѡ\n\nӦòظѣôϢĹУҪϢУ顣\n\nˣ͵ɣһƪǽRocketMQĻ"
  },
  {
    "path": "docs/mq/RocketMQ/RocketMQ系列：顺序消费.md",
    "content": "# [RocketMQϵУһ](https://www.cnblogs.com/boboooo/p/13038950.html)\n\n\n\n\n\nRocketMQǰƷһԴϢмľϢĹܡҵУϢмѡʹRocketMQĻͦģһϵе¶RocketMQģȴRocketMQһЩͻĴʼ\n\nRocketMQ4ɣֱǣƷName ServerϢУBrokersߣproducerߣconsumer4ֶԽˮƽչӶⵥϣͼ\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/1191201-20200603173058174-1551688390.png)\n\nRocketMQϵһͼǳг4֣ҶǼȺģʽǾͷֱ˵һ˵4֡\n\n## ƷNameServer\n\nName ServerݵĽɫһעģZookeeperòࡣҪ£\n\n*   brokerĹbrokerȺԼϢעᵽNameServerNameServerṩƼÿһbrokerǷ\n*   ·ɹÿһNameServerbrokerȺͶеϢԱͻˣߺߣѯ\n\nNameServerЭŷֲʽϵͳеÿһҹÿһTopic·Ϣ\n\n## Broker\n\nBrokerҪǴ洢ϢṩTopicĻơṩƺģʽһЩֵĴʩϢǿһBrokcerӻơ\n\nBrokerĽɫΪ첽ͬ͡ӡɫ㲻ϢĶʧһͬ͡ӡBrokerϢʧҲνֻҪпþokĻá첽͡ӡbrokerֻ򵥵Ĵֻһ첽áӡҲǿԵġ\n\nᵽbroker֮ıݣbrokerϢҲǿԱ浽̵ģ浽̵ķʽҲ֣Ƽķʽ첽̣ͬǷǳܵġ\n\n## \n\nּ֧ȺbrokerȺϢֶָ֧ؾķʽ\n\nbrokerϢʱõͽͽһ״̬ǵУϢ`isWaitStoreMsgOK = true`ĬҲ`true`Ϊ`false`ڷϢĹУֻҪ쳣ͽ`SEND_OK``isWaitStoreMsgOK = true`ͽ¼֣\n\n*   `FLUSH_DISK_TIMEOUT`̳ʱ̵ķʽΪSYNC_FLUSHͬsyncFlushTimeoutõʱڣĬ5sûɱ̵Ķõ״̬\n*   `FLUSH_SLAVE_TIMEOUT`ͬӡʱbrokerĽɫΪͬʱõͬʱڣĬΪ5sû֮ͬͻõ״̬\n*   `SLAVE_NOT_AVAILABLE`ӡãáͬûáӡbrokerʱ᷵״̬\n*   `SEND_OK`Ϣͳɹ\n\nϢظϢʧ㷢ϢʧʱͨѡһǶͶɣϢĶˣһѡϢ·ͣпϢظͨ£Ƽ·͵ģϢʱҪȥظϢ\n\nmessageĴСһ㲻512kĬϵķϢķʽͬģͷһֱֱȵصӦȽܣҲ`send(msg, callback)`첽ķʽϢ\n\n## \n\n߿**飨consumer group**ͬ****ԶͬTopicҲԶTopicÿһ鶼Լƫ\n\nϢѷʽһ֣˳ѺͲѡ\n\n*   ˳ѣ߽סϢУȷϢ˳һһıѵ˳ѻһʧϢʱ쳣ֱ׳Ӧ÷`SUSPEND_CURRENT_QUEUE_A_MOMENT`״̬߹һʱ󣬻Ϣ\n*   ѣ߽ϢַʽܷǳãҲƼѷʽѵĹУ쳣ֱ׳Ƿ`RECONSUME_LATER`״̬ڲȷһʱ󣬻ٴ\n\nڲʹ`ThreadPoolExecutor`Ϊ̳߳صģǿͨ`setConsumeThreadMin``setConsumeThreadMax`С̡̺߳߳\n\nһµ齨ԺҪǷ֮ǰʷϢ`CONSUME_FROM_LAST_OFFSET`ʷϢµϢ`CONSUME_FROM_FIRST_OFFSET`ѶеÿһϢ֮ǰʷϢҲһ顣`CONSUME_FROM_TIMESTAMP`ָϢʱ䣬ָʱԺϢᱻѡ\n\nӦòظѣôϢĹУҪϢУ顣\n\nˣ͵ɣһƪǽRocketMQĻ"
  },
  {
    "path": "docs/mq/kafka/消息队列kafka详解：Kafka 快速上手（Java版）.md",
    "content": "**前言**\n\n上篇文章讲解了 Kafka 的基础概念和架构，了解了基本概念之后，必须得实践一波了，所谓“实践才是检验真理的唯一办法”，后续系列关于 Kafka 的文章都以 kafka_2.11-0.9.0.0 为例；另外为了让大家快速入门，本文只提供单机版的安装实战教程，如果有想尝试集群方案的，后面在出一篇集群安装的教程，废话不多说了，直接开干。\n\n## **安装**\n\n### **1\\. 下载**\n\n版本号：kafka_2.11-0.9.0.0\n\n下载地址：[http://kafka.apache.org/downloads](https://link.zhihu.com/?target=http%3A//kafka.apache.org/downloads)\n\n### **2\\. 安装**\n\n\n\n```\n# 安装目录\n$ pwd\n/Users/my/software/study\n\n# 减压\n$ sudo tar -zxvf kafka_2.11-0.9.0.0.tgz\n\n# 重命名\n$ sudo mv kafka_2.11-0.9.0.0.tgz kafka-0.9\n\n# 查看目录结构\n$ cd kafka-0.9 && ls\nLICENSE   NOTICE    bin       config    libs      site-docs\n\n# 目录结构介绍：\n# bin: \t\t\t存放kafka 客户端和服务端的执行脚本\n# config:\t\t存放kafka的一些配置文件\n# libs:\t\t\t存放kafka运行的的jar包\n# site-docs:\t存放kafka的配置文档说明\n\n# 配置环境变量，方便在任意目录下运行kafka命令\n# 博主使用的Mac，所以配置在了 ~/.bash_profile文件中，\n# Linux中则配置在 ~/.bashrc 或者  ~/.zshrc文件中\n$ vim ~/.bash_profile\n\nexport KAFKA_HOME=/Users/haikuan1/software/study/kafka-0.9\nexport PATH=$PATH:$JAVA_HOME:$KAFKA_HOME/bin\n\n# 使得环境变量生效\n$ source ~/.bash_profile\n\n```\n\n\n\n### **3.运行**\n\n### **3.1 启动 zookeeper**\n\n\n\n```\n# 启动zookeeper，因为kafka的元数据需要保存到zookeeper中\n$ bin/zookeeper-server-start.sh config/zookeeper.properties\n\n# 若出现如下信息，则证明zookeeper启动成功了\n[2020-04-25 16:23:44,493] INFO Server environment:user.dir=/Users/haikuan1/software/study/kafka-0.10 (org.apache.zookeeper.server.ZooKeeperServer)\n[2020-04-25 16:23:44,505] INFO tickTime set to 3000 (org.apache.zookeeper.server.ZooKeeperServer)\n[2020-04-25 16:23:44,505] INFO minSessionTimeout set to -1 (org.apache.zookeeper.server.ZooKeeperServer)\n[2020-04-25 16:23:44,505] INFO maxSessionTimeout set to -1 (org.apache.zookeeper.server.ZooKeeperServer)\n[2020-04-25 16:23:44,548] INFO binding to port 0.0.0.0/0.0.0.0:2181 (org.apache.zookeeper.server.NIOServerCnxnFactory)\n\n```\n\n\n\n### **3.2 启动 Kafka server**\n\n\n\n```\n# 以守护进程的方式启动kafka服务端，去掉 -daemon 参数则关闭当前窗口服务端自动退出\n$ bin/kafka-server-start.sh -daemon config/server.properties\n\n```\n\n\n\n### **3.3 kafka 基础命令使用**\n\n\n\n```\n# 1\\. 创建一个topic\n# --replication-factor：指定副本个数\n# --partition：指定partition个数\n# --topic：指定topic的名字\n$ bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partition 1 --topic mytopic\n\n# 2\\. 查看创建成功的topic\n$ kafka-topics.sh --list --zookeeper localhost:2181\n\n# 3\\. 创建生产者和消费者\n\n# 3.1 启动kafka消费端\n# --from-beginning：从头开始消费，该特性也表明kafka消息具有持久性\n$ bin/kafka-console-consumer.sh --zookeeper localhost:2181 --topic mytopic --from-beginning\n\n# 3.2 启动kafka生产端\n# --broker-list：当前的Broker列表，即提供服务的列表\n$ bin/kafka-console-producer.sh --broker-list localhost:9092 --topic mytopic\n\n```\n\n\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-a1e0c6db02c2822b2ad88db1c3b0b8a7_720w.webp)\n\n</figure>\n\n### **4.使用 Java 连接 kafka 进行测试**\n\n### **4.1 创建一个 maven 工程，引入如下 pom 依赖**\n\n\n\n```\n<dependency>\n    <groupId>org.apache.kafka</groupId>\n    kafka-clients\n    <version>0.9.0.0</version>\n</dependency>\n\n<dependency>\n    <groupId>org.apache.kafka</groupId>\n    kafka_2.11\n    <version>0.9.0.0</version>\n</dependency>\n\n```\n\n\n\n### **4.2 消费者端代码**\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-5e9876ca0dc733fe8c2df51d2e42d1ce_720w.webp)\n\n</figure>\n\n### **4.3 生产者端代码**\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-d1e6bfdf23c2b42e23f30d4430c587e2_720w.webp)\n\n</figure>\n\n### **4.4 消费者端效果图**\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-1912f5b2b12ac766d746d88a04b9bd28_720w.webp)\n\n</figure>\n\n### **5.总结**\n\n本文介绍了 kafka 单机版安装及简单命令使用，然后使用 Java 实现了生产者和消费者的简单功能，虽然内容可能比较简单，但还是**强烈建议大家手动去实践一下**，从而对 kafka 的架构有一个更深入的理解。"
  },
  {
    "path": "docs/mq/kafka/消息队列kafka详解：Kafka一条消息存到broker的过程.md",
    "content": "**前言**\n\n经过上篇文章的简单实战之后，**今天来聊聊生产者将消息从客户端发送到 Broker 上背后发生了哪些故事**，看不看由你，但是我保证可以本篇文章你一定可以学到应用背后的一些实质东西。\n\n本文我们从以下 4 个方面来探讨下一条消息如何被准确的发送到 Broker 的 partition 上。\n\n**1\\. 客户端组件**\n\n**2\\. 客户端缓存存储模型**\n\n**3\\. 确定消息的 partition 位置**\n\n**4\\. 发送线程的工作原理**\n\n* * *\n\n## **客户端组件**\n\n*   **KafkaProducer:**\n\nKafkaProducer 是一个生产者客户端的进程，通过该对象启动生产者来发送消息。\n\n*   **RecordAccumulator:**\n\nRecordAccumulator 是一个记录收集器，用于收集客户端发送的消息，并将收集到的消息暂存到客户端缓存中。\n\n*   **Sender:**\n\nSender 是一个发送线程，负责读取记录收集器中缓存的批量消息，经过一些中间转换操作，将要发送的数据准备好，然后交由 Selector 进行网络传输。\n\n*   **Selector:**\n\nSelector 是一个选择器，用于处理网络连接和读写处理，使用网络连接处理客户端上的网络请求。\n\n通过使用以上四大组件即可完成客户端消息的发送工作。消息在网络中传输的方式只能通过二级制的方式，所以首先需要将消息序列化为二进制形式缓存在客户端，kafka 使用了双端队列的方式将消息缓存起来，然后使用发送线程（Sender）读取队列中的消息交给 Selector 进行网络传输发送给服务端（Broker）\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-7d57acd1d7dc5942e999e6ffebb28679_720w.webp)\n\n</figure>\n\n以上为发送消息的主流程，附上部分源码供大家参考，接下来分析下几个非常重要流程的具体实现原理。\n\n* * *\n\n## **客户端缓存存储模型**\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-5da65c5f9f8c0c9082e07c6431e78cd2_720w.webp)\n\n</figure>\n\n从上图可以看出，一条消息首先需要确定要被存储到那个 partition 对应的双端队列上；其次，存储消息的双端队列是以批的维度存储的，即 N 条消息组成一批，一批消息最多存储 N 条，超过后则新建一个组来存储新消息；其次，新来的消息总是从左侧写入，即越靠左侧的消息产生的时间越晚；最后，只有当一批消息凑够 N 条后才会发送给 Broker，否则不会发送到 Broker 上。\n\n了解了客户端存储模型后，来探讨下确定消息的 partition（分区）位置？\n\n* * *\n\n## **确定消息的 partition 位置**\n\n消息可分为两种，一种是指定了 key 的消息，一种是没有指定 key 的消息。\n\n对于指定了 key 的消息，partition 位置的计算方式为：**`Utils.murmur2(key) % numPartitions`**，即先对 key 进行哈希计算，然后在于 partition 个数求余，从而得到该条消息应该被存储在哪个 partition 上。\n\n对于没有指定 key 的消息，partition 位置的计算方式为：**采用 round-robin 方式确定 partition 位置**，即采用轮询的方式，平均的将消息分布到不同的 partition 上，从而避免某些 partition 数据量过大影响 Broker 和消费端性能。\n\n### **注意**\n\n由于 partition 有主副的区分，此处参与计算的 partition 数量是当前有主 partition 的数量，即如果某个 partition 无主的时候，则此 partition 是不能够进行数据写入的。\n\n稍微解释一下，主副 partition 的机制是为了提高 kafka 系统的容错性的，即当某个 Broker 意外宕机时，在此 Broker 上的主 partition 状态为不可读写时（只有主 partition 可对外提供读写服务，副 partition 只有数据备份的功能），kafka 会从主 partition 对应的 N 个副 partition 中挑选一个，并将其状态改为主 partition，从而继续对外提供读写操作。\n\n消息被确定分配到某个 partition 对应记录收集器（即双端队列）后，接下来，发送线程（Sender）从记录收集器中收集满足条件的批数据发送给 Broker，那么发送线程是如何收集满足条件的批数据的？批数据是按照 partition 维度发送的还是按照 Broker 维度发送数据的？\n\n* * *\n\n## **发送线程的工作原理**\n\nSender 线程的主要工作是收集满足条件的批数据，何为满足条件的批数据？缓存数据是以批维度存储的，当一批数据量达到指定的 N 条时，就满足发送给 Broker 的条件了。\n\npartition 维度和 Broker 维度发送消息模型对比。\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-36b7c2761f17fb2d6481747523999011_720w.webp)\n\n</figure>\n\n从图中可以看出，左侧按照 partition 维度发送消息，每个 partition 都需要和 Broker 建连，总共发生了四次网络连接。而右侧将分布在同一个 Broker 的 partition 按组聚合后在与 Broker 建连，只需要两次网络连接即可。所以 Kafka 选择右侧的方式。\n\n### **Sender 的主要工作**\n\n第一步：扫描记录收集器中满足条件的批数据，然后将 partition -> 批数据映射转换成 BrokerId -> N 批数据的映射。第二步：Sender 线程会为每个 BrokerId 创建一个客户端请求，然后将请求交给 NetWorkClient，由 NetWrokClient 去真正发送网络请求到 Broker。\n\n### **NetWorkClient 的工作内容**\n\nSender 线程准备好要发送的数据后，交由 NetWorkClient 来进行网络相关操作。主要包括客户端与服务端的建连、发送客户端请求、接受服务端响应。完成如上一系列的工作主要由如下方法完成。\n\n1.  reday()方法。从记录收集器获取准备完毕的节点，并连接所有准备好的节点。\n2.  send()方法。为每个节点创建一个客户端请求，然后将请求暂时存到节点对应的 Channel（通道）中。\n3.  poll()方法。该方法会真正轮询网络请求，发送请求给服务端节点和接受服务端的响应。\n\n* * *\n\n## **总结**\n\n以上，即为生产者客户端的一条消息从生产到发送到 Broker 上的全过程。现在是不是就很清晰了呢？也许有些朋友会比较疑惑它的**网络请求模型是什么样的**，作者就猜你会你会问，下一篇我们就来扒开它的神秘面纱看看其究竟是怎么实现的，敬请期待。"
  },
  {
    "path": "docs/mq/kafka/消息队列kafka详解：Kafka介绍.md",
    "content": "## 一.官网Kafka介绍\n\n## 1.1 什么是事件流?\n\n事件流是相当于人体中枢神经系统的数字系统。它是“永远在线”世界的技术基础，在这个世界里，企业越来越多地由软件定义和自动化，软件的用户也更多地是软件。\n\n从技术上讲，事件流是指以事件流的形式从数据库、传感器、移动设备、云服务和软件应用等事件源实时捕获数据的实践;持久地存储这些事件流以备以后检索;实时和回顾性地操作、处理和响应事件流;并根据需要将事件流路由到不同的目的地技术。因此，事件流确保了数据的连续流动和解释，从而使正确的信息在正确的地点、正确的时间出现。\n\n## 1.2 我可以使用事件流做什么?\n\n事件流应用于众多行业和组织的各种用例。它的许多例子包括: 1. 实时处理支付和金融交易，如股票交易所、银行和保险。 2. 实时跟踪和监控汽车、卡车、车队和运输，如物流和汽车行业。 3. 持续捕获和分析来自物联网设备或其他设备的传感器数据，如工厂和风电场。 4. 收集并立即响应客户的互动和订单，如零售、酒店和旅游行业，以及移动应用程序。 5. 监测住院病人，预测病情变化，确保在紧急情况下及时治疗。 6. 连接、存储公司不同部门产生的数据并使其可用。 7. 作为数据平台、事件驱动架构和微服务的基础。\n\n## 1.3 Apache Kafka®是一个事件流平台。这是什么意思?\n\nKafka结合了三个关键的功能，所以你可以用一个单一的战斗测试解决方案来实现端到端事件流的用例: 1. 发布(写)和订阅(读)事件流，包括从其他系统连续导入/导出数据。 2. 持久性和可靠地存储事件流，只要你想。 3. 在事件发生或回顾时处理事件流。\n\n所有这些功能都是以分布式、高度可伸缩、弹性、容错和安全的方式提供的。Kafka可以部署在裸金属硬件、虚拟机和容器上，也可以部署在云上。您可以选择自管理您的Kafka环境和使用由各种供应商提供的完全管理的服务。\n\n## 1.4 简而言之，Kafka是如何工作的?\n\nKafka是一个分布式系统，由服务器和客户端组成，通过高性能的TCP网络协议进行通信。它可以部署在裸金属硬件、虚拟机和内部环境中的容器上，也可以部署在云环境中。\n\n**服务器:**Kafka作为一个集群运行一个或多个服务器，可以跨越多个数据中心或云区域。其中一些服务器构成存储层，称为代理。其他服务器运行Kafka Connect来持续导入和导出数据作为事件流，将Kafka与您现有的系统集成，如关系数据库以及其他Kafka集群。为了让你实现关键任务的用例，Kafka集群具有高度的可扩展性和容错性:如果它的任何一个服务器发生故障，其他服务器将接管它们的工作，以确保持续的操作而不丢失任何数据。\n\n**客户机:**它们允许您编写分布式应用程序和微服务，这些应用程序和微服务可以并行地、大规模地读取、写入和处理事件流，甚至在出现网络问题或机器故障的情况下也可以容错。Kafka附带了一些这样的客户端，这些客户端被Kafka社区提供的几十个客户端增强了:客户端可以用于Java和Scala，包括更高级别的Kafka Streams库，用于Go、Python、C/ c++和许多其他编程语言以及REST api。\n\n## 1.5 主要概念和术语\n\n事件记录了在世界上或你的企业中“发生了某事”的事实。在文档中也称为记录或消息。当你读或写数据到Kafka时，你以事件的形式做这件事。从概念上讲，事件具有键、值、时间戳和可选的元数据头。下面是一个例子: 1. 活动重点:“爱丽丝” 2. 事件值:“向Bob支付200美元” 3. 事件时间戳:“2020年6月25日下午2:06。”\n\n生产者是那些向Kafka发布(写)事件的客户端应用程序，而消费者是那些订阅(读和处理)这些事件的应用程序。在Kafka中，生产者和消费者是完全解耦的，彼此是不可知的，这是实现Kafka闻名的高可扩展性的一个关键设计元素。例如，生产者从不需要等待消费者。Kafka提供了各种各样的保证，比如精确处理一次事件的能力。\n\n事件被组织并持久地存储在主题中。很简单，一个主题类似于文件系统中的一个文件夹，事件就是该文件夹中的文件。一个示例主题名称可以是“payments”。Kafka中的主题总是多生产者和多订阅者:一个主题可以有0个、1个或多个生产者向它写入事件，也可以有0个、1个或多个消费者订阅这些事件。主题中的事件可以根据需要经常读取——与传统消息传递系统不同，事件在使用后不会删除。相反，你可以通过每个主题的配置设置来定义Kafka应该保留你的事件多长时间，之后旧的事件将被丢弃。Kafka的性能相对于数据大小来说是不变的，所以长时间存储数据是完全可以的。\n\n主题是分区的，这意味着一个主题分散在位于不同Kafka broker上的多个“桶”上。这种数据的分布式位置对于可伸缩性非常重要，因为它允许客户机应用程序同时从/向多个代理读取和写入数据。当一个新事件被发布到一个主题时，它实际上被附加到主题的一个分区中。具有相同事件键(例如，客户或车辆ID)的事件被写入同一个分区，Kafka保证任何给定主题分区的消费者都将始终以写入的完全相同的顺序读取该分区的事件。\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-39059344afb24ff7436bf7fb06bddde4_720w.webp)\n\n</figure>\n\n让你的数据容错和可用性,每一个主题可以被复制,甚至跨geo-regions或数据中心,这样总有多个经纪人有一份数据以防出错,你想做代理维护,等等。一个常见的生产设置是复制因子3，也就是说，您的数据总是有三个副本。这个复制是在主题分区级别执行的。\n\n## 二.Kafka简介\n\nKafka是一种消息队列，主要用来处理大量数据状态下的消息队列，一般用来做日志的处理。既然是消息队列，那么Kafka也就拥有消息队列的相应的特性了。\n\n**消息队列的好处** 1. 解耦合 耦合的状态表示当你实现某个功能的时候，是直接接入当前接口，而利用消息队列，可以将相应的消息发送到消息队列，这样的话，如果接口出了问题，将不会影响到当前的功能。\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-e37a18ea7eddc69582d634cc881eb257_720w.webp)\n\n</figure>\n\n1.  异步处理 异步处理替代了之前的同步处理，异步处理不需要让流程走完就返回结果，可以将消息发送到消息队列中，然后返回结果，剩下让其他业务处理接口从消息队列中拉取消费处理即可。\n\n2.  流量削峰 高流量的时候，使用消息队列作为中间件可以将流量的高峰保存在消息队列中，从而防止了系统的高请求，减轻服务器的请求处理压力。\n\n## 2.1 Kafka消费模式\n\nKafka的消费模式主要有两种：一种是一对一的消费，也即点对点的通信，即一个发送一个接收。第二种为一对多的消费，即一个消息发送到消息队列，消费者根据消息队列的订阅拉取消息消费。\n\n**一对一**\n\n<figure data-size=\"normal\">\n\n\n![image-20230525200024084](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230525200024084.png)\n\n</figure>\n\n消息生产者发布消息到Queue队列中，通知消费者从队列中拉取消息进行消费。消息被消费之后则删除，Queue支持多个消费者，但对于一条消息而言，只有一个消费者可以消费，即一条消息只能被一个消费者消费。\n\n**一对多**\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-d97a2898f3bdc417262bc88be616281c_720w.webp)\n\n</figure>\n\n这种模式也称为发布/订阅模式，即利用Topic存储消息，消息生产者将消息发布到Topic中，同时有多个消费者订阅此topic，消费者可以从中消费消息，注意发布到Topic中的消息会被多个消费者消费，消费者消费数据之后，数据不会被清除，Kafka会默认保留一段时间，然后再删除。\n\n## 2.2 Kafka的基础架构\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-ef94691300117c049301d88c6337c9c2_720w.webp)\n\n</figure>\n\nKafka像其他Mq一样，也有自己的基础架构，主要存在生产者Producer、Kafka集群Broker、消费者Consumer、注册中心Zookeeper.\n\n1. Producer：消息生产者，向Kafka中发布消息的角色。\n\n2. Consumer：消息消费者，即从Kafka中拉取消息消费的客户端。\n\n3. Consumer Group：消费者组，消费者组则是一组中存在多个消费者，消费者消费Broker中当前Topic的不同分区中的消息，消费者组之间互不影响，所有的消费者都属于某个消费者组，即消费者组是逻辑上的一个订阅者。某一个分区中的消息只能够一个消费者组中的一个消费者所消费\n\n4. Broker：经纪人，一台Kafka服务器就是一个Broker，一个集群由多个Broker组成，一个Broker可以容纳多个Topic。\n\n5. Topic：主题，可以理解为一个队列，生产者和消费者都是面向一个Topic\n\n6. Partition：分区，为了实现扩展性，一个非常大的Topic可以分布到多个Broker上，一个Topic可以分为多个Partition，每个Partition是一个有序的队列(分区有序，不能保证全局有序)\n\n7. Replica：副本Replication，为保证集群中某个节点发生故障，节点上的Partition数据不丢失，Kafka可以正常的工作，Kafka提供了副本机制，一个Topic的每个分区有若干个副本，一个Leader和多个Follower\n\n8. Leader：每个分区多个副本的主角色，生产者发送数据的对象，以及消费者消费数据的对象都是Leader。\n\n9. Follower：每个分区多个副本的从角色，实时的从Leader中同步数据，保持和Leader数据的同步，Leader发生故障的时候，某个Follower会成为新的Leader。\n\n上述一个Topic会产生多个分区Partition，分区中分为Leader和Follower，消息一般发送到Leader，Follower通过数据的同步与Leader保持同步，消费的话也是在Leader中发生消费，如果多个消费者，则分别消费Leader和各个Follower中的消息，当Leader发生故障的时候，某个Follower会成为主节点，此时会对齐消息的偏移量。\n\n## 参考文章\nhttps://blog.csdn.net/cao131502\nhttps://zhuanlan.zhihu.com/p/137811719"
  },
  {
    "path": "docs/mq/kafka/消息队列kafka详解：Kafka原理分析总结篇.md",
    "content": "## 一、概述\n\nKakfa起初是由LinkedIn公司开发的一个分布式的消息系统，后成为Apache的一部分，它使用Scala编写，以可水平扩展和高吞吐率而被广泛使用。目前越来越多的开源分布式处理系统如Cloudera、Apache Storm、Spark等都支持与Kafka集成。\n\nKafka凭借着自身的优势，越来越受到互联网企业的青睐，唯品会也采用Kafka作为其内部核心消息引擎之一。Kafka作为一个商业级消息中间件，消息可靠性的重要性可想而知。如何确保消息的精确传输?如何确保消息的准确存储?如何确保消息的正确消费?这些都是需要考虑的问题。本文首先从Kafka的架构着手，先了解下Kafka的基本原理，然后通过对kakfa的存储机制、复制原理、同步原理、可靠性和持久性保证等等一步步对其可靠性进行分析，最后通过benchmark来增强对Kafka高可靠性的认知。\n\n\n\n\n\n[回到顶部](https://www.cnblogs.com/wangzhuxing/p/10051512.html#_labelTop)\n\n## 二、Kafka的使用场景\n\n（1）日志收集：一个公司可以用Kafka可以收集各种服务的log，通过kafka以统一接口服务的方式开放给各种consumer，例如Hadoop、Hbase、Solr等；\n\n（2）消息系统：解耦和生产者和消费者、缓存消息等；\n\n（3）用户活动跟踪：Kafka经常被用来记录web用户或者app用户的各种活动，如浏览网页、搜索、点击等活动，这些活动信息被各个服务器发布到kafka的topic中，然后订阅者通过订阅这些topic来做实时的监控分析，或者装载到Hadoop、数据仓库中做离线分析和挖掘；\n\n（4）运营指标：Kafka也经常用来记录运营监控数据。包括收集各种分布式应用的数据，生产各种操作的集中反馈，比如报警和报告；\n\n（5）流式处理：比如spark streaming和storm；\n\n（6）事件源；\n\n\n\n[回到顶部](https://www.cnblogs.com/wangzhuxing/p/10051512.html#_labelTop)\n\n## 三、Kafka基本架构\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/843808-20181201215942487-1393117307.png)\n\n如上图所示，一个典型的Kafka体系架构包括：\n\n*   若干Producer(可以是服务器日志，业务数据，页面前端产生的page view等等)，\n*   若干broker(Kafka支持水平扩展，一般broker数量越多，集群吞吐率越高)，\n*   若干Consumer (Group)，以及一个Zookeeper集群。\n\nKafka通过Zookeeper管理集群配置，选举leader，以及在consumer group发生变化时进行rebalance。Producer使用push(推)模式将消息发布到broker，Consumer使用pull(拉)模式从broker订阅并消费消息。\n\n\n\n### 1、Topic & Partition\n\n一个topic可以认为一个一类消息，每个topic将被分成多个partition，每个partition在存储层面是append log文件。任何发布到此partition的消息都会被追加到log文件的尾部，每条消息在文件中的位置称为offset(偏移量)，offset为一个long型的数字，它唯一标记一条消息。每条消息都被append到partition中，是顺序写磁盘，因此效率非常高(经验证，顺序写磁盘效率比随机写内存还要高，这是Kafka高吞吐率的一个很重要的保证)。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/843808-20181201220820060-2075971944.png)\n\n每一条消息被发送到broker中，会根据partition规则选择被存储到哪一个partition。partition机制可以通过指定producer的partition.class这一参数来指定，该class必须实现kafka.producer.Partitioner接口。如果partition规则设置的合理，所有消息可以均匀分布到不同的partition里，这样就实现了水平扩展。(如果一个topic对应一个文件，那这个文件所在的机器I/O将会成为这个topic的性能瓶颈，而partition解决了这个问题)。在创建topic时可以在$KAFKA_HOME/config/server.properties中指定这个partition的数量(如下所示)，当然可以在topic创建之后去修改partition的数量。\n\n\n\n<pre># The default number of log partitions per topic. More partitions allow greater\n# parallelism for consumption, but this will also result in more files across\n# the brokers.\n#默认partitions数量\nnum.partitions=1</pre>\n\n\n\n\n[回到顶部](https://www.cnblogs.com/wangzhuxing/p/10051512.html#_labelTop)\n\n## 四、高可靠性存储分析概述\n\nKafka的高可靠性的保障来源于其健壮的副本(replication)策略。通过调节其副本相关参数，可以使得Kafka在性能和可靠性之间运转的游刃有余。Kafka从0.8.x版本开始提供partition级别的复制,replication的数量可以在$KAFKA_HOME/config/server.properties中配置(default.replication.refactor)。\n\n这里先从Kafka文件存储机制入手，从最底层了解Kafka的存储细节，进而对其的存储有个微观的认知。之后通过Kafka复制原理和同步方式来阐述宏观层面的概念。最后从ISR，HW，leader选举以及数据可靠性和持久性保证等等各个维度来丰富对Kafka相关知识点的认知。\n\n[回到顶部](https://www.cnblogs.com/wangzhuxing/p/10051512.html#_labelTop)\n\n## 五、Kafka文件存储机制\n\nKafka中消息是以topic进行分类的，生产者通过topic向Kafka broker发送消息，消费者通过topic读取数据。然而topic在物理层面又能以partition为分组，一个topic可以分成若干个partition，那么topic以及partition又是怎么存储的呢?partition还可以细分为segment，一个partition物理上由多个segment组成，那么这些segment又是什么呢?下面我们来一一揭晓。\n\n为了便于说明问题，假设这里只有一个Kafka集群，且这个集群只有一个Kafka broker，即只有一台物理机。在这个Kafka broker中配置($KAFKA_HOME/config/server.properties中)log.dirs=/tmp/kafka-logs，以此来设置Kafka消息文件存储目录，与此同时创建一个topic：topic_zzh_test，partition的数量为4($KAFKA_HOME/bin/kafka-topics.sh –create –zookeeper localhost:2181 –partitions 4 –topic topic_vms_test –replication-factor 4)。那么我们此时可以在/tmp/kafka-logs目录中可以看到生成了4个目录：\n\n\n\n\n\n<pre>drwxr-xr-x 2 root root 4096 Apr 10 16:10 topic_zzh_test-0 \ndrwxr-xr-x 2 root root 4096 Apr 10 16:10 topic_zzh_test-1 \ndrwxr-xr-x 2 root root 4096 Apr 10 16:10 topic_zzh_test-2 \ndrwxr-xr-x 2 root root 4096 Apr 10 16:10 topic_zzh_test-3  </pre>\n\n\n\n\n在Kafka文件存储中，同一个topic下有多个不同的partition，每个partiton为一个目录，partition的名称规则为：topic名称+有序序号，第一个序号从0开始计，最大的序号为partition数量减1，partition是实际物理上的概念，而topic是逻辑上的概念。\n\n上面提到partition还可以细分为segment，这个segment又是什么?如果就以partition为最小存储单位，我们可以想象当Kafka producer不断发送消息，必然会引起partition文件的无限扩张，这样对于消息文件的维护以及已经被消费的消息的清理带来严重的影响，所以这里以segment为单位又将partition细分。每个partition(目录)相当于一个巨型文件被平均分配到多个大小相等的segment(段)数据文件中(每个segment 文件中消息数量不一定相等)这种特性也方便old segment的删除，即方便已被消费的消息的清理，提高磁盘的利用率。每个partition只需要支持顺序读写就行，segment的文件生命周期由服务端配置参数(log.segment.bytes，log.roll.{ms,hours}等若干参数)决定。\n\n\n\n\n\n[![复制代码](https://common.cnblogs.com/images/copycode.gif)](javascript:void(0); \"复制代码\")\n\n<pre> #在强制刷新数据到磁盘允许接收消息的数量\n#log.flush.interval.messages=10000 # 在强制刷新之前，消息可以在日志中停留的最长时间\n#log.flush.interval.ms=1000 #一个日志的最小存活时间，可以被删除\nlog.retention.hours=168 #  一个基于大小的日志保留策略。段将被从日志中删除只要剩下的部分段不低于log.retention.bytes。\n#log.retention.bytes=1073741824 #  每一个日志段大小的最大值。当到达这个大小时，会生成一个新的片段。\nlog.segment.bytes=1073741824 # 检查日志段的时间间隔，看是否可以根据保留策略删除它们\nlog.retention.check.interval.ms=300000</pre>\n\n\n[![复制代码](https://common.cnblogs.com/images/copycode.gif)](javascript:void(0); \"复制代码\")\n\n\n\nsegment文件由两部分组成，分别为“.index”文件和“.log”文件，分别表示为segment索引文件和数据文件。这两个文件的命令规则为：partition全局的第一个segment从0开始，后续每个segment文件名为上一个segment文件最后一条消息的offset值，数值大小为64位，20位数字字符长度，没有数字用0填充，如下：\n\n\n\n\n\n\n\n[![复制代码](https://common.cnblogs.com/images/copycode.gif)](javascript:void(0); \"复制代码\")\n\n<pre>00000000000000000000.index 00000000000000000000.log 00000000000000170410.index 00000000000000170410.log 00000000000000239430.index 00000000000000239430.log  </pre>\n\n[![复制代码](https://common.cnblogs.com/images/copycode.gif)](javascript:void(0); \"复制代码\")\n\n\n\n以上面的segment文件为例，展示出segment：00000000000000170410的“.index”文件和“.log”文件的对应的关系，如下图：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/843808-20181201224133022-2085407889.png)\n\n\n\n\n\n如上图，“.index”索引文件存储大量的元数据，“.log”数据文件存储大量的消息，索引文件中的元数据指向对应数据文件中message的物理偏移地址。其中以“.index”索引文件中的元数据[3, 348]为例，在“.log”数据文件表示第3个消息，即在全局partition中表示170410+3=170413个消息，该消息的物理偏移地址为348。\n\n那么如何从partition中通过offset查找message呢?\n\n以上图为例，读取offset=170418的消息，首先查找segment文件，其中00000000000000000000.index为最开始的文件，第二个文件为00000000000000170410.index(起始偏移为170410+1=170411)，而第三个文件为00000000000000239430.index(起始偏移为239430+1=239431)，所以这个offset=170418就落到了第二个文件之中。其他后续文件可以依次类推，以其实偏移量命名并排列这些文件，然后根据二分查找法就可以快速定位到具体文件位置。其次根据00000000000000170410.index文件中的[8,1325]定位到00000000000000170410.log文件中的1325的位置进行读取。\n\n要是读取offset=170418的消息，从00000000000000170410.log文件中的1325的位置进行读取，那么怎么知道何时读完本条消息，否则就读到下一条消息的内容了?\n\n这个就需要联系到消息的物理结构了，消息都具有固定的物理结构，包括：offset(8 Bytes)、消息体的大小(4 Bytes)、crc32(4 Bytes)、magic(1 Byte)、attributes(1 Byte)、key length(4 Bytes)、key(K Bytes)、payload(N Bytes)等等字段，可以确定一条消息的大小，即读取到哪里截止。\n\n[回到顶部](https://www.cnblogs.com/wangzhuxing/p/10051512.html#_labelTop)\n\n## 六、复制原理和同步方式\n\nKafka中topic的每个partition有一个预写式的日志文件，虽然partition可以继续细分为若干个segment文件，但是对于上层应用来说可以将partition看成最小的存储单元(一个有多个segment文件拼接的“巨型”文件)，每个partition都由一些列有序的、不可变的消息组成，这些消息被连续的追加到partition中。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/843808-20181201231403669-958736996.png)\n\n上图中有两个新名词：HW和LEO。这里先介绍下LEO，LogEndOffset的缩写，表示每个partition的log最后一条Message的位置。HW是HighWatermark的缩写，是指consumer能够看到的此partition的位置，这个涉及到多副本的概念，这里先提及一下，下节再详表。\n\n言归正传，为了提高消息的可靠性，Kafka每个topic的partition有N个副本(replicas)，其中N(大于等于1)是topic的复制因子(replica fator)的个数。Kafka通过多副本机制实现故障自动转移，当Kafka集群中一个broker失效情况下仍然保证服务可用。在Kafka中发生复制时确保partition的日志能有序地写到其他节点上，N个replicas中，其中一个replica为leader，其他都为follower, leader处理partition的所有读写请求，与此同时，follower会被动定期地去复制leader上的数据。\n\n如下图所示，Kafka集群中有4个broker, 某topic有3个partition,且复制因子即副本个数也为3：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/843808-20181201231724531-2038730622.png)\n\nKafka提供了数据复制算法保证，如果leader发生故障或挂掉，一个新leader被选举并被接受客户端的消息成功写入。Kafka确保从同步副本列表中选举一个副本为leader，或者说follower追赶leader数据。leader负责维护和跟踪ISR(In-Sync Replicas的缩写，表示副本同步队列，具体可参考下节)中所有follower滞后的状态。当producer发送一条消息到broker后，leader写入消息并复制到所有follower。消息提交之后才被成功复制到所有的同步副本。消息复制延迟受最慢的follower限制，重要的是快速检测慢副本，如果follower“落后”太多或者失效，leader将会把它从ISR中删除。\n\n[回到顶部](https://www.cnblogs.com/wangzhuxing/p/10051512.html#_labelTop)\n\n## 七、零拷贝\n\nKafka中存在大量的网络数据持久化到磁盘（Producer到Broker）和磁盘文件通过网络发送（Broker到Consumer）的过程。这一过程的性能直接影响Kafka的整体吞吐量。\n\n\n\n### 1、传统模式下的四次拷贝与四次上下文切换\n\n以将磁盘文件通过网络发送为例。传统模式下，一般使用如下伪代码所示的方法先将文件数据读入内存，然后通过Socket将内存中的数据发送出去。\n\n\n\n<pre>buffer = File.read\nSocket.send(buffer)</pre>\n\n\n\n\n这一过程实际上发生了四次数据拷贝。首先通过系统调用将文件数据读入到内核态Buffer（DMA拷贝），然后应用程序将内存态Buffer数据读入到用户态Buffer（CPU拷贝），接着用户程序通过Socket发送数据时将用户态Buffer数据拷贝到内核态Buffer（CPU拷贝），最后通过DMA拷贝将数据拷贝到NIC Buffer。同时，还伴随着四次上下文切换，如下图所示。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/843808-20181227142339448-1209004133.png)\n\n### 2、sendfile和transferTo实现零拷贝\n\nLinux 2.4+内核通过`sendfile`系统调用，提供了零拷贝。数据通过DMA拷贝到内核态Buffer后，直接通过DMA拷贝到NIC Buffer，无需CPU拷贝。这也是零拷贝这一说法的来源。除了减少数据拷贝外，因为整个读文件-网络发送由一个`sendfile`调用完成，整个过程只有两次上下文切换，因此大大提高了性能。零拷贝过程如下图所示。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/843808-20181227142423000-1025665055.png)\n\n从具体实现来看，Kafka的数据传输通过TransportLayer来完成，其子类`PlaintextTransportLayer`通过[Java NIO](http://www.jasongj.com/java/nio_reactor/)的FileChannel的`transferTo`和`transferFrom`方法实现零拷贝，如下所示。\n\n\n\n<pre>@Override public long transferFrom(FileChannel fileChannel, long position, long count) throws IOException { return fileChannel.transferTo(position, count, socketChannel);\n}</pre>\n\n\n\n\n**注：** `transferTo`和`transferFrom`并不保证一定能使用零拷贝。实际上是否能使用零拷贝与操作系统相关，如果操作系统提供`sendfile`这样的零拷贝系统调用，则这两个方法会通过这样的系统调用充分利用零拷贝的优势，否则并不能通过这两个方法本身实现零拷贝。\n\n\n\n\n\n[回到顶部](https://www.cnblogs.com/wangzhuxing/p/10051512.html#_labelTop)\n\n## 八、 ISR（副本同步队列）\n\n上节我们涉及到ISR (In-Sync Replicas)，这个是指副本同步队列。副本数对Kafka的吞吐率是有一定的影响，但极大的增强了可用性。默认情况下Kafka的replica数量为1，即每个partition都有一个唯一的leader，为了确保消息的可靠性，通常应用中将其值(由broker的参数offsets.topic.replication.factor指定)大小设置为大于1，比如3。 所有的副本(replicas)统称为Assigned Replicas，即AR。ISR是AR中的一个子集，由leader维护ISR列表，follower从leader同步数据有一些延迟(包括延迟时间replica.lag.time.max.ms和延迟条数replica.lag.max.messages两个维度, 当前最新的版本0.10.x中只支持replica.lag.time.max.ms这个维度)，任意一个超过阈值都会把follower剔除出ISR, 存入OSR(Outof-Sync Replicas)列表，新加入的follower也会先存放在OSR中。AR=ISR+OSR\n\nKafka 0.10.x版本后移除了replica.lag.max.messages参数，只保留了replica.lag.time.max.ms作为ISR中副本管理的参数。为什么这样做呢?replica.lag.max.messages表示当前某个副本落后leaeder的消息数量超过了这个参数的值，那么leader就会把follower从ISR中删除。假设设置replica.lag.max.messages=4，那么如果producer一次传送至broker的消息数量都小于4条时，因为在leader接受到producer发送的消息之后而follower副本开始拉取这些消息之前，follower落后leader的消息数不会超过4条消息，故此没有follower移出ISR，所以这时候replica.lag.max.message的设置似乎是合理的。但是producer发起瞬时高峰流量，producer一次发送的消息超过4条时，也就是超过replica.lag.max.messages，此时follower都会被认为是与leader副本不同步了，从而被踢出了ISR。但实际上这些follower都是存活状态的且没有性能问题。那么在之后追上leader,并被重新加入了ISR。于是就会出现它们不断地剔出ISR然后重新回归ISR，这无疑增加了无谓的性能损耗。而且这个参数是broker全局的。设置太大了，影响真正“落后”follower的移除;设置的太小了，导致follower的频繁进出。无法给定一个合适的replica.lag.max.messages的值，故此，新版本的Kafka移除了这个参数。注：ISR中包括：leader和follower。\n\n\n\n上面一节还涉及到一个概念，即HW。HW俗称高水位，HighWatermark的缩写，取一个partition对应的ISR中最小的LEO作为HW，consumer最多只能消费到HW所在的位置。另外每个replica都有HW,leader和follower各自负责更新自己的HW的状态。对于leader新写入的消息，consumer不能立刻消费，leader会等待该消息被所有ISR中的replicas同步后更新HW，此时消息才能被consumer消费。这样就保证了如果leader所在的broker失效，该消息仍然可以从新选举的leader中获取。对于来自内部broKer的读取请求，没有HW的限制。\n\n下图详细的说明了当producer生产消息至broker后，ISR以及HW和LEO的流转过程：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/843808-20181202175002622-1830127657.png)\n\n由此可见，Kafka的复制机制既不是完全的同步复制，也不是单纯的异步复制。事实上，同步复制要求所有能工作的follower都复制完，这条消息才会被commit，这种复制方式极大的影响了吞吐率。而异步复制方式下，follower异步的从leader复制数据，数据只要被leader写入log就被认为已经commit，这种情况下如果follower都还没有复制完，落后于leader时，突然leader宕机，则会丢失数据。而Kafka的这种使用ISR的方式则很好的均衡了确保数据不丢失以及吞吐率。\n\nKafka的ISR的管理最终都会反馈到Zookeeper节点上。具体位置为：/brokers/topics/[topic]/partitions/[partition]/state。\n\n目前有两个地方会对这个Zookeeper的节点进行维护：\n\nController来维护：Kafka集群中的其中一个Broker会被选举为Controller，主要负责Partition管理和副本状态管理，也会执行类似于重分配partition之类的管理任务。在符合某些特定条件下，Controller下的LeaderSelector会选举新的leader，ISR和新的leader_epoch及controller_epoch写入Zookeeper的相关节点中。同时发起LeaderAndIsrRequest通知所有的replicas。\n\nleader来维护：leader有单独的线程定期检测ISR中follower是否脱离ISR, 如果发现ISR变化，则会将新的ISR的信息返回到Zookeeper的相关节点中。\n\n\n\n\n\n[回到顶部](https://www.cnblogs.com/wangzhuxing/p/10051512.html#_labelTop)\n\n## 九、数据可靠性和持久性保证\n\n当producer向leader发送数据时，可以通过request.required.acks参数来设置数据可靠性的级别：\n\n\n\n* 1(默认)：这意味着producer在ISR中的leader已成功收到的数据并得到确认后发送下一条message。如果leader宕机了，则会丢失数据。\n\n* 0：这意味着producer无需等待来自broker的确认而继续发送下一批消息。这种情况下数据传输效率最高，但是数据可靠性确是最低的。\n\n* -1：producer需要等待ISR中的所有follower都确认接收到数据后才算一次发送完成，可靠性最高。但是这样也不能保证数据不丢失，比如当ISR中只有leader时(前面ISR那一节讲到，ISR中的成员由于某些情况会增加也会减少，最少就只剩一个leader)，这样就变成了acks=1的情况。\n\n  [官网配置说明](http://kafka.apache.org/documentation/#configuration)\n\n如果要提高数据的可靠性，在设置request.required.acks=-1的同时，也要min.insync.replicas这个参数(可以在broker或者topic层面进行设置)的配合，这样才能发挥最大的功效。min.insync.replicas这个参数设定ISR中的最小副本数是多少，默认值为1，当且仅当request.required.acks参数设置为-1时，此参数才生效。如果ISR中的副本数少于min.insync.replicas配置的数量时，客户端会返回异常：org.apache.kafka.common.errors.NotEnoughReplicasExceptoin: Messages are rejected since there are fewer in-sync replicas than required。\n\n\n\n接下来对acks=1和-1的两种情况进行详细分析：\n\n\n\n\n\n### 9.1、request.required.acks=1\n\nproducer发送数据到leader，leader写本地日志成功，返回客户端成功;此时ISR中的副本还没有来得及拉取该消息，leader就宕机了，那么此次发送的消息就会丢失。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/843808-20181202181329621-1088596676.png)\n\n\n\n### 9.2、request.required.acks=-1\n\n同步(Kafka默认为同步，即producer.type=sync)的发送模式，replication.factor>=2且min.insync.replicas>=2的情况下，不会丢失数据。\n\n有两种典型情况。acks=-1的情况下(如无特殊说明，以下acks都表示为参数request.required.acks)，数据发送到leader, ISR的follower全部完成数据同步后，leader此时挂掉，那么会选举出新的leader，数据不会丢失。\n\n![](https://img2018.cnblogs.com/blog/843808/201812/843808-20181202212242480-242555451.png)\n\nacks=-1的情况下，数据发送到leader后 ，部分ISR的副本同步，leader此时挂掉。比如follower1h和follower2都有可能变成新的leader, producer端会得到返回异常，producer端会重新发送数据，数据可能会重复\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/843808-20181202212407453-662912091.png)\n\n当然上图中如果在leader crash的时候，follower2还没有同步到任何数据，而且follower2被选举为新的leader的话，这样消息就不会重复。\n\n注：Kafka只处理fail/recover问题,不处理Byzantine问题。\n\n\n\n\n\n### 9.3、关于HW的进一步探讨\n\n考虑上图(即acks=-1,部分ISR副本同步)中的另一种情况，如果在Leader挂掉的时候，follower1同步了消息4,5，follower2同步了消息4，与此同时follower2被选举为leader，那么此时follower1中的多出的消息5该做如何处理呢?\n\n这里就需要HW的协同配合了。如前所述，一个partition中的ISR列表中，leader的HW是所有ISR列表里副本中最小的那个的LEO。类似于木桶原理，水位取决于最低那块短板。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/843808-20181203204010599-1107873190.png)\n\n\n\n如上图，某个topic的某partition有三个副本，分别为A、B、C。A作为leader肯定是LEO最高，B紧随其后，C机器由于配置比较低，网络比较差，故而同步最慢。这个时候A机器宕机，这时候如果B成为leader，假如没有HW，在A重新恢复之后会做同步(makeFollower)操作，在宕机时log文件之后直接做追加操作，而假如B的LEO已经达到了A的LEO，会产生数据不一致的情况，所以使用HW来避免这种情况。\n\nA在做同步操作的时候，先将log文件截断到之前自己的HW的位置，即3，之后再从B中拉取消息进行同步。\n\n如果失败的follower恢复过来，它首先将自己的log文件截断到上次checkpointed时刻的HW的位置，之后再从leader中同步消息。leader挂掉会重新选举，新的leader会发送“指令”让其余的follower截断至自身的HW的位置然后再拉取新的消息。\n\n当ISR中的个副本的LEO不一致时，如果此时leader挂掉，选举新的leader时并不是按照LEO的高低进行选举，而是按照ISR中的顺序选举。\n\n\n\n\n\n### 9.4、Leader选举\n\n一条消息只有被ISR中的所有follower都从leader复制过去才会被认为已提交。这样就避免了部分数据被写进了leader，还没来得及被任何follower复制就宕机了，而造成数据丢失。而对于producer而言，它可以选择是否等待消息commit，这可以通过request.required.acks来设置。这种机制确保了只要ISR中有一个或者以上的follower，一条被commit的消息就不会丢失。\n\n有一个很重要的问题是当leader宕机了，怎样在follower中选举出新的leader，因为follower可能落后很多或者直接crash了，所以必须确保选择“最新”的follower作为新的leader。一个基本的原则就是，如果leader不在了，新的leader必须拥有原来的leader commit的所有消息。这就需要做一个折中，如果leader在表名一个消息被commit前等待更多的follower确认，那么在它挂掉之后就有更多的follower可以成为新的leader，但这也会造成吞吐率的下降。\n\n一种非常常用的选举leader的方式是“少数服从多数”，Kafka并不是采用这种方式。这种模式下，如果我们有2f+1个副本，那么在commit之前必须保证有f+1个replica复制完消息，同时为了保证能正确选举出新的leader，失败的副本数不能超过f个。这种方式有个很大的优势，系统的延迟取决于最快的几台机器，也就是说比如副本数为3，那么延迟就取决于最快的那个follower而不是最慢的那个。“少数服从多数”的方式也有一些劣势，为了保证leader选举的正常进行，它所能容忍的失败的follower数比较少，如果要容忍1个follower挂掉，那么至少要3个以上的副本，如果要容忍2个follower挂掉，必须要有5个以上的副本。也就是说，在生产环境下为了保证较高的容错率，必须要有大量的副本，而大量的副本又会在大数据量下导致性能的急剧下降。这种算法更多用在Zookeeper这种共享集群配置的系统中而很少在需要大量数据的系统中使用的原因。HDFS的HA功能也是基于“少数服从多数”的方式，但是其数据存储并不是采用这样的方式。\n\n实际上，leader选举的算法非常多，比如Zookeeper的Zab、Raft以及Viewstamped Replication。而Kafka所使用的leader选举算法更像是微软的PacificA算法。\n\nKafka在Zookeeper中为每一个partition动态的维护了一个ISR，这个ISR里的所有replication都跟上了leader，只有ISR里的成员才能有被选为leader的可能(unclean.leader.election.enable=false)。在这种模式下，对于f+1个副本，一个Kafka topic能在保证不丢失已经commit消息的前提下容忍f个副本的失败，在大多数使用场景下，这种模式是十分有利的。事实上，为了容忍f个副本的失败，“少数服从多数”的方式和ISR在commit前需要等待的副本的数量是一样的，但是ISR需要的总的副本的个数几乎是“少数服从多数”的方式的一半。\n\n上文提到，在ISR中至少有一个follower时，Kafka可以确保已经commit的数据不丢失，但如果某一个partition的所有replica都挂了，就无法保证数据不丢失了。这种情况下有两种可行的方案：\n\n等待ISR中任意一个replica“活”过来，并且选它作为leader\n\n选择第一个“活”过来的replica(并不一定是在ISR中)作为leader\n\n这就需要在可用性和一致性当中作出一个简单的抉择。如果一定要等待ISR中的replica“活”过来，那不可用的时间就可能会相对较长。而且如果ISR中所有的replica都无法“活”过来了，或者数据丢失了，这个partition将永远不可用。选择第一个“活”过来的replica作为leader,而这个replica不是ISR中的replica,那即使它并不保障已经包含了所有已commit的消息，它也会成为leader而作为consumer的数据源。默认情况下，Kafka采用第二种策略，即　　 \n\n\n\n*   unclean.leader.election.enable=true，也可以将此参数设置为false来启用第一种策略。\n*   unclean.leader.election.enable这个参数对于leader的选举、系统的可用性以及数据的可靠性都有至关重要的影响。\n\n下面我们来分析下几种典型的场景。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/843808-20181203213455180-1212737615.png)\n\n如果上图所示，假设某个partition中的副本数为3，replica-0, replica-1, replica-2分别存放在broker0, broker1和broker2中。AR=(0,1,2)，ISR=(0,1)。\n\n设置request.required.acks=-1, min.insync.replicas=2，unclean.leader.election.enable=false。这里讲broker0中的副本也称之为broker0起初broker0为leader，broker1为follower。\n\n当ISR中的replica-0出现crash的情况时，broker1选举为新的leader[ISR=(1)]，因为受min.insync.replicas=2影响，write不能服务，但是read能继续正常服务。此种情况恢复方案：\n\n尝试恢复(重启)replica-0，如果能起来，系统正常;\n如果replica-0不能恢复，需要将min.insync.replicas设置为1，恢复write功能。\n\n\n当ISR中的replica-0出现crash，紧接着replica-1也出现了crash, 此时[ISR=(1),leader=-1],不能对外提供服务，此种情况恢复方案：\n\n尝试恢复replica-0和replica-1，如果都能起来，则系统恢复正常;\n如果replica-0起来，而replica-1不能起来，这时候仍然不能选出leader，因为当设置unclean.leader.election.enable=false时，leader只能从ISR中选举，当ISR中所有副本都失效之后，需要ISR中最后失效的那个副本能恢复之后才能选举leader, 即replica-0先失效，replica-1后失效，需要replica-1恢复后才能选举leader。保守的方案建议把unclean.leader.election.enable设置为true,但是这样会有丢失数据的情况发生，这样可以恢复read服务。同样需要将min.insync.replicas设置为1，恢复write功能;replica-1恢复，replica-0不能恢复，这个情况上面遇到过，read服务可用，需要将min.insync.replicas设置为1，恢复write功能;\nreplica-0和replica-1都不能恢复，这种情况可以参考情形2.\n\n当ISR中的replica-0, replica-1同时宕机,此时[ISR=(0,1)],不能对外提供服务，此种情况恢复方案：尝试恢复replica-0和replica-1，当其中任意一个副本恢复正常时，对外可以提供read服务。直到2个副本恢复正常，write功能才能恢复，或者将将min.insync.replicas设置为1。\n\n\n\n\n\n### 9.5、Kafka的发送模式\n\nKafka的发送模式由producer端的配置参数producer.type来设置，这个参数指定了在后台线程中消息的发送方式是同步的还是异步的，默认是同步的方式，即producer.type=sync。如果设置成异步的模式，即producer.type=async，可以是producer以batch的形式push数据，这样会极大的提高broker的性能，但是这样会增加丢失数据的风险。如果需要确保消息的可靠性，必须要将producer.type设置为sync。\n\n对于异步模式，还有4个配套的参数，如下：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/843808-20181203213857717-291133501.png)\n\n以batch的方式推送数据可以极大的提高处理效率，kafka producer可以将消息在内存中累计到一定数量后作为一个batch发送请求。batch的数量大小可以通过producer的参数(batch.num.messages)控制。通过增加batch的大小，可以减少网络请求和磁盘IO的次数，当然具体参数设置需要在效率和时效性方面做一个权衡。在比较新的版本中还有batch.size这个参数。\n\n\n\n[回到顶部](https://www.cnblogs.com/wangzhuxing/p/10051512.html#_labelTop)\n\n## 十、高可靠性使用分析\n\n\n\n### 10.1、消息传输保障\n\n前面已经介绍了Kafka如何进行有效的存储，以及了解了producer和consumer如何工作。接下来讨论的是Kafka如何确保消息在producer和consumer之间传输。有以下三种可能的传输保障(delivery guarantee):\n\n\n\n*   At most once: 消息可能会丢，但绝不会重复传输\n*   At least once：消息绝不会丢，但可能会重复传输\n*   Exactly once：每条消息肯定会被传输一次且仅传输一次\n\nKafka的消息传输保障机制非常直观。当producer向broker发送消息时，一旦这条消息被commit，由于副本机制(replication)的存在，它就不会丢失。但是如果producer发送数据给broker后，遇到的网络问题而造成通信中断，那producer就无法判断该条消息是否已经提交(commit)。虽然Kafka无法确定网络故障期间发生了什么，但是producer可以retry多次，确保消息已经正确传输到broker中，所以目前Kafka实现的是at least once。\n\nconsumer从broker中读取消息后，可以选择commit，该操作会在Zookeeper中存下该consumer在该partition下读取的消息的offset。该consumer下一次再读该partition时会从下一条开始读取。如未commit，下一次读取的开始位置会跟上一次commit之后的开始位置相同。当然也可以将consumer设置为autocommit，即consumer一旦读取到数据立即自动commit。如果只讨论这一读取消息的过程，那Kafka是确保了exactly once, 但是如果由于前面producer与broker之间的某种原因导致消息的重复，那么这里就是at least once。\n\n考虑这样一种情况，当consumer读完消息之后先commit再处理消息，在这种模式下，如果consumer在commit后还没来得及处理消息就crash了，下次重新开始工作后就无法读到刚刚已提交而未处理的消息，这就对应于at most once了。\n\n读完消息先处理再commit。这种模式下，如果处理完了消息在commit之前consumer crash了，下次重新开始工作时还会处理刚刚未commit的消息，实际上该消息已经被处理过了，这就对应于at least once。\n\n要做到exactly once就需要引入消息去重机制。\n\n\n\n### 10.2、消息去重\n\n\n\n如上一节所述，Kafka在producer端和consumer端都会出现消息的重复，这就需要去重处理。\n\nKafka文档中提及GUID(Globally Unique Identifier)的概念，通过客户端生成算法得到每个消息的unique id，同时可映射至broker上存储的地址，即通过GUID便可查询提取消息内容，也便于发送方的幂等性保证，需要在broker上提供此去重处理模块，最新版本已经支持。\n\n针对GUID, 如果从客户端的角度去重，那么需要引入集中式缓存，必然会增加依赖复杂度，另外缓存的大小难以界定。\n\n不只是Kafka, 类似RabbitMQ以及RocketMQ这类商业级中间件也只保障at least once, 且也无法从自身去进行消息去重。所以我们建议业务方根据自身的业务特点进行去重，比如业务消息本身具备幂等性，或者借助Redis等其他产品进行去重处理。\n\n\n\n\n\n### 10.3、高可靠性配置\n\nKafka提供了很高的数据冗余弹性，对于需要数据高可靠性的场景，我们可以增加数据冗余备份数(replication.factor)，调高最小写入副本数的个数(min.insync.replicas)等等，但是这样会影响性能。反之，性能提高而可靠性则降低，用户需要自身业务特性在彼此之间做一些权衡性选择。\n\n要保证数据写入到Kafka是安全的，高可靠的，需要如下的配置：\n\n\n\n1.  topic的配置：replication.factor>=3,即副本数至少是3个;2<=min.insync.replicas<=replication.factor\n2.  broker的配置：leader的选举条件unclean.leader.election.enable=false\n3.  producer的配置：request.required.acks=-1(all)，producer.type=sync\n\n\n\n[回到顶部](https://www.cnblogs.com/wangzhuxing/p/10051512.html#_labelTop)\n\n## 十一、内部网络框架\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/843808-20181212101055804-450926848.png)\n\nBroker的内部处理流水线化，分为多个阶段来进行(SEDA)，以提高吞吐量和性能，尽量避免Thead盲等待，以下为过程说明。\n\n*   Accept Thread负责与客户端建立连接链路，然后把Socket轮转交给Process Thread\n*   Process Thread负责接收请求和响应数据，Process Thread每次基于Selector事件循环，首先从Response Queue读取响应数据，向客户端回复响应，然后接收到客户端请求后，读取数据放入Request Queue。\n*   Work Thread负责业务逻辑、IO磁盘处理等，负责从Request Queue读取请求，并把处理结果放入Response Queue中，待Process Thread发送出去。\n\n[回到顶部](https://www.cnblogs.com/wangzhuxing/p/10051512.html#_labelTop)\n\n## 十二、rebalance机制\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/843808-20181212101229091-1187958161.png)\n\nKafka保证同一consumer group中只有一个consumer会消费某条消息，实际上，Kafka保证的是稳定状态下每一个consumer实例只会消费某一个或多个特定的数据，而某个partition的数据只会被某一个特定的consumer实例所消费。这样设计的劣势是无法让同一个consumer group里的consumer均匀消费数据，优势是每个consumer不用都跟大量的broker通信，减少通信开销，同时也降低了分配难度，实现也更简单。另外，因为同一个partition里的数据是有序的，这种设计可以保证每个partition里的数据也是有序被消费。\n\n如果某consumer group中consumer数量少于partition数量，则至少有一个consumer会消费多个partition的数据，如果consumer的数量与partition数量相同，则正好一个consumer消费一个partition的数据，而如果consumer的数量多于partition的数量时，会有部分consumer无法消费该topic下任何一条消息。\n\n**Consumer Rebalance算法如下 ：**\n\n\n\n<pre>1. 将目标 topic 下的所有 partirtion 排序，存于PT 2. 对某 consumer group 下所有 consumer 排序，存于 CG，第 i 个consumer 记为 Ci 3. N=size(PT)/size(CG)，向上取整 4. 解除 Ci 对原来分配的 partition 的消费权（i从0开始） 5. 将第i*N到（i+1）*N-1个 partition 分配给 Ci　</pre>\n\n\n\n目前consumer rebalance的控制策略是由每一个consumer通过Zookeeper完成的。具体的控制方式如下：\n\n\n\n<pre>在/consumers/[consumer-group]/下注册id\n设置对/consumers/[consumer-group] 的watcher\n设置对／brokers/ids的watcher\nzk下设置watcher的路径节点更改，触发consumer rebalance</pre>\n\n\n\n\n在这种策略下，**每一个consumer或者broker的增加或者减少都会触发consumer rebalance**。因为每个consumer只负责调整自己所消费的partition，为了保证整个consumer group的一致性，所以当一个consumer触发了rebalance时，该consumer group内的其它所有consumer也应该同时触发rebalance。\n\n*   Herd effect\n\n任何broker或者consumer的增减都会触发所有的consumer的rebalance\n\n*   Split Brain\n\n每个consumer分别单独通过Zookeeper判断哪些partition down了，那么不同consumer从Zookeeper“看”到的view就可能不一样，这就会造成错误的reblance尝试。而且有可能所有的consumer都认为rebalance已经完成了，但实际上可能并非如此。\n\n[回到顶部](https://www.cnblogs.com/wangzhuxing/p/10051512.html#_labelTop)\n\n## 十三、BenchMark\n\nKafka在唯品会有着很深的历史渊源，根据唯品会消息中间件团队(VMS团队)所掌握的资料显示，在VMS团队运转的Kafka集群中所支撑的topic数已接近2000，每天的请求量也已达千亿级。这里就以Kafka的高可靠性为基准点来探究几种不同场景下的行为表现，以此来加深对Kafka的认知，为大家在以后高效的使用Kafka时提供一份依据。\n\n\n\n### 13.1、测试环境\n\nKafka broker用到了4台机器，分别为broker[0/1/2/3]配置如下：\n\nCPU: 24core/2.6GHZ\nMemory: 62G\nNetwork: 4000Mb\nOS/kernel: CentOs release 6.6 (Final)\nDisk: 1089G\nKafka版本：0.10.1.0\nbroker端JVM参数设置：\n\n\n\n\n\n<pre>-Xmx8G -Xms8G -server -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSClassUnloadingEnabled -XX:+CMSScavengeBeforeRemark -XX:+DisableExplicitGC -Djava.awt.headless=true -Xloggc:/apps/service/kafka/bin/../logs/kafkaServer-gc.log -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.port=9999 </pre>\n\n\n\n客户端机器配置：\n\n*   CPU: 24core/2.6GHZ\n*   Memory: 3G\n*   Network: 1000Mb\n*   OS/kernel: CentOs release 6.3 (Final)\n*   Disk: 240G\n\n### 13.2、不同场景测试\n\n#### 场景1：\n\n测试不同的副本数、min.insync.replicas策略以及request.required.acks策略(以下简称acks策略)对于发送速度(TPS)的影响。\n\n具体配置：一个producer;发送方式为sync;消息体大小为1kB;partition数为12。副本数为：1/2/4;min.insync.replicas分别为1/2/4;acks分别为-1(all)/1/0。\n\n具体测试数据如下表(min.insync.replicas只在acks=-1时有效)：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/843808-20181203221444373-736905445.png)\n\n测试结果分析：\n\n\n\n1.  客户端的acks策略对发送的TPS有较大的影响，TPS：acks_0 > acks_1 > ack_-1;\n2.  副本数越高，TPS越低;副本数一致时，min.insync.replicas不影响TPS;\n3.  acks=0/1时，TPS与min.insync.replicas参数以及副本数无关，仅受acks策略的影响。\n\n下面将partition的个数设置为1，来进一步确认下不同的acks策略、不同的min.insync.replicas策略以及不同的副本数对于发送速度的影响，详细请看情景2和情景3。\n\n#### 场景2：\n\n在partition个数固定为1，测试不同的副本数和min.insync.replicas策略对发送速度的影响。\n\n具体配置：一个producer;发送方式为sync;消息体大小为1kB;producer端acks=-1(all)。变换副本数：2/3/4; min.insync.replicas设置为：1/2/4。\n\n测试结果如下：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/843808-20181203221832812-2007554062.png)\n\n测试结果分析：\n\n副本数越高，TPS越低(这点与场景1的测试结论吻合)，但是当partition数为1时差距甚微。min.insync.replicas不影响TPS。\n\n#### 场景3：\n\n在partition个数固定为1，测试不同的acks策略和副本数对发送速度的影响。\n\n具体配置：一个producer;发送方式为sync;消息体大小为1kB;min.insync.replicas=1。topic副本数为：1/2/4;acks： 0/1/-1。\n\n测试结果如下：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/843808-20181203222003811-323209661.png)\n\n\n\n\n\n\n\n\n\n\n\n\n\n测试结果分析(与情景1一致)：\n\n*   副本数越多，TPS越低;\n*   客户端的acks策略对发送的TPS有较大的影响，TPS：acks_0 > acks_1 > ack_-1。\n\n#### 场景4：\n\n测试不同partition数对发送速率的影响\n\n具体配置：一个producer;消息体大小为1KB;发送方式为sync;topic副本数为2;min.insync.replicas=2;acks=-1。partition数量设置为1/2/4/8/12。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/843808-20181203222113272-1167910484.png)\n\n测试结果分析：\n\npartition的不同会影响TPS，随着partition的个数的增长TPS会有所增长，但并不是一直成正比关系，到达一定临界值时，partition数量的增加反而会使TPS略微降低。\n\n#### 场景5：\n\n通过将集群中部分broker设置成不可服务状态，测试对客户端以及消息落盘的影响。\n\n具体配置：一个producer;消息体大小1KB;发送方式为sync;topic副本数为4;min.insync.replicas设置为2;acks=-1;retries=0/100000000;partition数为12。\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/843808-20181203222220687-1891442968.png)\n\n测试结果分析：\n\n\n\n1.  kill两台broker后，客户端可以继续发送。broker减少后，partition的leader分布在剩余的两台broker上，造成了TPS的减小;\n2.  kill三台broker后，客户端无法继续发送。Kafka的自动重试功能开始起作用，当大于等于min.insync.replicas数量的broker恢复后，可以继续发送;\n3.  当retries不为0时，消息有重复落盘;客户端成功返回的消息都成功落盘，异常时部分消息可以落盘。\n\n#### 场景6：\n\n测试单个producer的发送延迟，以及端到端的延迟。\n\n具体配置：一个producer;消息体大小1KB;发送方式为sync;topic副本数为4;min.insync.replicas设置为2;acks=-1;partition数为12。\n\n测试数据及结果(单位为ms)：\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/843808-20181203222407063-2086989349.png)\n\n**各场景测试总结：**\n\n\n\n1.  当acks=-1时，Kafka发送端的TPS受限于topic的副本数量(ISR中)，副本越多TPS越低;\n2.  acks=0时，TPS最高，其次为1，最差为-1，即TPS：acks_0 > acks_1 > ack_-1\n3.  min.insync.replicas参数不影响TPS;\n4.  partition的不同会影响TPS，随着partition的个数的增长TPS会有所增长，但并不是一直成正比关系，到达一定临界值时，partition数量的增加反而会使TPS略微降低;\n5.  Kafka在acks=-1,min.insync.replicas>=1时，具有高可靠性，所有成功返回的消息都可以落盘。 "
  },
  {
    "path": "docs/mq/kafka/消息队列kafka详解：Kafka常见命令及配置总结.md",
    "content": "## **启动zookeeper**\n\nbin/zookeeper-server-start.sh config/zookeeper.properties &\n\n## **启动kafka：**\n\nbin/kafka-server-start.sh config/server.properties\n\n这样启动又一个坏处，就是kafka启动完毕之后，不能关闭终端，为此，我们可以运行这条命令：\n\nnohup bin/kafka-server-start.sh config/server.properties > ./dev/null 2>&1 &\n\n![](https://img2022.cnblogs.com/blog/796632/202208/796632-20220812161146385-332776455.png)\n\n多个kafka的话，在各个虚拟机上运行kafka启动命令多次即可。\n\n当然这个是单机的命令，集群的命令后面再讲。\n\n## **查看是否启动**\n\njps -lm\n\n![](https://img2022.cnblogs.com/blog/796632/202208/796632-20220812161210221-836644701.png)\n\n说明没有启动kfka\n\n![](https://img2022.cnblogs.com/blog/796632/202208/796632-20220812161224734-562363764.png)\n\n说明启动kafka了\n\n## 查看kafka版本\n\nfind ./libs/ -name \\*kafka_\\* | head -1 | grep -o '\\kafka[^\\n]*'\n\nkafka_2.12-2.4.1.jar\n\n结果:\n\n就可以看到kafka的具体版本了。\n\n其中，2.12为scala版本，2.4.1为kafka版本。\n\n## **停止kafka**\n\nbin/kafka-server-stop.sh\n\n## **停止zookeeper**\n\nbin/zookeeper-server-stop.sh\n\n## **创建topic**\n\nbin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic test\n\n多集群创建，执行这个需要搭建多机器的kafka集群环境，zkq1/zkq2/zkq3分别代表了3台zookeeper集群的三台机器\n\n/bin/kafka-topics.sh —create —zookeeper zkq1:2181,zkq2:2181,zkq3:2181 -replication-factor 6 —partition 6 —topic test\n\n解释：\n\n--topic后面的test0是topic的名称\n\n--zookeeper应该和server.properties文件中的zookeeper.connect一样\n\n--config指定当前topic上有效的参数值\n\n--partitions指定topic的partition数量，如果不指定该数量，默认是server.properties文件中的num.partitions配置值\n\n--replication-factor指定每个partition的副本个数，默认是1个\n\n也可以向没有的topic发送消息的时候创建topic\n\n需要\n\n开启自动创建配置：auto.create.topics.enable=true\n\n使用程序直接往kafka中相应的topic发送数据，如果topic不存在就会按默认配置进行创建。\n\n## **展示topic**\n\nbin/kafka-topics.sh --list --zookeeper localhost:2181\n\n## **描述topic**\n\nbin/kafka-topics.sh --describe --zookeeper localhost:2181 --topic my-replicated-topic\n\n![](https://img2022.cnblogs.com/blog/796632/202208/796632-20220812161250801-1389051022.png)\n\n解释：\n\n要查看多个topic用逗号分割\n\n**leader**:\n\n是该partitons所在的所有broker中担任leader的broker id，每个broker都有可能成为leader，负责处理消息的读和写，leader是从所有节点中随机选择的.\n\n-1表示此broker移除了\n\n**Replicas**:\n\n显示该partiton所有副本所在的broker列表，包括leader，不管该broker是否是存活，不管是否和leader保持了同步。列出了所有的副本节点，不管节点是否在服务中.\n\n**Isr**:\n\nin-sync replicas的简写，表示存活且副本都已同步的的broker集合，是replicas的子集，是正在服务中的节点.\n\n举例：\n\n比如上面结果的第一行：Topic: test0 .Partition:0 ...Leader: 0 ......Replicas: 0,2,1 Isr: 1,0,2\n\nPartition: 0[该partition编号是0]\n\nReplicas: 0,2,1[代表partition0 在broker0，broker1，broker2上保存了副本]\n\nIsr: 1,0,2 [代表broker0，broker1，broker2都存活而且目前都和leader保持同步]\n\nLeader: 0\n\n代表保存在broker0，broker1，broker2上的这三个副本中，leader是broker0\n\nleader负责读写，broker1、broker2负责从broker0同步信息，平时没他俩什么事\n\n## **查看topic的partition及增加partition**\n\n/kafka-topics.sh –zookeeper 10.2.1.1:2181 –topic mcc-logs –describe.\n\n## **删除Topic**\n\n/bin/kafka-topics.sh --zookeeper localhost:2181 --delete --topic test\n\n如果你的server.properties内没有配置相关的配置的话，会出现如下错误：\n\nTopic test is marked for deletion.\n\nNote: This will have no impact if delete.topic.enable is not set to true.\n\n这边是说，你的Topic已经被标记为待删除的Topic，但是呢，你配置文件的开关没有打开，所以只是给它添加了一个标记，实际上，这个Topic并没有被删除。只有，你打开开关之后，会自动删除被标记删除的Topic。\n\n解决办法：\n\n设置server.properties文件内的“delete.topic.enable=true”，并且重启Kafka就可以了。\n\n如果不想修改配置也可以完全删除\n\n1、删除kafka存储目录（server.propertiewenjian log.dirs配置，默认为“/tmp/kafka-logs”）下对应的topic。(不同broker下存储的topic不一定相同，所有broker都要看一下)\n\n2、进入zookeeper客户端删掉对应topic\n\nzkCli.sh .-server 127.0.0.1:42182\n\n找到topic目录:\n\nls ../brokers/topics\n\n删掉对应topic\n\nrmr ./brokers/topic/topic-name\n\n找到目录:\n\nls .../config/topics\n\n删掉对应topic\n\nrmr ./config/topics/topic-name .\n\n这样就完全删除了\n\n## **删除topic中存储的内容**\n\n在config/server.properties中找到如下的位置\n\n![](https://img2022.cnblogs.com/blog/796632/202208/796632-20220812161312458-550425542.png)\n\n删除log.dirs指定的文件目录，\n\n登录zookeeper client。\n\n命令：\n\n/home/ZooKeeper/bin/zkCli.sh\n\n删除zookeeper中该topic相关的目录\n\n命令：\n\nrm -r /kafka/config/topics/test0\n\nrm -r /kafka/brokers/topics/test0\n\nrm -r /kafka/admin/delete_topics/test0 （topic被标记为marked for deletion时需要这个命令）\n\n重启zookeeper和broker\n\n## **生产者发送消息：**\n\nbin/kafka-console-producer.sh --broker-list 130.51.23.95:9092 --topic my-replicated-topic\n\n这里的ip和端口是broker的ip及端口，根据自己kafka机器的ip和端口写就可以\n\n## **消费者消费消息：**\n\nbin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic test --new-consumer --from-beginning --consumer.config config/consumer.properties\n\n## **查看topic某分区偏移量最大（小）值**\n\nbin/kafka-run-class.sh kafka.tools.GetOffsetShell --topic hive-mdatabase-hostsltable .--time -1 --broker-list node86:9092 --partitions 0\n\n注： time为-1时表示最大值，time为-2时表示最小值\n\n不指定--partitions 就是指这个topic整体的情况\n\n## 查看指定group的消费情况\n\nkafka-consumer-groups.sh --bootstrap-server 172.20.72.93:9092 --describe --group mygroup\n\n运行结果：\n\n![](https://img2022.cnblogs.com/blog/796632/202208/796632-20220816164455794-344440282.png)\n\n*   GROUP:消费者组\n*   TOPIC：topic名字\n*   PARTITION ：partition id\n*   CURRENT-OFFSET： .当前消费到的offset . . . . . . . .\n*   LOG-END-OFFSETSIZE ：最新的offset\n*   LAG:未消费的条数\n*   CONSUMER-ID:消费者组中消费者的id,为—代表没有active的消费者\n*   HOST：消费者的机器ip，为—代表没有active的消费者\n*   CLIENT-ID:消费者clientID，为—代表没有active的消费者\n\n## .查看所有group的消费情况\n\nkafka-consumer-groups.sh --bootstrap-server 172.20.72.93:9092 --all-groups --all-topics --describe\n\n![](https://img2022.cnblogs.com/blog/796632/202208/796632-20220816172442100-1560497638.png)\n\n## 修改group消费的offset\n\nkafka-consumer-groups.sh --bootstrap-server 172.20.72.93:9092 --group mygroup --reset-offsets --topic mytopic --to-offset 61 --execute\n\n上面就是把mygroup在mytopic的消费offset修改到了61\n\n重设位移有几种选项:\n\n--to-earliest：. .设置到最早位移处，也就是0\n\n--to-latest：. . .设置到最新处，也就是主题分区HW的位置\n\n--to-offset NUM： 指定具体的位移位置\n\n--shift-by NUM：. 基于当前位移向前回退多少\n\n--by-duration：. .回退到多长时间\n\n## 查看指定group中活跃的消费者\n\nkafka-consumer-groups.sh --bootstrap-server 172.20.72.93:9092 --describe --group mygroup --members\n\n## **增加topic分区数**\n\n（只能增加不能减少）\n\n为topic t_cdr 增加10个分区\n\nbin/kafka-topics.sh --zookeeper node01:2181 .--alter --topic t_cdr --partitions 10\n\n## **常用配置及说明**\n\nkafka 常见重要配置说明，分为四部分\n\n*   Broker Config：kafka 服务端的配置\n*   Producer Config：生产端的配置\n*   Consumer Config：消费端的配置\n*   Kafka Connect Config：kafka 连接相关的配置\n\n### **Broker Config**\n\n1.  **zookeeper.connect**\n\n连接 zookeeper 集群的地址，用于将 kafka 集群相关的元数据信息存储到指定的 zookeeper 集群中\n\n**2\\. advertised.port**\n\n注册到 zookeeper 中的地址端口信息，在 IaaS 环境中，默认注册到 zookeeper 中的是内网地址，通过该配置指定外网访问的地址及端口，advertised.host.name 和 advertised.port 作用和 advertised.port 差不多，在 0.10.x 之后，直接配置 advertised.port 即可，前两个参数被废弃掉了。\n\n**3\\. auto.create.topics.enable**\n\n自动创建 topic，默认为 true。其作用是当向一个还没有创建的 topic 发送消息时，此时会自动创建一个 topic，并同时设置 -num.partition 1 (partition 个数) 和 default.replication.factor (副本个数，默认为 1) 参数。\n\n一般该参数需要手动关闭，因为自动创建会影响 topic 的管理，我们可以通过 kafka-topic.sh 脚本手动创建 topic，通常也是建议使用这种方式创建 topic。在 0.10.x 之后提供了 kafka-admin 包，可以使用其来创建 topic。\n\n**4\\. auto.leader.rebalance.enable**\n\n自动 rebalance，默认为 true。其作用是通过后台线程定期扫描检查，在一定条件下触发重新 leader 选举；在生产环境中，不建议开启，因为替换 leader 在性能上没有什么提升。\n\n**5\\. background.threads**\n\n后台线程数，默认为 10。用于后台操作的线程，可以不用改动。\n\n**6\\. broker.id**\n\nBroker 的唯一标识，用于区分不同的 Broker。kafka 的检查就是基于此 id 是否在 zookeeper 中/brokers/ids 目录下是否有相应的 id 目录来判定 Broker 是否健康。\n\n**7\\. compression.type**\n\n压缩类型。此配置可以接受的压缩类型有 gzip、snappy、lz4。另外可以不设置，即保持和生产端相同的压缩格式。\n\n**8\\. delete.topic.enable**\n\n启用删除 topic。如果关闭，则无法使用 admin 工具进行 topic 的删除操作。\n\n**9\\. leader.imbalance.check.interval.seconds**\n\npartition 检查重新 rebalance 的周期时间\n\n**10\\. leader.imbalance.per.broker.percentage**\n\n标识每个 Broker 失去平衡的比率，如果超过改比率，则执行重新选举 Broker 的 leader\n\n**11\\. log.dir / log.dirs**\n\n保存 kafka 日志数据的位置。如果 log.dirs 没有设置，则使用 log.dir 指定的目录进行日志数据存储。\n\n**12\\. log.flush.interval.messages**\n\npartition 分区的数据量达到指定大小时，对数据进行一次刷盘操作。比如设置了 1024k 大小，当 partition 积累的数据量达到这个数值时则将数据写入到磁盘上。\n\n**13\\. log.flush.interval.ms**\n\n数据写入磁盘时间间隔，即内存中的数据保留多久就持久化一次，如果没有设置，则使用 log.flush.scheduler.interval.ms 参数指定的值。\n\n**14\\. log.retention.bytes**\n\n表示 topic 的容量达到指定大小时，则对其数据进行清除操作，默认为-1，永远不删除。\n\n**15\\. log.retention.hours**\n\n标示 topic 的数据最长保留多久，单位是小时\n\n**16\\. log.retention.minutes**\n\n表示 topic 的数据最长保留多久，单位是分钟，如果没有设置该参数，则使用 log.retention.hours 参数\n\n**17\\. log.retention.ms**\n\n表示 topic 的数据最长保留多久，单位是毫秒，如果没有设置该参数，则使用 log.retention.minutes 参数\n\n**18\\. log.roll.hours**\n\n新的 segment 创建周期，单位小时。kafka 数据是以 segment 存储的，当周期时间到达时，就创建一个新的 segment 来存储数据。\n\n**19\\. log.segment.bytes**\n\nsegment 的大小。当 segment 大小达到指定值时，就新创建一个 segment。\n\n**20\\. message.max.bytes**\n\ntopic 能够接收的最大文件大小。需要注意的是 producer 和 consumer 端设置的大小需要一致。\n\n**21\\. min.insync.replicas**\n\n最小副本同步个数。当 producer 设置了 request.required.acks 为-1 时，则 topic 的副本数要同步至该参数指定的个数，如果达不到，则 producer 端会产生异常。\n\n**22\\. num.io.threads**\n\n指定 io 操作的线程数\n\n**23\\. num.network.threads**\n\n执行网络操作的线程数\n\n**24\\. num.recovery.threads.per.data.dir**\n\n每个数据目录用于恢复数据的线程数\n\n**25\\. num.replica.fetchers**\n\n从 leader 备份数据的线程数\n\n**26\\. offset.metadata.max.bytes**\n\n允许消费者端保存 offset 的最大个数\n\n**27\\. offsets.commit.timeout.ms**\n\noffset 提交的延迟时间\n\n**28\\. offsets.topic.replication.factor**\n\ntopic 的 offset 的备份数量。该参数建议设置更高保证系统更高的可用性\n\n**29\\. port**\n\n端口号，Broker 对外提供访问的端口号。\n\n**30\\. request.timeout.ms**\n\nBroker 接收到请求后的最长等待时间，如果超过设定时间，则会给客户端发送错误信息\n\n**31\\. zookeeper.connection.timeout.ms**\n\n客户端和 zookeeper 建立连接的超时时间，如果没有设置该参数，则使用 zookeeper.session.timeout.ms 值\n\n**32\\. connections.max.idle.ms**\n\n空连接的超时时间。即空闲连接超过该时间时则自动销毁连接。\n\n### **Producer Config**\n\n1.  **bootstrap.servers**\n\n服务端列表。即接收生产消息的服务端列表\n\n**2\\. key.serializer**\n\n消息键的序列化方式。指定 key 的序列化类型\n\n3..**value.serializer**\n\n消息内容的序列化方式。指定 value 的序列化类型\n\n4..**acks**\n\n消息写入 Partition 的个数。通常可以设置为 0，1，all；当设置为 0 时，只需要将消息发送完成后就完成消息发送功能；当设置为 1 时，即 Leader Partition 接收到数据并完成落盘；当设置为 all 时，即主从 Partition 都接收到数据并落盘。\n\n5..**buffer.memory**\n\n客户端缓存大小。即 Producer 生产的数据量缓存达到指定值时，将缓存数据一次发送的 Broker 上。\n\n6..**compression.type**\n\n压缩类型。指定消息发送前的压缩类型，通常有 none, gzip, snappy, or, lz4 四种。不指定时消息默认不压缩。\n\n7..**retries**\n\n消息发送失败时重试次数。当该值设置大于 0 时，消息因为网络异常等因素导致消息发送失败进行重新发送的次数。\n\n### **Consumer Config**\n\n1.  **bootstrap.servers**\n\n服务端列表。即消费端从指定的服务端列表中拉取消息进行消费。\n\n2..**key.deserializer**\n\n消息键的反序列化方式。指定 key 的反序列化类型，与序列化时指定的类型相对应。\n\n3..**value.deserializer**\n\n消息内容的反序列化方式。指定 value 的反序列化类型，与序列化时指定的类型相对应。\n\n4..**fetch.min.bytes**\n\n抓取消息的最小内容。指定每次向服务端拉取的最小消息量。\n\n5..**group.id**\n\n消费组中每个消费者的唯一表示。\n\n6..**heartbeat.interval.ms**\n\n心跳检查周期。即在周期性的向 group coordinator 发送心跳，当服务端发生 rebalance 时，会将消息发送给各个消费者。该参数值需要小于 session.timeout.ms，通常为后者的 1/3。\n\n7..**max.partition.fetch.bytes**\n\nPartition 每次返回的最大数据量大小。\n\n**8\\. session.timeout.ms**\n\nconsumer 失效的时间。即 consumer 在指定的时间后，还没有响应则认为该 consumer 已经发生故障了。\n\n**9\\. auto.offset.reset**\n\n当 kafka 中没有初始偏移量或服务器上不存在偏移量时，指定从哪个位置开始消息消息。earliest：指定从头开始；latest：从最新的数据开始消费。\n\n### **Kafka Connect Config**\n\n1.  **group.id**\n\n消费者在消费组中的唯一标识\n\n**2\\. internal.key.converter**\n\n内部 key 的转换类型。\n\n**3\\. internal.value.converter**\n\n内部 value 的转换类型。\n\n**4\\. key.converter**\n\n服务端接收到 key 时指定的转换类型。\n\n5..**value.converter**\n\n服务端接收到 value 时指定的转换类型。\n\n**6\\. bootstrap.servers**\n\n服务端列表。\n\n**7\\. heartbeat.interval.ms**\n\n心跳检测，与 consumer 中指定的配置含义相同。\n\n**8\\. session.timeout.ms**\n\nsession 有效时间，与 consumer 中指定的配置含义相同。\n\n## **总结**\n\n本文总结了平时经常用到的一些 Kafka 配置及命令说明，方便随时查看；喜欢的朋友可以收藏以备不时之需。\n\n\n## 参考文章\nhttps://blog.csdn.net/cao131502\nhttps://zhuanlan.zhihu.com/p/137811719"
  },
  {
    "path": "docs/mq/kafka/消息队列kafka详解：Kafka架构介绍.md",
    "content": "## 一. 工作流程\n\nKafka中消息是以topic进行分类的，Producer生产消息，Consumer消费消息，都是面向topic的。\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-b9d626794f6625526598db6627b780e7_720w.webp)\n\n</figure>\n\nTopic是逻辑上的改变，Partition是物理上的概念，每个Partition对应着一个log文件，该log文件中存储的就是producer生产的数据，topic=N*partition；partition=log\n\nProducer生产的数据会被不断的追加到该log文件的末端，且每条数据都有自己的offset，consumer组中的每个consumer，都会实时记录自己消费到了哪个offset，以便出错恢复的时候，可以从上次的位置继续消费。流程：Producer => Topic（Log with offset）=> Consumer.\n\n## 二. 文件存储\n\nKafka文件存储也是通过本地落盘的方式存储的，主要是通过相应的log与index等文件保存具体的消息文件。\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-116ebd7dffd85595d69f080e5b5f6948_720w.webp)\n\n</figure>\n\n生产者不断的向log文件追加消息文件，为了防止log文件过大导致定位效率低下，Kafka的log文件以1G为一个分界点，当.log文件大小超过1G的时候，此时会创建一个新的.log文件，同时为了快速定位大文件中消息位置，Kafka采取了分片和索引的机制来加速定位。\n\n在kafka的存储log的地方，即文件的地方，会存在消费的偏移量以及具体的分区信息，分区信息的话主要包括.index和.log文件组成\n\n<figure data-size=\"normal\">\n\n\n![](https://pic3.zhimg.com/80/v2-c6de61f43ecbe58d4f3e7aa29541220e_720w.webp)\n\n</figure>\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-8345e4966d8c5274a1e74e29151bd9c6_720w.webp)\n\n</figure>\n\n副本目的是为了备份，所以同一个分区存储在不同的broker上，即当third-2存在当前机器kafka01上，实际上再kafka03中也有这个分区的文件（副本），分区中包含副本，即一个分区可以设置多个副本，副本中有一个是leader，其余为follower。\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-6e8de9e7dcbdac0b7bd424eaaf4f8568_720w.webp)\n\n</figure>\n\n如果.log文件超出大小，则会产生新的.log文件。如下所示:\n\n\n\n```\n00000000000000000000.index\n00000000000000000000.log\n00000000000000170410.index\n00000000000000170410.log\n00000000000000239430.index\n00000000000000239430.log\n\n```\n\n\n\n**此时如何快速定位数据，步骤：**\n\n\n\n```\n.index文件存储的消息的offset+真实的起始偏移量。.log中存放的是真实的数据。\n\n```\n\n\n\n首先通过二分查找.index文件到查找到当前消息具体的偏移，如上图所示，查找为2，发现第二个文件为6，则定位到一个文件中。 然后通过第一个.index文件通过seek定位元素的位置3，定位到之后获取起始偏移量+当前文件大小=总的偏移量。 获取到总的偏移量之后，直接定位到.log文件即可快速获得当前消息大小。\n\n## 三. 生产者分区策略\n\n**分区的原因** 1\\. 方便在集群中扩展：每个partition通过调整以适应它所在的机器，而一个Topic又可以有多个partition组成，因此整个集群可以适应适合的数据。 2\\. 可以提高并发：以Partition为单位进行读写。类似于多路。\n\n**分区的原则** 1\\. 指明partition（这里的指明是指第几个分区）的情况下，直接将指明的值作为partition的值 2\\. 没有指明partition的情况下，但是存在值key，此时将key的hash值与topic的partition总数进行取余得到partition值 3\\. 值与partition均无的情况下，第一次调用时随机生成一个整数，后面每次调用在这个整数上自增，将这个值与topic可用的partition总数取余得到partition值，即round-robin算法。\n\n## 四. 生产者ISR\n\n为保证producer发送的数据能够可靠的发送到指定的topic中，topic的每个partition收到producer发送的数据后，都需要向producer发送ackacknowledgement，如果producer收到ack就会进行下一轮的发送，否则重新发送数据。\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-409ea1af4f66bd2f44398850cc2ba9e2_720w.webp)\n\n</figure>\n\n**发送ack的时机** 确保有follower与leader同步完成，leader再发送ack，这样可以保证在leader挂掉之后，follower中可以选出新的leader（主要是确保follower中数据不丢失）\n\n**follower同步完成多少才发送ack** 1\\. 半数以上的follower同步完成，即可发送ack 2\\. 全部的follower同步完成，才可以发送ack\n\n## 4.1 副本数据同步策略\n\n### 4.1.1 半数follower同步完成即发送ack\n\n优点是延迟低\n\n缺点是选举新的leader的时候，容忍n台节点的故障，需要2n+1个副本（因为需要半数同意，所以故障的时候，能够选举的前提是剩下的副本超过半数），容错率为1/2\n\n### 4.1.2 全部follower同步完成完成发送ack\n\n优点是容错率搞，选举新的leader的时候，容忍n台节点的故障只需要n+1个副本即可，因为只需要剩下的一个人同意即可发送ack了\n\n缺点是延迟高，因为需要全部副本同步完成才可\n\n### 4.1.3 kafka的选择\n\nkafka选择的是第二种，因为在容器率上面更加有优势，同时对于分区的数据而言，每个分区都有大量的数据，第一种方案会造成大量数据的冗余。虽然第二种网络延迟较高，但是网络延迟对于Kafka的影响较小。\n\n## 4.2 ISR(同步副本集)\n\n**猜想** 采用了第二种方案进行同步ack之后，如果leader收到数据，所有的follower开始同步数据，但有一个follower因为某种故障，迟迟不能够与leader进行同步，那么leader就要一直等待下去，直到它同步完成，才可以发送ack，此时需要如何解决这个问题呢？\n\n**解决** leader中维护了一个动态的ISR（in-sync replica set），即与leader保持同步的follower集合，当ISR中的follower完成数据的同步之后，给leader发送ack，如果follower长时间没有向leader同步数据，则该follower将从ISR中被踢出，该之间阈值由replica.lag.time.max.ms参数设定。当leader发生故障之后，会从ISR中选举出新的leader。\n\n## 五. 生产者ack机制\n\n对于某些不太重要的数据，对数据的可靠性要求不是很高，能够容忍数据的少量丢失，所以没有必要等到ISR中所有的follower全部接受成功。\n\nKafka为用户提供了三种可靠性级别，用户根据可靠性和延迟的要求进行权衡选择不同的配置。\n\n**ack参数配置** 0：producer不等待broker的ack，这一操作提供了最低的延迟，broker接收到还没有写入磁盘就已经返回，当broker故障时有可能丢失数据\n\n1：producer等待broker的ack，partition的leader落盘成功后返回ack，如果在follower同步成功之前leader故障，那么将丢失数据。（只是leader落盘）\n\n<figure data-size=\"normal\">\n\n\n![](https://pic1.zhimg.com/80/v2-a219d261edd97432347f4edf5794e170_720w.webp)\n\n</figure>\n\n-1(all)：producer等待broker的ack，partition的leader和ISR的follower全部落盘成功才返回ack，但是如果在follower同步完成后，broker发送ack之前，如果leader发生故障，会造成数据重复。(这里的数据重复是因为没有收到，所以继续重发导致的数据重复)\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-c9741a10f418f7ea4eed929f0f266bbb_720w.webp)\n\n</figure>\n\nproducer返ack，0无落盘直接返，1只leader落盘然后返，-1全部落盘然后返\n\n## 六. 数据一致性问题\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-031d84a2012f64b122dd64ab67a4e52a_720w.webp)\n\n</figure>\n\nLEO(Log End Offset)：每个副本最后的一个offset HW(High Watermark)：高水位，指代消费者能见到的最大的offset，ISR队列中最小的LEO。\n\n**follower故障和leader故障** 1\\. follower故障：follower发生故障后会被临时踢出ISR，等待该follower恢复后，follower会读取本地磁盘记录的上次的HW，并将log文件高于HW的部分截取掉，从HW开始向leader进行同步，等待该follower的LEO大于等于该partition的HW，即follower追上leader之后，就可以重新加入ISR了。 2\\. leader故障：leader发生故障之后，会从ISR中选出一个新的leader，为了保证多个副本之间的数据的一致性，其余的follower会先将各自的log文件高于HW的部分截掉，然后从新的leader中同步数据。\n\n**这只能保证副本之间的数据一致性，并不能保证数据不丢失或者不重复**\n\n## 七. ExactlyOnce\n\n将服务器的ACK级别设置为-1（all），可以保证producer到Server之间不会丢失数据，即At Least Once至少一次语义。将服务器ACK级别设置为0，可以保证生产者每条消息只会被发送一次，即At Most Once至多一次。\n\nAt Least Once可以保证数据不丢失，但是不能保证数据不重复，而At Most Once可以保证数据不重复，但是不能保证数据不丢失，对于重要的数据，则要求数据不重复也不丢失，即Exactly Once即精确的一次。\n\n在0.11版本的Kafka之前，只能保证数据不丢失，在下游对数据的重复进行去重操作，多余多个下游应用的情况，则分别进行全局去重，对性能有很大影响。\n\n0.11版本的kafka，引入了一项重大特性：幂等性，幂等性指代Producer不论向Server发送了多少次重复数据，Server端都只会持久化一条数据。幂等性结合At Least Once语义就构成了Kafka的Exactly Once语义。\n\n启用幂等性，即在Producer的参数中设置enable.idempotence=true即可，Kafka的幂等性实现实际是将之前的去重操作放在了数据上游来做，开启幂等性的Producer在初始化的时候会被分配一个PID，发往同一个Partition的消息会附带Sequence Number，而Broker端会对做缓存，当具有相同主键的消息的时候，Broker只会持久化一条。\n\n但PID在重启之后会发生变化，同时不同的Partition也具有不同的主键，所以幂等性无法保证跨分区跨会话的Exactly Once。\n\n要解决跨分区跨会话的Exactly Once，就引入了生产者事务的概念。\n\n## 八. Kafka消费者分区分配策略\n\n**消费方式:** consumer采用pull拉的方式来从broker中读取数据。\n\npush推的模式很难适应消费速率不同的消费者，因为消息发送率是由broker决定的，它的目标是尽可能以最快的速度传递消息，但是这样容易造成consumer来不及处理消息，典型的表现就是拒绝服务以及网络拥塞。而pull方式则可以让consumer根据自己的消费处理能力以适当的速度消费消息。\n\npull模式不足在于如果Kafka中没有数据，消费者可能会陷入循环之中 (因为消费者类似监听状态获取数据消费的)，一直返回空数据，针对这一点，Kafka的消费者在消费数据时会传入一个时长参数timeout，如果当前没有数据可供消费，consumer会等待一段时间之后再返回，时长为timeout。\n\n## 8.1\\. 分区分配策略\n\n一个consumer group中有多个consumer，一个topic有多个partition，所以必然会涉及到partition的分配问题，即确定那个partition由那个consumer消费的问题。\n\n**Kafka的两种分配策略：** 1\\. round-robin循环 2\\. range\n\n**Round-Robin** 主要采用轮询的方式分配所有的分区，该策略主要实现的步骤： 假设存在三个topic：t0/t1/t2，分别拥有1/2/3个分区，共有6个分区，分别为t0-0/t1-0/t1-1/t2-0/t2-1/t2-2，这里假设我们有三个Consumer，C0、C1、C2，订阅情况为C0：t0，C1：t0、t1，C2：t0/t1/t2。\n\n此时round-robin采取的分配方式，则是按照分区的字典对分区和消费者进行排序，然后对分区进行循环遍历，遇到自己订阅的则消费，否则向下轮询下一个消费者。即按照分区轮询消费者，继而消息被消费。\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-21eed325191d7d72c9d4c39455c4cae5_720w.webp)\n\n</figure>\n\n分区在循环遍历消费者，自己被当前消费者订阅，则消息与消费者共同向下（消息被消费），否则消费者向下消息继续遍历（消息没有被消费）。轮询的方式会导致每个Consumer所承载的分区数量不一致，从而导致各个Consumer压力不均。上面的C2因为订阅的比较多，导致承受的压力也相对较大。\n\n**Range** Range的重分配策略，首先计算各个Consumer将会承载的分区数量，然后将指定数量的分区分配给该Consumer。假设存在两个Consumer，C0和C1，两个Topic，t0和t1，这两个Topic分别都有三个分区，那么总共的分区有6个，t0-0，t0-1，t0-2，t1-0，t1-1，t1-2。分配方式如下：\n\nrange按照topic一次进行分配，即消费者遍历topic，t0，含有三个分区，同时有两个订阅了该topic的消费者，将这些分区和消费者按照字典序排列。 按照平均分配的方式计算每个Consumer会得到多少个分区，如果没有除尽，多出来的分区则按照字典序挨个分配给消费者。按照此方式以此分配每一个topic给订阅的消费者，最后完成topic分区的分配。\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-d642ed5512a4abdca9a8f35f2d27c277_720w.webp)\n\n</figure>\n\n## 8.2\\. 消费者offset的存储\n\n由于Consumer在消费过程中可能会出现断电宕机等故障，Consumer恢复以后，需要从故障前的位置继续消费，所以Consumer需要实时记录自己消费到了那个offset，以便故障恢复后继续消费。\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-f2a50fd7f054821e36a80b1f6d99ecb0_720w.webp)\n\n</figure>\n\nKafka0.9版本之前，consumer默认将offset保存在zookeeper中，从0.9版本之后，consumer默认将offset保存在kafka一个内置的topic中，该topic为__consumer_offsets\n\n## 九. 高效读写&Zookeeper作用\n\n## 9.1 高效读写\n\n**顺序写磁盘** Kafka的producer生产数据，需要写入到log文件中，写的过程是追加到文件末端，顺序写的方式，官网有数据表明，同样的磁盘，顺序写能够到600M/s，而随机写只有200K/s，这与磁盘的机械结构有关，顺序写之所以快，是因为其省去了大量磁头寻址的时间。\n\n**零复制技术**\n\nNIC：Network Interface Controller网络接口控制器\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-2807d010381d304949bf8cea16ba1744_720w.webp)\n\n</figure>\n\n这是常规的读取操作： 1\\. 操作系统将数据从磁盘文件中读取到内核空间的页面缓存 2\\. 应用程序将数据从内核空间读入到用户空间缓冲区 3\\. 应用程序将读到的数据写回内核空间并放入到socket缓冲区 4\\. 操作系统将数据从socket缓冲区复制到网卡接口，此时数据通过网络发送给消费者\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-390cbabdeaba6f9e79a8ba6f4d08d75f_720w.webp)\n\n</figure>\n\n零拷贝技术只用将磁盘文件的数据复制到页面缓存中一次，然后将数据从页面缓存直接发送到网络中（发送给不同的订阅者时，都可以使用同一个页面缓存），从而避免了重复复制的操作。\n\n如果有10个消费者，传统方式下，数据复制次数为4*10=40次，而使用“零拷贝技术”只需要1+10=11次，一次为从磁盘复制到页面缓存，10次表示10个消费者各自读取一次页面缓存。\n\n## 9.2 zookeeper作用\n\nKafka集群中有一个broker会被选举为Controller，负责管理集群broker的上下线、所有topic的分区副本分配和leader的选举等工作。Controller的工作管理是依赖于zookeeper的。\n\n**Partition的Leader的选举过程**\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-fc64ea72cba32e702b15344767bdace9_720w.webp)\n\n</figure>\n\n## 十. 事务\n\nkafka从0.11版本开始引入了事务支持，事务可以保证Kafka在Exactly Once语义的基础上，生产和消费可以跨分区的会话，要么全部成功，要么全部失败。\n\n## 10.1 Producer事务\n\n为了按跨分区跨会话的事务，需要引入一个全局唯一的Transaction ID，并将Producer获得的PID(可以理解为Producer ID)和Transaction ID进行绑定，这样当Producer重启之后就可以通过正在进行的Transaction ID获得原来的PID。\n\n为了管理Transaction，Kafka引入了一个新的组件Transaction Coordinator，Producer就是通过有和Transaction Coordinator交互获得Transaction ID对应的任务状态，Transaction Coordinator还负责将事务信息写入内部的一个Topic中，这样即使整个服务重启，由于事务状态得到保存，进行中的事务状态可以恢复，从而继续进行。\n\n## 10.2 Consumer事务\n\n对于Consumer而言，事务的保证相比Producer相对较弱，尤其是无法保证Commit的信息被精确消费，这是由于Consumer可以通过offset访问任意信息，而且不同的Segment File声明周期不同，同一事务的消息可能会出现重启后被删除的情况。\n\n## 参考文章\n\nhttps://blog.csdn.net/cao131502\nhttps://zhuanlan.zhihu.com/p/137811719"
  },
  {
    "path": "docs/mq/kafka/消息队列kafka详解：Kafka的集群工作原理.md",
    "content": "**前言**\n\n上篇文章讲到了消息在 Partition 上的存储形式，本来准备接着来聊聊生产中的一些使用方式，想了想还有些很重要的工作组件原理没有讲清楚，比如一个 Topic 由 N 个 Partition 组成，那么这些 Partition 是如何均匀的分布在不同的 Broker 上？再比如当一个 Broker 宕机后，其上负责读写请求的主 Partition 无法正常访问，如何让从 Partition 转变成主 Partition 来继续提供正常的读写服务？想要解决这些问题，就必须先要了解一下 Kafka 集群内部的管理机制，其中一个非常重要的控制器就是 KafkaController。本文我们就来讲讲 KafkaController 是如何来解决上面提到的那些问题的。\n\n### **文章概览**\n\n1.  KafkaController 是什么及其选举策略。\n2.  KafkaController 监控 ZK 的目录分布。\n3.  Partition 分布算法。\n4.  Partition 的状态转移。\n5.  Kafka 集群的负载均衡处理流程解析。\n\n## **KafkaController 是什么及其选举策略**\n\nKafka 集群由多台 Broker 构成，每台 Broker 都有一个 KafkaController 用于管理当前的 Broker。试想一下，如果一个集群没有一个“领导者”，那么谁去和“外界”（比如 ZK）沟通呢？谁去协调 Partition 应该如何分布在集群中的不同 Broker 上呢？谁去处理 Broker 宕机后，在其 Broker 上的主 Partition 无法正常提供读写服务后，将对应的从 Partition 转变成主 Partition 继续正常对外提供服务呢？那么由哪个 Broker 的 KafkaController 来担当“领导者”这个角色呢？\n\nKafka 的设计者很聪明，Zookeeper 既然是分布式应用协调服务，那么干脆就让它来帮 Kafka 集群选举一个“领导者”出来，这个“领导者”对应的 KafkaController 称为 Leader，其他的 KafkaController 被称为 Follower，在同一时刻，一个 Kafka 集群只能有一个 Leader 和 N 个 Follower。\n\n### **Zookeeper 是怎么实现 KafkaController 的选主工作呢？**\n\n稍微熟悉 Zookeeper 的小伙伴应该都比较清楚，Zookeeper 是通过监控目录（zNode）的变化，从而做出一些相应的动作。\n\nZookeeper 的目录分为四种，第一种是永久的，被称作为 `Persistent`；\n\n第二种是顺序且永久的，被称作为 `Persistent_Sequential`；\n\n第三种是临时的，被称为 `Ephemeral`；\n\n第四种是顺序且临时的，被称作为 `Ephemeral_Sequential`。\n\nKafkaController 正是利用了临时的这一特性来完成选主的，在 Broker 启动时，每个 Broker 的 KafkaController 都会向 ZK 的 `/controller` 目录写入 BrokerId，谁先写入谁就是 Leader，剩余的 KafkaController 是 Follower，当 Leader 在周期内没有向 ZK 发送报告的话，则认为 Leader 挂了，此时 ZK 删除临时的 `/controller` 目录，Kafka 集群中的其他 KafkaController 开始新的一轮争主操作，流程和上面一样。下面是选 Leader 的流程图。\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-af1f22f109f85fe6b169c6e4a271016f_720w.webp)\n\n<figcaption>Leader选举流程图</figcaption>\n\n</figure>\n\n从上图可以看出，第一次，Broker1 成功抢先在 Zookeeper 的 /controller 目录上写入信息，所以 Broker1 的 KafkaController 为 Leader，其他两个为 Follower。第二次，Broker1 宕机或者下线，此时 Broker2 和 Broker3 检测到，于是开始新一轮的争抢将信息写入 Zookeeper，从图中可以看出，Broker2 争到了 Leader，所以 Broker3 是 Follower 状态。\n\n正常情况下，上面这个流程没有问题，但是如果在 Broker1 离线的情况下，Zookeeper 准备删除 /controller 的临时 node 时，系统 hang 住没办法删除，改怎么办呢？这里留个小疑问供大家思考。后面会用一篇文章专门来解答 Kafka 相关的问题（包括面试题哦，敬请期待）。\n\n## **KafkaController 监控的 ZK 目录分布**\n\nKafkaController 在初始化的时候，会针对不同的 zNode 注册各种各样的监听器，以便处理不同的用户请求或者系统内部变化请求。监控 ZK 的目录大概可以分为两部分，分别是 `/admin` 目录和 `/brokers` 目录。各目录及其对应的功能如下表所示，需要的朋友自提。\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-2a603adc2e06f3663e693259e8bf16d4_720w.webp)\n\n</figure>\n\n## **Partition 分布算法**\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-36d40cb264f6432a81ad83c9365d7997_720w.webp)\n\n<figcaption>Partition分布算法图</figcaption>\n\n</figure>\n\n图解：假设集群有 3 个 Broker，Partition 因子为 2。\n\n1.  随机选取 Broker 集群中的一个 Broker 节点，然后以轮询的方式将主 Partition 均匀的分布到不同的 Broker 上。\n2.  主 Partition 分布完成后，将从 Partition 按照 AR 组内顺序以轮询的方式将 Partition 均匀的分布到不同的 Broker 上。\n\n## **Partition 的状态转移**\n\n用户针对特定的 Topic 创建了相应的 Partition ，但是这些 Partition 不一定时刻都能够正常工作，所有 Partition 在同一时刻会对应 4 个状态中的某一个；其整个生命周期会经历如下状态的转移，分别是 NonExistentPartition、NewPartition、OnlinePartition、OfflinePartition，其对应的状态转移情况如下图所示。\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-58a8609aa2698130679d9fb80541d19b_720w.webp)\n\n<figcaption>Partition状态转移图</figcaption>\n\n</figure>\n\n从上图可以看出，Partition 的状态会由前置状态才能够转移到目标状态的，而不是胡乱转移状态的。\n\n`NonExistentPartition：`代表没有创建 Partition 时的状态，也有可能是离线的 Partition 被删除后的状态。\n\n`NewPartition：`当 Partition 被创建时，此时只有 AR（Assigned Replica），还没有 ISR（In-Synic Replica），此时还不能接受数据的写入和读取。\n\n`OnlinePartition：`由 NewPartition 状态转移为 OnlinePartition 状态，此时 Partition 的 Leader 已经被选举出来了，并且也有对应的 ISR 列表等。此时已经可以对外提供读写请求了。\n\n`OfflinePartition：`当 Partition 对应的 Broker 宕机或者网络异常等问题，由 OnlinePartition 转移到 OfflinePartition，此时的 Partition 已经不能在对外提供读写服务。当 Partition 被彻底删除后状态就转移成 NonExistentPartition，当网络恢复或者 Broker 恢复后，其状态又可以转移到 OnlinePartition，从而继续对外提供读写服务。\n\n## **Kafka 集群的负载均衡处理流程解析**\n\n前面的文章讲到过，Partition 有 Leader Replica 和 Preferred Replica 两种角色，Leader Replica 负责对外提供读写服务 Preferred Replica 负责同步 Leader Replica 上的数据。现在集群中假设有 3 个 Broker，3 个 Partition，每个 Partition 有 3 个 Replica，当集群运行一段时候后，集群中某些 Broker 宕机，Leader Replica 进行转移，其过程如下图所示。\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-dc0bcd6f072f7e6cef8289882259d59e_720w.webp)\n\n<figcaption>Partition的Leader转移图</figcaption>\n\n</figure>\n\n从上图可以看出，集群运行一段时间后，Broker1 挂掉了，在其上运行的 Partition0 对应的 Leader Replica 转移到了 Broker2 上。假设一段时间后 Broker3 也挂了，则 Broker3 上的 Partition3 对应的 Leader Replica 也转移到了 Broker2 上，集群中只有 Broker2 上的 Partition 在对外提供读写服务，从而造成 Broker2 上的服务压力比较大，之后 Broker1 和 Broker3 恢复后，其上只有 Preferred Replica 做备份操作。\n\n针对以上这种随着时间的推移，集群不在像刚开始时那样平衡，需要通过后台线程将 Leader Replica 重新分配到不同 Broker 上，从而使得读写服务尽量均匀的分布在不同的节点上。\n\n重平衡操作是由 partition-rebalance-thread 后台线程操作的，由于其优先级很低，所以只会在集群空闲的时候才会执行。集群的不平衡的评判标准是由`leader.imbalance.per.broker.percentage`配置决定的，当集群的不平衡度达到 10%（默认）时，会触发后台线程启动重平衡操作，其具体执行步骤如下：\n\n1.  对 Partition 的 AR 列表根据 Preferred Replica 进行分组操作。\n2.  遍历 Broker，对其上的 Partition 进行处理。\n3.  统计 Broker 上的 Leader Replica 和 Preferred Replica 不相等的 Partition 个数。\n4.  统计 Broker 上的 Partition 个数。\n5.  Partition 个数 / 不相等的 Partition 个数，如果大于 10%，则触发重平衡操作；反之，则不做任何处理。\n\n## **总结**\n\n本文主要介绍了 Kafka 集群服务内部的一些工作机制，相信小伙伴们掌握了这部分内容后，对 Broker 服务端的工作流程有了进一步的理解，从而更好的把控整体集群服务。下篇文章我们来正式介绍一下**Kafka 常用的命令行操作**，敬请期待。\n\n# 参考文章\nhttps://blog.csdn.net/cao131502\nhttps://zhuanlan.zhihu.com/p/137811719"
  },
  {
    "path": "docs/mq/kafka/消息队列kafka详解：Kafka重要知识点+面试题大全.md",
    "content": "## 重要面试知识点\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-75cd70ad3052ba44bf706a3ab39e59d5_720w.webp)\n\n\nKafka 消费端确保一个 Partition 在一个消费者组内只能被一个消费者消费。这句话改怎么理解呢？\n\n1.  在同一个消费者组内，一个 Partition 只能被一个消费者消费。\n2.  在同一个消费者组内，所有消费者组合起来必定可以消费一个 Topic 下的所有 Partition。\n3.  在同一个消费组内，一个消费者可以消费多个 Partition 的信息。\n4.  在不同消费者组内，同一个分区可以被多个消费者消费。\n5.  每个消费者组一定会完整消费一个 Topic 下的所有 Partition。\n\n### **消费组存在的意义**\n\n了解了消费者与消费组的关系后，有朋友会比较疑惑消费者组有啥实际存在的意义呢？或者说消费组的作用是什么？\n\n作者对消费组的作用归结了如下两点。\n\n1.  在实际生产中，对于同一个 Topic，可能有 A、B、C 等 N 个消费方想要消费。比如一份用户点击日志，A 消费方想用来做一个用户近 N 天点击过哪些商品；B 消费方想用来做一个用户近 N 天点击过前 TopN 个相似的商品；C 消费方想用来做一个根据用户点击过的商品推荐相关周边的商品需求。对于多应用场景，就可以使用消费组来隔离不同的业务使用场景，从而达到一个 Topic 可以被多个消费组重复消费的目的。\n2.  消费组与 Partition 的消费进度绑定。当有新的消费者加入或者有消费者从消费组退出时，会触发消费组的 Repartition 操作（后面会详细介绍 Repartition）；在 Repartition 前，Partition1 被消费组的消费者 A 进行消费，Repartition 后，Partition1 消费组的消费者 B 进行消费，为了避免消息被重复消费，需要从消费组记录的 Partition 消费进度读取当前消费到的位置（即 OffSet 位置），然后在继续消费，从而达到消费者的平滑迁移，同时也提高了系统的可用性。\n\n## **Repartition 触发时机**\n\n使用过 Kafka 消费者客户端的同学肯定知道，消费者组内偶尔会触发 Repartition 操作，所谓 Repartition 即 Partition 在某些情况下重新被分配给参与消费的消费者。基本可以分为如下几种情况。\n\n1.  消费组内某消费者宕机，触发 Repartition 操作，如下图所示。\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-a9ef6a29cb9ba3456a05ad75cb91cb03_720w.webp)\n\n<figcaption>消费者宕机情况</figcaption>\n\n</figure>\n\n2\\. 消费组内新增消费者，触发 Repartition 操作，如下图所示。一般这种情况是为了提高消费端的消费能力，从而加快消费进度。\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-8803223d712fdde035b8e7b9170dd3fb_720w.webp)\n\n<figcaption>新增消费者情况</figcaption>\n\n</figure>\n\n3.Topic 下的 Partition 增多，触发 Repartition 操作，如下图所示。一般这种调整 Partition 个数的情况也是为了提高消费端消费速度的，因为当消费者个数大于等于 Partition 个数时，在增加消费者个数是没有用的（原因是：在一个消费组内，消费者:Partition = 1:N，当 N 小于 1 时，相当于消费者过剩了），所以一方面增加 Partition 个数同时增加消费者个数可以提高消费端的消费速度。\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-8f1a427c6842d9babf139454ce23cfa3_720w.webp)\n\n<figcaption>新增Partition个数情况</figcaption>\n\n</figure>\n\n## **消费者与 ZK 的关系**\n\n众所周知，ZK 不仅保存了消费者消费 partition 的进度，同时也保存了消费组的成员列表、partition 的所有者。消费者想要消费 Partition，需要从 ZK 中获取该消费者对应的分区信息及当前分区对应的消费进度，即 OffSert 信息。那么 Partition 应该由那个消费者进行消费，决定因素有哪些呢？从之前的图中不难得出，两个重要因素分别是：消费组中存活的消费者列表和 Topic 对应的 Partition 列表。通过这两个因素结合 Partition 分配算法，即可得出消费者与 Partition 的对应关系，然后将信息存储到 ZK 中。Kafka 有高级 API 和低级 API，如果不需要操作 OffSet 偏移量的提交，可通过高级 API 直接使用，从而降低使用者的难度。对于一些比较特殊的使用场景，比如想要消费特定 Partition 的信息，Kafka 也提供了低级 API 可进行手动操作。\n\n## **消费端工作流程**\n\n在介绍消费端工作流程前，先来熟悉一下用到的一些组件。\n\n*   `KakfaConsumer`：消费端，用于启动消费者进程来消费消息。\n*   `ConsumerConfig`：消费端配置管理，用于给消费端配置相关参数，比如指定 Kafka 集群，设置自动提交和自动提交时间间隔等等参数，都由其来管理。\n*   `ConsumerConnector`：消费者连接器，通过消费者连接器可以获得 Kafka 消息流，然后通过消息流就能获得消息从而使得客户端开始消费消息。\n\n以上三者之间的关系可以概括为：消费端使用消费者配置管理创建出了消费者连接器，通过消费者连接器创建队列（这个队列的作用也是为了缓存数据），其中队列中的消息由专门的拉取线程从服务端拉取然后写入，最后由消费者客户端轮询队列中的消息进行消费。具体操作流程如下图所示。\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-122b4a706de39655d257928005a83ff1_720w.webp)\n\n<figcaption>消费端工作流程</figcaption>\n\n</figure>\n\n我们在从消费者与 ZK 的角度来看看其工作流程是什么样的？\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-4ed25ebb9236986b2084ce8a042f65b9_720w.webp)\n\n<figcaption>消费端与ZK之间的工作流程</figcaption>\n\n</figure>\n\n从上图可以看出，首先拉取线程每拉取一次消息，同步更新一次拉取状态，其作用是为了下一次拉取消息时能够拉取到最新产生的消息；拉取线程将拉取到的消息写入到队列中等待消费消费线程去真正读取处理。消费线程以轮询的方式持续读取队列中的消息，只要发现队列中有消息就开始消费，消费完消息后更新消费进度，此处需要注意的是，消费线程不是每次都和 ZK 同步消费进度，而是将消费进度暂时写入本地。这样做的目的是为了减少消费者与 ZK 的频繁同步消息，从而降低 ZK 的压力。\n\n## **消费者的三种消费情况**\n\n消费者从服务端的 Partition 上拉取到消息，消费消息有三种情况，分别如下：\n\n1.  至少一次。即一条消息至少被消费一次，消息不可能丢失，但是可能会被重复消费。\n2.  至多一次。即一条消息最多可以被消费一次，消息不可能被重复消费，但是消息有可能丢失。\n3.  正好一次。即一条消息正好被消费一次，消息不可能丢失也不可能被重复消费。\n\n### **1.至少一次**\n\n消费者读取消息，先处理消息，在保存消费进度。消费者拉取到消息，先消费消息，然后在保存偏移量，当消费者消费消息后还没来得及保存偏移量，则会造成消息被重复消费。如下图所示：\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-1a047ed616ba44daebdb4b6ce786a61a_720w.webp)\n\n<figcaption>先消费后保存消费进度</figcaption>\n\n</figure>\n\n### **2.至多一次**\n\n消费者读取消息，先保存消费进度，在处理消息。消费者拉取到消息，先保存了偏移量，当保存了偏移量后还没消费完消息，消费者挂了，则会造成未消费的消息丢失。如下图所示：\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-1f9f91ae54396c5e5d93ae89251eb1ed_720w.webp)\n\n<figcaption>先保存消费进度后消费消息</figcaption>\n\n</figure>\n\n### **3.正好一次**\n\n正好消费一次的办法可以通过将消费者的消费进度和消息处理结果保存在一起。只要能保证两个操作是一个原子操作，就能达到正好消费一次的目的。通常可以将两个操作保存在一起，比如 HDFS 中。正好消费一次流程如下图所示。\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-a0bbb114e2ad551227f81c1f26d4bd5d_720w.webp)\n\n<figcaption>正好消费一次</figcaption>\n\n</figure>\n\n## Partition、Replica、Log 和 LogSegment 的关系\n\n假设有一个 Kafka 集群，Broker 个数为 3，Topic 个数为 1，Partition 个数为 3，Replica 个数为 2。Partition 的物理分布如下图所示。\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-f8f21631b138321f25c8821c677c5579_720w.webp)\n\n<figcaption>Partition分布图</figcaption>\n\n</figure>\n\n从上图可以看出，该 Topic 由三个 Partition 构成，并且每个 Partition 由主从两个副本构成。每个 Partition 的主从副本分布在不同的 Broker 上，通过这点也可以看出，当某个 Broker 宕机时，可以将分布在其他 Broker 上的从副本设置为主副本，因为只有主副本对外提供读写请求，当然在最新的 2.x 版本中从副本也可以对外读请求了。将主从副本分布在不同的 Broker 上从而提高系统的可用性。\n\nPartition 的实际物理存储是以 Log 文件的形式展示的，而每个 Log 文件又以多个 LogSegment 组成。Kafka 为什么要这么设计呢？其实原因比较简单，随着消息的不断写入，Log 文件肯定是越来越大，Kafka 为了方便管理，将一个大文件切割成一个一个的 LogSegment 来进行管理；每个 LogSegment 由数据文件和索引文件构成，数据文件是用来存储实际的消息内容，而索引文件是为了加快消息内容的读取。\n\n可能又有朋友会问，Kafka 本身消费是以 Partition 维度顺序消费消息的，磁盘在顺序读的时候效率很高完全没有必要使用索引啊。其实 Kafka 为了满足一些特殊业务需求，比如要随机消费 Partition 中的消息，此时可以先通过索引文件快速定位到消息的实际存储位置，然后进行处理。\n\n总结一下 Partition、Replica、Log 和 LogSegment 之间的关系。消息是以 Partition 维度进行管理的，为了提高系统的可用性，每个 Partition 都可以设置相应的 Replica 副本数，一般在创建 Topic 的时候同时指定 Replica 的个数；Partition 和 Replica 的实际物理存储形式是通过 Log 文件展现的，为了防止消息不断写入，导致 Log 文件大小持续增长，所以将 Log 切割成一个一个的 LogSegment 文件。\n\n**注意：** 在同一时刻，每个主 Partition 中有且只有一个 LogSegment 被标识为可写入状态，当一个 LogSegment 文件大小超过一定大小后（比如当文件大小超过 1G，这个就类似于 HDFS 存储的数据文件，HDFS 中数据文件达到 128M 的时候就会被分出一个新的文件来存储数据），就会新创建一个 LogSegment 来继续接收新写入的消息。\n\n## 写入消息流程分析\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-eb66e4ecf7cf07fcb6b12029bfdd9b71_720w.webp)\n\n<figcaption>消息写入及落盘流程</figcaption>\n\n</figure>\n\n流程解析\n\n在第 3 篇文章讲过，生产者客户端对于每个 Partition 一次会发送一批消息到服务端，服务端收到一批消息后写入相应的 Partition 上。上图流程主要分为如下几步：\n\n1.  **客户端消息收集器收集属于同一个分区的消息，并对每条消息设置一个偏移量，且每一批消息总是从 0 开始单调递增。比如第一次发送 3 条消息，则对三条消息依次编号 [0,1,2]，第二次发送 4 条消息，则消息依次编号为 [0,1,2,3]。注意此处设置的消息偏移量是相对偏移量。**\n2.  **客户端将消息发送给服务端，服务端拿到下一条消息的绝对偏移量，将传到服务端的这批消息的相对偏移量修改成绝对偏移量。**\n3.  **将修改后的消息以追加的方式追加到当前活跃的 LogSegment 后面，然后更新绝对偏移量。**\n4.  **将消息集写入到文件通道。**\n5.  **文件通道将消息集 flush 到磁盘，完成消息的写入操作。**\n\n了解以上过程后，我们在来看看消息的具体构成情况。\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-6e993c95decd5d274b032cd423936504_720w.webp)\n\n<figcaption>消息构成细节图</figcaption>\n\n</figure>\n\n一条消息由如下三部分构成：\n\n*   **OffSet：偏移量，消息在客户端发送前将相对偏移量存储到该位置，当消息存储到 LogSegment 前，先将其修改为绝对偏移量在写入磁盘。**\n*   **Size：本条 Message 的内容大小**\n*   **Message：消息的具体内容，其具体又由 7 部分组成，crc 用于校验消息，Attribute 代表了属性，key-length 和 value-length 分别代表 key 和 value 的长度，key 和 value 分别代表了其对应的内容。**\n\n### 消息偏移量的计算过程\n\n通过以上流程可以看出，每条消息在被实际存储到磁盘时都会被分配一个绝对偏移量后才能被写入磁盘。在同一个分区内，消息的绝对偏移量都是从 0 开始，且单调递增；在不同分区内，消息的绝对偏移量是没有任何关系的。接下来讨论下消息的绝对偏移量的计算规则。\n\n确定消息偏移量有两种方式，一种是顺序读取每一条消息来确定，此种方式代价比较大，实际上我们并不想知道消息的内容，而只是想知道消息的偏移量；第二种是读取每条消息的 Size 属性，然后计算出下一条消息的起始偏移量。比如第一条消息内容为 “abc”，写入磁盘后的偏移量为：8（OffSet）+ 4（Message 大小）+ 3（Message 内容的长度）= 15。第二条写入的消息内容为“defg”，其起始偏移量为 15，下一条消息的起始偏移量应该是：15+8+4+4=31，以此类推。\n\n## 消费消息及副本同步流程分析\n\n和写入消息流程不同，读取消息流程分为两种情况，分别是消费端消费消息和从副本（备份副本）同步主副本的消息。在开始分析读取流程之前，需要先明白几个用到的变量，不然流程分析可能会看的比较糊涂。\n\n*   **BaseOffSet**：基准偏移量，每个 Partition 由 N 个 LogSegment 组成，每个 LogSegment 都有基准偏移量，大概由如下构成，数组中每个数代表一个 LogSegment 的基准偏移量：[0,200,400,600, ...]。\n*   **StartOffSet**：起始偏移量，由消费端发起读取消息请求时，指定从哪个位置开始消费消息。\n*   **MaxLength**：拉取大小，由消费端发起读取消息请求时，指定本次最大拉取消息内容的数据大小。该参数可以通过[max.partition.fetch.bytes](https://link.zhihu.com/?target=https%3A//xie.infoq.cn/draft/3020%23)来指定，默认大小为 1M。\n*   **MaxOffSet**：最大偏移量，消费端拉取消息时，最高可拉取消息的位置，即俗称的“高水位”。该参数由服务端指定，其作用是为了防止生产端还未写入的消息就被消费端进行消费。此参数对于从副本同步主副本不会用到。\n*   **MaxPosition**：LogSegment 的最大位置，确定了起始偏移量在某个 LogSegment 上开始，读取 MaxLength 后，不能超过 MaxPosition。MaxPosition 是一个实际的物理位置，而非偏移量。\n\n假设消费端从 000000621 位置开始消费消息，关于几个变量的关系如下图所示。\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-cd9c62a71cddccd7bc8a5d810d5af216_720w.webp)\n\n<figcaption>位置关系图</figcaption>\n\n</figure>\n\n消费端和从副本拉取流程如下：\n\n1.  **客户端确定拉取的位置，即 StartOffSet 的值，找到主副本对应的 LogSegment。**\n2.  **LogSegment 由索引文件和数据文件构成，由于索引文件是从小到大排列的，首先从索引文件确定一个小于等于 StartOffSet 最近的索引位置。**\n3.  **根据索引位置找到对应的数据文件位置，由于数据文件也是从小到大排列的，从找到的数据文件位置顺序向后遍历，直到找到和 StartOffSet 相等的位置，即为消费或拉取消息的位置。**\n4.  **从 StartOffSet 开始向后拉取 MaxLength 大小的数据，返回给消费端或者从副本进行消费或备份操作。**\n\n假设拉取消息起始位置为 00000313，消息拉取流程图如下：\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-9417ca60a0c5e9474ec49a77fff18b1b_720w.webp)\n\n<figcaption>消息拉取流程图</figcaption>\n\n</figure>\n\n## kafka 如何保证系统的高可用、数据的可靠性和数据的一致性的？\n\n### kafka 的高可用性：\n\n1.  **Kafka 本身是一个分布式系统，同时采用了 Zookeeper 存储元数据信息，提高了系统的高可用性。**\n2.  **Kafka 使用多副本机制，当状态为 Leader 的 Partition 对应的 Broker 宕机或者网络异常时，Kafka 会通过选举机制从对应的 Replica 列表中重新选举出一个 Replica 当做 Leader，从而继续对外提供读写服务（当然，需要注意的一点是，在新版本的 Kafka 中，Replica 也可以对外提供读请求了），利用多副本机制在一定程度上提高了系统的容错性，从而提升了系统的高可用。**\n\n### Kafka 的可靠性：\n\n1.  **从 Producer 端来看，可靠性是指生产的消息能够正常的被存储到 Partition 上且消息不会丢失。Kafka 通过 [request.required.acks](https://link.zhihu.com/?target=https%3A//xie.infoq.cn/edit/49a133ad2b2f2671aa60706b0%23)和[min.insync.replicas](https://link.zhihu.com/?target=https%3A//xie.infoq.cn/edit/49a133ad2b2f2671aa60706b0%23) 两个参数配合，在一定程度上保证消息不会丢失。**\n2.  **[request.required.acks](https://link.zhihu.com/?target=https%3A//xie.infoq.cn/edit/49a133ad2b2f2671aa60706b0%23) 可设置为 1、0、-1 三种情况。**\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-7946f258c85fb8ca3d4aa423269c483a_720w.webp)\n\n<figcaption>request.required.acks=1</figcaption>\n\n</figure>\n\n设置为 1 时代表当 Leader 状态的 Partition 接收到消息并持久化时就认为消息发送成功，如果 ISR 列表的 Replica 还没来得及同步消息，Leader 状态的 Partition 对应的 Broker 宕机，则消息有可能丢失。\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-382c9f37f644feb37dd975c67bc1038f_720w.webp)\n\n<figcaption>request.required.acks=0</figcaption>\n\n</figure>\n\n设置为 0 时代表 Producer 发送消息后就认为成功，消息有可能丢失。\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-592996f264baadc64967d6f4b28f4d23_720w.webp)\n\n<figcaption>request.required.acks=-1</figcaption>\n\n</figure>\n\n设置为-1 时，代表 ISR 列表中的所有 Replica 将消息同步完成后才认为消息发送成功；但是如果只存在主 Partition 的时候，Broker 异常时同样会导致消息丢失。所以此时就需要[min.insync.replicas](https://link.zhihu.com/?target=https%3A//xie.infoq.cn/edit/49a133ad2b2f2671aa60706b0%23)参数的配合，该参数需要设定值大于等于 2，当 Partition 的个数小于设定的值时，Producer 发送消息会直接报错。\n\n上面这个过程看似已经很完美了，但是假设如果消息在同步到部分从 Partition 上时，主 Partition 宕机，此时消息会重传，虽然消息不会丢失，但是会造成同一条消息会存储多次。在新版本中 Kafka 提出了幂等性的概念，通过给每条消息设置一个唯一 ID，并且该 ID 可以唯一映射到 Partition 的一个固定位置，从而避免消息重复存储的问题（作者到目前还没有使用过该特性，感兴趣的朋友可以自行在深入研究一下）。\n\n### Kafka 的一致性：\n\n1.  **从 Consumer 端来看，同一条消息在多个 Partition 上读取到的消息是一直的，Kafka 通过引入 HW（High Water）来实现这一特性。**\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-9975539d98bf1a4e1a3038f2eceb2bb9_720w.webp)\n\n<figcaption>消息同步图</figcaption>\n\n</figure>\n\n从上图可以看出，假设 Consumer 从主 Partition1 上消费消息，由于 Kafka 规定只允许消费 HW 之前的消息，所以最多消费到 Message2。假设当 Partition1 异常后，Partition2 被选举为 Leader，此时依旧可以从 Partition2 上读取到 Message2。其实 HW 的意思利用了木桶效应，始终保持最短板的那个位置。\n\n从上面我们也可以看出，使用 HW 特性后会使得消息只有被所有副本同步后才能被消费，所以在一定程度上降低了消费端的性能，可以通过设置[replica.lag.time.max.ms](https://link.zhihu.com/?target=https%3A//xie.infoq.cn/edit/49a133ad2b2f2671aa60706b0%23)参数来保证消息同步的最大时间。\n\n## kafka 为什么那么快？\n\nkafka 使用了顺序写入和“零拷贝”技术，来达到每秒钟 200w（Apache 官方给出的数据） 的磁盘数据写入量，另外 Kafka 通过压缩数据，降低 I/O 的负担。\n\n1.  **顺序写入**\n\n大家都知道，对于磁盘而已，如果是随机写入数据的话，每次数据在写入时要先进行寻址操作，该操作是通过移动磁头完成的，极其耗费时间，而顺序读写就能够避免该操作。\n\n1.  **“零拷贝”技术**\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-6930901956f341f1ab4a6e5650a0680b_720w.webp)\n\n<figcaption>普通数据拷贝流程图</figcaption>\n\n</figure>\n\n普通的数据拷贝流程如上图所示，数据由磁盘 copy 到内核态，然后在拷贝到用户态，然后再由用户态拷贝到 socket，然后由 socket 协议引擎，最后由协议引擎将数据发送到网络中。\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-9e44873a63d8addca917e658667f0b61_720w.webp)\n\n<figcaption>&amp;quot;零拷贝&amp;quot;流程图</figcaption>\n\n</figure>\n\n采用了“零拷贝”技术后可以看出，数据不在经过用户态传输，而是直接在内核态完成操作，减少了两次 copy 操作。从而大大提高了数据传输速度。\n\n1.  **压缩**\n\nKafka 官方提供了多种压缩协议，包括 gzip、snappy、lz4 等等，从而降低了数据传输的成本。\n\n## Kafka 中的消息是否会丢失和重复消费？\n\n1.  **Kafka 是否会丢消息，答案相信仔细看过前面两个问题的同学都比较清楚了，这里就不在赘述了。**\n2.  **在低版本中，比如作者公司在使用的 Kafka0.8 版本中，还没有幂等性的特性的时候，消息有可能会重复被存储到 Kafka 上（原因见上一个问题的），在这种情况下消息肯定是会被重复消费的。**\n\n**这里给大家一个解决重复消费的思路，作者公司使用了 Redis 记录了被消费的 key，并设置了过期时间，在 key 还没有过期内，对于同一个 key 的消息全部当做重复消息直接抛弃掉。** 在网上看到过另外一种解决方案，使用 HDFS 存储被消费过的消息，是否具有可行性存疑（需要读者朋友自行探索），读者朋友们可以根据自己的实际情况选择相应的策略，如果朋友们还有其他比较好的方案，欢迎留言交流。\n\n## 为什么要使用 kafka，为什么要使用消息队列？\n\n### 先来说说为什么要使用消息队列？\n\n这道题比较主观一些（自认为没有网上其他文章写得话，轻喷），但是都相信大家使用消息队列无非就是为了 **解耦**、**异步**、**消峰**。\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-f7c1bb87ab46ddd03255c58109ce360f_720w.webp)\n\n<figcaption>系统调用图</figcaption>\n\n</figure>\n\n随着业务的发展，相信有不少朋友公司遇到过如上图所示的情况，系统 A 处理的结构被 B、C、D 系统所依赖，当新增系统 E 时，也需要系统 A 配合进行联调和上线等操作；还有当系统 A 发生变更时同样需要告知 B、C、D、E 系统需要同步升级改造。\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-0f0c8f9531a38f6d79b2cbb2973bfbfc_720w.webp)\n\n<figcaption>引入消息队列图</figcaption>\n\n</figure>\n\n引入消息队列后有两个好处：\n\n1.  **各个系统进行了解耦，从上图也可以看出，当系统 A 突然发生热点事件时，同一时间产生大量结果，MQ 充当了消息暂存的效果，防止 B、C、D、E 系统也跟着崩溃。**\n2.  **当新系统 E 需要接入系统 A 的数据，只需要和 MQ 对接就可以了，从而避免了与系统 A 的调试上线等操作。**\n\n引入消息队列的坏处：\n\n万事皆具备两面性，看似引入消息队列这件事情很美好，但是同时也增加了系统的复杂度、系统的维护成本提高（如果 MQ 挂了怎么办）、引入了一致性等等问题需要去解决。\n\n## 为什么要使用 Kafka?\n\n作者认为采用 Kafka 的原因有如下几点：\n\n1.  **Kafka 目前在业界被广泛使用，社区活跃度高，版本更新迭代速度也快。**\n2.  **Kafka 的生产者和消费者都用 Java 语言进行了重写，在一定程度降低了系统的维护成本（作者的主观意见，因为当下 Java 的使用群体相当庞大）。**\n3.  **Kafka 系统的吞吐量高，达到了每秒 10w 级别的处理速度。**\n4.  **Kafka 可以和很多当下优秀的大数据组件进行集成，包括 Spark、Flink、Flume、Storm 等等。**\n\n## 为什么 Kafka 不支持读写分离？\n\n这个问题有个先决条件，我们只讨论 Kafka0.9 版本的情况。对于高版本，从 Partition 也可以承担读请求了，这里不多赘述。\n\nKafka 如果支持读写分离的话，有如下几个问题。\n\n1.  **系统设计的复杂度会比较大，当然这个比较牵强，毕竟高版本的 Kafka 已经实现了。**\n\n<figure data-size=\"normal\">\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/v2-98093ad82970feb7a0c52954c6942aa1_720w.webp)\n\n</figure>\n\n**2\\. 从上图可以看出，从从 Partition 上读取数据会有两个问题。一、数据从主 Partition 上同步到从 Partition 有数据延迟问题，因为数据从生产到消费会经历 3 次网络传输才能够被消费，对于时效性要求比较高的场景本身就不适合了。二、数据一致性问题，假设主 Partition 将数据第一次修改成了 A，然后又将该数据修改成了 B，由于从主 Partition 同步到从 Partition 会有延迟问题，所以也就会产生数据一致性问题。**\n\n分析得出，通过解决以上问题来换取从 Partition 承担读请求，成本可想而知，而且对于写入压力大，读取压力小的场景，本身也就没有什么意义了。\n\n## 总结\n\n本文介绍了几个常见的 Kafka 的面试题\n\n### 常见面试题一览\n\n#### 1.1 Kafka 中的 ISR(InSyncRepli)、 OSR(OutSyncRepli)、 AR(AllRepli)代表什么？\n\nISR：速率和leader相差低于10s的follower的集合\n\nOSR：速率和leader相差大于10s的follwer\n\nAR：所有分区的follower\n\n#### 1.2 Kafka 中的 HW、 LEO 等分别代表什么？\n\nHW：High Water高水位，根据同一分区中最低的LEO决定（Log End Offset）\n\nLEO：每个分区最大的Offset\n\n#### 1.3 Kafka 中是怎么体现消息顺序性的？\n\n在每个分区内，每条消息都有offset，所以消息在同一分区内有序，无法做到全局有序性\n\n#### 1.4 Kafka 中的分区器、序列化器、拦截器是否了解？它们之间的处理顺序是什么？\n\n分区器Partitioner用来对分区进行处理的，即消息发送到哪一个分区的问题。序列化器，这个是对数据进行序列化和反序列化的工具。拦截器，即对于消息发送进行一个提前处理和收尾处理的类Interceptor，处理顺利首先通过拦截器=>序列化器=>分区器\n\n#### 1.5 Kafka 生产者客户端的整体结构是什么样子的？使用了几个线程来处理？分别是什么？\n\n使用两个线程：main和sender 线程，main线程会一次经过拦截器、序列化器、分区器将数据发送到RecoreAccumulator线程共享变量，再由sender线程从共享变量中拉取数据发送到kafka broker\n\nbatch.size达到此规模消息才发送，linger.ms未达到规模，等待当前时长就发送数据。\n\n#### 1.6 消费组中的消费者个数如果超过 topic 的分区，那么就会有消费者消费不到数据”这句 话是否正确？\n\n这句话是对的，超过分区个数的消费者不会在接收数据，主要原因是一个分区的消息只能够被一个消费者组中的一个消费者消费。\n\n#### 1.7 消费者提交消费位移时提交的是当前消费到的最新消息的 offset 还是 offset+1？\n\n生产者发送数据的offset是从0开始的，消费者消费的数据的offset是从1开始，故最新消息是offset+1\n\n#### 1.8 有哪些情形会造成重复消费？\n\n先消费后提交offset，如果消费完宕机了，则会造成重复消费\n\n#### 1.9 那些情景会造成消息漏消费？\n\n先提交offset，还没消费就宕机了，则会造成漏消费\n\n#### 1.10 当你使用 kafka-topics.sh 创建（删除）了一个 topic 之后， Kafka 背后会执行什么逻辑？\n\n会在 zookeeper 中的/brokers/topics 节点下创建一个新的 topic 节点，如：/brokers/topics/first 触发 Controller 的监听程序 kafka Controller 负责 topic 的创建工作，并更新 metadata cache\n\n#### 1.11 topic 的分区数可不可以增加？如果可以怎么增加？如果不可以，那又是为什么？\n\n可以增加，修改分区个数--alter可以修改分区个数\n\n#### 1.12 topic 的分区数可不可以减少？如果可以怎么减少？如果不可以，那又是为什么？\n\n不可以减少，减少了分区之后，之前的分区中的数据不好处理\n\n#### 1.13 Kafka 有内部的 topic 吗？如果有是什么？有什么所用？\n\n有，__consumer_offsets主要用来在0.9版本以后保存消费者消费的offset\n\n#### 1.14 Kafka 分区分配的概念？\n\nKafka分区对于Kafka集群来说，分区可以做到负载均衡，对于消费者来说分区可以提高并发度，提高读取效率\n\n#### 1.15 简述 Kafka 的日志目录结构？\n\n每一个分区对应着一个文件夹，命名为topic-0/topic-1…，每个文件夹内有.index和.log文件。\n\n#### 1.16 如果我指定了一个 offset， Kafka Controller 怎么查找到对应的消息？\n\noffset表示当前消息的编号，首先可以通过二分法定位当前消息属于哪个.index文件中，随后采用seek定位的方法查找到当前offset在.index中的位置，此时可以拿到初始的偏移量。通过初始的偏移量再通过seek定位到.log中的消息即可找到。\n\n#### 1.17 聊一聊 Kafka Controller 的作用？\n\nKafka集群中有一个broker会被选举为Controller，负责管理集群broker的上下线、所有topic的分区副本分配和leader的选举等工作。Controller的工作管理是依赖于zookeeper的。\n\n#### 1.18 Kafka 中有那些地方需要选举？这些地方的选举策略又有哪些？\n\n在ISR中需要选举出Leader，选择策略为先到先得。在分区中需要选举，需要选举出Leader和follower。\n\n#### 1.19 失效副本是指什么？有那些应对措施？\n\n失效副本为速率比leader相差大于10s的follower，ISR会将这些失效的follower踢出，等速率接近leader的10s内，会重新加入ISR\n\n#### 1.20 Kafka 的哪些设计让它有如此高的性能？\n\n1.  Kafka天生的分布式架构\n2.  对log文件进行了分segment，并对segment建立了索引\n3.  对于单节点使用了顺序读写，顺序读写是指的文件的顺序追加，减少了磁盘寻址的开销，相比随机写速度提升很多\n4.  使用了零拷贝技术，不需要切换到用户态，在内核态即可完成读写操作，且数据的拷贝次数也更少。\n\n## 参考文章\nhttps://blog.csdn.net/cao131502\nhttps://zhuanlan.zhihu.com/p/137811719"
  },
  {
    "path": "docs/mq/kafka/消息队列kafka详解：如何实现延迟队列.md",
    "content": "KafkaһֲʽϢϵͳ㷺ӦڻʹʵӦУҪʵӳٶеĹܣԱһʱִĳЩ߷ĳЩϢKafkaṩ˶ַʽʵӳٶУĽһֳʵַ\n\n\n\n\n\n### һӳٶи\n\n\n\n\n\nӳٶһһʱִϢĻơӦóʱϢ͡ʱȵȡӳٶеʵַʽж֣һֱȽϳʵַǻϢеӳϢơ\n\n\n\n\n\nKafkaУӳϢָڷϢʱָһӳʱ䣬Ϣӳʱ䵽űѡKafkaṩһЩTopicڴ洢ӳϢ\"delayed-messages\"߽̿ԶڴЩTopicϢϢ·͵ĿTopicУӶʵӳٶеĹܡ\n\n\n\n\n\n### KafkaеӳϢʵԭ\n\n\n\n\n\nKafkaӳϢʵԭȽϼ򵥣Ҫ漰ϢkeyʱϢkeyУһʱʾϢӳʱ䡣߷ϢʱϢ͵\"delayed-messages\" TopicУϢkeyеʱ߽̻ᶨڴ\"delayed-messages\" TopicϢϢkeyеʱǷѾڡʱѾڣϢ·͵ĿTopicУ\"target-messages\"ʱδڣϢ·͵\"delayed-messages\" TopicУһµӳʱͿʵӳٶеĹܡ\n\n\n\n![image-20230526211424767](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230526211424767.png)\n\n\n\n\n\n### Kafkaӳٶеʵֲ\n\n\n\n\n\nʵKafkaеӳٶУ԰²У\n\n\n\n\n\n1.һרŵTopicڴ洢ӳϢ\"delayed-messages\"ʹKafkaй߻Kafka APIд\n\n\n\n\n\n2.ϢkeyӳʱʹõǰʱӳʱΪkey磺\"key\":\"message_body\"ʹKafka APIϢ\"delayed-messages\" TopicС\n\n\n\n\n\n3.һ߽̣\"delayed-messages\" TopicеϢʹKafka APIʵ̡߽\n\n\n\n\n\n4.߽УϢkeyеʱǷѾڡʹõǰʱϢkeyеʱбȽϡʱѾڣϢ·͵ĿTopicУ\"target-messages\"ʹKafka APIʵϢ·͡\n\n\n\n\n\n5.ʱδڣϢ·͵\"delayed-messages\" TopicУһµӳʱʹKafka APIʵϢ·ͣϢkeyµӳʱ\n\n\n\n\n\n6.ȴһʱظִе4͵5ֱϢkeyеʱѾڡ\n\n\n\n\n\nͨϲ裬ͿʵKafkaеӳٶйܡҪעǣ߽Ҫڴ\"delayed-messages\" TopicϢϢkeyеʱǷѾڡԸݾӦóòͬӳʱ䡣\n\n\n\n\n\n![image-20230526211452328](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230526211452328.png)\n\n\n\n\n\n### ġӳϢʵֵȱ\n\n\n\n\n\n![image-20230526211506828](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230526211506828.png)\n\n\n\n\n\nKafkaʵӳٶеķŵ㣺\n\n\n\n\n\n1.ڷֲʽϵͳʵӳٶйܣнϸߵĿչԺͿɿԡ\n\n\n\n\n\n2.ʵּ򵥣ֻҪʹKafka APIɣ򹤾ߡ\n\n\n\n\n\n3.֧Ϣͺѣܺ\n\n\n\n\n\n4.صӳʱĿTopicڲͬӦó\n\n\n\n\n\nǣKafkaʵӳٶҲһЩȱ㣺\n\n\n\n\n\n1.Ҫ̶߽ڴ\"delayed-messages\" TopicϢ߽崻ֹͣӰӳٶеĹܡ\n\n\n\n\n\n2.߽ҪϢ·ͺͼ飬ҪһԴʱ䡣\n\n\n\n\n\n3.ӳʱ侫ޣСֻܴﵽ뼶\n\n\n\n\n\n### ġܽ\n\n\n\n\n\nKafkaһǿķֲʽϢϵͳʵӳٶеĹܡͨϢkeyӳʱ̵߽ĶѺ·ͣʵӳٶеĹܡKafkaʵӳٶеķʵּ򵥡չԸߡܺõŵ㣬ҲһЩȱ㡣ʵӦУҪݾӦóѡʵӳٶʵַ"
  },
  {
    "path": "docs/mq/kafka/消息队列kafka详解：如何实现死信队列.md",
    "content": "### Apache Kafka ڴŶУ Uber  Crowdstrike ʵͰо\n\n\n\n\n\nʶʹκοɿܵǱزٵġƪ̽**  Apache Kafka ܹ****ʹŶʵִʵ**ЩѡԶʵ֡Kafka StreamsKafka ConnectSpring ܺͲߡʵоչʾ UberCrowdStrike ɣ̹Լ˹ģɿʵʱ\n\n\n\n\n\nApache Kafka ΪҵܹϲļмʹսԣҵҲ Kafka Ϊԭƽ̨ (iPaaS)\n\n\n\n\n\n### Apache Kafka еϢģʽ\n\n\n\n\n\nҿʼƪ֮ǰ֪**ڡJMSϢк Apache KafkaĲϵ**һ֣\n\n\n\n\n\n*   JMS Ϣ Apache Kafka **10 Ƚϱ׼**\n*   _**ƪ**_**C ͨApache Kafka еŶ (DQL)**д\n*   ʹ Apache Kafkaʵ**-ظģʽ**\n*   __Ƴ**ѡȷϢϵͳľ**JMS  Apache Kafka\n*   _Ƴ_ JMS Ϣ Apache Kafka**ɡǨƺ/滻**\n\n\n\n\n\n### ʲôŶмģʽ Apache Kafka У\n\n\n\n\n\n**Ŷ (DLQ)**Ϣϵͳƽ̨ڵһַʵ֣**洢δɹϢ**ϵͳǱתϢǽƶŶС\n\n\n\n\n\nҵ**ģʽ (EIP)**ΪģʽͨǿԽͬʡ\n\n\n\n\n\n![image-20230526224702433](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230526224702433.png)\n\n\n\n\n\nصƽ̨ Apache Kafka** Kafka нϢ DLQ **ҪԭͨϢʽϢЧ/ȱʧ磬Ԥֵ߷ַᷢӦóڸ̬ĻУⲻڡ쳣޷Ϣһ\n\n\n\n\n\nˣͨҪʹме֪ʶMessage Queue м JMS  IBM MQTIBCO EMS  RabbitMQֲʽύ־ KafkaĹʽͬԭϢе DLQ ϢϵͳЩԭһһӳ䵽 Kafka磬MQ ϵͳеϢÿϢ TTLʱ䣩ڡ\n\n\n\n\n\nˣ** Kafka нϢ DLQ ҪԭϢʽϢЧ/ȱʧ**\n\n\n\n\n\n### Apache Kafka Ŷе\n\n\n\n\n\nKafka еŶһ Kafka ⣬**պʹ洢ڴ޷һܵдϢ**˸ʹ´ϢϢЧϢĴֹͣ\n\n\n\n\n\n### Kafka Broker ܱܶ˵ṩ\n\n\n\n\n\n**Kafka ܹ֧ broker** r еDLQأKafka ִ΢ͬԭϣʹáƹܵܶ˵㡱ԭΪʲô봫ͳϢȣKafka չ֮á˺ʹڿͻӦóС\n\n\n\n\n\nƽ̨ʵָɾơ**ÿ΢ӦóͨԼѡļͨŷʽʹʵ߼**\n\n\n\n\n\nڴͳмϢУṩ߼еĿչԺԽϲΪֻмŶӲʵּ߼\n\n\n\n\n\n### καԶʵ Kafka Ŷ\n\n\n\n\n\nKafka еŶжʹõĿܡһЩΪŶṩ˿伴õĹܡǣʹJavaGoC++Python **καΪ Kafka ӦóдŶ߼**Ҳס\n\n\n\n\n\n**Ŷʵ**Դһ try-catch Ԥڻ쳣ûзϢκ쳣뽫Ϣ͵רõ DLQ Kafka ⡣\n\n\n\n\n\n**ʧԭӦӵ Kafka Ϣıͷ**СӦļֵԱ㽫ʷ¼´͹Ϸ\n\n\n\n\n\n### ŶеĿ伴 Kafka ʵ\n\n\n\n\n\n㲢ҪʵŶС**ͿѾṩǵ DLQ ʵ**\n\n\n\n\n\nʹԼӦóͨԿƴڳִʱ޸롣ǣ** 3rd Ӧóļɲһܿ缯ϰĴ**ˣDLQ øҪĳЩС\n\n\n\n\n\n### Kafka Connect Ŷ\n\n\n\n\n\n**Kafka Connect  Kafka ļɿ**ڿԴ Kafka СҪ Connect Ⱥе\n\n\n\n\n\nĬ£ʹЧϢʹô JSON תȷ AVRO תʱKafka Connect ֹͣɾЧϢһѡ񡣺̴\n\n\n\n\n\nKafka Connect  DLQ úܼ򵥡ֻ轫ѡ ' errors.tolerance'  ' errors.deadletterqueue.topic.name' ֵΪȷֵ\n\n\n\n\n\n![image-20230526224856544](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230526224856544.png)\n\n\n\n\n\n¡ Kafka Connect Deep Dive C ŶСʾʹ DLQ ϸִʾ\n\n\n\n\n\n**Kafka Connect ڴ DLQ еĴϢ**ֻ貿һʹ te DLQ 磬Ӧó Avro ϢҴϢ JSON ʽȻʹ JSON ϢתΪ AVRO ϢԳɹ´\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/61aa7624a01c5016d3ab9aaa8ee6938d5514.jpeg)ע⣬Kafka Connect **ûԴŶ**\n\n\n\n\n\n### Kafka Streams ӦóеĴ\n\n\n\n\n\n**Kafka Streams  Kafka **ʽ Apache FlinkStormBeam ƹߡǣ Kafka ԭġζڵչҿɿĻܹйĶ˵\n\n\n\n\n\nֱʹ JavaJVM ̬ϵͳ Kafka Ӧó**鼸ʹ Kafka Streams  Kafka ı׼ Java ͻ**Ϊʲô\n\n\n\n\n\n*   Kafka StreamsֻǡһΧƳ Java ߺ API İװԼõĸӹܡ\n*   ߶ֻǶ뵽 Java ӦóеĿ⣨JAR ļ\n*   ߶ǿԴ Kafka صһ - ûж֤ġ\n*   Ѿ伴õؽԹܡ״̬Ƕʽ洢ڡʽѯȵȣ\n\n\n\n\n\nKafka Streams**ù֮һĬϵķл쳣**޷л**¼쳣**𻵵ݡȷл߼δļ¼Ͷܵ´󡣸ùܲΪŶУ伴õؽͬ⡣\n\n\n\n\n\n### Spring Kafka  Spring Cloud Stream Ĵ\n\n\n\n\n\nSpring ܶ Apache Kafka кܺõ֧֡ṩģԱԼд롣**Spring-Kafka  Spring Cloud Stream Kafka ָ֧Ժʹѡ**ʱ/ԡŶеȡ\n\n\n\n\n\n Spring ܹܷǳḻеأһѧϰߡˣǳʺ½ĿѾ Spring Ŀ\n\n\n\n\n\nкܶܰĲչʾ˲ͬʾѡŶеĹٷ Spring Cloud Stream ʾSpring ʹü򵥵ע͹߼ DLQ̷ֱһЩԱӰķһЩϲֻ˽ѡΪԼѡʵѡɡ\n\n\n\n\n\n### Apache Kafka ߵĿչʹ\n\n\n\n\n\nͻԻУʵ֤**ŶеҪԭͨǴӵⲿ Web ݿʧ**ʱ Kafka ޷з͸ᵼĳЩӦó̱һܺõĽ\n\n\n\n\n\nApache Kafka****Apache 2.0 **ĿԴĿ**ṩһпͻ˶еĲ Apache Kafka ͻ˰װһ**ؼԵĸ򵥵/ API**Լ**չķ IO**\n\n\n\n\n\nÿ**ͨ Kafka Consumer дϢζڲ**Ҫеķ Kafka Consumer жȡ**ͨ Kafka ĸ**ӳ١µ缫˲ԡⲿݷḻŶӡ\n\n\n\n\n\nһؼ**ڵ Kafka Ӧóд/ظ Web ݿ**лһη͵ Web Ҫ\n\n\n\n\n\n![image-20230526224910457](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230526224910457.png)\n\n\n\n\n\n**Parallel Consumer ͻ˾ǿ**߼õӳٺͶ̬ҲԷ͵ŶС\n\n\n\n\n\n### ʹŶеϢ\n\n\n\n\n\n**͵ŶкûɣϢҪٱأ**\n\n\n\n\n\nŶ**¼д⴦ݴ**ľѷʽζŴ¼ֿݱ䡣\n\n\n\n\n\nڴʹŶеĴԡDO  DONT ̽ʵ;ѵ\n\n\n\n\n\n### \n\n\n\n\n\nмѡڴ洢ŶеϢ\n\n\n\n\n\n*   **´**DLQеһЩϢҪ´ǣȣҪ⡣Զű༭Ϣ˹߷شҪ·ͣģϢ\n*   **ɾϢһ**ãִܻϢǣɾ֮ǰҵӦüǡ磬ǱӦóʹôϢǿӻ\n*   **߼**һѡǷԻȡʵʱ⣬Ǵ DLQ еÿϢ磬һ򵥵 ksqlDB ӦóӦм㣬ÿСʱϢƽκȷ Kafka ӦóеĴļ⡣\n*   **ֹͣ**ٻֻϢֹͣҵ̡öԶģҲ˾ȻֹͣҲ׳ Kafka ӦóɡҪDLQ ;ⲿ\n*   ****ѡֻŶʲôȻʹĳЩҲܺã Kafka ӦóΪסKafka бʱ䣬ڸʱ֮ɾϢֻΪȷķʽɡ DQL ǷΪ̫죩\n\n\n\n\n\n### Apache Kafka Ŷеʵ\n\n\n\n\n\n Kafka ӦóʹŶндһЩ**ʵ;ѵ**\n\n\n\n\n\n*   **ЧϢҵ**Զ˹\n    *   ʵͨû˴ DLQ Ϣ\n    *   ѡ 1ҪվǻܹŶ\n    *   ѡ 2Ӧ֪ͨ¼ŶϵͳݴǽҪӼ¼ϵͳ·/޸ݡ\n    *   û˹ĻԹ뿼ɺ DLQ ڵıҪԡ෴ЩϢҲڳʼ Kafka Ӧóбԡʡ˴縺ءʩʽ\n*   **ʵǱ**Ŷӣ磬ͨʼ Slack \n*   ÿ Kafka **ȼֹͣɾ´**\n*   **ԵĴϢ͵ DLQ** - ӦóΡ\n*   **ԭʼϢ**Ǵ洢 DLQ УжıͷϢʱ䡢ӦóƵȣʹ´͹ųøס\n*   **Ҫ Dead Letter Queue Kafka **ȡᡣǽд洢ڵ DLQ пܶԽһ´û塣\n\n\n\n\n\nס**DLQ б֤˳ֹʹκ͵ߴø**ˣKafka DQL ʺÿ\n\n\n\n\n\n### ʱ Kafka ʹŶУ\n\n\n\n\n\n̽һ²ӦýЩ͵Ϣ Kafka ŶУ\n\n\n\n\n\n*   **DLQ ڱѹ**ڴϢķֵʹ DLQ нһ⡣Kafka ־Ĵ洢Զѹ԰ԼٶȻȡݵķʽȡݣô󣩡ܵĻԵչߡʹĴ洢ռDLQ Ҳ޼¡⣬Ƿʹ DLQ ޹ء\n*   **ʧܵDLQ**ʧܶϢ DQL ޼£ʹڶ֮󣩡ϢҲ޷ӵϵͳҪ⡣ϢԸҪ洢ڳУȡڱʱ䣩\n\n\n\n\n\n### ʹԤģʽע\n\n\n\n\n\nͬҪǣ̽ĳЩ**ŶеĿԡ**\n\n\n\n\n\n****Schema Registry**һȷԷֹڸгķ** Kafka ǿִȷϢṹ\n\n![image-20230526224926155](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230526224926155.png)\n\n\n\n\n\nģʽעģʽĿͻ˼顣Confluent Server һЩʵڴṩ˶ģʽ飬ԾܾδʹģʽעߵЧϢ\n\n\n\n\n\n### Kafka Ŷеİо\n\n\n\n\n\nǿ**UberCrowdStrike  Santander Bank о Kafka ʩʵʲŶ**סЩǷǳӡÿĿҪôӡ\n\n\n\n\n\n### Uber - ɿٴŶ\n\n\n\n\n\nڷֲʽϵͳУǲɱġ󵽸⣬ϵжϣģеķ׼þŵʶʹϡ\n\n\n\n\n\n Uber ӪΧٶȣϵͳ**ݴʧʱЭ**Uber  Apache Kafka ڸּ˹ģʵһĿꡣ\n\n\n\n\n\nЩԣUber չŶչ Kafka ¼ܹеãͨʹ n**´Ŷʵֽɹ۲Ĵжʵʱ**òѡļʻԱ˺ƻ 200 пɿУΪעʻԱ۳ÿг̵ÿӢﱣѡ\n\n\n\n\n\n Uber ʾήļֱ½ DLQ\n\n\n\n\n\n![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230526225030750.png)\n\n\n\n\n\nйظϢĶ Uber ǳϸļ£ʹ Apache Kafka ɿٴŶС\n\n\n\n\n\n### CrowdStrike - ¼Ĵ\n\n\n\n\n\nCrowdStrike һλڵ¿˹ݰ˹͡**簲ȫ˾**ṩ**ƹغͶ˵㰲ȫв鱨繥Ӧ**\n\n\n\n\n\nCrowdStrike Ļʩ** ÿʹ Apache Kafka ڸ¼**ҵġ Apache Kaka 簲ȫϵСУҽκιģʵʱ̬Ƹ֪в鱨\n\n\n\n\n\nCrowdStrike ʵ ɹʵŶкʹ\n\n\n\n\n\n*   **ȷϵͳд洢Ϣ**ʩʹԲͼšCrowdStrike ʹ S3 洢洢ǱڵĴϢע⣬Kafka ķֲ洢伴õؽ⣬洢ӿڣ磬 Confluent Cloud е޴洢\n*   **ʹԶ**ùʹ޸һʧΪֶɴܷǳ׳\n*   **¼ҵ̲ƸŶ**׼ͼ¼ȷʹáйʦϤ֯ϢĲԡ\n\n\n\n\n\n** CrowdStrike 簲ȫƽ̨УģʵʱݴҪ**ҪҲڴ**һ繥ǹʵЧݵĶϢ** JavaScript ©ãˣͨŶʵʱ\n\n\n\n\n\n### ɣ̹ - Ժ DLQ ϵ 2.0\n\n\n\n\n\nɣ̹**Ӧóдݵͬݴپ޴ս**¼ܹǵĻܹһҿչļܹΪSantander Mailbox 2.0\n\n\n\n\n\nSantander ĹزתƵ** Apache Kafka ṩֵ֧¼Դ**\n\n\n\n\n\n![image-20230526225045196](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230526225045196.png)\n\n\n\n\n\nµĻ첽¼ļܹеһؼսǴ** Santander ʹԺ DQL Kafka ⹹Ĵ**Щ⣺\n\n\n\n\n\n![image-20230526225057151](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/image-20230526225057151.png)\n\n\n\n\n\n鿴 Santander ļɺ Consdata Kafka ݽԲԺ Apache Kafka еĿɿ¼ݡеϸϢ\n\n\n\n\n\n### Apache Kafka пɿҿչĴ\n\n\n\n\n\n**ڹɿܵƽ̨Ҫ**ڲͬ⡣ýŶеԶʵֻʹõĿܣ Kafka StreamsKafka ConnectSpring ܻ Kafka Ĳߡ\n\n\n\n\n\nŲCrowdStrike ɣ̹еİоǺʵ֡µӦóܹʱҪһʼͿǵһ㡣**ʹ Apache Kafka ʵʱֻܹΪʱܳɹ**Ŷೡľѡ"
  },
  {
    "path": "pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>groupId</groupId>\n    <artifactId>JavaTutorial</artifactId>\n    <version>1.0-SNAPSHOT</version>\n    <parent>\n        <groupId>org.springframework.boot</groupId>\n        <artifactId>spring-boot-starter-parent</artifactId>\n        <version>3.0.5</version>\n        <relativePath/>\n        <!-- lookup parent from repository -->\n    </parent>\n\n    <dependencies>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-web</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.github.houbb</groupId>\n            <artifactId>markdown-toc</artifactId>\n            <version>1.0.6</version>\n        </dependency>\n        <!--对分词索引查询解析-->\n        <dependency>\n            <groupId>org.apache.lucene</groupId>\n            <artifactId>lucene-queryparser</artifactId>\n            <version>7.1.0</version>\n        </dependency>\n        <!--高亮 -->\n        <dependency>\n            <groupId>org.apache.lucene</groupId>\n            <artifactId>lucene-highlighter</artifactId>\n            <version>7.1.0</version>\n        </dependency>\n        <!--smartcn 中文分词器 SmartChineseAnalyzer  smartcn分词器 需要lucene依赖 且和lucene版本同步-->\n        <dependency>\n            <groupId>org.apache.lucene</groupId>\n            <artifactId>lucene-analyzers-smartcn</artifactId>\n            <version>7.1.0</version>\n        </dependency>\n        <!--ik-analyzer 中文分词器-->\n        <dependency>\n            <groupId>cn.bestwu</groupId>\n            <artifactId>ik-analyzers</artifactId>\n            <version>5.1.0</version>\n        </dependency> <!--MMSeg4j 分词器-->\n        <dependency>\n            <groupId>com.chenlb.mmseg4j</groupId>\n            <artifactId>mmseg4j-solr</artifactId>\n            <version>2.4.0</version>\n            <exclusions>\n                <exclusion>\n                    <groupId>org.apache.solr</groupId>\n                    <artifactId>solr-core</artifactId>\n                </exclusion>\n            </exclusions>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-maven-plugin</artifactId>\n            </plugin>\n        </plugins>\n    </build>\n\n</project>"
  },
  {
    "path": "src/main/java/Test.java",
    "content": "import org.springframework.beans.factory.annotation.Lookup;\nimport org.springframework.stereotype.Service;\n\n/**\n * @author hpl\n * @date 2023/4/15 14:40\n */\n@Service\npublic class Test {\n\n\n\n\n    public static void main(String[] args) {\n\n    }\n\n\n    public void test(){\n\n    }\n}\n"
  },
  {
    "path": "src/main/java/md/codeFormat.java",
    "content": "package md;\n\n/**\n * @author hpl\n * @date 2023/4/7 21:17\n */\npublic class codeFormat {\n    //\n}\n"
  },
  {
    "path": "src/main/java/md/mdToc.java",
    "content": "package md;\n\nimport com.github.houbb.markdown.toc.core.impl.AtxMarkdownToc;\n\npublic class mdToc {\n    public static void main(String[] args) {\n        String path = \"D:\\\\idea_project\\\\JavaTutorial\\\\docs\\\\java-web\\\\temp\";\n        AtxMarkdownToc.newInstance().genTocDir(path);\n    }\n}\n"
  }
]