<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>易哥</title>
    <link>https://yeecode.top/</link>
    <description>易哥，高级软件架构师，著有书籍《高性能架构之道》《通用源码阅读指导书》等。开源对象日志记录系统ObjectLogger、高性能轻量级分布式权限系统MatrixAuth。</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>zh-cn</language>
    <lastBuildDate>Wed, 07 Oct 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://yeecode.top/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Java多线程状态总结！</title>
      <link>https://yeecode.top/blog/142/</link>
      <pubDate>Wed, 07 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/142/</guid>
      
      <description>&lt;p&gt;搞定线程，第一步得把&amp;quot;线程到底有哪几种状态、怎么切换&amp;quot;弄清楚。Java 里线程一共五种状态。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;新建（New）：刚 new 出来的线程处于这个状态，此时线程有自己的内存空间。被触发后进入可运行状态。&lt;/li&gt;
&lt;li&gt;可运行（Runnable）：具备了运行条件，正等着 CPU 资源。拿到 CPU 后，转入运行中状态。&lt;/li&gt;
&lt;li&gt;运行中（Running）：占着 CPU，正在处理线程内的任务。可能因为任务结束转入结束状态；也可能因为等用户输入、等锁、等其他线程结束而进入阻塞状态。&lt;/li&gt;
&lt;li&gt;阻塞（Blocked）：释放了 CPU，要等某些条件满足后才能重新拿到 CPU。阻塞又能细分为三种子状态：
&lt;ul&gt;
&lt;li&gt;等待阻塞：对锁对象执行等待方法后，持有锁的线程会进入等待阻塞，直到等待时间到或被重新唤醒，才会进入同步阻塞；&lt;/li&gt;
&lt;li&gt;同步阻塞：需要拿到同步锁，才能进入可运行状态；&lt;/li&gt;
&lt;li&gt;其他阻塞：需要等用户输入、等其他线程执行结束等，之后转入可运行状态。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;结束（Dead）：线程已经执行结束。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;图{@fig:engse}给出了线程的状态转换图，并标出了状态转换对应的 Java 操作（图中 t 表示线程对象，Thread 表示线程类，o 代表对象）。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/142/pic/xiancheng_hu_3358928dd6d3e244.webp&#34; alt=&#34;线程状态转化图&#34; width=&#34;758&#34; height=&#34;368&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;{#fig:engse}&lt;/p&gt;
&lt;p&gt;上面这些状态里，只有处在&amp;quot;运行中&amp;quot;的线程才占 CPU。某个线程一旦阻塞，CPU 就腾出来给别的线程，既提升了 CPU 利用率，也就提升了程序的整体效率。每个线程还有个优先级属性：优先级高的线程拿到 CPU 的概率更大，但低优先级的线程也不是完全没机会。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;在 AI 编程越来越普及的今天，并发与多线程这些&amp;quot;底层硬功夫&amp;quot;并不会被取代，反而是写好高性能程序绕不开的内功。聊完 Java 线程的状态与转换，如果你觉得架构、并发这些话题有意思，强烈推荐一本书《高性能架构之道（第二版）》。&lt;/p&gt;
&lt;p&gt;这是一本理论联系实际的架构书，系统讲了怎么从顶层把软件架构好：覆盖分布式、并发编程、数据库调优、缓存、IO、高可用、前端性能优化等众多知识，最后还拿一个真实项目完整走了一遍架构全过程，把书里的方法真正落了地。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://union-click.jd.com/jdc?e=&amp;p=AyIGZRprFQASBl0SXBIyVlgNRQQlW1dCFFlQCxxKQgFHRE5XDVULR0UVABIGXRJcEh1LQglGaxFiGlEWTTtIYHIAS35dTnduVBFPO3UOHjdUK1sUAxIDXB1TFwIiN1Uca0NsEgZUGloUBxICVitaJQIVB1AcWhMEFwFcEl8lBRIOZUAOewQaVAcfDh0FEQVUGVklMiIEZStrFTIRNxd1DxwKRgBcSFpFAUJTUExTE1JBVFUeWxFVQVNSE19AUBA3VxpaEQs%253D&#34;&gt;&lt;img src=&#34;https://yeecode.top/img/promo/ha.jpg&#34; width=&#34;500&#34; height=&#34;333&#34; loading=&#34;lazy&#34;  alt=&#34;高性能架构之道（第二版）&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://union-click.jd.com/jdc?e=&amp;amp;p=AyIGZRprFQASBl0SXBIyVlgNRQQlW1dCFFlQCxxKQgFHRE5XDVULR0UVABIGXRJcEh1LQglGaxFiGlEWTTtIYHIAS35dTnduVBFPO3UOHjdUK1sUAxIDXB1TFwIiN1Uca0NsEgZUGloUBxICVitaJQIVB1AcWhMEFwFcEl8lBRIOZUAOewQaVAcfDh0FEQVUGVklMiIEZStrFTIRNxd1DxwKRgBcSFpFAUJTUExTE1JBVFUeWxFVQVNSE19AUBA3VxpaEQs%253D&#34;&gt;《高性能架构之道（第二版）》&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;这本书还在台湾地区出了繁体版，叫《巨型服务架构》——繁体版嘛，贵得有点离谱。&lt;/p&gt;
&lt;p&gt;就先聊到这儿。&lt;/p&gt;
&lt;p&gt;我是软件架构师易哥。可以关注我，我会偶尔冒个泡，分享点编程干货！&lt;/p&gt;</description>
    </item>
    <item>
      <title>什么是微服务系统？</title>
      <link>https://yeecode.top/blog/141/</link>
      <pubDate>Sat, 26 Sep 2026 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/141/</guid>
      
      <description>&lt;p&gt;在分布式应用里，每个子应用存在的意义，是完成分布式应用中的一部分功能——子应用和应用之间是严格的从属关系。但恰恰是这种严格的从属，可能造成性能浪费。&lt;/p&gt;
&lt;p&gt;举个例子：有个应用 A，包含三个子应用，A1 管订单、A2 管库存、A3 管金额核算。当我们要做销售金额核算（涉及订单管理和金额核算）时，得调用 A，而 A 下的 A2（库存）和这次操作无关，就这么闲置了。换句话说，应用 A 干活时，无关的那些子应用是闲着的，性能没发挥出来。&lt;/p&gt;
&lt;p&gt;这就好比商店只卖&amp;quot;汉堡+可乐+薯片&amp;quot;的固定套餐，你不要可乐，买这个套餐也是浪费。避免浪费的办法，是允许你自由组合着买。&lt;/p&gt;
&lt;p&gt;于是思路就变了：做销售金额核算时，直接调 A1 和 A3；做库存资产核算（涉及库存管理和金额核算）时，直接调 A2 和 A3。不再在子应用外面套一个应用 A，而是让各个子应用直接对外服务，外部调用者按需自由挑选。这就组成了微服务系统。&lt;/p&gt;
&lt;p&gt;在微服务集群里，每个微服务子应用都是完备的、可独立对外服务的，也能自由组合后对外服务，灵活性很高。图{@fig:uwufe}展示了微服务集群的示意。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/141/pic/weifuwu_hu_3a0ac3e4df199908.webp&#34; alt=&#34;微服务集群&#34; width=&#34;573&#34; height=&#34;364&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;{#fig:uwufe}&lt;/p&gt;
&lt;p&gt;每个微服务子应用对各类资源的依赖程度不同、被调用的频次也不同，因此我们可以针对每个微服务去做资源配置、集群配置，提升它们各自的时间效率、资源利用率和容量。&lt;/p&gt;
&lt;p&gt;在单体应用内部，任何模块都可能和其他模块耦合；而在微服务集群里，每个微服务内聚性高、和其他微服务的耦合很低。所以只要对外接口不变，微服务就能自由改内部逻辑，由独立团队开发、维护、升级，不用去了解别的微服务的实现细节。这有利于提升系统的成熟度、可用性、容错性和可恢复性。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;在 AI 帮我们快速拼出业务功能的今天，怎么把系统拆成边界清晰、能独立演进的服务，恰恰是衡量一个架构好坏的关键。聊完微服务系统的来龙去脉，如果你觉得架构这些话题有意思，强烈推荐一本书《高性能架构之道（第二版）》。&lt;/p&gt;
&lt;p&gt;这是一本理论联系实际的架构书，系统讲了怎么从顶层把软件架构好：覆盖分布式、并发编程、数据库调优、缓存、IO、高可用、前端性能优化等众多知识，最后还拿一个真实项目完整走了一遍架构全过程，把书里的方法真正落了地。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://union-click.jd.com/jdc?e=&amp;p=AyIGZRprFQASBl0SXBIyVlgNRQQlW1dCFFlQCxxKQgFHRE5XDVULR0UVABIGXRJcEh1LQglGaxFiGlEWTTtIYHIAS35dTnduVBFPO3UOHjdUK1sUAxIDXB1TFwIiN1Uca0NsEgZUGloUBxICVitaJQIVB1AcWhMEFwFcEl8lBRIOZUAOewQaVAcfDh0FEQVUGVklMiIEZStrFTIRNxd1DxwKRgBcSFpFAUJTUExTE1JBVFUeWxFVQVNSE19AUBA3VxpaEQs%253D&#34;&gt;&lt;img src=&#34;https://yeecode.top/img/promo/ha.jpg&#34; width=&#34;500&#34; height=&#34;333&#34; loading=&#34;lazy&#34;  alt=&#34;高性能架构之道（第二版）&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://union-click.jd.com/jdc?e=&amp;amp;p=AyIGZRprFQASBl0SXBIyVlgNRQQlW1dCFFlQCxxKQgFHRE5XDVULR0UVABIGXRJcEh1LQglGaxFiGlEWTTtIYHIAS35dTnduVBFPO3UOHjdUK1sUAxIDXB1TFwIiN1Uca0NsEgZUGloUBxICVitaJQIVB1AcWhMEFwFcEl8lBRIOZUAOewQaVAcfDh0FEQVUGVklMiIEZStrFTIRNxd1DxwKRgBcSFpFAUJTUExTE1JBVFUeWxFVQVNSE19AUBA3VxpaEQs%253D&#34;&gt;《高性能架构之道（第二版）》&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;这本书还在台湾地区出了繁体版，叫《巨型服务架构》——繁体版嘛，贵得有点离谱。&lt;/p&gt;
&lt;p&gt;就先聊到这儿。&lt;/p&gt;
&lt;p&gt;我是软件架构师易哥。可以关注我，我会偶尔冒个泡，分享点编程干货！&lt;/p&gt;</description>
    </item>
    <item>
      <title>什么是分布式系统？</title>
      <link>https://yeecode.top/blog/140/</link>
      <pubDate>Sat, 20 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/140/</guid>
      
      <description>&lt;p&gt;集群系统把单节点的并发请求分散到多个节点上，降低了每个节点的压力。它有个前提：各个节点是同质的，各跑一套完整且相同的应用。&lt;/p&gt;
&lt;p&gt;但应用是会长大的。功能越加越多、边界越扩越宽，最后往往长成一个包含众多模块的单体应用（也叫巨石应用）。这种应用跑在物理节点上，CPU、内存、IO 等资源一吃紧，性能就往下掉——而且这跟并发高不高无关，纯粹是系统自身太复杂了。&lt;/p&gt;
&lt;p&gt;除了效率，单体应用还带来开发、维护、可靠性上的一堆麻烦：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;业务复杂：模块多，彼此还耦合。新开发者上手就得先搞懂所有模块的业务逻辑；&lt;/li&gt;
&lt;li&gt;变更维护复杂：任何一点微小的升级，都要重新部署整个系统，外加一堆全量测试、回归测试；&lt;/li&gt;
&lt;li&gt;难以分拆升级：不同组件需要的资源不一样，但都被绑在一起，没法单独升级；&lt;/li&gt;
&lt;li&gt;可靠性变差：任何一个模块异常都可能拖垮整个应用，而模块众多又让恢复变得很慢。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;把这些问题的解法，就是把单体应用拆成多个子应用，让每个子应用单独部署到一台机器上。这一拆，单体应用就变成了分布式应用，如图{@fig:i00aasf}所示。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/140/pic/fenbushi00_hu_15cd972da0d33e77.webp&#34; alt=&#34;分布式应用&#34; width=&#34;468&#34; height=&#34;308&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;{#fig:i00aasf}&lt;/p&gt;
&lt;p&gt;分布式应用通过拆分子应用，把原本集中在一台机器上的压力分散到多台机器上，进一步提升了承载能力；同时让内部模块之间解耦，子应用可以独立地开发、部署、升级、维护。&lt;/p&gt;
&lt;p&gt;当然，分布式应用也要面对分布式一致性问题。比如一个分布式系统里，子应用 A1 管订单、A2 管库存，外部购买请求到达后，如果 A1 完成了订单生成，A2 就得完成库存扣减——两个子应用的状态变化也得是一致的。具体怎么实现，和我们在&amp;quot;信息一致的节点集群&amp;quot;里介绍的方法一样，这里不再赘述。&lt;/p&gt;
&lt;p&gt;实际生产里，建议优先把大的单体应用拆成分布式系统；等拆出来的某个子应用因为并发过高又遇到瓶颈时，再把它按需部署成集群，如图{@fig:i0145s}所示。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/140/pic/fenbushi01_hu_7c4595f1207d2894.webp&#34; alt=&#34;分布式集群&#34; width=&#34;468&#34; height=&#34;308&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;{#fig:i0145s}&lt;/p&gt;
&lt;p&gt;这种&amp;quot;先拆分布式、再按需上集群&amp;quot;的演进方式，让每个子应用能针对自身的制约因素去扩展——有的要扩计算、有的要扩存储、有的暂时不用扩，避免了直接扩展大单体造成的资源浪费，更合理、也更高效。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;在 AI 编程越来越能替我们写完业务代码的今天，怎么把一个庞杂的系统拆分、部署、扩展得既稳又省，反而成了更值钱的能力。聊完分布式系统的来龙去脉，如果你觉得架构这些话题有意思，强烈推荐一本书《高性能架构之道（第二版）》。&lt;/p&gt;
&lt;p&gt;这是一本理论联系实际的架构书，系统讲了怎么从顶层把软件架构好：覆盖分布式、并发编程、数据库调优、缓存、IO、高可用、前端性能优化等众多知识，最后还拿一个真实项目完整走了一遍架构全过程，把书里的方法真正落了地。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://union-click.jd.com/jdc?e=&amp;p=AyIGZRprFQASBl0SXBIyVlgNRQQlW1dCFFlQCxxKQgFHRE5XDVULR0UVABIGXRJcEh1LQglGaxFiGlEWTTtIYHIAS35dTnduVBFPO3UOHjdUK1sUAxIDXB1TFwIiN1Uca0NsEgZUGloUBxICVitaJQIVB1AcWhMEFwFcEl8lBRIOZUAOewQaVAcfDh0FEQVUGVklMiIEZStrFTIRNxd1DxwKRgBcSFpFAUJTUExTE1JBVFUeWxFVQVNSE19AUBA3VxpaEQs%253D&#34;&gt;&lt;img src=&#34;https://yeecode.top/img/promo/ha.jpg&#34; width=&#34;500&#34; height=&#34;333&#34; loading=&#34;lazy&#34;  alt=&#34;高性能架构之道（第二版）&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://union-click.jd.com/jdc?e=&amp;amp;p=AyIGZRprFQASBl0SXBIyVlgNRQQlW1dCFFlQCxxKQgFHRE5XDVULR0UVABIGXRJcEh1LQglGaxFiGlEWTTtIYHIAS35dTnduVBFPO3UOHjdUK1sUAxIDXB1TFwIiN1Uca0NsEgZUGloUBxICVitaJQIVB1AcWhMEFwFcEl8lBRIOZUAOewQaVAcfDh0FEQVUGVklMiIEZStrFTIRNxd1DxwKRgBcSFpFAUJTUExTE1JBVFUeWxFVQVNSE19AUBA3VxpaEQs%253D&#34;&gt;《高性能架构之道（第二版）》&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;这本书还在台湾地区出了繁体版，叫《巨型服务架构》——繁体版嘛，贵得有点离谱。&lt;/p&gt;
&lt;p&gt;就先聊到这儿。&lt;/p&gt;
&lt;p&gt;我是软件架构师易哥。可以关注我，我会偶尔冒个泡，分享点编程干货！&lt;/p&gt;</description>
    </item>
    <item>
      <title>服务集群解决方案大整理！</title>
      <link>https://yeecode.top/blog/139/</link>
      <pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/139/</guid>
      
      <description>&lt;p&gt;很多人一听到&amp;quot;集群&amp;quot;，脑子里浮现的就是&amp;quot;多部署几台机器&amp;quot;。这话没错，但不完整。集群系统的本质，是把原本落在一个节点上的请求，通过反向代理这类手段，分摊到多个同质节点上——这些节点配置一样、跑的程序一样，一起对外提供服务。系统结构如图{@fig:jiasd}所示。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/139/pic/dengjia_hu_95c76c9d6773fde0.webp&#34; alt=&#34;集群系统&#34; width=&#34;427&#34; height=&#34;348&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;{#fig:jiasd}&lt;/p&gt;
&lt;p&gt;但&amp;quot;多部署几台&amp;quot;马上带来一个绕不开的问题：同一个用户先后发出的多个请求，可能落在不同的节点上，服务的连贯性就被打破了。比如用户先发 R1、再发 R2，而 R2 要靠 R1 留下的信息才能跑（R1 触发了一个任务，R2 来查这个任务的结果）。要是 R1、R2 被分到不同节点，R2 自然就查不到。&lt;/p&gt;
&lt;p&gt;为了解决这个问题，业界沉淀出了几种典型的集群方案，我们挨个看。&lt;/p&gt;
&lt;h3 id=&#34;无状态的节点集群&#34;&gt;无状态的节点集群&lt;/h3&gt;
&lt;p&gt;最容易从单节点扩展到多节点的，是无状态系统。所谓无状态节点，是说用户先后发 R1、R2 两个请求，不管它们是不是落到同一个节点，R2 拿到的都应该是一样的结果——节点给出什么答复，和它之前有没有收到过 R1 完全无关。&lt;/p&gt;
&lt;p&gt;要让系统满足无状态，前提是它所有的接口都是&amp;quot;幂等类&amp;quot;接口，也就是调用前后系统状态不发生任何改变。显然，只有查询类接口才能做到这一点。&lt;/p&gt;
&lt;p&gt;不过，就算是一堆无状态节点凑成的系统，也会遇到协作问题，最典型的就是&amp;quot;并行唤醒&amp;quot;。比如我们要给这个无状态集群加个定时功能：每天凌晨对外发一封邮件。结果你会发现，集群里每个节点都会在凌晨被同时唤醒，然后各自发一封邮件——因为所有节点同质、跑的程序一致。&lt;/p&gt;
&lt;p&gt;解决办法是用&amp;quot;外部请求唤醒&amp;quot;：在指定时刻由外部系统发一个请求来触发定时任务。这个请求最终只会交给一个节点处理，于是就实现了&amp;quot;只发一封&amp;quot;的独立唤醒。&lt;/p&gt;
&lt;p&gt;无状态节点集群设计简单、扩展方便，但只适合本身就能满足无状态要求的系统，应用范围比较受限。&lt;/p&gt;
&lt;h3 id=&#34;单一服务节点集群&#34;&gt;单一服务节点集群&lt;/h3&gt;
&lt;p&gt;很多服务是有状态的：用户的历史请求在系统里攒成了上下文，系统必须结合上下文来回应。比如聊天系统里用户之前的对话、游戏里用户之前买的装备和等级，都是上下文。&lt;/p&gt;
&lt;p&gt;单一节点的系统里，把每个用户的信息都存这一台上就行；到了集群里就麻烦了。最简单的一招，是在&amp;quot;用户&amp;quot;和&amp;quot;节点&amp;quot;之间建立对应关系：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;任意用户都对应一个固定节点，这个节点上存着该用户的上下文；&lt;/li&gt;
&lt;li&gt;用户的请求永远落到与之对应的节点上。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;图{@fig:ingdd}展示了这种对应关系。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/139/pic/duiying_hu_787220d0d204497a.webp&#34; alt=&#34;用户与指定节点对应&#34; width=&#34;537&#34; height=&#34;387&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;{#fig:ingdd}&lt;/p&gt;
&lt;p&gt;这种系统的特点是各个节点相互隔离：同样的代码、同样的配置，却各自守着不同用户的上下文，服务自己对应的那批用户。对用户来说，服务他的始终就是那一个节点，所以我们叫它&amp;quot;单一服务节点集群&amp;quot;。&lt;/p&gt;
&lt;p&gt;建立和维护这种对应关系，常见的做法有几种：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;注册时让用户自己选节点（很多游戏服务这么干）；&lt;/li&gt;
&lt;li&gt;注册时按用户所处网络分配节点（部分邮件服务这么干）；&lt;/li&gt;
&lt;li&gt;注册时按用户 id 分配节点（不少聊天系统这么干）；&lt;/li&gt;
&lt;li&gt;登录时随机或按规则分配节点，把结果写进 cookie，之后按 cookie 把请求路由到指定节点。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;最后一种和前面几种略有不同：前面几种能保证&amp;quot;用户→节点&amp;quot;的绑定在整个用户生命周期内不变，最后一种只保证在一次会话周期内不变，适合两次会话之间本就没有上下文关系的场景，比如登录系统、权限系统。&lt;/p&gt;
&lt;p&gt;无论哪种方式，都保证了会话过程中用户对应的节点不变，系统只要在会话里把请求路由过去即可，路由由反向代理、规则中心等组件完成。&lt;/p&gt;
&lt;p&gt;单一服务节点集群能解决有状态服务的问题，但因为节点彼此隔离、无法互相备份，一旦某个节点崩溃，它名下的用户就失去了服务——容错性比较差。&lt;/p&gt;
&lt;h3 id=&#34;信息共享的节点集群&#34;&gt;信息共享的节点集群&lt;/h3&gt;
&lt;p&gt;有没有既能解决有状态问题，又不会因单点崩溃而让用户失服的方案？有，就是信息共享的节点集群。在这种集群里，所有节点连到一个公共的信息池，用户的上下文信息都存在这个池子里。系统结构如图{@fig:izdiaa}所示。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/139/pic/chizi_hu_9ddab290abad737c.webp&#34; alt=&#34;信息共享的节点集群&#34; width=&#34;362&#34; height=&#34;370&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;{#fig:izdiaa}&lt;/p&gt;
&lt;p&gt;这是把单节点系统扩成多节点系统最常用的一招。通常服务节点和信息池分开部署在不同机器上，加节点就能扩集群。&lt;/p&gt;
&lt;p&gt;数据库就常当作信息池用：任意节点收到请求，都从库里读该用户的上下文，处理完再把新状态写回库。除了传统数据库，也可以是别的信息池——比如用 Redis 把信息池当共享内存，存用户的 Session 信息。&lt;/p&gt;
&lt;p&gt;在信息共享的集群里，每个节点都从信息池读写用户状态，所以对用户而言每个节点都是等价的，请求落谁都一样。&lt;/p&gt;
&lt;p&gt;节点间信息互通，就能用分布式锁解决&amp;quot;并行唤醒&amp;quot;这类协作问题。一个简单的做法是：定时任务触发时，每个节点都以同一个键往信息池里写一个&amp;quot;不允许覆盖&amp;quot;的数据，最终只有一个节点能写成功，它拿到执行权。&lt;/p&gt;
&lt;p&gt;信息共享的节点集群靠加节点提升了运算能力，但因为多个节点共享同一个信息池，受信息池容量和读写性能制约，系统在存储容量、数据吞吐上的提升并不明显。&lt;/p&gt;
&lt;h3 id=&#34;信息一致的节点集群&#34;&gt;信息一致的节点集群&lt;/h3&gt;
&lt;p&gt;信息共享的集群，运算能力分散在各个节点，存储能力却集中在信息池——信息池成了故障单点和性能瓶颈。&lt;/p&gt;
&lt;p&gt;要避免这个瓶颈，可以让每个节点各自拥有一个信息池。但为了保持有状态服务，又必须保证各个信息池里的数据是一致的，如图{@fig:i02bb}所示。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/139/pic/chizi02_hu_2321dce61c6885d.webp&#34; alt=&#34;信息一致的节点集群&#34; width=&#34;351&#34; height=&#34;393&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;{#fig:i02bb}&lt;/p&gt;
&lt;p&gt;这种&amp;quot;信息一致的节点集群&amp;quot;常被叫作分布式系统，但严格说它仍是集群——因为分布式系统的节点是异构的，分属不同模块；而这里的节点是同构的，出现的目的只是为了分担高并发带来的压力。不过，它也得面对分布式系统常遇到的那个老问题：分布式一致性。&lt;/p&gt;
&lt;p&gt;一致性要求：在系统的某个节点上做了变更，经过&amp;quot;一定时间&amp;quot;后，能从每个节点上读到这个变更。&lt;/p&gt;
&lt;p&gt;我们用图{@fig:zhiassdf}的例子直观看看一致性问题。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/139/pic/bingxyizhi_hu_e8524a432ef5608e.webp&#34; alt=&#34;分布式一致性示意图&#34; width=&#34;373&#34; height=&#34;321&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;{#fig:zhiassdf}&lt;/p&gt;
&lt;p&gt;图里调用方先通过节点集群把变量 a 设成 5，再去读 a，结果读到的却是 3。这完全可能发生，因为两次读写可能访问的是两个节点，只要节点间信息不同步、或同步有时延，就会出现。&lt;/p&gt;</description>
    </item>
    <item>
      <title>并行和并发并不相同！</title>
      <link>https://yeecode.top/blog/138/</link>
      <pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/138/</guid>
      
      <description>&lt;p&gt;并行和并发，是我们在架构设计与软件开发中经常涉及的概念，不过两者的意义并不完全相同。&lt;/p&gt;
&lt;p&gt;并行（parallelism）是指在同一时刻有多个任务同时进行。例如，你在家中一边读书一边听歌，&amp;ldquo;既读书又听歌&amp;quot;描述的就是并行，因为读书和听歌这两件事是同时发生的，如图{@fig:xsing}所示。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/138/pic/bingxing_hu_25fdd57ba56f303d.webp&#34; alt=&#34;并行示意图&#34; width=&#34;568&#34; height=&#34;475&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;{#fig:xsing}&lt;/p&gt;
&lt;p&gt;并发（concurrency）是指多个任务中的每个任务都被拆分成细小的任务片，从属于不同任务的任务片被轮番处理。因此，任意时刻都只有一个任务在进行，但从宏观上看，这些任务像是被同时处理的。例如，你在家中一边读书一边看综艺，&amp;ldquo;既读书又看综艺&amp;quot;描述的就是并发，因为在任意时刻，你要么在低头读书，要么在抬头看综艺，这两件事实际是交替进行的，如图{@fig:ngfa}所示。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/138/pic/bingfa_hu_f3da72149cfeda22.webp&#34; alt=&#34;并发示意图&#34; width=&#34;568&#34; height=&#34;475&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;{#fig:ngfa}&lt;/p&gt;
&lt;p&gt;所以说，并行是一种真正意义上的&amp;quot;并&amp;rdquo;，而并发只是宏观表现上的&amp;quot;并&amp;rdquo;。&lt;/p&gt;
&lt;p&gt;不过要注意，并发和并行的区分仅限于微观上。从宏观上看，并发或并行的任务都像是同时开展的。以并发请求为例，在同一时刻向某个系统发送大量请求，这些请求几乎会被同时处理；而具体这些请求在系统内部是&amp;quot;并行&amp;quot;处理还是&amp;quot;并发&amp;quot;处理的，无法从系统外部判断出来。所以宏观上常常把&amp;quot;并行&amp;quot;和&amp;quot;并发&amp;quot;统称为&amp;quot;并发&amp;quot;。&lt;/p&gt;
&lt;p&gt;后面我们会介绍一些使用并行和并发来提升软件性能的手段，而区分好这两个概念是基础。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;在 AI 编程迅猛发展的今天，编码能力逐步弱化，而架构能力则成了开发者更核心的竞争力。聊完并行与并发这对常被混淆的概念，如果你还想深入&amp;quot;单节点如何拆成多节点并行&amp;quot;的架构设计，推荐一本书《高性能架构之道（第二版）》。&lt;/p&gt;
&lt;p&gt;这是一本理论联系实际的架构书，系统讲了怎么从顶层把软件架构好：覆盖分布式、并发编程、数据库调优、缓存、IO、高可用、前端性能优化等众多知识，最后还拿一个真实项目完整走了一遍架构全过程，把书里的方法真正落了地。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://union-click.jd.com/jdc?e=&amp;p=AyIGZRprFQASBl0SXBIyVlgNRQQlW1dCFFlQCxxKQgFHRE5XDVULR0UVABIGXRJcEh1LQglGaxFiGlEWTTtIYHIAS35dTnduVBFPO3UOHjdUK1sUAxIDXB1TFwIiN1Uca0NsEgZUGloUBxICVitaJQIVB1AcWhMEFwFcEl8lBRIOZUAOewQaVAcfDh0FEQVUGVklMiIEZStrFTIRNxd1DxwKRgBcSFpFAUJTUExTE1JBVFUeWxFVQVNSE19AUBA3VxpaEQs%253D&#34;&gt;&lt;img src=&#34;https://yeecode.top/img/promo/ha.jpg&#34; width=&#34;500&#34; height=&#34;333&#34; loading=&#34;lazy&#34;  alt=&#34;高性能架构之道（第二版）&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://union-click.jd.com/jdc?e=&amp;amp;p=AyIGZRprFQASBl0SXBIyVlgNRQQlW1dCFFlQCxxKQgFHRE5XDVULR0UVABIGXRJcEh1LQglGaxFiGlEWTTtIYHIAS35dTnduVBFPO3UOHjdUK1sUAxIDXB1TFwIiN1Uca0NsEgZUGloUBxICVitaJQIVB1AcWhMEFwFcEl8lBRIOZUAOewQaVAcfDh0FEQVUGVklMiIEZStrFTIRNxd1DxwKRgBcSFpFAUJTUExTE1JBVFUeWxFVQVNSE19AUBA3VxpaEQs%253D&#34;&gt;《高性能架构之道（第二版）》&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;这本书还在台湾地区出了繁体版，叫《巨型服务架构》——繁体版嘛，贵得有点离谱。&lt;/p&gt;
&lt;p&gt;就先聊到这儿。&lt;/p&gt;
&lt;p&gt;我是软件架构师易哥。可以关注我，我会偶尔冒个泡，分享点编程干货！&lt;/p&gt;</description>
    </item>
    <item>
      <title>什么是反向代理？它有哪几类？</title>
      <link>https://yeecode.top/blog/137/</link>
      <pubDate>Sun, 05 Apr 2026 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/137/</guid>
      
      <description>&lt;p&gt;经过内容分发网络或者多地址直连方式的分流后，用户的请求已经分散到了不同的系统上。这里所说的一个系统，是指它对外呈现唯一的 IP 地址。&lt;/p&gt;
&lt;p&gt;通常来说，一个确定的 IP 地址会代表一台确定的机器（节点），但这不是绝对的。通过代理，可以让多个节点对外表现出唯一的 IP 地址。&lt;/p&gt;
&lt;p&gt;代理是软件架构和网络设计里都常接触的概念。例如，用户端可以设置代理服务器，让所有请求都由代理服务器发出。这样一来，从外部看，所有请求都由代理服务器发出，无法判断它背后代理了多少用户端。这种代理方式叫作正向代理，如图{@fig:zaili}所示。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/137/pic/zhengxiangdaili_hu_6aadc5e55a3451c9.webp&#34; alt=&#34;正向代理示意图&#34; width=&#34;407&#34; height=&#34;318&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;{#fig:zaili}&lt;/p&gt;
&lt;p&gt;同样的，服务端也可以设置代理服务器。所有请求都由代理服务器接收，再由它分发给后方的服务器。这样一来，从外部看，所有请求都由代理服务器处理，无法判断它后方到底有多少服务器。这种代理方式叫作反向代理，如图{@fig:faili}所示。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/137/pic/fanxiangdaili_hu_f03b3927e9701b45.webp&#34; alt=&#34;反向代理示意图&#34; width=&#34;445&#34; height=&#34;317&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;{#fig:faili}&lt;/p&gt;
&lt;p&gt;反向代理可以用来对请求进行分流，把请求分流到系统内部的多个节点上，从而减少每个节点的并发数。而这些节点在外界看来是一个系统，表现出唯一确定的 IP。&lt;/p&gt;
&lt;p&gt;实现反向代理的手段有很多，可以根据它工作的层级不同分为两类：四层反向代理和七层反向代理。这里说的层级，是 OSI 参考模型的层级。OSI 参考模型把通信功能划分为七个层次，每一层向相邻上层提供一套确定的服务，并使用相邻下层提供的服务实现当前层级的功能和协议。OSI 参考模型的层级划分和各层功能如图{@fig:fossi}所示。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/137/pic/osi_hu_39ddfe0667dad94b.webp&#34; alt=&#34;OSI参考模型&#34; width=&#34;678&#34; height=&#34;416&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;{#fig:fossi}&lt;/p&gt;
&lt;p&gt;在 OSI 参考模型中，第四层是传输层，TCP 协议和 UDP 协议等工作在这一层；第七层是应用层，HTTP 协议和 FTP 协议等工作在这一层。了解这些之后，就能理清楚四层和七层反向代理的工作原理：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;四层反向代理：可以根据用户请求的 IP 和端口号进行转发。&lt;/li&gt;
&lt;li&gt;七层反向代理：可以根据 FTP 请求、HTTP 请求中的具体内容进行转发，例如根据 HTTP 请求的请求方法、URL、请求首部、请求正文等信息进行转发。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;四层反向代理处在 OSI 协议的更底层，掌握的信息更少，因此实现原理更简单，运行效率也更高。七层反向代理工作在应用层，能收集到更多信息，因此可以做得更智能。例如，它可以分析请求的 cookie 信息，针对不同 cookie 做不同处理；也可以分析请求的 URL，把针对特殊资源（例如图像资源）的请求转发到特定服务器上。当然，获取全面信息的代价是牺牲效率，相比于四层反向代理，七层反向代理实现更复杂、效率也更低。&lt;/p&gt;
&lt;p&gt;在对系统请求进行内部分流时，七层反向代理应用得最为广泛。因为它可以对外表现出完全一致的 IP 地址和端口，再根据请求的具体内容分流。Nginx 就是一种典型的七层反向代理软件。下面以 Nginx 为例，简要介绍反向代理的实现。&lt;/p&gt;
&lt;p&gt;使用 Nginx 搭建反向代理时，主要用它的 upstream 模块。upstream 即&amp;quot;上游&amp;quot;的意思，指 Nginx 后方的服务节点，Nginx 只需要把请求以某种策略转发给后方节点即可。&lt;/p&gt;
&lt;p&gt;利用 Nginx 的转发策略，可以实现后方服务节点间的负载均衡。Nginx 支持的策略有轮询、加权轮询、请求源 IP 地址哈希，此外还支持用户自己扩展策略。其中轮询是指平均地为后方节点分配请求；加权轮询是指按一定比例分配；请求源 IP 地址哈希是指根据请求来源 IP 的哈希结果分配。这种哈希策略有个优点：只要请求方 IP 不变，服务它的后方节点也不变，这有利于维护 Session 等信息。&lt;/p&gt;</description>
    </item>
    <item>
      <title>什么是CDN？它的工作原理是怎样的？</title>
      <link>https://yeecode.top/blog/136/</link>
      <pubDate>Sat, 04 Apr 2026 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/136/</guid>
      
      <description>&lt;p&gt;我们部署的软件系统处在互联网的某个节点上，图{@fig:cdnnet}展示了这种情况，通常这个节点由一个 IP 地址标定。所有指向该系统的请求，都会在网络上经过多次路由后到达该节点。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/136/pic/cdnnet_hu_6fcc2ba3154bb82c.webp&#34; alt=&#34;网络上的系统&#34; width=&#34;554&#34; height=&#34;479&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;{#fig:cdnnet}&lt;/p&gt;
&lt;p&gt;如果我们在网络的多个位置都部署这套系统，就可以把请求分散到多个系统上，减少每个系统承担的并发请求数。不仅如此，还可以让每个用户的请求落到离他最近的一个系统上，这样便降低了网络延时，从而减小平均响应时间，可谓一举两得。&lt;/p&gt;
&lt;p&gt;这里所说的&amp;quot;最近&amp;quot;，不是地理位置上的最近，而是网络拓扑结构上的&amp;quot;最近&amp;quot;。在网络拓扑中，一个节点到另一个节点的距离，会根据网络负载情况、内容的可用性、设备工作状况等实时变化，所以这种&amp;quot;最近&amp;quot;的关系是动态变动的。&lt;/p&gt;
&lt;p&gt;但上面这套思路会带来一个严重的维护问题：如果同一个系统被部署到不同位置，我们就需要同时部署和维护不同位置的多个系统，系统间协作成本、部署维护成本都会很高。因此在实际生产中，通常的做法是把图片、视频、附件这类流量消耗大、又不经常改变的静态资源部署到互联网的多个位置，而核心系统只部署在一个位置。这就构成了我们常见的内容分发网络（Content Delivery Network，简称 CDN）。&lt;/p&gt;
&lt;h2 id=&#34;内容分发网络的结构&#34;&gt;内容分发网络的结构&lt;/h2&gt;
&lt;p&gt;内容分发网络的组成如图{@fig:tpic}所示。在 CDN 中，部署核心系统的节点被称为源站，它是所有静态、动态信息的来源；而部署图片、视频、附件等静态资源的节点，叫作 CDN 节点、缓存节点或者边缘节点。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/136/pic/cdnnetpic_hu_505e85850d65d872.webp&#34; alt=&#34;内容分发网络示意图&#34; width=&#34;608&#34; height=&#34;479&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;{#fig:tpic}&lt;/p&gt;
&lt;p&gt;边缘节点是相对于复杂的网络结构提出的一个概念，它是指与接入用户之间具有较少中间环节的网络节点。因此，相比于源站，用户请求到达边缘节点的网络延迟更小。&lt;/p&gt;
&lt;p&gt;当用户请求边缘节点的内容时，边缘节点会先判断自身是否已经缓存了用户要请求的内容。如果该内容已经缓存，边缘节点就直接把内容返回给用户；如果尚未缓存，边缘节点会去源站请求内容再发给用户，并且还会根据设置决定是否在自身也缓存一份。所以，边缘节点中存储的只是源站中部分内容的备份。&lt;/p&gt;
&lt;p&gt;边缘节点（也就是 CDN 节点）的数目，对内容分发网络的效率有较大的影响。节点数目越多，用户与最近的 CDN 节点之间的中间环节越少，通讯时延也就越小。但在互联网上部署众多 CDN 节点需要极大的成本，因此 CDN 节点多由专门的 CDN 服务商部署并对外提供服务。&lt;/p&gt;
&lt;p&gt;内容分发网络有以下几个优点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;减小了系统并发数。系统不再是网络上的一个节点，而是多个节点，每个系统只承担一部分请求，减少了各自的并发数。&lt;/li&gt;
&lt;li&gt;减少了平均响应时间。用户请求会被分配到最近的节点上，减小了网络延迟，也就缩短了平均响应时间。&lt;/li&gt;
&lt;li&gt;减少了网络拥堵。每个用户的请求都被分配到最近的节点上，无须经过骨干网络，减轻了对骨干网络的压力。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;不过 CDN 节点中只能缓存静态内容，一些涉及动态内容的请求仍然需要源站处理。通常我们会通过 CDN 控制中心为各个 CDN 节点配置规则列表，节点会根据规则对请求内容进行分类：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对于静态内容：CDN 节点从源站获取到该内容后，会在自身缓存一定的时间；之后再次收到针对同样内容的请求时，直接返回缓存内容。&lt;/li&gt;
&lt;li&gt;对于动态内容：CDN 节点不做缓存，而是直接将请求转交给源站处理。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;内容分发网络为源站分担了大量静态资源请求，降低了源站的并发请求数，使得同样软硬件配置的源站可以承担更多的外部并发。&lt;/p&gt;
&lt;h2 id=&#34;内容分发网络的原理&#34;&gt;内容分发网络的原理&lt;/h2&gt;
&lt;p&gt;调用方请求一个服务时给出的目的地址是源站的地址，对应网络中源站所在的节点。那么 CDN 是如何把指向源站地址的请求，分散到不同 CDN 节点上的呢？这是 CDN 要解决的一个核心问题，也就是把发往源站的请求拦截给 CDN 节点。这一过程，和域名解析有关。&lt;/p&gt;
&lt;p&gt;域名解析，就是把域名解析为 IP 地址的过程。IP 地址标志了网络中一个节点的位置，通过它可以在网络中寻址找到对应节点，例如&amp;quot;185.199.108.153&amp;quot;就是一个 IP 地址；域名则是为了让 IP 地址更容易被记住而设的代称，例如&amp;quot;yeecode.top&amp;quot;就是一个网站域名。&lt;/p&gt;
&lt;p&gt;域名解析由域名系统（Domain Name System，简称 DNS）来完成，可以把它看作一个保存了域名和 IP 地址对应关系的数据库；而域名解析，就是从这个数据库中查找某个域名对应 IP 地址的过程。&lt;/p&gt;</description>
    </item>
    <item>
      <title>什么是软件的性能？该怎么衡量与优化？</title>
      <link>https://yeecode.top/blog/135/</link>
      <pubDate>Sat, 21 Feb 2026 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/135/</guid>
      
      <description>&lt;p&gt;我们写代码、聊架构的时候，经常把&amp;quot;高性能&amp;quot;三个字挂在嘴边。但你要是真被人问一句&amp;quot;高性能到底指什么&amp;quot;，不少人会愣一下。今天咱们就把这个词掰开揉碎，看看它背后到底对应着软件质量的哪些维度，以及我们平时是怎么衡量、又该怎么去优化它的。&lt;/p&gt;
&lt;h2 id=&#34;什么是软件的性能&#34;&gt;什么是软件的性能&lt;/h2&gt;
&lt;p&gt;先说一个反直觉的点：在软件工程那套权威的《ISO/IEC 25010》质量模型里，其实压根没有&amp;quot;性能&amp;quot;这个特性维度。&lt;/p&gt;
&lt;p&gt;那&amp;quot;性能&amp;quot;是从哪来的？它其实是个俗称，主要对应着软件质量模型里的两个大维度：效率（efficiency）和可靠性（reliability）。一个软件如果在效率和可靠性上都表现不错，我们才会说它是&amp;quot;高性能&amp;quot;的。&lt;/p&gt;
&lt;p&gt;具体点说，在 ISO 25010 里，效率又拆成了三个子特性：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;时间效率（Time behaviour）：执行功能时，响应时间、处理时间、吞吐率达标的情况。&lt;/li&gt;
&lt;li&gt;资源利用率（Resource utilization）：执行功能时，占用的资源数量和类型达不达标。&lt;/li&gt;
&lt;li&gt;容量（Capacity）：系统各项参数的最大上限达不达标。比如能存多少条数据、撑多少并发用户、通信带宽多大、事务吞吐多少、数据库多大。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;而可靠性则拆成了四个子特性：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;成熟度（Maturity）：正常操作下满足可靠性需求的程度。&lt;/li&gt;
&lt;li&gt;可用性（Availability）：要用的时候，系统可操作、可访问的程度。&lt;/li&gt;
&lt;li&gt;容错性（Fault tolerance）：哪怕硬件或软件出了故障，系统还能按预期跑的程度。&lt;/li&gt;
&lt;li&gt;可恢复性（Recoverability）：出了中断或故障，能不能把受影响的数据恢复回来、把系统状态重新建起来。比如故障后计算机要关一阵子，关多久，就看可恢复性。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以，&amp;ldquo;高性能&amp;quot;的软件，得在这七个子特性上都有好表现。说大白话就是：处理能力强、响应快、容量大、故障率低、不容易崩、崩了恢复也快。&lt;/p&gt;
&lt;p&gt;为什么要死磕高性能？因为咱们国家的互联网用户群体太庞大了。海量的软件——尤其是互联网软件——得扛住巨量用户的访问：更短时间响应用户操作、用更少的资源服务更多人、存下更多用户的数据。这对软件的性能，提出了极高的要求。&lt;/p&gt;
&lt;h2 id=&#34;怎么衡量软件的性能&#34;&gt;怎么衡量软件的性能&lt;/h2&gt;
&lt;p&gt;聊完了&amp;quot;高性能&amp;quot;到底指什么，接下来我们逐个认识那些被归到&amp;quot;性能&amp;quot;名下的具体指标。日常里衡量软件性能，最常被提起的就是下面这三个。&lt;/p&gt;
&lt;h3 id=&#34;吞吐量&#34;&gt;吞吐量&lt;/h3&gt;
&lt;p&gt;&amp;ldquo;吞吐量&amp;quot;这个词最早是从电信网络那边借来的，指的是在一条通信信道上，单位时间里能成功传送的平均数据量。挪到软件系统里，它的意思也差不多：软件系统在单位时间里，能接收和发出多少数据量。&lt;/p&gt;
&lt;p&gt;不过要注意，吞吐量是个挺&amp;quot;模糊&amp;quot;的概念。不同系统、不同请求，对应的数据量不一样，操作复杂度也不一样，所以很难拿吞吐量去横向比较两个不同的系统。&lt;/p&gt;
&lt;p&gt;日常里，我们更常用几个更具体的指标来代表吞吐量，最典型的就是 TPS 和 QPS：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;TPS（Transaction Per Second）：每秒处理的事务数。这里的事务，一般指一次完整的操作，包含请求、变更、返回等全流程。&lt;/li&gt;
&lt;li&gt;QPS（Queries Per Second）：每秒进行的查询数。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;TPS 和 QPS 是有关联的，但这个关联并不固定。比如我们把&amp;quot;呈现一个页面&amp;quot;当成一个事务，那一个 TPS 里往往就包了好几个 QPS——因为一个页面通常要查 HTML、CSS、图片等好几个资源。所以 TPS 和 QPS 之间怎么换算，得看具体的业务场景。&lt;/p&gt;
&lt;p&gt;也正因为不同系统处理的事务、查询的内容千差万别，TPS 和 QPS 很难用来精确比出两个系统谁强谁弱；它们更多是用来衡量&amp;quot;同一个系统&amp;quot;在不同时间、不同环境下的性能变化。&lt;/p&gt;
&lt;p&gt;正因为吞吐量这么&amp;quot;虚&amp;rdquo;，本书后面统一用&amp;quot;吞吐量&amp;quot;这个词，来代表系统处理用户请求的能力。&lt;/p&gt;
&lt;h3 id=&#34;并发数&#34;&gt;并发数&lt;/h3&gt;
&lt;p&gt;说完吞吐量，另一个高频词就是&amp;quot;并发数&amp;quot;了。不过&amp;quot;并发数&amp;quot;和吞吐量一样，也是个挺宽泛的概念，它底下其实包含了好几样东西：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;并发用户数：同时使用软件功能的用户人数。但这里面，有些用户可能只是登录了，并没真在操作。&lt;/li&gt;
&lt;li&gt;并发连接数：软件承载的连接数量。这些连接里，有些正在传数据，有些仅仅只是保持着连接。&lt;/li&gt;
&lt;li&gt;并发请求数：软件同时扛着的请求数量。这些请求里，有的可能只是要个静态资源，有的可能要做读写操作。&lt;/li&gt;
&lt;li&gt;并发线程数：这是衡量系统内部运行情况的指标，指软件内部跑着的线程数。不同业务操作，触发的线程数也不一样。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;你看，上面这四样，没有哪一个是绝对清晰、绝对准确的——它们各自都有&amp;quot;含混&amp;quot;的地方。&lt;/p&gt;
&lt;p&gt;所以本书同样做了个统一约定：不再去纠结某一种具体的并发数，而是统一用&amp;quot;并发数&amp;quot;这个词，来代表系统同时服务的调用方有多少。&lt;/p&gt;
&lt;h3 id=&#34;平均响应时间&#34;&gt;平均响应时间&lt;/h3&gt;
&lt;p&gt;前面讲的吞吐量和并发数，都是衡量软件系统的重要指标。但站在一个&amp;quot;用户&amp;quot;的角度——这个用户可能是人，也可能是另一个系统——他其实根本感知不到这两个指标。用户作为一个个体，既不知道系统的吞吐量高低，也不知道此刻并发数是多少。用户能感受到的，是另一个指标：响应时间。&lt;/p&gt;
&lt;p&gt;响应时间，指的是用户发出调用请求，到收到系统回应，这中间花的时间。这个时间越短，体验就越好。&lt;/p&gt;
&lt;p&gt;放大到整个系统上，对应的指标就是平均响应时间，也就是系统服务的所有请求的响应时间的平均值。&lt;/p&gt;
&lt;p&gt;系统内部往往由很多模块组成，每个模块也都有自己的响应时间。我们想提升系统的平均响应时间，通常就是从提升各个模块的响应时间入手。这时候，就要请出阿姆达尔定律（Amdahl&amp;rsquo;s Law）了。&lt;/p&gt;
&lt;p&gt;阿姆达尔定律描述的是：当系统中某个模块的执行速度被提升时，整个系统的执行速度能提升多少。&lt;/p&gt;
&lt;p&gt;它先定义了&amp;quot;加速比&amp;quot;这个概念。假设我们优化了某个模块 $m$，让它的平均响应时间从 $T_{m,old}$ 缩短成了 $T_{m,new}$，那么这次优化带来的模块加速比 $r_m$ 就是：&lt;/p&gt;</description>
    </item>
    <item>
      <title>怎么评价软件的质量？</title>
      <link>https://yeecode.top/blog/134/</link>
      <pubDate>Tue, 17 Feb 2026 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/134/</guid>
      
      <description>&lt;p&gt;我们平时用软件，总会不自觉地在心里给它打分：&amp;ldquo;这个界面真漂亮&amp;quot;&amp;ldquo;这个用着真顺手&amp;quot;&amp;ldquo;这个怎么老崩溃&amp;quot;&amp;ldquo;这个功能真强&amp;rdquo;……其实啊，这些随口说的话，都是在从不同角度评价一个软件的质量。&lt;/p&gt;
&lt;p&gt;那有没有一套正经的标准，能把这些&amp;quot;感觉&amp;quot;说清楚呢？还真有。ISO/IEC 25010:2011，这是软件工程里专门用来评价产品质量的国际标准。它把软件质量拆成了八个特性维度，如图{@fig:pqmas}所示。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/134/pic/pqm_hu_19771c7c464e2b50.webp&#34; alt=&#34;软件质量的八个特性维度&#34; width=&#34;565&#34; height=&#34;565&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;{#fig:pqmas}&lt;/p&gt;
&lt;p&gt;这八个特性维度分别是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;功能性（Functional suitability）：软件在指定条件下，能不能提供满足明确和隐含需求的功能。这里只要求&amp;quot;功能有没有&amp;rdquo;，至于功能规不规矩、漂不漂亮，倒不在它的考察范围内。&lt;/li&gt;
&lt;li&gt;效率（Performance efficiency）：在指定条件下，软件对资源的占用情况。资源包括别的软件、系统的软硬件配置等等。&lt;/li&gt;
&lt;li&gt;兼容性（Compatibility）：产品、系统或组件能不能跟别的产品、系统、组件交换信息、协同完成功能，同时还共享同一套软硬件环境。&lt;/li&gt;
&lt;li&gt;易用性（Usability）：指定的用户能不能在指定的环境下，有效、高效、满意地达成目标。&lt;/li&gt;
&lt;li&gt;可靠性（Reliability）：系统、产品或组件在指定条件下、指定时间内，能不能稳定地执行指定功能。这里说的&amp;quot;指定功能&amp;quot;其实涵盖了可用性、安全性、可维护性等等。软件不会像硬件那样用着用着就磨损，所以它不可靠，要么是需求、设计、实现里埋了错，要么是运行环境变了。&lt;/li&gt;
&lt;li&gt;安全性（Security）：产品或系统保护信息和数据的程度，确保人员或其他产品、系统只能按自己的权限级别去访问数据。&lt;/li&gt;
&lt;li&gt;可维护性（Maintainability）：预期的维护者能不能方便、高效地去修改它。&lt;/li&gt;
&lt;li&gt;可移植性（Portability）：系统、产品或组件，从一种硬件、软件或其他运行环境，迁移到另一种环境时，能不能又快又好地搞定。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;把这八个维度合起来，就是软件质量的完整画像。前面那些大白话，对号入座一下就明白了：说&amp;quot;好用&amp;quot;&amp;ldquo;界面漂亮&amp;rdquo;，是在评价易用性；说&amp;quot;老崩溃&amp;rdquo;，是在评价可靠性；说&amp;quot;功能强大&amp;rdquo;，是在评价功能性。&lt;/p&gt;
&lt;p&gt;做架构设计的时候，我们当然希望软件在这八个维度上都能拿高分。但现实里常常要做取舍——比如为了界面更漂亮（把易用性拉满），可能就让加载变慢了（效率下降）。这种权衡，几乎天天都在发生。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;在AI编程迅猛发展的今天，编码能力则逐步弱化，而架构能力则成了每个开发者更为核心的能力。聊完&amp;quot;怎么评价软件的质量&amp;quot;，如果你觉得架构、质量这些话题有意思，强烈推荐一本书《高性能架构之道（第二版）》。&lt;/p&gt;
&lt;p&gt;这是一本理论联系实际的架构书，系统讲了怎么从顶层把软件架构好：覆盖分布式、并发编程、数据库调优、缓存、IO、高可用、前端性能优化等众多知识，最后还拿一个真实项目完整走了一遍架构全过程，把书里的方法真正落了地。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://union-click.jd.com/jdc?e=&amp;p=AyIGZRprFQASBl0SXBIyVlgNRQQlW1dCFFlQCxxKQgFHRE5XDVULR0UVABIGXRJcEh1LQglGaxFiGlEWTTtIYHIAS35dTnduVBFPO3UOHjdUK1sUAxIDXB1TFwIiN1Uca0NsEgZUGloUBxICVitaJQIVB1AcWhMEFwFcEl8lBRIOZUAOewQaVAcfDh0FEQVUGVklMiIEZStrFTIRNxd1DxwKRgBcSFpFAUJTUExTE1JBVFUeWxFVQVNSE19AUBA3VxpaEQs%253D&#34;&gt;&lt;img src=&#34;https://yeecode.top/img/promo/ha.jpg&#34; width=&#34;500&#34; height=&#34;333&#34; loading=&#34;lazy&#34;  alt=&#34;高性能架构之道（第二版）&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://union-click.jd.com/jdc?e=&amp;amp;p=AyIGZRprFQASBl0SXBIyVlgNRQQlW1dCFFlQCxxKQgFHRE5XDVULR0UVABIGXRJcEh1LQglGaxFiGlEWTTtIYHIAS35dTnduVBFPO3UOHjdUK1sUAxIDXB1TFwIiN1Uca0NsEgZUGloUBxICVitaJQIVB1AcWhMEFwFcEl8lBRIOZUAOewQaVAcfDh0FEQVUGVklMiIEZStrFTIRNxd1DxwKRgBcSFpFAUJTUExTE1JBVFUeWxFVQVNSE19AUBA3VxpaEQs%253D&#34;&gt;《高性能架构之道（第二版）》&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;这本书还在台湾地区出了繁体版，叫《巨型服务架构》——繁体版嘛，贵得有点离谱。&lt;/p&gt;
&lt;p&gt;就先聊到这儿。&lt;/p&gt;
&lt;p&gt;我是软件架构师易哥。可以关注我，我会偶尔冒个泡，分享点编程干货！&lt;/p&gt;</description>
    </item>
    <item>
      <title>什么是架构，架构的关键设计原则是什么？</title>
      <link>https://yeecode.top/blog/127/</link>
      <pubDate>Sat, 03 Jan 2026 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/127/</guid>
      
      <description>&lt;p&gt;架构是一个来源于传统领域的古老概念，用来指对房屋、桥梁等实体物理结构的设计与研究。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/127/pic/001_hu_67d050b5df589568.webp&#34; alt=&#34;建筑物的架构&#34; width=&#34;800&#34; height=&#34;533&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;后来，架构的概念被引入到软件系统领域，指&lt;strong&gt;软件系统总体结构的设计&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;我们可以类比传统工程设计领域的概念，将软件系统领域的研究由上到下地分为三层：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;理念层面&lt;/strong&gt;：研究软件系统开发中的理念和思想，例如研究软件的开发模型、评价指标、架构风格等。类比于传统工程领域研究建筑质量标准、研究商业区与居住区的分布关系等。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;架构层面&lt;/strong&gt;：研究如何协调和组织软件系统、子系统、模块之间的关系，从而指导系统实现并提升各类质量指标。类比于规划和设计建筑物的承重结构、功能结构等，并协调各结构的关系等。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;技术层面&lt;/strong&gt;：研究如何高效、可靠、经济地实现软件系统、子系统、模块等。类比于搭建建筑物中的楼梯、墙体、阳台等。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这一样一类比，是不是就比较好理解了。&lt;/p&gt;
&lt;p&gt;可见软件架构位于承上启下的中间层。因此，做出良好的架构需要我们对理念层面和技术层面的知识均由较为深刻的认识。&lt;/p&gt;
&lt;p&gt;理念层面的知识为我们的架构提供了思路和目标上的指引，技术层面的知识为我们的架构提供了手段和工具上的支撑。&lt;/p&gt;
&lt;p&gt;在软件开发过程中，架构发生在需求之后、规划之前。经过需求、架构、规划、开发几个主要步骤后，一个软件便从最初模糊的需求演变为最终可运行的实体。&lt;/p&gt;
&lt;p&gt;软件架构是在软件系统结构、行为和属性的高级抽象基础之上展开的全面的系统设计，其主要内容包括概要设计和详细设计两大步骤。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在概要设计阶段，研究构成系统的抽象组件以及组件之间的连接规则；&lt;/li&gt;
&lt;li&gt;在详细设计阶段，将这些抽象组件细化为模块、类、对象等实际的组件，并通过设计通信规则完成它们之间的连接。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/127/pic/002_hu_a388866421e21173.webp&#34; alt=&#34;软件的模块设计&#34; width=&#34;640&#34; height=&#34;480&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;软件系统架构的好坏对软件的质量有着重要的影响。在算法研究中，我们常使用时间复杂度和空间复杂度等指标来衡量算法的性能。然而，在一个完整的软件项目中，算法的研究与优化只是软件开发阶段的一个小环节，其对系统质量的影响是有限的。相比于算法优化，软件系统架构则是从顶层对软件系统的软硬件结构进行设计，其设计的好坏对系统质量的影响更为重大。&lt;/p&gt;
&lt;p&gt;因此，要想搭建高性能的系统，要先从架构层面出发对软件进行高性能架构，而不应将责任直接推给算法设计和代码优化。&lt;/p&gt;
&lt;p&gt;在了解什么是高性能架构之前，我们需要先了解什么是软件的质量。&lt;/p&gt;
&lt;p&gt;作为一名软件开发者和用户，我们经常在工作和生活中接触各种各样的软件，也会从不同的维度对软件进行评判。例如我们会使用“界面漂亮”、“好用”、“容易崩溃”、“功能强大”等各种类似的词语来描述一个软件，其实这都从不同的维度反映了一个软件的质量。&lt;/p&gt;
&lt;p&gt;那有没有确切的标准来衡量一个软件的质量呢？&lt;/p&gt;
&lt;p&gt;ISO/IEC 25010:2011是软件工程-产品质量评价国际标准。它给出了软件质量模型（Product Quality Model）并将软件质量划分成了八个特性维度，如图所示。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/127/pic/pqm_hu_19771c7c464e2b50.webp&#34; alt=&#34;软件质量的八个特性维度&#34; width=&#34;565&#34; height=&#34;565&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;这八个特性维度的介绍如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;功能性（Functional suitability）：在特定条件下使用时，产品或系统提供满足指定和隐含需求的功能的程度。这里仅对是否满足功能做要求，而对功能的规范性不做要求。&lt;/li&gt;
&lt;li&gt;效率（Performance efficiency）：在指定条件下，软件产品对资源的占用。资源包括其他软件产品、系统的软件和硬件配置等。&lt;/li&gt;
&lt;li&gt;兼容性（Compatibility）：产品、系统或组件可以与其他产品，系统或组件交换信息或执行其所需功能，同时共享相同的硬件或软件环境的程度。&lt;/li&gt;
&lt;li&gt;易用性（Usability）：指定用户可以使用产品或系统在指定的使用环境下以有效、高效和满意的方式达到指定目标的程度。&lt;/li&gt;
&lt;li&gt;可靠性（Reliability）：系统、产品或组件在指定条件下在指定时间内执行指定功能的程度。这里所说的指定功能涵盖可用性、安全性、可维护性等。因为软件不会在运行过程中损耗，所以其不可靠是由于软件需求、设计、实施中的错误引发，或者是执行上下文变化引发。&lt;/li&gt;
&lt;li&gt;安全性（Security）：
产品或系统保护信息和数据的程度，以便人员或其他产品或系统具有与其权限类型和授权级别相适应的数据访问程度。&lt;/li&gt;
&lt;li&gt;可维护性（Maintainability）：预期的维护者可以修改产品或系统的有效性和效率的程度。&lt;/li&gt;
&lt;li&gt;可移植性（Portability）：系统、产品或组件从一种硬件，软件或其他操作或使用环境转移到另一种环境的有效性和效率的程度。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以上八个特性维度共同构成了软件的质量。例如我们所说的“好用”、“界面漂亮”是在从易用性维度评价软件质量，“容易崩溃”是在从可靠性维度评价软件质量，“功能强大”是在从功能性维度评价软件质量。&lt;/p&gt;
&lt;p&gt;在软件架构设计过程中，我们应该&lt;strong&gt;尽量保证设计出的软件能够在以上八个维度都有较好的表现&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;当然也有很多时候，我们不得不做出取舍，为了某些重要的特性维度指标而牺牲一些次要的指标。例如为了界面漂亮（易用性高）而导致界面的加载时间变长（效率降低），需要不断权衡。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/127/pic/003_hu_a095b47814216207.webp&#34; alt=&#34;愈发美观的界面是以降低易用性为代价的&#34; width=&#34;640&#34; height=&#34;425&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;如果大家要学习架构方面的知识，可以参阅《高性能架构之道（第二版）》，这是一本理论联系实际的架构书籍，详细介绍了实现高性能架构的手段，涵盖分布式、并发编程、数据库调优、缓存、IO、高可用、前端性能优化等众多知识。最后还运用书中知识完成了一个实际软件项目的架构设计工作，向大家完整展示了软件架构的全过程。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://union-click.jd.com/jdc?e=&amp;p=AyIGZRprFQASBl0SXBIyVlgNRQQlW1dCFFlQCxxKQgFHRE5XDVULR0UVABIGXRJcEh1LQglGaxFiGlEWTTtIYHIAS35dTnduVBFPO3UOHjdUK1sUAxIDXB1TFwIiN1Uca0NsEgZUGloUBxICVitaJQIVB1AcWhMEFwFcEl8lBRIOZUAOewQaVAcfDh0FEQVUGVklMiIEZStrFTIRNxd1DxwKRgBcSFpFAUJTUExTE1JBVFUeWxFVQVNSE19AUBA3VxpaEQs%253D&#34;&gt;&lt;img src=&#34;https://yeecode.top/img/promo/ha.jpg&#34; width=&#34;500&#34; height=&#34;333&#34; loading=&#34;lazy&#34;  alt=&#34;高性能架构之道（第二版）&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://union-click.jd.com/jdc?e=&amp;amp;p=AyIGZRprFQASBl0SXBIyVlgNRQQlW1dCFFlQCxxKQgFHRE5XDVULR0UVABIGXRJcEh1LQglGaxFiGlEWTTtIYHIAS35dTnduVBFPO3UOHjdUK1sUAxIDXB1TFwIiN1Uca0NsEgZUGloUBxICVitaJQIVB1AcWhMEFwFcEl8lBRIOZUAOewQaVAcfDh0FEQVUGVklMiIEZStrFTIRNxd1DxwKRgBcSFpFAUJTUExTE1JBVFUeWxFVQVNSE19AUBA3VxpaEQs%253D&#34;&gt;《高性能架构之道（第二版）》&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;这本书还出版了繁体版，叫《巨型服务架构》。繁体版贵得很。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/127/pic/004_hu_73489fac7d6ce5b3.webp&#34; alt=&#34;《巨型服务架构》&#34; width=&#34;900&#34; height=&#34;675&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;就先说这么多吧。&lt;/p&gt;
&lt;p&gt;对于程序员而言，开始&lt;strong&gt;关注架构&lt;/strong&gt;就是重大进步。就已经从单纯写代码的层次里跳了出来，至少从“增删改查”中跳了出来，能以&lt;strong&gt;更宏观的视角&lt;/strong&gt;去思考代码、思考软件工程！而在AI编程迅猛发展的今天，编码能力则逐步弱化，而架构能力则更为重要。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;好了，我是软件架构师易哥。&lt;/p&gt;
&lt;p&gt;可以关注我，我会偶尔出没分享编程干货！&lt;/p&gt;</description>
    </item>
    <item>
      <title>VoiceXML入门学习指南</title>
      <link>https://yeecode.top/blog/126/</link>
      <pubDate>Thu, 01 Jan 2026 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/126/</guid>
      
      <description>&lt;p&gt;VoiceXML（语音可扩 展标记语言）是用来描述语音对话过程的标记语言。其定义了如何使用语音识别、语音合成、互联网访问、数据库访问、语音文件播放、DTMF输入等功能开发一 个完整的语音应用系统。&lt;/p&gt;
&lt;p&gt;用VoiceXML开发的语音应用系统，不仅可以完全代替传统CTI(计算机电话集成) 系统所能提供的功能，而且还可以使应用系统开发过程极其简单快捷、系统有极高的可扩展性、可维护性、可移植性、可重用性和开放性。&lt;/p&gt;
&lt;h2 id=&#34;1-概述&#34;&gt;1 概述&lt;/h2&gt;
&lt;h2 id=&#34;11-voicexml的历史与背景&#34;&gt;1.1 VoiceXML的历史与背景&lt;/h2&gt;
&lt;p&gt;VoiceXML的出现最早可以追溯到1995在AT&amp;amp;T公司开发的基于XML的电话标记语言（PML）。随后，AT&amp;amp;T、Lucent Technologies以及Motorola公司分别各自着手开发自己的类似于PML的语音标记语言。到了1998年，W3C组织的“语音浏览器”会议上，AT&amp;amp;T和Lucent Technologies分别展现了他们各自的类同PML的标记语言、Motorola和IBM公司分别推出VoxML和SpeechML、HP和PipeBeach公司也分别推出TalkML和VoiceHTML。AT&amp;amp;T、IBM、Lucent Technoglies、以及Motorola随后成立了VoiceXML论坛，其目的是为了建立一个语音对话应用系统的国际标准。到了2000年，AT&amp;amp;T、IBM、Lucent Technologies、以及Motorola通过W3C协会联合推出语音可扩展标记语言VoiceXML1.0。该标准一经推出，便得到相关行业众多公司的响应。经过两年多的论证和实际系统验证，VoiceXML2.0最终草案在2003年推出。&lt;/p&gt;
&lt;h2 id=&#34;12-为什么要用voicexml&#34;&gt;1.2 为什么要用VoiceXML？&lt;/h2&gt;
&lt;p&gt;VoiceXML的推出给电话语音系统带来全新的应用和开发概念，使传统的CTI技术从繁琐、封闭的模式中走了出来，使广大的语音系统开发人员可以用极其简单的方法实现复杂系统的开发。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;完全替代传统CTI&lt;/strong&gt;：传统的电话语音系统典型的工作方式是&lt;strong&gt;接受用户的按键输入和语音文件播放、跟据用户的需求为用户提供相应服务&lt;/strong&gt;。基于VoiceXML开发的电话语音系统完全可以替代传统CTI系统所能提供的功能。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;语音识别功能&lt;/strong&gt;：基于VoiceXML开发的电话语音系统除了接受用户的按键输入之外，还可以接受用户的语音输入。系统通过语音识别功能实现对用户输入语音到文本的转换。这对于许多仅用按键输入方式而无法解决或解决起来很繁琐的应用领域很有实用意义。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;语音合成功能&lt;/strong&gt;：基于VoiceXML开发的电话语音系统除了能够播放预先录制的语音文件之外，还可以将系统中的文本转换成语音的方式播放给用户。这对于那些需要播报即时消息和可变信息等的应用领域很有实用价值。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;与互联网联结&lt;/strong&gt;： 长期以来，公用电话网和互联网是分离的。随着互联网技术的迅速发展，互联网信息与日俱增，以及互联网相关服务的日新月异，将互联网与公用电话网整合为一 体，使广大的电话用户也能享受到互联网提供的信息和服务显得越来越有吸引力。VoiceXML使公用电话网与互联网有机地结合，使两网的信息能够相互流 动。例如，电话语音流言可以通过互联网以电子邮件的方式传送到目的地；互联网上的新闻可以通过电话播放给用户等。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;灵活的数据库接口&lt;/strong&gt;：许多电话语音应用系统离不开数据库访问。VoiceXM通过脚本方式提供灵活的数据库接口，使得应用系统可以方便地访问各种数据库，如：Microsoft SQL Server 2000、Oracle、Sybase、Microsoft Access、MySQL等。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;简化开发过程&lt;/strong&gt;： 用VoiceXML将电话语音应用系统开发人员从繁琐的编码细节中解脱了出来。开发人员不需要了解相关软件（如语音识别和语音合成）和硬件（如语音卡）API的编程接口，只需要把精力放在应用领域的业务流程上。开发过程轻松快捷、开发周期大大缩短（7-10倍）。VoiceXML使开发人员得以用简 单的方法实现复杂的工作。&lt;/p&gt;
&lt;p&gt;除此之外，基于VoiceXML开发的语音应用系统具有很高的&lt;strong&gt;系统可扩展性&lt;/strong&gt;、&lt;strong&gt;可维护性&lt;/strong&gt;、&lt;strong&gt;可移植性&lt;/strong&gt;、&lt;strong&gt;可重用性和开放性&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id=&#34;13-voicexml现状&#34;&gt;1.3 VoiceXML现状&lt;/h2&gt;
&lt;p&gt;自 2000年VoiceXML1.0推出以后经过不断的修改，于2003年1月推出最新的VoiceXML2.0最终推荐版本。根据W3C称，该版本将会比 较稳定，鼓励各VoiceXML浏览器生产厂家可以根据此版本进行开发。预计不久VoiceXML2.0将会正式推出。&lt;/p&gt;
&lt;p&gt;中国目前也有一些相关行业的公司在着手开发有关VoiceXML的产品。深圳市华琛科技有限公司是国内领先的VoiceXML语音平台生产厂家。在华琛公司 的VoPromote平台上，开发人员可以用VoiceXML2.0脚本语言开发应用于各行业的电话语音系统。&lt;/p&gt;
&lt;h2 id=&#34;14-小结&#34;&gt;1.4 小结&lt;/h2&gt;
&lt;p&gt;VoiceXML标准的出现给电话语音应用系统的开发带来了全新的概念，同时还大大扩展了语音应用系统的应用范围。可以预计，VoiceXML标准的推广将会极大地推动电话语音应用系统的开发和应用，将给广大的电话用户带来深远的影响。&lt;/p&gt;
&lt;h2 id=&#34;2-元素介绍&#34;&gt;2 元素介绍&lt;/h2&gt;
&lt;p&gt;Voice XML的常用元素如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;assign&amp;gt;&lt;/code&gt;    给变量赋值。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;audio&amp;gt;&lt;/code&gt; 播放语音文件。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;block&amp;gt;&lt;/code&gt; 无用户交互的可执行代码块。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;catch&amp;gt;&lt;/code&gt; 捕获一个事件。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;choice&amp;gt;&lt;/code&gt;    定义一个菜单项。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;clear&amp;gt;&lt;/code&gt; 清除一个或多个框架项。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;disconnect&amp;gt;&lt;/code&gt;    结束一次通话。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;else&amp;gt;&lt;/code&gt;  用于&lt;code&gt;&amp;lt;if&amp;gt;&lt;/code&gt;元素中的else。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;elseif&amp;gt;&lt;/code&gt;    用于&lt;code&gt;&amp;lt;if&amp;gt;&lt;/code&gt;元素中的elseif。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;enumerate&amp;gt;&lt;/code&gt; 列举菜单选项。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;error&amp;gt;&lt;/code&gt; 捕获一个&lt;code&gt;&amp;lt;error&amp;gt;&lt;/code&gt;事件。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;exit&amp;gt;&lt;/code&gt;  退出一次通话。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;field&amp;gt;&lt;/code&gt; 在框架中申明一个输入域。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;filled&amp;gt;&lt;/code&gt;    指定当输入域被填充以后需要作的操作。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt;  提示信息和接受数据的对话框架。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;goto&amp;gt;&lt;/code&gt;  把控制转向在同一个或不同文档中的对话。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;grammar&amp;gt;&lt;/code&gt;   指定语音识别或DTMF语法。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;help&amp;gt;&lt;/code&gt;  捕获一个&lt;code&gt;&amp;lt;help&amp;gt;&lt;/code&gt;事件。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;if&amp;gt;&lt;/code&gt;    条件逻辑。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;initial&amp;gt;&lt;/code&gt;   在进入一个双向交互式对话框架时申明初始对话逻辑。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt;  在所有对话公共链接区域指定控制转向。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;log&amp;gt;&lt;/code&gt;   生成调试信息。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;menu&amp;gt;&lt;/code&gt;  定义菜单对话。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;meta&amp;gt;&lt;/code&gt;  定义一个元数据项。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;metadata&amp;gt;&lt;/code&gt;  定义元数据信息。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;noinput&amp;gt;&lt;/code&gt;   捕获一个&lt;code&gt;&amp;lt;noinput&amp;gt;&lt;/code&gt;事件。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;nomatch&amp;gt;&lt;/code&gt;   捕获一个&lt;code&gt;&amp;lt;nomatch&amp;gt;&lt;/code&gt;事件。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;object&amp;gt;&lt;/code&gt;    系统扩展的对象。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;option&amp;gt;&lt;/code&gt;    在一个输入域中选择一个选项。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;param&amp;gt;&lt;/code&gt; &lt;code&gt;&amp;lt;object&amp;gt;&lt;/code&gt;或&lt;code&gt;&amp;lt;subdialog&amp;gt;&lt;/code&gt;的参数。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;prompt&amp;gt;&lt;/code&gt;    指定语音合成提示和声音文件输出。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;property&amp;gt;&lt;/code&gt;  指定特性值以控制平台设置。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;record&amp;gt;&lt;/code&gt;    录音。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;reprompt&amp;gt;&lt;/code&gt;  指定重新提示。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;return&amp;gt;&lt;/code&gt;    从子对话中返回。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;    定义ECMAScript模块。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;subdialog&amp;gt;&lt;/code&gt; 调用一个子对话。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;submit&amp;gt;&lt;/code&gt;    向文件服务器上传数据。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;throw&amp;gt;&lt;/code&gt; 产生一个事件。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;transfer&amp;gt;&lt;/code&gt;  电话转接。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;value&amp;gt;&lt;/code&gt; 在提示中插入一个值。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;var&amp;gt;&lt;/code&gt;   声明一个变量。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;vxml&amp;gt;&lt;/code&gt;  VoiceXML文档标识。
下面对VoiceXML2.0元素进行一些简单介绍。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;21-assign&#34;&gt;2.1 &lt;code&gt;&amp;lt;assign&amp;gt;&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;给一个声明过的变量赋值。在下面的例子中，&lt;code&gt;&amp;lt;assign&amp;gt;&lt;/code&gt;将“学习VoiceXML”赋值给变量verb。由于&lt;code&gt;&amp;lt;assign&amp;gt;&lt;/code&gt; 的属性expr的值是一个Javascript表达式，用单引号将学习VoiceXML括起来以表示其为字符串而不是变量。下面的例子将输出“我学习 VoiceXML”。&lt;/p&gt;</description>
    </item>
    <item>
      <title>为什么祖传代码被称为「屎山」？</title>
      <link>https://yeecode.top/blog/92/</link>
      <pubDate>Mon, 06 Oct 2025 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/92/</guid>
      
      <description>&lt;p&gt;祖传代码，又臭又恶心。每一个接触它的人都在心里骂了千百遍！&lt;/p&gt;
&lt;p&gt;但是，祖传代码在诞生之初，并不是这样的。&lt;/p&gt;
&lt;p&gt;时光经年，一份代码是慢慢变臭的。&lt;/p&gt;
&lt;p&gt;面对这些代码，我们要用历史发展观看它们。我们要理解，毕竟没有哪个程序员不想让自己的代码好，至少不会想让代码坏。&lt;/p&gt;
&lt;p&gt;最开始，你可能接触到的是一个很简单的需求。就和下图一样，四个模块关联起来。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/92/pic/001_hu_4dae2ac940f0aad5.webp&#34; alt=&#34;pic&#34; width=&#34;900&#34; height=&#34;284&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;于是，你把它开发成了上图的样子。一主三从、左右分布、清晰明了。&lt;/p&gt;
&lt;p&gt;甚至，你还对代码进行了精炼和优化，一如上图中那优美的弧线一样。&lt;/p&gt;
&lt;p&gt;这是一个不错的项目。&lt;/p&gt;
&lt;p&gt;后来，需要新增一些功能。&lt;/p&gt;
&lt;p&gt;其实变化不大，只不过是原始版本的稍加变更，你继续优雅地完成了。就和下图一样。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/92/pic/002_hu_35a13f63bd03b3dc.webp&#34; alt=&#34;pic&#34; width=&#34;900&#34; height=&#34;402&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;甚至，你还对这次变动进行了详细的注释。就像上图那绿色的连线一样，和初始版本的蓝色连线进行了清晰的区分。&lt;/p&gt;
&lt;p&gt;优雅，一如既往。&lt;/p&gt;
&lt;p&gt;后来，功能进一步地复杂了一些。你也从容出手，轻快地敲击键盘，完成了新功能。&lt;/p&gt;
&lt;p&gt;新功能就像下图的红色线条一样，虽然让项目多了几分凌乱，但也脉络清晰、功能完善。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/92/pic/003_hu_d5d74de7de92d443.webp&#34; alt=&#34;pic&#34; width=&#34;900&#34; height=&#34;409&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;可是，你怎会停留于此？&lt;/p&gt;
&lt;p&gt;你手中的每一份代码，都是精美的艺术品。注定不是只能完成功能的工具。&lt;/p&gt;
&lt;p&gt;于是，你对它的结构进行了调整。&lt;/p&gt;
&lt;p&gt;你对一些模块进行的标注，并重新梳理了模块间的逻辑。于是得到了下面的结构。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/92/pic/004_hu_7b8a45e787e8deae.webp&#34; alt=&#34;pic&#34; width=&#34;900&#34; height=&#34;352&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;依旧左右分布、依旧清晰明了。一如它初始时的样子，是一份艺术品。&lt;/p&gt;
&lt;p&gt;项目终究还是需要继续演进，模块间需要增加一些新的逻辑关系。&lt;/p&gt;
&lt;p&gt;不过，这次的逻辑关系稍有不同，这是你之前没有预料到的。&lt;/p&gt;
&lt;p&gt;出乎意料？这不正是编程的魅力所在么？&lt;/p&gt;
&lt;p&gt;每一个程序员都是着眼当下，面向未来！&lt;/p&gt;
&lt;p&gt;而且，这次的工期很紧、任务很难！&lt;/p&gt;
&lt;p&gt;纾难解困？这不正是编程的魅力所在么？&lt;/p&gt;
&lt;p&gt;每一个程序员都是危坐案前，敲击荆棘！&lt;/p&gt;
&lt;p&gt;所以，你还是修改代码，如期完成了上述功能。&lt;/p&gt;
&lt;p&gt;就和下面的图片一样。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/92/pic/005_hu_aaf36ee971f62efc.webp&#34; alt=&#34;pic&#34; width=&#34;900&#34; height=&#34;321&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;甚至，为了这次功能的变动，你再次微调了模块之间的关系。让每一个箭头都从左往右流动。&lt;/p&gt;
&lt;p&gt;工期紧张，但功能依旧完善。&lt;/p&gt;
&lt;p&gt;模块众多，但结构依然清晰。&lt;/p&gt;
&lt;p&gt;“每一个箭头都从左往右流动”这一点被打破，还是在开发某次重要的功能时。&lt;/p&gt;
&lt;p&gt;那次功能复杂，时间也紧张。&lt;/p&gt;
&lt;p&gt;“不可能因为这次功能重构整个系统结构”，你这样告诉自己。&lt;/p&gt;
&lt;p&gt;“是的，即使重构，业务团队也无法将旧有的功能全部回归测试”，你再次肯定到。&lt;/p&gt;
&lt;p&gt;但是你依旧努力做到最好。&lt;/p&gt;
&lt;p&gt;系统演进成了下面的样子。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/92/pic/006_hu_e29455227d97f4b4.webp&#34; alt=&#34;pic&#34; width=&#34;900&#34; height=&#34;397&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;岁月经年，没有那个团队会固守不变。&lt;/p&gt;
&lt;p&gt;时光荏苒，难有那个系统会止步不前。&lt;/p&gt;
&lt;p&gt;你也曾去负责其他系统，&lt;/p&gt;
&lt;p&gt;也曾再次接手过这个系统。&lt;/p&gt;
&lt;p&gt;系统也曾增加许多模块，&lt;/p&gt;
&lt;p&gt;也曾废弃很多模块。&lt;/p&gt;
&lt;p&gt;终于，&lt;/p&gt;
&lt;p&gt;系统演化成了下面的样子。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/92/pic/007_hu_456becb85540f128.webp&#34; alt=&#34;pic&#34; width=&#34;900&#34; height=&#34;449&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;凌乱，但能跑！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;有一天，&lt;/p&gt;
&lt;p&gt;团队来了一个新同事。&lt;/p&gt;
&lt;p&gt;他看完这个系统结构后，随口说了一句：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;“这系统是谁写的啊？一坨屎!”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;你心头一颤！&lt;/p&gt;
&lt;p&gt;想起了系统的每一个版本，每一次变动。&lt;/p&gt;
&lt;p&gt;想起了曾经功能简单的系统，&lt;/p&gt;
&lt;p&gt;想起了曾经秀发茂密的自己。&lt;/p&gt;
&lt;p&gt;你想反驳，却又不知从何说起。&lt;/p&gt;
&lt;p&gt;最终，&lt;/p&gt;
&lt;p&gt;你只是走过去，轻拍了他的肩。&lt;/p&gt;
&lt;p&gt;无言。&lt;/p&gt;</description>
    </item>
    <item>
      <title>记一次源码解读：MyBatis缓存修饰器中的锁</title>
      <link>https://yeecode.top/blog/86/</link>
      <pubDate>Thu, 02 Oct 2025 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/86/</guid>
      
      <description>&lt;h2 id=&#34;1-问题背景&#34;&gt;1 问题背景&lt;/h2&gt;
&lt;p&gt;事情是这样的，一天，有一位小伙伴 知乎用户@阳光沙滩裤 私信我问我书中的一个问题，如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/86/pic/001_hu_1b70d1c1779946be.webp&#34; width=&#34;684&#34; height=&#34;417&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;哎，不对啊。我都AT他了，那这图片还打什么码啊！&lt;/p&gt;
&lt;p&gt;打了个寂寞啊！&lt;/p&gt;
&lt;p&gt;不管了，继续讨论问题。&lt;/p&gt;
&lt;p&gt;说实话，这个问题我确实没有注意到。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/86/pic/002_hu_279983edff986986.webp&#34; width=&#34;900&#34; height=&#34;394&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;提问的小伙伴很有心，细致地对比了两个分支的区别。然后提出了这个问题。&lt;/p&gt;
&lt;p&gt;那当然，我要去给出这个问题的解答！&lt;/p&gt;
&lt;p&gt;尽量！&lt;/p&gt;
&lt;p&gt;开始。&lt;/p&gt;
&lt;h2 id=&#34;2-相关知识&#34;&gt;2 相关知识&lt;/h2&gt;
&lt;p&gt;开始之前，我们需要先介绍下这个问题的相关知识。&lt;/p&gt;
&lt;p&gt;主要的背景知识分为两块，一块是MyBatis缓存的实现机制，一块是软引用和弱引用。&lt;/p&gt;
&lt;h2 id=&#34;21-mybatis的缓存&#34;&gt;2.1 MyBatis的缓存&lt;/h2&gt;
&lt;p&gt;装饰器模式在编程开发中经常使用。通常的使用场景是在一个核心基本类的基础上，提供大量的装饰器，从而使得核心基本类经过不同的装饰器修饰后获得不同的功能。&lt;/p&gt;
&lt;p&gt;装饰器还有一个优点就是可以叠加使用，即一个核心基本类可以被多个装饰器修饰，从而同时具有这多个装饰器的功能。&lt;/p&gt;
&lt;p&gt;MyBatis便使用了大量的装饰器实现了不同类型的缓存，它们都在cache包中。在imple子包中存放了实现类，在decorators子包中存放了众多装饰器类。而Cache接口是实现类和装饰器类的共同接口。&lt;/p&gt;
&lt;p&gt;下面给出了Cache接口及其子类的类图。Cache接口的子类中，只有一个实现类，但却有十个装饰器类。通过使用不同的装饰器装饰实现类可以让实现类有着不同的功能。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/86/pic/Cache_hu_8eee8534b168284.webp&#34; width=&#34;900&#34; height=&#34;670&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;Cache接口的源码如下所示，在接口中定义了实现类和装饰器类中必须实现的方法。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;interface&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Cache&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;/**
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;   * 获取缓存id
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;   * @return 缓存id
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;   */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  String &lt;span style=&#34;color:#a6e22e&#34;&gt;getId&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;/**
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;   * 向缓存写入一条数据
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;   * @param key 数据的键
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;   * @param value 数据的值
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;   */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;putObject&lt;/span&gt;(Object key, Object value);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;/**
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;   * 从缓存中读取一条数据
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;   * @param key 数据的键
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;   * @return 数据的值
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;   */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  Object &lt;span style=&#34;color:#a6e22e&#34;&gt;getObject&lt;/span&gt;(Object key);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;/**
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;   * 从缓存中删除一条数据
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;   * @param key 数据的键
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;   * @return 原来的数据值
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;   */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  Object &lt;span style=&#34;color:#a6e22e&#34;&gt;removeObject&lt;/span&gt;(Object key);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;/**
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;   * 清空缓存
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;   */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;clear&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;/**
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;   * 读取缓存中数据的数目
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;   * @return 数据的数目
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;   */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;getSize&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;/**
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;   * 获取读写锁，该方法已经废弃
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;   * @return 读写锁
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;   */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;default&lt;/span&gt; ReadWriteLock &lt;span style=&#34;color:#a6e22e&#34;&gt;getReadWriteLock&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;缓存实现类&lt;code&gt;PerpetualCache&lt;/code&gt;的实现非常简单，但可以通过装饰器来为其增加更多的功能。&lt;code&gt;decorators&lt;/code&gt;子包中存在许多装饰器，根据装饰器的功能可以将它们可以分为以下几个大类：&lt;/p&gt;</description>
    </item>
    <item>
      <title>讨论一种低耦合的数据持久化方案</title>
      <link>https://yeecode.top/blog/81/</link>
      <pubDate>Sun, 01 Jun 2025 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/81/</guid>
      
      <description>&lt;p&gt;在软件开发过程中，进行持久化操作是十分常见的，甚至很多后端程序员谑称自己为CRUD工程师。&lt;/p&gt;
&lt;h2 id=&#34;传统层式方案&#34;&gt;传统层式方案&lt;/h2&gt;
&lt;p&gt;通常，我们进行数据持久化时，会使用Service、Dao、XML（例如MyBati中配置的XML）这三层结构。然后，对象在这三层结构中流动，如下图所示。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/81/pic/lsg_hu_6f874fa6fd1f411f.webp&#34; width=&#34;600&#34; height=&#34;448&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;向下流动便完成了对象写入数据库的操作，向上流动便完成了对象从数据库读出的操作。&lt;/p&gt;
&lt;p&gt;这样的持久化方案十分简单且通用，因此得到了非常广泛的应用。但是也存在以下几个问题：&lt;/p&gt;
&lt;p&gt;首先是，耦合严重。一个对象和它的Service、Dao、XML都是存在耦合的，其耦合点很多。&lt;/p&gt;
&lt;p&gt;其次是使用复杂，例如要完成一个对象的持久化，则需要先找到它的Service然后调用。在进行包含多种对象的批量操作时，找到某个对象的Service的过程将变得十分复杂，往往需要按照一定的命名规则进行反射。&lt;/p&gt;
&lt;p&gt;例如，对不同类型对象的集合进行持久化，则如下所示：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt;(Object obj : objectSet) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// 找到该对象对应的Service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	ObjService objService &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; findServie(obj);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// 调用其Service提供的持久化功能&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	objService.&lt;span style=&#34;color:#a6e22e&#34;&gt;insert&lt;/span&gt;(obj);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;findServie&lt;/code&gt;方法将会是一个较为复杂的方法。&lt;/p&gt;
&lt;p&gt;第三，其不太符合面向对象原则。因为，持久化这件事情，应该是某个对象自身的行为，没有必要依赖外部的其它类完成。&lt;/p&gt;
&lt;h2 id=&#34;对象行为式方案&#34;&gt;对象行为式方案&lt;/h2&gt;
&lt;p&gt;我们可以尝试将对象的持久化作为对象自身固有的行为。&lt;/p&gt;
&lt;p&gt;例如，在对象中设置一个&lt;code&gt;saveToDb()&lt;/code&gt;方法。只要调用某个对象的&lt;code&gt;saveToDb()&lt;/code&gt;方法，则将该对象持久化。按照这种方式，如果对不同类型对象的集合进行持久化，则如下所示：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt;(Object obj : objectSet) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// 持久化该对象&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	obj.&lt;span style=&#34;color:#a6e22e&#34;&gt;saveToDb&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;显然，操作变得更简单。&lt;/p&gt;
&lt;p&gt;更重要的是，&lt;code&gt;saveToDb()&lt;/code&gt;方法是对象自己的行为，与&lt;code&gt;toString()&lt;/code&gt;等方法一样，即如下图所示。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/81/pic/newcj_hu_a03c076658f7a335.webp&#34; width=&#34;541&#34; height=&#34;494&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;这更符合面向对象的思想。&lt;/p&gt;
&lt;p&gt;当存在大量对象时，这一方案带来的便利性更加凸显。例如存在下图所示的一棵树。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/81/pic/tree_hu_8f699dc0baec2c12.webp&#34; width=&#34;504&#34; height=&#34;370&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;则每个节点的持久化方法可以这样写：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;save() {
	this.saveToDb();
	for(Node node: this.children) {
		node.save();
	}
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;然后调用根节点的&lt;code&gt;save()&lt;/code&gt;方法便可以依次触发各个对象节点持久化，逐级触发，一气呵成。&lt;/p&gt;
&lt;p&gt;而不同对象节点的&lt;code&gt;saveToDb()&lt;/code&gt;可以自由掌控，按照不同的规划放入不同的表中。&lt;/p&gt;
&lt;h2 id=&#34;对象行为式方案中的查询&#34;&gt;对象行为式方案中的查询&lt;/h2&gt;
&lt;p&gt;为了实现单个对象的查询，我们可以设置一个&lt;code&gt;queryfromDb()&lt;/code&gt;方法，表示从数据库读取对象的数据。但是，读取数据之前，则必须要事先写定一些索引信息。例如下面的方法表示以&lt;code&gt;id=5&lt;/code&gt;和&lt;code&gt;name=&amp;quot;易哥&amp;quot;&lt;/code&gt;为索引，从数据库中查询出指定的User对象。&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;User user = new User();
user.setId(5);
user.setName(&amp;#34;易哥&amp;#34;);
// 以上面的既定信息为索引，去数据库查询一个满足条件的User对象
user.queryfromDb();
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;如果查询的类型比较多，可以演化出&lt;code&gt;queryFromDbById()&lt;/code&gt;、&lt;code&gt;queryFromDbByName()&lt;/code&gt;、&lt;code&gt;queryFromDbByIdAndName()&lt;/code&gt;等诸多版本。但是总体而言，这比传统层式方案要更清晰，也没有增加复杂度。&lt;/p&gt;</description>
    </item>
    <item>
      <title>Java中异常的装包与拆包</title>
      <link>https://yeecode.top/blog/75/</link>
      <pubDate>Sun, 04 May 2025 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/75/</guid>
      
      <description>&lt;p&gt;在Java中，某些异常会被装到其他异常中之后才能抛出，我们将这个过程称为异常的装包。而当我们获取到某些异常时，需要拆解开来才能见到其内部包裹的真正异常，我们将这个过程称为异常的装包。&lt;/p&gt;
&lt;p&gt;本文将从MyBatis源码中的ExceptionUtil类入手，详细了解Java中异常装包和拆包的原因和机制。相关内容参考《通用源码阅读指导书——MyBatis源码详解》一书。&lt;/p&gt;
&lt;p&gt;ExceptionUtil是MyBatis的一个异常工具类，它提供一个拆包异常的工具方法unwrapThrowable。该方法是将InvocationTargetException和UndeclaredThrowableException这两类异常进行拆包，得到它们中包含的真正的异常。&lt;/p&gt;
&lt;p&gt;unwrapThrowable方法带注释的源码如下所示。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/**
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; * 拆解InvocationTargetException和UndeclaredThrowableException异常的包装，从而得到被包装的真正异常
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; * @param wrapped 包装后的异常
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; * @return 拆解出的被包装异常
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; Throwable &lt;span style=&#34;color:#a6e22e&#34;&gt;unwrapThrowable&lt;/span&gt;(Throwable wrapped) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// 该变量用以存放拆包得到的异常&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  Throwable unwrapped &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; wrapped;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (unwrapped &lt;span style=&#34;color:#66d9ef&#34;&gt;instanceof&lt;/span&gt; InvocationTargetException) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;// 拆包获得内部异常&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      unwrapped &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ((InvocationTargetException) unwrapped).&lt;span style=&#34;color:#a6e22e&#34;&gt;getTargetException&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (unwrapped &lt;span style=&#34;color:#66d9ef&#34;&gt;instanceof&lt;/span&gt; UndeclaredThrowableException) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;// 拆包获得内部异常&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      unwrapped &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ((UndeclaredThrowableException) unwrapped).&lt;span style=&#34;color:#a6e22e&#34;&gt;getUndeclaredThrowable&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;// 该异常无需拆包&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; unwrapped;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;unwrapThrowable方法的结构非常简单，但是我们需要思考它存在的意义：为什么需要给InvocationTargetException和UndeclaredThrowableException这两个类拆包？这两个类为什么要把其他异常包装起来？&lt;/p&gt;
&lt;p&gt;很多时候读懂源码的实现并不难，但是一定要多思考源码为什么这么写。只有这样，才能在源码阅读的过程中有更多的收获。&lt;/p&gt;
&lt;p&gt;接下来我们通过了解InvocationTargetException和UndeclaredThrowableException这两个类来解答上述疑问。&lt;/p&gt;
&lt;p&gt;InvocationTargetException为必检异常，UndeclaredThrowableException为免检的运行时异常。它们都不属于MyBatis，而是都来自于&lt;code&gt;java.lang.reflect&lt;/code&gt;包。&lt;/p&gt;
&lt;p&gt;反射操作中，代理类通过反射调用目标类的方法时，目标类的方法可能抛出异常。反射可以调用各种目标方法，因此目标方法抛出的异常是多种多样无法确定的。这意味着反射操作可能抛出一个任意类型的异常。我们可以用Throwable去接收这个异常，但这无疑太过宽泛。&lt;/p&gt;</description>
    </item>
    <item>
      <title>并行计算的范式及其工程实现</title>
      <link>https://yeecode.top/blog/74/</link>
      <pubDate>Thu, 01 May 2025 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/74/</guid>
      
      <description>&lt;p&gt;本文将会介绍并行计算的基本概念和实现形式的演化，并引出MapReduce范式和DAG范式。这些知识将帮助大家理清楚并行计算的来龙去脉和两种范式之间的关系。&lt;/p&gt;
&lt;p&gt;本文也会详细介绍MapReduce范式和DAG范式的形式化定义，以及它们的工程实现。这些知识将帮助大家理清设计并行计算系统时面对的问题以及解决思路，将帮助大家设计实现一套并行计算系统。&lt;/p&gt;
&lt;p&gt;本文还会介绍Hadoop和Spark的使用。Hadoop和Spark分别是MapReduce范式和DAG范式的典型工程实现。这些知识将帮助大家了解这两个系统的核心实现，并可以上手它们。&lt;/p&gt;
&lt;p&gt;要注意的是，本文是从架构层面讨论如何设计和实现并行计算系统，而不是专门介绍Hadoop和Spark。因此在很多地方思路更为开阔，而不是全然依照这两种典型实现来阐述。本文最后也会提及，我们遵循DAG范式实现了一套与Spark截然不同的系统，服务于特定的业务场景。&lt;/p&gt;
&lt;p&gt;本文约有1.5万字，阅读约需要20分钟。全文目录如下。&lt;/p&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h2 id=&#34;1-并行计算概述&#34;&gt;1 并行计算概述&lt;/h2&gt;
&lt;p&gt;并行计算是指在多个计算引擎中开展的计算，这些计算引擎共同协作完成某一项任务。&lt;/p&gt;
&lt;p&gt;多个计算引擎可以分布在一个工作机内，例如一台多处理器工作机。多个计算引擎也可以分布在不同的工作机内，例如一个分布式系统。在本文的讨论中，我们统一用计算引擎或者节点来称呼它们，而不去细究它的物理实现。&lt;/p&gt;
&lt;p&gt;并行计算的实施是一个复杂的问题。并行计算系统要处理以下典型问题&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;计算分区问题：计算分区就是要将一个计算任务拆分为可以并行开展的小任务，并将这些小任务交给不同的节点处理，然后再将小任务的结果进行汇总。这是并行计算的核心问题。&lt;/li&gt;
&lt;li&gt;数据分区问题：在进行并行计算过程中，往往也需要将输入数据、中间数据、输出数据进行分区，以便于实现大文件的存取和提升数据的读取效率。&lt;/li&gt;
&lt;li&gt;数据交换问题：并行计算的过程中可能会出现数据的交换需求，例如某个计算节点的数据需要交给另外的节点进一步处理。并行计算系统需要为数据交换提供可靠的支持。并且由于并行计算的任务涉及的数据量比较大，因此需要数据交换具有较高的吞吐。&lt;/li&gt;
&lt;li&gt;节点调度问题：节点在运行过程中，可能因为宕机等原因退出，也可能因为数据依赖等原因等待。并行计算系统需要依据节点的工作状况、任务的进度等对参与并行计算的节点进行合理调度。整个过程中可能会涉及通信、同步等问题。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;本文更关心的是计算分区问题。计算分区问题较为复杂，包含可计算性、计算复杂性、系统可靠性、网络等众多领域的知识。如果把这些问题都交给并行计算系统的开发者来解决，势必会花费其大量精力进而导致业务问题被忽视&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;。因此，产生了并行计算的范式。&lt;/p&gt;
&lt;p&gt;接下来我们会介绍并行计算形式的演进，并引出其中两种极具参考性的形式：MapReduce范式和DAG范式。这两种范式的内部组件的耦合都是松散的，这使得它们的扩展能力和容错性都优于传统的并行计算模型（主要是指MPI）&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;。&lt;/p&gt;
&lt;h2 id=&#34;2-并行计算的形式及范式&#34;&gt;2 并行计算的形式及范式&lt;/h2&gt;
&lt;p&gt;范式是成熟可靠的形式。并行计算的范式为并行计算系统的实施提供了标准的参考，只要按照范式提供的方案实施，开发者便可以实现一套较为成熟的并行计算系统。&lt;/p&gt;
&lt;p&gt;并行计算范式带来了以下的优点&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;为并行计算问题提供了抽象和分层，屏蔽了底层资源。&lt;/li&gt;
&lt;li&gt;降低了并行计算问题的理论复杂度，便于程序员进场开发。&lt;/li&gt;
&lt;li&gt;提升了并行计算系统间的通用性，便于进行跨系统的交流。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;目前典型的并行计算范式有MapReduce&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt;和DAG&lt;sup id=&#34;fnref:6&#34;&gt;&lt;a href=&#34;#fn:6&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;6&lt;/a&gt;&lt;/sup&gt;，对应的成熟产品有Hadoop和Spark。&lt;/p&gt;
&lt;p&gt;很多读者都通过Hadoop和Spark对MapReduce和DAG这两种并行计算范式有一定的了解，但往往也会存在不少疑惑，这些疑惑可能包括：为什么会产生MapReduce这种形式？MapReduce能解决什么问题？MapReduce形式为什么会演化出DAG形式？两者到底有什么关系？等等。&lt;/p&gt;
&lt;p&gt;因此在详细介绍MapReduce和DAG之前，我们先介绍并行计算框架的发展思路，以便于大家理清楚这两种范式所处的位置，以及它们之间的关系。&lt;/p&gt;
&lt;h2 id=&#34;21-最简单的并行计算形式&#34;&gt;2.1 最简单的并行计算形式&lt;/h2&gt;
&lt;p&gt;说起并行计算，我们首先想到的最简单形式应该如下所示：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/74/pic/b01_hu_62527e7eca4916dc.webp&#34; width=&#34;345&#34; height=&#34;184&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;但是这并不能称之为一个真正的并行计算，因为各个计算节点都在独立进行自身的任务，而不是共同完成一项任务。计算结束后我们将得到多个结果而不是一个汇总的结果。&lt;/p&gt;
&lt;h2 id=&#34;22-mapreduce形式&#34;&gt;2.2 MapReduce形式&lt;/h2&gt;
&lt;p&gt;显然，我们可以对上述形式进行完善，对各个节点的计算结果进行汇总，如下所示：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/74/pic/b02_hu_cefa69f3542af60b.webp&#34; width=&#34;451&#34; height=&#34;184&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;这样，计算任务被分为了两类：前面的各个节点负责进行并行的计算；后面的一个节点负责对前面并行计算的结果进行汇总。这就是MapReduce范式的形式。我们将前面并行计算节点进行的操作称为Map操作，将后面汇总节点进行的操作称为Reduce操作。&lt;/p&gt;
&lt;p&gt;MapReduce范式的形式虽然简单，但是已经能够解决大量的并行计算问题。例如，过滤统计、搜索、排序等操作。&lt;/p&gt;
&lt;h2 id=&#34;23-mapreduce级联形式&#34;&gt;2.3 MapReduce级联形式&lt;/h2&gt;
&lt;p&gt;当然，仍然有一些问题没法通过单一的MapReduce解决。这时，我们想到通过多级MapReduce级联来解决更复杂的问题。&lt;/p&gt;
&lt;p&gt;例如，下面就展示了5个MapReduce的级联。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/74/pic/b03_hu_2bb685f8de00b99f.webp&#34; width=&#34;900&#34; height=&#34;620&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;24-dag形式&#34;&gt;2.4 DAG形式&lt;/h2&gt;
&lt;p&gt;这时我们依旧看着上图，思考一个问题。在MapReduce中，Map用来进行并行计算，Reduce用来进行汇总，两者共同完成一个任务。但在上图中，当多个MapReduce串联在一起时，并不是每个Map、Reduce都还有存在的意义。例如在极端情况下，Reduce操作可能只需要最后一个就可以了，而同样，很多Map操作也可以省略。这里所说的省略是指相关节点没有对数据流进行任何的修改，节点的输出数据和输入数据完全相同，即只进行了数据的透传。&lt;/p&gt;
&lt;p&gt;如果我们用虚线圆圈表示仅做透传操作的节点，则可能会得到下面的结果：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/74/pic/b04_hu_aead9eb99ef9aeaf.webp&#34; width=&#34;900&#34; height=&#34;620&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;显然，这些虚线圆圈表示的节点我们可以直接忽略掉，转化为下面的形式：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/74/pic/b05_hu_97e63883c734b287.webp&#34; width=&#34;900&#34; height=&#34;580&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;这时，Map和Reduce不再成对存在，因此也不再是MapReduce级联的形式，成了一种新的形式。&lt;/p&gt;
&lt;p&gt;我们可以看出，这是一棵树。&lt;/p&gt;
&lt;p&gt;既然已经到了树，我们完全可以尝试让节点连接多个父亲，进而将其泛化为图，如下所示。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/74/pic/b06_hu_fa0c32438c921597.webp&#34; width=&#34;900&#34; height=&#34;580&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;我们发现，一个节点的输出导出给多个节点、导出给非上一级节点等，都是可行的。也就是说，将树的形式升级为图的形式也是可以的，不会增加额外的复杂性。其实也可以在MapReduce级联阶段完成从树到图的升级，都是一样的。&lt;/p&gt;
&lt;p&gt;但是这个图是有条件的。首先边是有方向的，边代表了数据流，边的方向代表了数据传输的方向。其次不能存在环，否则任务将是无法终止的。到这里，我们得到了并行计算的又一种范式：有向无环图，即DAG。&lt;/p&gt;
&lt;p&gt;现在，我们已经讨论了并行计算的形式演进，并引出了MapReduce和DAG这两种典型的并行计算范式。在接下来的章节中，我们会对这两种范式进行更为深入的介绍。&lt;/p&gt;
&lt;blockquote&gt;
    &lt;p&gt;在前面的介绍中，我们刻意避免了对包含循环调用的MapReduce展开讨论，这有利于简化讨论过程，但并不影响最终结论。&lt;/p&gt;
&lt;p&gt;循环调用是指某个MapReduce的输出结果作为输入再次输入到前面的MapReduce中，按照这样的形式，推导出的图是有向有环的。但是，只要任务是可终止的，则说明循环次数是有限的。这种情况下，我们可以将有向有环图等价转化为一个有向无环图，其转化过程和等价性证明可以规约为非确定型有穷自动机转确定型有穷自动机的过程。&lt;/p&gt;
&lt;p&gt;因此，通常我们使用有向无环图而不是有向有环图来描述一个并行计算任务，这不会缩小并行计算系统的应用范围，却能够极大地简化系统的设计和开发难度。&lt;/p&gt;

  &lt;/blockquote&gt;&lt;h2 id=&#34;3-mapreduce范式&#34;&gt;3 MapReduce范式&lt;/h2&gt;
&lt;p&gt;在本节，我们将介绍MapReduce范式的形式化表示，并介绍它的具体工程实现。&lt;/p&gt;
&lt;h2 id=&#34;31-形式化表示&#34;&gt;3.1 形式化表示&lt;/h2&gt;
&lt;p&gt;MapReduce范式十分简单，它将并行计算划分为并行计算的Map操作和汇总的Reduce操作，Map操作先于Reduce操作开展。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/74/pic/b02_hu_cefa69f3542af60b.webp&#34; width=&#34;451&#34; height=&#34;184&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;某些资料会把这里的“Map”翻译为“映射”&lt;sup id=&#34;fnref:7&#34;&gt;&lt;a href=&#34;#fn:7&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;7&lt;/a&gt;&lt;/sup&gt;，将“Reduce”翻译为“化简”&lt;sup id=&#34;fnref:8&#34;&gt;&lt;a href=&#34;#fn:8&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;8&lt;/a&gt;&lt;/sup&gt;，从“信、达、雅”的角度看还是十分特切的。但作者认为，用“并行计算”和“汇总”来称呼这两类操作可能更有助于让读者明白两类操作的具体内容，因此在本文中我们沿用后者。&lt;/p&gt;
&lt;p&gt;我们可以用下面的伪代码对MapReduce范式进行形式化的表示：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;output Map(input) {
	// 用来被用户重写
}

output Reduce(input) {
	// 用来被用户重写
}

output Main(List&amp;lt;input&amp;gt;) { // 输入数据是多个数据片input组成的集合List&amp;lt;input&amp;gt;
	mapsOutput; // 所有的Map的输出的集合 
	for(Map in Maps){
		mapsOutput += Map(input);
	}
	return Reduce(mapsOutput);
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;上述伪代码中，用户需要定义的就是Map操作和Reduce操作。&lt;/p&gt;</description>
    </item>
    <item>
      <title>MyBatis插件平台原理详解</title>
      <link>https://yeecode.top/blog/72/</link>
      <pubDate>Sat, 12 Apr 2025 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/72/</guid>
      
      <description>&lt;p&gt;在前面的文章中我们已经讲过，MyBatis支持插件开发，并且实际开发了一个插件示例。本文将介绍MyBatis的插件原理。&lt;/p&gt;
&lt;p&gt;该文的内容均参考自《通用源码阅读指导书——MyBatis源码详解》一书，要想深入了解，则可以阅读该书籍。&lt;/p&gt;
&lt;p&gt;MyBatis在plugin包中搭建了一个拦截器平台。下图给出了拦截器平台的类图。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/72/pic/Plugin_hu_e22fad40210ce43d.webp&#34; alt=&#34;拦截器平台类图&#34; width=&#34;844&#34; height=&#34;436&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;整个类图中最为核心的类便是Plugin类，它继承了&lt;code&gt;java.lang.reflect.InvocationHandler&lt;/code&gt;接口，因此是一个基于反射实现的代理类。Plugin类的属性如下所示。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 被代理对象&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;final&lt;/span&gt; Object target;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 拦截器&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;final&lt;/span&gt; Interceptor interceptor;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 拦截器要拦截的所有的类，以及类中的方法&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;final&lt;/span&gt; Map&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;Class&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;?&amp;gt;&lt;/span&gt;, Set&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;Method&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&lt;/span&gt; signatureMap;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Plugin类的signatureMap属性存储的是当前拦截器要拦截的类和方法，该信息就是通过getSignatureMap方法从拦截器的Intercepts注解和Signature注解中获取的。下面给出了getSignatureMap方法的源码。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/**
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; * 获取拦截器要拦截的所有类和类中的方法
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; * @param interceptor 拦截器
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; * @return 入参拦截器要拦截的所有类和类中的方法
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; Map&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;Class&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;?&amp;gt;&lt;/span&gt;, Set&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;Method&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;getSignatureMap&lt;/span&gt;(Interceptor interceptor) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// 获取拦截器的Intercepts注解&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  Intercepts interceptsAnnotation &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; interceptor.&lt;span style=&#34;color:#a6e22e&#34;&gt;getClass&lt;/span&gt;().&lt;span style=&#34;color:#a6e22e&#34;&gt;getAnnotation&lt;/span&gt;(Intercepts.&lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// issue #251&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (interceptsAnnotation &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; PluginException(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;No @Intercepts annotation was found in interceptor &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; interceptor.&lt;span style=&#34;color:#a6e22e&#34;&gt;getClass&lt;/span&gt;().&lt;span style=&#34;color:#a6e22e&#34;&gt;getName&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// 将Intercepts注解的value信息取出来，是一个Signature数组&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  Signature&lt;span style=&#34;color:#f92672&#34;&gt;[]&lt;/span&gt; sigs &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; interceptsAnnotation.&lt;span style=&#34;color:#a6e22e&#34;&gt;value&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// 将Signature数组数组放入一个Map中。键为Signature注解的type类型，值为该类型下的方法集合&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  Map&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;Class&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;?&amp;gt;&lt;/span&gt;, Set&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;Method&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&lt;/span&gt; signatureMap &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; HashMap&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;gt;&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (Signature sig : sigs) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Set&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;Method&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; methods &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; signatureMap.&lt;span style=&#34;color:#a6e22e&#34;&gt;computeIfAbsent&lt;/span&gt;(sig.&lt;span style=&#34;color:#a6e22e&#34;&gt;type&lt;/span&gt;(), k &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; HashSet&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;gt;&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      Method method &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; sig.&lt;span style=&#34;color:#a6e22e&#34;&gt;type&lt;/span&gt;().&lt;span style=&#34;color:#a6e22e&#34;&gt;getMethod&lt;/span&gt;(sig.&lt;span style=&#34;color:#a6e22e&#34;&gt;method&lt;/span&gt;(), sig.&lt;span style=&#34;color:#a6e22e&#34;&gt;args&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      methods.&lt;span style=&#34;color:#a6e22e&#34;&gt;add&lt;/span&gt;(method);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    } &lt;span style=&#34;color:#66d9ef&#34;&gt;catch&lt;/span&gt; (NoSuchMethodException e) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; PluginException(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Could not find method on &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; sig.&lt;span style=&#34;color:#a6e22e&#34;&gt;type&lt;/span&gt;() &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34; named &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; sig.&lt;span style=&#34;color:#a6e22e&#34;&gt;method&lt;/span&gt;() &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;. Cause: &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; e, e);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; signatureMap;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;有了拦截器要拦截的类型信息之后，Plugin就可以判断出当前的类型是否需要被拦截器拦截。如果一个类需要拦截，则Plugin会为这个类创建一个代理类。这部分操作在wrap方法中完成，下面给出了wrap方法的源码。&lt;/p&gt;</description>
    </item>
    <item>
      <title>MyBatis插件开发实战</title>
      <link>https://yeecode.top/blog/71/</link>
      <pubDate>Sat, 15 Mar 2025 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/71/</guid>
      
      <description>&lt;p&gt;本文将开发一个功能非常简单的MyBatis插件，来了解MyBatis插件的开发过程。我们要开发的插件的功能是：在MyBatis查询列表形式的的结果时，打印出结果的数目。&lt;/p&gt;
&lt;p&gt;整个插件的源代码非常简单，我们直接给出，如下面代码所示。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@Intercepts&lt;/span&gt;({
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;@Signature&lt;/span&gt;(type &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ResultSetHandler.&lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;, method &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;handleResultSets&amp;#34;&lt;/span&gt;, args &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {Statement.&lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;})
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;})
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;YeecodeInterceptor&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;implements&lt;/span&gt; Interceptor {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; String info;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;@Override&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Object &lt;span style=&#34;color:#a6e22e&#34;&gt;intercept&lt;/span&gt;(Invocation invocation) &lt;span style=&#34;color:#66d9ef&#34;&gt;throws&lt;/span&gt; Throwable {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 执行原有方法&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Object result &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; invocation.&lt;span style=&#34;color:#a6e22e&#34;&gt;proceed&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 打印原方法输出结果的数目&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(info &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;:&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; ((List) result).&lt;span style=&#34;color:#a6e22e&#34;&gt;size&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 返回原有结果&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; result;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;@Override&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;setProperties&lt;/span&gt;(Properties properties) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 为拦截器设置属性&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        info &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; properties.&lt;span style=&#34;color:#a6e22e&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;preInfo&amp;#34;&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;toString&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;MyBatis插件是一个实现了Interceptor接口的类。Interceptor的含义是拦截器，因此我们所说的MyBatis插件真正的叫法是MyBatis拦截器。由于&lt;code&gt;plugin&lt;/code&gt;包中还有一个叫&lt;code&gt;Plugin&lt;/code&gt;的类，为了避免混淆，在接下来的叙述中，我们用拦截器来指代我们编写的插件类。&lt;/p&gt;
&lt;p&gt;拦截器类上有注解Intercepts，Intercepts的参数是Signature注解数组。每个Signature注解都声明了当前拦截器类要拦截的方法。Signature注解中参数的含义如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;type：拦截器要拦截的类型。YeecodeInterceptor拦截器要拦截的类型是ResultSetHandler类型。&lt;/li&gt;
&lt;li&gt;method：拦截器要拦截的type类型中的方法。YeecodeInterceptor拦截器要拦截的是ResultSetHandler类型中的handleResultSets方法。&lt;/li&gt;
&lt;li&gt;args：拦截器要拦截的type类型中method方法的参数类型列表。在YeecodeInterceptor拦截器中，ResultSetHandler类型中的handleResultSets方法只有一个Statement类型的参数。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当我们要拦截多个方法时，只需要在Intercepts数组中放入多个Signature注解即可。&lt;/p&gt;
&lt;p&gt;Interceptor接口中有三个方法供拦截器类实现，这三个方法的含义解释如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;intercept：拦截器类必须实现该方法。拦截器拦截到目标方法时，会将操作转接到该intercept方法上，其中的参数Invocation为拦截到的目标方法。在我们的YeecodeInterceptor拦截器的intercept方法中，会先执行原有的方法并获得原方法的输出结果，然后打印出原方法输出结果的数目，最后返回了原有结果。这样YeecodeInterceptor拦截器便实现了打印结果数目的功能。&lt;/li&gt;
&lt;li&gt;plugin：拦截器类可以选择实现该方法。该方法中可以输出一个对象来替换入参传入的目标对象。在YeecodeInterceptor拦截器中，我们没有实现该方法，而是直接使用了Interceptor接口中的默认实现。在默认实现中，会调用&lt;code&gt;Plugin.wrap&lt;/code&gt;方法给出了一个原有对象的包装对象，然后用该对象来替换原有对象。&lt;/li&gt;
&lt;li&gt;setProperties：拦截器类可以选择实现该方法。该方法用来为拦截器设置属性。在YeecodeInterceptor拦截器中，我们使用该方法为拦截器设置了info属性的值。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;拦截器配置结束后，还需要将拦截器设置到MyBatis的配置中才能生效。下面代码给出了YeecodeInterceptor拦截器在配置文件中的配置片段。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;plugin interceptor&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;com.github.yeecode.mybatisdemo.plugin.YeecodeInterceptor&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;property name&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;preInfo&amp;#34;&lt;/span&gt; value&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;本次查询记录数目&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/&lt;/span&gt;plugin&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;拦截器配置完成后，重新启动MyBatis就可以使拦截器生效。在配置了YeecodeInterceptor拦截器后，当我们查询列表形式的的结果时，控制台会打印出当前查询结果的数目，如图所示。&lt;/p&gt;</description>
    </item>
    <item>
      <title>责任链模式详解及其使用示例</title>
      <link>https://yeecode.top/blog/70/</link>
      <pubDate>Sat, 15 Feb 2025 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/70/</guid>
      
      <description>&lt;p&gt;在有些场景下，一个目标对象可能需要经过多个对象的处理。例如我们要筹办一个校园晚会，需要针对演员进行如下的准备工作：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;给演员发送邮件，告知晚会时间地点，该工作由邮件发送员负责。&lt;/li&gt;
&lt;li&gt;根据演员性别为其准备衣服，该工作由物资管理员负责。&lt;/li&gt;
&lt;li&gt;如果演员未成年，则为其安排校车接送，该工作由对外联络员负责。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下面代码展示了这一过程，每一个演员都要好三个工作人员打交道。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 不使用责任链模式&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;不使用责任链模式：&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 创建三个工作人员实例&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;MailSender mailSender &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; MailSender();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;MaterialManager materialManager &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; MaterialManager();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ContactOfficer contactOfficer &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; ContactOfficer();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 依次处理每个参与者&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (Performer performer : performerList) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;process &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; performer.&lt;span style=&#34;color:#a6e22e&#34;&gt;getName&lt;/span&gt;() &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;:&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; MailSender().&lt;span style=&#34;color:#a6e22e&#34;&gt;handle&lt;/span&gt;(performer);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; MaterialManager().&lt;span style=&#34;color:#a6e22e&#34;&gt;handle&lt;/span&gt;(performer);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; ContactOfficer().&lt;span style=&#34;color:#a6e22e&#34;&gt;handle&lt;/span&gt;(performer);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;---------&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;而责任链模式将多个处理器组装成一个链条，被处理对象被放置到链条的起始端后，会自动在整个链条上传递和处理。这样被处理对象不需要和每个处理器打交道，也不需要了解整个链条的传递过程，于是便实现了被处理对象和单个处理器的解耦。&lt;/p&gt;
&lt;p&gt;为实现责任链模式，我们首先创建了一个处理器抽象类Handler，如下所示。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;abstract&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Handler&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 当前处理器的下一个处理器&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; Handler nextHandler;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/**
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;     * 当前处理器的处理逻辑，交给子类实现
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;     * @param performer 被处理对象
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;     */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;abstract&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;handle&lt;/span&gt;(Performer performer);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/**
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;     * 触发当前处理器，并在处理结束后将被处理对象传给后续处理器
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;     * @param performer 被处理对象
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;     */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;triggerProcess&lt;/span&gt;(Performer performer) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        handle(performer);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (nextHandler &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            nextHandler.&lt;span style=&#34;color:#a6e22e&#34;&gt;triggerProcess&lt;/span&gt;(performer);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/**
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;     * 设置当前处理器的下一个处理器
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;     * @param nextHandler 下一个处理器
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;     * @return 下一个处理器
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;     */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Handler &lt;span style=&#34;color:#a6e22e&#34;&gt;setNextHandler&lt;/span&gt;(Handler nextHandler) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;nextHandler&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; nextHandler;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; nextHandler;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;然后每个处理器需要继承该抽象类Handler，并实现自身的handle方法。下图是责任链模式的类图。&lt;/p&gt;</description>
    </item>
    <item>
      <title>详解MyBatis中懒加载功能的使用</title>
      <link>https://yeecode.top/blog/69/</link>
      <pubDate>Sat, 11 Jan 2025 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/69/</guid>
      
      <description>&lt;p&gt;在我们进行跨表数据查询的时候，常出现先查询表A、再根据表A的输出结果查询表B的情况。而有些时候，我们从A表中查询出来的数据中，只有部分需要查询B表。&lt;/p&gt;
&lt;p&gt;例如我们需要从&lt;code&gt;user&lt;/code&gt;表查询用户信息并打印所有用户的姓名列表。而查询出的用户中，只有满足“&lt;code&gt;user.getAge() == 18&lt;/code&gt;”的用户才需要查询该用户在&lt;code&gt;task&lt;/code&gt;表中的信息。这个过程如下所示。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;User userParam &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; User();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;userParam.&lt;span style=&#34;color:#a6e22e&#34;&gt;setSex&lt;/span&gt;(0);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 查询满足条件的全部用户&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;List&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;User&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; userList &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; session.&lt;span style=&#34;color:#a6e22e&#34;&gt;selectList&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;com.github.yeecode.mybatisdemo.dao.UserDao.lazyLoadQuery&amp;#34;&lt;/span&gt;, userParam);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 打印全部用户姓名列表&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;users: &amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (User user : userList) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(user.&lt;span style=&#34;color:#a6e22e&#34;&gt;getName&lt;/span&gt;() &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;， age = &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; user.&lt;span style=&#34;color:#a6e22e&#34;&gt;getAge&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 根据条件打印用户任务信息&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;userDetail: &amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (User user : userList) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (user.&lt;span style=&#34;color:#a6e22e&#34;&gt;getAge&lt;/span&gt;() &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; 18) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(user.&lt;span style=&#34;color:#a6e22e&#34;&gt;getName&lt;/span&gt;() &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;:&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (Task task : user.&lt;span style=&#34;color:#a6e22e&#34;&gt;getTaskList&lt;/span&gt;()) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(task.&lt;span style=&#34;color:#a6e22e&#34;&gt;getTaskName&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;我们可以先从&lt;code&gt;user&lt;/code&gt;表获取用户信息，然后再从&lt;code&gt;task&lt;/code&gt;表查询所有用户的任务信息。这一定是可行的，但是这样操作会查询出许多多余的结果，所有不满足“&lt;code&gt;user.getAge() == 18&lt;/code&gt;”的用户的任务信息都是多余的。&lt;/p&gt;
&lt;p&gt;一种更好的方案是先从&lt;code&gt;user&lt;/code&gt;表获取用户信息，然后根据需要（即是否满足“&lt;code&gt;user.getAge() == 18&lt;/code&gt;”）决定是否查询该用户在&lt;code&gt;task&lt;/code&gt;表中的信息。&lt;/p&gt;
&lt;p&gt;这种先加载必须的信息，然后再根据需要进一步加载信息的方式叫作懒加载。MyBatis便支持数据的懒加载。&lt;/p&gt;
&lt;p&gt;要想使用懒加载，我们需要在MyBatis的配置文件中启用该功能，如下所示。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;settings&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;!--&lt;/span&gt;全局启用懒加载&lt;span style=&#34;color:#f92672&#34;&gt;--&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;setting name&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;lazyLoadingEnabled&amp;#34;&lt;/span&gt; value&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;true&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;!--&lt;/span&gt;激进懒加载设置 false即&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;：&lt;/span&gt;懒加载时&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;，&lt;/span&gt;每个属性都按需加载&lt;span style=&#34;color:#f92672&#34;&gt;--&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;setting name&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;aggressiveLazyLoading&amp;#34;&lt;/span&gt; value&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;false&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/&lt;/span&gt;settings&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;aggressiveLazyLoading是激进懒加载设置，我们对该属性进行一些说明。当aggressiveLazyLoading设置为true时，对对象任一属性的读或者写操作都会触发该对象所有懒加载属性的加载；当aggressiveLazyLoading设置为false时，对对象某一懒加载属性的读操作会触发该属性的加载。无论aggressiveLazyLoading的设置如何，调用对象的&amp;quot;equals&amp;quot;，&amp;ldquo;clone&amp;rdquo;，&amp;ldquo;hashCode&amp;rdquo;，&amp;ldquo;toString&amp;quot;中任意一个方法都会触发该对象所有懒加载属性加载。在源码中，我们会清晰地看到aggressiveLazyLoading设置项如何生效。&lt;/p&gt;</description>
    </item>
    <item>
      <title>数据库存储过程的创建与使用</title>
      <link>https://yeecode.top/blog/68/</link>
      <pubDate>Sat, 14 Dec 2024 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/68/</guid>
      
      <description>&lt;p&gt;存储过程（Stored Procedure）是数据库中的一段可以被重用的代码片段，可以通过外部调用完成较为复杂的操作。在调用时，可以为存储过程传入输入参数，而存储过程执行结束后也可以给出输出参数。&lt;/p&gt;
&lt;p&gt;主流的数据库都支持存储过程，MySQL也不例外。接下来我们以MySQL为例介绍存储过程的创建、使用。&lt;/p&gt;
&lt;p&gt;存储过程的创建过程并不复杂，创建语句格式如下所示：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;CREATE PROCEDURE 存储过程名称
([[IN|OUT|INOUT] 参数名 数据类型[,[IN|OUT|INOUT] 参数名 数据类型…]])
过程体
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;其中存储过程的参数分为三类：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;IN：输入参数，该参数向存储过程输入值，但是不能从存储过程中返回值。&lt;/li&gt;
&lt;li&gt;OUT: 输出参数，该参数可以从存储过程中返回值，但是不能向存储过程输入值。&lt;/li&gt;
&lt;li&gt;INOUT: 双向参数，该参数可以向存储过程输入值，也可以从存储过程中返回值。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在过程体中我们可以定义具体的操作，包括自定义变量、读取参数的值、设置参数的值、执行增删改查操作、进行逻辑判断等。&lt;/p&gt;
&lt;p&gt;存储过程创建之后，我们便可以进行存储过程的查询、调用、删除等工作。其中调用存储过程的语句格式如下所示：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;CALL 存储过程名称 ([参数 [,参数…]])
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;下面我们通过一个示例来展示下存储过程的使用。首先，我们使用命令行创建一个名为&lt;code&gt;yeecode&lt;/code&gt;的存储过程，如下所示。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/68/pic/048_hu_4a97d3c9240d0b61.webp&#34; alt=&#34;存储过程的创建&#34; width=&#34;900&#34; height=&#34;235&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;其中DELIMITER是一个单独的命令，与存储过程无关。通常，“;”是一条SQL语句的结束符，MySQL在遇到该符号时认为用户语句输入完毕。然而，在存储过程的创建中我们往往要输入多条语句，为了防止MySQL遇到第一个“;”便终止用户输入，因此需要先行将结束符修改为其他字符。DELIMITER命令就是修改结束符的命令。在上述示例中，我们先将结束符修改为“&lt;code&gt;$$&lt;/code&gt;”，然后输入了包含“;”的语句，之后使用“&lt;code&gt;$$&lt;/code&gt;”结束了我们的输入。最后，我们又使用DELIMITER命令将结束符修改回了“;”。&lt;/p&gt;
&lt;p&gt;这样，我们便创建了一个存储过程。该存储过程有两个输入参数，分别为ageMinLimit、ageMaxLimit；有两个输出参数，分别为count、maxAge。概存储过程的功能是找出年龄在ageMinLimit、ageMaxLimit区间内的用户数目和区间内的最大年龄，然后分别放入输出参数count和maxAge返回。&lt;/p&gt;
&lt;p&gt;创建完成后，我们可以调用我们创建的&lt;code&gt;yeecode&lt;/code&gt;存储过程，如下所示。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/68/pic/049_hu_f7a36279db6b31d8.webp&#34; alt=&#34;存储过程的调用&#34; width=&#34;900&#34; height=&#34;372&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;上述示例中，我们输入了年龄的下界10、上界30后调用了存储过程，然后通过输出参数得出了该区间内的人数和最大年龄。&lt;/p&gt;
&lt;p&gt;当然，使用结束后我们可以调用下图所示的语句删除存储过程。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/68/pic/050_hu_6d97dca987555d2f.webp&#34; alt=&#34;存储过程的删除&#34; width=&#34;900&#34; height=&#34;129&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;相比于普通的SQL语句，存储过程支持变量定义、逻辑判断、数据校验、多输出等，功能更为强大。并且基于存储过程还可以将操作逻辑封装到数据库中，提高了逻辑的保密性。但是操作逻辑封装到数据库中也带来了逻辑不清晰、与数据库耦合高等问题。在使用中，要根据具体使用场景判断是否使用存储过程。&lt;/p&gt;
&lt;p&gt;以上内容均参考《通用源码阅读指导书——MyBatis源码详解》一书。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://union-click.jd.com/jdc?e=&amp;p=JF8BAMYJK1olXDYCV1ZeCU4UCl9MRANLAjZbERscSkAJHTdNTwcKBlMdBgABFksUC2wJHlgcQl9HCANtb1FqVSleXj1wFXRcAC4CXAlkWTFyXVcZbQcyVF9cCE8eBWcKG2slXQEyAjBdCUoWAm4NG14WbQcyVFlYCkMTBGcBGVkWXzYFVFdtUx55BWdbSV9AVQEBVl9fCnsnM2w4K2sVbQUyFjAKDRtHUTgNHF4VVVMHAV5YAEhAAWsLHFkXCQEDXVcOXXsVAm4MEms&#34;&gt;&lt;img src=&#34;https://yeecode.top/img/promo/mb.jpg&#34; width=&#34;450&#34; height=&#34;348&#34; loading=&#34;lazy&#34;  alt=&#34;通用源码阅读指导书-京东自营&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://union-click.jd.com/jdc?e=&amp;amp;p=JF8BAMYJK1olXDYCV1ZeCU4UCl9MRANLAjZbERscSkAJHTdNTwcKBlMdBgABFksUC2wJHlgcQl9HCANtb1FqVSleXj1wFXRcAC4CXAlkWTFyXVcZbQcyVF9cCE8eBWcKG2slXQEyAjBdCUoWAm4NG14WbQcyVFlYCkMTBGcBGVkWXzYFVFdtUx55BWdbSV9AVQEBVl9fCnsnM2w4K2sVbQUyFjAKDRtHUTgNHF4VVVMHAV5YAEhAAWsLHFkXCQEDXVcOXXsVAm4MEms&#34;&gt;《通用源码阅读指导书》&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;这是一本以MyBatis的源码为实例讲述源码阅读方法的书籍，并且附带有示例项目源码，MyBatis的全中文注解。书籍还总结了大量的编程知识和架构经验，对提升编程和架构能力十分有用。&lt;/p&gt;
&lt;p&gt;而且，调用一次存储过程可能会得到多组结果。MyBatis也支持这个过程。MyBatis能够为存储过程准备参数，并解析存储过程返回的多组结果集，甚至可以对多组结果集进行组装。想了解相关内容，可以参考《通用源码阅读指导书——MyBatis源码详解》的“第22章 executor包”章节。&lt;/p&gt;</description>
    </item>
    <item>
      <title>多线程编程中的ThreadLocal详解</title>
      <link>https://yeecode.top/blog/67/</link>
      <pubDate>Sat, 16 Nov 2024 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/67/</guid>
      
      <description>&lt;p&gt;防止一个对象被多个线程同时读写是多线程编程中非常重要的工作，通常可以使用加锁等方式来实现。那有没有一种方式来彻底避免这种情况呢？&lt;/p&gt;
&lt;p&gt;有，ThreadLocal就是其中的一种。&lt;/p&gt;
&lt;p&gt;一个对象会被多个线程访问，是因为多个线程共享了这个对象。我们只要把对象转变为线程独有的，就可以避免这种情况。这是一种以“空间换时间”的思路：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当一个对象被多个线程共享时，节约了存储该对象的空间；但是在访问该对象时需要多个线程排队进行，这样便浪费了时间。&lt;/li&gt;
&lt;li&gt;当我们对象设置为线程独享时，每个线程都可以无需排队自由访问对象，节约了时间；但是同一个对象可能在多个线程中存在拷贝，这样就浪费了存储空间。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当然，有一些数据在多个线程之间共享是多个线程之间通讯的需要，这种情况不在此列。&lt;/p&gt;
&lt;p&gt;时间与空间的矛盾在程序设计中会经常出现，我们需要根据不同的场景选择不同的方案。而ThreadLocal是典型的“时间换空间”的思路，每个线程都独有一个ThreadLocal，可以在其中存放独属于该线程的数据。&lt;/p&gt;
&lt;p&gt;ThreadLocal的主要方法有：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;T get(): 从ThreadLocal中读取数据&lt;/li&gt;
&lt;li&gt;void set(T value)：向ThreadLocal写入数据&lt;/li&gt;
&lt;li&gt;void remove() ：从ThreadLocal中删除数据&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;接下来我们通过示例展示ThreadLocal的使用，并证明ThreadLocal空间是归各个线程独享的。&lt;/p&gt;
&lt;p&gt;在下面的代码中，我们创建threadLocalNumber和threadLocalString这两个ThreadLocal变量。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;DemoApplication&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 创建了两个ThreadLocal变量&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; ThreadLocal&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;Integer&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; threadLocalNumber &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; ThreadLocal&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;gt;&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; ThreadLocal&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;String&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; threadLocalString &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; ThreadLocal&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;gt;&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;(String&lt;span style=&#34;color:#f92672&#34;&gt;[]&lt;/span&gt; args) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Thread thread01 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Thread(&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Task01());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Thread thread02 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Thread(&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Task02());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            thread01.&lt;span style=&#34;color:#a6e22e&#34;&gt;start&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            thread02.&lt;span style=&#34;color:#a6e22e&#34;&gt;start&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Thread.&lt;span style=&#34;color:#a6e22e&#34;&gt;sleep&lt;/span&gt;(2L);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Main-number: &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; threadLocalNumber.&lt;span style=&#34;color:#a6e22e&#34;&gt;get&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Main-string: &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; threadLocalString.&lt;span style=&#34;color:#a6e22e&#34;&gt;get&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        } &lt;span style=&#34;color:#66d9ef&#34;&gt;catch&lt;/span&gt; (Exception ex) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            ex.&lt;span style=&#34;color:#a6e22e&#34;&gt;printStackTrace&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Task01&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;implements&lt;/span&gt; Runnable {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;@Override&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;run&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Thread01-number: &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; threadLocalNumber.&lt;span style=&#34;color:#a6e22e&#34;&gt;get&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Set Thread01-number : 3001&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            threadLocalNumber.&lt;span style=&#34;color:#a6e22e&#34;&gt;set&lt;/span&gt;(3001);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Thread01-number: &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; threadLocalNumber.&lt;span style=&#34;color:#a6e22e&#34;&gt;get&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Set Thread01-string : hello thread01&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            threadLocalString.&lt;span style=&#34;color:#a6e22e&#34;&gt;set&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;hello thread01&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Thread01-string: &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; threadLocalString.&lt;span style=&#34;color:#a6e22e&#34;&gt;get&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Task02&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;implements&lt;/span&gt; Runnable {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;@Override&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;run&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Set Thread02-number : 3002&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            threadLocalNumber.&lt;span style=&#34;color:#a6e22e&#34;&gt;set&lt;/span&gt;(3002);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Thread02-number: &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; threadLocalNumber.&lt;span style=&#34;color:#a6e22e&#34;&gt;get&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Thread02-string: &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; threadLocalString.&lt;span style=&#34;color:#a6e22e&#34;&gt;get&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Set Thread02-string : hello thread02&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            threadLocalString.&lt;span style=&#34;color:#a6e22e&#34;&gt;set&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;hello thread02&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Thread02-string: &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; threadLocalString.&lt;span style=&#34;color:#a6e22e&#34;&gt;get&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;上述代码中一种有三个线程，分别是main方法所在的主线程、执行Task01任务的thread01线程、执行Task02任务的thread02线程。在每个线程中，我们都对这两个ThreadLocal变量进行读写操作。&lt;/p&gt;</description>
    </item>
    <item>
      <title>详解Java序列化和反序列化中各钩子方法的执行顺序</title>
      <link>https://yeecode.top/blog/66/</link>
      <pubDate>Sat, 19 Oct 2024 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/66/</guid>
      
      <description>&lt;p&gt;前面，我们通过两篇文章介绍了Java序列化和反序列化中的writeExternal、readExternal和writeReplace、readResolve四个方法。&lt;/p&gt;
&lt;p&gt;那这四个方法具体的执行顺序如何呢？&lt;/p&gt;
&lt;p&gt;我们直接通过一个示例进行演示。在示例中，我们在&lt;code&gt;UserModel05&lt;/code&gt;中同时定义了以上四种方法，如下所示。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;UserModel05&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;implements&lt;/span&gt; Externalizable {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;final&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;long&lt;/span&gt; serialVerisionUID &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; 1L;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; Integer id;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; String name;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; String description;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 省略属性的get、set方法&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;@Override&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;writeExternal&lt;/span&gt;(ObjectOutput out) &lt;span style=&#34;color:#66d9ef&#34;&gt;throws&lt;/span&gt; IOException {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;writeExternal doing ...&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        out.&lt;span style=&#34;color:#a6e22e&#34;&gt;write&lt;/span&gt;(id);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        out.&lt;span style=&#34;color:#a6e22e&#34;&gt;writeObject&lt;/span&gt;(name &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;（from writeExternal）&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;@Override&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;readExternal&lt;/span&gt;(ObjectInput in) &lt;span style=&#34;color:#66d9ef&#34;&gt;throws&lt;/span&gt; IOException, ClassNotFoundException {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;readExternal doing ...&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        id &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; in.&lt;span style=&#34;color:#a6e22e&#34;&gt;read&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (String) in.&lt;span style=&#34;color:#a6e22e&#34;&gt;readObject&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;name in file is：&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; name);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; name &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;(from readExternal)&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; Object &lt;span style=&#34;color:#a6e22e&#34;&gt;writeReplace&lt;/span&gt;() &lt;span style=&#34;color:#66d9ef&#34;&gt;throws&lt;/span&gt; ObjectStreamException {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;writeReplace doing ...&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        UserModel05 userModel &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; UserModel05();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        userModel.&lt;span style=&#34;color:#a6e22e&#34;&gt;setId&lt;/span&gt;(2);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        userModel.&lt;span style=&#34;color:#a6e22e&#34;&gt;setName&lt;/span&gt;(name &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;(from writeReplace)&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        userModel.&lt;span style=&#34;color:#a6e22e&#34;&gt;setDescription&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;description from writeReplace&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; userModel;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; Object &lt;span style=&#34;color:#a6e22e&#34;&gt;readResolve&lt;/span&gt;() &lt;span style=&#34;color:#66d9ef&#34;&gt;throws&lt;/span&gt; ObjectStreamException {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;readResolve doing ...&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        UserModel05 userModel &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; UserModel05();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        userModel.&lt;span style=&#34;color:#a6e22e&#34;&gt;setId&lt;/span&gt;(2);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        userModel.&lt;span style=&#34;color:#a6e22e&#34;&gt;setName&lt;/span&gt;(name &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;(from readResolve)&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        userModel.&lt;span style=&#34;color:#a6e22e&#34;&gt;setDescription&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;description from readResolve&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; userModel;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;然后我们对UserModel05的对象进行序列化和反序列化，如下所示。&lt;/p&gt;</description>
    </item>
    <item>
      <title>编程是件孤独的事情么？</title>
      <link>https://yeecode.top/blog/123/</link>
      <pubDate>Sun, 15 Sep 2024 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/123/</guid>
      
      <description>&lt;p&gt;是的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;编程，从来就是一件孤独的事情。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;小时候，自己学习编程的时候，周围人都还不知道编程是什么。&lt;/p&gt;
&lt;p&gt;家在农村，家里当然也没有电脑。&lt;/p&gt;
&lt;p&gt;于是，就买了几本书籍翻看。&lt;/p&gt;
&lt;p&gt;然后，想要真正练习的时候，就跑到网吧。下载一套免安装的编程软件（那时候，写的是Flash的ActionScript），在网吧的电脑里写程序。写完之后，再将程序文本复制出来，粘贴到自己的邮箱里保存。&lt;/p&gt;
&lt;p&gt;那时的网吧管理很混乱，打游戏的、爆粗口的、抽烟的、看片的……全都有&lt;/p&gt;
&lt;p&gt;或许只有我，用脏兮兮的键盘在写程序。&lt;/p&gt;
&lt;p&gt;期间，会有几个混混过来好奇的问，“你TM的在干吗的？”&lt;/p&gt;
&lt;p&gt;我就只能笑嘻嘻地回应下，“写……写点东西”。&lt;/p&gt;
&lt;p&gt;我既不敢说自己在干吗，免得他们觉着我在装逼；也不敢不说，免得他们觉着我怠慢他们。&lt;/p&gt;
&lt;p&gt;好在，我戴个眼镜文绉绉的样子，他们也懒得欺负。&lt;/p&gt;
&lt;p&gt;而最怕的事情，还是死机或者断电！一旦发生，不仅程序没保存，网吧电脑的“还原精灵”还会将你下载的编程软件给清理的干干净净。这时，伴随着网吧此起彼伏的谩骂声重新下载一遍编程软件是少不了的！&lt;/p&gt;
&lt;p&gt;而当从网吧走出，你会觉着嗓子疼的厉害，好几天都能感觉到干涩——毕竟，在二手烟组成的浓云中呼吸了大半天！&lt;/p&gt;
&lt;p&gt;那时，编程的书也很少——毕竟从小县城能买到几本就不错了，买啥看啥！&lt;/p&gt;
&lt;p&gt;不过，那时候，我还是写了一些Flash小游戏。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/123/pic/005_hu_a6cecf7ad8365e7b.webp&#34; alt=&#34;图片&#34; width=&#34;676&#34; height=&#34;493&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/123/pic/009_hu_e3a8962e9793ae52.webp&#34; alt=&#34;图片&#34; width=&#34;701&#34; height=&#34;502&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;我相信，那时候，网吧里的我是孤独的——没有人懂我在做什么！&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;上大学时，我也并不是计算机专业。&lt;/p&gt;
&lt;p&gt;但是，喜欢编程的我参加了许多的比赛，也加入了学校的网站组织——学生在线，负责维护学校的网站。&lt;/p&gt;
&lt;p&gt;每个五一、十一的假期，我都在工程训练中心度过。&lt;/p&gt;
&lt;p&gt;那时，已经有了三五个和我一样热爱编程的好友，但我，或者说我们，依旧是孤独的。在学院里、班级里，我们依旧是少数，依旧是孤独的。&lt;/p&gt;
&lt;p&gt;我曾经被关在空荡荡的实验楼，我也熬过无数的通宵……&lt;/p&gt;
&lt;p&gt;有一次，在枯燥的实验课上。我和小伙伴总感觉教材上的PLC程序有些问题——在循环一定次数之后，电梯的调度会出现问题。于是，我们俩着手证实这件事。&lt;/p&gt;
&lt;p&gt;最终，在错过了晚饭、也差点要错过全院会议的时候，我们证实了我俩的判断是正确的，并且让实验课老师相信了这一点：教材上的程序有问题。&lt;/p&gt;
&lt;p&gt;老师说，这套教材和实验器材已经用了十几年了，从没有人发现过其中的错误。&lt;/p&gt;
&lt;p&gt;当然，除了上课，我还写过很多有趣的程序：机器人、智能车、供全校师生查校车的软件、许多学院的院系官网……&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/123/pic/006_hu_36676c709219fbd5.webp&#34; alt=&#34;图片&#34; width=&#34;306&#34; height=&#34;230&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/123/pic/003_hu_ce492fd70280142f.webp&#34; alt=&#34;图片&#34; width=&#34;306&#34; height=&#34;230&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/123/pic/004_hu_e21739d27d4f6e70.webp&#34; alt=&#34;图片&#34; width=&#34;720&#34; height=&#34;325&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;这些事情，有些是我一个人做的，有些是和学院的小伙们做的，有些是和学生在线的小伙伴做的……&lt;/p&gt;
&lt;p&gt;但是，我们都是孤独的——大多数同学在追求学分，或者沉迷网游，或者享受恋爱，而我们在其中，显然是孤独的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;在大家的眼中，我们是一批怪人。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;即使工作之后，编程也是孤独的。&lt;/p&gt;
&lt;p&gt;周围的同事当然都是程序员，但是，大多数时候，我们每个人都在忙着自己的事情：想自己的问题、写自己的程序、找自己的Bug。&lt;/p&gt;
&lt;p&gt;当你遇到些实在难缠的问题时，当然可以寻求同事的帮助，但也要记得：&lt;strong&gt;光是将自己眼前面对的问题阐述给对方，就需要很大的成本。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;你需要说明：整个逻辑的背景、你的编写思路、你遇到了什么问题、你觉着是什么原因、你已经进行了怎样的调试并得到了怎样的结果，然后，对方才能给你一些建议。&lt;/p&gt;
&lt;p&gt;他们能给的只是建议。而这些建议的落实，还是需要你来进行。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;所以，编程，一直都是孤独的事情。&lt;/p&gt;
&lt;p&gt;哪怕你周围渐渐有了三五好友，哪怕你周围已经全是码农同事，&lt;/p&gt;
&lt;p&gt;而&lt;strong&gt;编程，终究是孤独的事情。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;编程，是思维的游戏！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;这也是编程的魅力！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;最后，我是程序员&lt;strong&gt;易哥&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;可以&lt;strong&gt;关注&lt;/strong&gt;我，让我们一起探索编程的魅力！&lt;/p&gt;</description>
    </item>
    <item>
      <title>如何看待程序员持续写技术博客？</title>
      <link>https://yeecode.top/blog/122/</link>
      <pubDate>Sun, 01 Sep 2024 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/122/</guid>
      
      <description>&lt;p&gt;这是一件&lt;strong&gt;难能可贵&lt;/strong&gt;的事情。&lt;/p&gt;
&lt;p&gt;首先，我自己也是写技术博客、做开源项目的。&lt;/p&gt;
&lt;p&gt;技术博客会发在知乎上，也会发在自己的主页里。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/122/pic/001_hu_79c7caafbe2cabf.webp&#34; alt=&#34;图片&#34; width=&#34;900&#34; height=&#34;674&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;而开源项目主要是通过GitHub。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/122/pic/002_hu_680aead2aa06d4a4.webp&#34; alt=&#34;图片&#34; width=&#34;900&#34; height=&#34;628&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;而我想说，无论是持续写技术博客，还是做开源项目，都是难能可贵的：一是难能，二是可贵。&lt;/p&gt;
&lt;h2 id=&#34;一难能&#34;&gt;一、难能&lt;/h2&gt;
&lt;p&gt;持续做任何事都不太容易，何况是写技术博客。它要求作者必须要有：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;强大的学习能力、融会贯通能力。&lt;/strong&gt; 毕竟是技术文章，只有不断地学习，才能赶上最新的技术点。然后还要有自己的感悟、认知，进而能够输出文章。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;恒心，足够的恒心。&lt;/strong&gt; 坚持一件事情，肯定要有恒心！不能一段时间想写，一段时间就断更。当然，这一点我做的不好，有时确实会 忙不开，然后忘记写。&lt;/li&gt;
&lt;li&gt;**耐心！**其实写文章真的需要耐心。就不提写文章时需要坐在电脑前几个小时，就算是写好了文章然后发布到各个平台，光整理格式也需要很久。所以，我真的是常常写了文章，然后懒得去发……&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;强大的内心。&lt;/strong&gt; 文章写多了，难免会有一些错字、纰漏。可能会被一些读者曲解放大、揪着不放。甚至进行攻击。不过我这个还好，我的读者都比较友好。发现自己有错误也主动认错修改。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;二可贵&#34;&gt;二、可贵&lt;/h2&gt;
&lt;p&gt;而写技术博客从作者、读者等各个角度看，都是一件很可贵的事情。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;首先，对于作者自己是一个比较好的梳理知识的途径。&lt;/strong&gt; 通过技术博客，能够让作者将原本可能混沌的知识梳理成脉络并沉淀下来。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;其次，对于读者而言，是一个学习知识的平台。&lt;/strong&gt; 对于每个技术人员，其成长的过程中肯定受益于许多的技术文章。这些文章或许让我们入门了某项技术、或许让我们了解了某个前沿、或许让我们解开了某个疑惑。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;然后我还要再说一点：技术博客因为太小众、太专业，大都没什么浏览量。而及时被相关人员看到，大家也大都是看看、收藏，而很少点赞。所以，写技术博客大多时候是一件孤独的事情。这就更加体现了其可贵之处。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;大家可以看一下我的技术文章，基本是越干货的文章点赞越少。&lt;/p&gt;
&lt;p&gt;不过，持续写技术博客确实是一件难能可贵的事情。我会继续坚持。&lt;/p&gt;
&lt;p&gt;想要学习编程的同学也可以&lt;strong&gt;关注&lt;/strong&gt;我。&lt;/p&gt;
&lt;p&gt;我是程序员易哥。&lt;/p&gt;</description>
    </item>
    <item>
      <title>如何使用Hystrix实现服务请求合并？</title>
      <link>https://yeecode.top/blog/133/</link>
      <pubDate>Sat, 17 Aug 2024 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/133/</guid>
      
      <description>&lt;h2 id=&#34;概述&#34;&gt;概述&lt;/h2&gt;
&lt;p&gt;Hystrix支持对方法合并，即将多个单次调用合并为一个批量调用。其具体的条件是这样的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;原方法接受单个入参、给出单个结果，且必须是异步的。因为只有异步的方法才能延迟执行，进而与后续的调用进行合并。&lt;/li&gt;
&lt;li&gt;批处理方法接受入参列表、给出结果列表，也就是原方法的同步且批量版本。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这样当我们调用原方法时，Hystrix就将一定时间内的请求合并起来，然后去调用批处理方法。于是，针对原方法的多次调用就被转化为了针对批量方法的单次调用。如果原方法内执行的是网络请求、数据库查询等操作，则方法合并可以极大地提升性能。&lt;/p&gt;
&lt;p&gt;在进行请求的合并操作时，如果规定的时间内只有原方法的一次调用，那Hystrix也会去调用批处理方法。这意味着原方法永远都不会被真正调用。因此，我们在编写代码时，原方法内的逻辑不需要编写，直接返回null即可。&lt;/p&gt;
&lt;p&gt;下面，我们通过示例代码进行介绍。&lt;/p&gt;
&lt;h2 id=&#34;环境配置&#34;&gt;环境配置&lt;/h2&gt;
&lt;p&gt;通过Hystrix就可以方便地实现这一操作。接下来，我们给出简单的示例。&lt;/p&gt;
&lt;p&gt;首先，准备SpringBoot项目，然后在其中引入适用于SpringBoot的hystrix包：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-xml&#34; data-lang=&#34;xml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.cloud&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-cloud-starter-netflix-hystrix&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;2.2.7.RELEASE&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;注意，如果这时候启动提示如下的错误，则基本是SpringBoot版本与hystrix版本不匹配导致的。因为hystrix是基于SpringCloud的，而SpringCloud和SpringBoot存在版本对应关系。如果对应不一致，则可能导致出现下面的错误。&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;java.lang.NoSuchMethodError: &amp;#39;void org.springframework.boot.builder.SpringApplicationBuilder.&amp;lt;init&amp;gt;(java.lang.Object[])&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这里我们给出一个可用的对应版本，即SpringBoot的2.3.5.RELEASE和spring-cloud-starter-netflix-hystrix的2.2.7.RELEASE。它们这一对是肯定没有问题的。&lt;/p&gt;
&lt;p&gt;然后，我们需要在SpringBoot的主类上增加@EnableHystrix注解，以启用Hystrix。如下所示。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@SpringBootApplication&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@EnableHystrix&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;DemoApplication&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;(String&lt;span style=&#34;color:#f92672&#34;&gt;[]&lt;/span&gt; args) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        SpringApplication.&lt;span style=&#34;color:#a6e22e&#34;&gt;run&lt;/span&gt;(DemoApplication.&lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;, args);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;环境配置就是这么简单，接下来我们就可以写业务代码了。&lt;/p&gt;
&lt;h2 id=&#34;功能实现&#34;&gt;功能实现&lt;/h2&gt;
&lt;p&gt;下面的示例代码中即展示了原方法queryUserNameById和对应的批处理方法queryUsersByIds。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@HystrixCollapser&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        batchMethod &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;queryUsersByIds&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        collapserProperties &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#75715e&#34;&gt;// 批处理中允许的最大请求数为5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#a6e22e&#34;&gt;@HystrixProperty&lt;/span&gt;(name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; HystrixPropertiesManager.&lt;span style=&#34;color:#a6e22e&#34;&gt;MAX_REQUESTS_IN_BATCH&lt;/span&gt;, value &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;5&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#75715e&#34;&gt;// 批处理的最大等待时间为500ms&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#a6e22e&#34;&gt;@HystrixProperty&lt;/span&gt;(name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; HystrixPropertiesManager.&lt;span style=&#34;color:#a6e22e&#34;&gt;TIMER_DELAY_IN_MILLISECONDS&lt;/span&gt;, value &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;500&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Future&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;String&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;queryUserNameById&lt;/span&gt;(Integer i) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@HystrixCommand&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; List&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;String&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;queryUsersByIds&lt;/span&gt;(List&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;Integer&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; idList) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;批量查询用户名，编号列表为: &amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    idList.&lt;span style=&#34;color:#a6e22e&#34;&gt;forEach&lt;/span&gt;(x &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt; System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;print&lt;/span&gt;(x.&lt;span style=&#34;color:#a6e22e&#34;&gt;toString&lt;/span&gt;() &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;;&amp;#34;&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    List&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;String&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; stringList &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; ArrayList&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;gt;&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    idList.&lt;span style=&#34;color:#a6e22e&#34;&gt;forEach&lt;/span&gt;(x &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt; stringList.&lt;span style=&#34;color:#a6e22e&#34;&gt;add&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;易哥&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; x));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; stringList;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;我们需要在原方法上增加@HystrixCollapser注解，并在批处理方法上增加@HystrixCommand注解。这样，当我们调用原方法时，Hystrix会合并这些调用转而去调用批处理方法。调用批处理方法的时间点有一下几个：&lt;/p&gt;</description>
    </item>
    <item>
      <title>如何使用Hystrix实现服务请求缓存？</title>
      <link>https://yeecode.top/blog/132/</link>
      <pubDate>Wed, 17 Jan 2024 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/132/</guid>
      
      <description>&lt;h2 id=&#34;概述&#34;&gt;概述&lt;/h2&gt;
&lt;p&gt;Hystrix支持在方法层面增加缓存，这样，当下次在请求该方法时，如果存在缓存，则可以直接走缓存而不用执行方法。&lt;/p&gt;
&lt;h2 id=&#34;环境配置&#34;&gt;环境配置&lt;/h2&gt;
&lt;p&gt;通过Hystrix就可以方便地实现这一操作。接下来，我们给出简单的示例。&lt;/p&gt;
&lt;p&gt;首先，准备SpringBoot项目，然后在其中引入适用于SpringBoot的hystrix包：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-xml&#34; data-lang=&#34;xml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.cloud&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-cloud-starter-netflix-hystrix&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;2.2.7.RELEASE&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;注意，如果这时候启动提示如下的错误，则基本是SpringBoot版本与hystrix版本不匹配导致的。因为hystrix是基于SpringCloud的，而SpringCloud和SpringBoot存在版本对应关系。如果对应不一致，则可能导致出现下面的错误。&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;java.lang.NoSuchMethodError: &amp;#39;void org.springframework.boot.builder.SpringApplicationBuilder.&amp;lt;init&amp;gt;(java.lang.Object[])&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这里我们给出一个可用的对应版本，即SpringBoot的2.3.5.RELEASE和spring-cloud-starter-netflix-hystrix的2.2.7.RELEASE。它们这一对是肯定没有问题的。&lt;/p&gt;
&lt;p&gt;然后，我们需要在SpringBoot的主类上增加@EnableHystrix注解，以启用Hystrix。如下所示。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@SpringBootApplication&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@EnableHystrix&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;DemoApplication&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;(String&lt;span style=&#34;color:#f92672&#34;&gt;[]&lt;/span&gt; args) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        SpringApplication.&lt;span style=&#34;color:#a6e22e&#34;&gt;run&lt;/span&gt;(DemoApplication.&lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;, args);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;环境配置就是这么简单，接下来我们就可以写业务代码了。&lt;/p&gt;
&lt;h2 id=&#34;功能实现&#34;&gt;功能实现&lt;/h2&gt;
&lt;p&gt;缓存时，默认会将所有的入参共同作为缓存的键，也就是说下次调用时只有全部入参完全一致才会走缓存。另外，我们也可以使用@CacheKey注解来指定缓存的键。此外，我们还可以指定一个方法，以该方法的返回值作为缓存的键。这种通过方法指定键的方式具有最高的优先级。要注意的是，这个方法必须和原方法在同一个类中，且入参与原方法完全一致、返回值必须为String。&lt;/p&gt;
&lt;p&gt;为了能够清除缓存，我们还可以指定一个缓存清理方法。当调用该方法时，指定方法的缓存将会被清理。&lt;/p&gt;
&lt;p&gt;我们先给出请求缓存的示例代码：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@CacheResult&lt;/span&gt;(cacheKeyMethod &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;getUserCacheKey&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@HystrixCommand&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        commandKey &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;CacheService_queryUserNameById&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        commandProperties &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#75715e&#34;&gt;// 启用请求缓存功能（默认也是启用的）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#a6e22e&#34;&gt;@HystrixProperty&lt;/span&gt;(name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; HystrixPropertiesManager.&lt;span style=&#34;color:#a6e22e&#34;&gt;REQUEST_CACHE_ENABLED&lt;/span&gt;, value &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;true&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;String &lt;span style=&#34;color:#a6e22e&#34;&gt;queryUserNameById&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; i) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;正在查询用户名，用户编号为&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; i);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;易哥&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; i;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@CacheRemove&lt;/span&gt;(commandKey &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;CacheService_queryUserNameById&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        cacheKeyMethod &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;getUserCacheKey&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@HystrixCommand&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;clearUserResult&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; i) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;入参&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; i &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;对应的缓存已清空 &amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;String &lt;span style=&#34;color:#a6e22e&#34;&gt;getUserCacheKey&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; i) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;CACHE_KEY_&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; i;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在介绍上述示例代码的同时，我们顺便详细介绍两个注解。注解的介绍参考《高性能架构之道（第二版）》这本书，本文的示例代码也来自这本书。大家如果想要系统学习Hystrix的话，建议读下该书。&lt;/p&gt;</description>
    </item>
    <item>
      <title>详解Java序列化和反序列化中的writeReplace方法和readResolve方法</title>
      <link>https://yeecode.top/blog/65/</link>
      <pubDate>Sun, 16 Jul 2023 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/65/</guid>
      
      <description>&lt;p&gt;在前面的文章中，我们已经介绍了Java序列化和反序列化中的writeExternal方法和readExternal方法。在进行序列化和反序列化的目标类（可以是继承Serializable接口，也可以是继承Externalizable接口）中，还可以定义两个方法：writeReplace方法和readResolve方法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Object writeReplace()&lt;/code&gt;：如果一个类中定义了该方法，那对该类的对象进行序列化操作前会先调用该方法。最终该方法返回的对象将被序列化。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Object readResolve()&lt;/code&gt;：如果一个类中定义了该方法，那对该类的对象进行反序列操作后会调用该方法。最终该方法返回的对象将作为反序列化的结果。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;writeReplace方法和readResolve方法实际上为对象的序列化和反序列化提供了一种“偷梁换柱”的能力：无论实际对象如何，在序列化时都以writeReplace方法的返回值为准；无论序列化数据如何，在反序列化时都以readResolve方法的返回值为准。&lt;/p&gt;
&lt;p&gt;我们以&lt;code&gt;writeReplace&lt;/code&gt;方法为例，演示下这种能力。&lt;/p&gt;
&lt;p&gt;我们在UserModel03类中定义了writeReplace方法，并在writeReplace方法方法中返回一个全新的对象，如下所示。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;UserModel03&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;implements&lt;/span&gt; Serializable {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;final&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;long&lt;/span&gt; serialVerisionUID &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; 123L;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; Integer id;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; String name;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; String description;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 省略属性的get、set方法&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; Object &lt;span style=&#34;color:#a6e22e&#34;&gt;writeReplace&lt;/span&gt;() &lt;span style=&#34;color:#66d9ef&#34;&gt;throws&lt;/span&gt; ObjectStreamException {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;writeReplace doing ...&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        UserModel03 userModel &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; UserModel03();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        userModel.&lt;span style=&#34;color:#a6e22e&#34;&gt;setId&lt;/span&gt;(2);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        userModel.&lt;span style=&#34;color:#a6e22e&#34;&gt;setName&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;yeecode&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        userModel.&lt;span style=&#34;color:#a6e22e&#34;&gt;setDescription&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;description from writeReplace&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; userModel;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;然后我们对UserModel03的对象进行序列化和反序列化，如下所示。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;demo03&lt;/span&gt;() &lt;span style=&#34;color:#66d9ef&#34;&gt;throws&lt;/span&gt; Exception {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;run demo03:&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    UserModel03 userModel03 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; UserModel03();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    userModel03.&lt;span style=&#34;color:#a6e22e&#34;&gt;setId&lt;/span&gt;(1);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    userModel03.&lt;span style=&#34;color:#a6e22e&#34;&gt;setName&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;易哥&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ObjectOutputStream oos &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; ObjectOutputStream(&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; FileOutputStream(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;m3.tempdata&amp;#34;&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    oos.&lt;span style=&#34;color:#a6e22e&#34;&gt;writeObject&lt;/span&gt;(userModel03);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    oos.&lt;span style=&#34;color:#a6e22e&#34;&gt;flush&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    oos.&lt;span style=&#34;color:#a6e22e&#34;&gt;close&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;↑write;↓read&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ObjectInputStream ois &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; ObjectInputStream(&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; FileInputStream(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;m3.tempdata&amp;#34;&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    UserModel03 newUser &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (UserModel03) ois.&lt;span style=&#34;color:#a6e22e&#34;&gt;readObject&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;newUser:&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; newUser.&lt;span style=&#34;color:#a6e22e&#34;&gt;getId&lt;/span&gt;() &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;-&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; newUser.&lt;span style=&#34;color:#a6e22e&#34;&gt;getName&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;最终结果如图所示。&lt;/p&gt;</description>
    </item>
    <item>
      <title>详解Java序列化和反序列化中的writeExternal方法和readExternal方法</title>
      <link>https://yeecode.top/blog/64/</link>
      <pubDate>Sun, 18 Jun 2023 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/64/</guid>
      
      <description>&lt;p&gt;要表明一个类的对象是可序列化的则必须要继承Serializable接口或者Externalizable接口，而且Externalizable接口是Serializable接口的子接口。这些内容我们在前面的文章中均已经介绍过，还给出了继承Serializable接口进行序列化和反序列化的示例。&lt;/p&gt;
&lt;p&gt;继承Serializable接口实现序列化和反序列化是非常简单的，目标类除了继承Serializable接口外不需要任何其他的操作，整个序列化和反序列化的过程由Java内部的机制完成。而继承Externalizable接口实现序列化和反序列化则支持自定义序列化和反序列化的方法。Externalizable接口包含两个抽象方法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;void writeExternal(ObjectOutput out)&lt;/code&gt;: 该方法在目标对象序列化时调用。方法中可以调用DataOutput（入参ObjectOutput的父类）的方法来保存其基本值，或调用ObjectOutput的writeObject方法来保存对象、字符串和数组。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;void readExternal(ObjectInput in)&lt;/code&gt;：该方法在目标对象反序列化时调用。方法中调用DataInput（入参ObjectInput的父类）的方法来恢复其基础类型，或调用readObject来恢复对象、字符串和数组。需要注意的是readExternal方法读取数据时，必须与writeExternal方法写入数据时的顺序和类型一致。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我们通过示例来说明writeExternal方法和readExternal方法的作用。在下面代码中，我们设置了UserModel02类的writeExternal方法和readExternal方法。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;UserModel02&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;implements&lt;/span&gt; Externalizable {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;final&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;long&lt;/span&gt; serialVerisionUID &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; 1L;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; Integer id;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; String name;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; String description;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 省略属性的get、set方法&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;@Override&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;writeExternal&lt;/span&gt;(ObjectOutput out) &lt;span style=&#34;color:#66d9ef&#34;&gt;throws&lt;/span&gt; IOException {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;writeExternal doing ...&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        out.&lt;span style=&#34;color:#a6e22e&#34;&gt;write&lt;/span&gt;(id); &lt;span style=&#34;color:#75715e&#34;&gt;// DataOutput中的方法&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        out.&lt;span style=&#34;color:#a6e22e&#34;&gt;writeObject&lt;/span&gt;(name &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;(from writeExternal)&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;@Override&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;readExternal&lt;/span&gt;(ObjectInput in) &lt;span style=&#34;color:#66d9ef&#34;&gt;throws&lt;/span&gt; IOException, ClassNotFoundException {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;readExternal doing ...&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        id &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; in.&lt;span style=&#34;color:#a6e22e&#34;&gt;read&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (String) in.&lt;span style=&#34;color:#a6e22e&#34;&gt;readObject&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;name in file is：&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; name);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; name &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;(from readExternal)&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;然后我们对上述类的实例进行序列化和反序列化，过程如下所示。&lt;/p&gt;</description>
    </item>
    <item>
      <title>如何使用Hystrix实现服务请求限流？</title>
      <link>https://yeecode.top/blog/131/</link>
      <pubDate>Wed, 17 May 2023 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/131/</guid>
      
      <description>&lt;h2 id=&#34;概述&#34;&gt;概述&lt;/h2&gt;
&lt;p&gt;限流就是为某个方法增加流量阈值，当超过该阈值时，外部请求转而去调用备用方法，进而降低了原方法的压力，防止原方法因为过高的流量崩溃或给系统带来较大的时延。&lt;/p&gt;
&lt;h2 id=&#34;环境配置&#34;&gt;环境配置&lt;/h2&gt;
&lt;p&gt;通过Hystrix就可以方便地实现这一操作。接下来，我们给出简单的示例。&lt;/p&gt;
&lt;p&gt;首先，准备SpringBoot项目，然后在其中引入适用于SpringBoot的hystrix包：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-xml&#34; data-lang=&#34;xml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.cloud&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-cloud-starter-netflix-hystrix&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;2.2.7.RELEASE&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;注意，如果这时候启动提示如下的错误，则基本是SpringBoot版本与hystrix版本不匹配导致的。因为hystrix是基于SpringCloud的，而SpringCloud和SpringBoot存在版本对应关系。如果对应不一致，则可能导致出现下面的错误。&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;java.lang.NoSuchMethodError: &amp;#39;void org.springframework.boot.builder.SpringApplicationBuilder.&amp;lt;init&amp;gt;(java.lang.Object[])&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这里我们给出一个可用的对应版本，即SpringBoot的2.3.5.RELEASE和spring-cloud-starter-netflix-hystrix的2.2.7.RELEASE。它们这一对是肯定没有问题的。&lt;/p&gt;
&lt;p&gt;然后，我们需要在SpringBoot的主类上增加@EnableHystrix注解，以启用Hystrix。如下所示。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@SpringBootApplication&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@EnableHystrix&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;DemoApplication&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;(String&lt;span style=&#34;color:#f92672&#34;&gt;[]&lt;/span&gt; args) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        SpringApplication.&lt;span style=&#34;color:#a6e22e&#34;&gt;run&lt;/span&gt;(DemoApplication.&lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;, args);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;环境配置就是这么简单，接下来我们就可以写业务代码了。&lt;/p&gt;
&lt;h2 id=&#34;功能实现&#34;&gt;功能实现&lt;/h2&gt;
&lt;p&gt;例如我们编写如下的方法，也就是原方法。在原方法中，我们故意让它处理时长较长，以模拟复杂的操作。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@HystrixCommand&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        commandKey &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;LimiterService_queryUserNameById&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        fallbackMethod &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;fallBackForSayHi&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        threadPoolProperties &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#75715e&#34;&gt;// 核心线程设置为5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#a6e22e&#34;&gt;@HystrixProperty&lt;/span&gt;(name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; HystrixPropertiesManager.&lt;span style=&#34;color:#a6e22e&#34;&gt;CORE_SIZE&lt;/span&gt;, value &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;5&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#75715e&#34;&gt;// 最大排队队列长度为10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#a6e22e&#34;&gt;@HystrixProperty&lt;/span&gt;(name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; HystrixPropertiesManager.&lt;span style=&#34;color:#a6e22e&#34;&gt;MAX_QUEUE_SIZE&lt;/span&gt;, value &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;10&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#75715e&#34;&gt;// 队列中最多有2个任务排队，超过该数目的任务则拒绝&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#a6e22e&#34;&gt;@HystrixProperty&lt;/span&gt;(name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; HystrixPropertiesManager.&lt;span style=&#34;color:#a6e22e&#34;&gt;QUEUE_SIZE_REJECTION_THRESHOLD&lt;/span&gt;, value &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;2&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        commandProperties &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#75715e&#34;&gt;// 关掉熔断器，避免熔断器影响&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#a6e22e&#34;&gt;@HystrixProperty&lt;/span&gt;(name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; HystrixPropertiesManager.&lt;span style=&#34;color:#a6e22e&#34;&gt;CIRCUIT_BREAKER_ENABLED&lt;/span&gt;, value &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;false&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#75715e&#34;&gt;// 超时判定时间设置为2000ms，即下方的1000ms延时不会引发超时&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#a6e22e&#34;&gt;@HystrixProperty&lt;/span&gt;(name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; HystrixPropertiesManager.&lt;span style=&#34;color:#a6e22e&#34;&gt;EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS&lt;/span&gt;, value &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;2000&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;String &lt;span style=&#34;color:#a6e22e&#34;&gt;queryUserNameById&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; i) &lt;span style=&#34;color:#66d9ef&#34;&gt;throws&lt;/span&gt; InterruptedException {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;正在查询用户名，用户编号为&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; i &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;。该查询所在的线程名：&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; Thread.&lt;span style=&#34;color:#a6e22e&#34;&gt;currentThread&lt;/span&gt;().&lt;span style=&#34;color:#a6e22e&#34;&gt;getName&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 故意增加处理时延，以模拟堵塞&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Thread.&lt;span style=&#34;color:#a6e22e&#34;&gt;sleep&lt;/span&gt;(1000);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;易哥&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; i;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;我们在原方法上增加@HystrixCommand注解。其中比较关键的是fallbackMethod参数，我们指向了该原方法对应的备用方法，即当原方法发生错误时要调用的方法。备用方法如下所示。&lt;/p&gt;</description>
    </item>
    <item>
      <title>无所不能的javassist和基于它实现的动态代理</title>
      <link>https://yeecode.top/blog/63/</link>
      <pubDate>Sun, 14 May 2023 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/63/</guid>
      
      <description>&lt;p&gt;我们已经介绍了基于反射的动态代理和基于cglib(Code Generation Library，即代码生成库) 的动态代理。本文我们将介绍javassist，基于它也可以实现动态代理。&lt;/p&gt;
&lt;p&gt;cglib是基于底层的ASM框架来实现Java字节码的修改。而javassist和ASM类似，它是也是一个开源的用来创建、修改Java字节码的类库，能实现类的创建、方法的修改、继承关系的设置等一系列的操作。&lt;/p&gt;
&lt;p&gt;相比于ASM，javassist的优势是学习成本低，可以根据java代码生成字节码，而不需要直接操作字节码。&lt;/p&gt;
&lt;p&gt;javassist的使用虽然比ASM简单，但也并不是太容易。我们通过一个示例来展示下javassist的使用，以“无中生有”的方式创建一个类，并给类设置属性和方法。整个示例如如下所示。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;(String&lt;span style=&#34;color:#f92672&#34;&gt;[]&lt;/span&gt; args) &lt;span style=&#34;color:#66d9ef&#34;&gt;throws&lt;/span&gt; Exception {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ClassPool pool &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ClassPool.&lt;span style=&#34;color:#a6e22e&#34;&gt;getDefault&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 定义一个类&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    CtClass userCtClazz &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; pool.&lt;span style=&#34;color:#a6e22e&#34;&gt;makeClass&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;com.github.yeecode.mybatisdemo.User&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 创建name属性&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    CtField nameField &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; CtField(pool.&lt;span style=&#34;color:#a6e22e&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;java.lang.String&amp;#34;&lt;/span&gt;), &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;name&amp;#34;&lt;/span&gt;, userCtClazz);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    userCtClazz.&lt;span style=&#34;color:#a6e22e&#34;&gt;addField&lt;/span&gt;(nameField);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 创建name的set方法&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    CtMethod setMethod &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; CtNewMethod.&lt;span style=&#34;color:#a6e22e&#34;&gt;make&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;public void setName(String name) { this.name = name;}&amp;#34;&lt;/span&gt;, userCtClazz);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    userCtClazz.&lt;span style=&#34;color:#a6e22e&#34;&gt;addMethod&lt;/span&gt;(setMethod);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 创建sayHello方法&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    CtMethod sayHello &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; CtNewMethod.&lt;span style=&#34;color:#a6e22e&#34;&gt;make&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;public String sayHello() { return \&amp;#34;Hello, I am \&amp;#34; + this.name ;}&amp;#34;&lt;/span&gt;, userCtClazz);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    userCtClazz.&lt;span style=&#34;color:#a6e22e&#34;&gt;addMethod&lt;/span&gt;(sayHello);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Class&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;?&amp;gt;&lt;/span&gt; userClazz &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; userCtClazz.&lt;span style=&#34;color:#a6e22e&#34;&gt;toClass&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 创建一个对象&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Object user &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; userClazz.&lt;span style=&#34;color:#a6e22e&#34;&gt;newInstance&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 为对象设置name值&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Method&lt;span style=&#34;color:#f92672&#34;&gt;[]&lt;/span&gt; methods &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; userClazz.&lt;span style=&#34;color:#a6e22e&#34;&gt;getMethods&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (Method method: methods){
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (method.&lt;span style=&#34;color:#a6e22e&#34;&gt;getName&lt;/span&gt;().&lt;span style=&#34;color:#a6e22e&#34;&gt;equals&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;setName&amp;#34;&lt;/span&gt;)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            method.&lt;span style=&#34;color:#a6e22e&#34;&gt;invoke&lt;/span&gt;(user,&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;易哥&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 调用对象sayHello方法&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (Method method: methods){
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (method.&lt;span style=&#34;color:#a6e22e&#34;&gt;getName&lt;/span&gt;().&lt;span style=&#34;color:#a6e22e&#34;&gt;equals&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;sayHello&amp;#34;&lt;/span&gt;)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            String result &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (String) method.&lt;span style=&#34;color:#a6e22e&#34;&gt;invoke&lt;/span&gt;(user);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(result);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;运行上述代码，可以看到控制台打印出下图所示的输出结果。&lt;/p&gt;</description>
    </item>
    <item>
      <title>MyBatis中灵活的主键自增功能使用及其原理详解</title>
      <link>https://yeecode.top/blog/79/</link>
      <pubDate>Wed, 03 May 2023 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/79/</guid>
      
      <description>&lt;p&gt;在进行数据插入操作时，经常需要一个自增生成的主键编号，这既能保证主键的唯一性，又能保证主键的连续性。&lt;/p&gt;
&lt;p&gt;许多数据库都支持主键自增功能，如MySQL数据库、SQL Server数据库等。当然也有一些数据库不支持主键自增功能，如Oracle数据库。MyBatis的executor包中的keygen子包兼容以上这两种情况。&lt;/p&gt;
&lt;p&gt;接下来我们通过MyBatis源码详细了解MyBatis的主键自增功能的使用方法和实现原理。相关内容均参考《通用源码阅读指导书——MyBatis源码详解》一书。&lt;/p&gt;
&lt;p&gt;主键自增功能在MyBatis的executor包的keygen子包中，一共包含下图所示的四个类或接口。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/79/pic/KeyGenerator_hu_550ecb42842fff3a.webp&#34; alt=&#34;keygen子包类图&#34; width=&#34;900&#34; height=&#34;303&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;KeyGenerator&lt;/code&gt;作为接口提供了两个方法，即processBefore方法和processAfter方法。关于这两个方法的实现细节我们会在接下来的小节中分别介绍。&lt;/p&gt;
&lt;p&gt;NoKeyGenerator不提供任何主键自增功能，其processBefore方法和processAfter方法均为空方法。因此，我们不再介绍该类。&lt;/p&gt;
&lt;h2 id=&#34;1-主键自增的配置与生效&#34;&gt;1 主键自增的配置与生效&lt;/h2&gt;
&lt;p&gt;在阅读MyBatis的主键自增相关代码之前，我们先了解在怎么在MyBatis中启用主键自增功能。&lt;/p&gt;
&lt;p&gt;通过KeyGenerator的类图我们知道，MyBatis中的KeyGenerator实现类一共有三种：Jdbc3KeyGenerator、SelectKeyGenerator、NoKeyGenerator。在实际使用时，这三种实现中只能有一种实现生效。而如果生效的是NoKeyGenerator，则代表不具有任何的主键自增功能。&lt;/p&gt;
&lt;p&gt;要启用Jdbc3KeyGenerator可以在配置文件中增加如下所示的配置：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;setting name&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;useGeneratedKeys&amp;#34;&lt;/span&gt; value&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;true&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;或者直接在相关语句上启用useGeneratedKeys，如下所示。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;insert id&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;addUser_A&amp;#34;&lt;/span&gt; parameterType&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;User&amp;#34;&lt;/span&gt; useGeneratedKeys&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;true&amp;#34;&lt;/span&gt; keyProperty&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;id&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    INSERT INTO &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;user&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;name&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;,&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;email&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;,&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;age&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;,&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;sex&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;,&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;schoolName&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    VALUES
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;{name},&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;{email},&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;{age},&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;{sex},&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;{schoolName})
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/&lt;/span&gt;insert&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果要启用SelectKeyGenerator则需要在SQL语句前加一段&lt;code&gt;selectKey&lt;/code&gt;标签，如下所示。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;insert id&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;addUser_B&amp;#34;&lt;/span&gt; parameterType&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;User&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;selectKey resultType&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;java.lang.Integer&amp;#34;&lt;/span&gt; keyProperty&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;id&amp;#34;&lt;/span&gt; order&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;AFTER&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        SELECT &lt;span style=&#34;color:#a6e22e&#34;&gt;LAST_INSERT_ID&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/&lt;/span&gt;selectKey&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    INSERT INTO &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;user&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;name&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;,&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;email&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;,&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;age&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;,&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;sex&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;,&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;schoolName&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    VALUES
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;{name},&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;{email},&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;{age},&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;{sex},&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;{schoolName})
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/&lt;/span&gt;insert&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果某一条语句同时设置了useGeneratedKeys和&lt;code&gt;selectKey&lt;/code&gt;的话，则是后者生效。&lt;/p&gt;
&lt;p&gt;以上各个配置项的作用范围、优先级等结论，均可以通过阅读下面的源码得出。下面的源码在&lt;code&gt;XMLStatementBuilder&lt;/code&gt;类中，在集成开发软件的帮助下，我们可以通过查找KeyGenerator的引用找到。这段代码就是主键自增功能被解析的地方。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 处理SelectKey节点，在这里会将KeyGenerator加入到Configuration.keyGenerators中&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;processSelectKeyNodes(id, parameterTypeClass, langDriver);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 原注释：此时，&amp;lt;selectKey&amp;gt; 和 &amp;lt;include&amp;gt; 节点均已被解析完毕并被删除&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 进行SQL解析&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;KeyGenerator keyGenerator;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;String keyStatementId &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; id &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; SelectKeyGenerator.&lt;span style=&#34;color:#a6e22e&#34;&gt;SELECT_KEY_SUFFIX&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;keyStatementId &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; builderAssistant.&lt;span style=&#34;color:#a6e22e&#34;&gt;applyCurrentNamespace&lt;/span&gt;(keyStatementId, &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 判断是否已经有解析好的KeyGenerator&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (configuration.&lt;span style=&#34;color:#a6e22e&#34;&gt;hasKeyGenerator&lt;/span&gt;(keyStatementId)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  keyGenerator &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; configuration.&lt;span style=&#34;color:#a6e22e&#34;&gt;getKeyGenerator&lt;/span&gt;(keyStatementId);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;} &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// 全局或者本语句只要启用自动key生成，则使用key生成&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  keyGenerator &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; context.&lt;span style=&#34;color:#a6e22e&#34;&gt;getBooleanAttribute&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;useGeneratedKeys&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      configuration.&lt;span style=&#34;color:#a6e22e&#34;&gt;isUseGeneratedKeys&lt;/span&gt;() &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; SqlCommandType.&lt;span style=&#34;color:#a6e22e&#34;&gt;INSERT&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;equals&lt;/span&gt;(sqlCommandType))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt; Jdbc3KeyGenerator.&lt;span style=&#34;color:#a6e22e&#34;&gt;INSTANCE&lt;/span&gt; : NoKeyGenerator.&lt;span style=&#34;color:#a6e22e&#34;&gt;INSTANCE&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;processSelectKeyNodes方法经过最终解析了&lt;code&gt;selectKey&lt;/code&gt;节点的信息并在解析完成后将&lt;code&gt;selectKey&lt;/code&gt;节点从XML中删除了。而解析出来的信息而放入了&lt;code&gt;configuration&lt;/code&gt;的&lt;code&gt;keyGenerators&lt;/code&gt;中。之后，如果没有解析好的KeyGenerator，则会根据useGeneratedKeys判断是否使用Jdbc3KeyGenerator。&lt;/p&gt;</description>
    </item>
    <item>
      <title>MyBaits的二级缓存机制及其实现原理详解</title>
      <link>https://yeecode.top/blog/78/</link>
      <pubDate>Tue, 02 May 2023 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/78/</guid>
      
      <description>&lt;p&gt;MyBatis具有两级缓存，那它的两级缓存是如何实现的呢？本文将详细讨论相关的实现细节。&lt;/p&gt;
&lt;p&gt;本文内容参考《通用源码阅读指导书——MyBatis源码详解》一书。&lt;/p&gt;
&lt;p&gt;在介绍MyBatis的缓存机制之前，我们先提前了解下MyBatis的Executor接口的概况。&lt;/p&gt;
&lt;p&gt;Executor接口是执行器接口，它负责进行数据库查询等操作。它有两个直接子类，CachingExecute类和BaseExecute类：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CachingExecute是一个装饰器类，它能够为执行器实现类增加缓存功能。&lt;/li&gt;
&lt;li&gt;BaseExecute类是所有实际执行器类的基类，它有SimpleExecutor、BatchExecutor、ReuseExecutor、ClosedExecutor四个子类。而其中的ClosedExecutor类本身没有实际功能，我们暂时忽略它。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因此，最终我们可以给出下图所示的Executor接口的简化类图。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/78/pic/SimpleExecutor_hu_2eb7dbe154a7ce09.webp&#34; alt=&#34;Executor接口的简化类图&#34; width=&#34;839&#34; height=&#34;373&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;1-一级缓存&#34;&gt;1 一级缓存&lt;/h2&gt;
&lt;p&gt;MyBatis的一级缓存又叫本地缓存，它的结构和使用都比较简单，与它相关的配置项有两个。&lt;/p&gt;
&lt;p&gt;一个是在配置文件的settings节点下，我们可以增加如下所示的配置语句来改变一级缓存的作用范围。配置值的可选项有SESSION与STATEMENT，分别对应了一次会话和一条语句。一级缓存的默认作用范围是SESSION。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;setting name&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;localCacheScope&amp;#34;&lt;/span&gt; value&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;SESSION&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;二是可以在映射文件的数据库操作节点内增加flushCache属性项，如下所示，该属性可以配置true和false。当设置为true时，MyBatis会在该数据库操作执行前清空一二级缓存。该属性的默认值为false。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;select id&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;queryUserBySchoolName&amp;#34;&lt;/span&gt; resultType&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;User&amp;#34;&lt;/span&gt; flushCache&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;false&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  SELECT &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; FROM &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;user&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt; WHERE schoolName &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;{schoolName}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/&lt;/span&gt;select&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;了解了MyBatis一级缓存的配置后，我们查看一级缓存的源码。&lt;/p&gt;
&lt;p&gt;一级缓存功能由BaseExecutor类实现。BaseExecutor类作为实际执行器的基类，为所有实际执行器提供一些通用的基本功能，在这里增加了缓存也就意味着每个实际执行器都具有这一级缓存。&lt;/p&gt;
&lt;p&gt;在BaseExecutor内，我们可以看到与一级缓存相关的两个属性，分别是localCache和localOutputParameterCache，如下所示。这两个属性使用的都是没有经过任何装饰器装饰的PerpetualCache对象。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 查询操作的结果缓存&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;protected&lt;/span&gt; PerpetualCache localCache;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Callable查询的输出参数缓存&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;protected&lt;/span&gt; PerpetualCache localOutputParameterCache;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这两个变量中，localCache缓存的是数据库查询操作的结果。对于CALLABLE形式的语句，因为最终向上返回的是输出参数，便使用localOutputParameterCache直接缓存的输出参数。&lt;/p&gt;
&lt;p&gt;因为localCache和localOutputParameterCache都是Executor的属性，不可能超出Executor的作用范围。而Executor归属SqlSession，因此第一级缓存的最大作用范围便是SqlSession，即一次会话。&lt;/p&gt;
&lt;p&gt;下面代码给出了BaseExecutor中query操作的源码，通过它我们可以详细了解一级缓存的作用原理以及localCacheScope配置、flushCache配置如何生效。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/**
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; * 查询数据库中的数据
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; * @param ms 映射语句
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; * @param parameter 参数对象
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; * @param rowBounds 翻页限制条件
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; * @param resultHandler 结果处理器
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; * @param key 缓存的键
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; * @param boundSql 查询语句
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; * @param &amp;lt;E&amp;gt; 结果类型
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; * @return 结果列表
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; * @throws SQLException
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@SuppressWarnings&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;unchecked&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@Override&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;E&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; List&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;E&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;query&lt;/span&gt;(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) &lt;span style=&#34;color:#66d9ef&#34;&gt;throws&lt;/span&gt; SQLException {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ErrorContext.&lt;span style=&#34;color:#a6e22e&#34;&gt;instance&lt;/span&gt;().&lt;span style=&#34;color:#a6e22e&#34;&gt;resource&lt;/span&gt;(ms.&lt;span style=&#34;color:#a6e22e&#34;&gt;getResource&lt;/span&gt;()).&lt;span style=&#34;color:#a6e22e&#34;&gt;activity&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;executing a query&amp;#34;&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;object&lt;/span&gt;(ms.&lt;span style=&#34;color:#a6e22e&#34;&gt;getId&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (closed) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 执行器已经关闭&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; ExecutorException(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Executor was closed.&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (queryStack &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; 0 &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; ms.&lt;span style=&#34;color:#a6e22e&#34;&gt;isFlushCacheRequired&lt;/span&gt;()) { &lt;span style=&#34;color:#75715e&#34;&gt;// 新的查询栈且要求清除缓存&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 清除一级缓存&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    clearLocalCache();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  List&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;E&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; list;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    queryStack&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 尝试从本地缓存获取结果&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    list &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; resultHandler &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt; (List&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;E&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;) localCache.&lt;span style=&#34;color:#a6e22e&#34;&gt;getObject&lt;/span&gt;(key) : &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (list &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;// 本地缓存中有结果，则对于CALLABLE语句还需要绑定到IN/INOUT参数上&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;// 本地缓存没有结果，故需要查询数据库&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      list &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  } &lt;span style=&#34;color:#66d9ef&#34;&gt;finally&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    queryStack&lt;span style=&#34;color:#f92672&#34;&gt;--&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (queryStack &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; 0) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 懒加载操作的处理&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (DeferredLoad deferredLoad : deferredLoads) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      deferredLoad.&lt;span style=&#34;color:#a6e22e&#34;&gt;load&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    deferredLoads.&lt;span style=&#34;color:#a6e22e&#34;&gt;clear&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 如果本地缓存的作用域为STATEMENT，则立刻清除本地缓存&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (configuration.&lt;span style=&#34;color:#a6e22e&#34;&gt;getLocalCacheScope&lt;/span&gt;() &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; LocalCacheScope.&lt;span style=&#34;color:#a6e22e&#34;&gt;STATEMENT&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      clearLocalCache();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; list;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;数据库操作中的INSERT、UPDATE、DELETE操作都对应了BaseExecutor中的update方法。在update方法中，会引发一级缓存的更新。下面代码展示了BaseExecutor中update方法的源码。&lt;/p&gt;</description>
    </item>
    <item>
      <title>通过源码分析MyBatis如何完成XML配置文件的解析</title>
      <link>https://yeecode.top/blog/77/</link>
      <pubDate>Sun, 16 Apr 2023 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/77/</guid>
      
      <description>&lt;p&gt;MyBatis的配置文件与映射文件均是XML文件，那MyBatis是如何解析这些XML文件的呢？而且，XML文件中还存在“&lt;code&gt;${ }&lt;/code&gt;”等包裹的变量名，这些变量又是怎么被替换的呢？
本文我们就通过MyBatis的源码详细分析这一过程。&lt;/p&gt;
&lt;p&gt;相关内容参考《通用源码阅读指导书——MyBatis源码详解》一书。&lt;/p&gt;
&lt;p&gt;MyBatis中的&lt;code&gt;parsing&lt;/code&gt;包就是用来进行XML文件解析的包。在解析XML文件的过程，&lt;code&gt;XPathParser&lt;/code&gt;类与&lt;code&gt;XNode&lt;/code&gt;类是两个最为关键的类，下图给出了这两个类主要关系的类图。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/77/pic/xnode_hu_71ebdc4f0f0c400e.webp&#34; alt=&#34;XPathParser类与XNode类的类图&#34; width=&#34;566&#34; height=&#34;229&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;通过图可以看出，XPathParser类中封装了“&lt;code&gt;javax.xml.xpath.XPath&lt;/code&gt;”类的对象。《通用源码阅读指导书——MyBatis源码详解》的11.1.2 XPath章节分析了XPath，XPath是XML解析的利器，因此&lt;code&gt;XPathParser&lt;/code&gt;类便具有了XML解析的能力。&lt;/p&gt;
&lt;p&gt;下面代码给出了&lt;code&gt;XPathParser&lt;/code&gt;类的带注释的属性。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 代表要解析的整个XML文档&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;final&lt;/span&gt; Document document;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 是否开启验证&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;boolean&lt;/span&gt; validation;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// EntityResolver，通过它可以声明寻找DTD文件的方法，例如通过本地寻找，而不是只能通过网络下载DTD文件&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; EntityResolver entityResolver;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// MyBatis配置文件中的properties节点的信息&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; Properties variables;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// javax.xml.xpath.XPath工具&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; XPath xpath;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;有必要说明一下，上述“&lt;code&gt;private Properties variables&lt;/code&gt;”属性存储的内容就是MyBatis配置文件中&lt;code&gt;properties&lt;/code&gt;节点的信息。&lt;code&gt;properties&lt;/code&gt;节点会在解析配置文件的最开始就被解析，然后相关信息会被放入“&lt;code&gt;private Properties variables&lt;/code&gt;”属性并在解析后续节点时发挥作用，在《通用源码阅读指导书——MyBatis源码详解》的11.3 文档解析中的变量替换章节详细介绍了这一点。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;XPathParser&lt;/code&gt;存在多个重载的构造方法，它们均是根据传入的参数完成属性的初始化并构造出XML文档对应的Document对象。除去构造方法外，便是大量提供XML文档中节点解析功能的“&lt;code&gt;eval*&lt;/code&gt;”方法，这些方法最后都调用了如下所示的&lt;code&gt;evaluate&lt;/code&gt;方法。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/**
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; * 进行XML节点的解析
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; * @param expression 解析的语句
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; * @param root 解析根
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; * @param returnType 返回值类型
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; * @return 解析结果
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; Object &lt;span style=&#34;color:#a6e22e&#34;&gt;evaluate&lt;/span&gt;(String expression, Object root, QName returnType) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 对指定节点root运行解析语法expression，获得returnType类型的解析结果&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; xpath.&lt;span style=&#34;color:#a6e22e&#34;&gt;evaluate&lt;/span&gt;(expression, root, returnType);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  } &lt;span style=&#34;color:#66d9ef&#34;&gt;catch&lt;/span&gt; (Exception e) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; BuilderException(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Error evaluating XPath.  Cause: &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; e, e);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在&lt;code&gt;evaluate&lt;/code&gt;方法中，使用“&lt;code&gt;javax.xml.xpath.XPath&lt;/code&gt;”对象进行了节点的解析。因此，整个&lt;code&gt;XPathParser&lt;/code&gt;类本质就是对“&lt;code&gt;javax.xml.xpath.XPath&lt;/code&gt;”的封装和调用，可以把&lt;code&gt;XPathParser&lt;/code&gt;类看作是&lt;code&gt;javax.xml.xpath.XPath&lt;/code&gt;类的包装类。&lt;/p&gt;</description>
    </item>
    <item>
      <title>MyBatis中的Param注解是如何生效的？</title>
      <link>https://yeecode.top/blog/76/</link>
      <pubDate>Sun, 26 Mar 2023 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/76/</guid>
      
      <description>&lt;p&gt;在使用MyBatis时，我们一定接触过&lt;code&gt;Param&lt;/code&gt;注解。使用它，我们可以为变量指定别名，并在XML文件中引用到变量。那这一切是如何实现的呢，我们从MyBatis的源码着手分析。&lt;/p&gt;
&lt;p&gt;相关内容参考《通用源码阅读指导书——MyBatis源码详解》一书。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Param&lt;/code&gt;注解带注释的源码如下所示。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@Documented&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;// 表明该注解会保留在API文档中&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@Retention&lt;/span&gt;(RetentionPolicy.&lt;span style=&#34;color:#a6e22e&#34;&gt;RUNTIME&lt;/span&gt;) &lt;span style=&#34;color:#75715e&#34;&gt;// 表明注解会保留到运行阶段&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@Target&lt;/span&gt;(ElementType.&lt;span style=&#34;color:#a6e22e&#34;&gt;PARAMETER&lt;/span&gt;) &lt;span style=&#34;color:#75715e&#34;&gt;// 表明注解可以应用在参数上&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;@interface&lt;/span&gt; Param {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  String &lt;span style=&#34;color:#a6e22e&#34;&gt;value&lt;/span&gt;(); &lt;span style=&#34;color:#75715e&#34;&gt;// 整个注解只有一个属性，名为value&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;使用时，我们只需要在相关参数上使用该注解对参数进行命名，便可以在映射文件中引用该参数。例如下面代码中，我们使用该注解将&lt;code&gt;userId&lt;/code&gt;属性命名为&lt;code&gt;id&lt;/code&gt;。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;User &lt;span style=&#34;color:#a6e22e&#34;&gt;queryById&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;@Param&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;id&amp;#34;&lt;/span&gt;) Integer userId);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这样，我们便可以在Mapper引用&lt;code&gt;id&lt;/code&gt;所指代的变量，如下所示。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;select id&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;queryById&amp;#34;&lt;/span&gt; resultType&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;User&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    SELECT &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    FROM user
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    WHERE &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;id&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;{id}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/&lt;/span&gt;select&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接下来我们着重分析该注解是如何生效的。&lt;/p&gt;
&lt;p&gt;借助于开发工具，我们在&lt;code&gt;ParamNameResolver&lt;/code&gt;的构造方法中找到了MyBatis对引&lt;code&gt;Param&lt;/code&gt;的引用。相关源码如下所示。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/**
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; * 参数名解析器的构造方法
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; * @param config 配置信息
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; * @param method 要被分析的方法
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ParamNameResolver&lt;/span&gt;(Configuration config, Method method) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// 获取参数类型列表&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;final&lt;/span&gt; Class&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;?&amp;gt;[]&lt;/span&gt; paramTypes &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; method.&lt;span style=&#34;color:#a6e22e&#34;&gt;getParameterTypes&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// 准备存取所有参数的注解，是二维数组&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;final&lt;/span&gt; Annotation&lt;span style=&#34;color:#f92672&#34;&gt;[][]&lt;/span&gt; paramAnnotations &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; method.&lt;span style=&#34;color:#a6e22e&#34;&gt;getParameterAnnotations&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;final&lt;/span&gt; SortedMap&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;Integer, String&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; map &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; TreeMap&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;gt;&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; paramCount &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; paramAnnotations.&lt;span style=&#34;color:#a6e22e&#34;&gt;length&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// 循环处理各个参数&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; paramIndex &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; 0; paramIndex &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; paramCount; paramIndex&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (isSpecialParameter(paramTypes&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;paramIndex&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;// 跳过特别的参数&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;continue&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 参数名称&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    String name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (Annotation annotation : paramAnnotations&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;paramIndex&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;// 找出参数的注解&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (annotation &lt;span style=&#34;color:#66d9ef&#34;&gt;instanceof&lt;/span&gt; Param) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 如果注解是Param&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        hasParamAnnotation &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 那就以Param中值作为参数名&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ((Param) annotation).&lt;span style=&#34;color:#a6e22e&#34;&gt;value&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (name &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;// 否则，保留参数的原有名称&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (config.&lt;span style=&#34;color:#a6e22e&#34;&gt;isUseActualParamName&lt;/span&gt;()) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; getActualParamName(method, paramIndex);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (name &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 参数名称取不到，则按照参数index命名&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; String.&lt;span style=&#34;color:#a6e22e&#34;&gt;valueOf&lt;/span&gt;(map.&lt;span style=&#34;color:#a6e22e&#34;&gt;size&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    map.&lt;span style=&#34;color:#a6e22e&#34;&gt;put&lt;/span&gt;(paramIndex, name);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  names &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Collections.&lt;span style=&#34;color:#a6e22e&#34;&gt;unmodifiableSortedMap&lt;/span&gt;(map);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在上述代码中，首先使用语句“&lt;code&gt;final Annotation[][] paramAnnotations = method.getParameterAnnotations()&lt;/code&gt;”得到了目标方法的所有参数的注解。假设我们定义下面的方法：&lt;/p&gt;</description>
    </item>
    <item>
      <title>如何使用Hystrix实现服务请求隔离？</title>
      <link>https://yeecode.top/blog/130/</link>
      <pubDate>Wed, 01 Mar 2023 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/130/</guid>
      
      <description>&lt;h2 id=&#34;概述&#34;&gt;概述&lt;/h2&gt;
&lt;p&gt;在串联连接的模块中，如果一个模块失效，则它的前置模块因为无法获取后置模块的服务，也会失效。因此，失效会在串联的模块中向前蔓延。&lt;/p&gt;
&lt;p&gt;假设系统中节点N1会调用节点N2、N3、N4三个节点提供的不同服务，如图所示。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/130/pic/safechuan2_hu_97170014ecd69372.webp&#34; alt=&#34;服务串联示意图&#34; width=&#34;215&#34; height=&#34;282&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;在不考虑隔离的情况下，节点N1的工作过程通常如伪下面代码所示。&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;public Result service(Request request) {
    Result result = new Result();
    result.append(n2.service(request));
    result.append(n3.service(request));
    result.append(n4.service(request));
    return result;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;当节点N2失效时，&lt;code&gt;n2.service(request)&lt;/code&gt;操作将会阻塞，从而导致节点N1中的service操作会被阻塞。于是，大量的请求拥塞在节点N1上，使得节点N1的并发线程数急剧升高，最终导致节点N1内存耗尽而失效。&lt;/p&gt;
&lt;p&gt;有一种措施可以避免失效的蔓延，那就是隔离。&lt;/p&gt;
&lt;p&gt;我们可以使用线程池将节点N1和后方的节点N2、N3、N4隔离起来。具体措施是在N1中为调用节点N2、N3、N4的操作各设立一个线程池，每次需要调用它们的服务时，从线程池中取出一个线程操作，而不是使用N1节点的主线程操作。实现流程的伪代码如下。&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;public Result service(Request request) {
    Result result = new Result();

    // 从调用N2节点的专用线程池中取出一个线程
    Thread n2ServiceThread = n2ServiceThreadPool.get(); 
    if (n2ServiceThread != null) {
        // 使用获得的线程调用N2节点的服务
        n2ServiceThread.start();
        // 获得N2节点给出的结果，并汇总入N1节点的处理流程中
        result.append(n2ServiceThread.get()); 
    }
    
    // 省略对N3、N4节点的调用流程
    
    return result;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这样，当N2节点失效时，会使得N1节点中的调用线程阻塞，进而导致N1节点中操作N2节点的线程池被占满。之后，这一结果不会再继续扩散，不会对其他线程池造成影响，从而保证节点N1的资源不会被耗尽。&lt;/p&gt;
&lt;p&gt;这种操作将N2节点失效引发的影响隔离在了节点N1的一个线程池中，提升了节点N1的稳定性。&lt;/p&gt;
&lt;h2 id=&#34;环境配置&#34;&gt;环境配置&lt;/h2&gt;
&lt;p&gt;通过Hystrix就可以方便地实现这一操作。接下来，我们给出简单的示例。&lt;/p&gt;
&lt;p&gt;首先，准备SpringBoot项目，然后在其中引入适用于SpringBoot的hystrix包：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-xml&#34; data-lang=&#34;xml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.cloud&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-cloud-starter-netflix-hystrix&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;2.2.7.RELEASE&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;注意，如果这时候启动提示如下的错误，则基本是SpringBoot版本与hystrix版本不匹配导致的。因为hystrix是基于SpringCloud的，而SpringCloud和SpringBoot存在版本对应关系。如果对应不一致，则可能导致出现下面的错误。&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;java.lang.NoSuchMethodError: &amp;#39;void org.springframework.boot.builder.SpringApplicationBuilder.&amp;lt;init&amp;gt;(java.lang.Object[])&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这里我们给出一个可用的对应版本，即SpringBoot的2.3.5.RELEASE和spring-cloud-starter-netflix-hystrix的2.2.7.RELEASE。它们这一对是肯定没有问题的。&lt;/p&gt;
&lt;p&gt;然后，我们需要在SpringBoot的主类上增加@EnableHystrix注解，以启用Hystrix。如下所示。&lt;/p&gt;</description>
    </item>
    <item>
      <title>怎么理解3PC解决了2PC的阻塞问题？</title>
      <link>https://yeecode.top/blog/121/</link>
      <pubDate>Sun, 22 Jan 2023 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/121/</guid>
      
      <description>&lt;p&gt;正如题目所说，三阶段提交（Three-phase Commit，简称3PC）相比于两阶段提交（Two-phase Commit，简称2PC）的一个重大进步就是，前者解决了后者的阻塞问题。（或者说是部分解决，这个我们后面详细分析）&lt;/p&gt;
&lt;p&gt;那如何详细解释这一点，还得从两阶段提交算法说起。&lt;strong&gt;先分析下两阶段提交哪里存在阻塞。然后，再看三阶段提交算法如何避免了这个阻塞。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;好，我们开始介绍。&lt;/p&gt;
&lt;p&gt;我尽量写的简单生动些。&lt;/p&gt;
&lt;p&gt;而且，你没看错。虽然像是&lt;strong&gt;知乎十年前&lt;/strong&gt;才有的答案，但确实是新回答……&lt;/p&gt;
&lt;p&gt;哎……&lt;/p&gt;
&lt;h2 id=&#34;1-两阶段提交&#34;&gt;1 两阶段提交&lt;/h2&gt;
&lt;p&gt;两阶段提交（Two-phase Commit，简称2PC）是一种比较简单的一致性算法（或者说协议）。它将整个提交过程分成了&lt;strong&gt;准备阶段（Prepare）&lt;/strong&gt;、**提交阶段（Commit）**这两个阶段。&lt;/p&gt;
&lt;h2 id=&#34;11-具体实现&#34;&gt;1.1 具体实现&lt;/h2&gt;
&lt;p&gt;在准备阶段中，可以细分成如下的操作步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;协调者给参与者发送“Prepare”消息，“Prepare”消息中包含了此次更新要进行的所有操作。然后协调者等待各个参与者的响应。&lt;/li&gt;
&lt;li&gt;参与者收到“Prepare”消息后将消息中的操作封装为一个事务并执行，但不要提交。&lt;/li&gt;
&lt;li&gt;参与者在执行事务的过程中如果遇到任何问题导致事务中的操作无法完成，则向协调者回应“No”消息；如果事务中的全部操作能够完成，则向协调者回应“Yes”消息。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;经过准备阶段，所有的参与者已经尝试完成了所有的操作，但是没有把这些操作提交。并且，根据将自身能否完成操作的结果回应给了协调者。接下来，便进入了第二个阶段：提交阶段。&lt;/p&gt;
&lt;p&gt;在提交阶段中，协调者会根据收集到的各个参与者的回应执行不同的操作。&lt;/p&gt;
&lt;p&gt;如果协调者收到了所有参与者的“Yes”消息，则进行如下操作：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;协调者向参与者发送“Commit”消息。&lt;/li&gt;
&lt;li&gt;参与者收到“Commit”消息后，提交在准备阶段已经完成但尚未提交的事务。&lt;/li&gt;
&lt;li&gt;参与者向协调者发送“Done”消息。&lt;/li&gt;
&lt;li&gt;协调者收到所有参与者的“Done”消息，表明该一致性操作以成功的形式结束。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果协调者在一定时间内没有收齐所有参与者的消息，或者收到的消息中有“No”消息，则进行如下操作：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;协调者向参与者发送“Rollback”消息。&lt;/li&gt;
&lt;li&gt;参与者收到“Rollback”消息后，回滚在准备阶段已经完成但尚未提交的事务。&lt;/li&gt;
&lt;li&gt;参与者向协调者发送“Done”消息。&lt;/li&gt;
&lt;li&gt;协调者收到所有参与者节点的“Done”消息，表明该一致性操作以回滚的形式结束。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;可见在提交阶段，无论是决定要“Commit”还是“Rollback”，整个一致性操作都能结束。&lt;/p&gt;
&lt;p&gt;下图展示了两阶段提交的消息流。其中图的左侧部分表示一致性操作成功情况下的消息流；图的右侧部分表示因部分参与者不回应导致的一致性操作失败情况下的消息流。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/121/pic/2p_hu_8d8f95cb519f9750.webp&#34; alt=&#34;两阶段提交的消息流&#34; width=&#34;900&#34; height=&#34;673&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;上述过程中，协调者可以是一个专门起到协调作用而不参与一致性操作的节点，也可以是一个参与一致性操作的节点。如果它是后者，那它不仅要完成协调者的工作，还要作为参与者完成所有参与者要进行的工作，即它也要将操作封装成事务执行、回应“No”消息或“Yes”消息、提交或回滚事务等。&lt;/p&gt;
&lt;h2 id=&#34;12-阻塞在哪里&#34;&gt;1.2 阻塞在哪里？&lt;/h2&gt;
&lt;p&gt;通过了解两阶段提交的工作过程，我们其实已经可以看出明确阻塞点。&lt;/p&gt;
&lt;p&gt;参与者收到协调者发出的“Prepare”消息后，会开启事务完成消息中的操作。事务的开启意味着参与者的并发处理能力将受到很大的影响。而且，参与者不是一个节点而是一群节点，所以&lt;strong&gt;整个系统中的节点都会因为事务的开启而阻塞&lt;/strong&gt;。这个过程可能很长，要等待协调者收集完参与者的消息并发布进一步的消息后，该过程才能结束。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;那如果协调者在这个阶段宕机，则所有参与者会一直阻塞下去。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这就是两阶段提交存在的&lt;strong&gt;同步阻塞问题&lt;/strong&gt;。显然极大地影响了分布式系统的工作效率。&lt;/p&gt;
&lt;p&gt;好，接下来我们看一下三阶段提交，看它如何解决这一阻塞问题。&lt;/p&gt;
&lt;h2 id=&#34;2-三阶段提交&#34;&gt;2 三阶段提交&lt;/h2&gt;
&lt;p&gt;三阶段提交（Three-phase Commit，简称3PC）是二阶段提交的改进，它修复了二阶段提交的一些缺陷，但也使得整个一致性操作过程多出了一个阶段，更为复杂。此外，三阶段提交还引入了一些超时机制，以便于节点在失去或者部分失去与外界联系时做出一些操作。&lt;/p&gt;
&lt;p&gt;三阶段提交一共分为三个阶段：&lt;strong&gt;CanCommit阶段&lt;/strong&gt;、&lt;strong&gt;PreCommit阶段&lt;/strong&gt;、&lt;strong&gt;DoCommit阶段&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id=&#34;21-具体实现&#34;&gt;2.1 具体实现&lt;/h2&gt;
&lt;p&gt;CanCommit阶段可以细分为以下步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;协调者向参与者发送“CanCommit”消息，询问参与者能否完成消息中的操作。然后协调者等待各个参与者的响应。&lt;/li&gt;
&lt;li&gt;参与者接收到“CanCommit”消息后，判断自身是否能够顺利完成操作。如果自身可以，则向协调者回应“Yes”消息；如果自身不可以，则向协调者回应“No”消息。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在CanCommit阶段，协调者和参与者只是就能否完成操作进行了交流，并没有进行实际的工作。接下来进入PreCommit阶段。&lt;/p&gt;
&lt;p&gt;PreCommit阶段则根据协调者收到的参与者的反馈不同而不同。&lt;/p&gt;
&lt;p&gt;如果协调者收到了所有参与者的“Yes”消息，则进行如下操作：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;协调者向参与者发送“PreCommit”消息。然后协调者等待各个参与者的响应。&lt;/li&gt;
&lt;li&gt;参与者收到“PreCommit”消息后，将此次要进行的操作封装成事务执行，但不要提交。&lt;/li&gt;
&lt;li&gt;如果参与者完成了各项操作，则向协调者回应“ACK”消息。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在这种情况下，一致性操作则会进入DoCommit阶段。&lt;/p&gt;
&lt;p&gt;如果协调者在一定时间内没有收齐所有参与者的消息，或者收到的消息中有“No”消息，则进行如下操作：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;协调者向所有参与者发送“Abort”消息，表明此次一致性操作取消。&lt;/li&gt;
&lt;li&gt;参与者收到“Abort”后，中止当前的操作。如果参与者在一定时间内没有收到协调者发来的操作消息，也中止当前的操作。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这种情况下一致性操作结束，无需再进入DoCommit阶段，&lt;/p&gt;
&lt;p&gt;在DoCommit阶段中，协调者会根据参与者的回应采取不同的操作。&lt;/p&gt;
&lt;p&gt;如果协调者收到了所有参与者的“ACK”消息，则进行如下的操作：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;协调者向参与者发送“Commit”消息。&lt;/li&gt;
&lt;li&gt;参与者收到“Commit”消息后，提交在准备阶段已经完成但尚未提交的事务。&lt;/li&gt;
&lt;li&gt;参与者向协调者发送“Done”消息。&lt;/li&gt;
&lt;li&gt;协调者收到所有参与者的“Done”消息，表明该一致性操作以成功的形式结束。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果协调者在一定时间内没有收齐参与者的“ACK”消息，则进行如下的操作：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;协调者向参与者发送“Rollback”消息。&lt;/li&gt;
&lt;li&gt;参与者收到“Rollback”消息后，回滚在准备阶段已经完成但尚未提交的事务。&lt;/li&gt;
&lt;li&gt;参与者向协调者发送“Done”消息。&lt;/li&gt;
&lt;li&gt;协调者收到所有参与者的“Done”消息，表明该一致性操作以失败的形式结束。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;下图展示了三阶段提交的消息流。其中图的左侧部分表示一致性操作成功情况下的消息流；图的右侧部分表示因部分参与者在CanCommit阶段回应“No”消息导致的一致性操作失败情况下的消息流。&lt;/p&gt;</description>
    </item>
    <item>
      <title>别搞错了！Paxos不是一致性算法！</title>
      <link>https://yeecode.top/blog/120/</link>
      <pubDate>Sat, 21 Jan 2023 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/120/</guid>
      
      <description>&lt;p&gt;说到一致性，大家都不陌生。随着分布式系统、微服务系统、区块链等技术的发展，“一致性”一词出现的频率也越来越高。&lt;/p&gt;
&lt;p&gt;再说起一致性时，有人会联想到一个“一致性”算法——Paxos算法。&lt;/p&gt;
&lt;p&gt;然而，这是错误的，因为：&lt;strong&gt;Paxos并不是一个一致性算法！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;而正是上述误解，使得很多分布式的学习者陷入了混乱之中。给分布式的学习带来了很大的麻烦。&lt;/p&gt;
&lt;p&gt;为了让大家明确这些概念，我们先温习下分布式一致性的概念。&lt;/p&gt;
&lt;p&gt;分布式系统中的一致性是说，**用户在分布式系统的某个节点上进行了变更操作，则在一定时间后，用户能从系统的任意节点上读取到这个变更结果。**并且，一致性有强弱之分，如顺序一致性、线性一致性、最终一致性等等，都代表了特定的一致性级别。&lt;/p&gt;
&lt;p&gt;而熟悉Paxos算法的同学知道，Paxos并不能够帮助系统实现一致性，更不用说帮助系统达到具体的哪种一致性级别了。所以，Paxos算法，并不是一致性算法。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Paxos算法，是个共识算法！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;要想理清这一切，我们需要了解共识这一概念。&lt;/p&gt;
&lt;p&gt;共识与一致性是两个紧密关联但又彼此独立的概念，然而，在日常的讨论中我们常将两者混淆。这在理解和使用上带来了许多麻烦。&lt;/p&gt;
&lt;p&gt;**共识（Consensus）是指分布式系统中各个节点对某个提议达成一致的过程。**共识中提议的含义很广泛，可以是某个变量的值，可以某个节点的角色，等等。&lt;/p&gt;
&lt;p&gt;一致性是指系统各个节点对外表现一致，而共识则是各个节点就某个提议达成一致的内部过程。因此，一致性（Consistency）和共识（Consensus）并不是同一个概念，它们&lt;strong&gt;在英文中也完全不是一个单词&lt;/strong&gt;。但是两者却在很多工程领域的文章、书籍中被混淆，这一点我们要特别注意。&lt;/p&gt;
&lt;p&gt;共识和一致性也有一定的关系。在一个分布式系统中，数据副本存放在不同节点上。用户修改了某个节点的数据后，经过一定时间，如果用户能从系统任意节点读取到修改后的数据，那我们就说该分布式系统实现了一致性。既然用户能从系统中读到修改后的数据，则说明分布式系统中所有节点对这次的数据修改提议达成了共识。因此，&lt;strong&gt;一致性是目的，而共识是实现一致性这一目的所要经历的一个过程。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;此外一致性有强弱之分，而共识则没有。共识需要所有节点对某个提议达成一致，只要达成一致，就是实现了共识；只要达不成一致，就没有实现共识。&lt;/p&gt;
&lt;p&gt;我们可以通过组织班级春游的例子来理解共识和一致性这两个概念的区别。&lt;/p&gt;
&lt;p&gt;几个班委会成员在会议室讨论班级要去哪春游，讨论的过程就是一个共识过程。可能有的班委提议去西湖，有的班委提议去西溪湿地，有的班委提议去太子湾，最终大家不断讨论得出一个确定结果的过程就是共识。期间可能会涉及多轮的提议、投票等，这个过程和本节将要讨论的共识算法的实现流程也十分相似。&lt;/p&gt;
&lt;p&gt;当班委会讨论并确定春游的地点之后，需要向全班同学公布这一结果。如何公布这一结果就是一个一致性问题。如果选择张贴班级公告栏，则同学们看到公告的时间有先后（这时其他班级的同学询问起来，可能有人会回答“不知道”，有人会回答“公告说了，我们班春游去西湖”），那这就不满足线性一致性；如果选择群发短信，则所有同学会同时收到通知（这时其他班级的同学询问起来，大家都会回答“短信通知过了，我们班春游去西湖”），那这就满足线性一致性。&lt;/p&gt;
&lt;p&gt;可见，共识是一致性的基础。毕竟如果班委会无法就春游地点达成共识，那就无法向全班同学公布结果。但是，这两个概念并不相同，不能混为一谈。&lt;/p&gt;
&lt;p&gt;Paxos算法提出者Leslie Lamport描述Paxos算法的论文《Paxos Made Simple》中写道“ At its heart is a consensus algorithm—the “synod” algorithm of.”而论文第二章的名字就叫“The Consensus Algorithm”&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;。甚至，论文通篇都没有出现过“consistency（一致性）”这一单词。&lt;/p&gt;
&lt;p&gt;Paxos算法是一个比较完美的共识算法，以至于有人这样称赞它：“世上只有一种共识（consensus）算法，那就是Paxos”。它可以在存在故障节点但没有恶意节点的前提下保证系统中节点达成共识，这是一个完备的共识算法。Multi-Paxos算法、Fast-Paxos算法、Raft算法都是由Paxos演化来的。&lt;/p&gt;
&lt;p&gt;关于共识算法、一致性算法的区别，以及共识算法（包括Paxos算法、Raft算法）的详解，大家可以参考下面的书籍。会帮助大家对分布式系统的一致性建立更全面系统的认识。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://union-click.jd.com/jdc?e=&amp;p=JF8BAMYJK1olXDYCVl9YAEkUBV9MRANLAjZbERscSkAJHTdNTwcKBlMdBgABFksVAmoAGVgTQl9HCANtb05yfSYIcEV3CVJgMgctdStjWGwSa1cZbQcyVF9cCE8eBWcKG2slXQEyAjBdCUoWAm4NG14WbQcyVFlYCk0UBWoMHFwQXDYFVFdtUx55BWdbSV9AVQEBVl9fCnsnM2w4K2sVbQUyFjAIWBgRC2YLTFMWDQQHUVkKWx4SBDsNHVMdDwQLB1YPCnsVAm4MEms&#34;&gt;&lt;img src=&#34;https://yeecode.top/img/promo/dsbook.jpg&#34; width=&#34;680&#34; height=&#34;453&#34; loading=&#34;lazy&#34;  alt=&#34;分布式系统原理与工程实践&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://union-click.jd.com/jdc?e=&amp;amp;p=JF8BAMYJK1olXDYCVl9YAEkUBV9MRANLAjZbERscSkAJHTdNTwcKBlMdBgABFksVAmoAGVgTQl9HCANtb05yfSYIcEV3CVJgMgctdStjWGwSa1cZbQcyVF9cCE8eBWcKG2slXQEyAjBdCUoWAm4NG14WbQcyVFlYCk0UBWoMHFwQXDYFVFdtUx55BWdbSV9AVQEBVl9fCnsnM2w4K2sVbQUyFjAIWBgRC2YLTFMWDQQHUVkKWx4SBDsNHVMdDwQLB1YPCnsVAm4MEms&#34;&gt;《分布式系统原理与工程实践》&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;书中对各种一致性算法都进行了详细的介绍。还详细阐述了共识算法，给出了&lt;strong&gt;Paxos算法的推导过程、理解分析、具体示例&lt;/strong&gt;。应该是最&lt;strong&gt;简单易懂&lt;/strong&gt;的Paxos讲解书籍了。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;../pic/dsbook_list01.jpg&#34; alt=&#34;书籍部分目录&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;另外这本书对分布式系统相关的理论、实践、工程知识均进行了详细的介绍，层层递进，有助于大家建立完整的分布式系统知识体系。涉及&lt;strong&gt;一致性、共识、Paxos、分布式事务、服务治理、微服务、幂等、消息系统、Zookeeper&lt;/strong&gt;等。并且还发行了&lt;strong&gt;繁体版&lt;/strong&gt;！&lt;/p&gt;
&lt;p&gt;再次提醒大家，在学习分布式系统时，因为涉及到的理论、框架很多。所以大家一定要理清知识脉络，否则容易学得越多越混乱。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;最后，我是高级架构师&lt;strong&gt;易哥&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;真心希望本文能让大家有所收获，纠正大家的一些错误认知。&lt;/p&gt;
&lt;p&gt;欢迎&lt;strong&gt;关注&lt;/strong&gt;我们，我会偶尔出没分享&lt;strong&gt;软件架构&lt;/strong&gt;和&lt;strong&gt;编程&lt;/strong&gt;相关的干货知识。&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;Lamport L. Paxos made simple[J]. ACM SIGACT News (Distributed Computing Column) 32, 4 (Whole Number 121, December 2001), 2001: 51-58.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description>
    </item>
    <item>
      <title>两阶段、三阶段提交算法，真的可靠吗？</title>
      <link>https://yeecode.top/blog/119/</link>
      <pubDate>Fri, 20 Jan 2023 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/119/</guid>
      
      <description>&lt;p&gt;在前面的文章中，我们已经详细介绍了两阶段提交算法、三阶段提交算法。包括它们的具体实现流程、优劣等等。如果对这方面不了解的同学，可以再去温习以前的文章。&lt;/p&gt;
&lt;p&gt;关于两阶段提交，我们知道它可能会出现提交不一致的情况。这种情况有两种：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;假设前面各次通信均正常，但因为网络抖动导致部分参与者无法收到“Commit”消息，则会出现部分参与者提交了事务，部分参与者未提交事务这种不一致情况的发生。&lt;/li&gt;
&lt;li&gt;假设前面各次通信均正常，如果协调者在发出“Commit”消息或者“Rollback”消息后宕机，部分参与者收到了这条消息后提交或者回滚了事务，部分参与者没有收到这条消息。则也会出现提交不一致的情况。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;而三阶段提交，则出现提交不一致的情况更低。但是仍然存在：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;假设前面各次通信均正常，如果协调者在发送“Rollback”消息后宕机，部分参与者收到了“Rollback”消息回滚了事务；部分参与者因为没有收到“Rollback”消息便会默认提交事务。此时便出现提交不一致的情况。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;可见，两阶段提交、三阶段提交均不可靠。都可能会出现提交不一致的情况。&lt;/p&gt;
&lt;p&gt;但我们也发现：三阶段提交算法则比两阶段提交算法要好，出现不一致提交的概率更低。这里不仅仅是说出现异常的情况少了一种。而是说三阶段提交的通信次数更多，那&lt;strong&gt;前面各次通信均正常且最后一次通信异常&lt;/strong&gt;的事件，其出现概率也更低。&lt;/p&gt;
&lt;p&gt;顺着这个思路走下去，阶段越多，则提交越可靠。&lt;strong&gt;那是不是说，继续引入更多的阶段，就可以实现可靠的一致性提交呢？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;接下来，我们就来讨论这个问题。&lt;/p&gt;
&lt;p&gt;要回答上述疑问，我们可以先了解&lt;strong&gt;两军问题（Two Generals&amp;rsquo; Problem）&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;如下图所示，山顶的两支蓝军将白军左右围困在山谷。两支蓝军必须派信使跨过白军驻守的山谷才能通信。信使在跨过山谷时，可能被白军俘虏导致信息丢失。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/119/pic/2jun_hu_1e3b43513f9f818.webp&#34; alt=&#34;两军问题示意图&#34; width=&#34;884&#34; height=&#34;264&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;假设两支蓝军必须在同一天从两侧夹击白军才能成功，而任意一支蓝军单独发起进攻都会失败。那请问，蓝军能必胜吗？&lt;/p&gt;
&lt;p&gt;我们现在思考蓝军怎样通信才能商定出一个进攻日期。&lt;/p&gt;
&lt;p&gt;假设蓝军1派出信使，传递信息“第五天发起进攻！”&lt;/p&gt;
&lt;p&gt;然后，第五天时，蓝军1能放心地发起进攻吗？&lt;/p&gt;
&lt;p&gt;不能，因为蓝军1并不能确定蓝军2收到了自己的消息。&lt;/p&gt;
&lt;p&gt;于是，需要蓝军2在收到消息后，派出一个信使告知蓝军1自己收到了消息。&lt;/p&gt;
&lt;p&gt;这样，第五天时，蓝军2能放心地发起进攻吗？&lt;/p&gt;
&lt;p&gt;不能，因为蓝军2并不能确定蓝军1收到了自己的确认消息。&lt;/p&gt;
&lt;p&gt;于是，需要蓝军1在收到了蓝军2的确认消息后，再派出一个信使告诉蓝军2自己已经收到了蓝军2的确认消息。&lt;/p&gt;
&lt;p&gt;这样，第五天时，蓝军1能放心地发起进攻吗？&lt;/p&gt;
&lt;p&gt;不能，因为蓝军1并不能确定蓝军2收到了自己的确认消息。&lt;/p&gt;
&lt;p&gt;于是，需要蓝军2在收到蓝军1的确认消息后，再派出一个信使告诉蓝军1……&lt;/p&gt;
&lt;p&gt;这时我们发现，要想可靠地传递包含进攻日期的信息，互派信使的过程似乎要无穷无尽地进行下去。这意味着，两支蓝军无法实现信息的可靠传递。&lt;/p&gt;
&lt;p&gt;我们可以使用反证法证明这个结论。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;假设双方共派出n个必要的信使后，能够可靠地传递信息。双方在不派信使的情况下，显然无法可靠传递信息，故n≥1。由于信使可能会被俘，则第n个信使可能会被俘。而n个信使都是必要的，这意味着，信息无法可靠传递。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;两军问题讨论的是在信道不可靠的情况下，能否可靠传递信息的问题。结论是：&lt;strong&gt;如果信道不可靠，则无法可靠传递信息&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;举一反三，我们可以将两军算法的思路和结论迁移到一致性提交算法中，讨论能否在网络、节点均不稳定的分布式系统中实现可靠的一致性提交。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;假设经过n个必要的阶段后，能够实现可靠的一致性提交。不经过任何阶段，显然无法完成提交，故n≥1。由于分布式系统的通信、节点均不稳定，则第n个阶段可能因为通信故障或节点宕机无法顺利完成。而n个阶段都是必要的，这意味着，无法实现可靠的一致性提交。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;可见，&lt;strong&gt;即使引入更多的阶段，也无法在网络、节点均不稳定的分布式系统中实现可靠的一致性提交&lt;/strong&gt;。无论我们增加再多的阶段，都不可能完全避免。&lt;/p&gt;
&lt;p&gt;但是，确实，&lt;strong&gt;随着阶段的增加，出现不一致提交的概率降低&lt;/strong&gt;。因为在前n-1次无异常且在第n次恰好异常的情况，会随着n的增加逐渐降低。&lt;/p&gt;
&lt;p&gt;所以，一般来说，三阶段提交就够用了。已经可以将不一致提交降到一个极低的水平（因为通信异常本身就是一个小概率实践）。&lt;/p&gt;
&lt;p&gt;说到这里，可能有人会说，还有一个重要的一致性算法——Paxos算法——我们没有讲！听说它是一个很棒的算法，能不能实现可靠地一致性提交？&lt;/p&gt;
&lt;p&gt;如果问出这个问题，则说明他对一致性的概念是混乱的。因为：&lt;strong&gt;Paxos算法是一个共识算法，而不是一致性算法&lt;/strong&gt;。虽然，确实很多网上的资料都讲Paxos算法当做一致性算法。这种错误的认识，应该就是大家在学习分布式系统时容易感到混乱的原因。&lt;/p&gt;
&lt;p&gt;关于共识算法、一致性算法的区别，以及共识算法（包括Paxos算法、Raft算法）的详解，大家可以参考下面的书籍。会帮助大家对分布式系统的一致性建立更全面系统的认识。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://union-click.jd.com/jdc?e=&amp;p=JF8BAMYJK1olXDYCVl9YAEkUBV9MRANLAjZbERscSkAJHTdNTwcKBlMdBgABFksVAmoAGVgTQl9HCANtb05yfSYIcEV3CVJgMgctdStjWGwSa1cZbQcyVF9cCE8eBWcKG2slXQEyAjBdCUoWAm4NG14WbQcyVFlYCk0UBWoMHFwQXDYFVFdtUx55BWdbSV9AVQEBVl9fCnsnM2w4K2sVbQUyFjAIWBgRC2YLTFMWDQQHUVkKWx4SBDsNHVMdDwQLB1YPCnsVAm4MEms&#34;&gt;&lt;img src=&#34;https://yeecode.top/img/promo/dsbook.jpg&#34; width=&#34;680&#34; height=&#34;453&#34; loading=&#34;lazy&#34;  alt=&#34;分布式系统原理与工程实践&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://union-click.jd.com/jdc?e=&amp;amp;p=JF8BAMYJK1olXDYCVl9YAEkUBV9MRANLAjZbERscSkAJHTdNTwcKBlMdBgABFksVAmoAGVgTQl9HCANtb05yfSYIcEV3CVJgMgctdStjWGwSa1cZbQcyVF9cCE8eBWcKG2slXQEyAjBdCUoWAm4NG14WbQcyVFlYCk0UBWoMHFwQXDYFVFdtUx55BWdbSV9AVQEBVl9fCnsnM2w4K2sVbQUyFjAIWBgRC2YLTFMWDQQHUVkKWx4SBDsNHVMdDwQLB1YPCnsVAm4MEms&#34;&gt;《分布式系统原理与工程实践》&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;书中对各种一致性算法都进行了详细的介绍。还详细阐述了共识算法，给出了&lt;strong&gt;Paxos算法的推导过程、理解分析、具体示例&lt;/strong&gt;。应该是最&lt;strong&gt;简单易懂&lt;/strong&gt;的Paxos讲解书籍了。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;../pic/dsbook_list01.jpg&#34; alt=&#34;书籍部分目录&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;另外这本书对分布式系统相关的理论、实践、工程知识均进行了详细的介绍，层层递进，有助于大家建立完整的分布式系统知识体系。涉及&lt;strong&gt;一致性、共识、Paxos、分布式事务、服务治理、微服务、幂等、消息系统、Zookeeper&lt;/strong&gt;等。并且还发行了&lt;strong&gt;繁体版&lt;/strong&gt;！&lt;/p&gt;
&lt;p&gt;再次提醒大家，在学习分布式系统时，因为涉及到的理论、框架很多。所以大家一定要理清知识脉络，否则容易学得越多越混乱。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;别忘了&lt;strong&gt;点赞&lt;/strong&gt;……&lt;/p&gt;
&lt;p&gt;也欢迎&lt;strong&gt;关注&lt;/strong&gt;我！&lt;/p&gt;
&lt;p&gt;我是程序员易哥。&lt;/p&gt;</description>
    </item>
    <item>
      <title>从两阶段提交算法到三阶段提交算法</title>
      <link>https://yeecode.top/blog/118/</link>
      <pubDate>Thu, 12 Jan 2023 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/118/</guid>
      
      <description>&lt;p&gt;现在，分布式系统越来越多。不理解分布式系统，都不好意思说自己会编程。而说起分布式系统，则绕不开各种一致性问题。&lt;/p&gt;
&lt;p&gt;假设我们要在一个分布式系统中实现一致性，应该怎样做？&lt;/p&gt;
&lt;p&gt;大家可能脱口而出：用&lt;strong&gt;两阶段提交算法&lt;/strong&gt;或者&lt;strong&gt;三阶段提交算法&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;那大家对这些算法的细节了解的清楚吗？知道两者的区别么？为什么它们可以满足顺序一致性？&lt;/p&gt;
&lt;p&gt;本文将会解答这些问题。接下来，带大家详细了解这两个算法。&lt;/p&gt;
&lt;h2 id=&#34;背景&#34;&gt;背景&lt;/h2&gt;
&lt;p&gt;假设一个分布式系统中存在多个节点，如下图所示，其中的节点A收到了数据变更请求。如果系统要满足一致性，那节点A在完成自身数据变更的同时必须协调其他节点完成数据变更。这种情况下，我们将发起操作的节点称为协调者(Coordinator)，而将接受操作的节点称为参与者(Participants)。在图中，节点A是协调者，节点B和节点C都是参与者。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/118/pic/yizhi_hu_2b0528f93e111d31.webp&#34; alt=&#34;一致性操作中的角色划分&#34; width=&#34;694&#34; height=&#34;414&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;具体实现似乎很简单，作为协调者的节点A只要在更新自身数据的同时将变更操作通知各个参与者，参与者收到协调者的变更通知后各自完成自身变更即可,如下图所示。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/118/pic/yizhi01_hu_b07e475c62b2769c.webp&#34; alt=&#34;节点间的一致性操作&#34; width=&#34;900&#34; height=&#34;524&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;这样，用户再从系统中读取该数据时，无论读请求落在哪个节点上，读取到的结果都是一致的。系统便满足了一致性。&lt;/p&gt;
&lt;p&gt;可在实际中并没有这么理想：协调者和参与者之间的通讯可能不可靠，参与者也可能因为各种内部原因导致更新失败。&lt;/p&gt;
&lt;p&gt;协调者更新了自身状态后通知参与者，假设某个参与者因为&lt;strong&gt;通讯原因&lt;/strong&gt;或者&lt;strong&gt;节点自身原因&lt;/strong&gt;更新失败，则那用户便可能从集群中读取到&lt;strong&gt;不一致的结果&lt;/strong&gt;。这时系统的一致性便被打破。&lt;/p&gt;
&lt;p&gt;发生这种不一致的根源是每个节点都能知道自身是否操作成功，但却无法知道其他节点是否更新成功。&lt;/p&gt;
&lt;p&gt;类比后面要介绍的两阶段提交和三阶段提交，我们可以将上图所示的操作称为“一阶段提交”，因为整个一致性操作过程中只有一个“提交”阶段。显然我们的“一阶段提交”并不能够很好地实现一致性操作。&lt;/p&gt;
&lt;p&gt;接下来我们要学习的就是一些更好的也更为实用的一致性算法。也就是两阶段提交算法和三阶段提交算法。&lt;/p&gt;
&lt;p&gt;两阶段提交算法和三阶段提交算法能够实现的一致性是指线性一致性。而弱一致性算法，例如我们常听说的最终一致性算法，都是在线性一致性的基础上放宽约束实现的。所以，实现线性一致性的算法十分重要，例如各种分布式事务就要用到它！&lt;/p&gt;
&lt;h2 id=&#34;两阶段提交&#34;&gt;两阶段提交&lt;/h2&gt;
&lt;p&gt;两阶段提交（Two-phase Commit，简称2PC）是一种比较简单的一致性算法（或者说协议）。它将整个提交过程分成了&lt;strong&gt;准备阶段（Prepare）&lt;/strong&gt;、**提交阶段（Commit）**这两个阶段。&lt;/p&gt;
&lt;h2 id=&#34;具体实现&#34;&gt;具体实现&lt;/h2&gt;
&lt;p&gt;在准备阶段中，可以细分成如下的操作步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;协调者给参与者发送“Prepare”消息，“Prepare”消息中包含了此次更新要进行的所有操作。然后协调者等待各个参与者的响应。&lt;/li&gt;
&lt;li&gt;参与者收到“Prepare”消息后将消息中的操作封装为一个事务并执行，但不要提交。&lt;/li&gt;
&lt;li&gt;参与者在执行事务的过程中如果遇到任何问题导致事务中的操作无法完成，则向协调者回应“No”消息；如果事务中的全部操作能够完成，则向协调者回应“Yes”消息。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;经过准备阶段，所有的参与者已经尝试完成了所有的操作，但是没有把这些操作提交。并且，根据将自身能否完成操作的结果回应给了协调者。接下来，便进入了第二个阶段：提交阶段。&lt;/p&gt;
&lt;p&gt;在提交阶段中，协调者会根据收集到的各个参与者的回应执行不同的操作。&lt;/p&gt;
&lt;p&gt;如果协调者收到了所有参与者的“Yes”消息，则进行如下操作：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;协调者向参与者发送“Commit”消息。&lt;/li&gt;
&lt;li&gt;参与者收到“Commit”消息后，提交在准备阶段已经完成但尚未提交的事务。&lt;/li&gt;
&lt;li&gt;参与者向协调者发送“Done”消息。&lt;/li&gt;
&lt;li&gt;协调者收到所有参与者的“Done”消息，表明该一致性操作以成功的形式结束。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果协调者在一定时间内没有收齐所有参与者的消息，或者收到的消息中有“No”消息，则进行如下操作：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;协调者向参与者发送“Rollback”消息。&lt;/li&gt;
&lt;li&gt;参与者收到“Rollback”消息后，回滚在准备阶段已经完成但尚未提交的事务。&lt;/li&gt;
&lt;li&gt;参与者向协调者发送“Done”消息。&lt;/li&gt;
&lt;li&gt;协调者收到所有参与者节点的“Done”消息，表明该一致性操作以回滚的形式结束。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;可见在提交阶段，无论是决定要“Commit”还是“Rollback”，整个一致性操作都能结束。&lt;/p&gt;
&lt;p&gt;下图展示了两阶段提交的消息流。其中图的左侧部分表示一致性操作成功情况下的消息流；图的右侧部分表示因部分参与者不回应导致的一致性操作失败情况下的消息流。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/118/pic/2p_hu_8d8f95cb519f9750.webp&#34; alt=&#34;两阶段提交的消息流&#34; width=&#34;900&#34; height=&#34;673&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;上述过程中，协调者可以是一个专门起到协调作用而不参与一致性操作的节点，也可以是一个参与一致性操作的节点。如果它是后者，那它不仅要完成协调者的工作，还要作为参与者完成所有参与者要进行的工作，即它也要将操作封装成事务执行、回应“No”消息或“Yes”消息、提交或回滚事务等。&lt;/p&gt;
&lt;h2 id=&#34;优劣&#34;&gt;优劣&lt;/h2&gt;
&lt;p&gt;两阶段提交的操作步骤比较简单，但也有几个明显的问题。&lt;/p&gt;
&lt;p&gt;首先，两阶段提交存在&lt;strong&gt;同步阻塞问题&lt;/strong&gt;。参与者收到协调者发出的“Prepare”消息后，会开启事务完成消息中的操作。事务的开启意味着参与者的并发处理能力将受到很大的影响。而且，参与者不是一个节点而是一群节点，所以整个系统中的节点都会因为事务的开启而阻塞。这个过程可能很长，要等待协调者收集完参与者的消息并发布进一步的消息后，该过程才能结束。&lt;/p&gt;
&lt;p&gt;其次，两阶段提交&lt;strong&gt;高度依赖协调者发出的消息&lt;/strong&gt;，因此存在&lt;strong&gt;单点故障&lt;/strong&gt;。如果协调者在两阶段提交的过程中出现问题，则会导致系统失控。尤其是在准备阶段和提交阶段之间，如果此时协调者宕机，则会导致已经开启了事务的各个参与者既不能收到“Commit”消息，也不能收到“Rollback”消息。这样事务会一直开启，导致系统全局阻塞。&lt;/p&gt;
&lt;p&gt;第三，两阶段提交其实设置了一个假设：如果参与者能够收到和正常回应“Prepare”消息，那它应该也能正常收到“Commit”消息或者“Rollback”消息。通常，这个假设是成立的。因为参与者在准备阶段和提交阶段之间宕机的概率很小。但是，再小的概率也有可能发生，尤其是在高并发和多节点的情况下。一旦因为网络抖动导致部分参与者无法收到“Commit”消息，则会出现部分参与者提交了事务，部分参与者未提交事务这种&lt;strong&gt;不一致情况的发生&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;第四，两阶段提交存在&lt;strong&gt;状态丢失问题&lt;/strong&gt;。如果协调者在发出“Commit”消息或者“Rollback”消息后宕机，部分参与者收到了这条消息后提交或者回滚了事务，部分参与者没有收到这条消息。那即使重新选举出一个新的协调者，新的协调者也无法确定各个参与者到底处在哪个状态。&lt;/p&gt;
&lt;p&gt;为了解决两阶段提交的缺陷，出现了三阶段提交。&lt;/p&gt;
&lt;h2 id=&#34;三阶段提交&#34;&gt;三阶段提交&lt;/h2&gt;
&lt;p&gt;三阶段提交（Three-phase Commit，简称3PC）是二阶段提交的改进，它修复了二阶段提交的一些缺陷，但也使得整个一致性操作过程多出了一个阶段，更为复杂。此外，三阶段提交还引入了一些超时机制，以便于节点在失去或者部分失去与外界联系时做出一些操作。&lt;/p&gt;
&lt;p&gt;三阶段提交一共分为三个阶段：&lt;strong&gt;CanCommit阶段&lt;/strong&gt;、&lt;strong&gt;PreCommit阶段&lt;/strong&gt;、&lt;strong&gt;DoCommit阶段&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id=&#34;具体实现-1&#34;&gt;具体实现&lt;/h2&gt;
&lt;p&gt;CanCommit阶段可以细分为以下步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;协调者向参与者发送“CanCommit”消息，询问参与者能否完成消息中的操作。然后协调者等待各个参与者的响应。&lt;/li&gt;
&lt;li&gt;参与者接收到“CanCommit”消息后，判断自身是否能够顺利完成操作。如果自身可以，则向协调者回应“Yes”消息；如果自身不可以，则向协调者回应“No”消息。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在CanCommit阶段，协调者和参与者只是就能否完成操作进行了交流，并没有进行实际的工作。接下来进入PreCommit阶段。&lt;/p&gt;
&lt;p&gt;PreCommit阶段则根据协调者收到的参与者的反馈不同而不同。&lt;/p&gt;
&lt;p&gt;如果协调者收到了所有参与者的“Yes”消息，则进行如下操作：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;协调者向参与者发送“PreCommit”消息。然后协调者等待各个参与者的响应。&lt;/li&gt;
&lt;li&gt;参与者收到“PreCommit”消息后，将此次要进行的操作封装成事务执行，但不要提交。&lt;/li&gt;
&lt;li&gt;如果参与者完成了各项操作，则向协调者回应“ACK”消息。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在这种情况下，一致性操作则会进入DoCommit阶段。&lt;/p&gt;
&lt;p&gt;如果协调者在一定时间内没有收齐所有参与者的消息，或者收到的消息中有“No”消息，则进行如下操作：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;协调者向所有参与者发送“Abort”消息，表明此次一致性操作取消。&lt;/li&gt;
&lt;li&gt;参与者收到“Abort”后，中止当前的操作。如果参与者在一定时间内没有收到协调者发来的操作消息，也中止当前的操作。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这种情况下一致性操作结束，无需再进入DoCommit阶段，&lt;/p&gt;
&lt;p&gt;在DoCommit阶段中，协调者会根据参与者的回应采取不同的操作。&lt;/p&gt;
&lt;p&gt;如果协调者收到了所有参与者的“ACK”消息，则进行如下的操作：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;协调者向参与者发送“Commit”消息。&lt;/li&gt;
&lt;li&gt;参与者收到“Commit”消息后，提交在准备阶段已经完成但尚未提交的事务。&lt;/li&gt;
&lt;li&gt;参与者向协调者发送“Done”消息。&lt;/li&gt;
&lt;li&gt;协调者收到所有参与者的“Done”消息，表明该一致性操作以成功的形式结束。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果协调者在一定时间内没有收齐参与者的“ACK”消息，则进行如下的操作：&lt;/p&gt;</description>
    </item>
    <item>
      <title>三阶段提交算法详解</title>
      <link>https://yeecode.top/blog/117/</link>
      <pubDate>Wed, 11 Jan 2023 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/117/</guid>
      
      <description>&lt;p&gt;通过本文，我们将对三阶段提交（Three-phase Commit，简称3PC）算法进行详细的介绍。在阅读本文之前，需要大家对两阶段提交（Two-phase Commit，简称2PC）算法十分了解。不了解的同学可以翻看我之前的文章。&lt;/p&gt;
&lt;p&gt;我们从两阶段提交算法的缺点说起，然后引出三阶段提交算法，并分析三阶段提交算法如何克服了这些缺点。&lt;/p&gt;
&lt;h2 id=&#34;两阶段提交算法的缺点&#34;&gt;两阶段提交算法的缺点&lt;/h2&gt;
&lt;p&gt;两阶段提交的操作步骤比较简单，但也有几个明显的问题。&lt;/p&gt;
&lt;p&gt;首先，两阶段提交存在&lt;strong&gt;同步阻塞问题&lt;/strong&gt;。参与者收到协调者发出的“Prepare”消息后，会开启事务完成消息中的操作。事务的开启意味着参与者的并发处理能力将受到很大的影响。而且，参与者不是一个节点而是一群节点，所以整个系统中的节点都会因为事务的开启而阻塞。这个过程可能很长，要等待协调者收集完参与者的消息并发布进一步的消息后，该过程才能结束。&lt;/p&gt;
&lt;p&gt;其次，两阶段提交&lt;strong&gt;高度依赖协调者发出的消息&lt;/strong&gt;，因此存在&lt;strong&gt;单点故障&lt;/strong&gt;。如果协调者在两阶段提交的过程中出现问题，则会导致系统失控。尤其是在准备阶段和提交阶段之间，如果此时协调者宕机，则会导致已经开启了事务的各个参与者既不能收到“Commit”消息，也不能收到“Rollback”消息。这样事务会一直开启，导致系统全局阻塞。&lt;/p&gt;
&lt;p&gt;第三，两阶段提交其实设置了一个假设：如果参与者能够收到和正常回应“Prepare”消息，那它应该也能正常收到“Commit”消息或者“Rollback”消息。通常，这个假设是成立的。因为参与者在准备阶段和提交阶段之间宕机的概率很小。但是，再小的概率也有可能发生，尤其是在高并发和多节点的情况下。一旦因为网络抖动导致部分参与者无法收到“Commit”消息，则会出现部分参与者提交了事务，部分参与者未提交事务这种&lt;strong&gt;不一致情况的发生&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;第四，两阶段提交存在&lt;strong&gt;状态丢失问题&lt;/strong&gt;。如果协调者在发出“Commit”消息或者“Rollback”消息后宕机，部分参与者收到了这条消息后提交或者回滚了事务，部分参与者没有收到这条消息。那即使重新选举出一个新的协调者，新的协调者也无法确定各个参与者到底处在哪个状态。&lt;/p&gt;
&lt;p&gt;为了解决两阶段提交的缺陷，出现了三阶段提交。&lt;/p&gt;
&lt;h2 id=&#34;三阶段提交详解&#34;&gt;三阶段提交详解&lt;/h2&gt;
&lt;p&gt;三阶段提交（Three-phase Commit，简称3PC）是二阶段提交的改进，它修复了二阶段提交的一些缺陷，但也使得整个一致性操作过程多出了一个阶段，更为复杂。此外，三阶段提交还引入了一些超时机制，以便于节点在失去或者部分失去与外界联系时做出一些操作。&lt;/p&gt;
&lt;p&gt;三阶段提交一共分为三个阶段：&lt;strong&gt;CanCommit阶段&lt;/strong&gt;、&lt;strong&gt;PreCommit阶段&lt;/strong&gt;、&lt;strong&gt;DoCommit阶段&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;CanCommit阶段可以细分为以下步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;协调者向参与者发送“CanCommit”消息，询问参与者能否完成消息中的操作。然后协调者等待各个参与者的响应。&lt;/li&gt;
&lt;li&gt;参与者接收到“CanCommit”消息后，判断自身是否能够顺利完成操作。如果自身可以，则向协调者回应“Yes”消息；如果自身不可以，则向协调者回应“No”消息。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在CanCommit阶段，协调者和参与者只是就能否完成操作进行了交流，并没有进行实际的工作。接下来进入PreCommit阶段。&lt;/p&gt;
&lt;p&gt;PreCommit阶段则根据协调者收到的参与者的反馈不同而不同。&lt;/p&gt;
&lt;p&gt;如果协调者收到了所有参与者的“Yes”消息，则进行如下操作：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;协调者向参与者发送“PreCommit”消息。然后协调者等待各个参与者的响应。&lt;/li&gt;
&lt;li&gt;参与者收到“PreCommit”消息后，将此次要进行的操作封装成事务执行，但不要提交。&lt;/li&gt;
&lt;li&gt;如果参与者完成了各项操作，则向协调者回应“ACK”消息。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在这种情况下，一致性操作则会进入DoCommit阶段。&lt;/p&gt;
&lt;p&gt;如果协调者在一定时间内没有收齐所有参与者的消息，或者收到的消息中有“No”消息，则进行如下操作：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;协调者向所有参与者发送“Abort”消息，表明此次一致性操作取消。&lt;/li&gt;
&lt;li&gt;参与者收到“Abort”后，中止当前的操作。如果参与者在一定时间内没有收到协调者发来的操作消息，也中止当前的操作。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这种情况下一致性操作结束，无需再进入DoCommit阶段，&lt;/p&gt;
&lt;p&gt;在DoCommit阶段中，协调者会根据参与者的回应采取不同的操作。&lt;/p&gt;
&lt;p&gt;如果协调者收到了所有参与者的“ACK”消息，则进行如下的操作：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;协调者向参与者发送“Commit”消息。&lt;/li&gt;
&lt;li&gt;参与者收到“Commit”消息后，提交在准备阶段已经完成但尚未提交的事务。&lt;/li&gt;
&lt;li&gt;参与者向协调者发送“Done”消息。&lt;/li&gt;
&lt;li&gt;协调者收到所有参与者的“Done”消息，表明该一致性操作以成功的形式结束。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果协调者在一定时间内没有收齐参与者的“ACK”消息，则进行如下的操作：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;协调者向参与者发送“Rollback”消息。&lt;/li&gt;
&lt;li&gt;参与者收到“Rollback”消息后，回滚在准备阶段已经完成但尚未提交的事务。&lt;/li&gt;
&lt;li&gt;参与者向协调者发送“Done”消息。&lt;/li&gt;
&lt;li&gt;协调者收到所有参与者的“Done”消息，表明该一致性操作以失败的形式结束。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;图{@fig:251san}展示了三阶段提交的消息流。其中图的左侧部分表示一致性操作成功情况下的消息流；图的右侧部分表示因部分参与者在CanCommit阶段回应“No”消息导致的一致性操作失败情况下的消息流。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/117/pic/3p_hu_f0bb324cbf553453.webp&#34; alt=&#34;三阶段提交的消息流&#34; width=&#34;900&#34; height=&#34;813&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;{#fig:251san}&lt;/p&gt;
&lt;p&gt;在DoCommit阶段中，如果参与者在一定时间内没有收到“Commit”消息或者“Rollback”消息，则参与者会提交事务。这是因为既然能够进入DoCommit阶段，说明所有的参与者在CanCommit都回应了“Yes”。此时，所有的参与者都应该可以正确地提交事务，从而完成该一致性操作。&lt;/p&gt;
&lt;p&gt;DoCommit阶段中参与者会在无法收到“Commit”消息或者“Rollback”消息时提交事务，这解决了两个问题。首先，整个一致性操作不会因为协调者的突然宕机而导致参与者开启的事务无法关闭，从而阻塞整个系统。其次，如果协调者在发送“Commit”指令前后宕机，则整个系统的状态是确定的，因为各个参与者都会默认提交事务。&lt;/p&gt;
&lt;h2 id=&#34;三阶段提交的优劣&#34;&gt;三阶段提交的优劣&lt;/h2&gt;
&lt;p&gt;当然，这个机制&lt;strong&gt;仍然存在漏洞&lt;/strong&gt;。如果协调者在发送“Rollback”消息后宕机，部分参与者收到了“Rollback”消息回滚了事务；部分参与者因为没有收到“Rollback”消息便会默认提交事务。这将导致系统的一致性被打破。而且即使选举出新的协调者，新协调者也无法判断哪些参与者回滚了事务，哪些参与者提交了事务。&lt;/p&gt;
&lt;p&gt;不过，这种&lt;strong&gt;漏洞的发生概率极小&lt;/strong&gt;。“Rollback”消息在DoCommit阶段发出，既然能够进入该阶段，说明所有参与者在CanCommit阶段都回复了“Yes”消息，则大概率会在PreCommit阶段正常回复“ACK”消息。因此，协调者因为无法收起“ACK”消息而发出“Rollback”消息的概率很低，而在发出“Rollback”消息后恰好宕机的概率则更低。&lt;/p&gt;
&lt;p&gt;因此，三阶段提交通过加入一个新的阶段和引入超时机制&lt;strong&gt;减少了两阶段提交的同步阻塞问题&lt;/strong&gt;，&lt;strong&gt;减弱了对协调者的依赖&lt;/strong&gt;，&lt;strong&gt;降低了系统状态丢失的概率&lt;/strong&gt;。然而，正如我们上面所分析的，三阶段提交依然存在漏洞，并&lt;strong&gt;不完美&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;总体而言，两阶段提交和三阶段提交的实现比较简单，且能够实现线性一致性。尤其是三阶段提交，其故障概率很低，在实践中应用十分广泛。&lt;/p&gt;
&lt;p&gt;好，本文就介绍到这里。如果想对分布式系统的一致性有更全面系统的认识，可以参考下面的书籍。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://union-click.jd.com/jdc?e=&amp;p=JF8BAMYJK1olXDYCVl9YAEkUBV9MRANLAjZbERscSkAJHTdNTwcKBlMdBgABFksVAmoAGVgTQl9HCANtb05yfSYIcEV3CVJgMgctdStjWGwSa1cZbQcyVF9cCE8eBWcKG2slXQEyAjBdCUoWAm4NG14WbQcyVFlYCk0UBWoMHFwQXDYFVFdtUx55BWdbSV9AVQEBVl9fCnsnM2w4K2sVbQUyFjAIWBgRC2YLTFMWDQQHUVkKWx4SBDsNHVMdDwQLB1YPCnsVAm4MEms&#34;&gt;&lt;img src=&#34;https://yeecode.top/img/promo/dsbook.jpg&#34; width=&#34;680&#34; height=&#34;453&#34; loading=&#34;lazy&#34;  alt=&#34;分布式系统原理与工程实践&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://union-click.jd.com/jdc?e=&amp;amp;p=JF8BAMYJK1olXDYCVl9YAEkUBV9MRANLAjZbERscSkAJHTdNTwcKBlMdBgABFksVAmoAGVgTQl9HCANtb05yfSYIcEV3CVJgMgctdStjWGwSa1cZbQcyVF9cCE8eBWcKG2slXQEyAjBdCUoWAm4NG14WbQcyVFlYCk0UBWoMHFwQXDYFVFdtUx55BWdbSV9AVQEBVl9fCnsnM2w4K2sVbQUyFjAIWBgRC2YLTFMWDQQHUVkKWx4SBDsNHVMdDwQLB1YPCnsVAm4MEms&#34;&gt;《分布式系统原理与工程实践》&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;书籍对各种一致性算法都进行了详细的介绍。对大家体系化地掌握这些知识很有帮助。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;../pic/dsbook_list01.jpg&#34; alt=&#34;书籍部分目录&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;另外这本书对分布式系统相关的理论、实践、工程知识均进行了详细的介绍，层层递进，有助于大家建立完整的分布式系统知识体系。涉及一致性、共识、Paxos、分布式事务、服务治理、微服务、幂等、消息系统、Zookeeper等。并且还发行了繁体版。&lt;/p&gt;
&lt;p&gt;再次提醒大家，在学习分布式系统时，因为涉及到的理论、框架很多。所以大家一定要理清知识脉络，否则容易学得越多越混乱。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;好了，我是程序员易哥。&lt;/p&gt;
&lt;p&gt;别忘了&lt;strong&gt;点赞&lt;/strong&gt;……&lt;/p&gt;
&lt;p&gt;这回就说这么多！&lt;/p&gt;
&lt;p&gt;可以&lt;strong&gt;关注&lt;/strong&gt;我！等我下回接着说！&lt;/p&gt;</description>
    </item>
    <item>
      <title>两阶段提交算法为什么满足线性一致性？</title>
      <link>https://yeecode.top/blog/116/</link>
      <pubDate>Mon, 09 Jan 2023 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/116/</guid>
      
      <description>&lt;p&gt;两阶段提交（Two-phase Commit，简称2PC）算法我们都熟悉，我们也知道两阶段提交是一个常用的满足&lt;strong&gt;线性一致性&lt;/strong&gt;的算法。&lt;/p&gt;
&lt;p&gt;那它为什么满足线性一致性呢？我们今天就来证明这一点。&lt;/p&gt;
&lt;p&gt;提前说明一下，阅读本文需要大家对下面的两点有详细的了解：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;线性一致性的定义及约束&lt;/li&gt;
&lt;li&gt;两阶段提交算法的实现逻辑&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对这块概念不清晰的同学，可以阅读我们之前的文章。&lt;/p&gt;
&lt;p&gt;在证明之前，我们首先确认两点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;考虑到网络延迟、节点处理速度等的不同，各个节点收到“Prepare”消息并创建事务是有先后的。&lt;/li&gt;
&lt;li&gt;考虑到网络延迟、节点处理速度等的不同，各个节点收到“Commit”消息并提交事务是有先后的。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在一次成功的二阶段提交操作中一定存在一个全局锁时段。这一时段从准备阶段的最后一个“Yes”消息发出开始，到提交阶段的第一个“Commit”消息被接收结束。在这一时段中，所有的节点都已经创建事务但未提交事务。&lt;/p&gt;
&lt;p&gt;在全局锁时段之后，是提交时段。这一时段从提交阶段的第一个“Commit”消息被接收开始，到提交阶段的最后一个“Done”消息发出为止。在这一时段中，各个节点陆续完成事务提交，如下图所示。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/116/pic/2pcZ0_hu_abaf03929c35be50.webp&#34; alt=&#34;全局锁时段与全局更新时段示意图&#34; width=&#34;900&#34; height=&#34;774&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;这里我们假设参与者开启事务和发出Yes消息是同时进行的，参与者收到Commit消息和提交事务是同时进行的。这两个假设都&lt;strong&gt;不会&lt;/strong&gt;影响最终的结论。&lt;/p&gt;
&lt;p&gt;在全局锁时段和提交时段之间存在一个&lt;strong&gt;同步时刻&lt;/strong&gt;。同步时刻在&lt;strong&gt;全局历史中是唯一确定的&lt;/strong&gt;，且这一时刻具有以下特点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;除当前的2PC操作外，没有其他操作正在集群中开展。全局锁时段中在各个节点创建的锁可以共同保证这一点。&lt;/li&gt;
&lt;li&gt;对于任何一个节点而言，该时刻之后进行的第一个操作都是当前2PC的提交操作。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;那我们可以等价地认为2PC操作引发的变更就是在&lt;strong&gt;同步时刻&lt;/strong&gt;发生的。这样，2PC提交操作在全局历史中的位置就被确定了下来。因此，任何通过2PC操作开展的事件都可以唯一地映射到&lt;strong&gt;全局历史&lt;/strong&gt;中，就像是在全局历史中开展的事件一样，那这样的操作自然可以保证线性一致性。&lt;/p&gt;
&lt;p&gt;接下来，我们再用一个更为宏观视角的示例来理解这一过程。&lt;/p&gt;
&lt;p&gt;在下图中，节点A作为协调者发起了一次两阶段提交操作A2，并触发了参与者节点B和节点C的B2和C1操作。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/116/pic/2pcZ_hu_2768abb71f07d06e.webp&#34; alt=&#34;两阶段提交操作示例&#34; width=&#34;900&#34; height=&#34;943&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;可以看出，两阶段提交事件A2和另外的B1、B3事件是并发的。我们可以给出线性一致性要求的全局历史：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A1-A2、B1、B3并发- C2&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;根据线性一致性的约束，A1事件发生在A2事件之前，读到的变量x必须为旧值；C2事件发生在A2事件之后，读到的变量x必须为新值。显然在二阶段提交中这两者都是满足的。&lt;/p&gt;
&lt;p&gt;线性一致性要求单个节点的事件历史在全局历史上符合程序的先后顺序，但是对于并发事件没有约束。因此，关于B1、B2读取变量x的操作存在以下几种情况，且都满足线性一致性的所有约束。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;B1和B3都读取到A2变更前的旧值&lt;/li&gt;
&lt;li&gt;B1读取到A2变更前的旧值、B3读取到A2变更后的新值&lt;/li&gt;
&lt;li&gt;B1和B3都读取到A2变更后的新值&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;进一步地，我们可以把A2操作的两阶段提交细节步骤标注出来，如下图所示。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/116/pic/2pcZ1_hu_6fceefeae0d9705d.webp&#34; alt=&#34;两阶段提交操作细节示例&#34; width=&#34;900&#34; height=&#34;951&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;这时我们可以得出结论：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在同步时刻，一定不会发生读取变量x的操作&lt;/li&gt;
&lt;li&gt;在同步时刻之前，读变量x一定会读取到旧值&lt;/li&gt;
&lt;li&gt;在同步时刻之后，读变量x一定会读取到新值&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;上述A2操作的两阶段提交细节步骤是我们臆想绘制出来的。在实际的2PC操作中，同步时刻在2PC中的具体位置是难以确定。&lt;/p&gt;
&lt;p&gt;可见，两阶段提交不对并发事件给出保证。但我们知道，&lt;strong&gt;一定存在一个同步时刻，将两阶段提交映射到全局历史上&lt;/strong&gt;。这个时刻可能在B1之前，可能在B1和B3之间，可能在B3之后，如图所示。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/116/pic/2pcZ3_hu_c9a39b129bc8d655.webp&#34; alt=&#34;同步时刻可能所处的位置示意图&#34; width=&#34;900&#34; height=&#34;791&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;本质上，&lt;strong&gt;两阶段提交是通过给各个节点同时加锁来实现了一个全局同步的时钟&lt;/strong&gt;，这个时钟并不能够用来记录时间的长短，但足以标定出两阶段提交在全局历史中的位置，借此实现了线性一致性。&lt;/p&gt;
&lt;p&gt;到这里，我们就论证了两阶段提交满足线性一致性。&lt;/p&gt;
&lt;p&gt;当然，实现这一全局同步的时钟的代价也是很高的，它直接&lt;strong&gt;导致了所有节点的阻塞&lt;/strong&gt;，影响了分布式系统的并发性能。&lt;/p&gt;
&lt;p&gt;好，本文就介绍到这里。如果想对分布式系统的一致性有更全面系统的认识，可以参考下面的书籍。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://union-click.jd.com/jdc?e=&amp;p=JF8BAMYJK1olXDYCVl9YAEkUBV9MRANLAjZbERscSkAJHTdNTwcKBlMdBgABFksVAmoAGVgTQl9HCANtb05yfSYIcEV3CVJgMgctdStjWGwSa1cZbQcyVF9cCE8eBWcKG2slXQEyAjBdCUoWAm4NG14WbQcyVFlYCk0UBWoMHFwQXDYFVFdtUx55BWdbSV9AVQEBVl9fCnsnM2w4K2sVbQUyFjAIWBgRC2YLTFMWDQQHUVkKWx4SBDsNHVMdDwQLB1YPCnsVAm4MEms&#34;&gt;&lt;img src=&#34;https://yeecode.top/img/promo/dsbook.jpg&#34; width=&#34;680&#34; height=&#34;453&#34; loading=&#34;lazy&#34;  alt=&#34;分布式系统原理与工程实践&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://union-click.jd.com/jdc?e=&amp;amp;p=JF8BAMYJK1olXDYCVl9YAEkUBV9MRANLAjZbERscSkAJHTdNTwcKBlMdBgABFksVAmoAGVgTQl9HCANtb05yfSYIcEV3CVJgMgctdStjWGwSa1cZbQcyVF9cCE8eBWcKG2slXQEyAjBdCUoWAm4NG14WbQcyVFlYCk0UBWoMHFwQXDYFVFdtUx55BWdbSV9AVQEBVl9fCnsnM2w4K2sVbQUyFjAIWBgRC2YLTFMWDQQHUVkKWx4SBDsNHVMdDwQLB1YPCnsVAm4MEms&#34;&gt;《分布式系统原理与工程实践》&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;书籍对各种一致性算法都进行了介绍，包括两阶段提交算法、三阶段提交算法等，并进行了详细分析。对大家体系化地掌握这些知识很有帮助。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;../pic/dsbook_list01.jpg&#34; alt=&#34;书籍部分目录&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;另外这本书对分布式系统相关的理论、实践、工程知识均进行了详细的介绍，层层递进，有助于大家建立完整的分布式系统知识体系。涉及&lt;strong&gt;一致性、共识、Paxos、分布式事务、服务治理、微服务、幂等、消息系统、Zookeeper&lt;/strong&gt;等。并且还发行了繁体版。&lt;/p&gt;
&lt;p&gt;再次提醒大家，在学习分布式系统时，因为涉及到的理论、框架很多。所以大家一定要理清知识脉络，否则容易学得越多越混乱。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;好了，我是程序员易哥。&lt;/p&gt;
&lt;p&gt;别忘了&lt;strong&gt;点赞&lt;/strong&gt;……&lt;/p&gt;
&lt;p&gt;这回就说这么多！&lt;/p&gt;
&lt;p&gt;可以&lt;strong&gt;关注&lt;/strong&gt;我！等我下回接着说！&lt;/p&gt;</description>
    </item>
    <item>
      <title>两阶段提交算法详解</title>
      <link>https://yeecode.top/blog/115/</link>
      <pubDate>Sun, 08 Jan 2023 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/115/</guid>
      
      <description>&lt;p&gt;前面的文章中我们详细介绍了线性一致性，包括线性一致性的约束等。并且，我们说两阶段提交就是一个常用的满足线性一致性的算法。&lt;/p&gt;
&lt;p&gt;接下来我们介绍下两阶段提交算法。&lt;/p&gt;
&lt;h2 id=&#34;1-背景&#34;&gt;1、 背景&lt;/h2&gt;
&lt;p&gt;假设一个分布式系统中存在多个节点，如下图所示，其中的节点A收到了数据变更请求。如果系统要满足一致性，那节点A在完成自身数据变更的同时必须协调其他节点完成数据变更。这种情况下，我们将发起操作的节点称为协调者(Coordinator)，而将接受操作的节点称为参与者(Participants)。在图中，节点A是协调者，节点B和节点C都是参与者。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/115/pic/yizhi_hu_2b0528f93e111d31.webp&#34; alt=&#34;一致性操作中的角色划分&#34; width=&#34;694&#34; height=&#34;414&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;具体实现似乎很简单，作为协调者的节点A只要在更新自身数据的同时将变更操作通知各个参与者，参与者收到协调者的变更通知后各自完成自身变更即可,如下图所示。这样，用户再从系统中读取该数据时，无论读请求落在哪个节点上，读取到的结果都是一致的。系统便满足了一致性。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/115/pic/yizhi01_hu_b07e475c62b2769c.webp&#34; alt=&#34;节点间的一致性操作&#34; width=&#34;900&#34; height=&#34;524&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;可在实际中并没有这么理想：协调者和参与者之间的&lt;strong&gt;通讯&lt;/strong&gt;可能不可靠，参与者也可能因为各种&lt;strong&gt;内部原因&lt;/strong&gt;导致更新失败。&lt;/p&gt;
&lt;p&gt;协调者更新了自身状态后通知参与者，假设某个参与者因为通讯原因或者节点自身原因更新失败，则那用户便可能从集群中读取到&lt;strong&gt;不一致的结果&lt;/strong&gt;。这时系统的一致性便被打破。&lt;/p&gt;
&lt;p&gt;发生这种不一致的根源是每个节点都能知道自身是否操作成功，但却无法知道其他节点是否更新成功。&lt;/p&gt;
&lt;p&gt;类比后面要介绍的两阶段提交和三阶段提交，我们可以将上图所示的操作称为“一阶段提交”，因为整个一致性操作过程中只有一个“提交”阶段。&lt;/p&gt;
&lt;p&gt;显然我们的“一阶段提交”并不能够很好地实现一致性操作。接下来我们要学习的就是更好的也更为实用的一致性算法：两阶段提交算法。&lt;/p&gt;
&lt;h2 id=&#34;2-2pc的实现&#34;&gt;2、 2PC的实现&lt;/h2&gt;
&lt;p&gt;两阶段提交（Two-phase Commit，简称2PC）是一种比较简单的一致性算法（或者说协议）。它将整个提交过程分成了&lt;strong&gt;准备阶段（Prepare）&lt;/strong&gt;、**提交阶段（Commit）**这两个阶段。&lt;/p&gt;
&lt;p&gt;在准备阶段中，可以细分成如下的操作步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;协调者给参与者发送“Prepare”消息，“Prepare”消息中包含了此次更新要进行的所有操作。然后协调者等待各个参与者的响应。&lt;/li&gt;
&lt;li&gt;参与者收到“Prepare”消息后将消息中的操作封装为一个事务并执行，但不要提交。&lt;/li&gt;
&lt;li&gt;参与者在执行事务的过程中如果遇到任何问题导致事务中的操作无法完成，则向协调者回应“No”消息；如果事务中的全部操作能够完成，则向协调者回应“Yes”消息。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;经过准备阶段，所有的参与者已经尝试完成了所有的操作，但是没有把这些操作提交。并且，根据将自身能否完成操作的结果回应给了协调者。接下来，便进入了第二个阶段：提交阶段。&lt;/p&gt;
&lt;p&gt;在提交阶段中，协调者会根据收集到的各个参与者的回应执行不同的操作。&lt;/p&gt;
&lt;p&gt;如果协调者收到了所有参与者的“Yes”消息，则进行如下操作：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;协调者向参与者发送“Commit”消息。&lt;/li&gt;
&lt;li&gt;参与者收到“Commit”消息后，提交在准备阶段已经完成但尚未提交的事务。&lt;/li&gt;
&lt;li&gt;参与者向协调者发送“Done”消息。&lt;/li&gt;
&lt;li&gt;协调者收到所有参与者的“Done”消息，表明该一致性操作以成功的形式结束。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果协调者在一定时间内没有收齐所有参与者的消息，或者收到的消息中有“No”消息，则进行如下操作：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;协调者向参与者发送“Rollback”消息。&lt;/li&gt;
&lt;li&gt;参与者收到“Rollback”消息后，回滚在准备阶段已经完成但尚未提交的事务。&lt;/li&gt;
&lt;li&gt;参与者向协调者发送“Done”消息。&lt;/li&gt;
&lt;li&gt;协调者收到所有参与者节点的“Done”消息，表明该一致性操作以回滚的形式结束。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;可见在提交阶段，无论是决定要“Commit”还是“Rollback”，整个一致性操作都能结束。&lt;/p&gt;
&lt;p&gt;下图展示了两阶段提交的消息流。其中图的左侧部分表示一致性操作成功情况下的消息流；图的右侧部分表示因部分参与者不回应导致的一致性操作失败情况下的消息流。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/115/pic/2p_hu_8d8f95cb519f9750.webp&#34; alt=&#34;两阶段提交的消息流&#34; width=&#34;900&#34; height=&#34;673&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;上述过程中，协调者可以是一个专门起到协调作用而不参与一致性操作的节点，也可以是一个参与一致性操作的节点。如果它是后者，那它不仅要完成协调者的工作，还要作为参与者完成所有参与者要进行的工作，即它也要将操作封装成事务执行、回应“No”消息或“Yes”消息、提交或回滚事务等。&lt;/p&gt;
&lt;p&gt;上面就是两阶段提交算法的实现过程，还是十分简单的。我们可以方便地实现它。&lt;/p&gt;
&lt;h2 id=&#34;3-缺点&#34;&gt;3、 缺点&lt;/h2&gt;
&lt;p&gt;两阶段提交的操作步骤比较简单，但也有几个明显的问题。&lt;/p&gt;
&lt;p&gt;首先，两阶段提交存在&lt;strong&gt;同步阻塞问题&lt;/strong&gt;。参与者收到协调者发出的“Prepare”消息后，会开启事务完成消息中的操作。事务的开启意味着参与者的并发处理能力将受到很大的影响。而且，参与者不是一个节点而是一群节点，所以整个系统中的节点都会因为事务的开启而阻塞。这个过程可能很长，要等待协调者收集完参与者的消息并发布进一步的消息后，该过程才能结束。&lt;/p&gt;
&lt;p&gt;其次，两阶段提交&lt;strong&gt;高度依赖协调者发出的消息&lt;/strong&gt;，因此存在&lt;strong&gt;单点故障&lt;/strong&gt;。如果协调者在两阶段提交的过程中出现问题，则会导致系统失控。尤其是在准备阶段和提交阶段之间，如果此时协调者宕机，则会导致已经开启了事务的各个参与者既不能收到“Commit”消息，也不能收到“Rollback”消息。这样事务会一直开启，导致系统全局阻塞。&lt;/p&gt;
&lt;p&gt;第三，两阶段提交其实设置了一个假设：如果参与者能够收到和正常回应“Prepare”消息，那它应该也能正常收到“Commit”消息或者“Rollback”消息。通常，这个假设是成立的。因为参与者在准备阶段和提交阶段之间宕机的概率很小。但是，再小的概率也有可能发生，尤其是在高并发和多节点的情况下。一旦因为网络抖动导致部分参与者无法收到“Commit”消息，则会出现部分参与者提交了事务，部分参与者未提交事务这种&lt;strong&gt;不一致情况的发生&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;第四，两阶段提交&lt;strong&gt;存在状态丢失问题&lt;/strong&gt;。如果协调者在发出“Commit”消息或者“Rollback”消息后宕机，部分参与者收到了这条消息后提交或者回滚了事务，部分参与者没有收到这条消息。那即使重新选举出一个新的协调者，新的协调者也无法确定各个参与者到底处在哪个状态。&lt;/p&gt;
&lt;p&gt;为了解决两阶段提交的缺陷，出现了三阶段提交。&lt;/p&gt;
&lt;h2 id=&#34;4-展望&#34;&gt;4、 展望&lt;/h2&gt;
&lt;p&gt;两阶段提交算法是满足线性一致性的，《分布式系统原理与工程实践》一书给出了证明。想要详细了解的同学可以翻看该书的相关章节。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://union-click.jd.com/jdc?e=&amp;p=JF8BAMYJK1olXDYCVl9YAEkUBV9MRANLAjZbERscSkAJHTdNTwcKBlMdBgABFksVAmoAGVgTQl9HCANtb05yfSYIcEV3CVJgMgctdStjWGwSa1cZbQcyVF9cCE8eBWcKG2slXQEyAjBdCUoWAm4NG14WbQcyVFlYCk0UBWoMHFwQXDYFVFdtUx55BWdbSV9AVQEBVl9fCnsnM2w4K2sVbQUyFjAIWBgRC2YLTFMWDQQHUVkKWx4SBDsNHVMdDwQLB1YPCnsVAm4MEms&#34;&gt;&lt;img src=&#34;https://yeecode.top/img/promo/dsbook.jpg&#34; width=&#34;680&#34; height=&#34;453&#34; loading=&#34;lazy&#34;  alt=&#34;分布式系统原理与工程实践&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://union-click.jd.com/jdc?e=&amp;amp;p=JF8BAMYJK1olXDYCVl9YAEkUBV9MRANLAjZbERscSkAJHTdNTwcKBlMdBgABFksVAmoAGVgTQl9HCANtb05yfSYIcEV3CVJgMgctdStjWGwSa1cZbQcyVF9cCE8eBWcKG2slXQEyAjBdCUoWAm4NG14WbQcyVFlYCk0UBWoMHFwQXDYFVFdtUx55BWdbSV9AVQEBVl9fCnsnM2w4K2sVbQUyFjAIWBgRC2YLTFMWDQQHUVkKWx4SBDsNHVMdDwQLB1YPCnsVAm4MEms&#34;&gt;《分布式系统原理与工程实践》&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;书籍对各种一致性算法都进行了介绍，包括两阶段提交算法、三阶段提交算法等，并进行了详细分析。对大家体系化地掌握这些知识很有帮助。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;../pic/dsbook_list01.jpg&#34; alt=&#34;书籍部分目录&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;另外这本书对分布式系统相关的理论、实践、工程知识均进行了详细的介绍，层层递进，有助于大家建立完整的分布式系统知识体系。涉及&lt;strong&gt;一致性、共识、Paxos、分布式事务、服务治理、微服务、幂等、消息系统、Zookeeper&lt;/strong&gt;等。并且还发行了&lt;strong&gt;繁体版&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;再次提醒大家，在学习分布式系统时，因为涉及到的理论、框架很多。所以大家一定要理清知识脉络，否则容易学得越多越混乱。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;好了，我是程序员易哥。&lt;/p&gt;
&lt;p&gt;这回就说这么多！&lt;/p&gt;
&lt;p&gt;可以&lt;strong&gt;关注&lt;/strong&gt;我！等我下回接着说！&lt;/p&gt;</description>
    </item>
    <item>
      <title>什么是线性一致性？</title>
      <link>https://yeecode.top/blog/114/</link>
      <pubDate>Sun, 08 Jan 2023 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/114/</guid>
      
      <description>&lt;p&gt;线性一致性（Linearizability）又叫原子一致性（Atomic Consistency)这是现实中能实现的最高级的一致性级别，当然也是分布式系统的最高追求。&lt;/p&gt;
&lt;p&gt;然而要理解线性一致性（Linearizability），则必须先要理解顺序一致性（Sequential Consistency）。对于顺序一致性不是很理解的同学，可以翻看我之前的文章。&lt;/p&gt;
&lt;p&gt;我们这里就直接给出顺序一致性的两个约束：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;单个节点的事件历史在全局历史上符合程序的先后顺序&lt;/li&gt;
&lt;li&gt;全局事件历史在各个节点上一致&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;线性一致性在顺序一致性的基础上增加了一个约束：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果事件A的开始时间晚于事件B的结束时间，则在全局历史中，B在A之前&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;于是，线性一致性的约束变为了三个，即：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;单个节点的事件历史在全局历史上符合程序的先后顺序&lt;/li&gt;
&lt;li&gt;全局事件历史在各个节点上一致&lt;/li&gt;
&lt;li&gt;如果事件A的开始时间晚于事件B的结束时间，则在全局历史中，B在A之前&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;有了新增的这一个约束之后，我们就可以发现满足顺序一致性的下图（这就是我们在讨论顺序一致性时用过的图）不满足线性一致性。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/114/pic/lc_hu_31bdc441edbda9b7.webp&#34; alt=&#34;满足顺序一致性的事件&#34; width=&#34;898&#34; height=&#34;970&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;因为根据线性一致性新增的约束，B1的开始时间晚于A1的结束时间，那么在全局历史中，A1要在B1之前。但是B1却没有读到A1的操作结果，这显然是相悖的。&lt;/p&gt;
&lt;p&gt;同样的，A2要在B2之前，两者的结果是相悖的；C1要在B1之前，C1读到了A1的结果但B1却没有，这两个结果也是相悖的。&lt;/p&gt;
&lt;p&gt;于是上图中的事件顺序无法同时满足线性一致性的三个约束。&lt;/p&gt;
&lt;p&gt;在时序图中，越往下代表时间越晚。通过时序图我们可以清楚地知道各个节点中事件在全局的先后顺序。&lt;/p&gt;
&lt;p&gt;然而我们要明确，时序图实际是全局上帝视角。在计算机系统看来，每一个节点都只知道自身时间的先后顺序（例如A1在A2之前），但是却不知道节点间的先后顺序（例如A1到底是在B2之前还是B2之后）。&lt;/p&gt;
&lt;p&gt;但是线性一致性要求节点间事件满足全局先后顺序的约束，这就要求分布式系统必须协调出一个&lt;strong&gt;全局同步的时钟&lt;/strong&gt;。这一全局时钟不要求绝对精准，只要求能区分出事件的&lt;strong&gt;先后顺序&lt;/strong&gt;即可。但即便如此，这也是一个成本很高的工作。因此，线性一致性新增加的约束是一个&lt;strong&gt;很强的约束&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这就是为什么我们说线性一致性是分布式系统的最高追求。因为，它极为严格。&lt;/p&gt;
&lt;p&gt;全局锁就是一个常用的全局同步时钟。全局锁将全局时间分割为锁存在前、锁存在期间、锁释放后三段。这三段的先后关系是绝对成立的。基于此，便可以实现事件先后顺序的区分。&lt;/p&gt;
&lt;p&gt;如下图中，假设我们为B1事件增加全局的锁，则A1、A2、C1事件发生在锁存在前，一定在在B1事件之前；C2时间发生在锁释放后，一定在B1事件之后。这样，便确定了B1事件在全局中的位置为晚于A1、A2、C1，且早于C2。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/114/pic/lcs_hu_5573a9e94cfb5f72.webp&#34; alt=&#34;全局锁的作用&#34; width=&#34;900&#34; height=&#34;773&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;线性一致性通过“事件A的开始时间晚于事件B的结束时间”这样的描述增加了对节点间先后事件的限制，但&lt;strong&gt;没有对并发事件进行限制&lt;/strong&gt;。下图中，A1和C1是并发的。事件C1无论读出的是x=5还是x=0都不违反线性一致性约束，因为这两个事件是并发的。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/114/pic/lcxx_hu_20eefdfe8c65846e.webp&#34; alt=&#34;事件的并发示意图&#34; width=&#34;898&#34; height=&#34;970&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;可什么样的系统能做到线性一致性呢？&lt;/p&gt;
&lt;p&gt;为了实现线性一致性，可以将数据的变更和同步看作一个整体，不能允许外界读取一个已经变更但尚未完全同步的数据。&lt;/p&gt;
&lt;p&gt;最简单的，我们可以给每个操作都增加锁，从而使得&lt;strong&gt;全局串行化&lt;/strong&gt;，保证系统满足线性一致性要求，如下图所示。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/114/pic/lcsqu_hu_a96dcba0c3593f2a.webp&#34; alt=&#34;系统内部节点同步示意图&#34; width=&#34;900&#34; height=&#34;1538&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;但全局串行化对系统并发性能的损耗太大，因此很少被实际使用。在具体实施过程中，存在许多效率更高的满足线性一致性的算法，两阶段提交算法、三阶段提交算法。&lt;/p&gt;
&lt;p&gt;对于上述这些算法，我们就不再展开讲了。感兴趣的读者可以阅读《分布式系统原理与工程实践》一书。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://union-click.jd.com/jdc?e=&amp;p=JF8BAMYJK1olXDYCVl9YAEkUBV9MRANLAjZbERscSkAJHTdNTwcKBlMdBgABFksVAmoAGVgTQl9HCANtb05yfSYIcEV3CVJgMgctdStjWGwSa1cZbQcyVF9cCE8eBWcKG2slXQEyAjBdCUoWAm4NG14WbQcyVFlYCk0UBWoMHFwQXDYFVFdtUx55BWdbSV9AVQEBVl9fCnsnM2w4K2sVbQUyFjAIWBgRC2YLTFMWDQQHUVkKWx4SBDsNHVMdDwQLB1YPCnsVAm4MEms&#34;&gt;&lt;img src=&#34;https://yeecode.top/img/promo/dsbook.jpg&#34; width=&#34;680&#34; height=&#34;453&#34; loading=&#34;lazy&#34;  alt=&#34;分布式系统原理与工程实践&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://union-click.jd.com/jdc?e=&amp;amp;p=JF8BAMYJK1olXDYCVl9YAEkUBV9MRANLAjZbERscSkAJHTdNTwcKBlMdBgABFksVAmoAGVgTQl9HCANtb05yfSYIcEV3CVJgMgctdStjWGwSa1cZbQcyVF9cCE8eBWcKG2slXQEyAjBdCUoWAm4NG14WbQcyVFlYCk0UBWoMHFwQXDYFVFdtUx55BWdbSV9AVQEBVl9fCnsnM2w4K2sVbQUyFjAIWBgRC2YLTFMWDQQHUVkKWx4SBDsNHVMdDwQLB1YPCnsVAm4MEms&#34;&gt;《分布式系统原理与工程实践》&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;书中对上述算法的原理和使用进行了明确的剖析，并且对上述这些算法满足线性一致性进行了证明。对大家体系化地掌握这些知识很有帮助。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;../pic/dsbook_list01.jpg&#34; alt=&#34;书籍部分目录&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;另外这本书对分布式系统相关的理论、实践、工程知识均进行了详细的介绍，层层递进，有助于大家建立完整的分布式系统知识体系。涉及&lt;strong&gt;一致性、共识、Paxos、分布式事务、服务治理、微服务、幂等、消息系统、Zookeeper&lt;/strong&gt;等。并且还发行了繁体版。&lt;/p&gt;
&lt;p&gt;另外，在学习分布式系统时，因为涉及到的理论、框架很多。所以大家更要理清知识脉络，否则容易学得越多越混乱。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;好了，我是程序员易哥。&lt;/p&gt;
&lt;p&gt;这回就说这么多！&lt;/p&gt;
&lt;p&gt;可以&lt;strong&gt;关注&lt;/strong&gt;我！等我下回接着说！&lt;/p&gt;</description>
    </item>
    <item>
      <title>什么是顺序一致性？</title>
      <link>https://yeecode.top/blog/113/</link>
      <pubDate>Sat, 07 Jan 2023 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/113/</guid>
      
      <description>&lt;p&gt;在学习分布式系统时，我们需要清晰理解“&lt;strong&gt;一致性（Consistency）&lt;/strong&gt;”这一概念。&lt;/p&gt;
&lt;p&gt;分布式系统中的“一致性（Consistency）”是说数据副本存放在分布式系统中的不同节点上，如果用户修改了系统中的数据，则在一定时间后，用户能从系统中读取到修改后的数据。&lt;/p&gt;
&lt;p&gt;或者换一种说法，&lt;strong&gt;用户在分布式系统的某个节点上进行了变更操作，则在一定时间后，用户能从系统的任意节点上读取到这个变更结果。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;而一致性有强弱之分，这就是总要强调“&lt;strong&gt;经过一定时间后&lt;/strong&gt;”的原因。&lt;/p&gt;
&lt;p&gt;那这个时间到底要多长呢？在这个时间之前读取又会得到怎样的结果呢？回答这个问题便涉及到一致性的强弱概念。也正是根据一致性强弱的不同，我们把一致性分为了很多类。&lt;/p&gt;
&lt;p&gt;而其中，顺序一致性（Sequential Consistency）是非常重要且基础的一类。&lt;/p&gt;
&lt;blockquote&gt;
    &lt;p&gt;线性一致性就是以顺序一致性为基础定义的。
顺序一致性在1979年提出，线性一致性则是1987年在顺序一致性的基础上增加了约束得来的。&lt;/p&gt;

  &lt;/blockquote&gt;&lt;p&gt;顺序一致性（Sequential Consistency）提出了两个约束：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;单个节点的事件历史在全局历史上符合程序的先后顺序&lt;/li&gt;
&lt;li&gt;全局事件历史在各个节点上一致&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我们可以根据这两个约束来判断一个系统是否满足顺序一致性：如果在系统中找不到任何一个符合上述两个约束的&lt;strong&gt;全局事件历史&lt;/strong&gt;，则说明该系统一定不满足顺序一致性；如果能找到一个符合上述两个约束的全局事件历史，这说明系统在这段过程内是满足顺序一致性的。&lt;/p&gt;
&lt;p&gt;这个约束描述起来可能稍有拗口，我们通过示例来解释说明。&lt;/p&gt;
&lt;p&gt;假设分布式系统存在A、B、C三个节点，初始值x=0、y=0，各个节点上发生的事件如图所示。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/113/pic/sc_hu_50ef14fae9a390a4.webp&#34; alt=&#34;不满足顺序一致性的事件&#34; width=&#34;898&#34; height=&#34;970&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;B2读出y=3意味着在全局历史中A2在B2之前（记作A2-B2），B3读出x=0意味着全局历史中B3在A1之前（记作A2-B2，且B3-A1），而节点A的历史决定了A1在A2之前（记作B3-A1-A2-B2），节点B的历史决定了B2在B3之前（记作B3-A1-A2-B2，且B2-B3）。显然不存在一个全局历史能够将A1、A2、B2、B3排列起来，且满足上面的各个排序要求。&lt;/p&gt;
&lt;p&gt;而下图所示的事件是满足顺序一致性的。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/113/pic/lc_hu_31bdc441edbda9b7.webp&#34; alt=&#34;满足顺序一致性的事件&#34; width=&#34;898&#34; height=&#34;970&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;在上图中，我们能够找到满足以上约束的全局历史，例如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;B1-B2-A1-B3-A2-C1-C2&lt;/li&gt;
&lt;li&gt;B1-A1-B2-B3-A2-C1-C2&lt;/li&gt;
&lt;li&gt;B1-A1-C1-B2-A2-C2-B3&lt;/li&gt;
&lt;li&gt;……&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这样的全局历史还可以找出很多，这意味着目前的这些片段是满足顺序一致性的。而如果一个系统的工作中，其全部事件都能满足顺序一致性，那这个系统就满足顺序一致性。&lt;/p&gt;
&lt;p&gt;可见，明确顺序一致性的两个约束是十分重要的。基于这两个约束，我们就可以判断一个分布式系统是否满足顺序一致性。&lt;/p&gt;
&lt;p&gt;在后面的文章中，我们会在顺序一致性的基础上，介绍线性一致性。这也是一个十分常用的一致性。&lt;/p&gt;
&lt;p&gt;关于更多一致性的内容，以及分布式系统的知识，大家可以参照下面的书籍。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://union-click.jd.com/jdc?e=&amp;p=JF8BAMYJK1olXDYCVl9YAEkUBV9MRANLAjZbERscSkAJHTdNTwcKBlMdBgABFksVAmoAGVgTQl9HCANtb05yfSYIcEV3CVJgMgctdStjWGwSa1cZbQcyVF9cCE8eBWcKG2slXQEyAjBdCUoWAm4NG14WbQcyVFlYCk0UBWoMHFwQXDYFVFdtUx55BWdbSV9AVQEBVl9fCnsnM2w4K2sVbQUyFjAIWBgRC2YLTFMWDQQHUVkKWx4SBDsNHVMdDwQLB1YPCnsVAm4MEms&#34;&gt;&lt;img src=&#34;https://yeecode.top/img/promo/dsbook.jpg&#34; width=&#34;680&#34; height=&#34;453&#34; loading=&#34;lazy&#34;  alt=&#34;分布式系统原理与工程实践&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://union-click.jd.com/jdc?e=&amp;amp;p=JF8BAMYJK1olXDYCVl9YAEkUBV9MRANLAjZbERscSkAJHTdNTwcKBlMdBgABFksVAmoAGVgTQl9HCANtb05yfSYIcEV3CVJgMgctdStjWGwSa1cZbQcyVF9cCE8eBWcKG2slXQEyAjBdCUoWAm4NG14WbQcyVFlYCk0UBWoMHFwQXDYFVFdtUx55BWdbSV9AVQEBVl9fCnsnM2w4K2sVbQUyFjAIWBgRC2YLTFMWDQQHUVkKWx4SBDsNHVMdDwQLB1YPCnsVAm4MEms&#34;&gt;《分布式系统原理与工程实践》&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;书籍对常用的一致性进行了详细的介绍，还介绍了一致性相关算法等。对大家体系化地掌握这些知识很有帮助。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;../pic/dsbook_list01.jpg&#34; alt=&#34;书籍部分目录&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;这本书对分布式系统相关的&lt;strong&gt;理论、实践、工程&lt;/strong&gt;知识均进行了详细的介绍，层层递进，有助于大家建立完整的分布式系统知识体系。当然，书中也对各种“一致性”级别也进行分析，还分析了&lt;strong&gt;Paxos算法、Zookeeper&lt;/strong&gt;等。并且还发行了&lt;strong&gt;繁体版&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;好了，我是程序员易哥。&lt;/p&gt;
&lt;p&gt;这回就说这么多！&lt;/p&gt;
&lt;p&gt;可以&lt;strong&gt;关注&lt;/strong&gt;我！等我下回接着说！&lt;/p&gt;</description>
    </item>
    <item>
      <title>基因表达网络构想</title>
      <link>https://yeecode.top/blog/85/</link>
      <pubDate>Sun, 01 Jan 2023 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/85/</guid>
      
      <description>&lt;p&gt;说起神经网络，我们首先想到的是如火如荼的人工神经网络。这是计算机领域中正火的概念。&lt;/p&gt;
&lt;p&gt;如图所示，一个人工神经网络包含多层（图中只绘制了四层），输入层的信息经过层层映射后得到了输出层的结果。而每一条连线，都代表了一个权重。即每一层的节点会按照不同权重受到上一层节点的影响，并将自己的结果输出给下一层。整个过程是单向的。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/85/pic/001_hu_994b4f1d9f3b010c.webp&#34; alt=&#34;图片&#34; width=&#34;505&#34; height=&#34;284&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;这样，输入层输入一组数据后，就会在输出层得到一个结果。&lt;/p&gt;
&lt;p&gt;经过训练之后，神经网络能够对输入给出结果，但是往往不具有可解释性。也就是说，我们无法得知隐藏层中某个节点的含义，也无法得知某条连接的权重大小的含义。&lt;/p&gt;
&lt;p&gt;人工神经网络的层级可以很多，被称为深度神经网络。基于它的软件系统已经能够在自动驾驶、棋类博弈、计算机视觉等诸多领域取得了卓越的成就。&lt;/p&gt;
&lt;p&gt;听名称也能得知，人工神经网络是仿生学的成果。它的本源是生物神经网络。&lt;/p&gt;
&lt;p&gt;生物神经网络中，神经元是一个非常重要的结构，层级上它是一个细胞。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/85/pic/002_hu_54d1308a52a0fd34.webp&#34; alt=&#34;图片&#34; width=&#34;383&#34; height=&#34;250&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;一个神经元包含多个树突和一个轴突。其树突可以接受其他神经元或者信号源的刺激。神经元对这些刺激进行汇总后，可以判断自身是否产生神经冲动。而神经冲动则可以沿着轴突传导，最终传递给下一个神经元的树突。&lt;/p&gt;
&lt;p&gt;这时我们便可以发现，人工神经网络中的节点和节点之间的关系，其实就是对神经元的模仿。&lt;/p&gt;
&lt;p&gt;动物的神经系统就是一个神经网络，它能对外界环境做出判断，甚至产生记忆、意识等等。其同样的，整个系统的运行也不具有可解释性，甚至到现在，人类对其具体机制也不甚清楚。&lt;/p&gt;
&lt;p&gt;神经网络，是一个起源于生物的绝佳发明。&lt;/p&gt;
&lt;p&gt;既然这样，造物者可能不止使用一次。&lt;/p&gt;
&lt;p&gt;我认为，遗传信息的表达就是神经网络的另一个应用。其输入是生物的DNA（也可能是RNA甚等），然后经过复杂的映射，形成各种性状。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/85/pic/003_hu_264966f4d7a761f9.webp&#34; alt=&#34;图片&#34; width=&#34;554&#34; height=&#34;300&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;例如，第一层，可以是碱基对（部分内含子外显子，全部包括）。第二层，可以是密码子，如下图所示。第三层，可以是氨基酸。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/85/pic/0040_hu_b956b34b17ff47aa.webp&#34; alt=&#34;图片&#34; width=&#34;900&#34; height=&#34;604&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;于是，可以网络的前几层可以这样标定含义。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/85/pic/0050_hu_90942be257b5cbeb.webp&#34; alt=&#34;图片&#34; width=&#34;900&#34; height=&#34;567&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;但是我们也要知道，神经网络本身就不是可解释的。因此，我们的标定也只是粗略的指导。&lt;/p&gt;
&lt;p&gt;例如，第三层中我们标定为氨基酸。但事实上，64个密码子中有61个指定了蛋白质中最常见的20种氨基酸。仍然有一些不常见的氨基酸不能被密码子指定，例如胶原蛋白中含有的羟脯氨酸&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;。&lt;/p&gt;
&lt;p&gt;因此，第三层中的节点数实际上要大于20才对，并不是和密码子指定的氨基酸相对应的。&lt;/p&gt;
&lt;p&gt;最终，经过组织、器官等层层表达，形成了一个复杂的生命体。&lt;/p&gt;
&lt;p&gt;因此，不仅仅是神经系统。基因的表达，也可能是一个网络，其层级不少于7。&lt;/p&gt;
&lt;p&gt;以上，我的个人猜测。&lt;/p&gt;
&lt;p&gt;其验证，需要大量个体的基因序列。前期可以只训练和验证单一性状。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;最后，我是架构师&lt;strong&gt;易哥&lt;/strong&gt;，这里是架构研究所。&lt;/p&gt;
&lt;p&gt;希望本文能让大家有所收获，也欢迎大家共同参与讨论。&lt;/p&gt;
&lt;p&gt;欢迎&lt;strong&gt;关注&lt;/strong&gt;我们，我会偶尔出没分享&lt;strong&gt;软件架构&lt;/strong&gt;和&lt;strong&gt;编程开发&lt;/strong&gt;相关的干货知识。&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;〔美〕J.D.沃森. 基因的分子生物学[M]. 科学出版社, 2005.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description>
    </item>
    <item>
      <title>事务的“一致性”和分布式“一致性”完全不是同一个概念！</title>
      <link>https://yeecode.top/blog/112/</link>
      <pubDate>Sun, 25 Dec 2022 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/112/</guid>
      
      <description>&lt;p&gt;在前面的文章中，我们已经讲了：分布式系统中所说的一致性（CAP一致性）和数据库事务中所说的一致性（ACID一致性）完全不是同一个概念。&lt;/p&gt;
&lt;p&gt;CAP一致性是说：&lt;strong&gt;用户在分布式系统的某个节点上进行了变更操作，则在一定时间后，用户能从系统的任意节点上读取到这个变更结果。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;ACID一致性是说：&lt;strong&gt;事务的执行不会破坏数据库的完整性约束&lt;/strong&gt;，所谓的完整性约束包括数据关系的完整性和业务逻辑的完整性。因此，这里的“一致性”指的是完整性约束不会破坏。&lt;/p&gt;
&lt;p&gt;哪有人会问，两者之间有没有什么关联呢？&lt;/p&gt;
&lt;p&gt;如果真要找，也能找到。只发生在一些很特殊的场景下。&lt;/p&gt;
&lt;p&gt;我们已经知道ACID一致性讨论的是事务，CAP一致性讨论的是分布式。那在分布式事务中，这两种一致性会存在交集。如图所示。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/112/pic/2yizhix_hu_626e2339dfb2ef0b.webp&#34; alt=&#34;分布式事务示意图&#34; width=&#34;646&#34; height=&#34;476&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;假设存在一个支持事务操作的分布式数据库。&lt;/p&gt;
&lt;p&gt;那作为数据库，它应该满足CAP一致性，否则数据库中的值没有意义。例如你从数据库的某个节点中读取到变量的值为1，而从数据库的另一个节点中读取到同一变量的值为3，则无法判断哪一个值是正确的。这样无论值1还是值3都没有意义。&lt;/p&gt;
&lt;p&gt;又因为这个分布式数据库支持事务，则它应该满足ACID一致性。&lt;/p&gt;
&lt;p&gt;这时候我们发现，CAP一致性是ACID一致性的基础。即如果CAP一致性不成立，则分布式数据库各个节点的数据不一致且没有意义，那么ACID一致性要求的完整性约束便断然无法满足。&lt;/p&gt;
&lt;p&gt;除了上图所示的情况外，CAP一致性和ACID一致性少有交集。在平时的理论学习和工程实践中，区分好这两种一致性概念非常重要，否则很容易陷入疑惑和混沌。&lt;/p&gt;
&lt;p&gt;所以，还是建议大家将这些概念理清楚，避免学的越多混的越乱……。推荐大家一本书：&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://union-click.jd.com/jdc?e=&amp;p=JF8BAMYJK1olXDYCVl9YAEkUBV9MRANLAjZbERscSkAJHTdNTwcKBlMdBgABFksVAmoAGVgTQl9HCANtb05yfSYIcEV3CVJgMgctdStjWGwSa1cZbQcyVF9cCE8eBWcKG2slXQEyAjBdCUoWAm4NG14WbQcyVFlYCk0UBWoMHFwQXDYFVFdtUx55BWdbSV9AVQEBVl9fCnsnM2w4K2sVbQUyFjAIWBgRC2YLTFMWDQQHUVkKWx4SBDsNHVMdDwQLB1YPCnsVAm4MEms&#34;&gt;&lt;img src=&#34;https://yeecode.top/img/promo/dsbook.jpg&#34; width=&#34;680&#34; height=&#34;453&#34; loading=&#34;lazy&#34;  alt=&#34;分布式系统原理与工程实践&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://union-click.jd.com/jdc?e=&amp;amp;p=JF8BAMYJK1olXDYCVl9YAEkUBV9MRANLAjZbERscSkAJHTdNTwcKBlMdBgABFksVAmoAGVgTQl9HCANtb05yfSYIcEV3CVJgMgctdStjWGwSa1cZbQcyVF9cCE8eBWcKG2slXQEyAjBdCUoWAm4NG14WbQcyVFlYCk0UBWoMHFwQXDYFVFdtUx55BWdbSV9AVQEBVl9fCnsnM2w4K2sVbQUyFjAIWBgRC2YLTFMWDQQHUVkKWx4SBDsNHVMdDwQLB1YPCnsVAm4MEms&#34;&gt;《分布式系统原理与工程实践》&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;书籍对常用的一致性进行了详细的介绍，包括分布式系统中的一致性级别，还介绍了一致性相关算法等。对大家体系化地掌握这些知识很有帮助。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;../pic/dsbook_list01.jpg&#34; alt=&#34;书籍部分目录&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;这本书对分布式系统相关的&lt;strong&gt;理论、实践、工程&lt;/strong&gt;知识均进行了详细的介绍，层层递进，有助于大家建立完整的分布式系统知识体系。当然，书中也对各种“一致性”进行了分析。并且本书备受好评，以至于还发行了&lt;strong&gt;繁体版&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;好了，我是程序员易哥。&lt;/p&gt;
&lt;p&gt;这回就说这么多！&lt;/p&gt;
&lt;p&gt;可以&lt;strong&gt;关注&lt;/strong&gt;我！等我下回接着说！&lt;/p&gt;</description>
    </item>
    <item>
      <title>分布式中的“一致性”，到底指的是什么？</title>
      <link>https://yeecode.top/blog/111/</link>
      <pubDate>Sun, 20 Nov 2022 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/111/</guid>
      
      <description>&lt;h2 id=&#34;众多的一致性&#34;&gt;众多的“一致性”&lt;/h2&gt;
&lt;p&gt;说到一致性，大家都不陌生。随着分布式系统、微服务系统、区块链等技术的发展，“一致性”一词出现的频率也越来越高。&lt;/p&gt;
&lt;p&gt;然而，“一致性”这一词语所代表的概念却并不唯一。例如我们常听到“事务的一致性”、“最终一致性”、“一致性哈希”等，它们表述的&lt;strong&gt;并不是同一个概念&lt;/strong&gt;。理解这点十分重要。如果错误地认为以上几个“一致性”都指同一个概念，那就搞混了。&lt;/p&gt;
&lt;p&gt;在明确以上概念各不相同的基础上，我们介绍下&lt;strong&gt;分布式系统中的“一致性”&lt;/strong&gt;（例如“最终一致性”中的“一致性”）到底指的是什么。&lt;/p&gt;
&lt;h2 id=&#34;cap一致性&#34;&gt;CAP一致性&lt;/h2&gt;
&lt;p&gt;我们所说的“最终一致性”中的“一致性（Consistency）”是说数据副本存放在分布式系统中的不同节点上，如果用户修改了系统中的数据，则在一定时间后，用户能从系统中读取到修改后的数据。&lt;/p&gt;
&lt;p&gt;或者换一种说法，&lt;strong&gt;用户在分布式系统的某个节点上进行了变更操作，则在一定时间后，用户能从系统的任意节点上读取到这个变更结果。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;因此，这里的“一致性”指的是针对分布式系统的各个节点对外的表现是一致的，就像是一个节点一样。&lt;/p&gt;
&lt;p&gt;例如在图所示的分布式系统中存在大量的节点。我们设置a=5，这一操作可能落在任意一个节点上（图中所示写请求落在了节点A上），则在一定时间之后，访问系统一定能读到a=5（图中所示读请求落在了节点H上），则说明这个分布式系统满足一致性。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/111/pic/capC1_hu_d67cf953fe535081.webp&#34; alt=&#34;分布式系统的一致性示意图&#34; width=&#34;900&#34; height=&#34;475&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;为了便于表述，我们将这种一致性称为“CAP一致性”。&lt;/p&gt;
&lt;p&gt;要注意的是，我们虽然以同质节点（指这些节点运行同样的代码）进行了举例，但是CAP一致性对于异质节点同样也是成立的。例如分布式应用中存在异质的订单处理节点A和库存管理节点B。如果该分布式应用满足CAP一致性，则当我们向订单处理节点A发送生成新订单的请求一定时间后，能从库存管理节点B上读取到新订单引发的库存变化。&lt;/p&gt;
&lt;h2 id=&#34;cap一致性的强弱&#34;&gt;CAP一致性的强弱&lt;/h2&gt;
&lt;p&gt;在介绍一致性的概念时，我们说分布式系统满足一致性意味着用户通过某一个节点修改了数据，经过一定时间后，用户可以在任意节点上读取到修改后的数据。&lt;/p&gt;
&lt;p&gt;为什么总要强调“&lt;strong&gt;经过一定时间后&lt;/strong&gt;”呢？这个时间到底要多长呢？在这个时间之前读取又会得到怎样的结果呢？&lt;/p&gt;
&lt;p&gt;解答上述一连串的问题便涉及到&lt;strong&gt;一致性的强弱&lt;/strong&gt;概念。也正是根据一致性强弱的不同，我们把一致性分为了很多类。我们常见的，也是经常会用到的有：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;严格一致性&lt;/li&gt;
&lt;li&gt;顺序一致性&lt;/li&gt;
&lt;li&gt;线性一致性&lt;/li&gt;
&lt;li&gt;最终一致性&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当然，还有很多的一致性级别，例如因果一致性、会话一致性、单调读一致性、单调写一致性等。&lt;/p&gt;
&lt;p&gt;对于以上各个一致性级别的判别和证明，一句话说不明白。如果大家有兴趣可以参考下面的书籍。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://union-click.jd.com/jdc?e=&amp;p=JF8BAMYJK1olXDYCVl9YAEkUBV9MRANLAjZbERscSkAJHTdNTwcKBlMdBgABFksVAmoAGVgTQl9HCANtb05yfSYIcEV3CVJgMgctdStjWGwSa1cZbQcyVF9cCE8eBWcKG2slXQEyAjBdCUoWAm4NG14WbQcyVFlYCk0UBWoMHFwQXDYFVFdtUx55BWdbSV9AVQEBVl9fCnsnM2w4K2sVbQUyFjAIWBgRC2YLTFMWDQQHUVkKWx4SBDsNHVMdDwQLB1YPCnsVAm4MEms&#34;&gt;&lt;img src=&#34;https://yeecode.top/img/promo/dsbook.jpg&#34; width=&#34;680&#34; height=&#34;453&#34; loading=&#34;lazy&#34;  alt=&#34;分布式系统原理与工程实践&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://union-click.jd.com/jdc?e=&amp;amp;p=JF8BAMYJK1olXDYCVl9YAEkUBV9MRANLAjZbERscSkAJHTdNTwcKBlMdBgABFksVAmoAGVgTQl9HCANtb05yfSYIcEV3CVJgMgctdStjWGwSa1cZbQcyVF9cCE8eBWcKG2slXQEyAjBdCUoWAm4NG14WbQcyVFlYCk0UBWoMHFwQXDYFVFdtUx55BWdbSV9AVQEBVl9fCnsnM2w4K2sVbQUyFjAIWBgRC2YLTFMWDQQHUVkKWx4SBDsNHVMdDwQLB1YPCnsVAm4MEms&#34;&gt;《分布式系统原理与工程实践》&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;书籍对各种一致性都进行了详细的介绍。对大家体系化地掌握这些知识很有帮助。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;../pic/dsbook_list01.jpg&#34; alt=&#34;书籍部分目录&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;这本书对分布式系统相关的理论、实践、工程知识均进行了详细的介绍，层层递进，有助于大家建立完整的分布式系统知识体系。当然，书中也对各种“一致性”进行了分析。并且本书备受好评，以至于还发行了繁体版。&lt;/p&gt;
&lt;p&gt;此外，一致性也有强弱之分。但要注意，这只是一个相对的概念。并不能明确地说明那些一致性属于强一致性，那些一致性属于弱一致性。网上确实有一些将某某一致性归为强一致性还是弱一致性的争论，则是没有太大意义的。因为强、弱是倾向性的概念，而没有确切的定义。&lt;/p&gt;
&lt;h2 id=&#34;其他的一致性&#34;&gt;其他的一致性&lt;/h2&gt;
&lt;p&gt;以上我们们所述的都是CAP一致性。&lt;/p&gt;
&lt;p&gt;而我们还会在其他场合见到“一致性”这个词语，如“事务的一致性”、“一致性哈希”。这个都和CAP一致性（也就是分布式系统中的一致性）不是一个概念，别搞混了。如果大家想要了解其他的一致性，并且对这些概念进行梳理，可以参考《分布式系统原理与工程实践》这本书籍。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;好了，我是程序员易哥。&lt;/p&gt;
&lt;p&gt;这回就说这么多！&lt;/p&gt;
&lt;p&gt;可以&lt;strong&gt;关注&lt;/strong&gt;我！等我下回接着说！&lt;/p&gt;</description>
    </item>
    <item>
      <title>如何使用Hystrix实现服务熔断？</title>
      <link>https://yeecode.top/blog/129/</link>
      <pubDate>Tue, 01 Nov 2022 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/129/</guid>
      
      <description>&lt;h2 id=&#34;概述&#34;&gt;概述&lt;/h2&gt;
&lt;p&gt;降级就是说在调用某个方法发生超时、异常等错误时，改为调用备用方法，并使用备用方法的结果来替代原方法的操作。例如原方法是请求Redis中的数据，但因为Redis宕机，导致请求发生异常，则此时可以调用备用方法通过查询数据库给出结果。&lt;/p&gt;
&lt;p&gt;而熔断则在此基础上更进一步，即发现该方法频繁降级时直接停止对该方法的一切调用，直到该方法的情况好转。&lt;/p&gt;
&lt;h2 id=&#34;环境配置&#34;&gt;环境配置&lt;/h2&gt;
&lt;p&gt;通过Hystrix就可以方便地实现这一操作。接下来，我们给出简单的示例。&lt;/p&gt;
&lt;p&gt;首先，准备SpringBoot项目，然后在其中引入适用于SpringBoot的hystrix包：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-xml&#34; data-lang=&#34;xml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.cloud&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-cloud-starter-netflix-hystrix&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;2.2.7.RELEASE&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;注意，如果这时候启动提示如下的错误，则基本是SpringBoot版本与hystrix版本不匹配导致的。因为hystrix是基于SpringCloud的，而SpringCloud和SpringBoot存在版本对应关系。如果对应不一致，则可能导致出现下面的错误。&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;java.lang.NoSuchMethodError: &amp;#39;void org.springframework.boot.builder.SpringApplicationBuilder.&amp;lt;init&amp;gt;(java.lang.Object[])&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这里我们给出一个可用的对应版本，即SpringBoot的2.3.5.RELEASE和spring-cloud-starter-netflix-hystrix的2.2.7.RELEASE。它们这一对是肯定没有问题的。&lt;/p&gt;
&lt;p&gt;然后，我们需要在SpringBoot的主类上增加@EnableHystrix注解，以启用Hystrix。如下所示。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@SpringBootApplication&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@EnableHystrix&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;DemoApplication&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;(String&lt;span style=&#34;color:#f92672&#34;&gt;[]&lt;/span&gt; args) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        SpringApplication.&lt;span style=&#34;color:#a6e22e&#34;&gt;run&lt;/span&gt;(DemoApplication.&lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;, args);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;环境配置就是这么简单，接下来我们就可以写业务代码了。&lt;/p&gt;
&lt;h2 id=&#34;功能实现&#34;&gt;功能实现&lt;/h2&gt;
&lt;p&gt;例如我们编写如下的方法，也就是原方法。在原方法中，我们故意让它一定概率抛出异常、一定概率发生延迟、一定概率成功。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@HystrixCommand&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        commandKey &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;CircuitBreakerService_queryUserNameById&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        fallbackMethod &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;fallBackForSayHi&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        commandProperties &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#75715e&#34;&gt;// 开启熔断器（可以省略，因为默认就是开启的）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#a6e22e&#34;&gt;@HystrixProperty&lt;/span&gt;(name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; HystrixPropertiesManager.&lt;span style=&#34;color:#a6e22e&#34;&gt;CIRCUIT_BREAKER_ENABLED&lt;/span&gt;, value &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;true&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#75715e&#34;&gt;// 超时判定时间设置为30ms&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#a6e22e&#34;&gt;@HystrixProperty&lt;/span&gt;(name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; HystrixPropertiesManager.&lt;span style=&#34;color:#a6e22e&#34;&gt;EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS&lt;/span&gt;, value &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;30&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#75715e&#34;&gt;// 时间窗内的最小采样值为5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#a6e22e&#34;&gt;@HystrixProperty&lt;/span&gt;(name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; HystrixPropertiesManager.&lt;span style=&#34;color:#a6e22e&#34;&gt;CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD&lt;/span&gt;, value &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;5&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#75715e&#34;&gt;// 错误超过30%时即引发熔断&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#a6e22e&#34;&gt;@HystrixProperty&lt;/span&gt;(name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; HystrixPropertiesManager.&lt;span style=&#34;color:#a6e22e&#34;&gt;CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE&lt;/span&gt;, value &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;30&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#75715e&#34;&gt;// 熔断后恢复时间为500ms&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#a6e22e&#34;&gt;@HystrixProperty&lt;/span&gt;(name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; HystrixPropertiesManager.&lt;span style=&#34;color:#a6e22e&#34;&gt;CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS&lt;/span&gt;, value &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;500&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;String &lt;span style=&#34;color:#a6e22e&#34;&gt;queryUserNameById&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; i) &lt;span style=&#34;color:#66d9ef&#34;&gt;throws&lt;/span&gt; InterruptedException {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;正在查询用户名，用户编号为&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; i);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; randomValue &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Random().&lt;span style=&#34;color:#a6e22e&#34;&gt;nextInt&lt;/span&gt;(100);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (randomValue &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;=&lt;/span&gt; 30) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 约30%概率会触发异常&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34; : 查询发生异常 &amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; RuntimeException();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (randomValue &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;=&lt;/span&gt; 60) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 约30%概率会触发超时&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34; : 查询发生延迟 &amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Thread.&lt;span style=&#34;color:#a6e22e&#34;&gt;sleep&lt;/span&gt;(50);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;编号&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; i &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;的用户名查询超时&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 约40%概率会正常执行&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34; : 查询成功 &amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;易哥 &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; i;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;我们在原方法上增加@HystrixCommand注解。其中比较关键的是fallbackMethod参数，我们指向了该原方法对应的备用方法，即当原方法发生错误时要调用的方法。备用方法如下所示。&lt;/p&gt;</description>
    </item>
    <item>
      <title>云原生与数据智能</title>
      <link>https://yeecode.top/blog/91/</link>
      <pubDate>Sun, 11 Sep 2022 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/91/</guid>
      
      <description>&lt;h2 id=&#34;一什么是数据智能&#34;&gt;一、什么是数据智能&lt;/h2&gt;
&lt;p&gt;目前，数字化产业在国内发展迅速，渗透到了人民群众衣食住行的各个领域之中。从2005年到2020年，我国的数字经济占GDP的比重从14.2%提升到了38.6%，2020年数字经济增速是GDP增速的3倍多，数字经济为各行业的降本增效做出了重大的贡献。&lt;/p&gt;
&lt;p&gt;而数字化的发展也经历了以下几个阶段。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第一阶段为信息化阶段。在这一阶段，数字化致力于实现流程的自动化。&lt;/li&gt;
&lt;li&gt;第二阶段为数据应用阶段。在这一阶段，数据的离线处理成为可能，推动了业务数据化和数据业务化。&lt;/li&gt;
&lt;li&gt;第三阶段为数据智能阶段。在这一阶段，全场景的智能、跨领域协同成为新阶段的发展目标，推动数字化不断拓展更多应用场景。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;数据智能化有利于充分发挥数据在商业、制造、政务、金融等各个领域的作用，推动相关领域的成本降低、效率优化。&lt;/p&gt;
&lt;p&gt;“十四五规划”中明确指出，2035年中国要基本实现新型工业化、信息化。其中，推动数据资源开发利用成为重要命题之一。&lt;/p&gt;
&lt;p&gt;在国家自上而下政策的推动下，本就十分广阔的中国企业服务市场，其潜力将会得到进一步挖掘。同时在由内而外的企业改革中，数字化与协作的理念将会进一步普及。&lt;/p&gt;
&lt;h2 id=&#34;二数据智能的意义&#34;&gt;二、数据智能的意义&lt;/h2&gt;
&lt;p&gt;数据智能有着广泛的意义。&lt;/p&gt;
&lt;p&gt;从国家层面看，数据智能能够促进数据在社会主义市场经济中的流动，便于国家更好地实施宏观调控，并对调控的反馈进行实时收集和监控。提升调控在微观和宏观层面的指导作用。&lt;/p&gt;
&lt;p&gt;从产业层面看，数据智能能够实现数据价值化、数据资产化，助力产业结构调整和数据市场的良性发展。&lt;/p&gt;
&lt;p&gt;从企业层面看，数据智能能够整合企业的供应链、财务、售后、库存、促销等各方面数据，提升企业的运营能力，助力企业降本增效。&lt;/p&gt;
&lt;p&gt;因此，数据智能化是一个涉及上下各个层级的趋势。将对商业、制造、政务、金融等各领域带来深刻影响。&lt;/p&gt;
&lt;p&gt;以制造领域为例，数据智能的实施将促进智能制造的发展。我们举例说明数据智能在这一过程中的重要作用。&lt;/p&gt;
&lt;p&gt;如今，同一版本同一型号的手机，几乎是一模一样的。当我们说去选购商品，其实是在有限的型号中选择我们需要的那一款。可是，人的需求千变万化，为什么偏偏要归结到几个固定的型号上去呢？每个人不应该根据自己的需求，定义自己的产品么？&lt;/p&gt;
&lt;p&gt;这很难。因为每个人的需求都是不同的，而工业化的生产却是标准化的。&lt;/p&gt;
&lt;p&gt;如今，库存问题依旧困扰着商业流程，那我们能否直接对接消费者和生产者，从而消除库存呢？&lt;/p&gt;
&lt;p&gt;这很难。因为生产者和消费者之间的信息还没有顺畅到可以直接打通。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/91/pic/001_hu_d61a176df2d80f69.webp&#34; alt=&#34;库存&#34; width=&#34;900&#34; height=&#34;506&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;解决以上问题的答案就是数据智能。&lt;/p&gt;
&lt;p&gt;在物资贫瘠的时代，人类通过屏蔽个性化的需求来创造出一个标准化的环境，从而能提升效率创造更多的物资；而当物资充盈的时候，人类终于有精力来满足每个人个性化的需求。&lt;/p&gt;
&lt;p&gt;你再也不需要将自己的需求归结到几个特定的标准型号上，物资将被完全依照你的需求而生产。你不需要再购买XL的衣服，衣服将绝对贴合你的身材；你不需要再挑选合适的手机，工厂为你制造你的手机。&lt;/p&gt;
&lt;p&gt;而数据智能的意义不仅于此。&lt;/p&gt;
&lt;p&gt;当物资急剧匮乏时，提高生产力是最重要的。而当物资变得极为丰富时，哪怕不提高生产力，只要能让物资能被利用的稍微合理些，就能产生巨大的价值。例如，如果生产商的订单数据能够实时来自于市场，那库存问题不就解决了，这会避免巨大的资源浪费。而且，如果买卖双方之间的信息足够通畅，负责进行信息协调的中间商似乎也没有存在的必要了。&lt;/p&gt;
&lt;p&gt;如今我们想购买一辆自行车，是到市场上选购一辆。而智能制造时代，我们可以直接登录自行车生产商的网站。然后根据自己的需求选取合适的车把、车架、车轮……然后全自动的生产设备按照你的选配组装一辆完全契合你需求的自行车。再进一步看，自行车生产商也会根据你的选取，去相应的车把生产商提交订单，获取完全符合你要求的车把，因此车把的参数你可以完全自定。而整个生产过程，完全为你进行，没有库存，没有中间商赚差价，没有生产力的浪费。&lt;/p&gt;
&lt;p&gt;数据智能让我们渐渐地有能力实现上面描述的一切，也让我们渐渐地回到了遥远的过去，回到了买卖双方直接交流、物资协调更为便利的古代，毕竟，那时展露的才是我们需求的本质。愈发丰富的物资让着它们变得不可能，而数据智能又渐渐地使它们变得可能。&lt;/p&gt;
&lt;p&gt;在制造业领域，数据智能是一个涉及信息、管理等多方面的革新，是对人类生产方式的又一次颠覆。像工业时代中的流水线给人类社会带来的冲击一样，数据智能也必将会给人类社会带来深远的影响。&lt;/p&gt;
&lt;p&gt;当然，数据智能不仅可以在制造业领域发挥作用，在商业、政务、金融等各领域都大有可为。例如，在政务领域的智慧政务系统，便可以实现在线政务办理、自动事项审批、企业特征画像等众多功能，打造政务服务新模式，创建数字经济先行区。&lt;/p&gt;
&lt;h2 id=&#34;三数据智能的特点&#34;&gt;三、数据智能的特点&lt;/h2&gt;
&lt;p&gt;不同于传统的信息化改造，数据智能需要收集被改造主体的多方面数据，并在此基础上进行智能分析，它具有以下特点。&lt;/p&gt;
&lt;p&gt;首先，数据源多样。从来源上看，在数据智能系统中，其分析的数据可能来自于用户填入的数据，也可能来自于生产过程数据，还可能来自于供应链数据。从形式上看，在数据智能系统中，其分析的数据可能是传统数据库中的结构化数据，可能是网页中的半结构化数据，也可能是文本数据库中的非结构化数据。因此数据源十分多样。&lt;/p&gt;
&lt;p&gt;其次，数据智能系统所分析的数据量巨大，且增长快速。不同于传统数据库中的高信息密度数据，这些数据的信息密度是低的，量是大的，并且会随着时间推移迅速增长。&lt;/p&gt;
&lt;p&gt;再次，相比于数据应用阶段的以离线计算为主的数据分析方式，数据智能系统需要大量的实时性计算，其对增强性计算需求量较大。&lt;/p&gt;
&lt;p&gt;第四，数据智能系统的程序异构性强。其可能包含提供网页服务的WEB程序，也可能包含提供分布式存储的数据库程序，还可能包含提供数据分析的流处理、批处理程序，等等。因此，其程序异构性强。&lt;/p&gt;
&lt;p&gt;因为数据智能系统具有数据源多样、数据量大且增长迅速、需要大量的实时性计算、程序异构性强等特点，所以其搭建：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;具有一定的门槛，对于普通机构而言，其实现成本较高。&lt;/li&gt;
&lt;li&gt;采用传统应用方式搭建的系统在面对大数据、实时计算时无法保证可靠性。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;那数据智能的出路在何方？&lt;/p&gt;
&lt;p&gt;那就是云原生。&lt;/p&gt;
&lt;h2 id=&#34;四云原生&#34;&gt;四、云原生&lt;/h2&gt;
&lt;p&gt;云原生是一种方法，它用于构建和运行充分利用云计算模型优势的应用。它主要包含如下的技术：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;微服务架构:服务与服务之间通过高内聚低耦合的方式交互;&lt;/li&gt;
&lt;li&gt;容器:作为微服务的最佳载体,提供了一个自包含的打包方式;&lt;/li&gt;
&lt;li&gt;容器编排:解决了微服务在生产环境的部署问题;&lt;/li&gt;
&lt;li&gt;服务网络:作为基础设施,解决了服务之间的通信;&lt;/li&gt;
&lt;li&gt;不可变基础:设施提升发布效率,方便快速扩展;&lt;/li&gt;
&lt;li&gt;声明式API:让系统更加健壮;&lt;/li&gt;
&lt;li&gt;DevOps:缩短研发周期,增加部署频率,更安全地方便。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;其中的容器、容器编排、服务网格将所有的异构应用都抽象为相同的服务，这可以方便地将异构应用组成系统，统一地对外服务，降低了系统的搭建难度。&lt;/p&gt;
&lt;p&gt;其中的不可变基础、DevOps使得系统能够更好的利用云上的弹性资源，能够更有弹性地应对存储空间、计算能力的需求变化。&lt;/p&gt;
&lt;p&gt;其中的声明式API是相对于命令式API而言的。命令式API接受的是让系统直接执行的具体命令，例如&amp;quot;启动应用&amp;quot;、&amp;ldquo;更新数据源&amp;quot;等，而声明式API接受的期望系统达到的状态。在这个过程中，系统可以不断调整自身现有状态，以最终达到期望状态。这提升了系统运行的智能性、可靠性。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/91/pic/004_hu_ffe146e70e913066.webp&#34; alt=&#34;库存&#34; width=&#34;651&#34; height=&#34;477&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;可见，云原生是实现数据智能的途径。&lt;/p&gt;
&lt;h2 id=&#34;五云原生与数据智能&#34;&gt;五、云原生与数据智能&lt;/h2&gt;
&lt;p&gt;数据智能系统具有数据源多样、数据量大且增长迅速、需要大量的实时性计算、程序异构性强等特点，使得传统的应用架构无法实现。而云原生则为数据智能系统的落地提供了途径。&lt;/p&gt;
&lt;p&gt;就以滴普科技提供的FastData能力矩阵为例，它基于云原生实现了对多数据源的支持，并为各类数据提供了统一存储。继而可以对各类数据进行流批一体的分析处理和湖仓一体的给出。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/91/pic/002_hu_b1263e1654d5c6da.webp&#34; alt=&#34;库存&#34; width=&#34;900&#34; height=&#34;451&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;基于以上能力，能够继续提供离线、实时的数据分析服务，提供数据资产服务，提供调度服务，从而服务企业中的各类角色。&lt;/p&gt;
&lt;p&gt;下面的图中，展示了滴普科技的产品架构图。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/91/pic/003_hu_f0d0f2ce4f46d073.webp&#34; alt=&#34;库存&#34; width=&#34;900&#34; height=&#34;443&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;通过架构图我们可以看出，整个系统主要分为自下而上的三层结构：&lt;/p&gt;
&lt;p&gt;最下层为新一代云原生数据智能平台FastData。整个FastData是搭建在云原生基础服务上的，每个子平台可以利用云原生提供的各项基础服务（权限验证等），实现了基础功能和子平台业务功能的解耦。这种设计确保了每个子平台的安全性，也提升了每个子平台的可拓展性。例如，在这种云原生的架构下，拓展湖存储引擎、流批计算引擎时的具体工作都仅限于业务子平台内部，而不用关系权限验证、数据安全等基础服。甚至这种拓展工作可以由全链路运维系统根据当前负载情况自动完成。&lt;/p&gt;
&lt;p&gt;中间层为数据资产治理与运营平台。这一部分承载了主要的业务逻辑，可以根据需求实现商业、智能精益制造、智慧政务等不同领域的业务逻辑，而不需要关心存储、计算等底层信息。如果需要对业务逻辑进行更新、优化，则都只需要在这一层展开，这使得项目升级的过程更为简单和纯粹。&lt;/p&gt;
&lt;p&gt;最上层则为交付服务，包括线上线下的交付。由于已经在最下层和中间层进行了逻辑梳理与隔离，这里的交付工作可以更为标准和快速的实现，可以大大减少系统的搭建时间和部署人员的工作量。&lt;/p&gt;
&lt;p&gt;以上架构充分利用了云原生的优势，确保了整个系统的可拓展性、可靠性、可维护性。能够较好地应对机构的业务量增长，并能根据机构的需求增加各种特异性服务。例如为商业企业提供消费者运营分析、业财一体等服务，为智能精益制造企业提供质量追溯、EVA分析等服务，为政府机构提供数字双碳分析、经济大脑等服务。&lt;/p&gt;
&lt;p&gt;云原生是数据智能落地的重要保障。它能在满足数据智能所需的多数据源、大数据量、实时计算、异构性强等要求的基础上，提升整个系统的可拓展性、可靠性、可维护性。因此，云原生是数据智能的发展方向。&lt;/p&gt;</description>
    </item>
    <item>
      <title>如何使用Hystrix实现服务降级？</title>
      <link>https://yeecode.top/blog/128/</link>
      <pubDate>Thu, 01 Sep 2022 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/128/</guid>
      
      <description>&lt;h2 id=&#34;概述&#34;&gt;概述&lt;/h2&gt;
&lt;p&gt;降级就是说在调用某个方法发生超时、异常等错误时，改为调用备用方法，并使用备用方法的结果来替代原方法的操作。例如原方法是请求Redis中的数据，但因为Redis宕机，导致请求发生异常，则此时可以调用备用方法通过查询数据库给出结果。&lt;/p&gt;
&lt;h2 id=&#34;环境配置&#34;&gt;环境配置&lt;/h2&gt;
&lt;p&gt;通过Hystrix就可以方便地实现这一操作。接下来，我们给出简单的示例。&lt;/p&gt;
&lt;p&gt;首先，准备SpringBoot项目，然后在其中引入适用于SpringBoot的hystrix包：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-xml&#34; data-lang=&#34;xml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.cloud&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-cloud-starter-netflix-hystrix&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;2.2.7.RELEASE&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;注意，如果这时候启动提示如下的错误，则基本是SpringBoot版本与hystrix版本不匹配导致的。因为hystrix是基于SpringCloud的，而SpringCloud和SpringBoot存在版本对应关系。如果对应不一致，则可能导致出现下面的错误。&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;java.lang.NoSuchMethodError: &amp;#39;void org.springframework.boot.builder.SpringApplicationBuilder.&amp;lt;init&amp;gt;(java.lang.Object[])&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这里我们给出一个可用的对应版本，即SpringBoot的2.3.5.RELEASE和spring-cloud-starter-netflix-hystrix的2.2.7.RELEASE。它们这一对是肯定没有问题的。&lt;/p&gt;
&lt;p&gt;然后，我们需要在SpringBoot的主类上增加@EnableHystrix注解，以启用Hystrix。如下所示。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@SpringBootApplication&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@EnableHystrix&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;DemoApplication&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;(String&lt;span style=&#34;color:#f92672&#34;&gt;[]&lt;/span&gt; args) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        SpringApplication.&lt;span style=&#34;color:#a6e22e&#34;&gt;run&lt;/span&gt;(DemoApplication.&lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;, args);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;环境配置就是这么简单，接下来我们就可以写业务代码了。&lt;/p&gt;
&lt;h2 id=&#34;功能实现&#34;&gt;功能实现&lt;/h2&gt;
&lt;p&gt;例如我们编写如下的方法，也就是原方法。在原方法中，我们故意让它一定概率抛出异常、一定概率发生延迟、一定概率成功。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@HystrixCommand&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        commandKey &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;FallBackService_queryUserNameById&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        fallbackMethod &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;queryDefaultUserName&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        commandProperties &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#75715e&#34;&gt;// 超时判定时间设置为30ms&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#a6e22e&#34;&gt;@HystrixProperty&lt;/span&gt;(name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; HystrixPropertiesManager.&lt;span style=&#34;color:#a6e22e&#34;&gt;EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS&lt;/span&gt;, value &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;30&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;String &lt;span style=&#34;color:#a6e22e&#34;&gt;queryUserNameById&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; i) &lt;span style=&#34;color:#66d9ef&#34;&gt;throws&lt;/span&gt; InterruptedException {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;正在查询用户名，用户编号为&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; i);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; randomValue &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Random().&lt;span style=&#34;color:#a6e22e&#34;&gt;nextInt&lt;/span&gt;(100);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (randomValue &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;=&lt;/span&gt; 30) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 约30%概率会触发异常&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34; : 查询发生异常 &amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; RuntimeException();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (randomValue &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;=&lt;/span&gt; 60) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 约30%概率会触发超时&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34; : 查询发生延迟 &amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Thread.&lt;span style=&#34;color:#a6e22e&#34;&gt;sleep&lt;/span&gt;(50);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;编号&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; i &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;的用户名查询超时&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 约40%概率会正常执行&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34; : 查询成功 &amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;易哥&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; i;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;我们在原方法上增加@HystrixCommand注解。其中比较关键的是fallbackMethod参数，我们指向了该原方法对应的备用方法，即当原方法发生错误时要调用的方法。然后我们编写备用方法。&lt;/p&gt;</description>
    </item>
    <item>
      <title>神通广大的cglib和基于它实现的动态代理</title>
      <link>https://yeecode.top/blog/62/</link>
      <pubDate>Sun, 21 Aug 2022 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/62/</guid>
      
      <description>&lt;p&gt;前面的文章中我们已经介绍了基于反射的动态代理。但是它有一个制约条件，即被代理的类必须要有一个父接口。但是有些类确实没有父接口，对于这些类而言，基于反射的动态代理是不适用的。&lt;/p&gt;
&lt;p&gt;本文我们介绍另一种实现动态代理的方式：基于cglib(Code Generation Library，即代码生成库) 的动态代理。&lt;/p&gt;
&lt;p&gt;一个类必须要通过类加载过程将类文件加载到JVM后才能使用。那是否能够直接修改JVM中的字节码信息来修改和创建类呢？&lt;/p&gt;
&lt;p&gt;答案是可以的，cglib就是基于这个原理工作的。cglib使用字节码处理框架ASM来转换字节码并生成被代理类的子类。然后这个子类就可以作为代理类展开工作。ASM是一个非常底层的框架，除非你对JVM内部结构包括class文件的格式和指令集都很熟悉，否则不要直接使用ASM。&lt;/p&gt;
&lt;p&gt;接下来我们通过示例介绍一下如何用cglib实现动态代理。&lt;/p&gt;
&lt;p&gt;首先要在项目中引入cglib工具包，以使用Maven为例，在pom文件中增加下面所示的依赖。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;dependency&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;groupId&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;cglib&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/&lt;/span&gt;groupId&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;artifactId&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;cglib&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/&lt;/span&gt;artifactId&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;version&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;3.&lt;span style=&#34;color:#a6e22e&#34;&gt;2&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;9&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/&lt;/span&gt;version&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/&lt;/span&gt;dependency&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;被代理类不需要实现任何接口，下面代码给出了一个简单的被代理类。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;User&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; String &lt;span style=&#34;color:#a6e22e&#34;&gt;sayHello&lt;/span&gt;(String name) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;hello &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; name);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OK&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接下来我们编写一个实现了&lt;code&gt;org.springframework.cglib.proxy.MethodInterceptor&lt;/code&gt;接口的类，如下所示。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ProxyHandler&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;T&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;implements&lt;/span&gt; MethodInterceptor {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;@Override&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Object &lt;span style=&#34;color:#a6e22e&#34;&gt;intercept&lt;/span&gt;(Object o, Method method, Object&lt;span style=&#34;color:#f92672&#34;&gt;[]&lt;/span&gt; objects, MethodProxy methodProxy) &lt;span style=&#34;color:#66d9ef&#34;&gt;throws&lt;/span&gt; Throwable {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;before speak&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Object ans &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; methodProxy.&lt;span style=&#34;color:#a6e22e&#34;&gt;invokeSuper&lt;/span&gt;(o, objects);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;after speak&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; ans;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;被代理类中的方法被拦截后，会进入该类中&lt;code&gt;intercept&lt;/code&gt;方法。在该类的&lt;code&gt;intercept&lt;/code&gt;方法中，我们在被代理对象的方法执行前后各增加了一句输出语句。&lt;/p&gt;
&lt;p&gt;之后我们就可以建立代理对象，实现动态代理操作，该过程如下所示。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;(String&lt;span style=&#34;color:#f92672&#34;&gt;[]&lt;/span&gt; args) &lt;span style=&#34;color:#66d9ef&#34;&gt;throws&lt;/span&gt; Exception {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Enhancer enhancer &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Enhancer();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 设置enhancer的回调对象&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    enhancer.&lt;span style=&#34;color:#a6e22e&#34;&gt;setCallback&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; ProxyHandler&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;gt;&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 设置enhancer对象的父类&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    enhancer.&lt;span style=&#34;color:#a6e22e&#34;&gt;setSuperclass&lt;/span&gt;(User.&lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 创建代理对象，实际为User的子类&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    User user &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (User) enhancer.&lt;span style=&#34;color:#a6e22e&#34;&gt;create&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 通过代理对象调用目标方法&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    String ans &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; user.&lt;span style=&#34;color:#a6e22e&#34;&gt;sayHello&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;易哥&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(ans);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;我们创建了一个代理对象（即变量user对应的对象），然后调用了其中的方法。最终得到了下图所示的运行结果。&lt;/p&gt;</description>
    </item>
    <item>
      <title>事务的“一致性”究竟指的是什么？</title>
      <link>https://yeecode.top/blog/110/</link>
      <pubDate>Sat, 09 Jul 2022 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/110/</guid>
      
      <description>&lt;h2 id=&#34;众多的一致性&#34;&gt;众多的“一致性”&lt;/h2&gt;
&lt;p&gt;说到一致性，大家都不陌生。随着分布式系统、微服务系统、区块链等技术的发展，“一致性”一词出现的频率也越来越高。&lt;/p&gt;
&lt;p&gt;然而，“一致性”这一词语所代表的概念却并不唯一。例如我们常听到“事务的一致性”、“最终一致性”、“一致性哈希”等，它们表述的&lt;strong&gt;并不是同一个概念&lt;/strong&gt;。理解这点十分重要。如果错误地认为以上几个“一致性”都指同一个概念，那就&lt;strong&gt;搞混&lt;/strong&gt;了。&lt;/p&gt;
&lt;p&gt;在明确以上概念各不相同的基础上，我们再将“事务的一致性”中的“一致性”概念单拿出来进行分析。&lt;/p&gt;
&lt;h2 id=&#34;acid一致性&#34;&gt;ACID一致性&lt;/h2&gt;
&lt;p&gt;事务要满足ACID约束，即原子性（Atomicity）、一致性（Consistency）、隔离性（Isolation）、持久性（Durability）。为了便于表述，我们将这种一致性称为“ACID一致性”。“事务的一致性”中的“一致性”就是指“ACID一致性”。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ACID一致性是指事务的执行不会破坏数据库的完整性约束，所谓的完整性约束包括数据关系的完整性和业务逻辑的完整性。&lt;strong&gt;因此，这里的“一致性”指的是&lt;/strong&gt;完整性约束不会破坏&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这里要着重理解的就是：什么是&lt;strong&gt;完整性约束&lt;/strong&gt;？&lt;/p&gt;
&lt;p&gt;我们先举个例子。如图所示，假设完整性约束要求事务执行前后总有数据A和数据B的和为10。那图中的事务致性后，该完整性约束依然满足。因此，这个事务就满足一致性。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/110/pic/acidC_hu_3625f9b983d6fac4.webp&#34; alt=&#34;事务的一致性示意图&#34; width=&#34;900&#34; height=&#34;160&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;这是我们发现，&lt;strong&gt;“完整性约束”是一个外在的业务约束&lt;/strong&gt;。这就意味着，同样的操作，对应的外在的业务约束不同，则该操作可能满足事务条件，又可能不满足事务条件。&lt;/p&gt;
&lt;p&gt;假设完整性约束要求事务执行前后总有数据B减数据A为8，那图中的事务便不成立，因为它执行结束后该完整性约束不在满足，即不满足一致性。&lt;/p&gt;
&lt;p&gt;说到这里，大家可能发现了一些不对劲的地方。这可能也是大家最开始困惑的源头。因为，在事务的ACID约束中，一致性与其他三个特性也颇有不同。其实这点确实引发了一些争议。&lt;/p&gt;
&lt;p&gt;其他三个特性均是事务内在保证的，而一致性的约束条件是由外部业务逻辑规定的。这就意味者，同样的一个操作，根据外部业务逻辑规定的完整性约束的不同，可能满足事务要求，也可能不满足。&lt;/p&gt;
&lt;p&gt;一个固定的操作集合，其是不是事务却要由外部业务逻辑规定，显然不是很合理。基于此，有学者甚至怀疑C是为了凑数而加入到ACID约束中的，提议将C从ACID中剔除。当然，这是题外话。对于目前的认知而言，ACID还是一个整体。&lt;/p&gt;
&lt;h2 id=&#34;其他的一致性&#34;&gt;其他的一致性&lt;/h2&gt;
&lt;p&gt;对于“分布式一致性”“一致性哈希”中的一致性，我们就不在这里展开讲了。大家只要知道，它们完全是不同的概念就好。如果大家想要了解其他的一致性，并且对这些概念进行梳理，可以参考这本书籍。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://union-click.jd.com/jdc?e=&amp;p=JF8BAMYJK1olXDYCVl9YAEkUBV9MRANLAjZbERscSkAJHTdNTwcKBlMdBgABFksVAmoAGVgTQl9HCANtb05yfSYIcEV3CVJgMgctdStjWGwSa1cZbQcyVF9cCE8eBWcKG2slXQEyAjBdCUoWAm4NG14WbQcyVFlYCk0UBWoMHFwQXDYFVFdtUx55BWdbSV9AVQEBVl9fCnsnM2w4K2sVbQUyFjAIWBgRC2YLTFMWDQQHUVkKWx4SBDsNHVMdDwQLB1YPCnsVAm4MEms&#34;&gt;&lt;img src=&#34;https://yeecode.top/img/promo/dsbook.jpg&#34; width=&#34;680&#34; height=&#34;453&#34; loading=&#34;lazy&#34;  alt=&#34;分布式系统原理与工程实践&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://union-click.jd.com/jdc?e=&amp;amp;p=JF8BAMYJK1olXDYCVl9YAEkUBV9MRANLAjZbERscSkAJHTdNTwcKBlMdBgABFksVAmoAGVgTQl9HCANtb05yfSYIcEV3CVJgMgctdStjWGwSa1cZbQcyVF9cCE8eBWcKG2slXQEyAjBdCUoWAm4NG14WbQcyVFlYCk0UBWoMHFwQXDYFVFdtUx55BWdbSV9AVQEBVl9fCnsnM2w4K2sVbQUyFjAIWBgRC2YLTFMWDQQHUVkKWx4SBDsNHVMdDwQLB1YPCnsVAm4MEms&#34;&gt;《分布式系统原理与工程实践》&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;书籍对各种一致性都进行了详细的介绍。对大家体系化地掌握这些知识很有帮助。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;../pic/dsbook_list01.jpg&#34; alt=&#34;书籍部分目录&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;这本书对分布式系统相关的&lt;strong&gt;理论、实践、工程&lt;/strong&gt;知识均进行了详细的介绍，层层递进，有助于大家建立完整的分布式系统知识体系。当然，书中也对各种“一致性”进行了分析。并且本书备受好评，以至于还发行了&lt;strong&gt;繁体版&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;好了，我是程序员易哥。&lt;/p&gt;
&lt;p&gt;这回就说这么多！&lt;/p&gt;
&lt;p&gt;可以&lt;strong&gt;关注&lt;/strong&gt;我！等我下回接着说！&lt;/p&gt;</description>
    </item>
    <item>
      <title>RPC原理详解与实现</title>
      <link>https://yeecode.top/blog/109/</link>
      <pubDate>Sun, 05 Jun 2022 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/109/</guid>
      
      <description>&lt;h2 id=&#34;概述&#34;&gt;概述&lt;/h2&gt;
&lt;p&gt;初看起来，远程过程调用（Remote Procedure Call，简称RPC）是一个比较复杂的名字。但是这个名字起得真的确实好，功能如其名：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;远程&lt;/strong&gt;：指的是需要经过网络的，而不是应用内部、机器内部进行的。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;过程&lt;/strong&gt;：也就是方法。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;那“远程过程调用”，就是：&lt;strong&gt;可以跨过一段网络，调用另外一个网络节点上的方法&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;以上就是对远程过程调用的简单理解。&lt;/p&gt;
&lt;h2 id=&#34;背景&#34;&gt;背景&lt;/h2&gt;
&lt;p&gt;远程过程调用的广泛使用，是伴随着分布式系统的普及发生的。要了解它的背景，我们需要从这里开始讲起。&lt;/p&gt;
&lt;p&gt;单体应用内部存在&lt;strong&gt;模块间的调用&lt;/strong&gt;，这种调用发生在应用内。通常，一个模块可以直接调用另一个模块中的类、对象、方法，十分方便。并且这种调用发生在同一个机器内，甚至是同一个进程、线程内，&lt;strong&gt;十分高效&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;应用之间也会存在调用&lt;/strong&gt;，常基于接口实现。调用过程中会涉及到序列化、反序列化、网络传输、参数校验等多个环节，但因为其发生&lt;strong&gt;频率很低&lt;/strong&gt;，也是可以接受的。&lt;/p&gt;
&lt;p&gt;分布式应用内部的节点之间也会存在调用。这种调用由单体应用的模块间调用演化而来，其&lt;strong&gt;调用频率是相对较高的&lt;/strong&gt;。但是，他们之间的调用已经&lt;strong&gt;无法通过应用的内部调用来实现&lt;/strong&gt;，基于接口的调用则成本太高、效率太低。这时需要一种能够跨节点的、相对低成本和高效的方式来解决节点间的调用问题，这个问题如下图所示。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/109/pic/diaoyongpinlv_hu_17133fed9a1a7e05.webp&#34; alt=&#34;节点调用问题&#34; width=&#34;900&#34; height=&#34;307&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;“高内聚、低耦合”是软件设计尤其是面向对象设计中的一个重要原则。依据此原则设计的软件系统，由下到上的每一层级都会提升自身的内聚性，降低与外界的耦合。最终使得整个系统中，层级越低的组织间耦合越高，层级越高的组织间耦合越低。&lt;/p&gt;
&lt;p&gt;“高内聚、低耦合”这一原则最终会反映在组织间的互相调用上。最终，&lt;strong&gt;层级越低的组织间互相调用的频率越高，层级越高的组织间互相调用的频率越低&lt;/strong&gt;。如上图所示。以上图所示的应用、节点、模块组成的三层的软件结构为例。应用处在最高层，应用间的耦合是很低的，它们间的调用是低频的。节点处在中间层，它们间的耦合居中，其调用频率也是居中的。模块处在最底层，它们之间的耦合最高，模块之间的调用频率也最高。&lt;/p&gt;
&lt;p&gt;节点间的调用，也常被称为服务调用，是需要跨机器的，而其发生频率介于应用间调用和模块间调用之间，是相对高频的。因此&lt;strong&gt;需要一种相对简单高效的、支持跨机器的调用方式，这就是服务调用要解决的问题&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;那显然这种调用方式要满足如下几点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;能够跨机器&lt;/strong&gt;。因为分布式系统的节点往往部署在不同的机器上。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;运行效率高&lt;/strong&gt;。要能够简化各种不必要的包装环节，提升有效信息的比例。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;使用方便&lt;/strong&gt;。用户能够无感知地使用，降低进行调用时候的使用成本。这里主要包括透明性&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;，尤其是其中的访问透明性。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;而远程过程调用则是满足上述要求的不错选择。&lt;/p&gt;
&lt;h2 id=&#34;原理&#34;&gt;原理&lt;/h2&gt;
&lt;p&gt;远程过程调用使得服务可以像调用本地方法一样调用网络上另一个服务中的方法。可这一切是怎么实现的呢？接下来，我们介绍下它的具体原理。&lt;/p&gt;
&lt;p&gt;在使用远程过程调用前，服务提供方需要将自身服务的接口文件导出。而服务调用方则要引入这些接口文件。&lt;/p&gt;
&lt;p&gt;进行远程过程调用时，服务提供方只需要&lt;strong&gt;调用接口文件中的接口&lt;/strong&gt;，便&lt;strong&gt;相当于调用了服务提供方中的具体实现方法&lt;/strong&gt;，并得到服务提供方给出的执行结果。调用其他服务中的方法就像调用本地方法一样方便。如图所示。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/109/pic/jyjk00_hu_f2cae20302f36bc1.webp&#34; alt=&#34;远程过程调用的流程&#34; width=&#34;900&#34; height=&#34;395&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;接下来我们从服务调用方、通信、服务提供方三部分介绍远程过程调用的具体实现过程。&lt;/p&gt;
&lt;h2 id=&#34;服务调用方&#34;&gt;服务调用方&lt;/h2&gt;
&lt;p&gt;服务调用方在本地调用服务提供方给出的接口后，相当于远程调用了该接口的具体实现。这主要用到了&lt;strong&gt;动态代理&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;动态代理能够为原本的空接口注入一个代理实现。于是，服务调用方对接口的调用便转化为了对代理实现的调用。&lt;/p&gt;
&lt;p&gt;在代理实现中，会向服务注册中心查询服务提供方的具体地址、端口，并将调用参数序列化，然后向服务提供方发送调用请求。之后，代理实现还会接收请求的回应，并通过接口返回给服务调用方的业务逻辑。&lt;/p&gt;
&lt;h2 id=&#34;通信&#34;&gt;通信&lt;/h2&gt;
&lt;p&gt;根据OSI（Open System Interconnect，开放式系统互联）模型，将网络通信的工作划分为图所示的七层。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/109/pic/osi_hu_dd63328acf90af6.webp&#34; alt=&#34;图&#34; width=&#34;900&#34; height=&#34;552&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;RPC在工作时，通常使用第七层的HTTP协议或者第四层的TCP协议、UDP协议在两个节点之间进行通信。&lt;/p&gt;
&lt;p&gt;相比而言，HTTP协议工作在第七层，对传输的信息进行了多层的封装，因此有用信息占比低，效率比较低，但使用更为简单；TCP协议、UDP协议工作在第四层，减少了封装的次数，因此有用信息占比高，效率比较高，但使用略为复杂。&lt;/p&gt;
&lt;h2 id=&#34;服务提供方&#34;&gt;服务提供方&lt;/h2&gt;
&lt;p&gt;服务提供方在接受到调用请求后，需要定位到请求所要调用的具体方法。然后将请求中的参数反序列化后展开对方法的实际调用。并在调用结束后将执行结果返回。&lt;/p&gt;
&lt;h2 id=&#34;实践&#34;&gt;实践&lt;/h2&gt;
&lt;p&gt;那要想实现这个过程该怎么办呢？别急，咱们一步一步来。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;首先&lt;/strong&gt;，调用方调用的是接口，必须得为接口构造一个假的实现。显然，要使用动态代理。这样，调用方的调用就被动态代理接收到了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第二&lt;/strong&gt;，动态代理接收到调用后，应该想办法调用远程的实际实现。这包括下面几步：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;识别具体要调用的远程方法的IP、端口&lt;/li&gt;
&lt;li&gt;将调用方法的入参进行序列化&lt;/li&gt;
&lt;li&gt;通过通信将请求发送到远程的方法中&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;第三&lt;/strong&gt;，远程的服务接收到了调用方的请求后，它应该：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;反序列化各个调用参数&lt;/li&gt;
&lt;li&gt;定位到实际要调用的方法，然后输入参数，执行方法&lt;/li&gt;
&lt;li&gt;按照调用的路径返回调用的结果&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;整个过程如下所示，虚线框内的部分是RPC框架实现的功能。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/109/pic/jyjk01_hu_17ad4af6307ab0c1.webp&#34; alt=&#34;远程过程调用的实现过程&#34; width=&#34;900&#34; height=&#34;607&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;当然在具体实现中，还需要解决一些细节问题。例如，如何具体定位要调用的方法，如何确定参数的类型等等。而至于成熟的框架，则更为复杂了。&lt;/p&gt;
&lt;p&gt;为了让大家更好地理解RPC的实现，我们直接编写了一个极简（可能是最简单的了）的RPC示例项目。该实例只用少量的几个类便实现了RPC功能，并且配有服务调用方和服务提供方的展示示例。成熟的框架要比我们的项目复杂的多，但越是简单的项目，越能让大家理解其基本原理。项目地址为：https://github.com/yeecode/EasyRPC&lt;/p&gt;
&lt;p&gt;下面是&lt;strong&gt;客户端的代码结构&lt;/strong&gt;，看着类有点多，其实代码不长。其中的RPC代码完成完成动态代理、远程调用参数序列化、远程调用发起、远程调用结果反序列化的工作。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/109/pic/z01_hu_4af3050f9f722797.webp&#34; alt=&#34;图&#34; width=&#34;478&#34; height=&#34;347&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;下面是&lt;strong&gt;服务端的代码结构&lt;/strong&gt;，代码更少，完成远程调用接收、调用参数反序列化、调用实际触发、调用结果序列化的工作。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/109/pic/z02_hu_5da7357446db5521.webp&#34; alt=&#34;图&#34; width=&#34;466&#34; height=&#34;242&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;这样，一个RPC小框架就做完了，并不复杂。&lt;/p&gt;
&lt;p&gt;大家可以查看代码，了解其具体实现。&lt;/p&gt;
&lt;h2 id=&#34;总结&#34;&gt;总结&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;RPC是易用的&lt;/strong&gt;。它是一个涉及动态代理、序列化、通信、反序列化的复杂过程，但是，以上&lt;strong&gt;这些过程都被RPC框架隐藏了起来&lt;/strong&gt;。在使用RPC框架时，我们只需要调用服务提供方的接口便可以调用到服务提供方的具体实现，而不用关心其实现细节。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;RPC是高效的&lt;/strong&gt;。它通过将底层的通信协议封装了起来，RPC可以基于第四层的协议来&lt;strong&gt;提升信息传输的效率&lt;/strong&gt;，并可以自由设计通信格式。&lt;/p&gt;
&lt;p&gt;但是，RPC也有副作用。RPC的使用使得服务提供方不需要给出HTTP接口。这对非RPC的调用是不友好的，可见，&lt;strong&gt;RPC调用损失了服务的通用性、可读性&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;该回答参考了书籍《分布式系统原理与工程实践》。书中还介绍了RPC如何结合注册中心模型使用，如何在服务调用方的代理实现中完成服务提供方的地址查询等内容。想了解的同学可以自行阅读。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://union-click.jd.com/jdc?e=&amp;p=JF8BAMYJK1olXDYCVl9YAEkUBV9MRANLAjZbERscSkAJHTdNTwcKBlMdBgABFksVAmoAGVgTQl9HCANtb05yfSYIcEV3CVJgMgctdStjWGwSa1cZbQcyVF9cCE8eBWcKG2slXQEyAjBdCUoWAm4NG14WbQcyVFlYCk0UBWoMHFwQXDYFVFdtUx55BWdbSV9AVQEBVl9fCnsnM2w4K2sVbQUyFjAIWBgRC2YLTFMWDQQHUVkKWx4SBDsNHVMdDwQLB1YPCnsVAm4MEms&#34;&gt;&lt;img src=&#34;https://yeecode.top/img/promo/dsbook.jpg&#34; width=&#34;680&#34; height=&#34;453&#34; loading=&#34;lazy&#34;  alt=&#34;分布式系统原理与工程实践&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://union-click.jd.com/jdc?e=&amp;amp;p=JF8BAMYJK1olXDYCVl9YAEkUBV9MRANLAjZbERscSkAJHTdNTwcKBlMdBgABFksVAmoAGVgTQl9HCANtb05yfSYIcEV3CVJgMgctdStjWGwSa1cZbQcyVF9cCE8eBWcKG2slXQEyAjBdCUoWAm4NG14WbQcyVFlYCk0UBWoMHFwQXDYFVFdtUx55BWdbSV9AVQEBVl9fCnsnM2w4K2sVbQUyFjAIWBgRC2YLTFMWDQQHUVkKWx4SBDsNHVMdDwQLB1YPCnsVAm4MEms&#34;&gt;《分布式系统原理与工程实践》&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>一种适用于网格计算的主动存储计算机模型</title>
      <link>https://yeecode.top/blog/80/</link>
      <pubDate>Sun, 15 May 2022 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/80/</guid>
      
      <description>&lt;p&gt;图灵机作为一种基础的理论模型，在解决可计算性问题上十分重要。但是，在实际使用中，这种模型过于简单。通常，在算法设计和分析领域，我们使用随机访问计算机模型RAM（Random Access Machine）。但是，在网格计算中，RAM模型过于基础，因此产生了众多其他模型。&lt;/p&gt;
&lt;p&gt;本文讨论一种适用于网格计算的主动存储计算机模型CAM（Computer with Active Memory），包括其提出背景，使用范围，局限性等。本文主要内容参考中科院计算所徐志伟等人编著的《网格计算技术》一书以及徐志伟先生的论文《一种主动存储计算机模型》。&lt;/p&gt;
&lt;p&gt;同时，我们也对徐志伟先生所提的理论进行了一些修改，包括将字长等元组从模型中删除，修改了一些定义等。但具体思想仍由原作给出。&lt;/p&gt;
&lt;h2 id=&#34;ram模型&#34;&gt;RAM模型&lt;/h2&gt;
&lt;p&gt;RAM模式是我们展开讨论的基础，因此我们先形式化定义RAM模型。&lt;/p&gt;
&lt;p&gt;一个RAM计算机C可以定义为一个三元组，$C=(P,M,Ci)$，其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;P是计算机的处理器&lt;/li&gt;
&lt;li&gt;M是计算机的存储器。存储器M中包含众多的存储单元，我们依次记为$M(0),M(1),M(2)……$，因此M是以上各个存储单元的集合，即$M=  {  M(0),M(1),M(2)…… } $。&lt;/li&gt;
&lt;li&gt;Ci是计算机的指令集。指令集Ci能够覆盖完成图灵机的操作，例如可以是下列指令的集合：load,store,add,subtract,multiply,divide,jump,jzero,halt。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在计算机C上执行一次计算Q可以被定义为一个四元组，$Q=(C,R,I,O)$，其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;C为计算机&lt;/li&gt;
&lt;li&gt;R为有限条指令组成的程序&lt;/li&gt;
&lt;li&gt;I是M的一个有限子集，其中存储有输入数据&lt;/li&gt;
&lt;li&gt;O是M的一个有限子集，用于存储输出数据&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这样，在运行一次计算Q时，计算机C可以按照R的指令，从I中取出输入数据进行一系列运算，并将结果存入O中。&lt;/p&gt;
&lt;p&gt;如果最后R调用了halt函数，则计算机最后停机，此时O中就是计算结果；如果Q不能结束，则Q是一个发散计算，例如流处理。&lt;/p&gt;
&lt;h2 id=&#34;cam模型&#34;&gt;CAM模型&lt;/h2&gt;
&lt;p&gt;在讨论CAM模型之前，我们先粗略介绍网格计算中涉及的两个基本的参与者：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;客户机：计算命令的发起者&lt;/li&gt;
&lt;li&gt;服务器：网格中的一个节点，是计算命令的执行者&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;另外，这两个概念是相对的。在不同的计算任务中，以上角色都可以互换，而且我们对其中的一些协调层角色进行了省略。&lt;/p&gt;
&lt;p&gt;使用RAM模型来进行网格计算是十分复杂的，这需要客户机完成大量的数据发送、任务下发工作，服务器完成大量的数据接收、计算等工作。因此，提出了CAM模型。&lt;/p&gt;
&lt;p&gt;一个CAM计算机D是一个三元组，$D=(P,M,CI)$,其中这三个元素我们分别介绍。&lt;/p&gt;
&lt;h2 id=&#34;p&#34;&gt;P&lt;/h2&gt;
&lt;p&gt;P是计算机的处理器。要注意，这里的处理器不仅仅包含客户机的处理器，也包含网格上各个服务器的处理器。&lt;/p&gt;
&lt;h2 id=&#34;m&#34;&gt;M&lt;/h2&gt;
&lt;p&gt;M是计算机的存储器。同样的，不仅包含客户机的存储器，也包含网格上各个服务器的存储器。这些存储器统一编号，即也有$M={M(0),M(1),M(2)……}$。&lt;/p&gt;
&lt;p&gt;另外要注意，CAM中的存储器M中的每个存储单元内部是划分区域的，例如将其划分为三个区域。这样，$M(1)$包含三个区域，我们分别记为$M(1,in)$,$M(1,f)$,$M(1,out)$。其中，我们可以让$M(1,in)$存储输入数据，让$M(1,f)$存储计算函数，让$M(1,out)$存储输出结果。对于每个存储单元都遵循这样的三个区域划分。&lt;/p&gt;
&lt;p&gt;因此，对于任意一个$M(i)$，它包含了一个$M(i,in)$存储输入数据，一个$M(i,f)$存储计算函数，一个$M(i,out)$存储输出结果。于是，仅依赖这些信息，它可以完成一个计算。我们也让$M(i)$具有运行能力。&lt;/p&gt;
&lt;p&gt;当运行$M(i)$时，实际是触发了下面的计算：&lt;/p&gt;
$$M(i,out)=(M(i,f))(M(i,in))$$&lt;p&gt;因此不同于RAM中的存储器，这里的存储器具有执行能力。&lt;/p&gt;
&lt;h2 id=&#34;ci&#34;&gt;CI&lt;/h2&gt;
&lt;p&gt;CI是计算机的指令集合，该集合是RAM中指令集合Ci的超集。&lt;/p&gt;
&lt;p&gt;例如，在CI中存在$Execute M(i)$表示触发运行$M(i)$，这条指令是Ci中没有的。&lt;/p&gt;
&lt;p&gt;另外要注意，$Execute M(i)$指令在计算机中仅代表触发运行$M(i)$，而不需要等$M(i)$运行结束。因此，如果存在下面的指令序列：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;a;
Execute M(i);
b;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;则不等$Execute M(i)$执行结束，便会触发指令$b$。这种定义实际是在发挥计算网格的并行计算能力。&lt;/p&gt;
&lt;h2 id=&#34;cam模型讨论&#34;&gt;CAM模型讨论&lt;/h2&gt;
&lt;p&gt;相比于RAM，CAM是RAM的扩充，其P变为一个集群，M具有了运行能力，CI扩充了命令集合。&lt;/p&gt;
&lt;p&gt;其中最重要的一点是M具有了运算能力，我们可以将其看做一个包。只要将包中的内容发送给网格中的一个节点$i$，然后使用$Execute M(i)$指令触发它，便可以在节点$i$上并发地进行这个任务。&lt;/p&gt;
&lt;p&gt;例如，下面的程序片段便完成了任务下发、执行、回收的过程：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;MOV InputData M(7,in);
MOV Function M(7,f);
Execute M(7);
MOV M(7,out) LocalFile;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;但是CAM理论并不完善，因为各个节点之间的计算往往是有依赖关系的。例如，节点7的结果作为节点9和节点17的输入等。按照$Execute M(i)$指令的定义，这种异步执行的方式不能满足这种要求。&lt;/p&gt;
&lt;p&gt;而如果将$Execute M(i)$修改为非并发的，则会导致网格计算失去并行能力。&lt;/p&gt;</description>
    </item>
    <item>
      <title>详解Iterable接口与Iterator接口异同与使用</title>
      <link>https://yeecode.top/blog/61/</link>
      <pubDate>Sun, 24 Apr 2022 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/61/</guid>
      
      <description>&lt;p&gt;Iterable接口与Iterator接口是大家经常接触到的两个接口，它们都代表与迭代操作相关的能力。&lt;/p&gt;
&lt;p&gt;Iterator单词的意思是“迭代器”，Iterable单词的意思是“可迭代的”。&lt;strong&gt;如果一个类是迭代器，那基于它可以实现迭代操作；而如果一个类能够给出一个迭代自身内元素的迭代器，那它就是可迭代的。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;因此，Iterable接口非常简单，主要定义了一个&lt;code&gt;Iterator&amp;lt;T&amp;gt; iterator()&lt;/code&gt;抽象方法用以返回一个Iterator对象（在Jdk 1.8中增加了forEach方法和spliterator方法）。&lt;/p&gt;
&lt;p&gt;Iterator接口表示一个针对集合的迭代器，Iterator接口定义了迭代器最重要的方法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;boolean hasNext()&lt;/code&gt;：判断当前迭代中是否还有未迭代的元素。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;E next()&lt;/code&gt;:返回迭代中的下一个元素。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;default void remove()&lt;/code&gt;：从迭代器指向的集合中移除迭代器返回的最后一个元素。默认情况下不支持此操作，因为很容易造成迭代混乱。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在编程开发中，Iterable接口与Iterator接口经常被用到。例如我们常用的&lt;code&gt;for-each&lt;/code&gt;就是基于这两个接口实现的。例如下面代码所示的&lt;code&gt;for-each&lt;/code&gt;操作：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;List&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;User&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; userList &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; ArrayList&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;gt;&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (User user : userList) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(user);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;经过编译后在class文件中变成了如下所示的样子。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;List&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;User&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; userList &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; ArrayList();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Iterator var2 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; userList.&lt;span style=&#34;color:#a6e22e&#34;&gt;iterator&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt;(var2.&lt;span style=&#34;color:#a6e22e&#34;&gt;hasNext&lt;/span&gt;()) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    User user &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (User)var2.&lt;span style=&#34;color:#a6e22e&#34;&gt;next&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(user);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这是因为&lt;code&gt;for-each&lt;/code&gt;是一个语法糖操作，会由编译器在编译阶段帮我们转化为基本语法。于是我们使用&lt;code&gt;for-each&lt;/code&gt;操作对List中的元素进行遍历时，List作为Iterable接口的子类先通过iterator方法给出一个Iterator对象，然后基于Iterator对象实现了List中所有元素的遍历。&lt;/p&gt;
&lt;blockquote&gt;
    &lt;p&gt;语法糖是编程语言中一种简化的语法，它能够使得代码更加简洁。对于Java语言而言，JVM并不识别语法糖中的语句，因此这些语句会在编译阶段被还原成基本的语句。&lt;/p&gt;
&lt;p&gt;要想查看一段java代码在class文件中的真实形态，最简单的方法是使用集成开发软件找到target目录下对应的class文件后查看。也可以使用自己使用javac命令编译后使用相关工具打开对应的class文件查看。&lt;/p&gt;

  &lt;/blockquote&gt;&lt;p&gt;最后我们再总结一下，Iterable接口表征一个类是可迭代的，Iterator接口表征一个类是迭代器：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果一个类，能够给出一个迭代器（通过iterator方法）用来对某个集合中的元素进行迭代，那么这个类可以继承Iterable接口。&lt;/li&gt;
&lt;li&gt;如果一个类本身就是一个迭代器，能够对某个集合展开迭代操作，那么这个类可以继承Iterator接口。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以上内容均参考《通用源码阅读指导书——MyBatis源码详解》一书。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://union-click.jd.com/jdc?e=&amp;p=JF8BAMYJK1olXDYCV1ZeCU4UCl9MRANLAjZbERscSkAJHTdNTwcKBlMdBgABFksUC2wJHlgcQl9HCANtb1FqVSleXj1wFXRcAC4CXAlkWTFyXVcZbQcyVF9cCE8eBWcKG2slXQEyAjBdCUoWAm4NG14WbQcyVFlYCkMTBGcBGVkWXzYFVFdtUx55BWdbSV9AVQEBVl9fCnsnM2w4K2sVbQUyFjAKDRtHUTgNHF4VVVMHAV5YAEhAAWsLHFkXCQEDXVcOXXsVAm4MEms&#34;&gt;&lt;img src=&#34;https://yeecode.top/img/promo/mb.jpg&#34; width=&#34;450&#34; height=&#34;348&#34; loading=&#34;lazy&#34;  alt=&#34;通用源码阅读指导书-京东自营&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://union-click.jd.com/jdc?e=&amp;amp;p=JF8BAMYJK1olXDYCV1ZeCU4UCl9MRANLAjZbERscSkAJHTdNTwcKBlMdBgABFksUC2wJHlgcQl9HCANtb1FqVSleXj1wFXRcAC4CXAlkWTFyXVcZbQcyVF9cCE8eBWcKG2slXQEyAjBdCUoWAm4NG14WbQcyVFlYCkMTBGcBGVkWXzYFVFdtUx55BWdbSV9AVQEBVl9fCnsnM2w4K2sVbQUyFjAKDRtHUTgNHF4VVVMHAV5YAEhAAWsLHFkXCQEDXVcOXXsVAm4MEms&#34;&gt;《通用源码阅读指导书》&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;阅读源码确实对编程能力的提升有很大的帮助。《通用源码阅读指导书——MyBatis源码详解》是一本以MyBatis的源码为实例讲述源码阅读方法的书籍，并且附带有示例项目源码，MyBatis的全中文注解。书籍还总结了大量的编程知识和架构经验，对提升编程和架构能力十分有用。&lt;/p&gt;
&lt;p&gt;而MyBatis中有很多地方用到了Iterable接口与Iterator接口，我们参照《通用源码阅读指导书——MyBatis源码详解》了解下其具体使用。&lt;/p&gt;
&lt;p&gt;在使用MyBatis进行数据库查询时，经常会查询到大量的结果。如下面代码所示的例子中，我们查询到了大量的User对象，并使用List接受了这些对象。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;List&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;User&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; userList &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; userMapper.&lt;span style=&#34;color:#a6e22e&#34;&gt;queryUserBySchoolName&lt;/span&gt;(userParam);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;但有些时候，我们希望逐一读入和处理查询结果，而不是一次读入整个结果集。因为前者能够减少对内存的占用，这在处理大量的数据时会显得十分必要。游标就能够帮我们实现这一目的，它支持我们每次从结果集中取出一条结果。&lt;/p&gt;
&lt;p&gt;MyBatis中使用游标进行查询非常简单，映射文件不需要任何的变动，只需要在映射接口中标明返回值类型是Cursor即可，如下所示。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Cursor&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;User&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;queryUserBySchoolName&lt;/span&gt;(User user);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;然后便可以用下面的方式来接收和处理结果。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;UserMapper userMapper &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; session.&lt;span style=&#34;color:#a6e22e&#34;&gt;getMapper&lt;/span&gt;(UserMapper.&lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;User userParam &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; User();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;userParam.&lt;span style=&#34;color:#a6e22e&#34;&gt;setSchoolName&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Sunny School&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Cursor&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;User&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; userCursor &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; userMapper.&lt;span style=&#34;color:#a6e22e&#34;&gt;queryUserBySchoolName&lt;/span&gt;(userParam);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (User user : userCursor) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;name : &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; user.&lt;span style=&#34;color:#a6e22e&#34;&gt;getName&lt;/span&gt;() &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34; ;  email : &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; user.&lt;span style=&#34;color:#a6e22e&#34;&gt;getEmail&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;其最终实现就是使用迭代器Iterator。其涉及的具体的MyBatis源码我们不再展开，大家可以参考《通用源码阅读指导书——MyBatis源码详解》的“第21章 cursor包”章节。&lt;/p&gt;</description>
    </item>
    <item>
      <title>详解Java中的对象的引用和引用级别</title>
      <link>https://yeecode.top/blog/60/</link>
      <pubDate>Sat, 19 Mar 2022 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/60/</guid>
      
      <description>&lt;p&gt;通常，我们创建一个对象后，会直接引用它，这种引用是强引用。而Java还提供多种级别的引用方式，供我们根据不同的场景使用。本节，我们将介绍Java中的对象引用级别。&lt;/p&gt;
&lt;p&gt;此外，我们还将介绍一个有用的引用监控类ReferenceQueue，它将帮我们记录哪些对象已经被回收。&lt;/p&gt;
&lt;h2 id=&#34;1-java对象的引用级别&#34;&gt;1 Java对象的引用级别&lt;/h2&gt;
&lt;p&gt;在Java程序的运行过程中，JVM会自动地帮我们进行垃圾回收操作以避免无用的对象占用内存空间。这个过程主要分为两步：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;找出所有的垃圾对象&lt;/li&gt;
&lt;li&gt;清理找出的垃圾对象&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;我们这里重点关注第一步，即如何找出垃圾对象。这里的关键问题在于如何判断一个对象是否为垃圾对象。&lt;/p&gt;
&lt;p&gt;判断一个对象是否为垃圾对象的方法主要有引用计数法和可达性分析法，JVM采用的是可达性分析法。&lt;/p&gt;
&lt;p&gt;可达性分析是指JVM会从垃圾回收的根对象（Garbage Collection Root, 简称GC Root）为起点，沿着对象之间的引用关系不断遍历。最终能够遍历到的对象都是有用的对象，而遍历结束后也无法遍历到的应用便是垃圾对象。&lt;/p&gt;
&lt;p&gt;根对象不止一个，例如栈中引用的对象、方法区中的静态成员等都是常见的根对象。&lt;/p&gt;
&lt;p&gt;我们举一个例子。如果下图中的对象c不再引用对象d，则通过GC Root便无法到达对象d和对象f，那么对象d和f便成了垃圾对象。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/60/pic/gcr_hu_9bc9f25672d32852.webp&#34; alt=&#34;可达性分析法示例&#34; width=&#34;900&#34; height=&#34;411&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;有一点要说明，在上图中我们只绘制了一个GC Root，实际在JVM中有多个GC Root。当一个对象无法通过任何一个GC Root遍历到时，它才是垃圾对象。&lt;/p&gt;
&lt;p&gt;不过上图展示的这种引用关系是有局限性的。试想存在一个非必须的大对象，我们希望系统在内存不紧张时可以保留它，而在内存紧张时释放它以为更重要的对象让渡内存空间。这时应该怎么做呢？&lt;/p&gt;
&lt;p&gt;Java已经考虑到了这种情况，Java的引用中并不是只有“引用”、“不引用”这两种情况，而是有四种情况。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;强引用（SoftReference）：即我们平时所说的引用。只要一个对象能够被GC Root强引用到，那它就不是垃圾对象。当内存不足时，JVM会抛出OutOfMemoryError错误而不是清除被强引用的对象。&lt;/li&gt;
&lt;li&gt;软引用（SoftReference）：如果一个对象只能被GC Root软引用到，则说明它是非必须的。当内存空间不足时，JVM会回收该对象。&lt;/li&gt;
&lt;li&gt;弱引用（WeakReference）：如果一个对象只能被GC Root弱引用到，则说明它是多余的。JVM只要发现它，不管内存空间是否充足都会回收该对象。与软引用相比，弱引用的引用强度更低，被弱引用的对象存在时间相对更短。&lt;/li&gt;
&lt;li&gt;虚引用（PhantomReference）：如果一个对象只能被GC Root虚引用到，则和无法被GC Root引用到时一样。因此，就垃圾回收过程而言，虚引用就像不存在一样，并不会决定对象的生命周期。虚引用主要用来跟踪对象被垃圾回收器回收的活动。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下面给出了强引用、软引用、弱引用的示例。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 通过等号直接建立的引用都是强引用&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;User user &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; User();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 通过SoftReference建立的引用是软引用&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;SoftReference&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;User&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; softRefUser &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; SoftReference&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;gt;&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; User());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 通过WeakReference建立的引用是弱引用&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;WeakReference&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;User&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; weakRefUser &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; WeakReference&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;gt;&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; User());
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;2-referencequeue&#34;&gt;2 ReferenceQueue&lt;/h2&gt;
&lt;p&gt;如果一个对象只有软引用或者弱引用，则它随时可能会被JVM垃圾回收掉。于是它就成了薛定谔的猫，在我们读取它之前，根本无法知道它是否还存在。&lt;/p&gt;
&lt;p&gt;可是，有时我们需要知道被软引用或者弱引用的对象在何时被回收，以便于进行一些后续的处理工作。ReferenceQueue类便提供了这样的功能。ReferenceQueue本身是一个列表，我们可以在创建软引用或者弱引用的包装对象时传入该列表。这样，当JVM回收被包装的对象时，会将其包装类加入到ReferenceQueue中。&lt;/p&gt;
&lt;p&gt;我们可以通过一个可能并不恰当的例子来理解这些概念。假设我们的目标对象是雪糕，软引用或者弱引用的包装对象就是雪糕棒。我们虽然持有了雪糕棒，但是雪糕棒上的雪糕却随时可能融化后掉在地上（也可能是被我们偷吃了，总之是没有了，相当于被JVM销毁了）。ReferenceQueue是我们收集雪糕棒的小木桶，当我们发现某根雪糕棒上的雪糕消失时，就会把雪糕棒放到小木桶中。这样一来，我们只要观察小木桶，就能知道哪些雪糕已经消失了。&lt;/p&gt;
&lt;p&gt;我们用示例来展示ReferenceQueue的作用。下面代码给出了目标对象User的源码，它的toString方法会返回包含自身id的字符串。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;User&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;long&lt;/span&gt; id;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;User&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;User&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;long&lt;/span&gt; id) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; id;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;@Override&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; String &lt;span style=&#34;color:#a6e22e&#34;&gt;toString&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;User:&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; id;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;下面是主方法的源码。我们对目标对象User建立弱引用包装，在建立包装的构造方法中传入了ReferenceQueue。这样，当被User对象被清理后，它对应的包装对象WeakReference会被放入ReferenceQueue中。&lt;/p&gt;</description>
    </item>
    <item>
      <title>详解Java中与数据库相关的包和类</title>
      <link>https://yeecode.top/blog/59/</link>
      <pubDate>Fri, 04 Feb 2022 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/59/</guid>
      
      <description>&lt;p&gt;很多时候，用Java编写的程序都需要和数据库打交道，这需要我们对Java中与数据库相关的包和类比较了解。&lt;/p&gt;
&lt;p&gt;当然，目前很多ORM框架，例如MyBatis等，都会对数据库操作进行进一步的封装，但即使这样，在异常发生时，也会打印出和这些类相关的提示信息。因此无论何种使用场景，了解这些包和类都是十分必要的。本文也主要参考了《通用源码阅读指导书——MyBatis源码详解》一书，这是一本以MyBatis的源码为实例讲述源码阅读方法的书籍，并且附带有示例项目源码，MyBatis的全中文注解。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://union-click.jd.com/jdc?e=&amp;p=JF8BAMYJK1olXDYCV1ZeCU4UCl9MRANLAjZbERscSkAJHTdNTwcKBlMdBgABFksUC2wJHlgcQl9HCANtb1FqVSleXj1wFXRcAC4CXAlkWTFyXVcZbQcyVF9cCE8eBWcKG2slXQEyAjBdCUoWAm4NG14WbQcyVFlYCkMTBGcBGVkWXzYFVFdtUx55BWdbSV9AVQEBVl9fCnsnM2w4K2sVbQUyFjAKDRtHUTgNHF4VVVMHAV5YAEhAAWsLHFkXCQEDXVcOXXsVAm4MEms&#34;&gt;&lt;img src=&#34;https://yeecode.top/img/promo/mb.jpg&#34; width=&#34;450&#34; height=&#34;348&#34; loading=&#34;lazy&#34;  alt=&#34;通用源码阅读指导书-京东自营&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://union-click.jd.com/jdc?e=&amp;amp;p=JF8BAMYJK1olXDYCV1ZeCU4UCl9MRANLAjZbERscSkAJHTdNTwcKBlMdBgABFksUC2wJHlgcQl9HCANtb1FqVSleXj1wFXRcAC4CXAlkWTFyXVcZbQcyVF9cCE8eBWcKG2slXQEyAjBdCUoWAm4NG14WbQcyVFlYCkMTBGcBGVkWXzYFVFdtUx55BWdbSV9AVQEBVl9fCnsnM2w4K2sVbQUyFjAKDRtHUTgNHF4VVVMHAV5YAEhAAWsLHFkXCQEDXVcOXXsVAm4MEms&#34;&gt;《通用源码阅读指导书》&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;接下来，我们开始介绍，将先介绍Java提供的两个数据库操作包，然后介绍几个主要的类。&lt;/p&gt;
&lt;h2 id=&#34;1-javasql包和javaxsql包&#34;&gt;1 &lt;code&gt;java.sql&lt;/code&gt;包和&lt;code&gt;javax.sql&lt;/code&gt;包&lt;/h2&gt;
&lt;p&gt;Java提供的与数据库操作相关的包主要有两个，它们是&lt;code&gt;java.sql&lt;/code&gt;包和&lt;code&gt;javax.sql&lt;/code&gt;包。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;java.sql&lt;/code&gt;和&lt;code&gt;javax.sql&lt;/code&gt;共同为JAVA提供了强大的JDBC能力。我们接下来会介绍几个MyBatis中常接触到的类，它们都由&lt;code&gt;java.sql&lt;/code&gt;或&lt;code&gt;javax.sql&lt;/code&gt;提供。&lt;/p&gt;
&lt;h2 id=&#34;11-javasql包&#34;&gt;1.1 &lt;code&gt;java.sql&lt;/code&gt;包&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;java.sql&lt;/code&gt;通常被称为JDBC核心API包，它为Java提供了访问数据源中数据的基础功能。基于该包能实现将SQL语句传递给数据库、从数据库中以表格的形式读写数据等功能。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;java.sql&lt;/code&gt;提供了一个&lt;code&gt;Driver&lt;/code&gt;接口作为数据库驱动的接口。如下图所示。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/59/pic/DriverManager_hu_545b32d998d71901.webp&#34; alt=&#34;Driver接口相关类的类图&#34; width=&#34;900&#34; height=&#34;311&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;不同种类的数据库厂商只需要根据自身数据库特点开发相应的&lt;code&gt;Driver&lt;/code&gt;实现类，并通过&lt;code&gt;DriverManager&lt;/code&gt;进行注册即可。这样，基于JDBC便可以连接不同公司不同种类的数据库。&lt;/p&gt;
&lt;p&gt;除此之外，&lt;code&gt;java.sql&lt;/code&gt;还为数据库连接、SQL语句、结果集等提供了众多的类，例如表示数据库连接的Connection类、表示数据库操作语句的Statement类、表示数据库操作结果的ResultSet类等。&lt;/p&gt;
&lt;p&gt;基于&lt;code&gt;java.sql&lt;/code&gt;包，Java程序能够完成各种数据库操作。通常完成一次数据库操作的流程如下所示：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;建立DriverManager对象。&lt;/li&gt;
&lt;li&gt;从DriverManager对象中获取Connection对象。&lt;/li&gt;
&lt;li&gt;从Connection对象中获取Statement对象。&lt;/li&gt;
&lt;li&gt;将SQL语句交给Statement对象执行，并获取返回的结果，结果通常放在ResultSet中。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;12-javaxsql包&#34;&gt;1.2 &lt;code&gt;javax.sql&lt;/code&gt;包&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;javax.sql&lt;/code&gt;通常被称为JDBC扩展API包，它扩展了JDBC核心API包的功能，提供了对服务器端的支持，是Java企业版的重要部分。&lt;/p&gt;
&lt;p&gt;例如，&lt;code&gt;javax.sql&lt;/code&gt;提供了&lt;code&gt;DataSource&lt;/code&gt;接口，通过它可以获取面向数据源的Connection，与&lt;code&gt;java.sql&lt;/code&gt;中直接使用&lt;code&gt;DriverManager&lt;/code&gt;建立连接的方式相比更为灵活（实际上，&lt;code&gt;DataSource&lt;/code&gt;接口的实现中也是通过DriverManager对象获取的Connection对象）。除此之外，&lt;code&gt;javax.sql&lt;/code&gt;还提供了连接池、语句池、分布式事务等方面的诸多特性。&lt;/p&gt;
&lt;p&gt;使用了&lt;code&gt;javax.sql&lt;/code&gt;包扩展了&lt;code&gt;java.sql&lt;/code&gt;包之后，建议使用&lt;code&gt;DataSource&lt;/code&gt;来获取Connection，而不是直接使用DriverManager。于是，一条SQL语句的执行过程如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;建立DataSource对象。&lt;/li&gt;
&lt;li&gt;从DataSource对象中获取Connection对象。&lt;/li&gt;
&lt;li&gt;从Connection对象中获取Statement对象。&lt;/li&gt;
&lt;li&gt;将SQL语句交给Statement对象执行，并获取返回的结果，结果通常放在ResultSet中。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;2-drivermanager&#34;&gt;2 DriverManager&lt;/h2&gt;
&lt;p&gt;DriverManager接口位于java.sql，它是JDBC驱动程序管理器，可以管理一组JDBC驱动程序。&lt;/p&gt;
&lt;p&gt;DriverManager有一个重要的功能是能够给出一个面向数据库的连接对象Connection对象，该功能是由DriverManager中的&lt;code&gt;getConnection&lt;/code&gt;方法提供的。&lt;/p&gt;
&lt;p&gt;当调用getConnection方法时，DriverManager会尝试已经加载的驱动程序中找出合适的一个，并用找出的驱动程序建立一个面向指定数据库的连接，最后将建立的连接返回。&lt;/p&gt;
&lt;p&gt;DriverManager中主要有下面几个方法。这些方法都是静态方法，不需要建立DriverManager对象便可以直接调用。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;void  registerDriver： 向DriverManager中注册给定的驱动程序。&lt;/li&gt;
&lt;li&gt;void deregisterDriver： 从DriverManager中删除给定的驱动程序。&lt;/li&gt;
&lt;li&gt;Driver getDriver： 查找能匹配给定URL路径的驱动程序。&lt;/li&gt;
&lt;li&gt;Enumeration&lt;Driver&gt; getDrivers：获取当前调用者可以访问的所有已加载的JDBC驱动程序。&lt;/li&gt;
&lt;li&gt;Connection getConnection：建立到给定数据库的连接。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;3-datasource&#34;&gt;3 DataSource&lt;/h2&gt;
&lt;p&gt;DataSource是&lt;code&gt;javax.sql&lt;/code&gt;的一个接口。故名思议，它代表了一个实际的数据源，它的功能是作为工厂提供数据库连接。&lt;/p&gt;
&lt;p&gt;DataSource接口中只有两个接口方法，都用来获取一个Connection对象：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;getConnection()：从当前的数据源中建立一个连接。&lt;/li&gt;
&lt;li&gt;getConnection(String, String)：从当前的数据源中建立一个连接，输入的参数为数据源的用户名和密码。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;javax.sql&lt;/code&gt;中的DataSource仅仅是一个接口，不同的数据库可以为其提供多种实现。常见的实现有以下几种：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;基本实现：生成基本的到数据库的连接对象Connection。&lt;/li&gt;
&lt;li&gt;连接池实现：生成的Connection对象能够自动加入到连接池中。&lt;/li&gt;
&lt;li&gt;分布式事务实现：生成的Connection对象能够参与分布式事务。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;正因为DataSource接口可以有多种实现，与直接使用DriverManager获得连接对象Connection的方式相比更为灵活。在日常的开发过程中，建议采取使用DataSource来获取数据库连接。&lt;/p&gt;
&lt;p&gt;而实际上在DataSource的具体实现中，最终也是基于DriverManager获得的Connection，因此DataSource只是DriverManager的进一步封装。&lt;/p&gt;
&lt;h2 id=&#34;4-connection&#34;&gt;4 Connection&lt;/h2&gt;
&lt;p&gt;Connection接口位于&lt;code&gt;java.sql&lt;/code&gt;中，它代表对某个数据库的连接。基于这个连接，可以完成SQL语句的执行和结果的获取等工作。&lt;/p&gt;
&lt;p&gt;Connection中常用的方法如下:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Statement  createStatement：创建一个Statement对象，通过它能将SQL语句发送到数据库。&lt;/li&gt;
&lt;li&gt;CallableStatement  prepareCall：创建一个CallableStatement对象，通过它能调用存储过程。&lt;/li&gt;
&lt;li&gt;PreparedStatement  prepareStatement：创建一个PreparedStatement对象，通过它能将参数化的SQL语句发送到数据库。&lt;/li&gt;
&lt;li&gt;String  nativeSQL：将输入的SQL语句转换成本地可用的SQL语句。&lt;/li&gt;
&lt;li&gt;void  commit：提交当前事务。&lt;/li&gt;
&lt;li&gt;void  rollback：回滚当前事务。&lt;/li&gt;
&lt;li&gt;void  close：关闭当前的Connection对象。&lt;/li&gt;
&lt;li&gt;boolean  isClosed：查询当前Connection对象是否关闭。&lt;/li&gt;
&lt;li&gt;boolean  isValid：查询当前Connection是否有效。&lt;/li&gt;
&lt;li&gt;void  setAutoCommit：根据输入参数设定当前Connection对象的自动提交模式。&lt;/li&gt;
&lt;li&gt;int  getTransactionIsolation：获取当前Connection对象的事务隔离级别。&lt;/li&gt;
&lt;li&gt;void  setTransactionIsolation：设定当前Connection对象的事务隔离级别。&lt;/li&gt;
&lt;li&gt;DatabaseMetaData  getMetaData：获取当前Connection对象锁连接的数据库的所有元数据信息。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;上述方法主要是用来完成获取Statement对象、设置Connection属性等功能。&lt;/p&gt;</description>
    </item>
    <item>
      <title>OGNL详解及其解析源码分析</title>
      <link>https://yeecode.top/blog/58/</link>
      <pubDate>Mon, 31 Jan 2022 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/58/</guid>
      
      <description>&lt;p&gt;OGNL（Object Graph Navigation Language，中文名为：对象图导航语言）是一种功能强大的表达式语言（Expression Language，简称EL）。通过它，能够完成从集合中选取对象、读写对象的属性、调用对象和类的方法、表达式求值与判断等操作。&lt;/p&gt;
&lt;p&gt;OGNL应用十分广泛，例如同样是获取Map中某个对象的属性，用Java语言表示出来如下：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;userMap.get(&amp;#34;user2&amp;#34;).getName();
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;而使用OGNL表达式则为：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;#user2.name
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;除了简单清晰以外，OGNL有着更高的环境适应性。我们可以将OGNL表达式应用在配置文件、XML文件等处，而只要在解析这些文件时使用OGNL进行解析即可。例如，下面所示的一段XML配置中，&lt;code&gt;test&lt;/code&gt;条件的判断就使用了OGNL表达式。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;select id&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;selectUsersByNameOrSchoolName&amp;#34;&lt;/span&gt; parameterMap&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;userParam01&amp;#34;&lt;/span&gt; resultType&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;User&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    select &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; from &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;user&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;where&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; test&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;name != null&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;{name}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; test&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;schoolName != null&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            AND schoolName &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;{schoolName}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/&lt;/span&gt;where&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/&lt;/span&gt;select&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;那Java程序如何解析OGNL中的表达式呢？&lt;/p&gt;
&lt;p&gt;OGNL有Java工具包，只要引入它我们便可以在Java中使用OGNL的功能。这样我们就可以使用Java来解析引入了OGNL的各种文档。在介绍OGNL用法之前，我们先介绍OGNL解析时要接触的三个重要概念：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;表达式（expression）：是一个带有语法含义的字符串，是整个OGNL的核心内容。通过表达式来确定需要进行的OGNL操作。&lt;/li&gt;
&lt;li&gt;根对象（root）：可以理解为OGNL的被操作对象。表达式中表示的操作就是针对这个对象展开的。&lt;/li&gt;
&lt;li&gt;上下文（context）：整个OGNL处理时的上下文环境，该环境是一个Map对象。在进行OGNL处理之前，我们可以传入一个初始化过的上下文环境。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;例如在下面代码中，Java便使用OGNL完成了根对象信息的读写操作、上下文信息的读写操作。并且，整个操作过程中也支持逻辑表达式的判断等。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;User user01 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; User(1, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;易哥&amp;#34;&lt;/span&gt;, 18);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;User user02 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; User(2, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;莉莉&amp;#34;&lt;/span&gt;, 15);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Map&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;String, User&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; userMap &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; HashMap&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;gt;&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;userMap.&lt;span style=&#34;color:#a6e22e&#34;&gt;put&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;user1&amp;#34;&lt;/span&gt;, user01);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;userMap.&lt;span style=&#34;color:#a6e22e&#34;&gt;put&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;user2&amp;#34;&lt;/span&gt;, user02);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 使用表达式读写根对象中信息的示例&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 该示例中要用到的OGNL函数：&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// getValue(String expression, Object root) ：对root内容执行expression中的操作，并返回结果&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 读取根对象的属性值&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Integer age &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (Integer) Ognl.&lt;span style=&#34;color:#a6e22e&#34;&gt;getValue&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;age&amp;#34;&lt;/span&gt;, userMap.&lt;span style=&#34;color:#a6e22e&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;user1&amp;#34;&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;读取根对象属性，得到age：&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; age);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 设置根对象的属性值&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Ognl.&lt;span style=&#34;color:#a6e22e&#34;&gt;getValue&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;age = 19&amp;#34;&lt;/span&gt;, userMap.&lt;span style=&#34;color:#a6e22e&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;user1&amp;#34;&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;age &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (Integer) Ognl.&lt;span style=&#34;color:#a6e22e&#34;&gt;getValue&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;age&amp;#34;&lt;/span&gt;, userMap.&lt;span style=&#34;color:#a6e22e&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;user1&amp;#34;&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;设置根对象属性后，得到age：&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; age);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 使用表达式读写环境中信息的示例&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 该示例中要用到的OGNL函数：&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// getValue(String expression, Map context, Object root) ：在context环境中对root内容执行expression中的操作，并返回结果&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 读取环境中的信息&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;String userName2 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (String) Ognl.&lt;span style=&#34;color:#a6e22e&#34;&gt;getValue&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;#user2.name&amp;#34;&lt;/span&gt;, userMap, &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Object());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;读取环境中的信息，得到user2的name：&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; userName2);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 读取环境中的信息，并进行判断&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Boolean result &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (Boolean) Ognl.&lt;span style=&#34;color:#a6e22e&#34;&gt;getValue&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;#user2.name != &amp;#39;丽丽&amp;#39;&amp;#34;&lt;/span&gt;, userMap, &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Object());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;读取环境中的信息，并进行判断，得到：&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; result);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 设置环境中的信息&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Ognl.&lt;span style=&#34;color:#a6e22e&#34;&gt;getValue&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;#user2.name = &amp;#39;小华&amp;#39;&amp;#34;&lt;/span&gt;, userMap, &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Object());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;String newUserName &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (String) Ognl.&lt;span style=&#34;color:#a6e22e&#34;&gt;getValue&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;#user2.name&amp;#34;&lt;/span&gt;, userMap, &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Object());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;设置环境中的信息后，得到user2的name：&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; newUserName);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;上述的运行结果如下所示。&lt;/p&gt;</description>
    </item>
    <item>
      <title>建造者模式详解及其实战源码分析</title>
      <link>https://yeecode.top/blog/57/</link>
      <pubDate>Sun, 19 Dec 2021 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/57/</guid>
      
      <description>&lt;p&gt;在软件开发过程中，经常需要新建对象并给对象的属性赋值。当对象的属性比较多时，创建对象的过程会变得比较烦琐。&lt;/p&gt;
&lt;p&gt;例如，某个平台需要在开学季给Sunny School和Garden School两所学校的新同学注册账号。Sunny School是一所小学，新同学年龄大多为7岁；Garden School是一所中学，新同学年龄大多为13岁。在注册账号的过程中，如果同学没有电子邮箱的话还要为其注册电子邮箱。&lt;/p&gt;
&lt;p&gt;先建造空对象，然后在不断调用set方法为对象属性赋值是一种常见的建造对象的方式，如下面代码所示。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;User user01 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; User();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;user01.&lt;span style=&#34;color:#a6e22e&#34;&gt;setName&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Candy&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;user01.&lt;span style=&#34;color:#a6e22e&#34;&gt;setEmail&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;candy@sunnyschool.com&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;user01.&lt;span style=&#34;color:#a6e22e&#34;&gt;setAge&lt;/span&gt;(7);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;user01.&lt;span style=&#34;color:#a6e22e&#34;&gt;setSex&lt;/span&gt;(1);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;user01.&lt;span style=&#34;color:#a6e22e&#34;&gt;setSchoolName&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Sunny School&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这种方式需要了解对象的所有属性细节，是和对象的属性耦合的，而且这个过程中可能会导致属性的遗忘。&lt;/p&gt;
&lt;p&gt;使用具有多个入参的构造方法直接建造对象也是一种常见的建造对象的方式，如下所示。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;User user02 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; User(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Candy&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;candy@sunnyschool.com&amp;#34;&lt;/span&gt;, 7, 1, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Sunny School&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这种情况下，为了能适应多种入参组合，通常需要重载出大量的构造方法。&lt;/p&gt;
&lt;p&gt;建造者模式给我们提供了另一种建造对象的思路。使用建造者模式，对象的建造细节均交给建造者来完成，调用者只需要掌控总体流程即可，而不需要了解被建造对象的细节。&lt;/p&gt;
&lt;p&gt;例如，我们编写一个UserBuilder接口作为建造User对象的接口，如下所示。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;interface&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;UserBuilder&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; UserBuilder &lt;span style=&#34;color:#a6e22e&#34;&gt;setEmail&lt;/span&gt;(String email);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; UserBuilder &lt;span style=&#34;color:#a6e22e&#34;&gt;setAge&lt;/span&gt;(Integer age);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; UserBuilder &lt;span style=&#34;color:#a6e22e&#34;&gt;setSex&lt;/span&gt;(Integer sex);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; User &lt;span style=&#34;color:#a6e22e&#34;&gt;build&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;然后我们继承UserBuilder接口编写一个SunnySchoolUserBuilder类，它用来建造Sunny School的用户，如下所示。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;SunnySchoolUserBuilder&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;implements&lt;/span&gt; UserBuilder {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; String name;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; String email;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; Integer age;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; Integer sex;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; String schoolName;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;SunnySchoolUserBuilder&lt;/span&gt;(String name) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; name;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; SunnySchoolUserBuilder &lt;span style=&#34;color:#a6e22e&#34;&gt;setEmail&lt;/span&gt;(String email) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;email&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; email;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; SunnySchoolUserBuilder &lt;span style=&#34;color:#a6e22e&#34;&gt;setAge&lt;/span&gt;(Integer age) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;age&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; age;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; SunnySchoolUserBuilder &lt;span style=&#34;color:#a6e22e&#34;&gt;setSex&lt;/span&gt;(Integer sex) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;sex&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; sex;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; User &lt;span style=&#34;color:#a6e22e&#34;&gt;build&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;email&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;email&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;toLowerCase&lt;/span&gt;().&lt;span style=&#34;color:#a6e22e&#34;&gt;replace&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34; &amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;concat&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;@sunnyschool.com&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;age&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;age&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; 7;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;sex&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;sex&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; 0;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;schoolName&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Sunny School&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; User(name, email, age, sex, schoolName);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这样一来，我们可以灵活地建造对象。&lt;/p&gt;</description>
    </item>
    <item>
      <title>如何使用Java解析XML文档</title>
      <link>https://yeecode.top/blog/56/</link>
      <pubDate>Sun, 07 Nov 2021 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/56/</guid>
      
      <description>&lt;p&gt;在前面的文章中，我们介绍了XML文件的规范与使用。而且XML应用非常广泛，在很多的软件中，会使用XML作为配置文档。那这些配置文档是如何被解析的呢？本文我们将参考《通用源码阅读指导书——MyBatis源码详解》一书介绍如何使用Java解析XML文件。&lt;/p&gt;
&lt;p&gt;前面的文章中我们已经说明XML表述了一种树状结构，并通过给出了XML片段的结构树。而XPath（XML Path Language，即，XML路径语言）作为一种小型的查询语言能够根据XML结构树在树中寻找节点。&lt;/p&gt;
&lt;p&gt;XPath定义了一组语法，能够从结构树中筛选出满足要求的节点。如果读者对CSS选择器或者jQuery选择器比较熟悉的话，那掌握XPath的语法还是非常简单的，因为这些选择器的语法思路是相通的。&lt;/p&gt;
&lt;p&gt;例如XML片段如下：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;?&lt;/span&gt;xml version&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;1.0&amp;#34;&lt;/span&gt; encoding&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;UTF-8&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;?&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;members&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;user type&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;student&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;id&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;1&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/&lt;/span&gt;id&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;name&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;易哥&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/&lt;/span&gt;name&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;shcool&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;Sunny School&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/&lt;/span&gt;shcool&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/&lt;/span&gt;user&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;user type&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;student&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;id&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;2&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/&lt;/span&gt;id&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;name&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;莉莉&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/&lt;/span&gt;name&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;shcool&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;Garden School&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/&lt;/span&gt;shcool&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/&lt;/span&gt;user&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/&lt;/span&gt;members&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;我们可以通过下表所示的例子来简单了解下XPath的语法。&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;路径表达式&lt;/th&gt;
					&lt;th&gt;含义&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;.&lt;/td&gt;
					&lt;td&gt;当前元素&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;..&lt;/td&gt;
					&lt;td&gt;当前元素的父元素&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;user&lt;/td&gt;
					&lt;td&gt;user元素&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;/user&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;user根元素&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;user/id &lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;user元素的名为id的直接子元素&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;user//id &lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;user元素的名为id的直接或间接子元素&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;user/id[1]&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;user元素的名为id的直接子元素中的第一个&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;user/*[1]&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;user元素的第一个子元素&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;user/id[last()-1]&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;user元素的名为id的直接子元素中的倒数第二个&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;//user[@type=&#39;student&#39;]&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;所有type属性值为“student”的user元素&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;//user[id&amp;gt;3]&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;所有id子元素值大于3的的user元素&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;javax.xml.xpath&lt;/code&gt;包提供了强大的XPath解析功能，因此我们可以基于它实现XML的解析。&lt;/p&gt;
&lt;p&gt;我们可以通过下面的代码解析该XML片段。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;String resource &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;info.xml&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;DocumentBuilderFactory dbf &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; DocumentBuilderFactory.&lt;span style=&#34;color:#a6e22e&#34;&gt;newInstance&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;DocumentBuilder db &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; dbf.&lt;span style=&#34;color:#a6e22e&#34;&gt;newDocumentBuilder&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Document doc &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; db.&lt;span style=&#34;color:#a6e22e&#34;&gt;parse&lt;/span&gt;(Thread.&lt;span style=&#34;color:#a6e22e&#34;&gt;currentThread&lt;/span&gt;().&lt;span style=&#34;color:#a6e22e&#34;&gt;getContextClassLoader&lt;/span&gt;().&lt;span style=&#34;color:#a6e22e&#34;&gt;getResourceAsStream&lt;/span&gt;(resource));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;XPathFactory factory &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; XPathFactory.&lt;span style=&#34;color:#a6e22e&#34;&gt;newInstance&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;XPath xpath &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; factory.&lt;span style=&#34;color:#a6e22e&#34;&gt;newXPath&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;XPathExpression compile &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; xpath.&lt;span style=&#34;color:#a6e22e&#34;&gt;compile&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/members/user[id=1]&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(compile.&lt;span style=&#34;color:#a6e22e&#34;&gt;evaluate&lt;/span&gt;(doc));
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;得到下图所示的输出&lt;/p&gt;</description>
    </item>
    <item>
      <title>详解XML文件的规范与使用</title>
      <link>https://yeecode.top/blog/55/</link>
      <pubDate>Sat, 16 Oct 2021 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/55/</guid>
      
      <description>&lt;p&gt;可扩展标记语言（Extensible Markup Language，简称XML）是一种标记语言。所谓的标记是指计算机所能理解的信息符号，通过标记可以实现软件开发者与计算机之间的信息沟通。我们常见的HTML便是一种标记语言，不过HTML语言中的标签（例如“&lt;code&gt;&amp;lt;h1&amp;gt; &amp;lt;/h1&amp;gt;&lt;/code&gt;”、“&lt;code&gt;&amp;lt;img \&amp;gt;&lt;/code&gt;”等）都是固定的，是不可以扩展的。XML则可以由开发人员自由扩展定义。&lt;/p&gt;
&lt;p&gt;XML可扩展的一个重要表现就是XML文档的结构是可以自由定义的。定义XML文档可以使用DTD（Document Type Definition，即：文档类型定义），也可以使用XML Schema。不过在介绍DTD和XML Schema之前，我们先了解下XML文档的结构。&lt;/p&gt;
&lt;p&gt;XML文档中包含众多的节点。节点分为以下几类：元素节点、属性节点、文本节点、文档节点等，在实际指代中，我们可以省略“节点”二字，也可以将以上各类统称“节点”。&lt;/p&gt;
&lt;p&gt;下面代码给出了一个XML文档。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;?&lt;/span&gt;xml version&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;1.0&amp;#34;&lt;/span&gt; encoding&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;UTF-8&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;?&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;members&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;user type&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;student&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;id&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;1&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/&lt;/span&gt;id&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;name&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;易哥&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/&lt;/span&gt;name&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;shcool&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;Sunny School&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/&lt;/span&gt;shcool&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/&lt;/span&gt;user&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;user type&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;student&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;id&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;2&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/&lt;/span&gt;id&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;name&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;莉莉&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/&lt;/span&gt;name&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;shcool&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;Garden School&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/&lt;/span&gt;shcool&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/&lt;/span&gt;user&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/&lt;/span&gt;members&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;文档第一行为XML声明，它声明了XML的版本是1.0，使用的编码是&lt;code&gt;UTF-8&lt;/code&gt;。XML中从一个标签开始（含）到一个标签结束（含）的部分叫作元素节点，例如从第一个“&lt;code&gt;&amp;lt;user&amp;gt;&lt;/code&gt;”到第一个“&lt;code&gt;&amp;lt;/user&amp;gt;&lt;/code&gt;”之间的部分就是一个&lt;code&gt;user&lt;/code&gt;元素节点。元素节点可以有属性节点，例如“&lt;code&gt;type=&amp;quot;student&amp;quot;&lt;/code&gt;”。元素节点可以包含其他元素节点，例如&lt;code&gt;user&lt;/code&gt;元素包含了&lt;code&gt;id&lt;/code&gt;、&lt;code&gt;name&lt;/code&gt;、&lt;code&gt;school&lt;/code&gt;这三个元素节点。元素节点中也可以有文本节点，例如第一个&lt;code&gt;name&lt;/code&gt;元素节点中就包含了文本节点，值为“易哥”。&lt;/p&gt;
&lt;p&gt;上述XML中，&lt;code&gt;members&lt;/code&gt;元素位于最顶层，因此是根元素。每一个XML文档都必须要有一个根元素。&lt;/p&gt;
&lt;p&gt;XML文档实际上表述了一棵树。下图展示了上述XML对应的结构树。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/55/pic/xmlTree_hu_e2ef641b5617e9a2.webp&#34; alt=&#34;结构树&#34; width=&#34;900&#34; height=&#34;335&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;在一个XML文档中，可以存在什么元素以及每个元素是怎样的，这些是由XML文档的定义文件来进行描述的，例如DTD（此类文件的后缀名为dtd）或者XML Schema（此类文件的后缀名为xsd）。&lt;/p&gt;
&lt;p&gt;以XML Schema文档为例，我们可以使用下面的代码来定义上述XML片段。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;xs:schema attributeFormDefault&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;unqualified&amp;#34;&lt;/span&gt; elementFormDefault&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;qualified&amp;#34;&lt;/span&gt; xmlns:xs&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;http://www.w3.org/2001/XMLSchema&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;xs:element name&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;members&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;xs:complexType&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;xs:sequence&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;xs:element maxOccurs&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;unbounded&amp;#34;&lt;/span&gt; name&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;user&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;xs:complexType&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;xs:sequence&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;xs:element name&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;id&amp;#34;&lt;/span&gt; type&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;xs:unsignedByte&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;xs:element name&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;name&amp;#34;&lt;/span&gt; type&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;xs:string&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;xs:element name&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;school&amp;#34;&lt;/span&gt; type&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;xs:string&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/&lt;/span&gt;xs:sequence&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;xs:attribute name&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;type&amp;#34;&lt;/span&gt; type&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;xs:string&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/&lt;/span&gt;xs:complexType&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/&lt;/span&gt;xs:element&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/&lt;/span&gt;xs:sequence&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/&lt;/span&gt;xs:complexType&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/&lt;/span&gt;xs:element&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/&lt;/span&gt;xs:schema&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;而使用DTD，则可以用下面的代码来定义。&lt;/p&gt;</description>
    </item>
    <item>
      <title>日志框架、日志等级、日志框架的正确使用方法</title>
      <link>https://yeecode.top/blog/54/</link>
      <pubDate>Sun, 19 Sep 2021 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/54/</guid>
      
      <description>&lt;h2 id=&#34;1-日志框架与日志等级&#34;&gt;1. 日志框架与日志等级&lt;/h2&gt;
&lt;p&gt;日志框架是一种在目标对象发生变化时将相关信息记录进日志文件的框架。这样，当目标对象出现问题或需要核查目标对象变动历史时，日志框架记录的日志文件便可以提供详实的资料。&lt;/p&gt;
&lt;p&gt;起初，Java的日志打印依靠软件开发者自行编辑输出语句将日志输出到文件流中。例如通过“&lt;code&gt;System.out.println&lt;/code&gt;”方法打印普通信息或通过“&lt;code&gt;System.err.println&lt;/code&gt;”方法打印错误信息。&lt;/p&gt;
&lt;p&gt;开发者自行编辑输出语句进行日志打印的方式非常烦琐，而且还会导致日志格式混乱，不利于日志分析软件的进一步处理。为了解决这些问题，产生了大量的日志框架。&lt;/p&gt;
&lt;p&gt;经过多年的发展，Java领域的日志框架已经非常丰富，有log4j、Logging、commons-logging、slf4j、logback等，它们为Java的日志打印工作提供了极大的便利。&lt;/p&gt;
&lt;p&gt;为了方便日志管理，日志框架大都对日志等级进行了划分。常见的日志等级划分方式如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;FATAL：致命等级的日志，指发生了严重的会导致应用程序退出的事件。&lt;/li&gt;
&lt;li&gt;ERROR：错误等级的日志，指发生了错误，但是不影响系统运行。&lt;/li&gt;
&lt;li&gt;WARN： 警告等级的日志，指发生了异常，可能是潜在的错误。&lt;/li&gt;
&lt;li&gt;INFO： 信息等级的日志，指一些在粗粒度级别上需要强调的应用程序运行信息。&lt;/li&gt;
&lt;li&gt;DEBUG：调试等级的日志，指一些细粒度的对于程序调试有帮助的信息。&lt;/li&gt;
&lt;li&gt;TRACE：跟踪等级的日志，指一些包含程序运行详细过程的信息。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;有了以上日志划分后，在打印日志时我们就可以定义日志的级别。而进行日志的输出时也可以根据日志等级进行输出，防止大量的日志信息混杂在一起。目前在很多集成开发环境中可以调节日志的显示级别，使得具有一定级别以上的日志才会显示出来，这样能够根据不同的使用情形进行日志的筛选。下图展示了划分了等级的日志在集成开发环境IntelliJ IDEA Community Edition中的展示效果。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/54/pic/008_hu_a56a2d304bd360f3.webp&#34; width=&#34;900&#34; height=&#34;291&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;2-日志框架的正确使用&#34;&gt;2. 日志框架的正确使用&lt;/h2&gt;
&lt;p&gt;在这一节，我们将通过MyBatis的源码学习如何正确地使用日志框架。&lt;/p&gt;
&lt;p&gt;MyBatis作为一个ORM框架，需要在进行参数解析、数据库操作、结果生成过程中打印日志，而它的日志打印并不是自己实现的。而是要对接到应用设置的日志框架上，例如log4j、Logging、commons-logging、slf4j、logback等，由它们打印日志。因此，MyBatis不仅要使用日志框架，而且要根据需要使用多种日志框架。&lt;/p&gt;
&lt;p&gt;MyBatis对接和调用日志框架的相关代码在其logging包中，该包中最重要的就是Log接口，它有11个实现，分布在&lt;code&gt;logging&lt;/code&gt;包的不同子包中。&lt;/p&gt;
&lt;p&gt;Log接口及其实现类的类图如下所示。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/54/pic/log_hu_9804587b0b2cb1ab.webp&#34; width=&#34;900&#34; height=&#34;349&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;我们先详细了解下Log接口中的方法。Log接口中定义了日志框架要实现的几个基本方法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;error：打印Error级别日志&lt;/li&gt;
&lt;li&gt;warn：打印Warn级别日志&lt;/li&gt;
&lt;li&gt;debug：打印Debug级别日志&lt;/li&gt;
&lt;li&gt;trace：打印Trace级别日志&lt;/li&gt;
&lt;li&gt;isDebugEnabled：判断打印Debug级别的日志的功能是否开启&lt;/li&gt;
&lt;li&gt;isTraceEnabled：判断打印Trace级别日志的功能是否开启&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;上述各个方法主要是实现不同级别日志的打印功能。然而，其中的&lt;code&gt;isDebugEnabled&lt;/code&gt;方法和&lt;code&gt;isTraceEnabled&lt;/code&gt;方法略显突兀，我们单独进行下说明。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;isDebugEnabled&lt;/code&gt;方法和&lt;code&gt;isTraceEnabled&lt;/code&gt;方法是从效率角度考虑而设计的。&lt;/p&gt;
&lt;p&gt;首先，&lt;code&gt;Debug&lt;/code&gt;和&lt;code&gt;Trace&lt;/code&gt;是两个级别比较低的日志，越是低级别的日志越有这样的特点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;很少开启：因为它们级别很低，多数时候该级别的信息不需要展示。&lt;/li&gt;
&lt;li&gt;输出频次高：低级别日志的触发门槛很低，这意味着一旦它们开启，往往会以非常高的频率输出日志信息。&lt;/li&gt;
&lt;li&gt;内容冗长：它们中往往包含非常丰富和细致的信息，因此信息内容往往十分冗长。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;假如存在下面所示的日志打印操作，在日志打印过程中调用了trace方法。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;trace(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Application is : &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; appName &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;; &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Class is : &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; className &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;; &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Function is : &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; funcitonName &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;. &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Params : &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; params &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;; &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Return is : &amp;#34;&lt;/span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; result  &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;.&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;以“&lt;code&gt;org.apache.commons.logging.impl.SimpleLog&lt;/code&gt;”下的&lt;code&gt;trace&lt;/code&gt;方法（可以通过&lt;code&gt;JakartaCommonsLoggingImpl&lt;/code&gt;实现类中的trace方法追踪到该方法）为例，其具体实现如下所示。&lt;/p&gt;</description>
    </item>
    <item>
      <title>适配器模式详解与使用示例</title>
      <link>https://yeecode.top/blog/53/</link>
      <pubDate>Sat, 21 Aug 2021 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/53/</guid>
      
      <description>&lt;h2 id=&#34;1-适配器思想&#34;&gt;1. 适配器思想&lt;/h2&gt;
&lt;p&gt;适配器模式（Adapter Pattern）是一种结构型模式，基于该模式设计的类能够在两个或者多个不兼容的类之间起到沟通桥梁的作用。&lt;/p&gt;
&lt;p&gt;转换插头就是一个适配器的典型例子。不同的转换插头能都够适配不同国家的插座标准，从而使得一个电器能在各个国家使用。&lt;/p&gt;
&lt;p&gt;适配器的思想在程序设计中非常常见，下面代码就体现了这种思想：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 方法一&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;K, V&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; Map&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;K, V&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;selectMap&lt;/span&gt;(String statement, String mapKey) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;selectMap&lt;/span&gt;(statement, &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;, mapKey, RowBounds.&lt;span style=&#34;color:#a6e22e&#34;&gt;DEFAULT&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 方法二&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;K, V&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; Map&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;K, V&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;selectMap&lt;/span&gt;(String statement, Object parameter, String mapKey) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;selectMap&lt;/span&gt;(statement, parameter, mapKey, RowBounds.&lt;span style=&#34;color:#a6e22e&#34;&gt;DEFAULT&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 方法三&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;K, V&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; Map&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;K, V&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;selectMap&lt;/span&gt;(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;final&lt;/span&gt; List&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;?&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;extends&lt;/span&gt; V&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; list &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; selectList(statement, parameter, rowBounds);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;final&lt;/span&gt; DefaultMapResultHandler&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;K, V&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; mapResultHandler &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; DefaultMapResultHandler&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;gt;&lt;/span&gt;(mapKey,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          configuration.&lt;span style=&#34;color:#a6e22e&#34;&gt;getObjectFactory&lt;/span&gt;(), configuration.&lt;span style=&#34;color:#a6e22e&#34;&gt;getObjectWrapperFactory&lt;/span&gt;(), configuration.&lt;span style=&#34;color:#a6e22e&#34;&gt;getReflectorFactory&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;final&lt;/span&gt; DefaultResultContext&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;V&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; context &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; DefaultResultContext&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;gt;&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (V o : list) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    context.&lt;span style=&#34;color:#a6e22e&#34;&gt;nextResultObject&lt;/span&gt;(o);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    mapResultHandler.&lt;span style=&#34;color:#a6e22e&#34;&gt;handleResult&lt;/span&gt;(context);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; mapResultHandler.&lt;span style=&#34;color:#a6e22e&#34;&gt;getMappedResults&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;上述代码中，方法三是核心方法，它需要四个输入参数。而有些场景下，调用方只能提供三个参数或者两个参数。为了使得只有三个参数或者两个参数的调用方能够正常地调用核心方法，方法一和方法二充当了方法适配器的作用。这两个适配器通过为未知参数设置默认值的方式，搭建起了调用方和核心方法之间的桥梁。&lt;/p&gt;</description>
    </item>
    <item>
      <title>一文搞清楚模板模式的定义及其在MyBatis中的使用</title>
      <link>https://yeecode.top/blog/52/</link>
      <pubDate>Sat, 17 Jul 2021 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/52/</guid>
      
      <description>&lt;p&gt;说起模板大家应该都熟悉。一般情况下，模板中规定了大体的框架，只留下一些细节供使用者来修改和完善。使用同一模板做出的不同产品都具有一致的框架。&lt;/p&gt;
&lt;p&gt;设计模式中的模板模式与上述模板的概念相同。在模板模式中，需要使用一个抽象类定义一套操作的整体步骤（即，模板），而抽象类的子类则完成每个步骤的具体实现。这样，抽象类的不同子类总是遵循了同样的一套模板。&lt;/p&gt;
&lt;p&gt;例如我们定义一套打扫卫生的模板，如下面代码所示。它为所有的打扫卫生工作定义了四个大的步骤：准备（prepare）、实施（implement）、善后（windup）、汇报（report）。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;abstract&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Cleaning&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;clean&lt;/span&gt;(){
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        prepare();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        implement();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        windup();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        report();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;abstract&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;prepare&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;abstract&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;implement&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;abstract&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;windup&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;report&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;告诉别人已经打扫完成。&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这样，我们可以基于该模板完成一些具体的打扫卫生工作。下面分别给出擦玻璃和擦黑板的实现：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;WipeGlass&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;extends&lt;/span&gt; Cleaning{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;@Override&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;prepare&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;找到抹布。&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;浸湿和清洗抹布。&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;@Override&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;implement&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;擦玻璃。&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;@Override&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;windup&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;清理窗台。&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;WipeBlackboard&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;extends&lt;/span&gt; Cleaning {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;@Override&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;prepare&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;找到黑板檫。&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;@Override&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;implement&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;用力擦黑板。&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;@Override&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;windup&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;清理粉笔屑。&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可以看到，虽然具体的行为不同，但是CleanGlass和WipeBlackboard两者遵循的模板是一样的，都是由Cleaning类给定的。这就是模板模式的作用，即确定了一套操作的框架，而子类只需要在此框架的基础上定义具体的实现。&lt;/p&gt;</description>
    </item>
    <item>
      <title>通过源码分析MyBatis中Param注解的定义与解析过程</title>
      <link>https://yeecode.top/blog/51/</link>
      <pubDate>Sat, 19 Jun 2021 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/51/</guid>
      
      <description>&lt;p&gt;前面的文章中，我们已经对Java注解的创建、使用进行了介绍。接下来，我们将介绍如何在程序中解析注解，从而获取注解中承载的信息。&lt;/p&gt;
&lt;p&gt;我们以MyBatis中最常用的&lt;code&gt;Param&lt;/code&gt;注解为例，介绍下它如何被解析然后发挥作用。（均参考自书籍《通用源码阅读指导书——MyBatis源码详解》）&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Param&lt;/code&gt;注解带注释的源码如下面代码所示。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@Documented&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;// 表明该注解会保留在API文档中&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@Retention&lt;/span&gt;(RetentionPolicy.&lt;span style=&#34;color:#a6e22e&#34;&gt;RUNTIME&lt;/span&gt;) &lt;span style=&#34;color:#75715e&#34;&gt;// 表明注解会保留到运行阶段&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@Target&lt;/span&gt;(ElementType.&lt;span style=&#34;color:#a6e22e&#34;&gt;PARAMETER&lt;/span&gt;) &lt;span style=&#34;color:#75715e&#34;&gt;// 表明注解可以应用在参数上&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;@interface&lt;/span&gt; Param {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  String &lt;span style=&#34;color:#a6e22e&#34;&gt;value&lt;/span&gt;(); &lt;span style=&#34;color:#75715e&#34;&gt;// 整个注解只有一个属性，名为value&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;使用时，我们只需要在相关参数上使用该注解对参数进行命名，便可以在映射文件中引用该参数。例如下面的示例中，我们使用该注解将&lt;code&gt;userId&lt;/code&gt;属性命名为&lt;code&gt;id&lt;/code&gt;。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;User &lt;span style=&#34;color:#a6e22e&#34;&gt;queryById&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;@Param&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;id&amp;#34;&lt;/span&gt;) Integer userId);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这样，我们便可以在Mapper引用&lt;code&gt;id&lt;/code&gt;所指代的变量，如下所示。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;select id&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;queryById&amp;#34;&lt;/span&gt; resultType&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;User&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    SELECT &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    FROM user
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    WHERE &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;id&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;{id}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/&lt;/span&gt;select&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接下来我们着重分析该注解是如何生效的。&lt;/p&gt;
&lt;p&gt;借助于开发工具，我们在&lt;code&gt;ParamNameResolver&lt;/code&gt;的构造方法中找到了MyBatis对引&lt;code&gt;Param&lt;/code&gt;的引用。相关源码如下所示。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/**
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; * 参数名解析器的构造方法
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; * @param config 配置信息
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; * @param method 要被分析的方法
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ParamNameResolver&lt;/span&gt;(Configuration config, Method method) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// 获取参数类型列表&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;final&lt;/span&gt; Class&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;?&amp;gt;[]&lt;/span&gt; paramTypes &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; method.&lt;span style=&#34;color:#a6e22e&#34;&gt;getParameterTypes&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// 准备存取所有参数的注解，是二维数组&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;final&lt;/span&gt; Annotation&lt;span style=&#34;color:#f92672&#34;&gt;[][]&lt;/span&gt; paramAnnotations &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; method.&lt;span style=&#34;color:#a6e22e&#34;&gt;getParameterAnnotations&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;final&lt;/span&gt; SortedMap&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;Integer, String&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; map &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; TreeMap&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;gt;&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; paramCount &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; paramAnnotations.&lt;span style=&#34;color:#a6e22e&#34;&gt;length&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// 循环处理各个参数&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; paramIndex &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; 0; paramIndex &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; paramCount; paramIndex&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (isSpecialParameter(paramTypes&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;paramIndex&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;// 跳过特别的参数&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;continue&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 参数名称&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    String name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (Annotation annotation : paramAnnotations&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;paramIndex&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;// 找出参数的注解&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (annotation &lt;span style=&#34;color:#66d9ef&#34;&gt;instanceof&lt;/span&gt; Param) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 如果注解是Param&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        hasParamAnnotation &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 那就以Param中值作为参数名&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ((Param) annotation).&lt;span style=&#34;color:#a6e22e&#34;&gt;value&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (name &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;// 否则，保留参数的原有名称&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (config.&lt;span style=&#34;color:#a6e22e&#34;&gt;isUseActualParamName&lt;/span&gt;()) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; getActualParamName(method, paramIndex);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (name &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 参数名称取不到，则按照参数index命名&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; String.&lt;span style=&#34;color:#a6e22e&#34;&gt;valueOf&lt;/span&gt;(map.&lt;span style=&#34;color:#a6e22e&#34;&gt;size&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    map.&lt;span style=&#34;color:#a6e22e&#34;&gt;put&lt;/span&gt;(paramIndex, name);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  names &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Collections.&lt;span style=&#34;color:#a6e22e&#34;&gt;unmodifiableSortedMap&lt;/span&gt;(map);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在上述代码中，首先使用语句“&lt;code&gt;final Annotation[][] paramAnnotations = method.getParameterAnnotations()&lt;/code&gt;”得到了目标方法的所有参数的注解。假设我们定义如下面代码所示的方法：&lt;/p&gt;</description>
    </item>
    <item>
      <title>Java注解的原理和使用详解</title>
      <link>https://yeecode.top/blog/50/</link>
      <pubDate>Sat, 15 May 2021 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/50/</guid>
      
      <description>&lt;p&gt;Java注解是一种标注。Java中的类、方法、变量、参数、包等均可以被注解标注从而被添加额外的信息。相比于直接修改代码的硬编码方式，基于注解的这种松耦合的信息添加方式受到了广泛的欢迎。&lt;/p&gt;
&lt;p&gt;本文，我们将对注解的声明方式、注解的原理进行详细的介绍。&lt;/p&gt;
&lt;h2 id=&#34;1-元注解&#34;&gt;1. 元注解&lt;/h2&gt;
&lt;p&gt;随便打开一个注解类，我们会发现它们中也包含注解。例如下面代码所示的@Param注解（来自MyBatis源码）中就存在&lt;code&gt;@Documented &lt;/code&gt;、&lt;code&gt;@Retention&lt;/code&gt;、&lt;code&gt;@Target&lt;/code&gt;等注解。这些用来注解其他注解的注解，称为元注解。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@Documented&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;// 表明该注解会保留在API文档中&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@Retention&lt;/span&gt;(RetentionPolicy.&lt;span style=&#34;color:#a6e22e&#34;&gt;RUNTIME&lt;/span&gt;) &lt;span style=&#34;color:#75715e&#34;&gt;// 表明注解会保留到运行阶段&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@Target&lt;/span&gt;(ElementType.&lt;span style=&#34;color:#a6e22e&#34;&gt;PARAMETER&lt;/span&gt;) &lt;span style=&#34;color:#75715e&#34;&gt;// 表明注解可以应用在参数上&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;@interface&lt;/span&gt; Param {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  String &lt;span style=&#34;color:#a6e22e&#34;&gt;value&lt;/span&gt;(); &lt;span style=&#34;color:#75715e&#34;&gt;// 整个注解只有一个属性，名为value&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;元注解一共有五个，分别是：&lt;code&gt;@Target&lt;/code&gt;、&lt;code&gt;@Retention&lt;/code&gt;、&lt;code&gt;@Documented&lt;/code&gt;、&lt;code&gt;@Inherited&lt;/code&gt;、&lt;code&gt;@Repeatable&lt;/code&gt;，我们分别进行介绍。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@Target&lt;/code&gt;注解用来声明注解可以用在什么地方，它的值需要从枚举类&lt;code&gt;ElementType&lt;/code&gt;中选取。&lt;code&gt;ElementType&lt;/code&gt;的枚举值及其含义如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;TYPE：类、接口、注解、枚举&lt;/li&gt;
&lt;li&gt;FIELD：字段&lt;/li&gt;
&lt;li&gt;METHOD：方法&lt;/li&gt;
&lt;li&gt;PARAMETER：参数&lt;/li&gt;
&lt;li&gt;CONSTRUCTOR：构造方法&lt;/li&gt;
&lt;li&gt;LOCAL_VARIABLE：本地变量&lt;/li&gt;
&lt;li&gt;ANNOTATION_TYPE：注解&lt;/li&gt;
&lt;li&gt;PACKAGE：包&lt;/li&gt;
&lt;li&gt;TYPE_PARAMETER：类型参数&lt;/li&gt;
&lt;li&gt;TYPE_USE：类型使用&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;例如我们要声明一个注解只能声明在参数上，则可以如下所示进行设置。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@Target&lt;/span&gt;(ElementType.&lt;span style=&#34;color:#a6e22e&#34;&gt;PARAMETER&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;元注解也有Target值，而且该值都是ElementType.ANNOTATION_TYPE，表明它们只能声明在注解上。&lt;/p&gt;
&lt;p&gt;当然，以上枚举值也可以选择多个，例如下面代码声明注解可以用在字段和方法上。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@Target&lt;/span&gt;(value&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{ElementType.&lt;span style=&#34;color:#a6e22e&#34;&gt;FIELD&lt;/span&gt;, ElementType.&lt;span style=&#34;color:#a6e22e&#34;&gt;METHOD&lt;/span&gt;})
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;@Retention&lt;/code&gt;注解用来声明注解的生命周期，即表明注解会被保留到哪一阶段。它的值需要从枚举类RetentionPolicy选取。RetentionPolicy的枚举值如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SOURCE：保留到源代码阶段。这一类注解一般是留给编译器使用，在编译时会被擦除。&lt;/li&gt;
&lt;li&gt;CLASS：保留到类文件阶段。这是默认的生命周期，注解会保留到类文件阶段，但是JVM运行时不包含这些信息.&lt;/li&gt;
&lt;li&gt;RUNTIME：保留到JVM运行阶段。如果我们想在程序运行时获得注解，则需要保留在这一阶段。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;@Documented&lt;/code&gt;不需要设置具体的值。如果一个注解被&lt;code&gt;@Documented&lt;/code&gt;标注，则该注解会被生成到javadoc中。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@Inherited&lt;/code&gt;不需要设置具体的值。如果一个注解被&lt;code&gt;@Inherited&lt;/code&gt;标注，表明允许子类继承父类的该注解（可以从父类继承该注解，但是不能从接口继承该注解）。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@Repeatable&lt;/code&gt;是JDK 8中新加入的。如果一个注解被&lt;code&gt;@Repeatable&lt;/code&gt;标注，则该注解可以在同一个地方被重复使用多次。用&lt;code&gt;@Repeatable&lt;/code&gt;来修饰注解时需要指明一个接受重复注解的容器。&lt;/p&gt;
&lt;p&gt;（以上均参考自书籍《通用源码阅读指导书——MyBatis源码详解》）&lt;/p&gt;
&lt;h2 id=&#34;2-自定义注解&#34;&gt;2. 自定义注解&lt;/h2&gt;
&lt;p&gt;自定义一个注解非常简单，需要使用元注解进行一些声明，然后就可以定义注解中的属性。下面代码给出了一个自定义注解的示例。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@Target&lt;/span&gt;(value&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;ElementType.&lt;span style=&#34;color:#a6e22e&#34;&gt;METHOD&lt;/span&gt;) &lt;span style=&#34;color:#75715e&#34;&gt;// 表明该注解可以标注到方法上&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@Retention&lt;/span&gt;(RetentionPolicy.&lt;span style=&#34;color:#a6e22e&#34;&gt;RUNTIME&lt;/span&gt;) &lt;span style=&#34;color:#75715e&#34;&gt;// 表明该注解会保留到运行时&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@Documented&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;// 表明该注解会被生成到javadoc中&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;@interface&lt;/span&gt; Yeecode { &lt;span style=&#34;color:#75715e&#34;&gt;// 自定义注解的名称为Yeecode&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    String &lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;(); &lt;span style=&#34;color:#75715e&#34;&gt;// 一个属性&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    String &lt;span style=&#34;color:#a6e22e&#34;&gt;value&lt;/span&gt;() &lt;span style=&#34;color:#66d9ef&#34;&gt;default&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;123&amp;#34;&lt;/span&gt;; &lt;span style=&#34;color:#75715e&#34;&gt;// 一个指定了默认值的属性&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;age&lt;/span&gt;() &lt;span style=&#34;color:#66d9ef&#34;&gt;default&lt;/span&gt; 12;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    SizeUnits &lt;span style=&#34;color:#a6e22e&#34;&gt;height&lt;/span&gt;() &lt;span style=&#34;color:#66d9ef&#34;&gt;default&lt;/span&gt; SizeUnits.&lt;span style=&#34;color:#a6e22e&#34;&gt;CM&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Class&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;?&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;clazz&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;[]&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;index&lt;/span&gt;() &lt;span style=&#34;color:#66d9ef&#34;&gt;default&lt;/span&gt; {0,1};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在定义注解的属性时，是使用方法的形式来定义的，即属性名就是方法名。每个属性都可以定义默认值。如果不为属性指定默认值，则在使用时必须赋值。&lt;/p&gt;</description>
    </item>
    <item>
      <title>Java泛型以及MyBatis泛型解析器的实现源码详解</title>
      <link>https://yeecode.top/blog/49/</link>
      <pubDate>Sat, 17 Apr 2021 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/49/</guid>
      
      <description>&lt;p&gt;在反射中，我们经常会遇到Type接口，它代表一个类型，位于“&lt;code&gt;java.lang.reflect&lt;/code&gt;”包内。该接口的源码如下所示，接口内只定义了一个方法。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;interface&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Type&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/**
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;     * 获取类型的名称
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;     */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;default&lt;/span&gt; String &lt;span style=&#34;color:#a6e22e&#34;&gt;getTypeName&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; toString();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Type接口及其子类的类图如图所示。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/49/pic/type_hu_620cbffcb05758e7.webp&#34; alt=&#34;Type接口及其子类类图&#34; width=&#34;900&#34; height=&#34;209&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;我们对Type接口的子类分别进行介绍。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Class类：它代表运行的Java程序中的类和接口，枚举类型（属于类）、注解（属于接口）也都是Class类的子类。&lt;/li&gt;
&lt;li&gt;WildcardType接口：它代表通配符表达式。例如“?”、“? extends Number”、“? super Integer”都是通配符表达式。&lt;/li&gt;
&lt;li&gt;TypeVariable接口：它是类型变量的父接口。例如“Map&lt;code&gt;&amp;lt;K，V&amp;gt;&lt;/code&gt;”中的“K”、“V”就是类型变量。&lt;/li&gt;
&lt;li&gt;ParameterizedType接口：它代表参数化的类型。例如“Collection&lt;code&gt;&amp;lt;String&amp;gt;&lt;/code&gt;”就是参数化的类型。&lt;/li&gt;
&lt;li&gt;GenericArrayType接口：它代表包含ParameterizedType或者TypeVariable元素的列表。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在学习这些子类时，没有必要死记硬背。只要有个大体的印象，遇到时直接通过开发工具跳转到源代码处查看定义即可，下图展示了WildcardType接口上的原生注释。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/49/pic/029_hu_6e600206e65a8a2c.webp&#34; alt=&#34;WildcardType接口上的原生注释&#34; width=&#34;768&#34; height=&#34;202&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;遇到不了解的类、方法时，直接跳转到类、方法的定义处查看其原生注释是学习Java编程、阅读项目源码时非常有效的方法。（均参考自书籍《通用源码阅读指导书——MyBatis源码详解》）&lt;/p&gt;
&lt;p&gt;但是，泛型的解析并不简单，因为它指代的类是不确定的，是一个范围。接下来，我们通过MyBatis的源码学习如何进行泛型的解析。&lt;/p&gt;
&lt;p&gt;MyBatis的泛型解析由TypeParameterResolver实现，我们称它为泛型参数解析器。&lt;/p&gt;
&lt;p&gt;在阅读它的源代码之前我们先弄清一个问题：它的功能是什么？很多情况下，弄清一个类的功能对阅读它的源代码十分必要。&lt;/p&gt;
&lt;p&gt;试想我们有User和Student两个类，分别如下所示。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;User&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;T&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; List&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;T&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;getInfo&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Student&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;extends&lt;/span&gt; User&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;Number&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;请问：Student类中的getInfo方法（继承自父类User）的输出参数类型是什么？&lt;/p&gt;
&lt;p&gt;答案很简单，是“&lt;code&gt;List&amp;lt;Number&amp;gt;&lt;/code&gt;”。但是这个得出这个答案的过程却涉及到User和Student两个类。首先通过User类确定getInfo方法的输出结果是“&lt;code&gt;List&amp;lt;T&amp;gt;&lt;/code&gt;”，然后通过Student类得知“T”被设置为“Number”。因此，Student类中的getInfo方法的输出参数是“&lt;code&gt;List&amp;lt;Number&amp;gt;&lt;/code&gt;”。&lt;/p&gt;
&lt;p&gt;TypeParameterResolver类的功能就是完成上述分析过程，帮助MyBatis推断出属性、返回值、入参中泛型的具体类型。例如通过下面所示的调用，TypeParameterResolver便分析出User类中的getInfo方法的输出参数是“&lt;code&gt;List&amp;lt;Object&amp;gt;&lt;/code&gt;”，Student类中的getInfo方法的输出参数是“&lt;code&gt;List&amp;lt;Number&amp;gt;&lt;/code&gt;”。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 使用TypeParameterResolver分析User类中getInfo方法输出结果的具体类型&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Type type1 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; TypeParameterResolver.&lt;span style=&#34;color:#a6e22e&#34;&gt;resolveReturnType&lt;/span&gt;(User.&lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;getMethod&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;getInfo&amp;#34;&lt;/span&gt;), User.&lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;User类中getInfo方法的输出结果类型 :\n&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; type1);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 使用TypeParameterResolver分析Student类中getInfo方法输出结果的具体类型&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Type type2 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; TypeParameterResolver.&lt;span style=&#34;color:#a6e22e&#34;&gt;resolveReturnType&lt;/span&gt;(User.&lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;getMethod&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;getInfo&amp;#34;&lt;/span&gt;), Student.&lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Student类中getInfo方法的输出结果类型 :\n&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; type2);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;TypeParameterResolver类分析出的结果如图所示。&lt;/p&gt;</description>
    </item>
    <item>
      <title>强大的反射功能详解与应用源码解析</title>
      <link>https://yeecode.top/blog/48/</link>
      <pubDate>Mon, 15 Mar 2021 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/48/</guid>
      
      <description>&lt;p&gt;反射是Java提供的一个灵活又强大的功能。通过Java反射，能够在类的运行过程中知道这个类有哪些属性和方法，还可以修改属性、调用方法、建立类的实例。&lt;/p&gt;
&lt;p&gt;说起来可能有些不好理解。假设我们有一个代码所示的类。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;User&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; Integer id;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; String name;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 省略构造方法与get、set方法&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;那我们在编程时可以使用下面的代码为对象赋值。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;User user &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; User();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;user.&lt;span style=&#34;color:#a6e22e&#34;&gt;setName&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;xiaoming&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;我们能调用User实例的setName方法，是因为编译器通过分析源码知道User中确实存在一个setName方法。那如果我们要实现一个对象比较功能，比较两个User对象的属性有什么不同。则可以通过下面的代码实现。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/**
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; * 比较两个User对象的属性不同
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; * @param oldUser 第一个User对象
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; * @param newUser 第二个User对象
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; * @return 两个User对象的属性变化
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; Map&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;String,String&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;diffUser&lt;/span&gt;(User oldUser, User newUser) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Map&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;String,String&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; diffMap &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; HashMap&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;gt;&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; ((oldUser.&lt;span style=&#34;color:#a6e22e&#34;&gt;getId&lt;/span&gt;() &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; newUser.&lt;span style=&#34;color:#a6e22e&#34;&gt;getId&lt;/span&gt;() &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            (oldUser.&lt;span style=&#34;color:#a6e22e&#34;&gt;getId&lt;/span&gt;()&lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;oldUser.&lt;span style=&#34;color:#a6e22e&#34;&gt;getId&lt;/span&gt;().&lt;span style=&#34;color:#a6e22e&#34;&gt;equals&lt;/span&gt;(newUser.&lt;span style=&#34;color:#a6e22e&#34;&gt;getId&lt;/span&gt;())))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        diffMap.&lt;span style=&#34;color:#a6e22e&#34;&gt;put&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;id&amp;#34;&lt;/span&gt;,&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;from &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; oldUser.&lt;span style=&#34;color:#a6e22e&#34;&gt;getId&lt;/span&gt;() &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34; to &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; newUser.&lt;span style=&#34;color:#a6e22e&#34;&gt;getId&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; ((oldUser.&lt;span style=&#34;color:#a6e22e&#34;&gt;getName&lt;/span&gt;() &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; newUser.&lt;span style=&#34;color:#a6e22e&#34;&gt;getName&lt;/span&gt;() &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            (oldUser.&lt;span style=&#34;color:#a6e22e&#34;&gt;getName&lt;/span&gt;()&lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;oldUser.&lt;span style=&#34;color:#a6e22e&#34;&gt;getName&lt;/span&gt;().&lt;span style=&#34;color:#a6e22e&#34;&gt;equals&lt;/span&gt;(newUser.&lt;span style=&#34;color:#a6e22e&#34;&gt;getName&lt;/span&gt;())))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        diffMap.&lt;span style=&#34;color:#a6e22e&#34;&gt;put&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;name&amp;#34;&lt;/span&gt;,&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;from &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; oldUser.&lt;span style=&#34;color:#a6e22e&#34;&gt;getName&lt;/span&gt;() &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34; to &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; newUser.&lt;span style=&#34;color:#a6e22e&#34;&gt;getName&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; diffMap;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在上面代码所示的diffUser方法中，我们在编码时就知道User对象有哪些属性、方法，然后轮番比较即可。因此，该diffUser方法只能比较两个User对象的不同，而无法比较其他类的对象。&lt;/p&gt;</description>
    </item>
    <item>
      <title>灵活又强大的装饰器模式实例与应用源码解析</title>
      <link>https://yeecode.top/blog/47/</link>
      <pubDate>Sun, 21 Feb 2021 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/47/</guid>
      
      <description>&lt;p&gt;装饰器模式，又称为包装模式，是一种结构型模式。这种设计模式是指能够在一个类的基础上增加一个装饰类（也可以叫包装类），并在装饰类中增加一些新的特性和功能。这样，通过对原有类的包装，就可以在不改变原有类的情况下为原有类增加更多的功能。&lt;/p&gt;
&lt;p&gt;例如我们定义Phone接口，它规定了发送和接收语音的抽象方法。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;interface&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Phone&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    String &lt;span style=&#34;color:#a6e22e&#34;&gt;callIn&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Boolean &lt;span style=&#34;color:#a6e22e&#34;&gt;callOut&lt;/span&gt;(String info);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;然后定义一个类TelePhone，实现了Phone接口，能够实现打电话的功能。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;TelePhone&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;implements&lt;/span&gt; Phone {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;@Override&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; String &lt;span style=&#34;color:#a6e22e&#34;&gt;callIn&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;接受语音……&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;get info&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;@Override&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Boolean &lt;span style=&#34;color:#a6e22e&#34;&gt;callOut&lt;/span&gt;(String info) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;发送语音：&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; info);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;那现在我们要创建一个装饰器，在不改变原有TelePhone的基础上，实现通话录音功能。我们的装饰器类的源码如下所示。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;PhoneRecordDecorator&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;implements&lt;/span&gt; Phone {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; Phone decoratedPhone;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;PhoneRecordDecorator&lt;/span&gt;(Phone decoratedPhone) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;decoratedPhone&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; decoratedPhone;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;@Override&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; String &lt;span style=&#34;color:#a6e22e&#34;&gt;callIn&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;启动录音……&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        String info &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; decoratedPhone.&lt;span style=&#34;color:#a6e22e&#34;&gt;callIn&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;结束录音并保存录音文件。&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; info;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;@Override&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Boolean &lt;span style=&#34;color:#a6e22e&#34;&gt;callOut&lt;/span&gt;(String info) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;启动录音……&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Boolean result &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; decoratedPhone.&lt;span style=&#34;color:#a6e22e&#34;&gt;callOut&lt;/span&gt;(info);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;结束录音并保存录音文件。&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; result;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这样，经过我们的PhoneRecordDecorator包装过的Phone就具有了通话录音的能力，如下所示。&lt;/p&gt;</description>
    </item>
    <item>
      <title>全文检索的原理</title>
      <link>https://yeecode.top/blog/46/</link>
      <pubDate>Sat, 09 Jan 2021 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/46/</guid>
      
      <description>&lt;p&gt;本文将介绍全文检索的原理，也是Lucene工作原理。而开源的搜索引擎Solr和Elasticsearch也都是基于Lucene实现的。因此本文介绍的原理是以上这些软件的基础。&lt;/p&gt;
&lt;p&gt;另外，本文并非原创，是参考网络上的资料整理而来，删减了一些冗余的段落、整理了错乱的格式等。本文原始出处难以考证。&lt;/p&gt;
&lt;h2 id=&#34;1-全文检索概述&#34;&gt;1. 全文检索概述&lt;/h2&gt;
&lt;p&gt;数据总体分为两种：结构化数据和非结构化数据。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;结构化数据：指具有固定格式或有限长度的数据，如数据库，元数据等。&lt;/li&gt;
&lt;li&gt;非结构化数据：指不定长或无固定格式的数据，如邮件，word文档等。非结构化数据又一种叫法叫全文数据。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当然有的地方还会提到第三种，半结构化数据，如XML，HTML等，当根据需要可按结构化数据来处理，也可抽取出纯文本按非结构化数据来处理。&lt;/p&gt;
&lt;p&gt;按照数据的分类，搜索也分为两种：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对结构化数据的搜索：如对数据库的搜索，用SQL语句。再如对元数据的搜索，如利用windows搜索对文件名，类型，修改时间进行搜索等。&lt;/li&gt;
&lt;li&gt;对非结构化数据的搜索：如利用windows的搜索也可以搜索文件内容，Linux下的grep命令，再如用Google和百度可以搜索大量内容数据。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我们说的全文检索就是对非结构化数据的搜索。主要分为两种方式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;顺序扫描法(Serial Scanning)：从头到尾对全文数据进行扫描。&lt;/li&gt;
&lt;li&gt;全文检索(Full-text Search)：先建立索引，然后基于索引进行搜索。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这里就引出了全文检索。&lt;strong&gt;全文检索是基于索引，对非结构化数据进行的搜索。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;下面一图展现了全文搜索的整体过程。全文检索大体分两个过程，索引创建(Indexing)和搜索索引(Search)。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;索引创建：将现实世界中所有的结构化和非结构化数据提取信息，创建索引的过程。&lt;/li&gt;
&lt;li&gt;搜索索引：就是得到用户的查询请求，搜索创建的索引，然后返回结果的过程。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/46/pic/001_hu_89aae74c8d824c63.webp&#34; alt=&#34;全文检索&#34; width=&#34;544&#34; height=&#34;479&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;于是全文检索就存在三个重要问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;索引里面究竟存些什么？(Index)&lt;/li&gt;
&lt;li&gt;如何创建索引？(Indexing)&lt;/li&gt;
&lt;li&gt;如何对索引进行搜索？(Search)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;下面依次进行介绍。&lt;/p&gt;
&lt;h2 id=&#34;2-索引里存在什么&#34;&gt;2. 索引里存在什么&lt;/h2&gt;
&lt;p&gt;假设我的文档集合里面有100篇文档，为了方便表示，我们为文档编号从1到100，得到下面的结构&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/46/pic/002_hu_83cfbf22a4bdcbff.webp&#34; alt=&#34;图片&#34; width=&#34;544&#34; height=&#34;153&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;左边保存的是一系列字符串，称为词典。&lt;/p&gt;
&lt;p&gt;每个字符串都指向包含此字符串的文档(Document)链表，此文档链表称为倒排表(Posting List)。&lt;/p&gt;
&lt;p&gt;有了索引，便使保存的信息和要搜索的信息一致，可以大大加快搜索的速度。&lt;/p&gt;
&lt;p&gt;比如说，我们要寻找既包含字符串“lucene”又包含字符串“solr”的文档，我们只需要以下几步：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;取出包含字符串“lucene”的文档链表。&lt;/li&gt;
&lt;li&gt;取出包含字符串“solr”的文档链表。&lt;/li&gt;
&lt;li&gt;通过合并链表，找出既包含“lucene”又包含“solr”的文件。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/46/pic/003_hu_1fb3f08698bdb73c.webp&#34; alt=&#34;图片&#34; width=&#34;544&#34; height=&#34;55&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;看到这个地方，有人可能会说，全文检索的确加快了搜索的速度，但是多了索引的过程，两者加起来不一定比顺序扫描快多少。的确，加上索引的过程，全文检索不一定比顺序扫描快，尤其是在数据量小的时候更是如此。而对一个很大量的数据创建索引也是一个很慢的过程。&lt;/p&gt;
&lt;p&gt;然而两者还是有区别的，顺序扫描是每次都要扫描，而创建索引的过程仅仅需要一次，以后便是一劳永逸的了，每次搜索，创建索引的过程不必经过，仅仅搜索创建好的索引就可以了。&lt;/p&gt;
&lt;p&gt;这也是全文搜索相对于顺序扫描的优势之一：一次索引，多次使用。&lt;/p&gt;
&lt;h2 id=&#34;3-如何创建索引&#34;&gt;3. 如何创建索引&lt;/h2&gt;
&lt;p&gt;全文检索的索引创建过程一般有以下几步：&lt;/p&gt;
&lt;h2 id=&#34;31-一些要索引的原文档document&#34;&gt;3.1 一些要索引的原文档(Document)。&lt;/h2&gt;
&lt;p&gt;为了方便说明索引创建过程，这里特意用两个文件为例：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;文件一：Students should be allowed to go out with their friends, but not allowed to drink beer.
文件二：My friend Jerry went to school to see his students but found them drunk which is not allowed.
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;32-将原文档传给分次组件tokenizer&#34;&gt;3.2 将原文档传给分次组件(Tokenizer)。&lt;/h2&gt;
&lt;p&gt;分词组件(Tokenizer)会做以下几件事情(此过程称为Tokenize)：&lt;/p&gt;</description>
    </item>
    <item>
      <title>写在Flash落幕时</title>
      <link>https://yeecode.top/blog/83/</link>
      <pubDate>Sat, 09 Jan 2021 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/83/</guid>
      
      <description>&lt;p&gt;大家肯定也从各种途径听说过，Adobe正式停止了对Flash的支持。&lt;/p&gt;
&lt;p&gt;Adobe已向用户发出多轮提醒，以通知用户Adobe在2020年12月31日正式停止对Flash Player的更新支持。而且，即便用户不手动删除Flash Player，Flash内容也会在2021年1月份停止工作。&lt;/p&gt;
&lt;p&gt;Flash终于还是落幕了。&lt;/p&gt;
&lt;p&gt;其实关于Flash要被淘汰的消息早已人尽皆知，而且Flash本身也式微多年。&lt;/p&gt;
&lt;p&gt;可当我真正听到这个消息时，还是有些震惊和不舍。&lt;/p&gt;
&lt;p&gt;一切似乎有点太快。&lt;/p&gt;
&lt;p&gt;毕竟上古神器Visual Basic虽停止了演化，但微软还是在其稳定性和兼容性上做工作。&lt;/p&gt;
&lt;p&gt;可对Flash而言，就有点余音尚存却戛然而止的意思。&lt;/p&gt;
&lt;p&gt;Flash中的ActionScript是我学习的第一门编程语言。正是通过Flash和ActionScript，我才第一次感受到什么是编程。&lt;/p&gt;
&lt;p&gt;所以，一直想写篇文章记录下，且与一片细数弊端和欢庆落幕的主流论调并不相恰。&lt;/p&gt;
&lt;p&gt;不过确实很懒，也亏了早上公交车堵，才在上班路上码了下来。&lt;/p&gt;
&lt;p&gt;自学Flash时是在高中。而且不得不说，Flash确实是一门极好的编程入门环境，直到现在我也这么认为：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;它具有图层和帧，且与编程语言无缝对接。配合图层和帧，你可以快速创建出界面优美、交互良好的界面。相比而言C语言那黑框框简直弱爆了，而VB那上古界面也复杂和逊色太多。&lt;/li&gt;
&lt;li&gt;它具有强大的本地操作能力。你可以直接通过语句操作本地文件，这点就和C、Java、VB没有太多区别。&lt;/li&gt;
&lt;li&gt;它可以方便地嵌入网页中，为网页提供固定的或者是可交互的酷炫的动画。这可能是它应用最广的场景。&lt;/li&gt;
&lt;li&gt;它可以直接导出gif、swf、html甚至exe。尤其是exe，它让你感觉你真的在编写一个“软件”，而不是“网页”，这是JavaScript所无法比拟的。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;也正因为以上优点，Flash几乎是最好玩的编程环境，ActionScript几乎是最好的编程语言。你可以用它来写软件、做网页、搞游戏……做好多好多事情……&lt;/p&gt;
&lt;p&gt;所以，很幸运将它做为上手的编程语言。&lt;/p&gt;
&lt;p&gt;当然，那时我并没有分析和选择编程语言的能力，或许只是我在写网页时久仰过太多次它的大名。&lt;/p&gt;
&lt;p&gt;那时，Flash的开发者被称为“闪客”，在我心目中是一个带着光芒的称呼。&lt;/p&gt;
&lt;p&gt;力有不逮，心向往之。&lt;/p&gt;
&lt;p&gt;我也尝试和闪客们一样，做一些电子相册之类的东西。例如下面这个就是当时下载了网上的素材包自己做的。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/83/pic/010_hu_3315bfb2e2202b1e.webp&#34; width=&#34;891&#34; height=&#34;539&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;但是不得不说，对我而言，太难了。碰撞检测、速度优化，这些东西对面向对象都还没搞明白的我而言都太难了，而且搞完也难有用武之地。&lt;/p&gt;
&lt;p&gt;所以，大多数时候用它来做的就是写一些照猫画虎的乱七八糟的小游戏。&lt;/p&gt;
&lt;p&gt;但不得不说，这确实可以给高中的我带来极大的成就感。&lt;/p&gt;
&lt;p&gt;如今，我电脑上早已没了Flash开发环境，甚至连Flash Player都没有了。但，硬盘中仍保存着高中编写的小游戏。&lt;/p&gt;
&lt;p&gt;幸运的是，当时为了便于运行，我在有些项目做完后导出了exe。而这些exe文件，至今仍可运行。&lt;/p&gt;
&lt;p&gt;例如，早在当年，我就为退休后的时光做好了准备——去街头给人电脑算命！&lt;/p&gt;
&lt;p&gt;“小兄弟，我看你脸色为#FFFF00，近期可能遇到异常啊，要不要来我这测个人品？”&lt;/p&gt;
&lt;p&gt;然后我就掏出这个软件。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/83/pic/001_hu_d9eff021d6280ebe.webp&#34; width=&#34;591&#34; height=&#34;430&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;给他测一番人品。&lt;/p&gt;
&lt;p&gt;这东西，融合了东方五千年乾坤瑰宝，吸纳了西方近现代科技文明。给出的结果，谁不信？！&lt;/p&gt;
&lt;p&gt;这就是说服力，是伏羲、周公、冯诺依曼、乔治布尔等人一同带来的说服力，谁不服？！&lt;/p&gt;
&lt;p&gt;所以，这个生意一定行！&lt;/p&gt;
&lt;p&gt;而且，这繁体字体，加上红绿配色给出的结果，让人感觉踏实准确的同时，给人以沁人心脾的美感！&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/83/pic/002_hu_caa82900a56fb867.webp&#34; width=&#34;588&#34; height=&#34;424&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;奥，对了。“电磁铁”是我那时候的网名，初中起的。没啥太大意思，就是酷爱物理，然后起名时恰好看到了物理书上的电磁铁，想到电磁铁看似简单，却实现了电与磁两种看不见摸不着的神奇场的转换。古乘东方司南之意，今切西方电波之味。所以就起了这个名字。&lt;/p&gt;
&lt;p&gt;而且，这名字，人品好。上图就是证据！&lt;/p&gt;
&lt;p&gt;而且这软件测得准，准极了！&lt;/p&gt;
&lt;p&gt;都是随机数出来的，能不准么？&lt;/p&gt;
&lt;p&gt;不准那也是随机种子选的不对，和程序没啥关系。&lt;/p&gt;
&lt;p&gt;然后接元宝也做过，老游戏了。&lt;/p&gt;
&lt;p&gt;天若有情天亦老，春风送暖接元宝！&lt;/p&gt;
&lt;p&gt;这界面看着就喜庆！&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/83/pic/004_hu_1336910b723d4dba.webp&#34; width=&#34;676&#34; height=&#34;494&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;当时还给自己设计了个吉祥物，毕竟么，玩Flash也不能忘了PhotoShop。&lt;/p&gt;
&lt;p&gt;萌萌的小熊多可爱！&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/83/pic/005_hu_a6cecf7ad8365e7b.webp&#34; width=&#34;676&#34; height=&#34;493&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;而且，这过年有，毕业肯定不能少。&lt;/p&gt;
&lt;p&gt;毕业的时候，我整理了班级同学的照片，做了个小游戏并发给大家。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/83/pic/006_hu_69b6a23ad06ea742.webp&#34; width=&#34;700&#34; height=&#34;497&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;这个游戏是找不同，也能让大家一起回忆下高中的生活。&lt;/p&gt;
&lt;p&gt;印象中，如果通关，会有一首我写的小诗。&lt;/p&gt;
&lt;p&gt;自己——写的——小诗？&lt;/p&gt;
&lt;p&gt;那肯定很尬，对吧。&lt;/p&gt;
&lt;p&gt;但放心，我现在完全感觉不到尬！&lt;/p&gt;
&lt;p&gt;因为当时不知怎么想的，把游戏做的太难了！&lt;/p&gt;
&lt;p&gt;现在我自己到第三关就卡住了！！！&lt;/p&gt;
&lt;p&gt;布置关卡的人，被自己的关卡卡住了。&lt;/p&gt;
&lt;p&gt;哈哈。&lt;/p&gt;
&lt;p&gt;每张图有三处不同，点击第三处就成功跳转了。所以只能截取到两处的图片。&lt;/p&gt;
&lt;p&gt;这是第一关（第三处为背后书立上标签少了）&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/83/pic/007_hu_890fb1a7569d5546.webp&#34; width=&#34;702&#34; height=&#34;504&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;这是第二关（第三处为左上角的树枝少了一根）&lt;/p&gt;</description>
    </item>
    <item>
      <title>什么是湖仓一体？它有什么意义？</title>
      <link>https://yeecode.top/blog/45/</link>
      <pubDate>Sat, 05 Dec 2020 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/45/</guid>
      
      <description>&lt;p&gt;看到阿里的湖仓一体，突然想起当时做大数据算法开发时，最怕的事情就是上线。&lt;/p&gt;
&lt;p&gt;上线后的第一次运行，十有八九会异常。&lt;/p&gt;
&lt;p&gt;那时最常用的还是Hadoop，往往一个任务跑了十几个小时后，总在Mapper到达99.9%的时候宕掉。&lt;/p&gt;
&lt;p&gt;不是前期测试的不充分，而是无论如何，前期的测试数据集也无法覆盖真实数据的所有异常。然后，只要遗漏一个点，Mapper就会在最后宕掉。前功尽弃，只能重来。要是赶上工期紧，光吓就能吓个半死。&lt;/p&gt;
&lt;p&gt;要想用全量的真实数据做前期验证是不可能的，因为任务所需要的数据往往在不同的地方，且常常数T大小。一是搬不动，二是没地放。因此，很多时候，只能从各处搬运一点数据出来做测试，数据量十分有限。&lt;/p&gt;
&lt;p&gt;就别说是开发和测试了，就连算法最终上线后，还要系统专门进行数据搬运。对的，一般我们算法团队产出的是按照规定格式生成的文本文档，没法直接给应用使用。需要系统数据转移模块中的定时任务将其搬运到数据库中，才能在明天供应用使用。&lt;/p&gt;
&lt;p&gt;那时就是这样的：开发时搬运数据的工作量大；上线后运行的故障率高；线上系统需要完成数据搬运工作；算法结果往往次日才能入库生效。&lt;/p&gt;
&lt;p&gt;大家倒也习以为常，“我们做的就是大数据开发，数据量大，搬来搬去浪费些时间也正常。”&lt;/p&gt;
&lt;p&gt;是的，数据量大，搬来搬去浪费些时间当然正常。&lt;/p&gt;
&lt;p&gt;可是，真的需要搬来搬去么？&lt;/p&gt;
&lt;p&gt;今天我们就来聊一聊。&lt;/p&gt;
&lt;h2 id=&#34;1-数据湖与数据仓库&#34;&gt;1 数据湖与数据仓库&lt;/h2&gt;
&lt;p&gt;很多人说起大数据，对它的印象就是“大”，然后没然后了。但其实不然，大数据也分很多种，通常我们可以通过两个维度来衡量它：结构化程度和信息密度。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;数据的结构化程度&lt;/strong&gt;描述的是数据本身的规范性。例如传统数据库中的数据主要是结构化的，每条记录包含了众多的字段，每个字段是一个独立的信息块。而邮件正文、发言稿件等这些信息就是非结构化的，它没有结构化的格式来约束其中的信息。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;数据的信息密度&lt;/strong&gt;描述的是单位存储体积内包含的信息量的大小。显然，传统数据库中存储的数据的信息密度是很高的，例如性别字段中的0、1就用很小的存储体积标明了一个明确的信息。而邮件正文、发言稿的信息密度是低的，有很多重复的语句，语句中也夹杂很多意义不大的词语甚至可能还有歧义。&lt;/p&gt;
&lt;p&gt;大数据处理的过程其实是一个提升数据的结构化程度和信息密度的过程：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;获取到的原始数据往往是非结构化的、信息密度很低的；&lt;/li&gt;
&lt;li&gt;然后通过数据清洗、分析等等，排除无用的数据、挖掘数据中的关联，不断提升其结构化程度、信息密度；&lt;/li&gt;
&lt;li&gt;最后，得到结构化程度较高、信息密度较高的结果，加以利用。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;整个过程中，信息的发展如下所示：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/45/pic/001_hu_e85862bb937218ed.webp&#34; alt=&#34;图片&#34; width=&#34;692&#34; height=&#34;300&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;其实这时我们可以发现，在大数据的处理过程中，其特征在一直变化。而不同的数据，其适宜的存储介质也是不同的。这就引出了数据湖和数据仓库的概念。&lt;/p&gt;
&lt;h2 id=&#34;11-什么是数据湖&#34;&gt;1.1 什么是数据湖&lt;/h2&gt;
&lt;p&gt;数据湖适合存储非结构化的、信息密度低的、未经清洗的数据。例如生产中我们获取到的日志信息、长文本信息等都可以直接放到数据湖中。例如Hive、Spark等都属于这一类。&lt;/p&gt;
&lt;p&gt;“湖”字将它的特点体现的淋漓尽致。水，纳百川，而无形。几乎任何数据都可以往里放，但也因为没有太多规范，里面很杂乱。&lt;/p&gt;
&lt;p&gt;数据湖就像是办公桌子上的对其的文档，有一些事笔记，有一些是书本，有一些是打印的材料，杂乱、多样，但是使用起来很方便，你可以从中挑选你喜欢的数据随意组合。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/45/pic/002_hu_447acf17d5b1a8d6.webp&#34; alt=&#34;图片&#34; width=&#34;640&#34; height=&#34;426&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;数据湖的这种开放性非常有利于前期的开发、创造。毕竟据说，人的桌子越乱，就越有创造力。数据湖里杂乱的数据就是创造力的象征。&lt;/p&gt;
&lt;h2 id=&#34;12-什么是数据仓库&#34;&gt;1.2 什么是数据仓库&lt;/h2&gt;
&lt;p&gt;数据仓库适合存储结构化的、信息密度高的、经过处理后的数据。例如我们通过大数据分析得到的关联信息、画像信息等，都可以放在数据仓库中。例如BigQuery、MaxCompute都属于这一类。&lt;/p&gt;
&lt;p&gt;“仓库”一词也将它的特点表现的清清楚楚。仓库，东西要放在规整的货架上，甚至还会给货架编号。这里的数据很规范，用起来没那么灵活。&lt;/p&gt;
&lt;p&gt;数据仓库就像是图书馆，里面的数据按照规范放好，你可以按照类别找到想要的信息。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/45/pic/003_hu_b7c1930394f3d6c7.webp&#34; alt=&#34;图片&#34; width=&#34;640&#34; height=&#34;426&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;数据仓库的规范性有利于数据的长期、大量存放。即使你到一个新的图书馆，只要按照图书分类法索引，你就能找到想看的书籍。&lt;/p&gt;
&lt;h2 id=&#34;13-湖还是仓&#34;&gt;1.3 湖还是仓？&lt;/h2&gt;
&lt;p&gt;曾经有一段时间，大家对于大数据的存储形式分裂为了两派。不断询问是选择数据湖，还是选择数据仓库？&lt;/p&gt;
&lt;p&gt;选择数据湖，才能拥有数据的多样与灵活，有利于将不同的数据组合在一起，发现新的规律。&lt;/p&gt;
&lt;p&gt;选择数据仓库，才能拥有数据的规范与清晰，有利于数据的便捷使用，也利于数据的长时间存储。&lt;/p&gt;
&lt;h2 id=&#34;2-湖仓一体下的开发&#34;&gt;2 湖仓一体下的开发&lt;/h2&gt;
&lt;p&gt;小孩子才做选择题，大人全都要。&lt;/p&gt;
&lt;p&gt;最好的形式就是前期在数据湖中进行开发、创造，然后等一切成熟之后，将整理后的数据在数据仓库中存储。&lt;/p&gt;
&lt;p&gt;这就带来了一个搬运问题，需要不断把数据湖中的东西搬运到数据仓库。而且，同一份数据可能在数据湖和数据仓库中均有存储。而后期可能会导致数据的不一致。&lt;/p&gt;
&lt;p&gt;看来全都要是有代价的！&lt;/p&gt;
&lt;p&gt;这就是这个回答最开始说的搬运问题。&lt;/p&gt;
&lt;p&gt;那么大的数据，搬起来又冗余，有浪费时间。冗余，浪费的是公司的存储空间，是钱；时间，浪费的是程序员的青春，是钱，更是生命啊！&lt;/p&gt;
&lt;p&gt;所以，小孩子不是不想全都要，是真没那个条件。&lt;/p&gt;
&lt;p&gt;现在阿里的湖仓一体创造了条件。&lt;/p&gt;
&lt;p&gt;湖仓一体，即打通数据仓库和数据湖两套体系，让数据和计算在湖和仓之间自由流动，从而构建一个完整的有机的大数据技术生态体系。这就像是在你的面前放了一个摆满了文档的书桌（数据湖），也放了一个小书架（数据仓库）。于是两者的数据都可以随意获取，在灵活与规范之间取得了平衡！&lt;/p&gt;
&lt;h2 id=&#34;3-湖仓一体下的公司&#34;&gt;3 湖仓一体下的公司&lt;/h2&gt;
&lt;p&gt;刚才说的是我等程序员的开发过程。上升到程序员的集合体——公司，也是一样的。&lt;/p&gt;
&lt;p&gt;公司开发大数据产品时，不可能一上来就定义好数据的字段，然后上数据仓库。所以前期肯定要在数据湖中开发，这时，数据量也不大，数据湖成本低且灵活，十分适合。&lt;/p&gt;
&lt;p&gt;然是，随着公司的发展，数据湖中的数据就显得杂乱了起来。长此以往，没有人能知道其中数据的具体含义！&lt;/p&gt;
&lt;p&gt;于是，得上数据仓库。只有规范数据，才能让数据长久地发挥价值。&lt;/p&gt;
&lt;p&gt;可见，对于公司而言：&lt;strong&gt;数据湖灵活易上手，对于公司初期发展是好的；数据仓库规范有未来，对于公司的长久发展是好的&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/45/pic/101_hu_e8513640bd67efe3.webp&#34; alt=&#34;图片&#34; width=&#34;900&#34; height=&#34;430&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;这个时候，就得进行数据的迁移。从数据湖迁移到数据仓库。&lt;/p&gt;
&lt;p&gt;哎？&lt;/p&gt;
&lt;p&gt;怎么又是迁移？&lt;/p&gt;
&lt;p&gt;所以，湖仓一体的好处就体现出来了。&lt;/p&gt;
&lt;h2 id=&#34;4-总结&#34;&gt;4 总结&lt;/h2&gt;
&lt;p&gt;所以：&lt;strong&gt;数据湖的灵活对于大数据前期开发和公司的前期部署是友好的；数据仓库的规范对于大数据后期运行和公司长期发展是友好的。两者不存在二选一的问题，结合使用是趋势。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;而&lt;strong&gt;湖仓一体创造了两者结合使用的桥梁，对开发者和公司而言，都比较有意义。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;码农福音，公司利器。&lt;/p&gt;</description>
    </item>
    <item>
      <title>什么是边缘计算？</title>
      <link>https://yeecode.top/blog/44/</link>
      <pubDate>Sat, 07 Nov 2020 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/44/</guid>
      
      <description>&lt;p&gt;计算机领域的各种概念炒作层出不穷，而我又是一个偏实干的码农。就是从写代码做起，而不是悬在空中做一些概念。&lt;/p&gt;
&lt;p&gt;所以当我看到“边缘计算”这一新概念之后，第一反应是看它是不是个纯炒作的概念。&lt;/p&gt;
&lt;p&gt;然后调研和思考了一番。&lt;/p&gt;
&lt;p&gt;别说，还真不是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;从发展趋势上看，边缘计算是顺应发展的；&lt;/li&gt;
&lt;li&gt;从工程实现上看，边缘计算是容易落地的；&lt;/li&gt;
&lt;li&gt;从最终收益上看，边缘计算是存在价值的。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在介绍以上内容之前，先说一下边缘计算是什么。&lt;/p&gt;
&lt;p&gt;“边缘”二字说的是边缘节点。这是一个网络概念，边缘节点是指那些离用户很近的、不在主干网络上的节点。用户在访问网络中的信息时，请求会先到达边缘节点，然后由边缘节点逐步转发到核心节点上。CDN部署的CDN节点其实就属于边缘节点，这样，用户请求静态资源时，可以在边缘节点上获得，而不需要经过层层转发后从核心节点获得。&lt;/p&gt;
&lt;p&gt;“边缘计算”就是在边缘节点上展开计算。在此之前，如果有计算工作，需要边缘节点将请求转发到核心节点上进行。有了边缘计算，则计算工作可以在靠近用户的边缘节点上开展，不需要再到核心节点开展。&lt;/p&gt;
&lt;h2 id=&#34;1发展趋势分析&#34;&gt;1、发展趋势分析&lt;/h2&gt;
&lt;p&gt;好多年以前，还在本科的我认准了物联网是未来的发展方向，然后侧重朝这方面发展。当然，事实证明，我的方向偏了，毕竟直到现在物联网也没大火过。&lt;/p&gt;
&lt;p&gt;那时候，在我（和我的学校、实验室）受限的认知和受限的资金条件下，物联终端采集走的是“终端-上位机-互联网”的路线。终端与上位机之间通过有线或者无线的串口或者并口连接，上位机和互联网之间通过WIFI或者双绞线连接。&lt;/p&gt;
&lt;p&gt;下面是本科发的某篇论文中的结构图，可以看到上位机的存在。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/44/pic/001_hu_b706a08806756476.webp&#34; alt=&#34;图片&#34; width=&#34;713&#34; height=&#34;446&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;而那时的界面也主要在上位机中开发。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/44/pic/002_hu_f2677785bd962699.webp&#34; alt=&#34;图片&#34; width=&#34;720&#34; height=&#34;292&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;所以是以下的三层结构：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;互联网：责任重，计算能力强，主要负责数据存储和展示&lt;/li&gt;
&lt;li&gt;上位机：责任重，计算能力强，主要负责数据展示&lt;/li&gt;
&lt;li&gt;终端：责任轻，计算能力弱，主要负责数据采集&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;而其实终端可以跨过上位机和互联网相连的。但那时的相关技术还不成熟，有两点：一是终端采用的单片机难以驱动WIFI或者GSM模块；二是有一些上述模块，但真的很贵。所以这两点可以总结为一点：我那时太穷，买不起这些芯片。&lt;/p&gt;
&lt;p&gt;是的，那时我们几个团队还经常互相借12864……&lt;/p&gt;
&lt;p&gt;到后来，实验室有钱了。这些芯片不在话下，于是我甚至可以终端直接从近海采集数据传到互联网了，如下图。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/44/pic/003_hu_635f3e944654af0f.webp&#34; alt=&#34;图片&#34; width=&#34;720&#34; height=&#34;540&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;注意，这里回传的不是视频信号本身。而是一些其他的二值化后的信号。另外，这东西部署在一个近海的钻井平台上，照片中看到的是小岛。&lt;/p&gt;
&lt;p&gt;这时变为了两层结构，如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;互联网：责任重，计算能力强，主要负责数据存储和展示&lt;/li&gt;
&lt;li&gt;终端：责任轻，计算能力弱，主要负责数据采集&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以上有一个共同的特点就是终端的计算能力很弱，往往只能做数据采集。&lt;/p&gt;
&lt;p&gt;曾经我们做项目时还做过数学计算表。就是单片机计算多级的三角函数等都太慢了，所以我们在单片机烧录完主程序后剩下的空间中全部烧录一个计算好的计算表，让单片机通过差别得出近似结果，而不是真正计算。可见，单片机计算能力真的很差，还有就是，计算能力差也有能力差的办法。&lt;/p&gt;
&lt;p&gt;之后终端的计算能力并不是没有长进，但是，应用场景的复杂度长进的更快。例如，现在的智能音箱，需要完成自然语言处理等操作，终端显然无法完成。&lt;/p&gt;
&lt;p&gt;那这时候，下面的两层结构基本没有改变。只是，终端可以具有展示功能。复杂的计算还是需要借助互联网的服务器集群完成。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;互联网：责任重，计算能力强，主要负责数据存储和计算&lt;/li&gt;
&lt;li&gt;终端：责任轻，计算能力弱，主要负责数据采集和展示&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对于服务器集群而言，它将要负责大量终端传来的计算任务。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/44/pic/004_hu_78189235b6d1f94.webp&#34; alt=&#34;图片&#34; width=&#34;554&#34; height=&#34;479&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;那此时，对其进行分流操作十分必要。边缘计算就是对互联网端的计算资源进行的分流。&lt;/p&gt;
&lt;p&gt;因此，从趋势上看，边缘计算是顺应发展的。&lt;/p&gt;
&lt;h2 id=&#34;2工程实现分析&#34;&gt;2、工程实现分析&lt;/h2&gt;
&lt;p&gt;我们知道，分布式系统是有价值的，但是它的实现成本是极高的。各种分布式算法、分布式中间件绕晕了一群人。&lt;/p&gt;
&lt;p&gt;而边缘计算的各个节点之间的网络距离更远，其实现可能比分布式系统更为复杂。如果真是如此的话，边缘计算将只能存在于理论界，难以落地。&lt;/p&gt;
&lt;p&gt;然而，万幸，边缘计算的实施难度要比分布式系统低很多。&lt;/p&gt;
&lt;p&gt;因为，它不需要追求节点间的一致性。&lt;/p&gt;
&lt;p&gt;边缘计算的核心节点负责分配流量，一旦流量分配结束。则某个终端接下来的所有处理均由边缘节点完成，且在整个处理过程中，该边缘节点无需和其他边缘节点同步状态信息。最多，只需要和核心节点同步少量状态信息。&lt;/p&gt;
&lt;p&gt;这使得众多边缘节点之间是解耦的，没有了共识、一致性的要求。其实现容易了很多！&lt;/p&gt;
&lt;p&gt;然后唯一的难点在于计算逻辑的分发，这点类似于CDN中静态资源的分发，但要略微复杂。因为CDN中的静态资源可以被动同源站保持一致，而边缘计算节点的计算逻辑则要一一推送。但即使这样，实现难度也不大。因为，没有了一致性的要求，推送可以是逐个开展的，甚至可以是分批次地支持灰度测试的开展的。&lt;/p&gt;
&lt;p&gt;因此，边缘节点是容易落地的。&lt;/p&gt;
&lt;h2 id=&#34;3最终收益分析&#34;&gt;3、最终收益分析&lt;/h2&gt;
&lt;p&gt;边缘计算是很有价值的，可以总结为以下几点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;避免了对单一节点的冲击，分散了计算压力、宕机风险。&lt;/li&gt;
&lt;li&gt;减少了核心网络的数据流量。在以前，终端采集的数据量是小的，可能就是几个温湿度值之类的，但是现在，终端采集到的可能是- 声音、图像数据，量很大。采用边缘计算可以减少这些数据在核心网络的传输。&lt;/li&gt;
&lt;li&gt;降低了通讯时延，提升了响应速度。大量的数据只需要经过较少的跳数便可以到达边缘节点，提升了网络速度。尤其是在5G推广之后，终端和边缘节点的通讯时延会大大降低。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;可见，边缘计算真的很有价值。&lt;/p&gt;
&lt;h2 id=&#34;4再说一点&#34;&gt;4、再说一点&lt;/h2&gt;
&lt;p&gt;计算机系统的发展总是这样，分分合合。从终端到个人计算机，这是分；从个人计算机到超级计算机，这是合；从超级计算机到分布式系统，这是分。&lt;/p&gt;
&lt;p&gt;而边缘计算，就是一次分，从唯一的计算集群分不到边缘节点；但从大视角看，它仍旧是合，所有的计算还是要在互联网上进行。&lt;/p&gt;
&lt;p&gt;或许，等终端的计算能力真的突飞猛进，可以独立实现AI等复杂计算的时候，边缘计算才会寿终正寝。那时，也便迎来了真正的分。每个终端可以独立处理复杂事务。&lt;/p&gt;
&lt;p&gt;所以，边缘计算是一次合中的分。&lt;/p&gt;</description>
    </item>
    <item>
      <title>Java异常体系详解</title>
      <link>https://yeecode.top/blog/43/</link>
      <pubDate>Sat, 24 Oct 2020 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/43/</guid>
      
      <description>&lt;p&gt;“异常”代表程序运行中遇到了意料之外的事情，为了表征异常，Java标准库中内建了一些通用的异常。今天我们就介绍Java中的异常体系。&lt;/p&gt;
&lt;p&gt;Java中的异常类均以Throwable为父类，而Throwable又派生出Error类和Exception类这两大子类。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Error类及其子类，代表了JVM自身的异常。这一类异常发生时，无法通过程序来修正。最可靠的方式就是尽快地停止JVM的运行。&lt;/li&gt;
&lt;li&gt;Exception类及其子类，代表程序运行中发生了意料之外的事情。这些意外的事情可以被Java异常处理机制处理。而Exception类及其子类又可以划分为两大类：
&lt;ul&gt;
&lt;li&gt;RuntimeException及其子类：这一类异常其实是程序设计的错误，通过修正程序设计是可以避免的。例如，数组越界异常、数值异常等。&lt;/li&gt;
&lt;li&gt;非RuntimeException及其子类：这一类异常的发生通常由外部因素导致，是不可预知和避免的。例如，IO异常、类型寻找异常等。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在遇到复杂的类间关系时，绘制它们的UML类图是理清它们关系的非常好的手段。很多时候，在绘制UML类图的过程中，你就会对它们之间的关系豁然开朗。Throwable及其子类的继承关系可以用下面的类图表示。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/43/pic/exception_hu_26911a5c0bebc7d.webp&#34; alt=&#34;Throwable及其子类类图&#34; width=&#34;900&#34; height=&#34;352&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;在所有异常中，Error及其子类代表JVM出现异常，无法通过软件修复；RuntimeException及其子类是程序设计的错误，可以在编写程序时避免。以上这两大类异常称为免检异常，即不需要对这两类异常进行强制的检查。而除去上面两类异常的其他异常，它们的发生与外部环境有关，被称为必检异常。在编写程序时必须用try、catch语句将其包围起来。&lt;/p&gt;
&lt;p&gt;对于Throwable对象，其主要的成员变量有&lt;code&gt;detailMessage&lt;/code&gt;和&lt;code&gt;cause&lt;/code&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;detailMessage&lt;/code&gt;为一个String，用来存储异常的详细信息；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cause&lt;/code&gt;为另一个Throwable对象，用来存储异常引发原因。这是因为一个异常发生时，通常引发的的上级程序也发生异常，从而导致一连串的异常产生，叫作异常链。一个异常的cause属性可以指向引发它的下级异常，从而将整个异常链保存下来，如图所示。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/43/pic/link_hu_7dd1e1e98615e27e.webp&#34; alt=&#34;异常链&#34; width=&#34;900&#34; height=&#34;221&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;在进行软件系统架构时，创建一套合理的异常框架供项目使用是十分必要的。这一点可以参照MyBatis的异常设计，例如在MyBatis中，便设计了如下的一套异常体系。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/43/pic/exceptions_hu_75ebe789fb189abf.webp&#34; alt=&#34;异常体系&#34; width=&#34;900&#34; height=&#34;470&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;这些异常类中除去RuntimeSqlException类外均为PersistenceException的子类。&lt;/p&gt;
&lt;p&gt;关于MyBatis的异常设计的具体架构思路和源码分析，大家可以参照《通用源码阅读指导书》。这是一本以MyBatis的源码为实例讲述源码阅读方法的书籍，非常推荐。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://union-click.jd.com/jdc?e=&amp;p=JF8BAMYJK1olXDYCV1ZeCU4UCl9MRANLAjZbERscSkAJHTdNTwcKBlMdBgABFksUC2wJHlgcQl9HCANtb1FqVSleXj1wFXRcAC4CXAlkWTFyXVcZbQcyVF9cCE8eBWcKG2slXQEyAjBdCUoWAm4NG14WbQcyVFlYCkMTBGcBGVkWXzYFVFdtUx55BWdbSV9AVQEBVl9fCnsnM2w4K2sVbQUyFjAKDRtHUTgNHF4VVVMHAV5YAEhAAWsLHFkXCQEDXVcOXXsVAm4MEms&#34;&gt;&lt;img src=&#34;https://yeecode.top/img/promo/mb.jpg&#34; width=&#34;450&#34; height=&#34;348&#34; loading=&#34;lazy&#34;  alt=&#34;通用源码阅读指导书-京东自营&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://union-click.jd.com/jdc?e=&amp;amp;p=JF8BAMYJK1olXDYCV1ZeCU4UCl9MRANLAjZbERscSkAJHTdNTwcKBlMdBgABFksUC2wJHlgcQl9HCANtb1FqVSleXj1wFXRcAC4CXAlkWTFyXVcZbQcyVF9cCE8eBWcKG2slXQEyAjBdCUoWAm4NG14WbQcyVFlYCkMTBGcBGVkWXzYFVFdtUx55BWdbSV9AVQEBVl9fCnsnM2w4K2sVbQUyFjAKDRtHUTgNHF4VVVMHAV5YAEhAAWsLHFkXCQEDXVcOXXsVAm4MEms&#34;&gt;《通用源码阅读指导书》&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;多读读源码，进步会很大。&lt;/p&gt;</description>
    </item>
    <item>
      <title>面向对象中父类的确立</title>
      <link>https://yeecode.top/blog/29/</link>
      <pubDate>Sun, 30 Aug 2020 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/29/</guid>
      
      <description>&lt;p&gt;封装、继承、多态，这是面向对象的三大特性。这三大特性中，有两个特性都与父类有着深深的关联：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;继承：是指子类可以获得父类的属性和方法。&lt;/li&gt;
&lt;li&gt;多态：是指父类引用可以指向父类对象，也可指向子类对象。在调用时，父类的引用可以根据引用具体指向的对象去调用。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;可见，父类概念的区分在面向对象的设计中十分基础也十分重要。但实际上，大多数开发者对父类的概念有不小的认知偏差。本文我们将讨论这一点。&lt;/p&gt;
&lt;h2 id=&#34;传统认知的误区&#34;&gt;传统认知的误区&lt;/h2&gt;
&lt;p&gt;首先我们给出一个问题。&lt;/p&gt;
&lt;p&gt;问题1：矩形是不是正方形的父类？&lt;/p&gt;
&lt;p&gt;请先在心中给出这个问题的答案，然后在继续阅读本文。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;接下来，我们给出矩形类Rectangle和正方形类Square中的方法（省略方法的具体实现），如下所示。&lt;/p&gt;
&lt;p&gt;矩形类：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;public class Rectangle {
    private double length;
    private double width;
    private String color;

    public Rectangle(double length, double width, String color) {}
    public void setLength(double length) {}
    public void setWidth(double width) {}
    public void setColor(String color) {}
    public double getArea() {}
    public String getColor() {}
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;正方形类：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;public class Square {
    private double sideLength;
    private String color;
    
    public Square(double sideLength, String color) {}
    public void setSideLength(double sideLength) {}
    public void setColor(String color) {}
    public double getArea() {}
    public String getColor() {}
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;然后，请再次回答问题。&lt;/p&gt;</description>
    </item>
    <item>
      <title>如何阅读源码？</title>
      <link>https://yeecode.top/blog/39/</link>
      <pubDate>Sun, 09 Aug 2020 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/39/</guid>
      
      <description>&lt;p&gt;本回答将按照下面的章节体系进行：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;1 我为什么阅读源码
2 阅读源码的好处
3 阅读源码的困难
4 阅读源码的步骤
5 阅读源码的方法
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;1-我为什么阅读源码&#34;&gt;1 我为什么阅读源码&lt;/h2&gt;
&lt;p&gt;我开始阅读源码是在进行互联网开发的第八九个年头。在此之前，我做过校园网站，接过网站开发的私活，进行过理论算法相关的研究，也设计开发了许多系统。我对我做过的系统都比较有信心，它们也都运行的不错，但是一个疑问却在我的心头逐渐浮现：我的架构和世界最优良架构之间的差距到底有多大？&lt;/p&gt;
&lt;p&gt;阅读开源项目的源码能给我答案。&lt;/p&gt;
&lt;p&gt;许多优秀的开源项目历经数千开发者的数万次提交，被数亿用户使用。这些项目从可扩展性、可靠性、可用性等各个角度考量，都是十分优良的。通过阅读这些项目的源码能让我找到自己在软件设计和开发上的不足。&lt;/p&gt;
&lt;p&gt;于是我开始了我的源码阅读计划。&lt;/p&gt;
&lt;h2 id=&#34;2-阅读源码的好处&#34;&gt;2 阅读源码的好处&lt;/h2&gt;
&lt;p&gt;阅读源码的好处很多，而且已经成为共识。光靠铺天盖地的源码阅读活动就能感知一二，虽然我觉着那些活动都太过浮躁，主要是个噱头，学不到啥。&lt;/p&gt;
&lt;p&gt;但是不可否认，阅读源码的好处极多，包括但不限于：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;透彻地理解项目的实现原理&lt;/li&gt;
&lt;li&gt;接触到成熟和先进的架构方案&lt;/li&gt;
&lt;li&gt;学习到可靠与巧妙的实施技巧&lt;/li&gt;
&lt;li&gt;发现自身知识盲点，提升自身知识储备&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以，我有些后悔，我觉着我应该更早地开始源码阅读。我推荐进行源码阅读的时机是：&lt;strong&gt;学会编程语言的基本知识，并做过少量项目之后&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;我们知道，学编程的过程中在不断接收新的知识，进步很快。刚开始做项目时，进入实践领域，进步很快。然后就到了平台期，可能几年下来都是重复的增删改查，毫无进步。&lt;strong&gt;这段平台期实际是进行源码阅读的良好时机，能让你的技术持续进步&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id=&#34;3-阅读源码的困难&#34;&gt;3 阅读源码的困难&lt;/h2&gt;
&lt;p&gt;当我们阅读一份源码时，需要面对的困难通常有：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;难以归纳的凌乱文件&lt;/li&gt;
&lt;li&gt;稀奇古怪的类型组织&lt;/li&gt;
&lt;li&gt;混乱不堪的逻辑跳转&lt;/li&gt;
&lt;li&gt;不明其意的方法变量&lt;/li&gt;
&lt;li&gt;……&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以很多人即使知道阅读源码的好处，也坚持不下来。就因为以上困难难以克服。&lt;/p&gt;
&lt;p&gt;而阅读源码的难，是正常的。&lt;/p&gt;
&lt;h2 id=&#34;31-时间维度带来的困难&#34;&gt;3.1 时间维度带来的困难&lt;/h2&gt;
&lt;p&gt;从时间维度上看，每一个优秀的工程项目都经历了从雏形到成熟的曲折演化过程。而整个过程都会被映射到一个时间点上，肯定很难。&lt;/p&gt;
&lt;p&gt;于是有人提出了“阅读源码时，从第一次提交开始阅读”的主意。我要说一下，这是错误的！&lt;/p&gt;
&lt;p&gt;这是一种纸上谈兵式的错误，提出这种方法的人肯定没怎么读过源码。事实上，我也有过这种想法，然后在实践中很快被抛弃了。&lt;/p&gt;
&lt;p&gt;如果一个项目只经过了几次提交，这种方法获取可行。而一个优秀的通常有几千上万次提交，将这几千上万次提交都读完，工作量要比把当前最新版本的代码读完难的多得多！因为其时间维度的复杂度比空间维度复杂度大得多了。&lt;/p&gt;
&lt;p&gt;我用下面的图片展示了项目发展的过程，一个项目从版本1到版本n，逐渐完善。版本1一般是最简单最基础的，但是，要想从版本1读起，一直读到版本n，可能要阅读几千个版本。难度极大，远不如直接去读版本n。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/39/pic/001_hu_e47191ef4447a2d7.webp&#34; alt=&#34;图片&#34; width=&#34;482&#34; height=&#34;304&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;还有，许多项目的第一次提交并不是只有几行代码的init项目，往往第一次提交就是几百个类。而且，初期的代码组织还比较混乱。毕竟，作者也不知道该项目能火，往往比较随意。&lt;/p&gt;
&lt;p&gt;更别说，历史提交中还夹杂很多的Bug、回退、依赖包升级等等。想要把每次Bug、回退、依赖包升级所产生的历史背景都搞清楚，这几乎是不可能的任务。&lt;/p&gt;
&lt;h2 id=&#34;32-空间维度的困难&#34;&gt;3.2 空间维度的困难&lt;/h2&gt;
&lt;p&gt;每一个优秀的工程项目都凝聚了众多开发者的缜密思维逻辑，而这些逻辑都被投射到了平面的代码上。这使得阅读源码的过程成了逆推开发者思维逻辑的过程，显然，逆推是很难的。&lt;/p&gt;
&lt;p&gt;不过，这方面的困难真有解决方案：&lt;strong&gt;详细了解开源项目的功能，然后自己琢磨该如何实现&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这样，就将“逆推”的过程转化为了“正向推导+验证”的过程，这样就相对简单了。&lt;/p&gt;
&lt;p&gt;但无论如何，阅读源码总不会太简单。于是有人说读懂源码比编写源码更为困难，想必也是有一定道理的。&lt;/p&gt;
&lt;h2 id=&#34;4-阅读源码的步骤&#34;&gt;4 阅读源码的步骤&lt;/h2&gt;
&lt;p&gt;阅读源码推荐按照下面的步骤进行：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;了解项目的背景。这相当于了解了项目的需求。&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;充分了解项目的功能，并熟练使用。我们说过，将“逆推”的过程转化为“正向推导+验证”的过程是阅读源码的一个重要方法。而了解项目的功能是这个方法的基础。&lt;/li&gt;
&lt;li&gt;调试代码。通过断点调试，掌握整个代码的流程走向、主次关系、依赖关系。&lt;/li&gt;
&lt;li&gt;划分源码。根据每个包、模块的功能不同，将源码划分为几个部分。&lt;/li&gt;
&lt;li&gt;由底层到上层，逐个击破。先阅读不依赖其他模块的，或者依赖其他模块比较少的基础模块。然后逐步上移，完成整个项目源码的阅读。&lt;/li&gt;
&lt;li&gt;横向总结。在由底层到上层阅读完成后，横向以功能点为维度串联源码的实现逻辑。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一般情况下，一个项目的源码要在3~4个月左右读完。否则，可能读到后面的，已经忘记前面的功能了。&lt;/p&gt;
&lt;p&gt;当然越快越好，但考虑到我猿们还要加班，时间太短了不太现实。&lt;/p&gt;
&lt;h2 id=&#34;5-阅读源码的方法&#34;&gt;5 阅读源码的方法&lt;/h2&gt;
&lt;p&gt;有人说读懂源码比编写源码更为困难，不无道理。&lt;strong&gt;而源码阅读和软件开发一样，是一个非常综合的能力，没有一个万全之法&lt;/strong&gt;。但有很多不错的方法。&lt;/p&gt;
&lt;p&gt;例如一些常用的方法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;调试追踪：多数情况下，当我们对某些变量的含义产生疑惑时，借助开发工具的调试功能直接查看变量值的变化是一个非常好的方法。而且该方法还能指引代码逻辑的跳转过程，对于理解源码极为有用。&lt;/li&gt;
&lt;li&gt;归类总结：优秀的源码都遵循一定的设计规则，这些规则可能是项目间通用的，也可能是项目内独有的。在源码阅读的过程中将这些设计规则总结出来，将会使得源码阅读的过程越来越顺畅。&lt;/li&gt;
&lt;li&gt;上下文整合：有些对象、属性、方法等仅仅通过自身很难判断其作用和实现。此时可以结合其调用的上下文，查看对象何时被引用、属性怎样被赋值、方法为何被调用，这对于了解它们的作用和实现很有帮助。&lt;/li&gt;
&lt;li&gt;类比阅读：如果存在实现某功能的两条或者多条路径，可以类别阅读，找出他们处理逻辑的不同。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;还有很多方法，不再一一介绍。因为没有实在的源码案例，光介绍显得很浮。&lt;/p&gt;</description>
    </item>
    <item>
      <title>《通用源码阅读指导书》前言</title>
      <link>https://yeecode.top/blog/73/</link>
      <pubDate>Sat, 01 Aug 2020 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/73/</guid>
      
      <description>&lt;p&gt;有一段时间，我觉着我非要阅读开源项目的源码不可。&lt;/p&gt;
&lt;p&gt;那时，我已经在公司负责设计和开发了许多系统。如果连大学时带领大家开发和维护学校网站也算上的话，我已经进行软件开发整整十个年头了。在这十个年头里，我对自己设计和开发的系统都很有信心。但却有一个疑惑一直在我心头萦绕：我不知道，我的架构和世界最优良架构之间的差距到底有多大。&lt;/p&gt;
&lt;p&gt;阅读开源项目的源码能给我答案。&lt;/p&gt;
&lt;p&gt;许多优秀的开源项目历经数千开发者的数万次提交，被数亿用户使用。这些项目从可扩展性、可靠性、可用性等各个角度考量，都是十分优良的。通过阅读这些项目的源码能让我找到自己在软件设计和开发上的不足。&lt;/p&gt;
&lt;p&gt;于是我开始了我的源码阅读计划。&lt;/p&gt;
&lt;p&gt;在阅读源码的过程中，我看过不少资料。但很多资料对于源码中简单的部分讲解的细致入微；而对复杂的部分则避而不谈或含糊其辞。
在阅读源码的过程中，我也走过了不少弯路。经常在一个难点中挣扎很久不得前进。&lt;/p&gt;
&lt;p&gt;当然，阅读源码也让我收获颇丰。它不仅让我知道了自己的设计与优良设计之间的差距，还让我学到了许多的架构技巧、编程知识。在源码阅读的过程我也总结出了许多的经验和方法。因此，我决定写这本书，将这些经验和方法分享出来，指引许多和我一样前行在源码阅读道路上的人。&lt;/p&gt;
&lt;p&gt;源码阅读首先要选定相应的源码作为材料。从项目的成熟度、涉及面、应用广度、项目规模等多方面考虑，本书最终选中MyBatis的源码。因此，本书将以阅读MyBatis源码为例，介绍源码阅读的经验和方法。&lt;/p&gt;
&lt;p&gt;在本书的写作中，我努力做到详尽而不罗嗦。本书以包为单位，对MyBatis源码中的300多个类都进行了介绍。在这个过程中，对于简单或重复的类一笔带过，但对于越复杂的类则越是逐方法、逐行地分析。力求让大家读的顺、读的懂、有收获。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;本书一共分为了五个模块，每个模块的主要内容如下。&lt;/p&gt;
&lt;h2 id=&#34;背景介绍模块&#34;&gt;背景介绍模块：&lt;/h2&gt;
&lt;p&gt;第1章介绍了源码阅读的意义和方法。&lt;/p&gt;
&lt;p&gt;第2章对MyBatis的背景和快速上手方法进行了介绍。这一章的内容是简单但重要的。对于任何一个软件，其背景对应于软件的“设计需求”，其使用对应于软件的“主要功能”。把握了一个软件设计需求和主要功能对于阅读软件的源码很有帮助。&lt;/p&gt;
&lt;p&gt;第3章中使用断点调试方法对MyBatis的执行过程进行了追踪。该章节的内容有助于我们了解整个MyBatis的内部框架。&lt;/p&gt;
&lt;p&gt;第4章对MyBatis的源码结构进行了介绍，并根据源码包的功能对包进行了分类。&lt;/p&gt;
&lt;h2 id=&#34;基础功能包源码阅读模块&#34;&gt;基础功能包源码阅读模块：&lt;/h2&gt;
&lt;p&gt;在这一模块中，我们对5个基础功能包中的源码进行了阅读。基础功能包相对独立，与MyBatis的核心逻辑耦合小，比较适合作为我们源码阅读的切入点。在阅读这些源码时，我们也会逐步介绍一些阅读源码时常用的方法技巧。&lt;/p&gt;
&lt;p&gt;第5章介绍了exceptions包的源码。我们可以通过该包了解MyBatis的整个异常体系。&lt;/p&gt;
&lt;p&gt;第6章介绍了reflection包的源码。该包基于反射提供了创建对象、修改对象属性、调用对象方法等功能。这些功能在MyBatis的参数处理、结果处理等环节都发挥了重要的作用。&lt;/p&gt;
&lt;p&gt;第7章介绍了annotations包与lang包的源码。这两个包中全是注解类。我们将通过对Java注解的学习详细了解每个注解类的含义。最后，我们还通过源码分析了注解类如何在MyBatis的运行中发挥作用。&lt;/p&gt;
&lt;p&gt;第8章介绍了type包的源码。通过这一章我们将了解MyBatis如何组织和实现类型处理器，以完成对各种类型数据的处理。&lt;/p&gt;
&lt;p&gt;第9章介绍了io包的源码。通过该包我们将了解到MyBatis如何完成外部类的筛选和载入。&lt;/p&gt;
&lt;p&gt;第10章介绍了logging包的源码。logging包不仅为MyBatis提供了日志记录功能，还提供了获取和记录JDBC中日志的功能。通过这一章节，我们将了解这些功能的实现细节。&lt;/p&gt;
&lt;p&gt;第11章介绍了parsing包的源码。通过这一章，我们将了解MyBatis如何完成XML文件的解析。&lt;/p&gt;
&lt;h2 id=&#34;配置解析包源码阅读模块&#34;&gt;配置解析包源码阅读模块：&lt;/h2&gt;
&lt;p&gt;第12章介绍了配置解析相关类的分类方法。对于配置解析类相关的类可以按照类的功能将其划分为解析器类或解析实体类。&lt;/p&gt;
&lt;p&gt;第13章介绍了binding包的源码。该包负责将SQL语句接入到映射接口中。&lt;/p&gt;
&lt;p&gt;第14章介绍了builder包的源码。该包中的建造者基类和工具类为MyBatis基于建造者模式建造对象提供了基础。此外，该包还完成了映射文件和映射注解的解析工作。&lt;/p&gt;
&lt;p&gt;第15章介绍了mapping包的源码。该包完成了SQL语句的处理、输入参数的处理、输出结果的处理等功能，并为MyBatis提供了多数据库支持的能力。&lt;/p&gt;
&lt;p&gt;第16章介绍了scripting包的源码。就是在这个包中，复杂的SQL节点被逐步解析为纯粹的SQL语句。该章节将带我们详细了解这一解析过程。&lt;/p&gt;
&lt;p&gt;第17章介绍了datasource包的源码。该包包含了MyBatis中与数据源相关的类，包括非池化数据源、池化数据源、数据源工厂等。也正是通过该包，MyBatis完成了和数据库的对接。&lt;/p&gt;
&lt;h2 id=&#34;核心操作包源码阅读模块&#34;&gt;核心操作包源码阅读模块：&lt;/h2&gt;
&lt;p&gt;在这一模块中，我们将详细介绍MyBatis的核心操作包。&lt;/p&gt;
&lt;p&gt;第18章介绍jdbc包的源码。该包仅使用6个类便为MyBatis提供了运行SQL语句和脚本的能力。&lt;/p&gt;
&lt;p&gt;第19章介绍了cache包的源码。该包向我们展示了MyBatis如何使用装饰器模式为用户提供丰富的、可配置的缓存。并且该章节还从功能维度出发详细介绍了MyBatis的两级缓存机制。&lt;/p&gt;
&lt;p&gt;第20章介绍了transaction包的源码。该包为MyBatis提供了内部和外部的事务支持。&lt;/p&gt;
&lt;p&gt;第21章介绍了cursor包的源码。通过该包，MyBatis能将查询结果封装为游标形式返回。&lt;/p&gt;
&lt;p&gt;第22章介绍了executor包的源码。executor包是MyBatis中最为重要也是最复杂的包。在这一章中，我们以子包为单位分别介绍了MyBatis的主键自增功能、懒加载功能、语句处理功能、参数处理功能、结果处理功能、结果集处理功能。然后在此基础上对MyBatis中执行器的源码进行了阅读。最后，我们阅读了MyBatis中错误上下文的源码，了解MyBatis如何及时地保留错误发生时的现场环境。&lt;/p&gt;
&lt;p&gt;第23章介绍了session包的源码。session包是一个对外接口包，是用户在使用MyBatis时接触最多的包。&lt;/p&gt;
&lt;p&gt;第24章介绍了plugin包的源码。在该章节中我们编写了一个插件，然后通过源码详细了解了MyBatis插件的实现原理以及MyBatis插件平台的架构。&lt;/p&gt;
&lt;h2 id=&#34;总结与展望模块&#34;&gt;总结与展望模块：&lt;/h2&gt;
&lt;p&gt;第25章对阅读MyBatis源码过程中的技巧方法进行了总结。&lt;/p&gt;
&lt;p&gt;第26章从项目的成熟度、涉及面、应用广度、规模等角度综合考量为大家推荐了一些优秀的开源项目。学习完本书后，大家可以从这些项目中挑选一些进行源码阅读。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;源码阅读毕竟是一个对知识广度和深度都有较高要求的工作。为了大家能够顺利地阅读MyBatis的源码，我们会在很多章节之前介绍该章节源码涉及的基础知识。先掌握这些基础知识后再阅读相关源码则会轻松很多。&lt;/p&gt;
&lt;p&gt;受篇幅所限，书中只能给出部分MyBatis源码。我们将完整的带中文注释的MyBatis源码整理成了开源项目供大家下载、参考。该项目的地址为：&lt;a href=&#34;https://github.com/yeecode/MyBatisCN&#34;&gt;https://github.com/yeecode/MyBatisCN&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;为了大家能更轻松地理解和掌握一些相对复杂的知识点，我们还准备了许多示例项目。该示例项目也开源供大家下载，地址为：&lt;a href=&#34;https://github.com/yeecode/MyBatisDemo&#34;&gt;https://github.com/yeecode/MyBatisDemo&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;受限于我的水平和时间，书中难免会有疏漏之处。您可以通过我的个人主页与我取得联系并与我交流。在那里也能看到我的最新项目。我的个人主页地址是：&lt;a href=&#34;https://yeecode.top&#34;&gt;https://yeecode.top&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;通过阅读本书，您将详细了解MyBatis中每一个类的结构、原理、细节。但要注意，这只是我们阅读本书的额外收获。掌握源码阅读的方法和技巧，并将这些方法技巧应用到其他项目的源码阅读工作、系统设计工作、软件开发工作中，这才是阅读本书的最终目的。&lt;/p&gt;
&lt;p&gt;源码阅读是一个过程艰苦而结果可观的工作。每一个潜心阅读源码的开发者都值的尊敬，也希望本书能够在您源码阅读的过程中为您提供一些帮助，让您多一些收获。&lt;/p&gt;
&lt;p&gt;加油！奋斗路上的你和我。&lt;/p&gt;</description>
    </item>
    <item>
      <title>六边形架构和插件式架构</title>
      <link>https://yeecode.top/blog/28/</link>
      <pubDate>Sun, 26 Jul 2020 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/28/</guid>
      
      <description>&lt;p&gt;在《软件复杂性的因素与解决思路》一文中我们说过，软件中技术逻辑和业务逻辑的混杂是导致软件系统复杂化的原因之一。为了降低软件的复杂度，我们需要尽量将软件中的技术逻辑和业务逻辑拆分开来。&lt;/p&gt;
&lt;p&gt;在通常的软件架构中，我们使用分层设计来实现两者的拆分。而领域驱动设计中的六边形架构则带来了新的拆分思路，进一步的，作者总结工作经验给出了插件式的拆分思路。&lt;/p&gt;
&lt;h2 id=&#34;六边形架构&#34;&gt;六边形架构&lt;/h2&gt;
&lt;p&gt;应用业务逻辑往往需要两个端口，一个是面向界面的端口，一个是面向持久层的端口。因此，我们往往会依据这两个端口将整个应用分层，进而实现业务逻辑和技术逻辑的拆离。例如典型的三层结构包含：接口层、业务层、持久层。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/28/pic/001_hu_3b2f0b4347c2b4fc.webp&#34; alt=&#34;图&#34; width=&#34;198&#34; height=&#34;188&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;在这种设计中，可能还会存在更多的层级，例如持久层又细分为service层、dao层等。而且，各个层级中可能还会穿插模型（Model）层。&lt;/p&gt;
&lt;p&gt;在这种分层的设计中，往往出现业务逻辑的碎片化。业务逻辑可能会散落到各个层级，例如，在SQL语句、模型方法、逻辑层中都存在业务逻辑，最终导致系统的混乱。&lt;/p&gt;
&lt;p&gt;六边形架构则忽略“上下”、“左右”的层级划分，而是主张“内外”。内层是指业务逻辑，外层是指接口。它是说，应用要以业务逻辑为核心，通过接口与外部系统进行交互。从其要表达的思想上看，“六边形”架构被称之为“同心圆”架构更为合适，因为它并不强调边的数目。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/28/pic/002_hu_7db257764bfa85da.webp&#34; alt=&#34;图&#34; width=&#34;587&#34; height=&#34;457&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;在六边形结构中，持久化功能只是一个挂载到业务逻辑核心上的外围系统，通过持久化接口与业务核心进行交互。同样的，客户端、WEB界面、服务API都是外围系统，通过特定的接口与业务逻辑核心进行交互。&lt;/p&gt;
&lt;p&gt;这种思想下，业务逻辑是核心，通过接口对外服务。外围系统多一方，架构也就多一个边，但无论外围系统怎么增加，接口都不会涉及业务逻辑。&lt;/p&gt;
&lt;h2 id=&#34;插件式架构&#34;&gt;插件式架构&lt;/h2&gt;
&lt;p&gt;六边形架构强调了业务逻辑的核心作用，而与之相对，我们可以以技术逻辑为核心，而将业务逻辑作为插件插入到应用中。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/28/pic/003_hu_dbd82125d26f0b0b.webp&#34; alt=&#34;图&#34; width=&#34;520&#34; height=&#34;208&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;在这种思想下，技术逻辑实现的是一个通用的平台。而业务逻辑则插接到平台上完成对应的功能。&lt;/p&gt;
&lt;p&gt;实现这种架构时要实现技术平台的通用性和易用性，以便于能够满足业务逻辑的需要。这种通用性和易用性不仅仅表现在某个业务逻辑内部，也表现在业务逻辑之间。因为，在某些场景下，业务逻辑之间需要通过平台进行互相的协作。&lt;/p&gt;
&lt;p&gt;规则引擎系统就是这种模型的良好实践，系统本身提供对规则的解析能力，而规则来决定业务逻辑。&lt;/p&gt;
&lt;p&gt;服务网格也是这种模型的良好实践，系统本身提供了服务发现和调用能力，业务应用只需要完成业务逻辑便可以插接到服务网格中享受这些服务。&lt;/p&gt;
&lt;p&gt;在插件式结构中，技术逻辑是核心，业务逻辑是插件。这种方式也能保证两类逻辑的松耦合。&lt;/p&gt;
&lt;h2 id=&#34;总结&#34;&gt;总结&lt;/h2&gt;
&lt;p&gt;本文介绍了六边形架构的核心思想，并结合工作实践提出了插件式架构。这两种架构方案都利于实现业务逻辑和技术逻辑的拆分，进而降低应用的复杂性，可供大家在进行软件架构时结合具体场景灵活选用。&lt;/p&gt;</description>
    </item>
    <item>
      <title>为什么网上总有人说WEB开发没技术含量？</title>
      <link>https://yeecode.top/blog/38/</link>
      <pubDate>Sun, 19 Jul 2020 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/38/</guid>
      
      <description>&lt;p&gt;确实，WEB开发毫无技术含量。&lt;/p&gt;
&lt;p&gt;早在十几年前，我就会WEB开发了。&lt;/p&gt;
&lt;p&gt;只不过，到现在，我还没学完……&lt;/p&gt;
&lt;p&gt;目前总结下来一共经历了下面几个阶段：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;第一阶段：能跑就行、全面开花
第二阶段：全栈开发、整站落地
第三阶段：并发挑战、容量挑战
第四阶段：理论探究、行知结合
第五阶段：阅读源码、探求本源
第六阶段：构建地基、制造轮子
第七阶段：架构规划、协调开发
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;同时本文也可以作为&lt;strong&gt;各位WEB开发者甚至软件开发者的进阶指南&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;不过我的水平有限，更高的阶段还未开启,仅供参考。&lt;/p&gt;
&lt;h2 id=&#34;第一阶段能跑就行全面开花&#34;&gt;第一阶段：能跑就行、全面开花&lt;/h2&gt;
&lt;p&gt;我最早开始WEB开发的切入点是……是WEB页面设计。&lt;/p&gt;
&lt;p&gt;对的，你没有听错，页面设计！就是画WEB页面。当时加入了某校园网站。曾获教育部十佳网站，日访问在15万到30万之间。&lt;/p&gt;
&lt;p&gt;所以我熟练掌握各种PhotoShop技能。&lt;/p&gt;
&lt;p&gt;当时设计的很多网页的源文件也都还在。刚在自己找了下作品看了下，那些作品放在十年前倒也还凑活。但是，总让人觉着怪怪的。&lt;/p&gt;
&lt;p&gt;是的，当年的我也很快发现了这一点：&lt;strong&gt;技术还行，审美太差！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我是我的业余职业生涯遇到了瓶颈。&lt;/p&gt;
&lt;p&gt;我这还没入行，就遇到了瓶颈！你能感受那种无助么。。。&lt;/p&gt;
&lt;p&gt;于是我，转行了！&lt;/p&gt;
&lt;p&gt;自此之后，我也会偶尔带带页面设计的新人，但是守住底线，只传授技术，绝不出作品。&lt;/p&gt;
&lt;p&gt;而后，我还是投入到新兴的前端开发工作中，并带起了一个前端团队。&lt;/p&gt;
&lt;p&gt;这方面的工作做了两三年，熟读《CSS权威指南》，导致我现在看到鲑鱼都觉着熟悉。&lt;/p&gt;
&lt;p&gt;各种嵌套、定位、JQuery、浮动……都不是问题。当时最大的挑战在于：浏览器兼容。&lt;/p&gt;
&lt;p&gt;有些老派的人在用IE6，而有些已经IE8\IE9，更大的挑战在于许多人用那时十分火爆的360浏览器、搜狗浏览器……各种浏览器对规范的支持各不相同，一片混乱。我们则是页面写一周，兼容调半月，苦不堪言。&lt;/p&gt;
&lt;p&gt;再后来，我学会了世界上最好的语言：PHP！&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;页面设计、前端开发、后端开发，我打通了！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;于是，我自己开发了一些小的站点，幸福感爆棚。&lt;/p&gt;
&lt;p&gt;这时的我，还会Flash呢，用ActionScript写点交互动画也不是问题。还做过小游戏，开心~&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;所以，那时的我自信地高呼：WEB开发没技术含量！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;对于用到的工具是什么原理，管它呢！我的站点能跑！那么多人用！都说我是大神！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对了，在这一阶段，会CRUD+百度就足够了！多了没用！&lt;/p&gt;
&lt;h2 id=&#34;第二阶段全栈开发整站落地&#34;&gt;第二阶段：全栈开发、整站落地&lt;/h2&gt;
&lt;p&gt;页面设计、前端开发、后端开发，全都会了，那还得了？&lt;/p&gt;
&lt;p&gt;在学校么，闲着也是闲着！&lt;/p&gt;
&lt;p&gt;拉了一帮人接活了！&lt;/p&gt;
&lt;p&gt;不过，确切说是总有活找上来，有时候看着学期初没啥事，就接了。&lt;/p&gt;
&lt;p&gt;各个方向都找一个人，我做协调，四个人就能接手个项目。甚至，或者就某个方向的人不用找，我顶上，三个人也能开工。反正我也会。&lt;/p&gt;
&lt;p&gt;有些学院的网站开发之类的，直接接过来做掉！&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;这时遇到了服务器、域名、部署、升级、运维问题。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;没事，兵来将挡，水来土掩。有百度呢！&lt;/p&gt;
&lt;p&gt;曾经接过香港高校的单子，是个实验室的项目。赚的都是港币！不过当时真没经验，铆足了劲要价，要低了人家还给机会让我们再报一次，还是要低了……真是没见过世面，不会要太高。&lt;/p&gt;
&lt;p&gt;几个项目搞下来，发现技术毫无长进，就是不断重复。倒是团队磨合了不少，简直都快流水线作业了。几个人不用见面，都能把活干了！&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;技术没长进，就说明WEB开发的技术已经到头了，一定是这样……哈哈&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;所以，WEB开发不过如此。&lt;/p&gt;
&lt;p&gt;所以，&lt;strong&gt;那时的我会悄悄地说：WEB开发没技术含量！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;注意，要悄悄地！自己赚钱的东西，大声说，这不绝了自己的财路。&lt;/p&gt;
&lt;p&gt;不过在开发中，&lt;strong&gt;也遇到了一些算法问题。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;管他的，网上都能搜到！修改下，就能跑！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;又不是不能用！&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;第三阶段并发挑战容量挑战&#34;&gt;第三阶段：并发挑战、容量挑战&lt;/h2&gt;
&lt;p&gt;之前做的项目都是能跑就行。简单来说，能跑就是成功，不能跑就再改！&lt;/p&gt;
&lt;p&gt;但是，进入公司正式工作开始面临一些新的问题：&lt;strong&gt;并发问题、容量问题&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;比如典型的，一个应用需要部署多个节点啊，多个应用的分布式节点之间互相依赖啊。依赖存在先后关系啊等等。&lt;/p&gt;
&lt;p&gt;于是乎，分布式登场了，携带着一众相关知识：分布式一致性、分布式存储、分布式计算、分布式锁、分库、分表、分流、读写分离……还附带了一众相关框架：Kafka\Rabbit\Zookeeper\RabbitMQ\RocketMq\Etcd……&lt;/p&gt;
&lt;p&gt;以上我就不列举了，大家耳熟能详。&lt;/p&gt;
&lt;p&gt;那段时间，我就主要在学习。&lt;/p&gt;
&lt;p&gt;当然学习不只是学会用，也要考虑其实现原理。可以动手自己实现下。&lt;/p&gt;
&lt;p&gt;例如下面是个RPC的最小实现，用来学习RPC就很好。&lt;/p&gt;
&lt;p&gt;用极少的类、方法演示了RPC的实现原理，是一个学习RPC工作原理的良好示例。&lt;/p&gt;
&lt;p&gt;​
&lt;a href=&#34;https://github.com/yeecode/EasyRPC&#34;&gt;https://github.com/yeecode/EasyRPC&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;而且那段时间我在参与一些集群部署的项目。就是通过网站来完成应用的部署、升级、配置等工作，涉及到制品管理、质量管理等各个环节……表面呈现是一个网页，后面东西一大堆……&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;但是，我知道，那些都是纸老虎，没什么难的！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;毕竟，WEB开发没技术含量，能有什么难的。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;第四阶段理论探究行知结合&#34;&gt;第四阶段：理论探究、行知结合&lt;/h2&gt;
&lt;p&gt;并发挑战、容量挑战遇到的多了，我也学的多了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;功夫不负有心人，脑袋里终于成了一锅浆糊……&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;于是，我开始走上另一条路：&lt;strong&gt;理论研究&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;我开始直接研究分布式理论，还包括相关的论文啊，都掏出来读一读……&lt;/p&gt;</description>
    </item>
    <item>
      <title>实体和值对象的聚合</title>
      <link>https://yeecode.top/blog/27/</link>
      <pubDate>Sat, 27 Jun 2020 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/27/</guid>
      
      <description>&lt;p&gt;在《面向对象中的实体和值对象》一文中，我们探讨了面向对象设计中的实体和值对象。本文将在前文的基础上继续探讨实体和值对象的分簇。&lt;/p&gt;
&lt;p&gt;实体是对象，值对象也是对象。实体之间、实体和值对象之间互相引用，最终会形成一个图。如下所示。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/27/pic/001_hu_1c2bd1382e9317ed.webp&#34; alt=&#34;图片&#34; width=&#34;539&#34; height=&#34;393&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;实体之间，实体和值对象之间的互相引用是普遍的，因此这个图将是十分巨大的。这时面临一个拆分问题，即怎样将图拆分为多个块。&lt;/p&gt;
&lt;p&gt;在UML中，存在聚合概念。聚合是指对象之间的部分和整体的关系，并且，部分可以脱离整体而单独存在。（如果部分无法脱离整体而单独存在，则为组合。）&lt;/p&gt;
&lt;p&gt;在领域驱动设计（Domain-Driven Design，简称DDD）中，也存在聚合的概念。如果几个类必须强一致性地变化，则它们就属于一个聚合。例如，User、email、address等，它们都是和User一起变化的，因此属于一个聚合。这个聚合更像UML中的组合，因为email、address脱离了User后便失去了意义，因此具有和User相同的生命周期。&lt;/p&gt;
&lt;p&gt;DDD中的聚合有以下特点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;存在一个聚合根，它是唯一可以被外界访问的对象，且一定是实体。&lt;/strong&gt; 例如，User就是聚合根，只有它可以被外界访问，email、address等属性都从属与它。即外部对象只能持有对聚合根的引用。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;聚合内的各个对象都一致地变化，因此要被一起读写。&lt;/strong&gt; 为了实现这一点，往往将一个聚合组成一个表。也就是说，email，address信息都存在user表中。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DDD的聚合中，非根的对象脱离了聚合根则没有意义。&lt;/strong&gt; 聚合根是核心，非根则是聚合根的说明与修饰。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因为外部只能引用聚合根，于是可以得到下面的图。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/27/pic/002_hu_b436e5dcb236e363.webp&#34; alt=&#34;图片&#34; width=&#34;550&#34; height=&#34;433&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;图中虚线内的部分为一个聚合，外部只能访问到聚合根，而不能直接访问其他节点。&lt;/p&gt;
&lt;p&gt;聚合之间不需要强一致性地变动，可以放在不同的表中。&lt;/p&gt;
&lt;p&gt;这样我们就可以实现聚合的划分，即系统的类的划分。&lt;/p&gt;
&lt;p&gt;DDD中的聚合的概念是一种良好的类设计、类属性设计、类表结构设计的指导思想。&lt;/p&gt;</description>
    </item>
    <item>
      <title>面向对象中的实体和值对象</title>
      <link>https://yeecode.top/blog/26/</link>
      <pubDate>Sun, 31 May 2020 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/26/</guid>
      
      <description>&lt;p&gt;在面向对象的软件设计中，万物皆为对象。甚至在Java中，为了保证万物皆为对象，还为基本类型设置了包装类。但即使这样，这些对象也需要我们进一步地进行区分。&lt;/p&gt;
&lt;p&gt;假设存在下面的对象：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;public class User {
    private Integer id;
    private String name;
    private String address;
    private String email;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;显然User是我们定义的一个对象，而内部则是该对象的属性。&lt;/p&gt;
&lt;p&gt;我们继续分析这几个属性：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;name属性&lt;/strong&gt;。按照国人的习惯，最好是姓氏、名字单独存放，以便于在必要时展现“李先生”、“刘女士”等这样的称呼。使用一个简单的String存储显然无法实现。我们可以将name属性拆分为firstName和lastName两个属性，但这样一来，姓氏与名字的拼接工作必须由User对象负责。但是，总感觉不合适，因为所有人的姓氏和名字的拼接规则都一样，似乎不应该有独立的User完成拼接。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;address属性&lt;/strong&gt;。地址是一个长长的字符串，包含省、市、县等信息，更关键的，还有非必填的邮编等信息。如果我们把他们都放在一个String里面，显然过于混乱，也面临不好拆分的问题。于是，我们可以考虑将其作为一个地址对象存储，让地址对象包含各个子信息。但这时又让人感觉怪怪的，因为这个对象似乎难以复用。即使用户A和用户B填写了相同的地址也不好复用这一地址，因为不能让一方的修改影响另一方。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;email属性&lt;/strong&gt;。电子邮件地址有着固定的规则，在存入前需要校验。如果使用String存放电子邮件地址，则校验规则要放在别处，这破坏了内聚性。最好是让email属性自身完成。&lt;/p&gt;
&lt;p&gt;再进一步分析，我们发现&lt;strong&gt;id属性&lt;/strong&gt;也是如此。id不一定是数字，可能是一个字符串。而且，id可能包含了一套生成和校验规则，例如我们的身份证号就包含了所属人的籍贯、生日、性别等信息。&lt;/p&gt;
&lt;p&gt;分析完以上几个属性后，我们发现这些属性不是基本类型，但是又和User这种对象不同。例如我们可以分别定义Name、Address、Email类，他们介于User这类对象和基本类型之间。这些对象具有以下的特点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;没有编号，不能进行相等与否的比较。除非对象内存地址相同，否则他们就是不等的。&lt;/li&gt;
&lt;li&gt;不能更新，很像是一个基本值。如果我们要修改它，则直接生成一个新的它即可，而不是更新它。这意味着我们可以放心地多处引用同一个它，而不用担心一处修改了它影响别处。这点和String的不可更新特性一样。&lt;/li&gt;
&lt;li&gt;他们具有业务意义，仅从名字就能看出他们的作用。这一点是十分友好的。&lt;/li&gt;
&lt;li&gt;他们可以集成相关功能。因为他们本身就是对象，内部可以实现相关的验证逻辑等各类逻辑。因此是一个包含属性和行为的整体。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以上这种对象，我们称之为值对象。&lt;strong&gt;值对象用来表示属性的不变值和属性的行为。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在面向对象的编程中引入值对象能够提升代码的可读性，提升内聚性。&lt;/p&gt;
&lt;p&gt;而像User这样的对象则成为实体。实体和值对象是不同的，&lt;strong&gt;实体存在唯一性标志，而实体是否相等的判断依据就是唯一性标志。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;例如，两个User对象，他们的地址可能并不相同，但是只要两者的id一样，则这样两个对象就是相等的。假设User01存放在内存中，将其序列化再反序列化后得到User02，则User01和User02的地址并不相同，但因为两者id一样，实际为一个对象。&lt;/p&gt;
&lt;p&gt;另外再说一点，实体和值对象的界限不是绝对的。例如在一个外卖系统中，地址是一个值对象，从属于人；在社区管理系统中，地址则是一个实体，与人存在多对多的关系。具体的划分要根据业务场景来确定。&lt;/p&gt;
&lt;p&gt;接下来，我们会在实体和值对象的基础上继续探讨《实体和值对象的聚合》。&lt;/p&gt;</description>
    </item>
    <item>
      <title>学习Java应该如何理解反射？</title>
      <link>https://yeecode.top/blog/31/</link>
      <pubDate>Sun, 24 May 2020 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/31/</guid>
      
      <description>&lt;p&gt;首先，对于初学者而言，产生这种疑问是非常正常的。&lt;/p&gt;
&lt;p&gt;首先，我们看反射的定义。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;JAVA反射机制是在运行状态中，对于任意一个类，都能够知道这个类的所有属性和方法；对于任意一个对象，都能够调用它的任意方法和属性；这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;初学者往往会有这样的的想法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;本身，程序中所有的类都是我写的。我相当于所有类的上帝，因此，我知道每个类中应该有怎样的方法。&lt;/li&gt;
&lt;li&gt;既然我知道每个类的方法，那我就可以直接调用它，不用反射在运行中获取。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因此，反射似乎是无用的。&lt;/p&gt;
&lt;p&gt;下面我就直接举一个例子，来说明反射的作用。&lt;/p&gt;
&lt;p&gt;需求:比较同一个类的两个对象的属性的不同。&lt;/p&gt;
&lt;p&gt;方法名为diff，输入参数为两个同类的对象。输出为一个List，其中存放了两个对象的不同的属性的属性名称。&lt;/p&gt;
&lt;p&gt;假设User对象，包含name\age\phone三个参数。&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;List&amp;lt;String&amp;gt; diff(User user1, User user2) {
List&amp;lt;String&amp;gt; result = new ArrayList&amp;lt;&amp;gt;(); 
if(user1.getName() == null &amp;amp;&amp;amp; user2.getName() != null || !user1.getName().equals(user2.getName())) {
    result.add(&amp;#34;name&amp;#34;);
 }
if(user1.getAge() == null &amp;amp;&amp;amp; user2.getAge() != null || !user1.getAge().equals(user2.getAge())) {
    result.add(&amp;#34;age&amp;#34;);
 }
if(user1.getPhone() == null &amp;amp;&amp;amp; user2.getPhone() != null || !user1.getPhone().equals(user2.getPhone())) {
    result.add(&amp;#34;phone&amp;#34;);
 }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;我们发现一个问题，每个属性都需要分别处理，相同的代码需要些三遍。&lt;/p&gt;
&lt;p&gt;当属性更多的话，则要更多遍。&lt;/p&gt;
&lt;p&gt;更严重的是，我们的diff方法只能处理User类的对象，不够通用。如果是另外一个School对象，因为属性不同，则要重新编写diff方法。&lt;/p&gt;
&lt;p&gt;这时候，就需要反射。&lt;/p&gt;
&lt;p&gt;如果使用反射，整个方法可以这样写。&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;List&amp;lt;String&amp;gt; diff(Object obj1, Object obj2) {
	try{
		Class clazz1 = obj1.getClass();
	    Class clazz2 = obj2.getClass();
	    if(clazz1.equals(clazz2)) {
	    	List&amp;lt;String&amp;gt; result = new ArrayList&amp;lt;&amp;gt;(); 
	    	for (Field field ; clazz1.getDeclaredFields()  ) {
	    		field.setAccessible(true);
	    		Object value1= field.get(obj1);
	    		Object value2= field.get(obj2);
	    		if (value1 == null &amp;amp;&amp;amp; value2 != null || !value1.equals(value2)) {
	    			result.add(field.getName());
	    		}
	    	}
	    	return result;
	    } else {
	    	return null;
	    }
	} catch (Exception ex) {
		ex.printStackTrace();
		return null;
	}
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;该方法的优点是可以处理任何类的两个对象，而不需要关心它的属性，十分通用。&lt;/p&gt;</description>
    </item>
    <item>
      <title>怎样成为全栈工程师（Full Stack Developer）？</title>
      <link>https://yeecode.top/blog/36/</link>
      <pubDate>Sun, 17 May 2020 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/36/</guid>
      
      <description>&lt;p&gt;看到很多答案都是采用意淫大法，揣测全栈工程师是怎样的。&lt;/p&gt;
&lt;p&gt;当然，很多思路也都是对的。&lt;/p&gt;
&lt;p&gt;我就不采用意淫大法了，用自己的实际经历介绍下如何成为全栈工程师。并且介绍成长路上和成为全栈工程师之后要注意的事情。&lt;/p&gt;
&lt;p&gt;开始了，小板凳搬好。&lt;/p&gt;
&lt;p&gt;本回答一共分为下面几个部分：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;1、Web前端
2、后端
3、客户端
----------小节
4、算法
5、运维与脚本
----------小节
6、其他可有可无
7、现在在干吗
8、最后也是最重要的一点
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;人没在美国，也没坐飞机。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为了说明一切都是真实的，不是意淫的，我会配上作品图。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;作品会涉及到页面，如果有人说丑（尤其是我的页面设计部分，因为是真丑），请将其转化为点赞抒发出来！！！当做对我勇气的鼓励，谢谢。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;1-前端&#34;&gt;1、 前端&lt;/h2&gt;
&lt;p&gt;我是从前端入手开始编程的。不，确切说是页面设计。&lt;/p&gt;
&lt;p&gt;是的，我最开始接触互联网开发的身份是：页面设计师。就是设计和绘制网页。&lt;/p&gt;
&lt;p&gt;那时，我大一。&lt;/p&gt;
&lt;p&gt;一天，我有了一个想法：做一个校车查询系统。&lt;/p&gt;
&lt;p&gt;那时候，我们学校六七个小区包围了省会。做实验、或者进城等，校车成了最好的途径。然而，学校网站提供的校车是个表是一个表格……巨难使用！&lt;/p&gt;
&lt;p&gt;你想想，当你太阳底下背着书包，用Nokia 6300的小屏幕（2010年，有个Nokia 6300就不错了）一边心疼流量（10元10M？贵的很）进入学校网站，曲曲折折找到那个表格，多麻烦！&lt;/p&gt;
&lt;p&gt;于是，我要做一个校车查询应用。&lt;/p&gt;
&lt;p&gt;设计出来下面的图：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/36/pic/001_hu_faad6b4a59301d12.webp&#34; alt=&#34;图片&#34; width=&#34;720&#34; height=&#34;579&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;然后，会PS只是第一步。前端开发我也会，三下五除二写出了前端页面。&lt;/p&gt;
&lt;p&gt;完美。&lt;/p&gt;
&lt;p&gt;但是还有一个问题，应用场景！&lt;/p&gt;
&lt;p&gt;用户在查询校车时，往往是在路上。10年左右，流量那么贵，让大家访问这么漂亮（额，漂亮么？十年前的审美真是奇怪呢！）的界面浪费流量，而且那时的手机一般屏幕小，使用起来不方便。&lt;/p&gt;
&lt;p&gt;所以，我用WAP协议（上古协议？）写了一个手机版的。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/36/pic/002_hu_9c28cb9ee01377db.webp&#34; alt=&#34;图片&#34; width=&#34;650&#34; height=&#34;510&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;每次查询不到5k，四舍五入就是不需要流量。&lt;/p&gt;
&lt;p&gt;对了，后来我不做页面了，也不做前端了。因为我觉着我审美太差，不合适。而且审美这东西又很难通过努力弥补。&lt;/p&gt;
&lt;p&gt;所以，&lt;strong&gt;没必要真的完全全栈，每个人都有自己的短板&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id=&#34;2后端&#34;&gt;2、后端&lt;/h2&gt;
&lt;p&gt;光写了前端没有用啊，得有后端支持啊！&lt;/p&gt;
&lt;p&gt;不要怕，PHP是世界上最好的语言，世界上所有的网站都运行在Apache上。其他网站就不配叫网站！&lt;/p&gt;
&lt;p&gt;PHP大法好，学起来。&lt;/p&gt;
&lt;p&gt;于是后端就有了啊。&lt;/p&gt;
&lt;p&gt;然后我就把它挂到了学校网站的服务器上（我是网站技术部门的成员），然后发了个QQ空间装逼。&lt;/p&gt;
&lt;p&gt;然后，就被疯狂转发。&lt;/p&gt;
&lt;p&gt;历史最高访问几千独立访客每天。&lt;/p&gt;
&lt;p&gt;对的，我在里面加了CNZZ的访问统计代码。&lt;/p&gt;
&lt;p&gt;那几天，我那个激动啊！！！&lt;/p&gt;
&lt;p&gt;所以，到这里，至少前后端通了。&lt;/p&gt;
&lt;h2 id=&#34;3客户端&#34;&gt;3、客户端&lt;/h2&gt;
&lt;p&gt;但是！万一用户5K都不想用呢？&lt;/p&gt;
&lt;p&gt;或者，网络不好呢？&lt;/p&gt;
&lt;p&gt;再说了，校车信息表一般是一个学期更新一次，直接下载下来，用户一直用多好啊。&lt;/p&gt;
&lt;p&gt;于是，搞个客户端。&lt;/p&gt;
&lt;p&gt;不会没关系，学啊。&lt;/p&gt;
&lt;p&gt;Android，搞起啊。&lt;/p&gt;
&lt;p&gt;于是有了：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/36/pic/003_hu_3e43d85e5828c87b.webp&#34; alt=&#34;图片&#34; width=&#34;720&#34; height=&#34;325&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;它的优点是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用内置数据库存储信息，没有网络也能查询，有网络则会校验数据后判断是不是要更新&lt;/li&gt;
&lt;li&gt;根据GPS信息自动判断你的出发校区&lt;/li&gt;
&lt;li&gt;根据你的历史记录，自动判断你的目的校区&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当然，那些自动判断逻辑都很弱智的，没什么AI算法。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;小节&#34;&gt;小节&lt;/h2&gt;
&lt;p&gt;好，这个时候告一段落，我们总结下。&lt;/p&gt;
&lt;p&gt;其实，从前端到后端到客户端，我就在做一件事情：校车查询。就这一个最简单的事情，一直做。&lt;/p&gt;
&lt;p&gt;除了说到的这些，还有数据库、应用部署、访问统计、应用发布等等。只要盯着一件事情从头到尾做下来，都会涉及到。&lt;/p&gt;
&lt;p&gt;所以，如果要想成为全栈工程师，你可以选择一个有意义的事情，一直做。有以下几个好处：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;做一个实在的东西出来，有成就感。我看到QQ空间都在转发我的网页我就特别有成就感，有动力。&lt;/li&gt;
&lt;li&gt;有连贯性。前后都在做一个事情，你会意识到之前做的哪些地方可以改进，这样很有连贯性，利于进步。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;好了，接下来进入新的阶段。&lt;/p&gt;</description>
    </item>
    <item>
      <title>软件复杂性的因素与解决思路</title>
      <link>https://yeecode.top/blog/19/</link>
      <pubDate>Sun, 26 Apr 2020 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/19/</guid>
      
      <description>&lt;p&gt;软件是复杂的，这一点是软件从业者的共识。也正因为软件的复杂，使得Bug几乎成了和软件绑定的词语。在本文，我们将探讨软件复杂度的原因，及其降低软件复杂度的办法。&lt;/p&gt;
&lt;p&gt;我们认为造成软件复杂度的原因有三点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;软件的规模：软件的规模往往是庞大的，而且会随着时间的推移不断扩增。&lt;/li&gt;
&lt;li&gt;软件的结构：软件是在用技术将业务落地，因此在软件内部存在着技术逻辑和业务逻辑这两类逻辑。这两类信息的交织使得任何只懂得一方逻辑的人无法读懂软件的代码。&lt;/li&gt;
&lt;li&gt;软件的变化：相比于传统系统，软件的需求是在不断变化的。没有人会要求在已经建好的摩天大楼下再挖一个地下室，但在软件行业，这种需求屡见不鲜。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;而且以上三点是共存和互相影响的。例如，随着软件规模的扩充，则它会集成更多的功能，也便会接收到更多的变化；软件的变化可能会破坏原有的结构，使得结构进一步混乱；结构的混乱使得软件变得无序，这又增加了软件的代码规模……&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/19/pic/fuza_hu_3a63f708a83bde6.webp&#34; alt=&#34;图片&#34; width=&#34;289&#34; height=&#34;264&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;要想降低软件的复杂度，可以从以上三个方面分别入手。&lt;/p&gt;
&lt;h2 id=&#34;规模&#34;&gt;规模&lt;/h2&gt;
&lt;p&gt;控制软件的规模可以有效地降低软件的复杂度。要注意，这里的规模是指功能角度的规模，不是性能角度的规模。&lt;/p&gt;
&lt;p&gt;当软件的规模增大时，可以考虑将这些功能拆分开来，拆成不同的子应用。然后让这些应用互相协作，完成原有的功能。这实际上是复杂度的转移策略：将一个应用的复杂度转移到了多个应用和应用的协作中。在这个转移的过程中，复杂度的总量是提升的，但是可以保证拆分出的每个复杂度都在可控的范围内。&lt;/p&gt;
&lt;p&gt;往往，每一个应用的规模要控制在6-7个人能够掌控的范围内。因为这个人数时，交流成本相对较低。&lt;/p&gt;
&lt;p&gt;应用间的协作实现则可以有专门的团队搭建基础平台完成。这个工作的复杂度也是可控的。&lt;/p&gt;
&lt;h2 id=&#34;结构&#34;&gt;结构&lt;/h2&gt;
&lt;p&gt;合理的结构规划可以降低软件的复杂度。应该保证软件结构的清晰。&lt;/p&gt;
&lt;p&gt;清晰的结构是说软件模块（或者说层次）分明，模块（或者说层次）之间的关系分明。而这些模块之间，最重要的是业务逻辑和技术逻辑之间的分明，确保两者不会交织杂糅，互相干扰。&lt;/p&gt;
&lt;p&gt;关于这一块的实现思想，我会单独理清思路介绍。&lt;/p&gt;
&lt;h2 id=&#34;变化&#34;&gt;变化&lt;/h2&gt;
&lt;p&gt;在软件的设计流程中，能否拥抱变化时一个重要的评价指标。也正因为如此，敏捷取代了传统的瀑布式编程，流行了起来。&lt;/p&gt;
&lt;p&gt;敏捷的“最小可用”思路与瀑布的“规划设计”思路不同，大大地压缩了规划设计阶段的时间，而是用频繁的迭代去补足规划设计的缺失。但是，规划的缺失可能造成工作量的成倍增加。或者，我们可以换一种描述：在敏捷开发中，当我们接到需求后，到底是要仔细地规划为以后的扩展留有余地，还是按照敏捷的原则给出最小实现？&lt;/p&gt;
&lt;p&gt;我们知道，前期规划设计的丧失，可能需要后期通过大量的返工才能补足。因此，上述问题是敏捷开发中必然面对的一个问题：选择仔细规划就背离了敏捷的初衷，选择最小实现就堵死了扩展的余地。&lt;/p&gt;
&lt;p&gt;关于此，有一个解决思路是：只被第一次变化侵扰。&lt;/p&gt;
&lt;p&gt;当面临一组需求时，直接按照敏捷的原则给出最小实现。然后，如果哪个需求发生了变更，则我们认为它可能还会变更，于是对哪个需求的实现进行详细的规划，为其保留扩展性。这样，在敏捷和扩展中寻求平衡，以此来拥抱变化。&lt;/p&gt;
&lt;h2 id=&#34;结语&#34;&gt;结语&lt;/h2&gt;
&lt;p&gt;本文分析了造成软件复杂性的三个因素，并给出了各个因素的解决思路。当然，我们这里仅作思路的探讨，对于每一种思路的具体实现将会涉及到许多具体的细节问题，我们以后再逐渐展开。&lt;/p&gt;</description>
    </item>
    <item>
      <title>如何入门Python爬虫？</title>
      <link>https://yeecode.top/blog/35/</link>
      <pubDate>Sun, 19 Apr 2020 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/35/</guid>
      
      <description>&lt;p&gt;今天我们就从&lt;strong&gt;最基本和最本质的途径去编写一个爬虫&lt;/strong&gt;，让大家真正&lt;strong&gt;了解爬虫的工作原理&lt;/strong&gt;。并且，能够&lt;strong&gt;在此基础上根据自身需求改造出需要的爬虫&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;绝对基础、绝对易懂、绝对能上手。如果看完还不会写，我给你表演100个俯卧撑！&lt;/p&gt;
&lt;p&gt;主要内容分为以下几个部分：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;1、被爬取对象
2、当前对象的爬取
3、顺藤摸瓜的实现
4、取来即用的最简单框架
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;1被爬取对象&#34;&gt;1、被爬取对象&lt;/h2&gt;
&lt;p&gt;爬虫是在从网络上爬取内容，常见的爬取的内容由：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;网页本身&lt;/li&gt;
&lt;li&gt;网站接口给出的数据&lt;/li&gt;
&lt;li&gt;网站中的图片、视频等特定资源&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;其中最基本最原始的就是爬取网页本身。如果我们会爬取网页，那接口的数据也会被带出来，特定的资源也可以被筛选出来。因此，我们以爬取网页为例进行介绍。&lt;/p&gt;
&lt;p&gt;那我们先了解什么是网页。例如，下面就是一个网页的截图。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/35/pic/001_hu_d6069a688f7fa239.webp&#34; alt=&#34;图片&#34; width=&#34;900&#34; height=&#34;832&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;那这个网页是怎样构成的呢？&lt;/p&gt;
&lt;p&gt;我们在网页上点击右键，选择“查看网页源代码”。&lt;/p&gt;
&lt;p&gt;就看到了下面的内容，乱糟糟的一片。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/35/pic/002_hu_166571f52d326882.webp&#34; alt=&#34;图片&#34; width=&#34;900&#34; height=&#34;341&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;正是这乱糟糟的一片，经过浏览器渲染后，便成了眼前的页面。这东西就叫做网页源代码，是HTML文件。&lt;/p&gt;
&lt;p&gt;我们可以在其中找到很多的元素。&lt;/p&gt;
&lt;p&gt;例如，下面的是该网页的主旨，主要是给搜索引擎看的：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/35/pic/003_hu_5ab4e7e49ca31392.webp&#34; alt=&#34;图片&#34; width=&#34;900&#34; height=&#34;132&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;下面这段则指向了一张图片的地址，渲染时浏览器会去下载和展示这张图片：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/35/pic/004_hu_a381689a970e778f.webp&#34; alt=&#34;图片&#34; width=&#34;900&#34; height=&#34;196&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;下面这个则指向了一个后缀为css的文件。CSS文件是网页的样式文件，负责修饰网页上内容的样式（长宽高、间距、背景颜色等）：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/35/pic/005_hu_a6f18848aa3630b8.webp&#34; alt=&#34;图片&#34; width=&#34;831&#34; height=&#34;213&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;下面这个则指向了一个后缀为js的文件。JS文件即javascript文件，负责为网页提供一些动态的响应功能、逻辑处理功能。例如一些点击后的操作逻辑都是由它实现的：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/35/pic/006_hu_ea0017d43c7bb258.webp&#34; alt=&#34;图片&#34; width=&#34;872&#34; height=&#34;207&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;还有非常重要的一部分，就是超链接。它指向了另外一个网页的地址。我们通过它，可以跳转到另外一个页面。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/35/pic/007_hu_299c42f35b82f664.webp&#34; alt=&#34;图片&#34; width=&#34;900&#34; height=&#34;204&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;另外还有很多元素，都是界面上会显示的内容。&lt;/p&gt;
&lt;p&gt;可见，HTML文件很重要，它包含了：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当前页面要显示的内容&lt;/li&gt;
&lt;li&gt;修饰当前页面样式的CSS文件的地址&lt;/li&gt;
&lt;li&gt;为当前页面提供逻辑处理功能的JS文件的地址&lt;/li&gt;
&lt;li&gt;当前页面中图片视频等资源的地址&lt;/li&gt;
&lt;li&gt;通过当前页面能够跳转到的其他页面的地址&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;可见它十分重要。&lt;/p&gt;
&lt;p&gt;浏览器在工作时就是先下载指定地址的HTML文件，然后再根据HTML文件中给出的CSS、JS等资源连接去下载相关资源。可见，HTML文件是核心。&lt;/p&gt;
&lt;p&gt;而一个网页的组成部分我们也理清楚了，如下图所示。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/35/pic/web_hu_6489ec69144981a9.webp&#34; alt=&#34;图片&#34; width=&#34;494&#34; height=&#34;186&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;我们在爬取时，会根据不同部分进行不同处理：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;HTML文件：因为其中包含有网页上要显示的内容，因此这是爬虫爬取的重点内容。&lt;/li&gt;
&lt;li&gt;CSS文件：用来修饰样式，一般没什么具体信息，不去爬取&lt;/li&gt;
&lt;li&gt;Javascript：也没什么信息，不去爬取&lt;/li&gt;
&lt;li&gt;图片、视频等：如果我们是爬文字，则忽略他们。如果我们想专门爬取图片，则要爬取这些内容。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;好，对被爬取对象的分析就到这里。&lt;/p&gt;
&lt;h2 id=&#34;2当前对象的爬取&#34;&gt;2、当前对象的爬取&lt;/h2&gt;
&lt;p&gt;假如我们已经有了某个页面的地址，怎么爬取其中的内容呢？&lt;/p&gt;
&lt;p&gt;其实很简单，我们将Python程序伪装为一个浏览器，向网站请求数据。那网站自然就会将HTML信息发送给我们。我们可以将其保存在一个文本文件中。&lt;/p&gt;
&lt;p&gt;下面的代码就可以实现。&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;htmlFile=open(&amp;#39;./output/&amp;#39;+(str(pageId)+&amp;#39;.txt&amp;#39;),&amp;#39;w&amp;#39;)
htmlFile.write(urllib.urlopen(url).read())
htmlFile.close()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;是不是很简单！&lt;/p&gt;
&lt;h2 id=&#34;3顺藤摸瓜的实现&#34;&gt;3、顺藤摸瓜的实现&lt;/h2&gt;
&lt;p&gt;爬虫，有一个重要的点是“爬”。能顺藤摸瓜地爬到下一个地方，继续下载。怎么实现？&lt;/p&gt;
&lt;p&gt;还记得么，我们已经在第二步中下载了当前页面的HTML文件。而在第一步中我们说过，HTML文件中有到其他页面的链接！&lt;/p&gt;
&lt;p&gt;那我们要做的就是将这些到其他页面的链接找出来，然后到对应的页面下载相关内容。简单！&lt;/p&gt;
&lt;p&gt;怎么找出来呢？正则表达式！简单来说，就是按照一定的格式在文本中寻找。&lt;/p&gt;
&lt;p&gt;在HTML文件中，指向其它网页的地址是放在”href=“后面的，而且我们也说过，网页的地址一定不是css、js等结尾的文件，也不是图片（ico、jpg等结尾的就是图片）。&lt;/p&gt;
&lt;p&gt;所以，我们可以粗略地使用下面的表达式将链接过滤出来。&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;pattern=re.compile(&amp;#39;href=&amp;#34;[^(javascript)]\S*[^(#)(css)(js)(ico)]\&amp;#34;&amp;#39;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;然后我们就可以将过滤出来的链接内容下载下来（即再次执行第二步），然后继续分析（再次执行第三步）……然后一直爬呀爬……&lt;/p&gt;
&lt;p&gt;这时我们可以增加一些新的规则，例如，不仅仅下载HTML文件，顺便现在其中的图片等。做到爬图功能。&lt;/p&gt;
&lt;h2 id=&#34;4整体流程&#34;&gt;4、整体流程&lt;/h2&gt;
&lt;p&gt;好了，爬虫已经不难了。&lt;/p&gt;
&lt;p&gt;简单来说就是下面的步骤：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;爬取函数（a）
  下载网页a的内容
  分析网页a中的链接，对得到的每一个链接b调用  爬取函数（b）
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;当然，这个过程中也要处理一些小的细节。例如：&lt;/p&gt;</description>
    </item>
    <item>
      <title>WEB前端是不是没有前景了？</title>
      <link>https://yeecode.top/blog/34/</link>
      <pubDate>Sun, 12 Apr 2020 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/34/</guid>
      
      <description>&lt;p&gt;我最开始接触前端开发是在十年前，然后还带出了一个前端团队。&lt;/p&gt;
&lt;p&gt;到现在，也一直关注前端的进展。&lt;/p&gt;
&lt;p&gt;可即使这样，我也&lt;strong&gt;无法给出前景好坏的明确回答&lt;/strong&gt;。也绝不能为了骗赞，乱答一个。&lt;/p&gt;
&lt;p&gt;接下来我会从&lt;strong&gt;软件宏观发展&lt;/strong&gt;和&lt;strong&gt;前端自身发展&lt;/strong&gt;两个角度分析前端的前景。具体结论，还希望读者自行总结。&lt;/p&gt;
&lt;p&gt;当然，我会在最后给出一些建议。&lt;/p&gt;
&lt;blockquote&gt;
    &lt;p&gt;还要说明一下，本回答说的前端是指大前端，不仅仅包括Web前端，也包括客户端、H5、几近消亡的Flash等等。它们只是所处的硬件载体不同。并且按照趋势，他们将来也会被统一起来，例如，现在HTML已经可以在大多数载体上运行。&lt;/p&gt;

  &lt;/blockquote&gt;&lt;h2 id=&#34;软件宏观发展&#34;&gt;软件宏观发展&lt;/h2&gt;
&lt;p&gt;同许多行业一样，软件行业的发展也在经历一个从&lt;strong&gt;功能到体验&lt;/strong&gt;的过程。&lt;/p&gt;
&lt;p&gt;早期的软件编程主要指完成功能，至于表现形式则并不重要。在那个时代中，命令行是最常见的交互形式，简单、明确。当然，主要是简单。&lt;/p&gt;
&lt;p&gt;甚至，在二十年前，网游还是文字版本的呢。&lt;/p&gt;
&lt;p&gt;“你对对方发起了普通攻击，对方血量-5%！”&lt;/p&gt;
&lt;p&gt;结果那时照样玩的很嗨！&lt;/p&gt;
&lt;p&gt;渐渐地，核心需求被满足了，软件开始面向大众。&lt;strong&gt;对于大众而已，复杂的功能他们感受不到，漂亮的界面他们却可以感受到&lt;/strong&gt;。于是软件便开始追求体验，这是前端才诞生，并开始发展。&lt;/p&gt;
&lt;p&gt;早期的界面也是丑的可怜，以表达信息为主。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/34/pic/001_hu_854963e83a70faaa.webp&#34; alt=&#34;图片&#34; width=&#34;720&#34; height=&#34;529&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;而后，早期的客户也被收割完毕。只有更好的体验才能吸引更多的用户，&lt;strong&gt;体验变得越来越重要，前端也变得越来越重要&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;美观上。从文字到图片到视频。&lt;/p&gt;
&lt;p&gt;载体上。从电脑到手机，甚至到手表。&lt;/p&gt;
&lt;p&gt;用户在哪里，界面就出现在哪里。&lt;/p&gt;
&lt;p&gt;于是渐渐地，功能倒没那么重要，前端带来的体验才最重要。说个大家都知道的例子：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;lt;a href=&amp;#34;javascript: alert(&amp;#34;缓存清除成功!&amp;#34;)&amp;#34; &amp;gt;清空缓存&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;要啥功能啊，只要用户觉着爽就好了！&lt;/p&gt;
&lt;p&gt;而这个趋势，会一直发展下去。其实，我们近些年常提的供给侧改革也是由于这样的原因产生的。&lt;strong&gt;即随着行业的发展，其相关需求基本被满足后，行业的重心会从功能转为体验&lt;/strong&gt;。这个过程中，得用户者得未来。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;从软件宏观发展分析，前端就是软件系统最贴近用户的部分。大有未来！&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;前端自身发展&#34;&gt;前端自身发展&lt;/h2&gt;
&lt;p&gt;在09年左右，我刚开始做前端时，常戏称自己是“切图工”。很大一部分工作就是在切图……然后更大的工作比重在调试配。&lt;/p&gt;
&lt;p&gt;IE6\IE7\IE8\搜狗浏览器\360浏览器\傲游浏览器……那是一个浏览器遍地开花的时代。每个浏览器对标准的支持不同，适配起来难度极大。尤其是IE6，经历过的前端们应该还能回想起那时的痛苦。&lt;/p&gt;
&lt;p&gt;但是要说前端的自身发展，还要从更早说起。在JavaScript诞生的年代，那时，前端语言都没有统一。&lt;/p&gt;
&lt;p&gt;所以，前端的发展&lt;strong&gt;本可以&lt;/strong&gt;用下面的图表示。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/34/pic/002_hu_2ce2dc86b0e92da3.webp&#34; alt=&#34;图片&#34; width=&#34;900&#34; height=&#34;152&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;首先，&lt;strong&gt;实现规范的统一&lt;/strong&gt;。这一点上，前端做的非常好，比后端好很多。&lt;/p&gt;
&lt;p&gt;后端什么Restful、微服务之类，也都只是停留在概念层次，然后以尚不清晰的概念去指导后端发展。（你不信啊，随便拽几个后端的人出来，对这些概念，每个人都有每个人的理解，还特有理！）但是前段的主体可是W3C制定的准确的规范。&lt;/p&gt;
&lt;p&gt;可见，W3C具有很强的掌控能力，也具有极佳的技术引导力。牛逼！&lt;/p&gt;
&lt;p&gt;然后，&lt;strong&gt;就是框架的统一&lt;/strong&gt;。本来，这一点可以在十年以内迅速完成，然后形成一套最为大众和成熟的框架。就像后端的Spring框架一样，慢慢地成为共识。&lt;/p&gt;
&lt;p&gt;但是，还没来得及完成，就受到了硬件高速发展的冲击。成了下面的图。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/34/pic/002_hu_2ce2dc86b0e92da3.webp&#34; alt=&#34;图片&#34; width=&#34;900&#34; height=&#34;152&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;PC端、手机浏览器、Android客户端、IOS客户端……众多的客户端瞬间袭来，也在各自的领域孕育了自己的框架。然后，任何一个框架，可能在某些领域表现的好，在某些领域表现的不好……框架的优劣变得难以比较，统一的过程也便被拉长。&lt;/p&gt;
&lt;p&gt;当然，框架的统一过程也在进行中，例如，ActionScript不就消失了么（心痛，我第一个学的语言实际是ActionScript），JQuery不也渐渐远去了么。现在，Angular\Vue\React等等众多框架开始争斗中，当然，也渐渐有了优劣之分（我不做一家之言的判断，留给大家自己判断）。重点，这就是现在的形势。&lt;/p&gt;
&lt;p&gt;然后，在经过足够长的时间。前端的框架还是会统一起来。用一套框架编写各个端的界面，这绝对是趋势。例如，Electron就很好啊。&lt;/p&gt;
&lt;p&gt;等框架统一结束后，也会有新的客户端类型产生。但是那时的新客户端也会使用统一后的规范和框架进行编程，毕竟开发者在那里。&lt;/p&gt;
&lt;p&gt;一个框架，一份代码，多端运行，自然不需要太多的前端开发者。&lt;strong&gt;因此，从前端自身发展分析，前端会渐渐实现统一。这个过程中，需要的从业者是减少的。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;总结&#34;&gt;总结&lt;/h2&gt;
&lt;p&gt;汇总一下上面的两点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;从软件宏观发展分析，前端就是软件系统最贴近用户的部分。大有未来！&lt;/li&gt;
&lt;li&gt;从前端自身发展分析，前端会渐渐实现统一。这个过程中，需要的从业者是减少的。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;宏观和微观的结论是相悖的，所以，我不能给出确切的结论&lt;/strong&gt;。接下来前端的从业者人数会怎样，会兴起还是衰落，很难说。&lt;/p&gt;
&lt;p&gt;但是，我们现在处在框架争斗合并的过程中，可以得出几点建议：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;前端开发&lt;strong&gt;一定要选好框架，站好队！如果有一天，你选择的框架失势了，那你掌握的技能毫无价值！而如果你选择的框架得势了，你将成为行业专家&lt;/strong&gt;。这一点十分重要。因此，选框架时一定要根据当&lt;strong&gt;前开发活跃度（汇总国内外，别只看国内）、框架支持方实力、发展趋势、优势劣势&lt;/strong&gt;等综合判断。再说一遍，非常重要。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;善于学习新技术&lt;/strong&gt;。如果你觉着你无法一下就选中未来的框架，那就多学。出现新的框架，多看看，多试试。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;发现哪个框架有未来之后，深入学习&lt;/strong&gt;！因为框架的大一统结束后，&amp;ldquo;一份代码多端运行&amp;quot;是必然。那会导致前端开发者需求的减少。深入学习，成为行业大牛不仅不会让你失业，还会让你大有前途。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;最后，在前端框架迭起的现在。学习前端实属不易。&lt;/p&gt;
&lt;p&gt;加油！&lt;/p&gt;</description>
    </item>
    <item>
      <title>既然有 HTTP 请求，为什么还要用 RPC 调用？</title>
      <link>https://yeecode.top/blog/33/</link>
      <pubDate>Sun, 29 Mar 2020 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/33/</guid>
      
      <description>&lt;p&gt;首先，实名赞扬题主的问题。这个问题非常好。&lt;/p&gt;
&lt;p&gt;其次，实名反对各个上来就讲RPC好而HTTP不好的答案。因为，题主的观点非常对。&lt;/p&gt;
&lt;p&gt;HTTP协议，以其中的Restful规范为代表，其优势很大。它&lt;strong&gt;可读性好，且可以得到防火墙的支持、跨语言的支持&lt;/strong&gt;。而且，在去年的报告中，&lt;strong&gt;Restful大有超过RPC的趋势&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;但是HTTP也有其缺点，这是与其优点相对应的。首先是有用&lt;strong&gt;信息占比少&lt;/strong&gt;，毕竟HTTP工作在第七层，包含了大量的HTTP头等信息。其次是&lt;strong&gt;效率低&lt;/strong&gt;，还是因为第七层的缘故。还有，其&lt;strong&gt;可读性似乎没有必要&lt;/strong&gt;，因为我们可以引入网关增加可读性。此外，使用HTTP协议调用远程方法&lt;strong&gt;比较复杂&lt;/strong&gt;，要封装各种参数名和参数值。&lt;/p&gt;
&lt;p&gt;而RPC则与HTTP互补，我们详细介绍下。&lt;strong&gt;看完这篇回答，能让你对RPC的产生、原理、实现代码都有着清晰的了解&lt;/strong&gt;。这样，也能在业务系统中，在RPC和HTTP之间做好抉择。&lt;/p&gt;
&lt;p&gt;但需要再说一句，不是说RPC好，也不是说HTTP好，两者各有千秋，还在比拼中。&lt;/p&gt;
&lt;p&gt;要问我站谁？我根据业务场景，灵活站位……&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;评论区产生了一些争论，我在这里统一进行说明。争论主要发生在两点：&lt;/p&gt;
&lt;p&gt;1、HTTP和RPC同一级别，还是被RPC包含？&lt;/p&gt;
&lt;p&gt;2、Restful也属于RPC么？&lt;/p&gt;
&lt;p&gt;对于以上两点，我画图来一一说明。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/33/pic/000_hu_d0ad16da11a58164.webp&#34; alt=&#34;图片&#34; width=&#34;718&#34; height=&#34;515&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;上图是一个比较完整的关系图，这时我们发现HTTP（图中蓝色框）出现了两次。其中一个是和RPC并列的，都是跨应用调用方法的解决方案；另一个则是被RPC包含的，是RPC通信过程的可选协议之一。&lt;/p&gt;
&lt;p&gt;因此，&lt;strong&gt;第一个问题的答案是都对。看指的是哪一个蓝色框&lt;/strong&gt;。从题主的提问看，既然题主在纠结这两者，应该是指与RPC并列的蓝色框。&lt;/p&gt;
&lt;p&gt;第二个问题是在问远程过程调用（红色框）是不是包含了Restful（黄色框），这种理解的关键在于对RPC的理解。&lt;/p&gt;
&lt;p&gt;RPC字面理解是远程过程调用，即在一个应用中调用另一个应用的方法。那Restful是满足的，通过它可以实现在一个应用中调用另一个应用的方法。&lt;/p&gt;
&lt;p&gt;但是，上述理解使得RPC的定义过于宽泛。RPC通常特指在一个应用中调用另一个应用的接口而实现的远程调用，即红色框所指的范围。这样，RPC是不包含Restful的。&lt;/p&gt;
&lt;p&gt;因此，&lt;strong&gt;第二个问题的答案是Restful不属于RPC，除非对RPC有着非常规的宽泛理解&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;RPC的英文全称是Remote Procedure Call，翻译为中文叫“远程过程调用”。其中稍显晦涩的其实就是“过程”，过程其实就是方法。所以，可以把RPC理解为“远程方法调用”。&lt;/p&gt;
&lt;p&gt;要了解远程过程调用，那先理解过程调用。非常简单，如下图，就是调用一个方法。这太常见了，不多解释。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/33/pic/001_hu_5e05eeef3cb00735.webp&#34; alt=&#34;图片&#34; width=&#34;416&#34; height=&#34;93&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;而在分布式系统中，因为每个服务的边界都很小，很有可能调用别的服务提供的方法。这就出现了服务A调用服务B中方法的需求，即远程过程调用。&lt;/p&gt;
&lt;p&gt;要想让服务A调用服务B中的方法，最先想到的就是通过HTTP请求实现。是的，这是很常见的，例如服务B暴露Restful接口，然后让服务A调用它的接口。基于Restful的调用方式因为可读性好（服务B暴露出的是Restful接口，可读性当然好）而且HTTP请求可以通过各种防火墙，因此非常不错。&lt;/p&gt;
&lt;p&gt;然而，如前面所述，基于Restful的远程过程调用有着明显的缺点，主要是效率低、封装调用复杂。当存在大量的服务间调用时，这些缺点变得更为突出。&lt;/p&gt;
&lt;p&gt;服务A调用服务B的过程是应用间的内部过程，&lt;strong&gt;牺牲可读性提升效率、易用性是可取的&lt;/strong&gt;。基于这种思路，RPC产生了。&lt;/p&gt;
&lt;p&gt;通常，RPC要求在调用方中放置被调用的方法的接口。&lt;strong&gt;调用方只要调用了这些接口，就相当于调用了被调用方的实际方法，十分易用&lt;/strong&gt;。于是，调用方可以像调用内部接口一样调用远程的方法，而不用封装参数名和参数值等操作。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/33/pic/002_hu_57955f25c0da7b55.webp&#34; alt=&#34;图片&#34; width=&#34;511&#34; height=&#34;287&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;那要想实现这个过程该怎么办呢？别急，咱们一步一步来。&lt;/p&gt;
&lt;p&gt;首先，调用方调用的是接口，必须得为接口构造一个假的实现。显然，要使用动态代理。这样，调用方的调用就被动态代理接收到了。&lt;/p&gt;
&lt;p&gt;第二，动态代理接收到调用后，应该想办法调用远程的实际实现。这包括下面几步：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;识别具体要调用的远程方法的IP、端口&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;将调用方法的入参进行序列化&lt;/li&gt;
&lt;li&gt;通过通信将请求发送到远程的方法中&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这样，远程的服务就接收到了调用方的请求。它应该：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;反序列化各个调用参数&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;定位到实际要调用的方法，然后输入参数，执行方法&lt;/li&gt;
&lt;li&gt;按照调用的路径返回调用的结果&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;整个过程如下所示。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/33/pic/003_hu_2416b89822ebb0ab.webp&#34; alt=&#34;图片&#34; width=&#34;571&#34; height=&#34;450&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;这样，RPC操作就完成了。&lt;/p&gt;
&lt;p&gt;调用方调用内部的一个方法，但是被RPC框架偷梁换柱为远程的一个方法。之间的通信数据可读性不需要好，只需要RPC框架能读懂即可，因此效率可以更高。通常使用UDP或者TCP作为通讯协议，当然也可以使用HTTP。例如下面的示例中，为了保证实现最简单，就用了HTTP进行通信。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;讲到这里，RPC的产生原因、原理应该清楚了。为了让大家真的明白，我写了一个真的是最最简单的RPC实现。把它放到了：&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/yeecode/EasyRPC&#34;&gt;https://github.com/yeecode/EasyRPC&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;​它包含一个客户端，一个服务端。客户端只要调用自身内部的接口，就通过这个小的RPC实现调用到了服务端的方法。&lt;/p&gt;
&lt;p&gt;下面是客户端的代码，看着类有点多，其实代码不长。其中的RPC代码完成完成动态代理、远程调用参数序列化、远程调用发起、远程调用结果反序列化的工作。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/33/pic/client_hu_8d3a469eab3f2b68.webp&#34; alt=&#34;图片&#34; width=&#34;478&#34; height=&#34;347&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;下面是服务端的代码，代码更少，完成远程调用接收、调用参数反序列化、调用实际触发、调用结果序列化的工作。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/33/pic/server_hu_59da3ef9910ac39.webp&#34; alt=&#34;图片&#34; width=&#34;466&#34; height=&#34;242&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;这样，一个RPC小框架就做完了，并不复杂。&lt;/p&gt;
&lt;p&gt;所以，不要被RPC吓到，它就是让一个应用调用另一个应用中方法的一种实现方式。与调用远程接口区别不大，条条大路通罗马。&lt;/p&gt;
&lt;p&gt;再说一次，&lt;strong&gt;不是说RPC好，也不是说HTTP好，两者各有千秋&lt;/strong&gt;。本质上，&lt;strong&gt;两者是可读性和效率之间的抉择，通用性和易用性之间的抉择&lt;/strong&gt;。最终谁能发展更好，很难说。&lt;/p&gt;
&lt;p&gt;要问我站谁？&lt;strong&gt;我根据业务场景，灵活站位……&lt;/strong&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>作为一名程序员，最大的成就感来自哪里？</title>
      <link>https://yeecode.top/blog/32/</link>
      <pubDate>Sun, 08 Mar 2020 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/32/</guid>
      
      <description>&lt;p&gt;如果你还没有入门编程就不要阅读本文了。&lt;strong&gt;否则读完会让你爱上编程不能自拔&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;那麻烦可就大了！！！&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;事情发生在10年前。我那时读大二，风华正茂，正在忙着发誓。&lt;/p&gt;
&lt;p&gt;我发誓——&lt;strong&gt;再也不能丢U盘了！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这已经是我丢的第二个U盘了。&lt;/p&gt;
&lt;p&gt;U盘里有我课程课件、从同学那拷贝来的考试题库、一些没啥用的代码、几个软件安装包，还有最重要的，一些来自岛国的室内动作电影。那些电影被我放在了一个隐藏的分区中，曾陪伴我度过很多安静的夜，它们的存在使得这个U盘意义更为重大。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/32/pic/001_hu_4755eb5a8676b00d.webp&#34; alt=&#34;图片&#34; width=&#34;255&#34; height=&#34;255&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;然而，所有的一切都丢了。&lt;/p&gt;
&lt;p&gt;我等了三天，不见有人还。于是我开始从怨恨自己的粗心变为怨恨捡到U盘人的素质低下。&lt;/p&gt;
&lt;p&gt;再一再二不再三，&lt;strong&gt;我不允许自己再丢U盘了&lt;/strong&gt;。这关系到我心爱的电影和一个&lt;strong&gt;编程爱好者的尊严&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;我有全校同学的学号信息，对应每个人的姓名、学院、专业，甚至每个人的高考考号。得到这些信息并不难，且合理合法，毕竟学校某些部门的隐私意识啊，真是一言难尽。&lt;/p&gt;
&lt;p&gt;嘿嘿，真棒！&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/32/pic/002_hu_c831244c6eee178c.webp&#34; alt=&#34;图片&#34; width=&#34;720&#34; height=&#34;459&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;如果我再丢U盘，一定从名单中把捡到我U盘的人找出来。&lt;/p&gt;
&lt;p&gt;很难，因为我并不知道U盘离我而去之后，&lt;strong&gt;插在了谁的电脑上&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;但并不是不可能，因为学校的IP是固定分配的，和每个人的学号绑定。只要知道U盘所在电脑的IP，就还有希望。&lt;/p&gt;
&lt;p&gt;我需要得到学校所有人的&lt;strong&gt;IP地址和学号的对应关系表&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;我成功了，过程就不详述了。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/32/pic/003_hu_6924698810216e1b.webp&#34; alt=&#34;图片&#34; width=&#34;255&#34; height=&#34;255&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;接下来要做的就明确了：&lt;strong&gt;我需要U盘自动告诉我它现在所处的电脑的IP地址&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;我要编程，让我的U盘具有找回功能——现在的年轻人可以参照自己iphone的找回功能。&lt;/p&gt;
&lt;p&gt;是的，两者的功能几乎一样，&lt;strong&gt;有理由怀疑，苹果可能抄袭我&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;于是我打开了Visual Basic——现在的年轻人可能不熟悉，这是一个上古编程语言，配有一个高贵前卫的编辑器。就像下面这样，高贵吧，前卫吧！&lt;/p&gt;
&lt;p&gt;别抱怨，那是你自己审美不行！&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/32/pic/004_hu_75e8370b41ae8f24.webp&#34; alt=&#34;图片&#34; width=&#34;720&#34; height=&#34;513&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;在前卫的编辑器前，我写下了第一个功能模块，它能获取U盘所在电脑的IP。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/32/pic/005_hu_429cae0b56cf64f7.webp&#34; alt=&#34;图片&#34; width=&#34;537&#34; height=&#34;76&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;然后，我需要程序能够将&lt;strong&gt;读取到的IP发送给我&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;最简单的方法是在服务器上暴露一个接口，让U盘找回软件把IP信息发送到接口。&lt;/p&gt;
&lt;p&gt;而我，确实掌握着学校网站和多个学院网站的服务器（正规且合法，我是学校多个网站的开发者维护者）。但是，权利越大，责任越大，我受过的教育不允许我利用公家的服务器做私事。&lt;/p&gt;
&lt;p&gt;我想到了邮箱。免费的邮箱干嘛不用。于是我申请了一个专门的邮箱用来发送信息。就通过下面的代码。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/32/pic/006_hu_5db25344670c491c.webp&#34; alt=&#34;图片&#34; width=&#34;720&#34; height=&#34;333&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;这样，当U盘插到电脑上时，它会自动通过这个邮箱给我发送一个邮件，邮件中包含了当前电脑的IP地址。&lt;/p&gt;
&lt;p&gt;当然，我也不想U盘每次被插上电脑就给我的邮箱发邮件，这不成了给自己制造垃圾邮件。于是，我申请了一个专门的邮箱来接收这个邮件。&lt;/p&gt;
&lt;p&gt;最终，我使用上古神器编译出来了一个上古运行软件——U盘找回软件。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/32/pic/007_hu_f09614a9d7a61563.webp&#34; alt=&#34;图片&#34; width=&#34;690&#34; height=&#34;225&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;看看文件后面的修改日期，这足以证明它们诞生在我青葱帅气的年代。&lt;/p&gt;
&lt;p&gt;别问我那些奇怪的后缀名都是什么文件类型，我都忘了！！&lt;/p&gt;
&lt;p&gt;这些古董语言编译出来的古董程序，我现在那还认识啊！！！&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/32/pic/008_hu_4cee5ff3ecf001a3.webp&#34; alt=&#34;图片&#34; width=&#34;250&#34; height=&#34;261&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;接下来，还有重要的事情要做——&lt;strong&gt;让找回软件在U盘插入电脑后自动运行&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这一步的设置不难，只要新建一个txt文本文档输入下面内容保存为autorun.inf文件 放到U盘中即可。&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt; [autorun]
open=U盘找回程序.exe
icon=UDiskFinder.ico
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;但是，这样的假设太过理想。谁能保证捡到U盘的人是不是安装了什么毫无卵用但是又能阻止我好事的杀毒软件呢。&lt;/p&gt;
&lt;p&gt;杀毒软件可是会阻止U盘中的软件自动运行。&lt;/p&gt;
&lt;p&gt;于是，这个软件要伪装！&lt;/p&gt;
&lt;p&gt;**杀毒软件能防病毒，但是不能防脑残。**如果电脑主人想要作，杀毒软件可是拦不住的。&lt;/p&gt;
&lt;p&gt;于是，我把改了软件的名称——U盘解压软件。然后再放入一个起着诱人名字的加密压缩包——“隐私文件”，平时存放文件时都加密压缩有放入U盘。&lt;/p&gt;
&lt;p&gt;我还给它设计了一个图标。看它的图标，谁不认为这是一个解压软件？？&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/32/pic/009_hu_5eb5f5321098ea19.webp&#34; alt=&#34;图片&#34; width=&#34;500&#34; height=&#34;300&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;而它的界面，也是难辨真假！&lt;/p&gt;
&lt;p&gt;请忽略左下角的logo，那是我曾经的网名。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/32/pic/010_hu_4069b18d4d9724ee.webp&#34; alt=&#34;图片&#34; width=&#34;493&#34; height=&#34;351&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;甚至点开“说明”还有解压操作提示：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/32/pic/011_hu_90282ea0950de44f.webp&#34; alt=&#34;图片&#34; width=&#34;495&#34; height=&#34;354&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;点开“关于”还能看到软件信息：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/32/pic/012_hu_f6f05a1d64bd617d.webp&#34; alt=&#34;图片&#34; width=&#34;495&#34; height=&#34;353&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;毫无破绽，毫无破绽！&lt;/p&gt;
&lt;p&gt;而整个界面，都是用来麻痹敌人的伪装！&lt;/p&gt;
&lt;p&gt;它的唯一功能，就是把当前IP发送给我的邮箱！！！！&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;这样的伪装，参透了人性！！！&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;可以设想这么一幕：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;小木捡到了U盘，回到了宿舍&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;好奇心驱使下，小木把U盘插入了电脑&lt;/li&gt;
&lt;li&gt;U盘找回软件自动给我发送了小木的IP。如果有杀毒软件，那这步计划取消，执行下一步。&lt;/li&gt;
&lt;li&gt;小木看到了U盘中多个压缩包，名字实在诱人。可是却不知如何打开。于是点击了“U盘解压软件”。&lt;/li&gt;
&lt;li&gt;软件将小木的IP地址发送到了我的邮箱。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;于是，知道IP，我就找到学号；知道学号，我就能知道学院、专业、姓名；通过我广阔的人脉打听打听，我就能找到宿舍。&lt;/p&gt;</description>
    </item>
    <item>
      <title>手机里塞两个处理器芯片运算能力会不会翻倍？</title>
      <link>https://yeecode.top/blog/42/</link>
      <pubDate>Sat, 29 Feb 2020 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/42/</guid>
      
      <description>&lt;blockquote&gt;
    &lt;p&gt;就是想到现在手机厂商都在比处理器运算能力，骁龙，麒麟，苹果几乎每年都要更新高端处理器，那在一个手机里放两个处理器，是不是就两倍欢乐了？&lt;/p&gt;

  &lt;/blockquote&gt;&lt;p&gt;该提问中提到的**“运算能力”是一个比较模糊的概念**，具体而言，存在两种理解：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第一种理解：**计算能力是处理器的一种内在属性，就像质量、体积一样，不需要对外表现。**例如，时钟频率、MIPS都是其典型的衡量指标，这些指标侧重衡量处理器的内在属性。&lt;/li&gt;
&lt;li&gt;第二种理解：**计算能力是处理器的一种外在表现，要展现出来，比如能够在更短的时间内解决一个计算问题。**例如，PDR、所有的基准测试程序法都是在衡量处理器的外在表现，基本是让处理器解决一组特点问题来判断其性能好坏。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因为“运算能力”是一个模糊的概念，没有明确的定义，因此这两种理解没有对错之分。&lt;strong&gt;事实上，时钟频率、MIPS、PDR、许多基准测试程序法都是衡量处理器性能的指标，也没有进行太明确的区分。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如果题主问的是第一种理解，那显然。放两个就翻倍，再放两个再翻倍。线性增长关系。&lt;/p&gt;
&lt;p&gt;如果题主问的是第二种理解，那就不是翻倍了，做不到。&lt;/p&gt;
&lt;p&gt;而且考虑到在题目描述中，题主问“是不是就两倍欢乐了”，那题主应该是在侧重询问外在表现。因为，&lt;strong&gt;通常觉着手机用起来更欢乐了，应该是手机更流畅了，而不是得知手机CPU的晶体管数目增加了……&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;其实，一般情况下，消费者看重的是第二种理解。举例子：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;“手机晶体管数多了，我真欢乐！”× 感觉这人不太聪明的样子&lt;/li&gt;
&lt;li&gt;“手机变得更流畅了，我真欢乐！”√ 感觉像个正常人了&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以，我们就按照第二种理解详细回答这个问题。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;首先我们不考虑CPU，先考虑人。&lt;/p&gt;
&lt;p&gt;是想本来打扫卫生的活是你自己完成，用时为10分钟。&lt;/p&gt;
&lt;p&gt;然后假设是你和你同桌完成，你俩干活一样麻利，那用时就是5分钟？&lt;/p&gt;
&lt;p&gt;注意：这里每个人都不能一边干活一边说话，而且如果想看下对方正在干什么，也要停下手中的活。这样是为了确保每个人都是单核的，每个时刻只能干一件事，就和单核CPU一样。&lt;/p&gt;
&lt;p&gt;那4个人呢？8个人呢？是不是隐隐感觉不对了？&lt;/p&gt;
&lt;p&gt;那如果这样，直接找600个人，1s钟完成教室打扫工作？！&lt;/p&gt;
&lt;p&gt;显然不是。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;两个人的时候，你们俩要协调分工，讨论下谁扫地谁整理桌子。这样多出来了一些协调工作，最终会大于5分钟，所以达不到翻倍的效果。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CPU也一样啊，需要将任务进行并行拆分，然后并行计算，然后再合并结果。其中并行拆分和合并结果部分，是相比单核结算多出来的工作。是会额外消耗时间的。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;而且，这还是理想的情况。因为&lt;strong&gt;有些任务无法进行并行拆分。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;想当年，矩阵的并行计算，学术界和工程界可是费了老大劲的。矩阵的并行计算解决，才能使用多核来实现高效的协同推荐等工作啊！（这个具体我记不太清了，似乎是谷歌在这方面做了不少工作，欢迎补充）&lt;/p&gt;
&lt;p&gt;好了，以上都是定性的思考。接下来我要请出阿姆达尔了！&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Gene Amdal 在1967提出了阿姆达尔定律。&lt;/p&gt;
&lt;p&gt;阿姆达尔定律（英语：Amdahl&amp;rsquo;s law，Amdahl&amp;rsquo;s argument），一个计算机科学界的经验法则，因吉恩·阿姆达尔而得名。它代表了处理器平行运算之后效率提升的能力。&lt;/p&gt;
&lt;p&gt;首先定律中提出了加速比的概念：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/42/pic/001_hu_e8f69541ace9c4a9.webp&#34; alt=&#34;图片&#34; width=&#34;413&#34; height=&#34;110&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;然后定律讨论了升级某个模块后，系统的总执行时间：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/42/pic/002_hu_8a6cb92475ccde9f.webp&#34; alt=&#34;图片&#34; width=&#34;415&#34; height=&#34;137&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;当然，这里有一点要注意：&lt;strong&gt;这是一个经验法则，不是绝对的，但是相对准确的。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;那我们再回到这个问题，假设手机任务中，必须串行计算的任务占比$n$ ，则 $0&amp;lt;n \leq1$ 。&lt;/p&gt;
&lt;p&gt;$ 0&amp;lt;n $ 是因为即使是完全可并行的任务，也需要进行任务拆分与汇总，因为手机的设备不是并行的。例如，计算完成后，必须通过唯一的显示屏进行显示，其中必定涉及到CPU串行汇总的工作，不可能两个核同时控制一个显示屏。
$ n \leq1 $ 是因为可能存在完全串行的任务，这在理论和实践中都是可能的。例如我写了一个没有进行多核优化的程序，而现在手机只运行了这一个程序。则此时，其他的核都闲着。电脑也一样，为啥玩游戏主要看单核性能？因为许多游戏没进行多核优化，其计算没办法拆解到多个核中并发，所以，核再多也没用，一个在拼命干活，其他在……闲着……
那么，由单核升级为多核后，则升级比例为 $ 1-n $ ；一核变两核，则加速比为 $ 2 $ ，那么有：&lt;/p&gt;
$$ 总加速比=\frac{1}{n&amp;#43;\frac{1-n}{2}} ，0&amp;lt;n\leq 1 $$&lt;p&gt;则显然有&lt;/p&gt;
$$ 1\leq总加速比&amp;lt;2 $$&lt;p&gt;所以，&lt;strong&gt;手机里塞两个处理器芯片运算能力可能会提升，但是不会翻倍。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;更经典的是，这个问题可以扩展到软件开发管理领域，甚至人力资源管理领域。&lt;/p&gt;
&lt;p&gt;例如在软件开发管理领域，我们知道&lt;strong&gt;两个程序员的产出不会达到一个程序员的两倍&lt;/strong&gt;（他们总得拿出一些时间来协si调bi）。&lt;/p&gt;
&lt;p&gt;那就出现几个问题：&lt;strong&gt;如果我有一个大项目，是人越多干的越快么？如果项目很大，必须要多人完成，该如何进行分工？&lt;/strong&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>为什么大学老师教编程上课都不敲代码?</title>
      <link>https://yeecode.top/blog/37/</link>
      <pubDate>Sun, 16 Feb 2020 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/37/</guid>
      
      <description>&lt;p&gt;如果你的大学老师上课不敲代码，那他并不一定是一个坏老师。当然，也有可能是。说不定他编程水平真的很烂。&lt;strong&gt;说不定，还是一个极好的老师&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;来，我们细细分析。&lt;/p&gt;
&lt;p&gt;我的分析将会按照下面的结构展开：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;一、  软件开发的层级划分
二、  技（技术）
三、  术（架构）
四、  道（理念）
五、  老师要不要写代码
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;一-软件开发的层级划分&#34;&gt;一、 软件开发的层级划分&lt;/h2&gt;
&lt;p&gt;软件开发是一个庞大的系统。我们可以类比传统工程设计领域的概念，将软件系统领域的研究由上到下地分为三层：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;理念层面：研究软件系统开发中的理念和思想，例如研究软件的开发模型、评价指标、架构风格等。类比于传统工程领域研究建筑质量标准、研究商业区与居住区的分布关系等。&lt;/li&gt;
&lt;li&gt;架构层面：研究如何协调和组织软件系统、子系统、模块之间的关系，从而指导系统实现并提升各类质量指标。类比于规划和设计建筑物的承重结构、功能结构等，并协调各结构的关系等。&lt;/li&gt;
&lt;li&gt;技术层面：研究如何高效、可靠、经济地实现软件系统、子系统、模块等。类比于搭建建筑物中的楼梯、墙体、阳台等。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以上三个层面，&lt;strong&gt;层次逐渐降低，且上层为下层提供指导、下层为上层提供基础&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;来，让我用并不优美的笔触做个板书，如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/37/pic/001_hu_51f22839a0618d2e.webp&#34; alt=&#34;图片&#34; width=&#34;720&#34; height=&#34;399&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;这其实就是我们常说的&lt;strong&gt;道&lt;/strong&gt;、&lt;strong&gt;术&lt;/strong&gt;、&lt;strong&gt;技&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;接下来我们分别讨论三个层级。&lt;/p&gt;
&lt;h2 id=&#34;二-技技术&#34;&gt;二、 技（技术）&lt;/h2&gt;
&lt;p&gt;对于“技”这一层级，我们平时接触到的最多。在学生阶段，说一个人是大牛也多指其“技”高一筹。而且，技是上面各个层级的基础。&lt;/p&gt;
&lt;p&gt;这一层级有以下几个特点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;上手快：学习一门编程语言是不难的，尤其是你掌握几门之后，再去掌握一门新语言，那是很轻松的事情。&lt;/li&gt;
&lt;li&gt;基础依赖少：无需太多其他基础就可以。这就是为什么培训班爆发，培训班里培训的都是“技”。如果真要说基础，可能就英文不要太差吧。例如变量命名param最好不要写成canshu。&lt;/li&gt;
&lt;li&gt;面很广：技术层面包含的语言十分众多，例如Java\C++\C#\Python\Lua\PHP\JavaScript\Shell……数不胜数。因此，这里的知识面极为广。&lt;/li&gt;
&lt;li&gt;实在：指的是会就是会，不会就是不会，糊弄不了人。你看谁吹厉害了，就让他手写个代码出来，一下就露馅。&lt;/li&gt;
&lt;li&gt;受重视：因为它实在，不虚，所以大家会拼命提升这方面的能力。因此大受重视，无论是高校学生、程序员、培训班……因为面试多会考察它，不重视不行。
一般情况在学生阶段和工作几年的程序员就处于提升“技”的阶段，可以认为是“技”崇拜阶段。谁的“技”高一筹谁就是大牛，毋庸置疑。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;三-术架构&#34;&gt;三、 术（架构）&lt;/h2&gt;
&lt;p&gt;这一层级大家接触的相对少一些，尤其是学生可能接触不到。工作几年后的软件开发者会接触到这些，但也不会太多。毕竟，相对于开发者而言，成为架构师的总是少数。&lt;/p&gt;
&lt;p&gt;这一层级有以下几个特点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;需要技术基础：“技”是“术”的基础，毋庸置疑。因为“术”本身就是为了指导“技”而产生的。如果连被指导的对象都不了解，那还扯个啥啊？&lt;/li&gt;
&lt;li&gt;需要理论知识：纯技术的积累已经不能够胜任架构工作，必须了解相关的理论知识。这些理论包括纯理论、也有一些实践经验。&lt;/li&gt;
&lt;li&gt;相对虚：因为不是直接的写代码，因此相对虚一些，容易出现水货，不太容易考察。但也不是完全不能考察。&lt;/li&gt;
&lt;li&gt;面更广：所需要的知识面更广。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;有必要再说一下相对虚和需要技术基础这两点。几年前听说某个创业公司的CTO作为首席架构师，竟然不会写代码，还将其作为自己的特色。这种，绝对水货，都不会写代码你架构啥啊，最终东西怎么实施的自己都搞不清楚，那哪是架构啊，坑人啊。&lt;/p&gt;
&lt;p&gt;如果感觉一个人侃侃而谈“术”，想考察他是不是真本事，而又没时间让他架构个实在的项目。那就让他写代码！**一个优秀的“术人”（架构师），必定是一个卓越的码农！**或许细节只是有些淡忘，但是，在编辑器的提示下，代码能力比大多数程序员厉害才对。&lt;/p&gt;
&lt;h2 id=&#34;四-道理念&#34;&gt;四、 道（理念）&lt;/h2&gt;
&lt;p&gt;谈到这一块，我接触的也比较少了，还触及不到。谁让我科研阶段也是偏实践的方向呢。当然，真正做这方面研究的人确实少，而且都是赫赫有名的大牛。&lt;/p&gt;
&lt;p&gt;这一块的内容主要以论文或者会议演讲的形式对外输出。例如，哪个大神又写了什么牛逼的论文（说到这我怎么总是想起谷歌这个大神级公司），或者又在某个会议上提出了新的思路。&lt;/p&gt;
&lt;p&gt;这一层级提出的思想往往会在两年左右落实到“术”上，进而影响整个软件领域的发展。&lt;/p&gt;
&lt;h2 id=&#34;五-老师要不要写代码&#34;&gt;五、 老师要不要写代码&lt;/h2&gt;
&lt;p&gt;讨论完“技”“术”“道”，我们再来回答这个问题。&lt;/p&gt;
&lt;p&gt;我们知道，大学里有很多代码课，例如Java\C等等。但是，&lt;strong&gt;大学这些课程本质上是在教你“技”“术”“道”的结合，而不是“技”本身&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;因为，“技”很简单，你下课自己看看书就能学会。作为一个大学生，真的不难。&lt;/p&gt;
&lt;p&gt;也因为，“技”涉及的面很广，各种语言Java\C++\C#\Python\Lua\PHP\JavaScript\Shell……所以，老师PPT上第15页第4行代码最后面是不是少了一个分号真的不重要。&lt;/p&gt;
&lt;p&gt;以Java为例，上课老师天天写代码，也顶多只能教会你“技术”层级的“Java”中的橙色的那一小块知识。（而且我觉着我画的太大了……毕竟板书能力有限。）&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/37/pic/002_hu_77fadeebd681dbf.webp&#34; alt=&#34;图片&#34; width=&#34;720&#34; height=&#34;397&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;而等你工作的时候，可能进入了腾讯，然后用的是C++。&lt;strong&gt;那橙色小块的知识迁移性太差了，对你的工作用处不大&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;因此，&lt;strong&gt;老师极力要教会你的，是蓝色的竖线：“技”“术”“道”的结合&lt;/strong&gt;。在这个目的下，老师写不写代码，不重要。甚至，写代码都有些浪费课堂时间。&lt;/p&gt;
&lt;p&gt;当然，还有一个原因。老师教授的目的是“技”“术”“道”的结合，因此，可能“技”的能力不是太高。学生中可能有技高一筹的人。那老师写段代码如果出点问题，脸面也不好放啊。毕竟处在“技”崇拜阶段的学生，不太会感知到老师“术”“道”的高超。所以，有时候，不出手，更保险。&lt;/p&gt;
&lt;p&gt;当然，以上我说的是高校。如果你参加了一个Java的培训班，就是来学“技”的，结果老师一行代码都不写，和你扯写高层级的乱七八糟，不用怀疑。那人不会写代码，退钱，走人。&lt;/p&gt;
&lt;p&gt;好了，就讲这么多。&lt;/p&gt;
&lt;p&gt;希望大家以更高的视角来看大学老师的授课，来看软件开发这一领域。&lt;/p&gt;</description>
    </item>
    <item>
      <title>如何架构设计一个用户操作日志模块?</title>
      <link>https://yeecode.top/blog/30/</link>
      <pubDate>Sun, 09 Feb 2020 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/30/</guid>
      
      <description>&lt;p&gt;这是一个非常不错的题目。该题目涉及软件架构设计与开发的多个方面，具有很强的通用性。研究好这个问题对于开发能力的提升很大。&lt;/p&gt;
&lt;p&gt;今天有时间，我来解答一下这个问题。并且，最后还会附上实现代码。&lt;/p&gt;
&lt;p&gt;最终实现的效果如下所示，这是实际截取的图：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/30/pic/009_hu_997c3b9520cb0dad.webp&#34; alt=&#34;图片&#34; width=&#34;720&#34; height=&#34;578&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;整个解答包括问题定义、模型设计、方案设计、最终实现等多个环节。展现了系统架构设计的全部流程。&lt;/p&gt;
&lt;p&gt;目录如下：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;1 功能定义
2 模型设计
    2.1 上层切面
    2.2 下层切面
    2.3 混合切面
3 对象属性对比功能实现
4 对象属性处理
    4.1 普通属性
    4.2 特殊属性
    4.3 业务属性
5 易用性注解
6 存储设计
7 方案总结
8 系统实现
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;1-功能定义&#34;&gt;1 功能定义&lt;/h2&gt;
&lt;p&gt;在开发一个系统之前，我们&lt;strong&gt;先要对系统进行明确的定义&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;在一个软件系统中，通常存在增删改查四类操作。对于日志系统而已，这四类操作的处理难度不同。&lt;/p&gt;
&lt;p&gt;查询操作往往不需要记录日志，增加和删除操作涉及一个对象状态，编辑操作涉及对象编辑前和编辑后的两个状态。&lt;/p&gt;
&lt;p&gt;**编辑操作是整个日志模块中最难处理的。**只要掌握了编辑操作，则新增操作、删除操作、查询操作都很简单了。因为，新增操作可以理解为null到新对象的编辑，删除操作可以理解为旧对象到null的编辑，查询操作可以理解为旧对象到旧对象的编辑。&lt;/p&gt;
&lt;p&gt;因此，本文主要以编辑操作为例进行介绍。&lt;/p&gt;
&lt;p&gt;为了便于描述，我们假设一个学校卫生大扫除系统。&lt;/p&gt;
&lt;p&gt;这个系统中包含很多方法，例如分配大扫除工作的assignTask方法，开始某个具体工作的startTask方法，验收某个具体工作的checkTask方法，增加新的人员的addUser方法等。每个方法都有不同的参数，涉及不同的对象。&lt;/p&gt;
&lt;p&gt;以startTask方法为例，开始一个任务需要在任务中记录开始时间、责任人、使用的工具，整个方法如下：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;public String startTask(String taskId, Integer userId, Date startTime, Tool tool) {
     // 业务代码      
} 
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;最简单的记录日志的方法便是在代码中直接根据业务逻辑写入日志操作语句，例如：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;public String startTask(String taskId, Integer userId, Date startTime, Tool tool) {
    // 业务代码
    log.add(&amp;#34;操作类型：开始任务。任务编号：&amp;#34; + taskId + &amp;#34;；责任人：&amp;#34; + userId ……);
    // 业务代码
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;如果你真的打算使用上述的方法记录日志，那已经没有什么可以教你的了。&lt;/p&gt;</description>
    </item>
    <item>
      <title>HTTP协议详解</title>
      <link>https://yeecode.top/blog/25/</link>
      <pubDate>Sun, 26 Jan 2020 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/25/</guid>
      
      <description>&lt;h2 id=&#34;1-简介&#34;&gt;1 简介&lt;/h2&gt;
&lt;p&gt;HTTP是Hyper Text Transfer Protocol（超文本传输协议）的缩写。HTTP协议用于从WWW服务器传输超文本到本地浏览器的传送协议。它可以使浏览器更加高效，使网络传输减少。它不仅保证计算机正确快速地传输超文本文档，还确定传输文档中的哪一部分，以及哪部分内容首先显示(如文本先于图形)等。HTTP是一个应用层协议，由请求和响应构成，是一个标准的客户端服务器模型。HTTP是一个无状态的协议。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/25/pic/007_hu_fbb5d5f485ab9fe.webp&#34; alt=&#34;图片&#34; width=&#34;663&#34; height=&#34;425&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;11-工作流程&#34;&gt;1.1 工作流程&lt;/h2&gt;
&lt;p&gt;一次HTTP操作称为一个事务，其工作过程可分为四步：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;首先客户机与服务器需要建立连接。只要单击某个超级链接，HTTP的工作开始。&lt;/li&gt;
&lt;li&gt;建立连接后，客户机发送一个请求给服务器，请求方式的格式为：统一资源标识符（URL）、协议版本号，后边是MIME信息包括请求修饰符、客户机信息和可能的内容。&lt;/li&gt;
&lt;li&gt;服务器接到请求后，给予相应的响应信息，其格式为一个状态行，包括信息的协议版本号、一个成功或错误的代码，后边是MIME信息包括服务器信息、实体信息和可能的内容。&lt;/li&gt;
&lt;li&gt;客户端接收服务器所返回的信息通过浏览器显示在用户的显示屏上，然后客户机与服务器断开连接。 如果在以上过程中的某一步出现错误，那么产生错误的信息将返回到客户端，有显示屏输出。对于用户来说，这些过程是由HTTP自己完成的，用户只要用鼠标点击，等待信息显示就可以了。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;12-http协议特点&#34;&gt;1.2 HTTP协议特点&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;支持客户/服务器模式。支持基本认证和安全认证。&lt;/li&gt;
&lt;li&gt;简单快速：客户向服务器请求服务时，只需传送请求方法和路径。请求方法常用的有GET、HEAD、POST。每种方法规定了客户与服务器联系的类型不同。由于HTTP协议简单，使得HTTP服务器的程序规模小，因而通信速度很快。&lt;/li&gt;
&lt;li&gt;灵活：HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记。
HTTP 0.9和1.0使用非持续连接：限制每次连接只处理一个请求，服务器处理完客户的请求，并收到客户的应答后，即断开连接。采用这种方式可以节省传输时间。HTTP 1.1使用持续连接：不必为每个web对象创建一个新的连接，一个连接可以传送多个对象。&lt;/li&gt;
&lt;li&gt;无状态：HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息，则它必须重传，这样可能导致每次连接传送的数据量增大。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;2-http11的新机制&#34;&gt;2 HTTP1.1的新机制&lt;/h2&gt;
&lt;p&gt;HTTP/1.1 版本发布，只比 1.0 版本晚了半年。它进一步完善了 HTTP 协议，一直用到了20年后的今天，直到现在还是最流行的版本。&lt;/p&gt;
&lt;h2 id=&#34;21-持久连接&#34;&gt;2.1 持久连接&lt;/h2&gt;
&lt;p&gt;1.1 版的最大变化，就是引入了持久连接（persistent connection），即TCP连接默认不关闭，可以被多个请求复用，不用声明Connection: keep-alive。客户端和服务器发现对方一段时间没有活动，就可以主动关闭连接。不过，规范的做法是，客户端在最后一个请求时，发送&lt;code&gt;Connection: close&lt;/code&gt;，明确要求服务器关闭TCP连接。&lt;/p&gt;
&lt;h2 id=&#34;22-管道机制&#34;&gt;2.2 管道机制&lt;/h2&gt;
&lt;p&gt;1.1 版还引入了管道机制（pipelining），即在同一个TCP连接里面，客户端可以同时发送多个请求。这样就进一步改进了HTTP协议的效率。&lt;/p&gt;
&lt;p&gt;举例来说，客户端需要请求两个资源。以前的做法是，在同一个TCP连接里面，先发送A请求，然后等待服务器做出回应，收到后再发出B请求。管道机制则是允许浏览器同时发出A请求和B请求，但是服务器还是按照顺序，先回应A请求，完成后再回应B请求。&lt;/p&gt;
&lt;p&gt;但是同一个TCP连接里面，所有的数据通信是按次序进行的。服务器只有处理完一个回应，才会进行下一个回应。要是前面的回应特别慢，后面就会有许多请求排队等着。这称为&amp;quot;队头堵塞&amp;quot;（Head-of-line blocking）。&lt;/p&gt;
&lt;p&gt;为了避免这个问题，只有两种方法：一是减少请求数，二是同时多开持久连接。这导致了很多的网页优化技巧，比如合并脚本和样式表、将图片嵌入CSS代码、域名分片（domain sharding）等等。如果HTTP协议设计得更好一些，这些额外的工作是可以避免的。&lt;/p&gt;
&lt;h2 id=&#34;23-content-length字段&#34;&gt;2.3 Content-Length字段&lt;/h2&gt;
&lt;p&gt;一个TCP连接现在可以传送多个回应，势必就要有一种机制，区分数据包是属于哪一个回应的。这就是Content-length字段的作用，声明本次回应的数据长度。&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Content-Length: 3295
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;上面代码告诉浏览器，本次回应的长度是3295个字节，后面的字节就属于下一个回应了。&lt;/p&gt;
&lt;p&gt;在1.0版中，Content-Length字段不是必需的，因为浏览器发现服务器关闭了TCP连接，就表明收到的数据包已经全了。&lt;/p&gt;
&lt;p&gt;使用Content-Length字段的前提条件是，服务器发送回应之前，必须知道回应的数据长度。对于一些很耗时的动态操作来说，这意味着，服务器要等到所有操作完成，才能发送数据，显然这样的效率不高。更好的处理方法是，产生一块数据，就发送一块，采用&amp;quot;流模式&amp;quot;（stream）取代&amp;quot;缓存模式&amp;quot;（buffer）。&lt;/p&gt;
&lt;p&gt;因此，1.1版规定可以不使用Content-Length字段，而使用&amp;quot;分块传输编码&amp;quot;（chunked transfer encoding）。只要请求或回应的头信息有Transfer-Encoding字段，就表明回应将由数量未定的数据块组成。&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Transfer-Encoding: chunked
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;每个非空的数据块之前，会有一个16进制的数值，表示这个块的长度。最后是一个大小为0的块，就表示本次回应的数据发送完了。&lt;/p&gt;
&lt;h2 id=&#34;24-其他改进&#34;&gt;2.4 其他改进&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;1.1版还新增了许多动词方法：PUT、PATCH、HEAD、 OPTIONS、DELETE。&lt;/li&gt;
&lt;li&gt;另外，客户端请求的头信息新增了Host字段，用来指定服务器的域名。有了Host字段，就可以将请求发往同一台服务器上的不同网站，为虚拟主机的兴起打下了基础。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;接下来，我们详细介绍1.1版本的相关规范。&lt;/p&gt;
&lt;h2 id=&#34;3-客户端请求&#34;&gt;3 客户端请求&lt;/h2&gt;
&lt;p&gt;发出的请求具体格式如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/25/pic/001_hu_7cff4a9386d84dca.webp&#34; alt=&#34;图片&#34; width=&#34;625&#34; height=&#34;239&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>你的编程能力从什么时候开始突飞猛进？</title>
      <link>https://yeecode.top/blog/41/</link>
      <pubDate>Sun, 26 Jan 2020 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/41/</guid>
      
      <description>&lt;p&gt;首先我们要明白一个道理：&lt;strong&gt;编程没有什么特殊的，它是&amp;quot;科学技术-&amp;gt;技术-&amp;gt;工程技术&amp;quot;中的一个小的类别。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;明白了这一点，如何提升编程能力便变得显而易见了。因为，**关于学习科学和技术，我们经历过十几二十年的训练。**而且，这一训练是极有成效的（相比于国外的一些欢乐的训练方法，这里仅作提及，不展开争论）。&lt;/p&gt;
&lt;p&gt;这时你可以回顾自己学习某一门课时，在什么时候有突飞猛进的感觉。&lt;/p&gt;
&lt;p&gt;主要有两个阶段：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;初学阶段&lt;/strong&gt;。对，就是上课听讲。这时我们通过课本学习基本的知识，经历从无到有的质的变化，每天都能学到大量的新知识，进步神速。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;训练阶段&lt;/strong&gt;。对，就是课后练习、以考代练。这时我们主要是通过做题，或者工程实训，将学到的基本知识用到实践中，并不断地反馈纠正自身的知识，最终建立知识体系和经验。这时你会常常有恍然大悟和醍醐灌顶的感觉，“原来这定理这么用，妙啊！”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;而其他的一些复习阶段、熟练阶段，则进步较小。常常许久感觉不到什么进步。&lt;/p&gt;
&lt;p&gt;反映到编程上，也是一样的。一个人编程能力突飞猛进的阶段主要有两个：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;学习基本编程知识、环境&lt;/strong&gt;。这一阶段是一个纯粹学习的过程，例如你在学习一门编程语言的语法，或者在学习一种算法。这个阶段重在学习的知识是正确的、成体系的。这点是容易实现的，只要确保自己使用了正规的业界认可的教科书，而不是什么《二十一天学会XXX》之流，应该问题不大。这个阶段你自己也能感受到自己的进步。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不断训练纠正提升&lt;/strong&gt;。这一阶段又可以拆分为两部分：
&lt;ul&gt;
&lt;li&gt;第一部分：&lt;strong&gt;不断训练&lt;/strong&gt;。相信这一点很容易实现，毕竟有很多程序员都昼夜兼程，急速狂奔，把头发都甩在脑后了。相信这个训练量是足够的。&lt;/li&gt;
&lt;li&gt;第二部分：&lt;strong&gt;收集反馈，不断改进&lt;/strong&gt;。这一点对于许多程序员而言是极为欠缺的。**试想，你做完哪个项目后，会有专家告诉你哪个地方设计的不好，更好的设计方案是什么；你写完哪段代码后，会有大神告诉你最好是怎么写，可以获得哪些提升。**以上的这些机会是进步的机会，但是，我相信是极少的。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以，&lt;strong&gt;程序员进行了大量的编程作业，却没法收集到足够的反馈。&lt;strong&gt;这就像写完了作业，考完了试，但是没人老师给你批改，也没人告诉你正确答案。那你是很难进步的，因为&lt;/strong&gt;你不知道自己做的对不对！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;于是只能在莽撞的试错中前进，这就是成长缓慢的原因。&lt;/p&gt;
&lt;p&gt;然而，各种原因，例如“教会徒弟饿死师傅”等，外加码农领域本来就是“年轻更吃香，年老被裁员”，所以，想获得前人的反馈难上加难。&lt;/p&gt;
&lt;p&gt;但是，有一种反馈是存在的，或者，&lt;strong&gt;有一种标准答案可以被你拿来作为反馈&lt;/strong&gt;。这种答案极为标准，比你周围大神写出的代码还要优秀！&lt;/p&gt;
&lt;p&gt;对的，我说的是开源项目的源码。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;优秀开源项目的源码经过了几千人的上万次修改，极为经典。这是极好的“标准答案”，就和课后习题的标准答案一样。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;你要用这些优秀的开源源码作为答案去校准自己写的代码：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;源码的包是如何划分的，你是怎么划分的？&lt;/li&gt;
&lt;li&gt;源码的类是怎么定义的，你是怎么定义的？&lt;/li&gt;
&lt;li&gt;源码的分支是如何拆分的，你是怎么拆分的？&lt;/li&gt;
&lt;li&gt;源码的架构是怎么规划的，你是怎么规划的？&lt;/li&gt;
&lt;li&gt;源码的异常是怎么处理的，你是怎么处理的？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当你阅读源码时，你会不自觉地和自己的代码对比。在这个过程中，你就进步了。&lt;/p&gt;
&lt;p&gt;好了，&lt;strong&gt;除了“学习基本编程知识、环境”外，另一个让自己编程能力突飞猛进的途径你已经知道了——阅读和学习优秀开源源码&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;或许，&lt;strong&gt;你早就知道了&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;只是，你难以坚持去读一份源码。&lt;/p&gt;
&lt;p&gt;因为很难！真的很难。&lt;/p&gt;
&lt;p&gt;一下面对那么多的代码，一团乱麻。&lt;/p&gt;
&lt;p&gt;我曾经也站在这困难之前。&lt;/p&gt;
&lt;p&gt;那时我已经是一名高级架构师，做过网站的技术负责人，也规划实现过很多系统。然后内心一直想知道自己的架构设计、编程实现和世界上最优良的架构设计、编程实现之间的距离有多大！&lt;/p&gt;
&lt;p&gt;可是我知道，&lt;strong&gt;除了读源码，我找不到其他进步的路线了&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;于是我开始沉下心来，一个包一个包的拆解、一个类一个类的阅读、一行一行的分析。&lt;/p&gt;
&lt;p&gt;最终，我收获极大，真的极大！&lt;/p&gt;
&lt;p&gt;然后也总结了很多源码阅读的技巧、经验。&lt;/p&gt;
&lt;p&gt;而后，出版社找到了我。之后我将这些经验技巧，都被汇聚成了一本书：&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://union-click.jd.com/jdc?e=&amp;p=JF8BAMYJK1olXDYCV1ZeCU4UCl9MRANLAjZbERscSkAJHTdNTwcKBlMdBgABFksUC2wJHlgcQl9HCANtb1FqVSleXj1wFXRcAC4CXAlkWTFyXVcZbQcyVF9cCE8eBWcKG2slXQEyAjBdCUoWAm4NG14WbQcyVFlYCkMTBGcBGVkWXzYFVFdtUx55BWdbSV9AVQEBVl9fCnsnM2w4K2sVbQUyFjAKDRtHUTgNHF4VVVMHAV5YAEhAAWsLHFkXCQEDXVcOXXsVAm4MEms&#34;&gt;&lt;img src=&#34;https://yeecode.top/img/promo/mb.jpg&#34; width=&#34;450&#34; height=&#34;348&#34; loading=&#34;lazy&#34;  alt=&#34;通用源码阅读指导书-京东自营&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://union-click.jd.com/jdc?e=&amp;amp;p=JF8BAMYJK1olXDYCV1ZeCU4UCl9MRANLAjZbERscSkAJHTdNTwcKBlMdBgABFksUC2wJHlgcQl9HCANtb1FqVSleXj1wFXRcAC4CXAlkWTFyXVcZbQcyVF9cCE8eBWcKG2slXQEyAjBdCUoWAm4NG14WbQcyVFlYCkMTBGcBGVkWXzYFVFdtUx55BWdbSV9AVQEBVl9fCnsnM2w4K2sVbQUyFjAKDRtHUTgNHF4VVVMHAV5YAEhAAWsLHFkXCQEDXVcOXXsVAm4MEms&#34;&gt;《通用源码阅读指导书》&lt;/a&gt;
​
一本教你如何阅读源码，并从源码中吸取架构、编程知识的书籍。&lt;/p&gt;
&lt;p&gt;自吹自擂的话都掠过吧，大家可以自行去看书籍介绍或者目录了解更多。&lt;/p&gt;
&lt;p&gt;最后以书中前言的一段话结尾，共勉！&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/41/pic/002_hu_1ada5a55f97cc284.webp&#34; alt=&#34;图片&#34; width=&#34;720&#34; height=&#34;262&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;所以，编程的进步之道就是：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;多学，多练，但千万别忘了对答案！&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;对了，这本书也受到外方出版社的好评，并被台湾地区购买版权。接下来，将会在台湾等地发行繁体版。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/41/pic/003_hu_d75a756b3d10c58b.webp&#34; alt=&#34;图片&#34; width=&#34;720&#34; height=&#34;450&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;当然，我也在想。如果有一天，国内的计算机书籍能够发行英文版，就算是反向文化输出了，那应该厉害了。&lt;/p&gt;
&lt;p&gt;这靠我们每位从业者的努力！&lt;/p&gt;
&lt;p&gt;加油！&lt;/p&gt;</description>
    </item>
    <item>
      <title>从荒野逐鹿到智能制造</title>
      <link>https://yeecode.top/blog/24/</link>
      <pubDate>Sun, 05 Jan 2020 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/24/</guid>
      
      <description>&lt;p&gt;到底什么是智能制造？智能制造又是怎么一步步发展起来的？智能制造能给我们带来什么？ 着眼于物资和信息这两条主线，我们从茹毛饮血的原始社会，一直扯到即将到来的智能制造时代。&lt;/p&gt;
&lt;p&gt;物资，在这里指人类制造或者使用的各种产品。信息，人与人之间、人与物之间、物与物之间传达的有意义的信号。&lt;/p&gt;
&lt;h2 id=&#34;荒野逐鹿&#34;&gt;荒野逐鹿&lt;/h2&gt;
&lt;p&gt;在遥远的采集时代，祖先们过着四处游荡靠采集和捕猎过活的日子。这样的生活一直持续到农业时代，祖先们不得不定居下来，照料自己的农作物和家畜。采集时代的祖先们过着居无定所的日子，可见他们的物资并不会太丰富，否则整天的搬家一定会让他们痛苦无比。祖先可能只有一件鹿皮衣服，几块打磨过的锋利石头和上次打猎后吃剩的半只兔子。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/24/pic/001_hu_bebd8398f2dfdfd.webp&#34; alt=&#34;图片&#34; width=&#34;695&#34; height=&#34;430&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;远古的祖先们当然也需要信息的交流，只是这些交流比较简单，甚至不需要成体系的语言，简单地几种发音就可以了。例如，嗷嗷大叫一声表示看到了野兽出没，嘿嘿几下表示看到了一窝鸟蛋。&lt;/p&gt;
&lt;h2 id=&#34;物物交换&#34;&gt;物物交换&lt;/h2&gt;
&lt;p&gt;或许是祖先们打猎愈发熟练，或许是他们开展了农业种植，渐渐地他们的物资有了富余。一天，祖先老王家打猎多打了一只兔子，而老李家恰好多做了一把石锤。于是，经过一番交流，他们俩将兔子和石锤交换了。&lt;/p&gt;
&lt;p&gt;这样，本该因吃不完而腐烂浪费的兔子和闲置的石锤都排上了用场。信息的交流和物资的交换带来了价值，虽然整个过程中并没有生产新的东西。&lt;/p&gt;
&lt;h2 id=&#34;货币产生&#34;&gt;货币产生&lt;/h2&gt;
&lt;p&gt;又过了几千几万年，先人们拥有的物资变得更加丰富了。繁多的物资使得简单的物物交换无法满足人们的需要，于是出现了货币。早期的货币是通常是贝壳或者其他物件。以货币为媒介，极大地方便了物资的协调过程。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/24/pic/002_hu_49913e1a64abce33.webp&#34; alt=&#34;图片&#34; width=&#34;690&#34; height=&#34;460&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;有人的兽皮衣服做的特别好，于是他成了方圆几里内的裁缝，有需要衣服的人便可以带着货币到他家去。裁缝估量下他的身高，给他做一件衣服。而他，则可以专心打猎，将多打的猎物换成货币。&lt;/p&gt;
&lt;p&gt;其实可以把货币看做是一种信息，一种承载着承诺的信息，这种承诺是说，通过这些货币将来还能兑换到等价值的物资。有了货币这种信息的加入，物资了之间的协调变得更加顺畅。&lt;/p&gt;
&lt;h2 id=&#34;商业产生&#34;&gt;商业产生&lt;/h2&gt;
&lt;p&gt;即便有了货币，生产者与消费者之间直接的买卖操作还是存在诸多不便。老张想买一把镰头，可是认识的人中却没有人会做；老刘想卖掉自己的镰头，却不知要卖给谁。可能他们的共同好友老李知道后，便撮合了这桩生意，还得到了两人的答谢。渐渐地，老李开始专门从事买卖撮合工作，收取其中的好处，于是成了商人。&lt;/p&gt;
&lt;p&gt;商人的出现使得买卖双方无需直接见面，促进了信息的交流，降低了物资交换的门坎。但这也带来了问题，买房和卖方不能直接见面，怎么保证卖方的商品能恰好满足买方的需求？为了解决这个问题，制鞋的工匠会制造大小不同的鞋子，供应给商人，而顾客则到商人那里选择适合自己的哪一款。&lt;/p&gt;
&lt;p&gt;即便是最精明的商人也无法预测哪款鞋子能卖多少只，因此进货时要留有余量，于是产生了库存问题。有一些款式供不应求，也总有很多款式囤积在仓库卖不出去。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/24/pic/003_hu_b04c68b01157e08b.webp&#34; alt=&#34;图片&#34; width=&#34;430&#34; height=&#34;345&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;商人并没有创造物资，但是却极大地促进了物资的合理利用，并在整个过程中带来了价值，商人也便能在其中分得一羹。&lt;/p&gt;
&lt;h2 id=&#34;工业时代&#34;&gt;工业时代&lt;/h2&gt;
&lt;p&gt;在商人充当媒介的情况下，为了保证买方和卖方顺畅的交流，渐渐出现了一些约定俗成的规范。例如，从东市买来的镰刀头不用太费劲就可以装到西市买来的镰刀木把上，因为它们接口处的直径是相仿的。&lt;/p&gt;
&lt;p&gt;如果说以上的这种约定俗成算是标准化的雏形的话，那工业时代的流水线将这种标准化发挥到了极致。每一颗螺丝都有固定的型号，每一次组装都有固定的流程。也正因为流水线的出现，人类的生产效率急剧提升。福特的第一条流水线让汽车开始走进了千家万户。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/24/pic/004_hu_8e910f018b7cfa9.webp&#34; alt=&#34;图片&#34; width=&#34;500&#34; height=&#34;395&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;当然，即便社会生产发展到这个阶段，非流水线的定做产品依然存在。只是它们因为成本高昂而成了奢侈的象征。&lt;/p&gt;
&lt;h2 id=&#34;智能制造&#34;&gt;智能制造&lt;/h2&gt;
&lt;p&gt;如今，同一版本同一型号的手机，几乎是一模一样的。当我们说去选购商品，其实是在有限的型号中选择我们需要的那一款。可是，人的需求千变万化，为什么偏偏要归结到几个固定的型号上去呢？每个人不应该根据自己的需求，定义自己的产品么？可是，这又怎么做得到呢？&lt;/p&gt;
&lt;p&gt;当物质急剧匮乏时，物资相互之间的配合显得不是那么重要。而当物资变得极为丰富时，只要能让它们能被利用的稍微合理些，就能产生巨大的价值。例如，如果卖方能够实时知道市场需要的商品的确切数量，库存问题不就解决了，这会避免巨大的资源浪费。而且，如果买卖双方之间的信息足够通畅，负责进行信息协调的商人似乎也没有存在的必要了。可这，这又怎么做的到呢？&lt;/p&gt;
&lt;p&gt;当问出以上的两个问题，大家似乎也有了答案，答案就是智能制造。&lt;/p&gt;
&lt;p&gt;信息化让我们渐渐地有能力解决以上问题，也让我们渐渐地回到了遥远的过去，回到了买卖双方直接交流、物资协调更为便利的古代，毕竟，那时展露的才是我们需求的本质。愈发丰富的物资让着它们变得不可能，而高速发展的信息化又渐渐地使它们变得可能。&lt;/p&gt;
&lt;blockquote&gt;
    &lt;p&gt;智能制造，它意味着在产品生命周期内对整个价值创造链的组织和控制再进一步，即意味着从创意、订单到研发、生产、终端客户产品交付，再到废物循环利用，包括与之紧密联系的各服务行业，在各个阶段都能更好满足日益个性化的客户需求。所有参与价值创造的相关实体形成网络，获得随时从数据中创造最大价值流的能力，从而实现所有相关信息的实时共享。以此为基础，通过人、物和系统的连接，实现企业价值网络的动态建立、实时优化和自组织，根据不同的标准，对成本、效率和能耗进行优化。 ——《工业4.0战略实施计划》&lt;/p&gt;

  &lt;/blockquote&gt;&lt;p&gt;在物资贫瘠的时代，人类的主要工作是创造更多的物资；而当物资充盈的时候，人类终于有精力来满足每个人个性化的需求。&lt;/p&gt;
&lt;p&gt;在物资贫瘠的时代，物资之间的协调很简单；而当物资极度丰富时，这一切变得困难至极，但只要能对协调的过程进行些许的优化，就可能释放巨大的价值。&lt;/p&gt;
&lt;p&gt;从荒野逐鹿的时代梳理过来，我们发现，智能制造并非天方夜谭，是人类生产力提升而将步入的必然阶段。在这新的阶段中，依赖信息化，人们的个性化需求开始得到满足，物资之间的利用变得更为合理。在逐步解决了物资贫瘠的问题之后，我们开始利用信息来优化物资的流转过程。&lt;/p&gt;
&lt;p&gt;智能制造，未来已来。&lt;/p&gt;</description>
    </item>
    <item>
      <title>代码和咒语是什么关系？</title>
      <link>https://yeecode.top/blog/40/</link>
      <pubDate>Sun, 22 Dec 2019 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/40/</guid>
      
      <description>&lt;p&gt;“师傅，&lt;strong&gt;代码和咒语是什么关系？&lt;/strong&gt;”思明站在我身后，问道。&lt;/p&gt;
&lt;p&gt;落叶，无声。&lt;/p&gt;
&lt;p&gt;在这料峭的初冬。&lt;/p&gt;
&lt;p&gt;我站在房檐下，看着对面山坡的竹林。&lt;/p&gt;
&lt;p&gt;竹浪绵延起伏。&lt;/p&gt;
&lt;p&gt;他悟了，比我早了八年。我问出这个问题的时候已经三十五岁，而我身后的思明尚不过而立之年。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;我三十五岁那年，已经是一名经验丰富的软件架构师，做过不少的项目，也写过不少的代码。而再往前推，我则是一名自动化专业的学生。那是一个软硬件均涉及的“万金油”专业。&lt;/p&gt;
&lt;p&gt;所以，我不仅能够用键盘敲击出代码，还知道代码如何被编译，然后用来控制每一个晶体管、继电器，从而发挥作用。&lt;/p&gt;
&lt;p&gt;而有一种代码，已经几近失传，但却能体现代码的究极含义——步进逻辑——暂且就叫这个名字。这是我的师傅传授给我的，那是一种纯用继电器、限位开关组建自动化流水线的方式。在这种方式中，代码本身是不存在的，但它又是存在的。它转化为继电器和限位开关之间的连接方式。&lt;/p&gt;
&lt;p&gt;而再往上抽象一层，代码就出现了。我们可以把这些连接方式全用继电器的开合表达出来，于是便成了PLC编程。而PLC编程语言，则和现在的编程语言Java很像了，至少和Java1.5很像。毕竟现在Java已经发展到了版本76，我也不是很熟悉了。&lt;/p&gt;
&lt;p&gt;所以，三十五岁之前的我就认为代码是虚拟世界和现实世界的连接，如果用编程语言来描述的话，&lt;strong&gt;代码就是现实世界给虚拟世界提供的接口&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;一切逻辑都是自洽的，而且是唯一的。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;然而，三十五岁的一天，一切都改变了。&lt;/p&gt;
&lt;p&gt;唯一的逻辑被打破，我的世界观崩塌了！&lt;/p&gt;
&lt;p&gt;我发现，物理世界给虚拟世界提供的接口不仅仅有代码，还有咒语。&lt;/p&gt;
&lt;p&gt;那天，无意间，我说出了一句咒语，一种仅存在传说中的东西。&lt;/p&gt;
&lt;p&gt;然后，我听到耳畔传来应答之声，那一刻我知道，咒语生效了。&lt;/p&gt;
&lt;p&gt;而事实上也确实如此，风愈劲，气愈冷，气动山河。&lt;/p&gt;
&lt;p&gt;而我，则愣在那里久久不能移动，汗流浃背。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;思明见我没有说话，便继续毕恭毕敬地站在我的身后。&lt;/p&gt;
&lt;p&gt;“&lt;strong&gt;如果人类文明的进步就像是一只树上攀爬的蜗牛，那你觉着，人类能爬到树的顶端么？&lt;/strong&gt;”我问他。&lt;/p&gt;
&lt;p&gt;“师傅，这棵树是分叉的么？”&lt;/p&gt;
&lt;p&gt;“对的，遍布树杈。”&lt;/p&gt;
&lt;p&gt;“能，但是很难。因为每到一个树杈，便很难预测哪个树杈会延伸的更高。所以，爬到顶端会很难……至少，一次爬到最高点很难，需要不断遍历和尝试。”他回答道。&lt;/p&gt;
&lt;p&gt;虽然他现在跟我学习咒语，但是我也经常教他编程的知识。所以，这样的问题难不住他。&lt;/p&gt;
&lt;p&gt;“可是，如果人类已经文明已经进步到了某个阶段，又怎么会愿意倒退到上一个分叉点，重新开始呢？”我悠悠地说道。&lt;/p&gt;
&lt;p&gt;“师傅，您是说，&lt;strong&gt;人类的文明，可能在很早的阶段就选错了分支，然后可能永远无法到达顶点？&lt;/strong&gt;”思明很快就领悟了我的意思。&lt;/p&gt;
&lt;p&gt;“不仅仅是分支吧，可能直接就选错了树木。&lt;strong&gt;或许人类选择了参天大树旁边的一颗低矮灌木攀爬了起来。&lt;/strong&gt;”&lt;/p&gt;
&lt;p&gt;思明不说话，我知道他在思考。&lt;/p&gt;
&lt;p&gt;“为什么你我非要借助外力才能腾空，为什么信息的传播速度突破不了光速，为什么世界的熵总有增加的倾向……或许这一切，在人类选择这棵科技树时，就已经决定了。”我继续说道。&lt;/p&gt;
&lt;p&gt;“那，咒语？”&lt;/p&gt;
&lt;p&gt;“&lt;strong&gt;咒语或许是另一棵树。它前期可能很曲折，难以获得太大的进步，甚至被认为是荒谬的。但，说不定，它才是真正的参天大树。&lt;/strong&gt;”&lt;/p&gt;
&lt;p&gt;“掌握了咒语，或许你我可以凭空而起，信息可以瞬间传递，世界可能倾向熵减……”我接着说道。&lt;/p&gt;
&lt;p&gt;“师傅，我懂了！您是说，咒语可能和代码，不，确切说，魔法可能和科学一样，都是供人类进步的树木，只是人类现在选择了科学这一棵树而已？”&lt;/p&gt;
&lt;p&gt;我转过身，看着他，坚毅地向他点了下头。&lt;/p&gt;
&lt;p&gt;“师傅，这就是您当年决定归隐到这里研究魔法的原因吧？”&lt;/p&gt;
&lt;p&gt;“对，**我想替人类攀爬另一个树枝或者另一棵树。**那可能是一棵更高的树，或许也更难攀爬。”我的视线越过思明的头顶，空洞地投向房檐下露出的一小块天空。&lt;/p&gt;
&lt;p&gt;“我已经年近古稀，尚未取得太大的成果。而你，悟的比我早。”&lt;/p&gt;
&lt;p&gt;“师傅，全依仗您的教导。”&lt;/p&gt;
&lt;p&gt;我转回头去，继续看着对面上的竹林。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;“可是，师傅。目前来看，科学这条道路完全是对的，创造了极其众多的结果。”&lt;/p&gt;
&lt;p&gt;“对的，它是对的。只是不一定是唯一对的。&lt;strong&gt;就如同每一个树枝都通往高处，但并不是每一个树枝都能是最高的。&lt;/strong&gt;”&lt;/p&gt;
&lt;p&gt;“&lt;strong&gt;假设世界的真理是一间房子，而科学则是房子的一扇窗户。我们从科学这扇窗户看进去，看到了一切道理都是对的。但这不代表没有另一扇窗户。从那扇窗户看进去，将是全新的视角，也全是对的。&lt;/strong&gt;”怕他不明白，我继续解释道。&lt;/p&gt;
&lt;p&gt;“师傅，这就是您说的‘&lt;strong&gt;道者实存，然吾之所谓道，皆为虚妄。&lt;/strong&gt;[注]’”&lt;/p&gt;
&lt;blockquote&gt;
    &lt;p&gt;[注] 出自《道道道》，易哥著，2048年户说芭岛出版社出版。书名意思为“在路上说些道理”。这句话的意思为：世界运行的真理是存在的，但是我们所说的真理，却只是从我们的观察角度得出的结论。&lt;/p&gt;

  &lt;/blockquote&gt;&lt;p&gt;“对，而且，&lt;strong&gt;除了科学以外的另一个不一定是窗户，说不定是一扇门。我们可以走进去，直接修改世界的真理。&lt;/strong&gt;”&lt;/p&gt;
&lt;p&gt;思明听了，不说话。&lt;/p&gt;
&lt;p&gt;任凭落叶在我们面前静静飘落。&lt;/p&gt;
&lt;p&gt;“那师傅，当年您为什么选我为弟子呢？”&lt;/p&gt;
&lt;p&gt;“因为你的父亲！”&lt;/p&gt;
&lt;p&gt;“我父亲？”&lt;/p&gt;
&lt;p&gt;“对的，在一个古老的叫知乎的论坛上，我见过你父亲的提问‘代码和咒语是什么关系？’，然后我觉着，或许他的思想会影响你。你继续我的研究再合适不过了。”&lt;/p&gt;
&lt;p&gt;“是的，我父亲是一个博学且充满好奇的人，直到现在，也支持我的研究。”&lt;/p&gt;
&lt;p&gt;“但，这将是一份艰苦的工作。&lt;strong&gt;得道之前，皆为异端。&lt;/strong&gt;”说出这句话时，我知道他能联想起古老的布鲁诺的故事。&lt;/p&gt;
&lt;p&gt;“可是师傅，您已经取得了不少的成果。”&lt;/p&gt;
&lt;p&gt;“不够，远远不够。希望在你身上。”&lt;/p&gt;
&lt;p&gt;“我身上？”&lt;/p&gt;
&lt;p&gt;“对的，在你身上。你比我发现代码和咒语的关系早了八年，或许你提前的这八年，会让你在有生之年有所收获。”&lt;/p&gt;
&lt;p&gt;思明感觉到了身上的担子。&lt;/p&gt;
&lt;p&gt;“就是这样，&lt;strong&gt;一代代传承，一代代提炼，最终经过几十年上百年，才会积累出成果。人类的认知就是这样传承的。&lt;/strong&gt;”&lt;/p&gt;
&lt;p&gt;对面山上的竹浪绵延起伏。&lt;/p&gt;
&lt;p&gt;“弟子一定珍惜时间，不断精进！”&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;时间似乎只带着风掠过竹林，而遗漏了其他的一切。&lt;/p&gt;
&lt;p&gt;竹林如海，波涛起伏。&lt;/p&gt;
&lt;p&gt;不知过了多久，思明再一次开口。&lt;/p&gt;
&lt;p&gt;“师傅，弟子想知道，您当时是如何悟出这人类文明之道的？”&lt;/p&gt;
&lt;p&gt;“因为我无意间说出了一句咒语。”&lt;/p&gt;
&lt;p&gt;“是哪一句？”&lt;/p&gt;
&lt;p&gt;我教过他很多咒语，有的威力无比，有的聊胜于无。他显然很想知道我第一次说出的咒语是哪一句。&lt;/p&gt;</description>
    </item>
    <item>
      <title>单片机与PLC的异同</title>
      <link>https://yeecode.top/blog/23/</link>
      <pubDate>Sun, 17 Feb 2019 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/23/</guid>
      
      <description>&lt;p&gt;许多人在接触到单片机（MCU）和可编程逻辑控制器（PLC）时，都会感到疑惑。疑惑主要有两点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;工业环境中，很多控制逻辑用MCU都可以很容易的实现，为何要用PLC？&lt;/li&gt;
&lt;li&gt;其实MCU与PLC两者都是可编程的控制器件，有什么不同？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;此文便在于解答大家的疑惑。&lt;/p&gt;
&lt;h2 id=&#34;1-工业环境为何都用plc&#34;&gt;1 工业环境为何都用PLC&lt;/h2&gt;
&lt;p&gt;接触过工业生产的工程师可能会发现，其实大多数工业生产的控制要求，使用MCU都可以做到。&lt;/p&gt;
&lt;p&gt;但同时又会发现，MCU在工业环境中极少被应用，取而代之的是PLC。那为什么在廉价的MCU可以实现功能的地方，要使用昂贵的PLC呢？&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/23/pic/001_hu_a403e70673c71bd4.webp&#34; alt=&#34;图片&#34; width=&#34;590&#34; height=&#34;400&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;11-可靠性方面&#34;&gt;1.1 可靠性方面&lt;/h2&gt;
&lt;p&gt;工业现场要求设备达到工业级。我们一般购买的单片机及其他元器件一般为商业级。加之自己开发后测试等水平达不到，所以可靠性很难保证，例如防电磁干扰等，要知道工业环境中电磁干扰是非常多的，如继电器的通断、电机的起停，这都可能导致单片机的重启、通讯的失误。&lt;/p&gt;
&lt;p&gt;而PLC等设备满足工业级要求，在抗干扰方面、输入输出点可靠性方面更为优越，并采用可靠的组态方式，因此可靠性更高，而且通信方面有很多工业控制设备采用光纤以抗干扰。&lt;/p&gt;
&lt;h2 id=&#34;12-易用性方面&#34;&gt;1.2 易用性方面&lt;/h2&gt;
&lt;p&gt;采用单片机实现，通信等系统架构太过麻烦，例如一个工业车间的几十个单片机工作小站可靠通信就很难实现。同时代码的增加也进一步降低了可靠性。在简单并且不关键的部位可采用单片机实现部分功能。&lt;/p&gt;
&lt;p&gt;而PLC等设备可采用整体式或者积木式，各个组件可以按需选用，并便于组态实现。各个工作站之间采用规范的总线通讯，这都是模块内置的，只需配置无需编写代码，可靠简单。&lt;/p&gt;
&lt;p&gt;工业设备的可靠性是极为关键的，关系到人身安全、连续工作的时间、生产的质量等，这时候生产设备的成本是相对次要的，可靠性才是首要的。因此单片机虽然便宜，但用单片机作为工业设备控制器是舍本逐末的。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/23/pic/002_hu_550cf13c4c584400.webp&#34; alt=&#34;图片&#34; width=&#34;720&#34; height=&#34;598&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;当然，也并不是说单片机在工业环境中就完全没有应用。例如，目前有采用Arduino进行工业环境和设备运行情况监测的应用，但不涉及关键生产过程的控制。相信以后也会有更多的单片机应用到工业环境中，但也不会作为核心的控制器。&lt;/p&gt;
&lt;h2 id=&#34;2-mcu与plc到底有何异同&#34;&gt;2 MCU与PLC到底有何异同?&lt;/h2&gt;
&lt;p&gt;那究竟是何种原因导致了PLC和MCU的不同呢？假设，你要用一款MCU完成工业现场的核心控制任务，那应该怎么做？&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/23/pic/003_hu_de6424390e6b0f05.webp&#34; alt=&#34;图片&#34; width=&#34;544&#34; height=&#34;362&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;首先，工业环境下，受周围交流电机启停等的影响，电压会有较大的波动。为了应对这种恶劣的环境，必须为MCU设计相应的电源模块，是的电压剧烈变动时不会引发MCU的工作异常。&lt;/p&gt;
&lt;p&gt;同时，在工业环境下，传感器的输出多采用4&lt;del&gt;20mA、0&lt;/del&gt;10V的形式，这与MCU常用的0&lt;del&gt;3.3V或者0&lt;/del&gt;5V的AD模块不相符合，因此需要针对工业环境设计传感信号接收模块。&lt;/p&gt;
&lt;p&gt;并且，工业环境的输出很多是用来控制继电器（经过继电器隔离后，再去控制电机等的启停），而MCU的输出只能控制微型继电器，无法驱动常用的工业继电器，因此，要在MCU外围设计具有大驱动能力的IO接口。&lt;/p&gt;
&lt;p&gt;同时，为了保证工业环境下通讯的可靠：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;有很多工业数据总线协议。为了能保证MCU与外界正常通讯，需要封装模块来支持这些协议；&lt;/li&gt;
&lt;li&gt;为了能够控制变频器，需要设计相应的变频器控制模块；为了能保证一定的防尘、防水级别，需要设计外壳满足相应的防护等级；&lt;/li&gt;
&lt;li&gt;为了保证编程的简单性，可能需要设计一套便于工业使用的控制语言……&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当以上这些工作都完成的时候，一个PLC就诞生了！&lt;/p&gt;
&lt;p&gt;所以，PLC就是在MCU基础上，针对工业环境专门设计的产物。PLC的内部就是一款MCU。&lt;/p&gt;
&lt;p&gt;到这里，PLC与MCU的异同，大家也应该明了了。&lt;/p&gt;</description>
    </item>
    <item>
      <title>智能计算浅思</title>
      <link>https://yeecode.top/blog/22/</link>
      <pubDate>Sun, 03 Feb 2019 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/22/</guid>
      
      <description>&lt;p&gt;个人粗鄙地认为，计算智能可以分为三个阶段：&lt;/p&gt;
&lt;h2 id=&#34;1-第一阶段&#34;&gt;1 第一阶段&lt;/h2&gt;
&lt;p&gt;固定程序实现有规律的工作，例如专家系统。&lt;/p&gt;
&lt;p&gt;给程序一个输入，它给出一个固定的输出。该阶段程序可以代替人类完成有规律的重复劳动。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/22/pic/001_hu_37a69d97c8227408.webp&#34; alt=&#34;图片&#34; width=&#34;640&#34; height=&#34;426&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;2-第二阶段&#34;&gt;2 第二阶段&lt;/h2&gt;
&lt;p&gt;计算智能的第二阶段是程序可以在运行中修正自身参数，例如现在正火的深度学习技术等。&lt;/p&gt;
&lt;p&gt;给程序一个输入，它可以给出一个输出，并在此过程中修正自身参数，因此给出的输出可能是人类计算难以甚至不能预测的。&lt;/p&gt;
&lt;p&gt;目前的人工智能即处在这一阶段。&lt;/p&gt;
&lt;p&gt;该阶段程序可以找出规律的细节规则，并代替人类劳动。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/22/pic/002_hu_c44e1fe8355a836f.webp&#34; alt=&#34;图片&#34; width=&#34;640&#34; height=&#34;426&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;3-第三阶段&#34;&gt;3 第三阶段&lt;/h2&gt;
&lt;p&gt;程序可以自我生成、修正程序逻辑本身。&lt;/p&gt;
&lt;p&gt;例如程序在运行时发生逻辑分支的增删等，直至可以生成程序自身。&lt;/p&gt;
&lt;p&gt;目前有最新技术可以让程序自动生成简短的小程序，但是是基于从网上拷贝程序片段实现，离第三阶段要求还很远。&lt;/p&gt;
&lt;p&gt;该阶段程序可以总结规律本身，并代替人类劳动。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/22/pic/003_hu_8f3e157c4fb535ae.webp&#34; alt=&#34;图片&#34; width=&#34;640&#34; height=&#34;426&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;4-总结&#34;&gt;4 总结&lt;/h2&gt;
&lt;p&gt;假设计算智能系统为f(ax)，则三个阶段可以概述为：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第一阶段：f(ax)固定不可变&lt;/li&gt;
&lt;li&gt;第二阶段：参数a可自动产生并修正&lt;/li&gt;
&lt;li&gt;第三阶段：函数f可自动产生并修正&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;未来可期，未来很远。&lt;/p&gt;</description>
    </item>
    <item>
      <title>Java原子化读并且写操作中存在的问题</title>
      <link>https://yeecode.top/blog/21/</link>
      <pubDate>Sun, 27 Jan 2019 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/21/</guid>
      
      <description>&lt;h2 id=&#34;背景&#34;&gt;背景&lt;/h2&gt;
&lt;p&gt;之前的文章中我们已经讲过，Java的AtomicInteger类中能够将读和写封装成为一个原子操作，例如其中的getAndIncrement()方法就可以实现原子化的i++操作。这一切的实现是通过系统原生的CAS操作实现的。&lt;/p&gt;
&lt;p&gt;CAS操作即比较并交换操作，能够在内存真值与预期原值一样时，将新值放入指定的内存中。&lt;/p&gt;
&lt;p&gt;本文我们探讨基于CAS操作实现的读写原子化中引发的问题。&lt;/p&gt;
&lt;h2 id=&#34;cas存在的问题&#34;&gt;CAS存在的问题&lt;/h2&gt;
&lt;p&gt;CAS实现了高效的原子操作，但是仍然存在一些问题，主要有三个：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ABA问题&lt;/li&gt;
&lt;li&gt;循环开销问题&lt;/li&gt;
&lt;li&gt;无法应用与多个共享变量&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;aba问题&#34;&gt;ABA问题&lt;/h2&gt;
&lt;p&gt;因为CAS需要在操作值的时候检查下值有没有发生变化，如果没有发生变化则更新，但是如果一个值原来是A，变成了B，又变成了A，那么使用CAS进行检查时会发现它的值没有发生变化，认为情况还是乐观的。此时可能引发错误。&lt;/p&gt;
&lt;p&gt;例如，时刻t1在队列中CAS操作前获取预期原值A=5，之后队列发生了移动，再次获取的原值仍然为5，此时进行了CAS操作。则会引发错误。&lt;/p&gt;
&lt;p&gt;【1-3】
ABA问题的解决思路就是使用版本号。在变量前面追加上版本号，每次变量更新的时候把版本号加一，那么A－B－A 就会变成1A-2B－3A。Java1.5开始，atomic包中存在一个AtomicStampedReference类来解决ABA问题。
该类的compareAndSet方法会先检查当前引用是否发生变化，如果没有变化才会使用CAS更新其值。&lt;/p&gt;
&lt;p&gt;源代码如下：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;public boolean compareAndSet(V   expectedReference,
                                 V   newReference,
                                 int expectedStamp,
                                 int newStamp) {
        Pair&amp;lt;V&amp;gt; current = pair;
        return
            expectedReference == current.reference &amp;amp;&amp;amp;
            expectedStamp == current.stamp &amp;amp;&amp;amp;
            ((newReference == current.reference &amp;amp;&amp;amp;
              newStamp == current.stamp) ||
             casPair(current, Pair.of(newReference, newStamp)));
    }
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;循环开销问题&#34;&gt;循环开销问题&lt;/h2&gt;
&lt;p&gt;因为CAS是基于乐观锁的思想的，需要不断判断乐观情况是否成立，因此是一个循环操作，常被称为CAS自旋。如果并发严重，则CAS自旋会不断尝试，导致CPU开销大。&lt;/p&gt;
&lt;p&gt;解决此问题的办法是在多次CAS操作失败时，能够暂停一段时间，在进行CAS操作。防止在其他线程密集修改某变量时对该变量不断进行CAS自旋。当然，这需要JVM的支持。&lt;/p&gt;
&lt;h2 id=&#34;无法应用于多个共享变量&#34;&gt;无法应用于多个共享变量&lt;/h2&gt;
&lt;p&gt;对一个共享变量进行CAS操作时可以的，那如果对一组变量展开操作呢？显然是不可以的，因为内存位置V是一个值，而非一组值。这个时候只能使用锁。&lt;/p&gt;
&lt;p&gt;其实，还有一个办法，即将这一组变量封装成一个对象，从而将对一组变量的操作转化为对一个对象的操作。Java1.5之后，便可以使用AtomicReference来封装一个需要原子化更新的对象。&lt;/p&gt;
&lt;h2 id=&#34;总结&#34;&gt;总结&lt;/h2&gt;
&lt;p&gt;虽然，AtomicInteger类中的原子化操作存在一些问题，但是它与加同步锁的方法相比仍然在性能、易用性上具有巨大的优势。希望大家能够在日常的编码中掌握并使用AtomicInteger类中的相关方法，写出简洁、高效的代码。&lt;/p&gt;</description>
    </item>
    <item>
      <title>Java为何能将读与写封装为一个原子操作</title>
      <link>https://yeecode.top/blog/20/</link>
      <pubDate>Sun, 13 Jan 2019 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/20/</guid>
      
      <description>&lt;p&gt;在编程中，我们知道如果对一个变量读取然后写入这并不是一个原子操作，例如我们常用的i++操作，其实可以简单划分为以下三步：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;1 读入i变量
2 对i变量进行+1操作
3 将新的i值写入
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;【1-1】&lt;/p&gt;
&lt;p&gt;以上三步操作时独立的，因此并不是原子化的。如果i变量在第1步和第3步之间被其他线程更改则会引发意向不到的结果。例如线程1对i=5进行i++操作时，线程2对变量i进行了写入操作，则还会发生混乱。如图所示：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;线程1 读入i变量，i=5
线程2 对i写入新的值：i=3
线程1 对变量i进行+1操作，i=6
线程1 对i进行写入：i=6
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;【1-2】
此时我们看到，因为i++操作不是原子的，中间受到线程2的影响，使得i++的操作是错误的。如果是原子化操作，则i++操作要么发生在线程2的操作之前，最终得到i=3；要么发生在线程2操作之后，得到i=4。而不能出现i=6这样的结果。&lt;/p&gt;
&lt;p&gt;其实i++就是一个最简单的汇聚了读取和写入的操作，因此读取并写入操作并不是原子化的。可是，Java中确实能够将读取并写入操作封装为一个原子化操作。那它是怎样实现的呢？&lt;/p&gt;
&lt;p&gt;我们先介绍一种粗暴的实现：使用同步块实现原子化。&lt;/p&gt;
&lt;h2 id=&#34;使用同步块实现原子化&#34;&gt;使用同步块实现原子化&lt;/h2&gt;
&lt;p&gt;为了能实现i++操作的原子化，则最容易想到的就是使用同步块：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;synchronized int selfIncrement(int value){
    return value++;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;但这显然大材小用，将整个操作放置在同步块中，虽然借助同步块实现了原子化，但因为同步块的操作要进行锁的分配等，使得程序的整体效率受到了影响。&lt;/p&gt;
&lt;p&gt;而我们知道Java中AtomicInteger类可以直接将读和写封装在一个原子操作中实现原子化的i++等操作，那它们是怎么实现的呢？我们接下来进行探讨。&lt;/p&gt;
&lt;h2 id=&#34;硬件cas操作&#34;&gt;硬件CAS操作&lt;/h2&gt;
&lt;p&gt;Java中AtomicInteger类便可以将读和写封装在一个原子操作中。那它是怎么实现的呢？&lt;/p&gt;
&lt;p&gt;它使用的是CAS操作。CAS是compare and swap的缩写，中文可以翻译成：比较并交换。CAS操作来源于底层硬件领域。因为CAS能够极大地提高并发效率，因此在硬件设计领域，CAS这种操作就是存在的。例如在intel的CPU中，使用cmpxchg指令就可以实现CAS操作。我们详细介绍下CAS操作的原理：&lt;/p&gt;
&lt;p&gt;CAS操作一共包含三个操作数：内存位置(V)、预期原值(A)和新值(B)。具体操作过程如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;首先，返回V位置的原值R
&lt;ul&gt;
&lt;li&gt;如果内存位置的值R与预期原值A相匹配，那么处理器会自动将该位置值更新为新值&lt;/li&gt;
&lt;li&gt;否则，处理器不做任何操作&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;无论结果如何，该操作总能拿到V处的原值R，如果R等于A，则说明V处的值已经被更新为了B；否则说明更新失败，V处的值依旧是R。因此，经过该操作后，可以知道CAS操作是否成功。&lt;/p&gt;
&lt;p&gt;当然，还有一种CAS实现是直接返回操作是否成功的结果，即：如果内存位置的值与预期原值相匹配，那么处理器会自动将该位置值更新为新值，并返回操作成功；否则，返回操作失败。但是这与返回V位置原值的方式大同小异，而且返回原值R的实现方式不仅能获得操作是否成功的结果，还能顺便获得R值，因此更为常用。&lt;/p&gt;
&lt;h2 id=&#34;java中的cas操作&#34;&gt;Java中的CAS操作&lt;/h2&gt;
&lt;p&gt;在Java初期，java是无法直接利用这些硬件操作来提升并发性能的，直到后来Java本地方法(JNI)的出现，使得Java直接调用本地方法称为可能，在Doug Lea提供的cucurenct包得以实现。&lt;/p&gt;
&lt;p&gt;在Java中如何实现呢？其实知道了CAS操作的原理，一下就变得简单起来。我们还是以i++操作为例，介绍CAS整个过程：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;读取内存位置V的原值R&lt;/li&gt;
&lt;li&gt;因为进行的是i++操作，因此CAS操作的预期原值A=R、新值B=R+1，进行CAS。&lt;/li&gt;
&lt;li&gt;获取V的原值R&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;如果R等于A，更改成功，操作结束&lt;/li&gt;
&lt;li&gt;如果R不等于A，则没有更改。但是获取当前时刻V的原值，将R重新设置为该值。则预期原值A=R、新值B=R+1，然后在继续进行CVS。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;空口无凭，我们看一下AtomicInteger中i++操作的实现方法getAndIncrement()：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;而getAndAddInt()方法如下：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;我们继续追踪到compareAndSwapInt()方法，分析该方法是一个本地方法。&lt;/p&gt;</description>
    </item>
    <item>
      <title>一致性Hash算法的缩容扩容分析</title>
      <link>https://yeecode.top/blog/18/</link>
      <pubDate>Sun, 16 Dec 2018 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/18/</guid>
      
      <description>&lt;h2 id=&#34;背景&#34;&gt;背景&lt;/h2&gt;
&lt;p&gt;之前的文章我们已经分析过，一致性Hash算法能够在Hash输出空间发生变化时，引起最小的变动。&lt;/p&gt;
&lt;p&gt;以上篇文章的例子来讲，增加或者移除一台服务器时，对原有的服务器和用户之间的映射关系产生的影响最小。&lt;/p&gt;
&lt;p&gt;本文我们讨论使用了一致性Hash算法后，扩容和缩容具体会产生怎样的影响，以及在扩容缩容时应该如何做来减少影响。&lt;/p&gt;
&lt;h2 id=&#34;扩容分析&#34;&gt;扩容分析&lt;/h2&gt;
&lt;p&gt;如果扩容增加一台服务器X，如下图：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/18/pic/203_hu_d41aae35b3b4df32.webp&#34; alt=&#34;图片&#34; width=&#34;741&#34; height=&#34;759&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;我们发现，该扩容对对象A、B、D无影响，只有对象C会被重定位到新的Node X。&lt;/p&gt;
&lt;p&gt;因此，在扩容时，仅仅影响扩容服务器所在的位置的后面一台服务器，原本分配到它身上的请求被分流了，使得它的请求减少。&lt;/p&gt;
&lt;p&gt;而对于新增加的服务器，接入了其后面一台服务器的部分流量。&lt;/p&gt;
&lt;p&gt;在实际生产中，使用了一致性Hash算法后，如果需要增加一台服务器X，只需要将其插入位置后面的服务器C的内容拷贝到服务器X上一份即可。&lt;/p&gt;
&lt;p&gt;更精细化的操作可以将服务器C的数据按照新的Hash输出结果分摊到服务器C和服务器X这两台服务器上，这样可以避免数据冗余，但操作相对繁杂一些。&lt;/p&gt;
&lt;h2 id=&#34;缩容分析&#34;&gt;缩容分析&lt;/h2&gt;
&lt;p&gt;假如服务器C被缩容摘除，则对数据对象A、B、D不会产生任何影响。只有C对象被重定位到服务器D上。&lt;/p&gt;
&lt;p&gt;而事实上，C对象对应的Node C不存在了，因此无论如何C都会受到影响。同时，会对服务器D产生影响，因为原本分配到服务器C上的流量会被转到它的身上，使其压力增大。&lt;/p&gt;
&lt;p&gt;在具体生产操作中，如果需要缩容服务器C，则需要将该服务器C的内容拷贝到其后面的服务器D上。这样，由服务器C转接过来的流量还可以访问到对应的数据。&lt;/p&gt;</description>
    </item>
    <item>
      <title>什么是一致性Hash算法？</title>
      <link>https://yeecode.top/blog/17/</link>
      <pubDate>Sat, 24 Nov 2018 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/17/</guid>
      
      <description>&lt;h2 id=&#34;hash算法&#34;&gt;Hash算法&lt;/h2&gt;
&lt;p&gt;说起Hash算法，我们都是熟悉的，它是一种摘要算法，即根据原有的内容产生一个简短的摘要结果。摘要结果跟原内容是相关的，原内容的改变（极大概率）会导致摘要内容的改变。这里说的极大概率会改变是因为这取决于该Hash算法的输出空间的大小：输出空间小了，则可能出现改变后的Hash结果与原Hash结果恰好相等的情况；而输出空间越大，上述情况发生的概率则越小。&lt;/p&gt;
&lt;p&gt;假设输入输出都是一个数字，那么我们可以定义一个简单的Hash算法：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;private final int size = 7;

public int simpleHash(int value) {
    return value%size;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;当一个值value输入时，该算法会输出0到6之间的自然数。&lt;/p&gt;
&lt;h2 id=&#34;hash算法的使用与问题&#34;&gt;Hash算法的使用与问题&lt;/h2&gt;
&lt;p&gt;Hash算法的使用非常广泛，例如对一段信息生成Hash值后，可以作为校验信息是否发生变更的依据；通过Hash算法，将请求均匀地分配到应用服务器上；使用Hash算法，确保同样的用户总是访问同一个服务器等。&lt;/p&gt;
&lt;p&gt;但是，Hash算法也存在一定的局限性。例如，我们有0-6共7台服务器，通过我们上一节的Hash算法将用户id(自然数)作为输入，得到一个服务器编号。然后将用户的信息近似均匀地分配到这7台服务器上，从而实现负载均衡。&lt;/p&gt;
&lt;p&gt;假如这时5号服务器出现故障，我们要将其下线，于是：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;private final int size = 6;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这时我们发现，用户id输入后，该Hash算法输出的结果与原来相差极大，使得大量用户无法访问到原来的服务器，这并不是我们想见到的。例如，28号用户，原来28%7=0，映射到0号服务器，现在则映射到了28%6=4，映射到了4号服务器。而原来的0号服务器是存在的，该28号用户应依旧分配到0号服务器才对。&lt;/p&gt;
&lt;p&gt;当然，随着用户增多，我们增加一台服务器时，也会引发相同的问题。此时一般需要进行重新Hash操作，对各个服务器上的数据进行重新分配，此操作几乎涉及所有服务器上的所有数据，工作量极大。&lt;/p&gt;
&lt;h2 id=&#34;一致性hash算法&#34;&gt;一致性Hash算法&lt;/h2&gt;
&lt;p&gt;在分布式系统中，扩容缩容操作极为常见，如果频繁进行重Hash操作显然是不可取的，一是消耗太大，而是可能引发服务的中断。此时，就要采用一致性Hash算法。&lt;/p&gt;
&lt;p&gt;一致性Hash算法（Consistent Hashing）是一种hash算法，它能够在Hash输出空间发生变化时，引起最小的变动。以我们的例子来讲，增加或者移除一台服务器时，对原有的服务器和用户之间的映射关系产生的影响最小。&lt;/p&gt;
&lt;p&gt;好的一致性Hash算法应该能够满足以下要求：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;平衡性：这是Hash算法的基本要求，是指哈希的结果均匀地分配在整个输出空间中。&lt;/li&gt;
&lt;li&gt;单调性：当发生数据节点变动时，对于相同的数据始终映射到相同的缓冲节点中或者新增加的缓冲节点中，避免无法找到原来的数据。&lt;/li&gt;
&lt;li&gt;稳定性：当出现节点坏掉或热点访问而需要动态扩容时，尽量减少数据的移动。显然一般的Hash方法不满足这一点。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;原理&#34;&gt;原理&lt;/h2&gt;
&lt;p&gt;一致性哈希算法最早在论文《Consistent Hashing and Random Trees: Distributed Caching Protocols for Relieving Hot Spots on the World Wide Web》中提出。具体思路就是一致性哈希将整个哈希输出空间设置为一个环形区域。例如，该环形区域用数字0-2^32-1表示，沿顺时针方向值不断增大，0与2^32重合。整个哈希空间环如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/17/pic/001_hu_357783e309a80fbd.webp&#34; alt=&#34;图片&#34; width=&#34;537&#34; height=&#34;525&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;接下来，我们将服务器进行Hash。可以使用服务器的编号或者ip等作为输入，得到一个输出值，映射在输出空间的环形区域上。假设它们的位置如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/17/pic/201_hu_ed4f1144147dad9d.webp&#34; alt=&#34;图片&#34; width=&#34;805&#34; height=&#34;795&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;对于用户数据，同样进行Hash操作，映射在环形区域上。然后，让该值沿着顺时针方向移动，遇到的第一个服务器就是它分配的服务器。例如有A、B、C、D四个数据对象，经过一致性哈希计算后，在环空间上的位置如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/17/pic/202_hu_d11fca1816491525.webp&#34; alt=&#34;图片&#34; width=&#34;746&#34; height=&#34;775&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;则根据刚才的规则，数据A会被分配到Node A上，B被分配到Node B上，C被分配到Node C上，D被分配到Node D上。&lt;/p&gt;</description>
    </item>
    <item>
      <title>Java8特性之Optional</title>
      <link>https://yeecode.top/blog/16/</link>
      <pubDate>Sat, 20 Oct 2018 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/16/</guid>
      
      <description>&lt;h2 id=&#34;1-概述&#34;&gt;1 概述&lt;/h2&gt;
&lt;p&gt;Java 8由Oracle公司于2014年3月18日发布，至今已过去数年之久。然而，直到今日仍有许多软件开发者对其相关特性不了解，这可能主要是Java教材更新缓慢的原因。为了使大家对与Java8的特性有全面系统的了解，本公众号将连续几篇文章介绍Java8中的各个特性。&lt;/p&gt;
&lt;p&gt;Java8中新增的特性主要有：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Lambda 表达式 − Lambda允许把函数作为一个方法的参数（函数作为参数传递进方法中。&lt;/li&gt;
&lt;li&gt;方法引用 − 方法引用提供了非常有用的语法，可以直接引用已有Java类或对象（实例）的方法或构造器。与lambda联合使用，方法引用可以使语言的构造更紧凑简洁，减少冗余代码。&lt;/li&gt;
&lt;li&gt;默认方法 − 默认方法就是一个在接口里面有了一个实现的方法。&lt;/li&gt;
&lt;li&gt;新工具 − 新的编译工具，如：Nashorn引擎 jjs、 类依赖分析器jdeps。&lt;/li&gt;
&lt;li&gt;Stream API −新添加的Stream API（java.util.stream） 把真正的函数式编程风格引入到Java中。&lt;/li&gt;
&lt;li&gt;Date Time API − 加强对日期与时间的处理。&lt;/li&gt;
&lt;li&gt;Optional 类 − Optional 类已经成为 Java 8 类库的一部分，用来解决空指针异常。&lt;/li&gt;
&lt;li&gt;Nashorn, JavaScript 引擎 − Java 8提供了一个新的Nashorn javascript引擎，它允许我们在JVM上运行特定的javascript应用。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;本文介绍其中的Optional类。其它特性会在后续文章中陆续介绍。&lt;/p&gt;
&lt;h2 id=&#34;2-optional类&#34;&gt;2 Optional类&lt;/h2&gt;
&lt;p&gt;到目前为止，臭名昭著的空指针异常是导致Java应用程序失败的最常见原因。以前，为了解决空指针异常，Google公司著名的Guava项目引入了Optional类，Guava通过使用检查空值的方式来防止代码污染，它鼓励程序员写更干净的代码。受到Google Guava的启发，Optional类已经成为Java 8类库的一部分。&lt;/p&gt;
&lt;p&gt;Optional实际上是个容器：它可以保存类型T的值，或者仅仅保存null。Optional提供很多有用的方法，这样我们就不用显式进行空值检测。&lt;/p&gt;
&lt;p&gt;常用函数：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;of：为非null的值创建一个Optional。of方法通过工厂方法创建Optional类。需要注意的是，创建对象时传入的参数不能为null。如果传入参数为null，则抛出NullPointerException。因此不经常用。&lt;/li&gt;
&lt;li&gt;ofNullable：为指定的值创建一个Optional，如果指定的值为null，则返回一个空的Optional。&lt;/li&gt;
&lt;li&gt;isPresent：如果值存在返回true，否则返回false。&lt;/li&gt;
&lt;li&gt;ifPresent：如果Optional实例有值则为其调用consumer，否则不做处理&lt;/li&gt;
&lt;li&gt;get：如果Optional有值则将其返回，否则抛出NoSuchElementException。因此也不经常用。&lt;/li&gt;
&lt;li&gt;orElse：如果有值则将其返回，否则返回指定的其它值。&lt;/li&gt;
&lt;li&gt;orElseGet：orElseGet与orElse方法类似，区别在于得到的默认值。orElse方法将传入的字符串作为默认值，orElseGet方法可以接受Supplier接口的实现用来生成默认值&lt;/li&gt;
&lt;li&gt;orElseThrow：如果有值则将其返回，否则抛出supplier接口创建的异常。&lt;/li&gt;
&lt;li&gt;filter：如果有值并且满足断言条件返回包含该值的Optional，否则返回空Optional。&lt;/li&gt;
&lt;li&gt;map：如果有值，则对其执行调用mapping函数得到返回值。如果返回值不为null，则创建包含mapping返回值的Optional作为map方法返回值，否则返回空Optional。&lt;/li&gt;
&lt;li&gt;flatMap：如果有值，为其执行mapping函数返回Optional类型返回值，否则返回空Optional。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;使用Optional，我们就可以把下面这样的代码进行改写。&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;public static String getName(User u) {
    if (u == null)
        return &amp;#34;Unknown&amp;#34;;
    return u.name;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;不过，千万不要改写成这副样子。&lt;/p&gt;</description>
    </item>
    <item>
      <title>Java8特性之流操作</title>
      <link>https://yeecode.top/blog/15/</link>
      <pubDate>Sun, 02 Sep 2018 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/15/</guid>
      
      <description>&lt;h2 id=&#34;1-概述&#34;&gt;1 概述&lt;/h2&gt;
&lt;p&gt;Java 8由Oracle公司于2014年3月18日发布，至今已过去数年之久。然而，直到今日仍有许多软件开发者对其相关特性不了解，这可能主要是Java教材更新缓慢的原因。为了使大家对与Java8的特性有全面系统的了解，本公众号将连续几篇文章介绍Java8中的各个特性。&lt;/p&gt;
&lt;p&gt;Java8中新增的特性主要有：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Lambda 表达式 − Lambda允许把函数作为一个方法的参数（函数作为参数传递进方法中。&lt;/li&gt;
&lt;li&gt;方法引用 − 方法引用提供了非常有用的语法，可以直接引用已有Java类或对象（实例）的方法或构造器。与lambda联合使用，方法引用可以使语言的构造更紧凑简洁，减少冗余代码。&lt;/li&gt;
&lt;li&gt;默认方法 − 默认方法就是一个在接口里面有了一个实现的方法。&lt;/li&gt;
&lt;li&gt;新工具 − 新的编译工具，如：Nashorn引擎 jjs、 类依赖分析器jdeps。&lt;/li&gt;
&lt;li&gt;Stream API −新添加的Stream API（java.util.stream） 把真正的函数式编程风格引入到Java中。&lt;/li&gt;
&lt;li&gt;Date Time API − 加强对日期与时间的处理。&lt;/li&gt;
&lt;li&gt;Optional 类 − Optional 类已经成为 Java 8 类库的一部分，用来解决空指针异常。&lt;/li&gt;
&lt;li&gt;Nashorn, JavaScript 引擎 − Java 8提供了一个新的Nashorn javascript引擎，它允许我们在JVM上运行特定的javascript应用。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;本文介绍其中的Steam操作。其它特性会在后续文章中陆续介绍。&lt;/p&gt;
&lt;h2 id=&#34;2-steam&#34;&gt;2 Steam&lt;/h2&gt;
&lt;h2 id=&#34;21-概述&#34;&gt;2.1 概述&lt;/h2&gt;
&lt;p&gt;Java 8 中的 Stream 是对集合（Collection）对象功能的增强，它专注于对集合对象进行各种非常便利、高效的聚合操作（aggregate operation），或者大批量数据操作 。Stream API 借助于同样新出现的 Lambda 表达式，极大的提高编程效率和程序可读性。&lt;/p&gt;
&lt;p&gt;同时它提供串行和并行两种模式进行汇聚操作，并发模式能够充分利用多核处理器的优势，内部使用 fork/join 并行方式来拆分任务和加速处理过程。通常编写并行代码很难而且容易出错, 但使用 Stream API 无需编写一行多线程的代码，就可以很方便地写出高性能的并发程序。&lt;/p&gt;
&lt;p&gt;在 Java 7 中，如果要发现 type 为 grocery 的所有交易，然后返回以交易值降序排序好的交易 ID 集合，我们需要这样写：&lt;/p&gt;</description>
    </item>
    <item>
      <title>Java8特性之方法引用</title>
      <link>https://yeecode.top/blog/14/</link>
      <pubDate>Sun, 19 Aug 2018 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/14/</guid>
      
      <description>&lt;h2 id=&#34;1-概述&#34;&gt;1 概述&lt;/h2&gt;
&lt;p&gt;Java 8由Oracle公司于2014年3月18日发布，至今已过去数年之久。然而，直到今日仍有许多软件开发者对其相关特性不了解，这可能主要是Java教材更新缓慢的原因。为了使大家对与Java8的特性有全面系统的了解，本公众号将连续几篇文章介绍Java8中的各个特性。&lt;/p&gt;
&lt;p&gt;Java8中新增的特性主要有：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Lambda 表达式 − Lambda允许把函数作为一个方法的参数（函数作为参数传递进方法中。&lt;/li&gt;
&lt;li&gt;方法引用 − 方法引用提供了非常有用的语法，可以直接引用已有Java类或对象（实例）的方法或构造器。与lambda联合使用，方法引用可以使语言的构造更紧凑简洁，减少冗余代码。&lt;/li&gt;
&lt;li&gt;默认方法 − 默认方法就是一个在接口里面有了一个实现的方法。&lt;/li&gt;
&lt;li&gt;新工具 − 新的编译工具，如：Nashorn引擎 jjs、 类依赖分析器jdeps。&lt;/li&gt;
&lt;li&gt;Stream API −新添加的Stream API（java.util.stream） 把真正的函数式编程风格引入到Java中。&lt;/li&gt;
&lt;li&gt;Date Time API − 加强对日期与时间的处理。&lt;/li&gt;
&lt;li&gt;Optional 类 − Optional 类已经成为 Java 8 类库的一部分，用来解决空指针异常。&lt;/li&gt;
&lt;li&gt;Nashorn, JavaScript 引擎 − Java 8提供了一个新的Nashorn javascript引擎，它允许我们在JVM上运行特定的javascript应用。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;本文介绍其中的方法引用。其它特性会在后续文章中陆续介绍。&lt;/p&gt;
&lt;h2 id=&#34;2-方法引用&#34;&gt;2 方法引用&lt;/h2&gt;
&lt;h2 id=&#34;21-概念&#34;&gt;2.1 概念&lt;/h2&gt;
&lt;p&gt;方法引用是用来直接访问类或者实例的已经存在的方法或者构造方法。方法引用提供了一种引用而不执行方法的方式，它需要由兼容的函数式接口构成的目标类型上下文。计算时，方法引用会创建函数式接口的一个实例。&lt;/p&gt;
&lt;p&gt;是不是没有看明白？没事的，我们举例子。&lt;/p&gt;
&lt;h2 id=&#34;22-例子&#34;&gt;2.2 例子&lt;/h2&gt;
&lt;p&gt;举例子，我们有一个对象UserBean。&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;public class UserBean {
    private Integer id;
    private String name;

    public UserBean(Integer id, String name) {
        this.id = id;
        this.name = name;
    }

    public static int compareById(UserBean a, UserBean b) {
        return a.id.compareTo(b.id);
    }
// 省略getter setter
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;然后对其进行排序：&lt;/p&gt;</description>
    </item>
    <item>
      <title>Java8特性之Lambda表达式</title>
      <link>https://yeecode.top/blog/13/</link>
      <pubDate>Sun, 29 Jul 2018 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/13/</guid>
      
      <description>&lt;h2 id=&#34;1-概述&#34;&gt;1 概述&lt;/h2&gt;
&lt;p&gt;Java 8由Oracle公司于2014年3月18日发布，至今已过去数年之久。然而，直到今日仍有许多软件开发者对其相关特性不了解，这可能主要是Java教材更新缓慢的原因。为了使大家对与Java8的特性有全面系统的了解，本公众号将连续几篇文章介绍Java8中的各个特性。&lt;/p&gt;
&lt;p&gt;Java8中新增的特性主要有：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Lambda 表达式 − Lambda允许把函数作为一个方法的参数（函数作为参数传递进方法中。&lt;/li&gt;
&lt;li&gt;方法引用 − 方法引用提供了非常有用的语法，可以直接引用已有Java类或对象（实例）的方法或构造器。与lambda联合使用，方法引用可以使语言的构造更紧凑简洁，减少冗余代码。&lt;/li&gt;
&lt;li&gt;默认方法 − 默认方法就是一个在接口里面有了一个实现的方法。&lt;/li&gt;
&lt;li&gt;新工具 − 新的编译工具，如：Nashorn引擎 jjs、 类依赖分析器jdeps。&lt;/li&gt;
&lt;li&gt;Stream API −新添加的Stream API（java.util.stream） 把真正的函数式编程风格引入到Java中。&lt;/li&gt;
&lt;li&gt;Date Time API − 加强对日期与时间的处理。&lt;/li&gt;
&lt;li&gt;Optional 类 − Optional 类已经成为 Java 8 类库的一部分，用来解决空指针异常。&lt;/li&gt;
&lt;li&gt;Nashorn, JavaScript 引擎 − Java 8提供了一个新的Nashorn javascript引擎，它允许我们在JVM上运行特定的javascript应用。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;本文介绍其中的Lambda表达式。其它特性会在后续文章中陆续介绍。&lt;/p&gt;
&lt;h2 id=&#34;2-lambda表达式&#34;&gt;2 Lambda表达式&lt;/h2&gt;
&lt;p&gt;经常听到一个概念：闭包。闭包就是能够读取其他函数内部变量的函数。例如在javascript中，只有函数内部的子函数才能读取局部变量，所以闭包可以理解成“定义在一个函数内部的函数“。在本质上，闭包是将函数内部和函数外部连接起来的桥梁。&lt;/p&gt;
&lt;p&gt;其实，就是Lambda表达式，Lambda 允许把函数作为一个方法的参数（函数作为参数传递进方法中）。&lt;/p&gt;
&lt;h2 id=&#34;21-出现原因&#34;&gt;2.1 出现原因&lt;/h2&gt;
&lt;p&gt;面向对象式编程就应该纯粹的面向对象，于是经常看到这样的写法： 如果你想写一个方法，那么就必须把它放到一个类里面，然后new出来对象，对象调用这个方法。最大的问题就在于其冗余的语法，有人戏称匿名类型导致了“高度问题”（height problem）： 大多匿名内部类的多行代码中仅有一行在做实际工作。因此JAVA8中就提供了这种“函数式编程”的方法 —— lambda表达式，供我们来更加简明扼要的实现内部匿名类的功能。&lt;/p&gt;
&lt;p&gt;也就是说，你在某处就真的只需要一个能做一件事情的函数而已，连它叫什么名字都无关紧要。 Lambda 表达式就可以用来做这件事。&lt;/p&gt;
&lt;h2 id=&#34;22-语法介绍&#34;&gt;2.2 语法介绍&lt;/h2&gt;
&lt;h3 id=&#34;221-函数式接口&#34;&gt;2.2.1 函数式接口&lt;/h3&gt;
&lt;p&gt;函数式接口（Functional Interface）：定义的一个接口，接口里面必须 有且只有一个抽象方法(可以有默认方法和静态方法) ，这样的接口就成为函数式接口。在可以使用lambda表达式的地方，方法声明时必须包含一个函数式的接口。 &lt;/p&gt;</description>
    </item>
    <item>
      <title>Java8特性之接口方法</title>
      <link>https://yeecode.top/blog/12/</link>
      <pubDate>Sat, 16 Jun 2018 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/12/</guid>
      
      <description>&lt;h2 id=&#34;1-概述&#34;&gt;1 概述&lt;/h2&gt;
&lt;p&gt;Java 8由Oracle公司于2014年3月18日发布，至今已过去数年之久。然而，直到今日仍有许多软件开发者对其相关特性不了解，这可能主要是Java教材更新缓慢的原因。为了使大家对与Java8的特性有全面系统的了解，本公众号将连续几篇文章介绍Java8中的各个特性。&lt;/p&gt;
&lt;p&gt;Java8中新增的特性主要有：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Lambda 表达式 − Lambda允许把函数作为一个方法的参数（函数作为参数传递进方法中。&lt;/li&gt;
&lt;li&gt;方法引用 − 方法引用提供了非常有用的语法，可以直接引用已有Java类或对象（实例）的方法或构造器。与lambda联合使用，方法引用可以使语言的构造更紧凑简洁，减少冗余代码。&lt;/li&gt;
&lt;li&gt;默认方法 − 默认方法就是一个在接口里面有了一个实现的方法。&lt;/li&gt;
&lt;li&gt;新工具 − 新的编译工具，如：Nashorn引擎 jjs、 类依赖分析器jdeps。&lt;/li&gt;
&lt;li&gt;Stream API −新添加的Stream API（java.util.stream） 把真正的函数式编程风格引入到Java中。&lt;/li&gt;
&lt;li&gt;Date Time API − 加强对日期与时间的处理。&lt;/li&gt;
&lt;li&gt;Optional 类 − Optional 类已经成为 Java 8 类库的一部分，用来解决空指针异常。&lt;/li&gt;
&lt;li&gt;Nashorn, JavaScript 引擎 − Java 8提供了一个新的Nashorn javascript引擎，它允许我们在JVM上运行特定的javascript应用。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;本文介绍其中的默认方法。其它特性会在后续文章中陆续介绍。&lt;/p&gt;
&lt;h2 id=&#34;2-接口中含有方法实现&#34;&gt;2 接口中含有方法实现&lt;/h2&gt;
&lt;p&gt;早期的Java版本的接口拥有非常的严格的接口，接口中只能有抽象方法，不能存在实现。抽象方法和实现方法混合的，那叫作抽象类。&lt;/p&gt;
&lt;p&gt;而继承了接口的对象，必须要指定接口的实现。&lt;/p&gt;
&lt;h2 id=&#34;21-默认方法&#34;&gt;2.1 默认方法&lt;/h2&gt;
&lt;p&gt;而Java8的新特性：接口中也可以有方法的默认实现。集成了接口的对象可以不指定默认方法的实现，而直接使用其默认实现。&lt;/p&gt;
&lt;p&gt;我们展开讲一下：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;public class TestBean {
    public static void main(String[] args) {
        Say say = new Say() {
            @Override
            public void sayHi(String name) {
                System.out.println(&amp;#34;hei hei&amp;#34;);
            }
        };
        say.sayHi(&amp;#34;lilei&amp;#34;);
        say.sayHelloWorld();
    }

    public interface Say {
        void sayHi(String name);
        default void sayHelloWorld() {
            System.out.println(&amp;#34;hello world&amp;#34;);
        }
    }
    
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;首先我们看一下Say接口，它本身有两个方法，一个抽象方法sayHi(String name)，另外一个是带有默认实现的sayHelloWorld()。它本身有default关键字声明，然后可以写它默认的实现。&lt;/p&gt;</description>
    </item>
    <item>
      <title>Zookeeper概述</title>
      <link>https://yeecode.top/blog/11/</link>
      <pubDate>Sat, 19 May 2018 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/11/</guid>
      
      <description>&lt;h2 id=&#34;zookeeper简介&#34;&gt;Zookeeper简介&lt;/h2&gt;
&lt;p&gt;Zookeeper是一个高性能的分布式一致系统，在分布式系统中有着广泛的应用。基于它，可以实现分布式同步、配置管理、命名空间管理等众多功能，是分布式系统中常见的基础系统。&lt;/p&gt;
&lt;p&gt;Zookeeper主要用来解决分布式集群中应用系统的一致性问题，它有着树状结构的节点，每个节点均可存储少量的数据。同时，用户可以修改和订阅节点中的内容。分布式系统中的节点通过监控这些数据状态的变化，从而可以达到基于数据的集群管理。 &lt;/p&gt;
&lt;p&gt;从设计模式角度来看，Zookeeper是基于观察者模式实现的。可以把它作为一个信息的中心。使用该服务的生产者和消费者都以Zookeeper中的数据为基准。即：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;生产者可以改变Zookeeper的节点，或者节点上的数据&lt;/li&gt;
&lt;li&gt;消费者通过订阅Zookeeper节点，从而能够在节点变动时收到通知&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;基于这样的机制，将Zookeeper作为信息中心，便可以实现分布式系统中节点状态的最终一致性。&lt;/p&gt;
&lt;h2 id=&#34;特点&#34;&gt;特点&lt;/h2&gt;
&lt;p&gt;Zookeeper具有以下特点：
 &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;最终一致性：客户端不论连接到哪个Zookeeper的哪一个节点，都会收到同一份状态。这是zookeeper最重要的性能。 &lt;/li&gt;
&lt;li&gt;可靠性：Zookeeper集群具有简单、健壮、良好的性能，如果消息m被到一台server接受，那么它将被所有的server接受。 &lt;/li&gt;
&lt;li&gt;实时性：Zookeeper保证client将在一个时间间隔范围内获得server的更新信息，或者server失效的信息。但由于网络延时等原因，Zookeeper不能保证两个client能同时得到刚更新的数据，如果需要最新数据，应该在读数据之前调用sync()接口。 &lt;/li&gt;
&lt;li&gt;等待无关（wait-free）：慢的或者失效的client不得干预快速的client的请求，使得每个client都能有效的等待。 &lt;/li&gt;
&lt;li&gt;原子性：更新只能成功或者失败，没有中间状态。 &lt;/li&gt;
&lt;li&gt;顺序性：包括全局有序和偏序两种：全局有序是指如果在一台server上消息a在消息b前发布，则在所有Server上消息a都将在消息b前被发布；偏序是指如果一个消息b在消息a后被同一个发送者发布，a必将排在b前面。 &lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;zookeeper应用场景&#34;&gt;zookeeper应用场景&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;数据发布与订阅：应用配置集中到节点上，应用启动时主动获取，并在节点上注册一个watcher，每次配置更新都会通知到应用。 &lt;/li&gt;
&lt;li&gt;名空间服务：分布式命名服务，创建一个节点后，节点的路径就是全局唯一的，可以作为全局名称使用。 &lt;/li&gt;
&lt;li&gt;分布式通知/协调：不同的系统都监听同一个节点，一旦有了更新，另一个系统能够收到通知。 &lt;/li&gt;
&lt;li&gt;分布式锁：Zookeeper能保证数据的强一致性，用户任何时候都可以相信集群中每个节点的数据都是相同的。一个用户创建一个节点作为锁，另一个用户检测该节点，如果存在，代表别的用户已经锁住，如果不存在，则可以创建一个节点，代表拥有一个锁。 &lt;/li&gt;
&lt;li&gt;集群管理：每个加入集群的机器都创建一个节点，写入自己的状态。监控父节点的用户会受到通知，进行相应的处理。离开时删除节点，监控父节点的用户同样会收到通知。如图： &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/11/pic/012_hu_f5ddeb2030da23e2.webp&#34; alt=&#34;图片&#34; width=&#34;529&#34; height=&#34;349&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;配置管理：在分布式应用环境中很常见，例如同一个应用系统需要多台节点运行，但是它们运行的应用系统的某些配置项是相同的，如果要修改这些相同的配置项，那么就必须同时修改每台运行这个应用系统的 PC Server，这样非常麻烦而且容易出错。像这样的配置信息完全可以交给 Zookeeper 来管理，将配置信息保存在 Zookeeper 的某个目录节点中，然后将所有需要修改的应用机器监控配置信息的状态，一旦配置信息发生变化，每台应用机器就会收到 Zookeeper 的通知，然后从 Zookeeper 获取新的配置信息应用到系统中。如图： &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/11/pic/013_hu_92bca02291b99ba0.webp&#34; alt=&#34;图片&#34; width=&#34;529&#34; height=&#34;319&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>RPC与动态代理</title>
      <link>https://yeecode.top/blog/10/</link>
      <pubDate>Sat, 21 Apr 2018 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/10/</guid>
      
      <description>&lt;h2 id=&#34;1-rpc-简介&#34;&gt;1 RPC 简介&lt;/h2&gt;
&lt;p&gt;RPC（Remote Procedure Call）—远程过程调用，它是一种通过网络从远程计算机程序上请求服务，而不需要了解底层网络技术的协议。 RPC协议假定某些传输协议的存在，如TCP或UDP，为通信程序之间携带信息数据。 在OSI网络通信模型中，RPC跨越了传输层和应用层。&lt;/p&gt;
&lt;h2 id=&#34;2-rpc实现&#34;&gt;2 RPC实现&lt;/h2&gt;
&lt;p&gt;RPC的实现中一个重要的功能就是动态代理。整个过程如下：&lt;/p&gt;
&lt;p&gt;首先，对于用户方而言：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;只提供接口不提供实现类，所有的接口文件都导出后，调用方放在一个专门目录下&lt;/li&gt;
&lt;li&gt;接口千奇百怪，但是都没有具体实现&lt;/li&gt;
&lt;li&gt;代理要代理掉专门目录下的所有接口，任何对这里接口的调用都被序列化后转到远端&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;假设，我们有一个接口如下：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;public interface RemoteService {
    public String getValue(String input);
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;只有方法名称，也不知道接口在远端的实现类的名称。&lt;/p&gt;
&lt;p&gt;然后，我们的ProxyHandler如下：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;public class ProxyHandler implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(&amp;#34;Proxy Doing.&amp;#34;);
        System.out.println(&amp;#34;proxy :&amp;#34; + proxy.getClass().toString());
        System.out.println(&amp;#34;method :&amp;#34; + method.toGenericString());
        System.out.println(&amp;#34;args :&amp;#34; + args);
        System.out.println(&amp;#34;序列化后去寻找具体实现……&amp;#34;);
        return &amp;#34;Proxy Finish.&amp;#34;;
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;具体使用时这样：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;public void test() {
    try {
       // 生成一个代理对象实例。注意，这里我们只需要接口，不需要知道任何实现类的信息
        RemoteService remoteService =
                (RemoteService) Proxy.newProxyInstance(
                        RemoteService.class.getClassLoader(),
                        new Class[] { RemoteService.class },
                        new ProxyHandler());

        String out2 = RemoteService.getValue(&amp;#34;AA&amp;#34;);
        System.out.println(out2);
    } catch (Throwable ex) {
        ex.printStackTrace();
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这样，就可以实现RPC的代理了。运行结果：&lt;/p&gt;</description>
    </item>
    <item>
      <title>详解Java的序列化和反序列化</title>
      <link>https://yeecode.top/blog/09/</link>
      <pubDate>Sat, 10 Mar 2018 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/09/</guid>
      
      <description>&lt;h2 id=&#34;1-概述&#34;&gt;1. 概述&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;序列化&lt;/strong&gt;：把Java对象转换为字节序列的过程。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;反序列化&lt;/strong&gt;：把字节序列恢复为Java对象的过程。&lt;/p&gt;
&lt;p&gt;对象的序列化主要有两种用途： &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;把对象的字节序列永久地保存到硬盘上，通常存放在一个文件中；（持久化对象） &lt;/li&gt;
&lt;li&gt;在网络上传送对象的字节序列。（网络传输对象）&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;2-使用&#34;&gt;2. 使用&lt;/h2&gt;
&lt;p&gt;要想实现序列化，必须要实现下面两个接口之一：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Serializable ：
&lt;ul&gt;
&lt;li&gt;一个对象想要被序列化，那么它的类就要实现此接口&lt;/li&gt;
&lt;li&gt; 这个对象的所有属性（包括private属性、包括其引用的对象）都可以被序列化和反序列化来保存、传递。&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;不想序列化的字段可以使用transient修饰&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt; 由于Serializable对象完全以它存储的二进制位为基础来构造，因此并不会调用任何构造函数，因此Serializable类无需默认构造函数，但是当Serializable类的父类没有实现Serializable接口时，反序列化过程会调用父类的默认构造函数，因此该父类必需有默认构造函数，否则会抛异常。&lt;/li&gt;
&lt;li&gt;使用transient关键字阻止序列化虽然简单方便，但被它修饰的属性被完全隔离在序列化机制之外，导致了在反序列化时无法获取该属性的值，而通过在需要序列化的对象的Java类里加入writeObject()方法与readObject()方法可以控制如何序列化各属性，甚至完全不序列化某些属性或者加密序列化某些属性。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Externalizable ：
&lt;ul&gt;
&lt;li&gt;他是Serializable接口的子类，用户要实现的writeExternal()和readExternal() 方法，用来决定如何序列化和反序列化&lt;/li&gt;
&lt;li&gt;方法需要自己实现，因此可以指定序列化哪些属性，而transient在这里无效。&lt;/li&gt;
&lt;li&gt;对Externalizable对象反序列化时，会先调用类的无参构造方法，这是有别于默认反序列方式的。如果把类的不带参数的构造方法删除，或者把该构造方法的访问权限设置为private、默认或protected级别，会抛出java.io.InvalidException: no valid constructor异常，因此Externalizable对象必须有默认构造函数，而且必需是public的。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;使用时，你只想隐藏一个属性，比如用户对象user的密码pwd，如果使用Externalizable，并除了pwd之外的每个属性都写在writeExternal()方法里，这样显得麻烦，可以使用Serializable接口，并在要隐藏的属性pwd前面加上transient就可以实现了。如果要定义很多的特殊处理，就可以使用Externalizable。&lt;/p&gt;
&lt;p&gt;当然这里我们有一些疑惑，Serializable 中的writeObject()方法与readObject()方法科可以实现自定义序列化，而Externalizable 中的writeExternal()和readExternal() 方法也可以，他们有什么异同呢？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;readExternal(),writeExternal()两个方法，这两个方法除了方法签名和readObject(),writeObject()两个方法的方法签名不同之外，其方法体完全一样。&lt;/li&gt;
&lt;li&gt;需要指出的是，当使用Externalizable机制反序列化该对象时，程序会使用public的无参构造器创建实例，然后才执行readExternal()方法进行反序列化，因此实现Externalizable的序列化类必须提供public的无参构造。&lt;/li&gt;
&lt;li&gt;虽然实现Externalizable接口能带来一定的性能提升，但由于实现ExternaLizable接口导致了编程复杂度的增加，所以大部分时候都是采用实现Serializable接口方式来实现序列化。&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&#34;3&#34;&gt;
&lt;li&gt;序列化版本&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在序列化过程中，可以控制序列化的版本。该字段为被序列化对象中的serialVersionUID字段。&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;public class User implements Serializable {  
private static final long serialVerisionUID = 1L ;  
...  
}  
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;一个对象数据，在反序列化过程中，如果序列化串中的serialVersionUID与当前对象值不同，则反序列化失败，否则成功。&lt;/p&gt;
&lt;p&gt;如果serialVersionUID没有显式生成，系统就会自动生成一个。生成的输入有：类名、类及其属性修饰符、接口及接口顺序、属性、静态初始化、构造器。任何一项的改变都会导致serialVersionUID变化。&lt;/p&gt;
&lt;p&gt;属性的变化都会导致自动生成的serialVersionUID发生变化。例如，对于对象A，我们生成序列化的S(A)，然后修改A的属性，则此时A的serialVersionUID发生变化。反序列化时，S(A)与A的serialVersionUID不同，无法反序列化。会报序列号版本不一致的错误。&lt;/p&gt;
&lt;p&gt;为了避免这种问题， 一般系统都会要求实现serialiable接口的类显式的生明一个serialVersionUID。显式定义serialVersionUID的两种用途： &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;希望类的不同版本对序列化兼容时，需要确保类的不同版本具有相同的serialVersionUID； &lt;/li&gt;
&lt;li&gt;不希望类的不同版本对序列化兼容时，需要确保类的不同版本具有不同的serialVersionUID。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果我们保持了serialVersionUID的一致，则在反序列化时，对于新增的字段会填入默认值null（int的默认值0）,对于减少的字段则直接忽略。&lt;/p&gt;
&lt;ol start=&#34;4&#34;&gt;
&lt;li&gt;使用举例&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;我们假设有一个对象需要进行序列化：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;public class User implements Serializable {
    
    private static final long serialVersionUID = 25454531454L;
    
    private String name;
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;然后，可以通过以下方式进行序列化和反序列化：&lt;/p&gt;</description>
    </item>
    <item>
      <title>Java中枚举类型（Enum）使用进阶</title>
      <link>https://yeecode.top/blog/08/</link>
      <pubDate>Sat, 17 Feb 2018 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/08/</guid>
      
      <description>&lt;p&gt;本文是Java枚举类型的第三篇文章，对于枚举类型的实现原理和包含的方法特性不了解的小伙伴可以先去参考历史文章中的《Java中的枚举类型（Enum）详解》和《详解Java枚举类型（Enum）中的方法》两篇文章。&lt;/p&gt;
&lt;p&gt;接下来，我们将深入了解枚举类的使用。&lt;/p&gt;
&lt;h2 id=&#34;扩展枚举值与自定义构造函数&#34;&gt;扩展枚举值与自定义构造函数&lt;/h2&gt;
&lt;p&gt;之前，我们讨论枚举类时，主要是针对最简单的枚举类型。每个枚举值只有一个字符串，如：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;public enum Season {
    SPRING, SUMMER, AUTUMN, WINTER
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;但是实际使用中，我们可能想给每个枚举值赋予更多的含义，例如，给每个季节一个中文说明和编码等。&lt;/p&gt;
&lt;p&gt;即实现：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;SPRING(&amp;#34;春天&amp;#34;, 1201),
SUMMER(&amp;#34;夏天&amp;#34;, 1202),
AUTUMN(&amp;#34;秋天&amp;#34;, 1203),
WINTER(&amp;#34;冬天&amp;#34;, 1204);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;那这样的操作是可以的么？答案是肯定的！可以操作。但是，因为最简单的枚举类型调用了默认的构造方法，如果我们要增加新的含义，则需要自己覆盖原来的构造方法。操作如下：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;public enum Season {
    SPRING(&amp;#34;春天&amp;#34;, 1201),
    SUMMER(&amp;#34;夏天&amp;#34;, 1202),
    AUTUMN(&amp;#34;秋天&amp;#34;, 1203),
    WINTER(&amp;#34;冬天&amp;#34;, 1204);

    private String name;
    private Integer code;

    Season(String name, Integer code) {
        this.name = name;
        this.code = code;
    }


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    @Override
    public String toString() {
        return this.name() + &amp;#34;(&amp;#34; + this.code + &amp;#34;)&amp;#34;;
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;我们在枚举类中增加了name/code两个属性，并重新编写了构造方法。实现了我们的要求。&lt;/p&gt;</description>
    </item>
    <item>
      <title>详解Java枚举类型（Enum）中的方法</title>
      <link>https://yeecode.top/blog/07/</link>
      <pubDate>Sat, 20 Jan 2018 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/07/</guid>
      
      <description>&lt;p&gt;在上篇文章中，我们对Java中的枚举类进行了详细的介绍。&lt;/p&gt;
&lt;p&gt;对于Enum还不了解的小伙伴，可以先预习《Java中的枚举类型（Enum）详解》一文。&lt;/p&gt;
&lt;p&gt;通过反编译，我们知道Java枚举类会在编译之后转化为一个继承了java
.lang.Enum的类，而我们定义的每个枚举值都会在类的初始化阶段被实例化为我们所定义的枚举类的一个对象。&lt;/p&gt;
&lt;p&gt;在枚举类被编译之后，有一些方法是编译器在编译阶段写入的，那这些方法有什么特点？枚举类中还有一些继承来的方法，它们又有哪些？枚举类中的枚举值是在编译阶段被创建为对象，那构造函数又在哪？&lt;/p&gt;
&lt;p&gt;这篇文章我们将详细分析。&lt;/p&gt;
&lt;h2 id=&#34;enum抽象类常见方法&#34;&gt;Enum抽象类常见方法&lt;/h2&gt;
&lt;p&gt;我们上篇文章已经讲过，枚举类实际上继承了Enum抽象类，因此Enum抽象类是所有枚举类型的基本类，下面是它的常见方法。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ordinal()方法：该方法获取的是枚举变量在枚举类中声明的顺序，下标从0开始，如日期中的MONDAY在第一个位置，那么MONDAY的ordinal值就是0，如果MONDAY的声明位置发生变化，那么ordinal方法获取到的值也随之变化，注意在大多数情况下我们都不应该首先使用该方法，毕竟它总是变幻莫测的。&lt;/li&gt;
&lt;li&gt;compareTo(E o)方法：则是比较枚举的大小，注意其内部实现是根据每个枚举的ordinal值大小进行比较的。&lt;/li&gt;
&lt;li&gt;﻿name()方法与toString()：几乎是等同的，都是输出变量的字符串形式。&lt;/li&gt;
&lt;li&gt;getDeclaringClass（）： 返回该枚举变量所在的枚举类。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;需要再次说明的是，以上的方法都是Enum抽象类的方法，会被Enum的对象继承，而不是Enum的静态方法。而最终枚举值被实例化成了Enum对象，所以，枚举值拥有以上的方法。&lt;/p&gt;
&lt;p&gt;这一块比较简单，我们直接举例子说明：&lt;/p&gt;
&lt;p&gt;首先我们定义一个最简单的枚举类：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;public enum  Season {
    SPRING,
    SUMMER,
    AUTUMN,
    WINTER
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;之后我们在定义一个附带属性的枚举类：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;public enum StatusEnum {

    DOING(&amp;#34;进行中&amp;#34;, &amp;#34;DOING&amp;#34;),
    DONE(&amp;#34;已完成&amp;#34;, &amp;#34;DONE&amp;#34;),
    CANCELLED(&amp;#34;已取消&amp;#34;, &amp;#34;CANCELLED&amp;#34;);

    private String name;
    private String status;

    StatusEnum(String name, String status) {
        this.name = name;
        this.status = status;
    }

   // 省略name/status的getter/setter
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;接下来，我们写方法进行试验：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;public void doTest() {
    Season[] Seasons = Season.values();
    for (Season Season : Seasons) {
        System.out.print(&amp;#34;toString:&amp;#34; + Season.toString());
        System.out.print(&amp;#34;; name:&amp;#34; + Season.name());
        System.out.println(&amp;#34;; ordinal:&amp;#34; + Season.ordinal());
    }

    System.out.println(&amp;#34;----&amp;#34;);
    System.out.println(Season.valueOf(&amp;#34;AUTUMN&amp;#34;).name());
    System.out.println(&amp;#34;----&amp;#34;);
    Class clazz = Season.SUMMER.getDeclaringClass();
    System.out.println(clazz.getName());
    System.out.println(&amp;#34;----&amp;#34;);
    System.out.println(StatusEnum.CANCELLED.name());// 这是Enum自有的方法
    System.out.println(StatusEnum.CANCELLED.getName()); // 这是我们写的getter方法
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;最后结果：&lt;/p&gt;</description>
    </item>
    <item>
      <title>Java中的枚举类型（Enum）详解</title>
      <link>https://yeecode.top/blog/06/</link>
      <pubDate>Sat, 16 Dec 2017 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/06/</guid>
      
      <description>&lt;h2 id=&#34;枚举类型初探&#34;&gt;枚举类型初探&lt;/h2&gt;
&lt;p&gt;枚举类型，其语法总让人觉着怪怪的，如下：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;public enum Season {
    SPRING, SUMMER, AUTUMN, WINTER
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;简单的一行，就定义了包含四个值的枚举类型，缺总让人觉着语法有点怪异。而在使用时：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;public void test() {
    System.out.println(&amp;#34;spring name:&amp;#34; + Season.SPRING.name());
    System.out.println(&amp;#34;spring ordinal:&amp;#34; + Season.SPRING.ordinal());
    System.out.println(&amp;#34;autumn name:&amp;#34; + Season.AUTUMN.name());
    System.out.println(&amp;#34;autumn ordinal:&amp;#34; + Season.AUTUMN.ordinal());
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;就可以得到输出：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;spring name:SPRING
spring ordinal:0
autumn name:AUTUMN
autumn ordinal:2
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;那我们简单的一行定义中，到底发生了什么？枚举类型在编译时是怎样实现的？它还有着怎样的特点？&lt;/p&gt;
&lt;p&gt;我们将会分几节对Java中的枚举类型进行介绍。&lt;/p&gt;
&lt;h2 id=&#34;枚举产生之前&#34;&gt;枚举产生之前&lt;/h2&gt;
&lt;p&gt;如果不使用枚举，我们要对“春夏秋冬”这四个值分别赋予一个数字，则常见的操作为：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;public class Season {
    public static final int SPRING = 0;
    public static final int SUMMER = 1;
    public static final int AUTUMN = 2;
    public static final int WINTER = 3;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;上述方法定义十分繁琐，而且容易出错。例如我们定义的int数字出现重复，编译器也不会给出任何的警示。同时，这样的操作是实在太频繁了，最终Java 5中增加了枚举类型。&lt;/p&gt;</description>
    </item>
    <item>
      <title>基于反射的动态代理实现</title>
      <link>https://yeecode.top/blog/05/</link>
      <pubDate>Wed, 08 Nov 2017 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/05/</guid>
      
      <description>&lt;h2 id=&#34;1-动态代理&#34;&gt;1 动态代理&lt;/h2&gt;
&lt;p&gt;动态代理的实现方式常用的有两种：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;反射实现：基于反射实现，目标对象必须要有接口。生成的代理类是接口的一个实现类&lt;/li&gt;
&lt;li&gt;Cglib实现：基于字节码实现，效率稍低，但目标对象不需要有接口。生成的代理类是目标类的子类，因此目标类不能是final的。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;2-背景介绍&#34;&gt;2 背景介绍&lt;/h2&gt;
&lt;p&gt;在Java中java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口，通过使用这个类和接口就可以生成动态代理对象，只能针对接口做代理。&lt;/p&gt;
&lt;p&gt;接口和目标类没有任何变化，只不过为了增加难度，我们增加了返回值。&lt;/p&gt;
&lt;p&gt;接口如下，除了增加了方法的返回值外无任何变化：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;public interface UserInterface {
    String sayHello(String name);
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;目标类如下，无任何变化：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;public class User implements UserInterface {
    @Override
    public String sayHello(String name) {
        System.out.println(&amp;#34;hello &amp;#34; + name);
        return &amp;#34;OK&amp;#34;;
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;2-反射调用方法&#34;&gt;2 反射调用方法&lt;/h2&gt;
&lt;p&gt;接下来，我们使用JDK完成动态代理，对于代理类只需要实现InvocationHandler接口即可，并且要实现其中的invoke（）方法。&lt;/p&gt;
&lt;p&gt;invoke（）中需要传入目标对象、目标对象的方法、调用目标所需的参数，传入后，就会直接调用指定对象的指定方法了。&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;public class ProxyHandler&amp;lt;T&amp;gt; implements InvocationHandler {
/**
 * @param proxy  被代理的对象
 * @param method 要调用的方法
 * @param args   方法调用时所需要参数
 * @return
 * @throws Throwable
 */
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    System.out.println(&amp;#34;pre words&amp;#34;);
    Object ans = method.invoke(proxy, args);
    System.out.println(&amp;#34;post words&amp;#34;);
    return ans;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;我们使用举例：&lt;/p&gt;</description>
    </item>
    <item>
      <title>代理模式与静态代理</title>
      <link>https://yeecode.top/blog/04/</link>
      <pubDate>Sat, 14 Oct 2017 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/04/</guid>
      
      <description>&lt;h2 id=&#34;1-代理模式&#34;&gt;1 代理模式&lt;/h2&gt;
&lt;h2 id=&#34;11-定义&#34;&gt;1.1 定义&lt;/h2&gt;
&lt;p&gt;代理模式：对某一个目标对象提供它的代理对象，并且由代理对象控制对原对象的引用。&lt;/p&gt;
&lt;p&gt;例如，我们想访问某个对象A时，不能直接访问，需要由对象A的代理对象A Proxy进行代理。通俗来说，A Proxy我们可以认为是A的助理、中介、对外联络人。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/04/pic/001_hu_f2d46b56a375458b.webp&#34; alt=&#34;图片&#34; width=&#34;677&#34; height=&#34;209&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;12-作用&#34;&gt;1.2 作用&lt;/h2&gt;
&lt;p&gt;隔离作用：可以防止对目标对象的直接访问，实现目标对象与外部的隔离，从而提供安全保障等。例如：在代理中增加权限身份验证。&lt;/p&gt;
&lt;p&gt;扩展功能：代理对象可以在目标对象的基础上增加功能。例如：Java切面操作通过建立代理实现。&lt;/p&gt;
&lt;p&gt;直接替换：代理对象可以直接替换目标对象的功能，带来全新的实现方式。例如：RPC通过建立代理，直接实现了不存在的接口实现（消费者中只有接口，没有实现类，RPC直接把对实现类的访问转走了）。&lt;/p&gt;
&lt;h2 id=&#34;2-静态代理&#34;&gt;2 静态代理&lt;/h2&gt;
&lt;p&gt;静态代理就是按照代理模式书写的代码，其特点是代理类和目标类在代码中是确定的，因此是静态的。&lt;/p&gt;
&lt;p&gt;我们举例子进行介绍。&lt;/p&gt;
&lt;p&gt;首先，写出目标类和代理类共同需要实现的接口，我们假设只有一个打招呼的功能。&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;public interface UserInterface {
    void sayHello(String name);
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;目标类实现如下：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;public class User implements UserInterface {
    @Override
    public void sayHello(String name) {
        System.out.println(&amp;#34;hello &amp;#34; + name);
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;代理类实现如下：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;public class UserProxy implements UserInterface {
    private UserInterface target;

    public UserProxy (UserInterface target) {
        this.target = target;
    }

    @Override
    public void sayHello(String name) {
        System.out.println(&amp;#34;pre words&amp;#34;);
        target.sayHello(name);
        System.out.println(&amp;#34;post words&amp;#34;);
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;测试方法如下：&lt;/p&gt;</description>
    </item>
    <item>
      <title>HashMap源码详解</title>
      <link>https://yeecode.top/blog/01/</link>
      <pubDate>Sat, 23 Sep 2017 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/01/</guid>
      
      <description>&lt;p&gt;HashMap是Java开发中常用的一种数据接口，常用于完成key:value结构的存储。而同时，HashMap又是HashSet、HashTable、ConcurrentHashMap这三种数据结构的基础。&lt;/p&gt;
&lt;p&gt;本篇文章我们详细分析HashMap的源码，后面我们会在此基础上介绍HashSet、HashTable、ConcurrentHashMap的源码，并比较他们与HashMap的异同。&lt;/p&gt;
&lt;h2 id=&#34;1-基本结构&#34;&gt;1 基本结构&lt;/h2&gt;
&lt;p&gt;HashMap使我们非常常用的一种数据结构，常用来存储各种键值数据。同时，它也是最为简单的。这里我们的讲解以Java 1.8 为例。&lt;/p&gt;
&lt;p&gt;HashMap的结构示意图如下：
&lt;img src=&#34;https://yeecode.top/blog/01/pic/001_hu_ac5cae1f185cab9a.webp&#34; alt=&#34;图片&#34; width=&#34;900&#34; height=&#34;431&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;11-动态数组&#34;&gt;1.1 动态数组&lt;/h2&gt;
&lt;p&gt;我们先看最上层。&lt;/p&gt;
&lt;p&gt;通过结构图我们看出，HashMap就是一个可以扩容的动态数组。考虑到扩容的需要，该动态数组有着以下属性：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;capacity：目前数组的长度。为了实现高效的扩容，其值总为2^n的形式。每次扩容后，n会加1，即整个数组的容量变为之前的2倍。该值初始默认值为16。&lt;/li&gt;
&lt;li&gt;loadFactor：负载因子，默认值为 0.75。该值与threshold配合使用。&lt;/li&gt;
&lt;li&gt;threshold：扩容的阈值，等于 capacity * loadFactor。即当数组内达到这么多元素时，会触发数组的扩容。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;12-数组元素&#34;&gt;1.2 数组元素&lt;/h2&gt;
&lt;p&gt;数组中的每个元素是一个Node，它的属性有：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;hash: 当前位置值的hash值&lt;/li&gt;
&lt;li&gt;key：当前位置的键&lt;/li&gt;
&lt;li&gt;value: 当前位置存储的值&lt;/li&gt;
&lt;li&gt;next;下一个Node&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;13-列表或者树&#34;&gt;1.3 列表或者树&lt;/h2&gt;
&lt;p&gt;对于每个Node元素，我们发现它有一个next属性。而通过它，挂载到数组同一个位置的多个Node就组成了列表或者树。&lt;/p&gt;
&lt;p&gt;在Java1.7阶段，是不存在树的，即挂载到数组同一个位置的多个Node通过next属性构成了一个单向链表。&lt;/p&gt;
&lt;p&gt;而在Java1.8中，当单项链表中元素大于等于8时，单项列表会变为一棵树。该树为红黑树。该转化操作是由final void treeifyBin(Node&amp;lt;K,V&amp;gt;[] tab, int hash)函数实现的。&lt;/p&gt;
&lt;p&gt;因为链表的查找时间复杂度为O(N)，而红黑树查找的时间复杂度为 O(logN)。因此，在数组的同一位置挂载的节点较多时，Java1.8的设计会降低时间复杂度。&lt;/p&gt;
&lt;h2 id=&#34;2-初始化操作&#34;&gt;2 初始化操作&lt;/h2&gt;
&lt;p&gt;HashMap的初始化操作非常简单，就是确定initialCapacity,loadFactor的初始值的过程。&lt;/p&gt;
&lt;p&gt;平时，我们调用无参构造函数public HashMap()时，所有的值会采用默认值。即loadFactor=0.75。&lt;/p&gt;
&lt;p&gt;如果我们传入initialCapacity,loadFactor，则会调用下面的方法。&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity &amp;lt; 0)
            throw new IllegalArgumentException(&amp;#34;Illegal initial capacity: &amp;#34; +
                                               initialCapacity);
        if (initialCapacity &amp;gt; MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor &amp;lt;= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException(&amp;#34;Illegal load factor: &amp;#34; +
                                               loadFactor);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
    }
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;但是有一点要注意，在进行初始化操作时，只是初始化了创建数组的相关参数，并没有真正创建动态数组。真正动态数组的创建是在第一次进行数据写入时引发的。&lt;/p&gt;</description>
    </item>
    <item>
      <title>论接口的归属</title>
      <link>https://yeecode.top/blog/03/</link>
      <pubDate>Sat, 23 Sep 2017 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/03/</guid>
      
      <description>&lt;p&gt;接口是系统架构设计中一种常用的抽象形式，它将功能的定义和功能的实现拆分开来。接口的使用也十分广泛，例如依赖倒置原则（Dependence Inversion Principle）便要求程序要依赖抽象接口，而不是依赖于具体的实现。&lt;/p&gt;
&lt;p&gt;在本文我们探讨接口的归属问题，即接口是归属于接口调用方还是接口的实现方。这是一个纯概念性的讨论，但将会对软件的架构设计理念有着重要的意义。&lt;/p&gt;
&lt;h2 id=&#34;1-接口属于实现方&#34;&gt;1. 接口属于实现方？&lt;/h2&gt;
&lt;p&gt;通常，我们认为一个接口属于它的实现方。&lt;/p&gt;
&lt;p&gt;因为，实现和接口共同组成了一个可以对外提供服务的模块整体，如下图所示。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/03/pic/shixian_hu_96e5104e4a285b7f.webp&#34; alt=&#34;接口属于实现方示意图&#34; width=&#34;310&#34; height=&#34;325&#34; loading=&#34;eager&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;这时，接口成为了实现的描述，调用方只需要通过接口便可以了解被调用模块的功能，而不需要分析其具体的实现细节。&lt;/p&gt;
&lt;p&gt;而调用方则被排除在外，成了模块的外部系统。它能做的就是接受接口的定义，在此基础上才能依照接口的定义使用模块。&lt;/p&gt;
&lt;p&gt;按照上述的理解，保证了接口和实现组成的被调用模块的内聚性，但却带来了一个严重的问题：变更传递。当模块发生变化时，接口和实现都会变化，这导致调用方不得不修改代码。当然，实现方也可以只修改实现而不修改接口，但这完全依赖实现方的仁慈。毕竟，接口是属于实现方的。&lt;/p&gt;
&lt;h2 id=&#34;2-接口属于调用方&#34;&gt;2. 接口属于调用方？&lt;/h2&gt;
&lt;p&gt;将接口划归调用方则可以解决变更传递的问题。而且，将接口划归调用方有以下依据。&lt;/p&gt;
&lt;h2 id=&#34;21-接口的契约功能&#34;&gt;2.1 接口的契约功能&lt;/h2&gt;
&lt;p&gt;接口实际可以理解为一个契约，契约的签约方是调用方和实现方。调用方承诺按照接口定义的方式来调用实现方；实现方承诺按照接口定义的方式来服务调用方。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/03/pic/qiyue_hu_73593a5835ab401c.webp&#34; alt=&#34;接口是契约&#34; width=&#34;312&#34; height=&#34;301&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;但是，我们会发现上述契约并不是对等的。如果调用方违背契约，例如修改了方法名或者参数数目，是无法对应到具体实现的，因此，调用方不会违背契约。如果实现方违背契约，例如修改了方法的实现逻辑或者返回值，是很有可能的，因此，实现方可能会违背契约。可见，接口这个契约，主要强调的应该是实现方对调用方的承诺。对于这样的契约应该交由调用方保管。&lt;/p&gt;
&lt;p&gt;或者换一种理解。实现方有修改接口（违背契约）的意愿，如果接口交由实现方管理，则接口很有可能会频繁变动，这就违背了契约精神。而将接口交由实现方管理，则可以保证接口的稳定。&lt;/p&gt;
&lt;p&gt;按照这种思路，接口应该交由调用方管理，即划归调用方。而实现方则必须按照接口的定义来实现具体的功能。&lt;/p&gt;
&lt;h2 id=&#34;22-接口的松耦合功能&#34;&gt;2.2 接口的松耦合功能&lt;/h2&gt;
&lt;p&gt;接口是调用方的桩，这种桩的耦合往往是紧密的，例如下面的一个DAO接口中，显然&lt;code&gt;RoleModel&lt;/code&gt;是来自于调用方的Bean,也就是说接口本身依赖了调用方。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;RoleModel &lt;span style=&#34;color:#a6e22e&#34;&gt;queryByName&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;@Param&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;name&amp;#34;&lt;/span&gt;) String name, &lt;span style=&#34;color:#a6e22e&#34;&gt;@Param&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;appName&amp;#34;&lt;/span&gt;) String appName);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接口是实现的抽象，同样是上述接口，其实现要确保返回值为&lt;code&gt;RoleModel&lt;/code&gt;，方法名为&lt;code&gt;queryByName&lt;/code&gt;，两个入参为&lt;code&gt;String&lt;/code&gt;。这些信息是在接口中定义的，会直接传递给实现方法，是一种强约束。&lt;/p&gt;
&lt;p&gt;因此，我们可以得到下面的图。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/03/pic/yilai_hu_9ace8c48990f83c7.webp&#34; alt=&#34;接口的归属划分&#34; width=&#34;474&#34; height=&#34;425&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;p&gt;这时我们发现：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果将接口划归实现方，如虚线1所示。则实现方和调用方之间存在双向依赖。&lt;/li&gt;
&lt;li&gt;如果将接口划归调用方，如虚线2所示。则调用方单向依赖实现方，实现方受到调用方给出的接口的强约束。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;显然，后者才是我们追求的调用方和实现方的关系。&lt;/p&gt;
&lt;p&gt;按照这种理解，调用方持有了接口，也就持有了它需要的服务。实现方则是服务的提供者，具体是由哪个具体实现提供，这不重要，只要符合调用方提供的接口规范即可。于是，这完美地实现了依赖倒置原则（Dependence Inversion Principle）的要求，避免了实现方变更的向上传递。如图所示。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://yeecode.top/blog/03/pic/diaoyongf_hu_8f179201c924e.webp&#34; alt=&#34;接口属于调用方&#34; width=&#34;615&#34; height=&#34;271&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;3-总结&#34;&gt;3. 总结&lt;/h2&gt;
&lt;p&gt;通常，我们认为接口由实现抽象而来，即在调用方和实现方中，实现方是接口的生成者。然而这种理解是片面的。&lt;/p&gt;
&lt;p&gt;接口，应该像是一种契约、一种需求，由调用方提出，并交由实现方给出具体的实现。这种思路利于保证接口契约的稳定，也利于实现模块间的松耦合。&lt;/p&gt;</description>
    </item>
    <item>
      <title>详解HashMap、HashTable、ConcurrentHashMap、HashSet的异同</title>
      <link>https://yeecode.top/blog/02/</link>
      <pubDate>Sat, 23 Sep 2017 00:00:00 +0000</pubDate>
      <guid>https://yeecode.top/blog/02/</guid>
      
      <description>&lt;p&gt;之前的文章《HashMap源码详解》中我们已经结合Java1.8中HashMap的源码对数据结构、数据存取、数据写入、扩容等操作进行了详细的梳理。&lt;/p&gt;
&lt;p&gt;而HashMap又是HashSet、HashTable、ConcurrentHashMap这三种数据结构的基础。今天的文章我们就在《HashMap源码详解》的基础上，介绍HashSet、HashTable、ConcurrentHashMap的源码，并比较他们与HashMap的异同。&lt;/p&gt;
&lt;h2 id=&#34;1-hashtable&#34;&gt;1 HashTable&lt;/h2&gt;
&lt;p&gt;HashTable和HashMap的关系最近，可以认为是HashMap的线程安全版本。&lt;/p&gt;
&lt;p&gt;我们仍然以Java1.8为例，对HashTable进行分析后发现，其读、写、扩容等操作与HashMap基本一致，但是所有方法都增加了synchronized关键词的修饰，将其变为了同步方法。&lt;/p&gt;
&lt;h2 id=&#34;11-源码分析&#34;&gt;1.1 源码分析&lt;/h2&gt;
&lt;p&gt;我们不再对所有的方法的源码进行一一分析，仅以put方法为例，进行介绍。其供外部公开调用的put方法如下：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;public synchronized V put(K key, V value) {
    // 不允许插入null
    if (value == null) {
        throw new NullPointerException();
    }

    // 这里要确认要插入的元素不是已经存在在动态数组中
    Entry&amp;lt;?,?&amp;gt; tab[] = table;
    // 根据hash计算要插入元素的位置
    int hash = key.hashCode();
    int index = (hash &amp;amp; 0x7FFFFFFF) % tab.length;
    @SuppressWarnings(&amp;#34;unchecked&amp;#34;)
    Entry&amp;lt;K,V&amp;gt; entry = (Entry&amp;lt;K,V&amp;gt;)tab[index];
    for(; entry != null ; entry = entry.next) {
        if ((entry.hash == hash) &amp;amp;&amp;amp; entry.key.equals(key)) {
            V old = entry.value;
            entry.value = value;
            return old;
        }
    }

    // 要插入的元素之前在动态数组中不存在，开始真正插入操作
    addEntry(hash, key, value, index);
    return null;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;对比《HashMap源码详解》中我们分析的HashMap的源码，我们主要发现了两点不同。&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
